Skip to content

Commit 96e248e

Browse files
authored
Merge pull request #240 from qccoders/develop
Prod deploy MVP.08
2 parents ff6ed8c + ea31ca9 commit 96e248e

File tree

16 files changed

+659
-121
lines changed

16 files changed

+659
-121
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
namespace Server.Tests
2+
{
3+
using Microsoft.AspNetCore.Mvc.ModelBinding;
4+
using QCVOC.Api.Common;
5+
using Xunit;
6+
7+
public class GetReadableStringTests
8+
{
9+
[Fact]
10+
public void NullDictionary()
11+
{
12+
ModelStateDictionary modelState = null;
13+
string result = modelState.GetReadableString();
14+
15+
Assert.Equal(string.Empty, result);
16+
}
17+
18+
[Fact]
19+
public void ValidDictionary()
20+
{
21+
ModelStateDictionary modelState = new ModelStateDictionary();
22+
string result = modelState.GetReadableString();
23+
24+
Assert.Equal(string.Empty, result);
25+
}
26+
27+
[Fact]
28+
public void SingleError()
29+
{
30+
ModelStateDictionary modelState = new ModelStateDictionary();
31+
modelState.AddModelError("Name", "The name is too short");
32+
string result = modelState.GetReadableString();
33+
34+
Assert.Equal("Name: The name is too short", result);
35+
}
36+
37+
[Fact]
38+
public void OneKeyTwoErrors()
39+
{
40+
ModelStateDictionary modelState = new ModelStateDictionary();
41+
modelState.AddModelError("Name", "Not capitalized.");
42+
modelState.AddModelError("Name", "Too short.");
43+
string result = modelState.GetReadableString();
44+
45+
Assert.Equal("Name: Not capitalized, Too short", result);
46+
}
47+
48+
[Fact]
49+
public void TwoKeysTwoErrors()
50+
{
51+
ModelStateDictionary modelState = new ModelStateDictionary();
52+
modelState.AddModelError("Name", "Too short.");
53+
modelState.AddModelError("Password", "Too long.");
54+
string result = modelState.GetReadableString();
55+
56+
Assert.Equal("Name: Too short; Password: Too long", result);
57+
}
58+
59+
[Fact]
60+
public void ThreeKeysMultiErrors()
61+
{
62+
ModelStateDictionary modelState = new ModelStateDictionary();
63+
modelState.AddModelError("Name", "Too short.");
64+
modelState.AddModelError("Password", "Too long");
65+
modelState.AddModelError("Password", "Not complex enough.");
66+
modelState.AddModelError("Password", "Already used");
67+
modelState.AddModelError("PrimaryPhone", "Invalid.");
68+
string result = modelState.GetReadableString();
69+
70+
Assert.Equal(
71+
"Name: Too short; "
72+
+ "Password: Too long, Not complex enough, Already used; "
73+
+ "PrimaryPhone: Invalid", result);
74+
}
75+
}
76+
}

api/QCVOC.Api.Tests.Unit/QCVOC.Api.Tests.Unit.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,8 @@
3232
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
3333
</ItemGroup>
3434

35+
<ItemGroup>
36+
<ProjectReference Include="..\QCVOC.Api\QCVOC.Api.csproj" />
37+
</ItemGroup>
38+
3539
</Project>

api/QCVOC.Api.sln

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ VisualStudioVersion = 15.0.26730.16
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QCVOC.Api", "QCVOC.Api\QCVOC.Api.csproj", "{918F1E13-C3BE-4FFB-9014-7B15FCA4511C}"
77
EndProject
8-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QCVOC.Api.Tests.Unit", "QCVOC.Api.Tests.Unit\QCVOC.Api.Tests.Unit.csproj", "{F2392328-5550-4273-89CD-54C4EF144EF5}"
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QCVOC.Api.Tests.Unit", "QCVOC.Api.Tests.Unit\QCVOC.Api.Tests.Unit.csproj", "{F2392328-5550-4273-89CD-54C4EF144EF5}"
9+
ProjectSection(ProjectDependencies) = postProject
10+
{918F1E13-C3BE-4FFB-9014-7B15FCA4511C} = {918F1E13-C3BE-4FFB-9014-7B15FCA4511C}
11+
EndProjectSection
912
EndProject
10-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QCVOC.Api.Tests.Integration", "QCVOC.Api.Tests.Integration\QCVOC.Api.Tests.Integration.csproj", "{C4BAB5FC-9072-4577-8273-74CF748756C1}"
13+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QCVOC.Api.Tests.Integration", "QCVOC.Api.Tests.Integration\QCVOC.Api.Tests.Integration.csproj", "{C4BAB5FC-9072-4577-8273-74CF748756C1}"
1114
EndProject
1215
Global
1316
GlobalSection(SolutionConfigurationPlatforms) = preSolution

api/QCVOC.Api/Common/Utility.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ namespace QCVOC.Api.Common
77
{
88
using System;
99
using System.IO;
10+
using System.Linq;
1011
using System.Security.Cryptography;
1112
using System.Text;
13+
using Microsoft.AspNetCore.Mvc.ModelBinding;
1214
using Microsoft.Extensions.Configuration;
1315

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

170172
return (T)Convert.ChangeType(retVal, typeof(T));
171173
}
174+
175+
/// <summary>
176+
/// Converts a ModelStateDictionary into a human-readable format.
177+
/// </summary>
178+
/// <param name="dictionary">The ModelStateDictionary to format.</param>
179+
/// <returns>The formatted error string.</returns>
180+
public static string GetReadableString(this ModelStateDictionary dictionary)
181+
{
182+
if (dictionary == null || dictionary.IsValid)
183+
{
184+
return string.Empty;
185+
}
186+
187+
var fields = dictionary.Keys
188+
.Select(key => (key, dictionary.Root.GetModelStateForProperty(key)))
189+
.Select(field => field.Item1 + ": " +
190+
field.Item2.Errors
191+
.Select(error => error.ErrorMessage)
192+
.Select(error => error.TrimEnd('.'))
193+
.Aggregate((a, b) => a + ", " + b));
194+
195+
return fields.Aggregate((a, b) => a + "; " + b);
196+
}
172197
}
173198
}

api/QCVOC.Api/Events/Controller/EventsController.cs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ namespace QCVOC.Api.Events.Controller
77
{
88
using System;
99
using System.Collections.Generic;
10+
using System.Linq;
1011
using Microsoft.AspNetCore.Authorization;
1112
using Microsoft.AspNetCore.Mvc;
12-
using Microsoft.AspNetCore.Mvc.ModelBinding;
1313
using QCVOC.Api.Common;
1414
using QCVOC.Api.Common.Data.Repository;
1515
using QCVOC.Api.Events.Data.Model;
@@ -89,19 +89,30 @@ public IActionResult Get([FromRoute]Guid id)
8989
/// <response code="400">The specified Event was invalid.</response>
9090
/// <response code="401">Unauthorized.</response>
9191
/// <response code="403">The user has insufficient rights to perform this operation.</response>
92+
/// <response code="409">The specified Event conflicts with an existing event.</response>
9293
/// <response code="500">The server encountered an error while processing the request.</response>
9394
[HttpPost("")]
9495
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
9596
[ProducesResponseType(typeof(Event), 201)]
96-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
97+
[ProducesResponseType(typeof(string), 400)]
9798
[ProducesResponseType(401)]
99+
[ProducesResponseType(403)]
98100
[ProducesResponseType(409)]
99101
[ProducesResponseType(typeof(Exception), 500)]
100102
public IActionResult Create([FromBody]EventRequest @event)
101103
{
102104
if (!ModelState.IsValid)
103105
{
104-
return BadRequest(ModelState);
106+
return BadRequest(ModelState.GetReadableString());
107+
}
108+
109+
var existingEvent = EventRepository
110+
.GetAll(new EventFilters() { Name = @event.Name })
111+
.Any(e => e.StartDate == @event.StartDate && e.EndDate == e.EndDate);
112+
113+
if (existingEvent)
114+
{
115+
return Conflict("The specified Event already exists.");
105116
}
106117

107118
var eventRecord = new Event()
@@ -142,7 +153,7 @@ public IActionResult Create([FromBody]EventRequest @event)
142153
[HttpPut("{id}")]
143154
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
144155
[ProducesResponseType(typeof(Event), 200)]
145-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
156+
[ProducesResponseType(typeof(string), 400)]
146157
[ProducesResponseType(401)]
147158
[ProducesResponseType(403)]
148159
[ProducesResponseType(404)]
@@ -151,7 +162,7 @@ public IActionResult Update([FromRoute]Guid id, [FromBody]EventRequest @event)
151162
{
152163
if (!ModelState.IsValid)
153164
{
154-
return BadRequest(ModelState);
165+
return BadRequest(ModelState.GetReadableString());
155166
}
156167

157168
var eventToUpdate = EventRepository.Get(id);

api/QCVOC.Api/Scans/Controller/ScansController.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public IActionResult GetAll([FromQuery]ScanFilters filters)
8383
[Authorize]
8484
[ProducesResponseType(typeof(Scan), 200)]
8585
[ProducesResponseType(typeof(Scan), 201)]
86-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
86+
[ProducesResponseType(typeof(string), 400)]
8787
[ProducesResponseType(401)]
8888
[ProducesResponseType(403)]
8989
[ProducesResponseType(404)]
@@ -92,7 +92,7 @@ public IActionResult Scan([FromBody]ScanRequest scan)
9292
{
9393
if (!ModelState.IsValid)
9494
{
95-
return BadRequest(ModelState);
95+
return BadRequest(ModelState.GetReadableString());
9696
}
9797

9898
var @event = EventRepository.Get((Guid)scan.EventId);

api/QCVOC.Api/Security/Controller/SecurityController.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,14 @@ public IActionResult CheckCredentials()
7676
[HttpPost("login")]
7777
[AllowAnonymous]
7878
[ProducesResponseType(typeof(TokenResponse), 200)]
79-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
79+
[ProducesResponseType(typeof(string), 400)]
8080
[ProducesResponseType(401)]
8181
[ProducesResponseType(typeof(Exception), 500)]
8282
public IActionResult Login([FromBody]TokenRequest credentials)
8383
{
8484
if (!ModelState.IsValid)
8585
{
86-
return BadRequest(ModelState);
86+
return BadRequest(ModelState.GetReadableString());
8787
}
8888

8989
var accountRecord = AccountRepository.GetAll()
@@ -280,7 +280,7 @@ public IActionResult Get([FromRoute]Guid id)
280280
[HttpPost("accounts")]
281281
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
282282
[ProducesResponseType(typeof(AccountResponse), 201)]
283-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
283+
[ProducesResponseType(typeof(string), 400)]
284284
[ProducesResponseType(401)]
285285
[ProducesResponseType(typeof(string), 403)]
286286
[ProducesResponseType(typeof(string), 409)]
@@ -289,7 +289,7 @@ public IActionResult Create([FromBody]AccountCreateRequest account)
289289
{
290290
if (!ModelState.IsValid)
291291
{
292-
return BadRequest(ModelState);
292+
return BadRequest(ModelState.GetReadableString());
293293
}
294294

295295
if ((account.Role == Role.Administrator || account.Role == Role.Supervisor) && !User.IsInRole(nameof(Role.Administrator)))
@@ -342,7 +342,7 @@ public IActionResult Create([FromBody]AccountCreateRequest account)
342342
[HttpPatch("accounts/{id}")]
343343
[Authorize]
344344
[ProducesResponseType(typeof(AccountResponse), 200)]
345-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
345+
[ProducesResponseType(typeof(string), 400)]
346346
[ProducesResponseType(401)]
347347
[ProducesResponseType(typeof(string), 403)]
348348
[ProducesResponseType(404)]
@@ -352,7 +352,7 @@ public IActionResult Update([FromRoute]Guid id, [FromBody]AccountUpdateRequest a
352352
{
353353
if (!ModelState.IsValid)
354354
{
355-
return BadRequest(ModelState);
355+
return BadRequest(ModelState.GetReadableString());
356356
}
357357

358358
var accountToUpdate = AccountRepository.Get(id);

api/QCVOC.Api/Services/Controller/ServicesController.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,15 @@ public IActionResult Get([FromRoute]Guid id)
9595
[HttpPost("")]
9696
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
9797
[ProducesResponseType(typeof(Service), 201)]
98-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
98+
[ProducesResponseType(typeof(string), 400)]
9999
[ProducesResponseType(401)]
100100
[ProducesResponseType(409)]
101101
[ProducesResponseType(typeof(Exception), 500)]
102102
public IActionResult Create([FromBody]ServiceAddRequest service)
103103
{
104104
if (!ModelState.IsValid)
105105
{
106-
return BadRequest(ModelState);
106+
return BadRequest(ModelState.GetReadableString());
107107
}
108108

109109
var existingService = ServiceRepository.GetAll(new ServiceFilters()
@@ -153,7 +153,7 @@ public IActionResult Create([FromBody]ServiceAddRequest service)
153153
[HttpPut("{id}")]
154154
[Authorize(Roles = nameof(Role.Administrator) + "," + nameof(Role.Supervisor))]
155155
[ProducesResponseType(typeof(Service), 200)]
156-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
156+
[ProducesResponseType(typeof(string), 400)]
157157
[ProducesResponseType(401)]
158158
[ProducesResponseType(404)]
159159
[ProducesResponseType(typeof(string), 409)]
@@ -162,7 +162,7 @@ public IActionResult Update([FromRoute]Guid id, [FromBody]ServiceUpdateRequest s
162162
{
163163
if (!ModelState.IsValid)
164164
{
165-
return BadRequest(ModelState);
165+
return BadRequest(ModelState.GetReadableString());
166166
}
167167

168168
var serviceToUpdate = ServiceRepository.Get(id);

api/QCVOC.Api/Veterans/Controller/VeteransController.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,15 @@ public IActionResult Get([FromRoute]Guid id)
9595
[HttpPost("")]
9696
[Authorize]
9797
[ProducesResponseType(typeof(Veteran), 201)]
98-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
98+
[ProducesResponseType(typeof(string), 400)]
9999
[ProducesResponseType(401)]
100100
[ProducesResponseType(409)]
101101
[ProducesResponseType(typeof(Exception), 500)]
102102
public IActionResult Enroll([FromBody]VeteranEnrollRequest veteran)
103103
{
104104
if (!ModelState.IsValid)
105105
{
106-
return BadRequest(ModelState);
106+
return BadRequest(ModelState.GetReadableString());
107107
}
108108

109109
var existingVeteran = Enumerable.Empty<Veteran>();
@@ -173,7 +173,7 @@ public IActionResult Enroll([FromBody]VeteranEnrollRequest veteran)
173173
/// <response code="500">The server encountered an error while processing the request.</response>
174174
[HttpPut("{id}")]
175175
[ProducesResponseType(typeof(Veteran), 200)]
176-
[ProducesResponseType(typeof(ModelStateDictionary), 400)]
176+
[ProducesResponseType(typeof(string), 400)]
177177
[ProducesResponseType(401)]
178178
[ProducesResponseType(404)]
179179
[ProducesResponseType(typeof(string), 409)]
@@ -182,7 +182,7 @@ public IActionResult Update([FromRoute]Guid id, [FromBody]VeteranUpdateRequest v
182182
{
183183
if (!ModelState.IsValid)
184184
{
185-
return BadRequest(ModelState);
185+
return BadRequest(ModelState.GetReadableString());
186186
}
187187

188188
var veteranToUpdate = VeteranRepository.Get(id);

0 commit comments

Comments
 (0)