Skip to content

Commit 0812983

Browse files
committed
Address some comments
1 parent 43bae11 commit 0812983

File tree

26 files changed

+2074
-2027
lines changed

26 files changed

+2074
-2027
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
using FluentAssertions;
2+
using FluentAssertions.Specialized;
3+
using JsonApiDotNetCore.OpenApi.Client.NSwag;
4+
using Newtonsoft.Json;
5+
using OpenApiNSwagEndToEndTests.ModelStateValidation.GeneratedCode;
6+
using OpenApiTests;
7+
using OpenApiTests.ModelStateValidation;
8+
using TestBuildingBlocks;
9+
using Xunit;
10+
using Xunit.Abstractions;
11+
12+
namespace OpenApiNSwagEndToEndTests.ModelStateValidation;
13+
14+
public sealed class ModelStateValidationTests : IClassFixture<IntegrationTestContext<OpenApiStartup<ModelStateValidationDbContext>, ModelStateValidationDbContext>>
15+
{
16+
private readonly IntegrationTestContext<OpenApiStartup<ModelStateValidationDbContext>, ModelStateValidationDbContext> _testContext;
17+
private readonly XUnitLogHttpMessageHandler _logHttpMessageHandler;
18+
private readonly ModelStateValidationFakers _fakers = new();
19+
20+
public ModelStateValidationTests(IntegrationTestContext<OpenApiStartup<ModelStateValidationDbContext>, ModelStateValidationDbContext> testContext, ITestOutputHelper testOutputHelper)
21+
{
22+
_testContext = testContext;
23+
_logHttpMessageHandler = new XUnitLogHttpMessageHandler(testOutputHelper);
24+
25+
testContext.UseController<SocialMediaAccountsController>();
26+
}
27+
28+
[Fact]
29+
public async Task xxx()
30+
{
31+
// Arrange
32+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
33+
34+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
35+
ModelStateValidationClient apiClient = new(httpClient);
36+
37+
// Act
38+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
39+
{
40+
Data = new SocialMediaAccountDataInPostRequest
41+
{
42+
Attributes = new SocialMediaAccountAttributesInPostRequest()
43+
}
44+
});
45+
46+
// Assert
47+
ExceptionAssertions<JsonSerializationException> assertion = await action.Should().ThrowExactlyAsync<JsonSerializationException>();
48+
assertion.Which.Message.Should().Be("Cannot write a null value for property 'lastName'. Property requires a value. Path 'data.attributes'.");
49+
}
50+
51+
[Theory]
52+
[InlineData("ab")]
53+
[InlineData("abcdefghijklmnopqrs")]
54+
public async Task Cannot_exceed_length_constraint(string userName)
55+
{
56+
// Arrange
57+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
58+
59+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
60+
ModelStateValidationClient apiClient = new(httpClient);
61+
62+
// Act
63+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
64+
{
65+
Data = new SocialMediaAccountDataInPostRequest
66+
{
67+
Attributes = new SocialMediaAccountAttributesInPostRequest
68+
{
69+
LastName = socialMediaAccount.LastName,
70+
UserName = userName
71+
}
72+
}
73+
});
74+
75+
// Assert
76+
ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync<ApiException<ErrorResponseDocument>>()).Which.Result;
77+
document.Errors.ShouldHaveCount(1);
78+
79+
ErrorObject errorObject = document.Errors.First();
80+
errorObject.Title.Should().Be("Input validation failed.");
81+
errorObject.Detail.Should().Be("The field UserName must be a string with a minimum length of 3 and a maximum length of 18.");
82+
errorObject.Source.ShouldNotBeNull();
83+
errorObject.Source.Pointer.Should().Be("/data/attributes/userName");
84+
}
85+
86+
[Fact]
87+
public async Task Cannot_violate_regular_expression_constraint()
88+
{
89+
// Arrange
90+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
91+
92+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
93+
ModelStateValidationClient apiClient = new(httpClient);
94+
95+
// Act
96+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
97+
{
98+
Data = new SocialMediaAccountDataInPostRequest
99+
{
100+
Attributes = new SocialMediaAccountAttributesInPostRequest
101+
{
102+
LastName = socialMediaAccount.LastName,
103+
UserName = "aB1"
104+
}
105+
}
106+
});
107+
108+
// Assert
109+
ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync<ApiException<ErrorResponseDocument>>()).Which.Result;
110+
document.Errors.ShouldHaveCount(1);
111+
112+
ErrorObject errorObject = document.Errors.First();
113+
errorObject.Title.Should().Be("Input validation failed.");
114+
errorObject.Detail.Should().Be("Only letters are allowed.");
115+
errorObject.Source.ShouldNotBeNull();
116+
errorObject.Source.Pointer.Should().Be("/data/attributes/userName");
117+
}
118+
119+
[Fact]
120+
public async Task Cannot_use_invalid_credit_card()
121+
{
122+
// Arrange
123+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
124+
125+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
126+
ModelStateValidationClient apiClient = new(httpClient);
127+
128+
// Act
129+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
130+
{
131+
Data = new SocialMediaAccountDataInPostRequest
132+
{
133+
Attributes = new SocialMediaAccountAttributesInPostRequest
134+
{
135+
LastName = socialMediaAccount.LastName,
136+
CreditCard = "123-456"
137+
}
138+
}
139+
});
140+
141+
// Assert
142+
ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync<ApiException<ErrorResponseDocument>>()).Which.Result;
143+
document.Errors.ShouldHaveCount(1);
144+
145+
ErrorObject errorObject = document.Errors.First();
146+
errorObject.Title.Should().Be("Input validation failed.");
147+
errorObject.Detail.Should().Be("The CreditCard field is not a valid credit card number.");
148+
errorObject.Source.ShouldNotBeNull();
149+
errorObject.Source.Pointer.Should().Be("/data/attributes/creditCard");
150+
}
151+
152+
[Fact]
153+
public async Task Cannot_use_invalid_email()
154+
{
155+
// Arrange
156+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
157+
158+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
159+
ModelStateValidationClient apiClient = new(httpClient);
160+
161+
// Act
162+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
163+
{
164+
Data = new SocialMediaAccountDataInPostRequest
165+
{
166+
Attributes = new SocialMediaAccountAttributesInPostRequest
167+
{
168+
LastName = socialMediaAccount.LastName,
169+
Email = "abc"
170+
}
171+
}
172+
});
173+
174+
// Assert
175+
ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync<ApiException<ErrorResponseDocument>>()).Which.Result;
176+
document.Errors.ShouldHaveCount(1);
177+
178+
ErrorObject errorObject = document.Errors.First();
179+
errorObject.Title.Should().Be("Input validation failed.");
180+
errorObject.Detail.Should().Be("The Email field is not a valid e-mail address.");
181+
errorObject.Source.ShouldNotBeNull();
182+
errorObject.Source.Pointer.Should().Be("/data/attributes/email");
183+
}
184+
185+
[Theory]
186+
[InlineData(-1)]
187+
[InlineData(-0.56)]
188+
[InlineData(123.98)]
189+
[InlineData(124)]
190+
public async Task Cannot_use_double_outside_of_valid_range(int age)
191+
{
192+
// Arrange
193+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
194+
195+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
196+
ModelStateValidationClient apiClient = new(httpClient);
197+
198+
// Act
199+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
200+
{
201+
Data = new SocialMediaAccountDataInPostRequest
202+
{
203+
Attributes = new SocialMediaAccountAttributesInPostRequest
204+
{
205+
LastName = socialMediaAccount.LastName,
206+
Age = age
207+
}
208+
}
209+
});
210+
211+
// Assert
212+
ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync<ApiException<ErrorResponseDocument>>()).Which.Result;
213+
document.Errors.ShouldHaveCount(1);
214+
215+
ErrorObject errorObject = document.Errors.First();
216+
errorObject.Title.Should().Be("Input validation failed.");
217+
errorObject.Detail.Should().Be("The field Age must be between 0 and 123.");
218+
errorObject.Source.ShouldNotBeNull();
219+
errorObject.Source.Pointer.Should().Be("/data/attributes/age");
220+
}
221+
222+
[Fact]
223+
public async Task Cannot_use_invalid_url()
224+
{
225+
// Arrange
226+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
227+
228+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
229+
ModelStateValidationClient apiClient = new(httpClient);
230+
231+
// Act
232+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
233+
{
234+
Data = new SocialMediaAccountDataInPostRequest
235+
{
236+
Attributes = new SocialMediaAccountAttributesInPostRequest
237+
{
238+
LastName = socialMediaAccount.LastName,
239+
BackgroundPicture = new Uri("/justapath", UriKind.Relative)
240+
}
241+
}
242+
});
243+
244+
// Assert
245+
ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync<ApiException<ErrorResponseDocument>>()).Which.Result;
246+
document.Errors.ShouldHaveCount(1);
247+
248+
ErrorObject errorObject = document.Errors.First();
249+
errorObject.Title.Should().Be("Input validation failed.");
250+
errorObject.Detail.Should().Be("The BackgroundPicture field is not a valid fully-qualified http, https, or ftp URL.");
251+
errorObject.Source.ShouldNotBeNull();
252+
errorObject.Source.Pointer.Should().Be("/data/attributes/backgroundPicture");
253+
}
254+
255+
[Fact]
256+
public async Task Cannot_use_TimeSpan_outside_of_valid_range()
257+
{
258+
// Arrange
259+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
260+
261+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
262+
ModelStateValidationClient apiClient = new(httpClient);
263+
264+
// Act
265+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
266+
{
267+
Data = new SocialMediaAccountDataInPostRequest
268+
{
269+
Attributes = new SocialMediaAccountAttributesInPostRequest
270+
{
271+
LastName = socialMediaAccount.LastName,
272+
NextRevalidation = "00:00:01"
273+
}
274+
}
275+
});
276+
277+
// Assert
278+
ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync<ApiException<ErrorResponseDocument>>()).Which.Result;
279+
document.Errors.ShouldHaveCount(1);
280+
281+
ErrorObject errorObject = document.Errors.First();
282+
errorObject.Title.Should().Be("Input validation failed.");
283+
errorObject.Detail.Should().Be("The field NextRevalidation must be between 01:00:00 and 05:00:00.");
284+
errorObject.Source.ShouldNotBeNull();
285+
errorObject.Source.Pointer.Should().Be("/data/attributes/nextRevalidation");
286+
}
287+
288+
[Fact]
289+
public async Task Cannot_use_invalid_TimeOnly()
290+
{
291+
// Arrange
292+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
293+
294+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
295+
ModelStateValidationClient apiClient = new(httpClient);
296+
297+
// Act
298+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
299+
{
300+
Data = new SocialMediaAccountDataInPostRequest
301+
{
302+
Attributes = new SocialMediaAccountAttributesInPostRequest
303+
{
304+
LastName = socialMediaAccount.LastName,
305+
ValidatedAtTime = TimeSpan.FromSeconds(-1)
306+
}
307+
}
308+
});
309+
310+
// Assert
311+
ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync<ApiException<ErrorResponseDocument>>()).Which.Result;
312+
document.Errors.ShouldHaveCount(1);
313+
314+
ErrorObject errorObject = document.Errors.First();
315+
errorObject.Title.Should().Be("Failed to deserialize request body: Incompatible attribute value found.");
316+
errorObject.Detail.Should().Be("Failed to convert attribute 'validatedAtTime' with value '-00:00:01' of type 'String' to type 'Nullable<TimeOnly>'.");
317+
errorObject.Source.ShouldNotBeNull();
318+
errorObject.Source.Pointer.Should().Be("/data/attributes/validatedAtTime");
319+
}
320+
321+
[Fact]
322+
public async Task Can_create_resource_with_valid_properties()
323+
{
324+
// Arrange
325+
SocialMediaAccount socialMediaAccount = _fakers.SocialMediaAccount.Generate();
326+
327+
using HttpClient httpClient = _testContext.Factory.CreateDefaultClient(_logHttpMessageHandler);
328+
ModelStateValidationClient apiClient = new(httpClient);
329+
330+
// Act
331+
Func<Task<SocialMediaAccountPrimaryResponseDocument>> action = () => apiClient.PostSocialMediaAccountAsync(new SocialMediaAccountPostRequestDocument
332+
{
333+
Data = new SocialMediaAccountDataInPostRequest
334+
{
335+
Attributes = new SocialMediaAccountAttributesInPostRequest
336+
{
337+
FirstName = socialMediaAccount.FirstName,
338+
GivenName = socialMediaAccount.GivenName,
339+
LastName = socialMediaAccount.LastName,
340+
UserName = socialMediaAccount.UserName,
341+
CreditCard = socialMediaAccount.CreditCard,
342+
Email = socialMediaAccount.Email,
343+
Phone = socialMediaAccount.Phone,
344+
Age = socialMediaAccount.Age,
345+
ProfilePicture = socialMediaAccount.ProfilePicture,
346+
BackgroundPicture = new Uri(socialMediaAccount.BackgroundPicture!),
347+
NextRevalidation = "02:00:00",
348+
ValidatedAt = socialMediaAccount.ValidatedAt!,
349+
ValidatedAtDate = new DateTimeOffset(socialMediaAccount.ValidatedAtDate!.Value.ToDateTime(new TimeOnly()).ToUniversalTime()),
350+
ValidatedAtTime = socialMediaAccount.ValidatedAtTime!.Value.ToTimeSpan()
351+
}
352+
}
353+
});
354+
355+
// Assert
356+
await action.Should().NotThrowAsync();
357+
}
358+
}

0 commit comments

Comments
 (0)