Skip to content

Commit

Permalink
Merge pull request #240 from qccoders/develop
Browse files Browse the repository at this point in the history
Prod deploy MVP.08
  • Loading branch information
jpdillingham authored Oct 8, 2018
2 parents ff6ed8c + ea31ca9 commit 96e248e
Show file tree
Hide file tree
Showing 16 changed files with 659 additions and 121 deletions.
76 changes: 76 additions & 0 deletions api/QCVOC.Api.Tests.Unit/GetReadableStringTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
namespace Server.Tests
{
using Microsoft.AspNetCore.Mvc.ModelBinding;
using QCVOC.Api.Common;
using Xunit;

public class GetReadableStringTests
{
[Fact]
public void NullDictionary()
{
ModelStateDictionary modelState = null;
string result = modelState.GetReadableString();

Assert.Equal(string.Empty, result);
}

[Fact]
public void ValidDictionary()
{
ModelStateDictionary modelState = new ModelStateDictionary();
string result = modelState.GetReadableString();

Assert.Equal(string.Empty, result);
}

[Fact]
public void SingleError()
{
ModelStateDictionary modelState = new ModelStateDictionary();
modelState.AddModelError("Name", "The name is too short");
string result = modelState.GetReadableString();

Assert.Equal("Name: The name is too short", result);
}

[Fact]
public void OneKeyTwoErrors()
{
ModelStateDictionary modelState = new ModelStateDictionary();
modelState.AddModelError("Name", "Not capitalized.");
modelState.AddModelError("Name", "Too short.");
string result = modelState.GetReadableString();

Assert.Equal("Name: Not capitalized, Too short", result);
}

[Fact]
public void TwoKeysTwoErrors()
{
ModelStateDictionary modelState = new ModelStateDictionary();
modelState.AddModelError("Name", "Too short.");
modelState.AddModelError("Password", "Too long.");
string result = modelState.GetReadableString();

Assert.Equal("Name: Too short; Password: Too long", result);
}

[Fact]
public void ThreeKeysMultiErrors()
{
ModelStateDictionary modelState = new ModelStateDictionary();
modelState.AddModelError("Name", "Too short.");
modelState.AddModelError("Password", "Too long");
modelState.AddModelError("Password", "Not complex enough.");
modelState.AddModelError("Password", "Already used");
modelState.AddModelError("PrimaryPhone", "Invalid.");
string result = modelState.GetReadableString();

Assert.Equal(
"Name: Too short; "
+ "Password: Too long, Not complex enough, Already used; "
+ "PrimaryPhone: Invalid", result);
}
}
}
4 changes: 4 additions & 0 deletions api/QCVOC.Api.Tests.Unit/QCVOC.Api.Tests.Unit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\QCVOC.Api\QCVOC.Api.csproj" />
</ItemGroup>

</Project>
7 changes: 5 additions & 2 deletions api/QCVOC.Api.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QCVOC.Api", "QCVOC.Api\QCVOC.Api.csproj", "{918F1E13-C3BE-4FFB-9014-7B15FCA4511C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QCVOC.Api.Tests.Unit", "QCVOC.Api.Tests.Unit\QCVOC.Api.Tests.Unit.csproj", "{F2392328-5550-4273-89CD-54C4EF144EF5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QCVOC.Api.Tests.Unit", "QCVOC.Api.Tests.Unit\QCVOC.Api.Tests.Unit.csproj", "{F2392328-5550-4273-89CD-54C4EF144EF5}"
ProjectSection(ProjectDependencies) = postProject
{918F1E13-C3BE-4FFB-9014-7B15FCA4511C} = {918F1E13-C3BE-4FFB-9014-7B15FCA4511C}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QCVOC.Api.Tests.Integration", "QCVOC.Api.Tests.Integration\QCVOC.Api.Tests.Integration.csproj", "{C4BAB5FC-9072-4577-8273-74CF748756C1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QCVOC.Api.Tests.Integration", "QCVOC.Api.Tests.Integration\QCVOC.Api.Tests.Integration.csproj", "{C4BAB5FC-9072-4577-8273-74CF748756C1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
25 changes: 25 additions & 0 deletions api/QCVOC.Api/Common/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ namespace QCVOC.Api.Common
{
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Configuration;

/// <summary>
Expand Down Expand Up @@ -169,5 +171,28 @@ public static T GetSetting<T>(string settingName, T defaultValue)

return (T)Convert.ChangeType(retVal, typeof(T));
}

/// <summary>
/// Converts a ModelStateDictionary into a human-readable format.
/// </summary>
/// <param name="dictionary">The ModelStateDictionary to format.</param>
/// <returns>The formatted error string.</returns>
public static string GetReadableString(this ModelStateDictionary dictionary)
{
if (dictionary == null || dictionary.IsValid)
{
return string.Empty;
}

var fields = dictionary.Keys
.Select(key => (key, dictionary.Root.GetModelStateForProperty(key)))
.Select(field => field.Item1 + ": " +
field.Item2.Errors
.Select(error => error.ErrorMessage)
.Select(error => error.TrimEnd('.'))
.Aggregate((a, b) => a + ", " + b));

return fields.Aggregate((a, b) => a + "; " + b);
}
}
}
21 changes: 16 additions & 5 deletions api/QCVOC.Api/Events/Controller/EventsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ namespace QCVOC.Api.Events.Controller
{
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using QCVOC.Api.Common;
using QCVOC.Api.Common.Data.Repository;
using QCVOC.Api.Events.Data.Model;
Expand Down Expand Up @@ -89,19 +89,30 @@ public IActionResult Get([FromRoute]Guid id)
/// <response code="400">The specified Event was invalid.</response>
/// <response code="401">Unauthorized.</response>
/// <response code="403">The user has insufficient rights to perform this operation.</response>
/// <response code="409">The specified Event conflicts with an existing event.</response>
/// <response code="500">The server encountered an error while processing the request.</response>
[HttpPost("")]
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
[ProducesResponseType(typeof(Event), 201)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(403)]
[ProducesResponseType(409)]
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult Create([FromBody]EventRequest @event)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

var existingEvent = EventRepository
.GetAll(new EventFilters() { Name = @event.Name })
.Any(e => e.StartDate == @event.StartDate && e.EndDate == e.EndDate);

if (existingEvent)
{
return Conflict("The specified Event already exists.");
}

var eventRecord = new Event()
Expand Down Expand Up @@ -142,7 +153,7 @@ public IActionResult Create([FromBody]EventRequest @event)
[HttpPut("{id}")]
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
[ProducesResponseType(typeof(Event), 200)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(403)]
[ProducesResponseType(404)]
Expand All @@ -151,7 +162,7 @@ public IActionResult Update([FromRoute]Guid id, [FromBody]EventRequest @event)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

var eventToUpdate = EventRepository.Get(id);
Expand Down
4 changes: 2 additions & 2 deletions api/QCVOC.Api/Scans/Controller/ScansController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public IActionResult GetAll([FromQuery]ScanFilters filters)
[Authorize]
[ProducesResponseType(typeof(Scan), 200)]
[ProducesResponseType(typeof(Scan), 201)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(403)]
[ProducesResponseType(404)]
Expand All @@ -92,7 +92,7 @@ public IActionResult Scan([FromBody]ScanRequest scan)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

var @event = EventRepository.Get((Guid)scan.EventId);
Expand Down
12 changes: 6 additions & 6 deletions api/QCVOC.Api/Security/Controller/SecurityController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ public IActionResult CheckCredentials()
[HttpPost("login")]
[AllowAnonymous]
[ProducesResponseType(typeof(TokenResponse), 200)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult Login([FromBody]TokenRequest credentials)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

var accountRecord = AccountRepository.GetAll()
Expand Down Expand Up @@ -280,7 +280,7 @@ public IActionResult Get([FromRoute]Guid id)
[HttpPost("accounts")]
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
[ProducesResponseType(typeof(AccountResponse), 201)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(typeof(string), 403)]
[ProducesResponseType(typeof(string), 409)]
Expand All @@ -289,7 +289,7 @@ public IActionResult Create([FromBody]AccountCreateRequest account)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

if ((account.Role == Role.Administrator || account.Role == Role.Supervisor) && !User.IsInRole(nameof(Role.Administrator)))
Expand Down Expand Up @@ -342,7 +342,7 @@ public IActionResult Create([FromBody]AccountCreateRequest account)
[HttpPatch("accounts/{id}")]
[Authorize]
[ProducesResponseType(typeof(AccountResponse), 200)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(typeof(string), 403)]
[ProducesResponseType(404)]
Expand All @@ -352,7 +352,7 @@ public IActionResult Update([FromRoute]Guid id, [FromBody]AccountUpdateRequest a
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

var accountToUpdate = AccountRepository.Get(id);
Expand Down
8 changes: 4 additions & 4 deletions api/QCVOC.Api/Services/Controller/ServicesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ public IActionResult Get([FromRoute]Guid id)
[HttpPost("")]
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
[ProducesResponseType(typeof(Service), 201)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(409)]
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult Create([FromBody]ServiceAddRequest service)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

var existingService = ServiceRepository.GetAll(new ServiceFilters()
Expand Down Expand Up @@ -153,7 +153,7 @@ public IActionResult Create([FromBody]ServiceAddRequest service)
[HttpPut("{id}")]
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
[ProducesResponseType(typeof(Service), 200)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(404)]
[ProducesResponseType(typeof(string), 409)]
Expand All @@ -162,7 +162,7 @@ public IActionResult Update([FromRoute]Guid id, [FromBody]ServiceUpdateRequest s
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

var serviceToUpdate = ServiceRepository.Get(id);
Expand Down
8 changes: 4 additions & 4 deletions api/QCVOC.Api/Veterans/Controller/VeteransController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ public IActionResult Get([FromRoute]Guid id)
[HttpPost("")]
[Authorize]
[ProducesResponseType(typeof(Veteran), 201)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(409)]
[ProducesResponseType(typeof(Exception), 500)]
public IActionResult Enroll([FromBody]VeteranEnrollRequest veteran)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

var existingVeteran = Enumerable.Empty<Veteran>();
Expand Down Expand Up @@ -173,7 +173,7 @@ public IActionResult Enroll([FromBody]VeteranEnrollRequest veteran)
/// <response code="500">The server encountered an error while processing the request.</response>
[HttpPut("{id}")]
[ProducesResponseType(typeof(Veteran), 200)]
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(401)]
[ProducesResponseType(404)]
[ProducesResponseType(typeof(string), 409)]
Expand All @@ -182,7 +182,7 @@ public IActionResult Update([FromRoute]Guid id, [FromBody]VeteranUpdateRequest v
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
return BadRequest(ModelState.GetReadableString());
}

var veteranToUpdate = VeteranRepository.Get(id);
Expand Down
Loading

0 comments on commit 96e248e

Please sign in to comment.