diff --git a/test/OpenApiEndToEndTests/ModelValidation/ModelValidationTests.cs b/test/OpenApiEndToEndTests/ModelValidation/ModelValidationTests.cs new file mode 100644 index 0000000000..1c2d1fca04 --- /dev/null +++ b/test/OpenApiEndToEndTests/ModelValidation/ModelValidationTests.cs @@ -0,0 +1,382 @@ +using FluentAssertions; +using FluentAssertions.Specialized; +using JsonApiDotNetCore.OpenApi.Client; +using Newtonsoft.Json; +using OpenApiEndToEndTests.ModelValidation.GeneratedCode; +using OpenApiTests; +using OpenApiTests.ModelValidation; +using TestBuildingBlocks; +using Xunit; +using DateTimeOffset = System.DateTimeOffset; +using TimeSpan = OpenApiEndToEndTests.ModelValidation.GeneratedCode.TimeSpan; + +namespace OpenApiEndToEndTests.ModelValidation; + +public sealed class ModelValidationTests : IClassFixture, ModelValidationDbContext>> +{ + private readonly IntegrationTestContext, ModelValidationDbContext> _testContext; + private readonly ModelValidationFakers _fakers = new(); + + public ModelValidationTests(IntegrationTestContext, ModelValidationDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + } + + [Fact] + public async Task Omitting_a_required_attribute_should_return_an_error() + { + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + } + } + }); + + // Assert + ExceptionAssertions assertion = await action.Should().ThrowExactlyAsync(); + assertion.Which.Message.Should().Be("Cannot write a null value for property 'lastName'. Property requires a value. Path 'data.attributes'."); + } + + [Theory] + [InlineData("ab")] + [InlineData("abcdefghijklmnopqrs")] + public async Task imbadathis(string userName) + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + UserName = userName + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be("The field UserName must be a string with a minimum length of 3 and a maximum length of 18."); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/userName"); + } + + [Fact] + public async Task imbadathis2() + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + UserName = "aB1" + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be("Only letters are allowed."); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/userName"); + } + + [Fact] + public async Task imbadathis3() + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + CreditCard = "123-456" + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be("The CreditCard field is not a valid credit card number."); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/creditCard"); + } + + [Fact] + public async Task imbadathis5() + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + Email = "abc" + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be("The Email field is not a valid e-mail address."); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/email"); + } + + [Theory] + [InlineData(-1)] + [InlineData(124)] + public async Task imbadathis6(int age) + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + Age = age + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be("The field Age must be between 0 and 123."); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/age"); + } + + [Fact] + public async Task imbadathis7() + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + ProfilePicture = new Uri("/justapath", UriKind.Relative) + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be("The ProfilePicture field is not a valid fully-qualified http, https, or ftp URL."); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/profilePicture"); + } + + [Fact] + public async Task imbadathis8() + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + NextRevalidation = new TimeSpan { TotalSeconds = 1 } + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be(""); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/nextRevalidation"); + } + + [Fact] + public async Task imbadathis10() + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + ValidatedAt = DateTimeOffset.MinValue + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be(""); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/"); + } + + [Fact] + public async Task imbadathis11() + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + ValidatedDateAt = DateTimeOffset.Now + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be(""); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/"); + } + + [Fact] + public async Task imbadathis9() + { + // Arrange + Fingerprint fingerprint = _fakers.Fingerprint.Generate(); + + using HttpClient httpClient = _testContext.Factory.CreateClient(); + var apiClient = new ModelValidationClient(httpClient); + + // Act + Func> action = () => apiClient.PostFingerprintAsync(null, new FingerprintPostRequestDocument + { + Data = new FingerprintDataInPostRequest + { + Attributes = new FingerprintAttributesInPostRequest + { + LastName = fingerprint.LastName, + ValidatedTimeAt = System.TimeSpan.FromSeconds(-1) + } + } + }); + + // Assert + ErrorResponseDocument document = (await action.Should().ThrowExactlyAsync>()).Which.Result; + document.Errors.ShouldHaveCount(1); + + ErrorObject errorObject = document.Errors.First(); + errorObject.Title.Should().Be("Input validation failed."); + errorObject.Detail.Should().Be(""); + errorObject.Source.ShouldNotBeNull(); + errorObject.Source.Pointer.Should().Be("/data/attributes/"); + } +} diff --git a/test/OpenApiEndToEndTests/ModelValidation/swagger.g.json b/test/OpenApiEndToEndTests/ModelValidation/swagger.g.json index a32512b6d7..909c5cca4f 100644 --- a/test/OpenApiEndToEndTests/ModelValidation/swagger.g.json +++ b/test/OpenApiEndToEndTests/ModelValidation/swagger.g.json @@ -722,12 +722,6 @@ "format": "int32", "nullable": true }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, "profilePicture": { "type": "string", "format": "uri", @@ -755,18 +749,13 @@ "type": "string", "format": "time", "nullable": true - }, - "signature": { - "type": "string", - "nullable": true } }, "additionalProperties": false }, "fingerprintAttributesInPostRequest": { "required": [ - "lastName", - "tags" + "lastName" ], "type": "object", "properties": { @@ -807,12 +796,6 @@ "format": "int32", "nullable": true }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, "profilePicture": { "type": "string", "format": "uri", @@ -840,10 +823,6 @@ "type": "string", "format": "time", "nullable": true - }, - "signature": { - "type": "string", - "nullable": true } }, "additionalProperties": false @@ -888,12 +867,6 @@ "format": "int32", "nullable": true }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, "profilePicture": { "type": "string", "format": "uri", @@ -921,10 +894,6 @@ "type": "string", "format": "time", "nullable": true - }, - "signature": { - "type": "string", - "nullable": true } }, "additionalProperties": false @@ -1201,16 +1170,6 @@ "format": "int32", "readOnly": true }, - "microseconds": { - "type": "integer", - "format": "int32", - "readOnly": true - }, - "nanoseconds": { - "type": "integer", - "format": "int32", - "readOnly": true - }, "minutes": { "type": "integer", "format": "int32", @@ -1236,16 +1195,6 @@ "format": "double", "readOnly": true }, - "totalMicroseconds": { - "type": "number", - "format": "double", - "readOnly": true - }, - "totalNanoseconds": { - "type": "number", - "format": "double", - "readOnly": true - }, "totalMinutes": { "type": "number", "format": "double", diff --git a/test/OpenApiEndToEndTests/OpenApiEndToEndTests.csproj b/test/OpenApiEndToEndTests/OpenApiEndToEndTests.csproj index 6566c17b4b..60ec8bd47a 100644 --- a/test/OpenApiEndToEndTests/OpenApiEndToEndTests.csproj +++ b/test/OpenApiEndToEndTests/OpenApiEndToEndTests.csproj @@ -30,6 +30,13 @@ NSwagCSharp /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /WrapResponses:true /GenerateResponseClasses:false /ResponseClass:ApiResponse /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client /GenerateNullableReferenceTypes:true + + OpenApiEndToEndTests.ModelValidation.GeneratedCode + ModelValidationClient + ModelValidationClient.cs + NSwagCSharp + /ClientClassAccessModifier:internal /GenerateExceptionClasses:false /AdditionalNamespaceUsages:JsonApiDotNetCore.OpenApi.Client /GenerateNullableReferenceTypes:true + OpenApiEndToEndTests.QueryStrings.GeneratedCode QueryStringsClient diff --git a/test/OpenApiTests/ModelValidation/Fingerprint.cs b/test/OpenApiTests/ModelValidation/Fingerprint.cs index 9a0dcbc595..34657c2af7 100644 --- a/test/OpenApiTests/ModelValidation/Fingerprint.cs +++ b/test/OpenApiTests/ModelValidation/Fingerprint.cs @@ -1,24 +1,25 @@ using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; namespace OpenApiTests.ModelValidation; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -[Resource(ControllerNamespace = "OpenApiTests.ModelValidation")] +[Resource(ControllerNamespace = "OpenApiTests.ModelValidation", GenerateControllerEndpoints = JsonApiEndpoints.Post)] public sealed class Fingerprint : Identifiable { [Attr] public string? FirstName { get; set; } [Attr] - [Required(ErrorMessage = "Last name is required")] + [Required(ErrorMessage = "Last name is required.")] public string LastName { get; set; } = default!; [Attr] [StringLength(18, MinimumLength = 3)] - [RegularExpression("^[a-zA-Z]+$", ErrorMessage = "Only letters are allowed")] + [RegularExpression("^[a-zA-Z]+$", ErrorMessage = "Only letters are allowed.")] public string? UserName { get; set; } [Attr] @@ -37,12 +38,6 @@ public sealed class Fingerprint : Identifiable [Range(0, 123)] public int? Age { get; set; } - [Attr] -#if NET8_0_OR_GREATER - [Length(0, 10, ErrorMessage = "{0} length must be between {2} and {1}.")] -#endif - public List Tags { get; set; } = []; - [Attr] [Url] public Uri? ProfilePicture { get; set; } @@ -59,10 +54,4 @@ public sealed class Fingerprint : Identifiable [Attr] public TimeOnly? ValidatedTimeAt { get; set; } - - [Attr] -#if NET8_0_OR_GREATER - [Base64String] -#endif - public string? Signature { get; set; } } diff --git a/test/OpenApiTests/ModelValidation/ModelValidationFakers.cs b/test/OpenApiTests/ModelValidation/ModelValidationFakers.cs index 3b32dd6ce0..f5bf77e216 100644 --- a/test/OpenApiTests/ModelValidation/ModelValidationFakers.cs +++ b/test/OpenApiTests/ModelValidation/ModelValidationFakers.cs @@ -19,13 +19,11 @@ public sealed class ModelValidationFakers : FakerContainer .RuleFor(fingerprint => fingerprint.Email, faker => faker.Person.Email) .RuleFor(fingerprint => fingerprint.Phone, faker => faker.Person.Phone) .RuleFor(fingerprint => fingerprint.Age, faker => faker.Random.Number(0, 123)) - .RuleFor(fingerprint => fingerprint.Tags, faker => faker.Make(faker.Random.Number(0, 10), () => faker.Random.String2(3))) .RuleFor(fingerprint => fingerprint.ProfilePicture, faker => new Uri(faker.Image.LoremFlickrUrl())) .RuleFor(fingerprint => fingerprint.NextRevalidation, faker => TimeSpan.FromMinutes(faker.Random.Number(1, 5))) .RuleFor(fingerprint => fingerprint.ValidatedAt, faker => faker.Date.Recent()) .RuleFor(fingerprint => fingerprint.ValidatedDateAt, faker => DateOnly.FromDateTime(faker.Date.Recent())) - .RuleFor(fingerprint => fingerprint.ValidatedTimeAt, faker => TimeOnly.FromDateTime(faker.Date.Recent())) - .RuleFor(fingerprint => fingerprint.Signature, faker => Convert.ToBase64String(faker.Random.Bytes(10)))); + .RuleFor(fingerprint => fingerprint.ValidatedTimeAt, faker => TimeOnly.FromDateTime(faker.Date.Recent()))); public Faker Fingerprint => _lazyFingerprintFaker.Value; } diff --git a/test/OpenApiTests/ModelValidation/ModelValidationTests.cs b/test/OpenApiTests/ModelValidation/ModelValidationTests.cs index 9129011e96..c96842515c 100644 --- a/test/OpenApiTests/ModelValidation/ModelValidationTests.cs +++ b/test/OpenApiTests/ModelValidation/ModelValidationTests.cs @@ -19,7 +19,7 @@ public ModelValidationTests(OpenApiTestContext + { userNameEl.Should().HaveProperty("pattern", "^[a-zA-Z]+$"); userNameEl.Should().HaveProperty("type", "string"); userNameEl.Should().HaveProperty("nullable", true); @@ -68,7 +83,7 @@ public async Task String_length_and_regex(string modelName) [Theory] [MemberData(nameof(ModelNames))] - public async Task Credit_card(string modelName) + public async Task CreditCard_annotation_on_resource_property_produces_expected_schema(string modelName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -84,7 +99,7 @@ public async Task Credit_card(string modelName) [Theory] [MemberData(nameof(ModelNames))] - public async Task Email(string modelName) + public async Task Email_annotation_on_resource_property_produces_expected_schema(string modelName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -100,7 +115,7 @@ public async Task Email(string modelName) [Theory] [MemberData(nameof(ModelNames))] - public async Task Phone(string modelName) + public async Task Phone_annotation_on_resource_property_produces_expected_schema(string modelName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -116,7 +131,7 @@ public async Task Phone(string modelName) [Theory] [MemberData(nameof(ModelNames))] - public async Task Age(string modelName) + public async Task Range_annotation_on_resource_property_produces_expected_schema(string modelName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -125,7 +140,9 @@ public async Task Age(string modelName) document.Should().ContainPath($"components.schemas.{modelName}.properties.age").With(ageEl => { ageEl.Should().HaveProperty("maximum", 123); + ageEl.Should().NotContainPath("exclusiveMaximum"); ageEl.Should().HaveProperty("minimum", 0); + ageEl.Should().NotContainPath("exclusiveMinimum"); ageEl.Should().HaveProperty("type", "integer"); ageEl.Should().HaveProperty("format", "int32"); ageEl.Should().HaveProperty("nullable", true); @@ -134,26 +151,7 @@ public async Task Age(string modelName) [Theory] [MemberData(nameof(ModelNames))] - public async Task Tags(string modelName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.Should().ContainPath($"components.schemas.{modelName}.properties.tags").With(tagsEl => - { - tagsEl.Should().HaveProperty("type", "array"); - tagsEl.Should().ContainPath("items").With(itemsEl => - { - itemsEl.Should().HaveProperty("type", "string"); - // TODO: no length constraint? - }); - }); - } - - [Theory] - [MemberData(nameof(ModelNames))] - public async Task Profile_picture(string modelName) + public async Task Url_annotation_on_resource_property_produces_expected_schema(string modelName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -169,7 +167,7 @@ public async Task Profile_picture(string modelName) [Theory] [MemberData(nameof(ModelNames))] - public async Task Next_revalidation(string modelName) + public async Task TimeSpan_range_annotation_on_resource_property_produces_expected_schema(string modelName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -184,7 +182,7 @@ public async Task Next_revalidation(string modelName) [Theory] [MemberData(nameof(ModelNames))] - public async Task Validated_at(string modelName) + public async Task DateTime_type_produces_expected_schema(string modelName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -200,7 +198,7 @@ public async Task Validated_at(string modelName) [Theory] [MemberData(nameof(ModelNames))] - public async Task Validated_date_at(string modelName) + public async Task DateOnly_type_produces_expected_schema(string modelName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -216,7 +214,7 @@ public async Task Validated_date_at(string modelName) [Theory] [MemberData(nameof(ModelNames))] - public async Task Validated_time_at(string modelName) + public async Task TimeOnly_type_produces_expected_schema(string modelName) { // Act JsonElement document = await _testContext.GetSwaggerDocumentAsync(); @@ -230,22 +228,6 @@ public async Task Validated_time_at(string modelName) }); } - [Theory] - [MemberData(nameof(ModelNames))] - public async Task Signature(string modelName) - { - // Act - JsonElement document = await _testContext.GetSwaggerDocumentAsync(); - - // Assert - document.Should().ContainPath($"components.schemas.{modelName}.properties.signature").With(signatureEl => - { - signatureEl.Should().HaveProperty("type", "string"); - // TODO: no format? - signatureEl.Should().HaveProperty("nullable", true); - }); - } - public static TheoryData ModelNames => new() {