From 23576885553f778a06fa2b9ed4c4937c13a4c95c Mon Sep 17 00:00:00 2001 From: Jezz Santos Date: Tue, 19 Sep 2023 15:10:07 +1200 Subject: [PATCH] Code cleanup --- README_PROJECT.md | 6 +- ...thValidatorTestingOnlyRequestValidator.cs} | 6 +- .../Apis/TestingOnly/TestingWebApi.cs | 12 +- src/ApiHost1/HostedModules.cs | 4 +- src/ApiHost1/Resources.resx | 3 +- src/ApiHost1/TestingOnlyApiModule.cs | 7 +- .../Apis/Cars/GetCarRequestValidator.cs | 2 + src/Common.UnitTests/Common.UnitTests.csproj | 8 +- src/Common/Annotations.cs | 101 ++++---- .../TestingWebApiSpec.cs | 36 +-- .../ServiceCollectionExtensionsSpec.cs | 2 +- .../SubDomainModulesSpec.cs | 2 + .../ContentNegotiationFilter.cs | 5 +- .../ISubDomainModule.cs | 1 + .../Resources.resx | 3 +- .../WebApiProjectVisitor.cs | 21 +- .../IWebRequest.cs | 1 + .../GetTestingOnlyExceptionRequest.cs | 7 - .../GetTestingOnlyUnvalidatedRequest.cs | 8 - ... => GetWithValidatorTestingOnlyRequest.cs} | 7 +- .../GetWithoutValidatorTestingOnlyRequest.cs | 11 + ...cs => StringMessageTestingOnlyResponse.cs} | 2 +- .../ThrowsExceptionTestingOnlyRequest.cs | 10 + .../WebApiRouteAttribute.cs | 2 + .../WebApiSpecSetup.cs | 2 +- src/SaaStack.sln.DotSettings | 215 ++++++++++++++++++ .../UnitTesting.Common.csproj | 2 +- 27 files changed, 371 insertions(+), 115 deletions(-) rename src/ApiHost1/Apis/TestingOnly/{GetTestingOnlyValidatedRequestValidator.cs => GetWithValidatorTestingOnlyRequestValidator.cs} (75%) delete mode 100644 src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyExceptionRequest.cs delete mode 100644 src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyUnvalidatedRequest.cs rename src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/{GetTestingOnlyValidatedRequest.cs => GetWithValidatorTestingOnlyRequest.cs} (59%) create mode 100644 src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetWithoutValidatorTestingOnlyRequest.cs rename src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/{GetTestingOnlyResponse.cs => StringMessageTestingOnlyResponse.cs} (68%) create mode 100644 src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/ThrowsExceptionTestingOnlyRequest.cs diff --git a/README_PROJECT.md b/README_PROJECT.md index 55e06c79..f225087c 100644 --- a/README_PROJECT.md +++ b/README_PROJECT.md @@ -8,7 +8,7 @@ You will need the following development tools to build, run, and test this project: -* Jetbrains Rider - obtain a license key for Jetbrains dotUltimate +* Jetbrains Rider - obtain a license key for Jetbrains dotUltimate * Install the .NET7.0 SDK. Available for [download here](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) @@ -120,7 +120,7 @@ When pushed, all branches will be built and tested with GitHub actions 2. Run these tests: -​ In Rider, run all C# tests with Category= `Unit`, `Unit.Architecture` and `Integration.Web` +In Rider, run all C# tests with Category= `Unit`, `Unit.Architecture` and `Integration.Web` > Note: Use `Group By > Category` in Rider's unit test explorer to view these three categories easily. @@ -182,7 +182,7 @@ This is what we modify and how: We run this API production DI code in-process in a Kestrel web server in the same process as your Integration Tests (e.g. in the process of `xunit.console.exe`, not as a separate process). We replace the `appsettings.json` file with the one in your integration testing project -> Which should never ever contain ANY production settings or secrets! +> Which should never ever contain ANY production settings or secrets! We then manipulate the DI container (seen in the `TestStartup.cs` of your integration testing project) and replace certain dependencies (e.g. the 3rd party adapters like: `INotificationsService,` etc) with their respective hardcoded Stubbed equivalents (found in the `Stubs` directory your integration testing project). diff --git a/src/ApiHost1/Apis/TestingOnly/GetTestingOnlyValidatedRequestValidator.cs b/src/ApiHost1/Apis/TestingOnly/GetWithValidatorTestingOnlyRequestValidator.cs similarity index 75% rename from src/ApiHost1/Apis/TestingOnly/GetTestingOnlyValidatedRequestValidator.cs rename to src/ApiHost1/Apis/TestingOnly/GetWithValidatorTestingOnlyRequestValidator.cs index 04623525..901c6f00 100644 --- a/src/ApiHost1/Apis/TestingOnly/GetTestingOnlyValidatedRequestValidator.cs +++ b/src/ApiHost1/Apis/TestingOnly/GetWithValidatorTestingOnlyRequestValidator.cs @@ -1,12 +1,14 @@ #if TESTINGONLY using FluentValidation; using Infrastructure.WebApi.Interfaces.Operations.TestingOnly; +using JetBrains.Annotations; namespace ApiHost1.Apis.TestingOnly; -public class GetTestingOnlyValidatedRequestValidator : AbstractValidator +[UsedImplicitly] +public class GetWithValidatorTestingOnlyRequestValidator : AbstractValidator { - public GetTestingOnlyValidatedRequestValidator() + public GetWithValidatorTestingOnlyRequestValidator() { RuleFor(req => req.Id) .NotEmpty() diff --git a/src/ApiHost1/Apis/TestingOnly/TestingWebApi.cs b/src/ApiHost1/Apis/TestingOnly/TestingWebApi.cs index c28899d3..93cdf995 100644 --- a/src/ApiHost1/Apis/TestingOnly/TestingWebApi.cs +++ b/src/ApiHost1/Apis/TestingOnly/TestingWebApi.cs @@ -7,21 +7,21 @@ namespace ApiHost1.Apis.TestingOnly; public class TestingWebApi : IWebApiService { [WebApiRoute("/testingonly/{id}/unvalidated", WebApiOperation.Get, true)] - public async Task Get(GetTestingOnlyUnvalidatedRequest request, CancellationToken cancellationToken) + public async Task Get(GetWithoutValidatorTestingOnlyRequest request, CancellationToken cancellationToken) { await Task.CompletedTask; - return Results.Ok(new GetTestingOnlyResponse { Message = $"amessage{request.Id}" }); + return Results.Ok(new StringMessageTestingOnlyResponse { Message = $"amessage{request.Id}" }); } [WebApiRoute("/testingonly/{id}/validated", WebApiOperation.Get, true)] - public async Task Get(GetTestingOnlyValidatedRequest request, CancellationToken cancellationToken) + public async Task Get(GetWithValidatorTestingOnlyRequest request, CancellationToken cancellationToken) { await Task.CompletedTask; - return Results.Ok(new GetTestingOnlyResponse { Message = $"amessage{request.Field1}" }); + return Results.Ok(new StringMessageTestingOnlyResponse { Message = $"amessage{request.Field1}" }); } - [WebApiRoute("/testingonly/exception", WebApiOperation.Get, true)] - public async Task Get(GetTestingOnlyExceptionRequest request, CancellationToken cancellationToken) + [WebApiRoute("/testingonly/throws", WebApiOperation.Get, true)] + public async Task Get(ThrowsExceptionTestingOnlyRequest request, CancellationToken cancellationToken) { await Task.CompletedTask; throw new InvalidOperationException("amessage"); diff --git a/src/ApiHost1/HostedModules.cs b/src/ApiHost1/HostedModules.cs index 1ac24df1..6c914810 100644 --- a/src/ApiHost1/HostedModules.cs +++ b/src/ApiHost1/HostedModules.cs @@ -10,8 +10,10 @@ public static SubDomainModules Get() // EXTEND: Add the sub domain of each API, to host in this project. // NOTE: The order of these registrations will matter for some dependencies var modules = new SubDomainModules(); - modules.Register(new CarsApiModule()); +#if TESTINGONLY modules.Register(new TestingOnlyApiModule()); +#endif + modules.Register(new CarsApiModule()); return modules; } diff --git a/src/ApiHost1/Resources.resx b/src/ApiHost1/Resources.resx index 9ce70efb..bcd1a22d 100644 --- a/src/ApiHost1/Resources.resx +++ b/src/ApiHost1/Resources.resx @@ -1,7 +1,8 @@ - diff --git a/src/ApiHost1/TestingOnlyApiModule.cs b/src/ApiHost1/TestingOnlyApiModule.cs index 95874c62..0aabe631 100644 --- a/src/ApiHost1/TestingOnlyApiModule.cs +++ b/src/ApiHost1/TestingOnlyApiModule.cs @@ -1,4 +1,6 @@ +#if TESTINGONLY using System.Reflection; +using ApiHost1.Apis.TestingOnly; using Infrastructure.WebApi.Common; namespace ApiHost1; @@ -15,5 +17,6 @@ public Action RegisterServicesFunction get { return (_, _) => { }; } } - public Assembly ApiAssembly => typeof(Program).Assembly; -} \ No newline at end of file + public Assembly ApiAssembly => typeof(TestingWebApi).Assembly; +} +#endif \ No newline at end of file diff --git a/src/CarsApi/Apis/Cars/GetCarRequestValidator.cs b/src/CarsApi/Apis/Cars/GetCarRequestValidator.cs index f32b851f..ef57fbf7 100644 --- a/src/CarsApi/Apis/Cars/GetCarRequestValidator.cs +++ b/src/CarsApi/Apis/Cars/GetCarRequestValidator.cs @@ -1,8 +1,10 @@ using FluentValidation; using Infrastructure.WebApi.Interfaces.Operations.Cars; +using JetBrains.Annotations; namespace CarsApi.Apis.Cars; +[UsedImplicitly] public class GetCarRequestValidator : AbstractValidator { public GetCarRequestValidator() diff --git a/src/Common.UnitTests/Common.UnitTests.csproj b/src/Common.UnitTests/Common.UnitTests.csproj index 5c322dd4..533523e7 100644 --- a/src/Common.UnitTests/Common.UnitTests.csproj +++ b/src/Common.UnitTests/Common.UnitTests.csproj @@ -3,14 +3,14 @@ net7.0 - + - + - - + + diff --git a/src/Common/Annotations.cs b/src/Common/Annotations.cs index 14c2b35d..dd1cfe04 100644 --- a/src/Common/Annotations.cs +++ b/src/Common/Annotations.cs @@ -149,10 +149,10 @@ public sealed class ItemCanBeNullAttribute : Attribute [Conditional("JETBRAINS_ANNOTATIONS")] public sealed class StringFormatMethodAttribute : Attribute { - /// - /// Specifies which parameter of an annotated method should be treated as the format string. - /// - public StringFormatMethodAttribute([NotNull] string formatParameterName) + /// + /// Specifies which parameter of an annotated method should be treated as the format string. + /// + public StringFormatMethodAttribute([NotNull] string formatParameterName) { FormatParameterName = formatParameterName; } @@ -244,9 +244,6 @@ public ValueProviderAttribute([NotNull] string name) [Conditional("JETBRAINS_ANNOTATIONS")] public sealed class ValueRangeAttribute : Attribute { - public object From { get; } - public object To { get; } - public ValueRangeAttribute(long from, long to) { From = from; @@ -268,6 +265,10 @@ public ValueRangeAttribute(ulong value) { From = To = value; } + + public object From { get; } + + public object To { get; } } /// @@ -706,12 +707,12 @@ public PublicAPIAttribute([NotNull] string comment) [Conditional("JETBRAINS_ANNOTATIONS")] public sealed class InstantHandleAttribute : Attribute { - /// - /// Require the method invocation to be used under the 'await' expression for this attribute to take effect on the code - /// analysis engine. - /// Can be used for delegate/enumerable parameters of 'async' methods. - /// - public bool RequireAwait { get; set; } + /// + /// Require the method invocation to be used under the 'await' expression for this attribute to take effect on the code + /// analysis engine. + /// Can be used for delegate/enumerable parameters of 'async' methods. + /// + public bool RequireAwait { get; set; } } /// @@ -887,28 +888,28 @@ public sealed class SourceTemplateAttribute : Attribute [Conditional("JETBRAINS_ANNOTATIONS")] public sealed class MacroAttribute : Attribute { - /// - /// Allows specifying a macro that will be executed for a source template - /// parameter when the template is expanded. - /// - [CanBeNull] + /// + /// Allows specifying a macro that will be executed for a source template + /// parameter when the template is expanded. + /// + [CanBeNull] public string Expression { get; set; } - /// - /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. - /// - /// - /// If the target parameter is used several times in the template, only one occurrence becomes editable; - /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, - /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. - /// - public int Editable { get; set; } - - /// - /// Identifies the target parameter of a source template if the - /// is applied on a template method. - /// - [CanBeNull] + /// + /// Allows specifying which occurrence of the target parameter becomes editable when the template is deployed. + /// + /// + /// If the target parameter is used several times in the template, only one occurrence becomes editable; + /// other occurrences are changed synchronously. To specify the zero-based index of the editable occurrence, + /// use values >= 0. To make the parameter non-editable when the template is expanded, use -1. + /// + public int Editable { get; set; } + + /// + /// Identifies the target parameter of a source template if the + /// is applied on a template method. + /// + [CanBeNull] public string Target { get; set; } } @@ -1464,12 +1465,12 @@ public AspRequiredAttributeAttribute([NotNull] string attribute) [Conditional("JETBRAINS_ANNOTATIONS")] public sealed class AspTypePropertyAttribute : Attribute { - public bool CreateConstructorReferences { get; } - public AspTypePropertyAttribute(bool createConstructorReferences) { CreateConstructorReferences = createConstructorReferences; } + + public bool CreateConstructorReferences { get; } } #endregion @@ -1798,13 +1799,14 @@ public sealed class RouteTemplateAttribute : Attribute [Conditional("JETBRAINS_ANNOTATIONS")] public sealed class RouteParameterConstraintAttribute : Attribute { - [NotNull] public string ConstraintName { get; } - [CanBeNull] public Type ProposedType { get; set; } - public RouteParameterConstraintAttribute([NotNull] string constraintName) { ConstraintName = constraintName; } + + [NotNull] public string ConstraintName { get; } + + [CanBeNull] public Type ProposedType { get; set; } } /// @@ -2033,6 +2035,7 @@ public RazorPageBaseTypeAttribute([NotNull] string baseType, string pageName) } [NotNull] public string BaseType { get; } + [CanBeNull] public string PageName { get; } } @@ -2154,20 +2157,20 @@ public sealed class XamlTwoWayBindingModeByDefaultAttribute : Attribute [Conditional("JETBRAINS_ANNOTATIONS")] public sealed class TestSubjectAttribute : Attribute { - /// - /// Gets the type of the subject being tested. - /// - [NotNull] - public Type Subject { get; } - - /// - /// Initializes a new instance of the class with the specified subject type. - /// - /// The type of the subject being tested. - public TestSubjectAttribute([NotNull] Type subject) + /// + /// Initializes a new instance of the class with the specified subject type. + /// + /// The type of the subject being tested. + public TestSubjectAttribute([NotNull] Type subject) { Subject = subject; } + + /// + /// Gets the type of the subject being tested. + /// + [NotNull] + public Type Subject { get; } } /// diff --git a/src/Infrastructure.Api.Common.IntegrationTests/TestingWebApiSpec.cs b/src/Infrastructure.Api.Common.IntegrationTests/TestingWebApiSpec.cs index 8a7971ae..837730c6 100644 --- a/src/Infrastructure.Api.Common.IntegrationTests/TestingWebApiSpec.cs +++ b/src/Infrastructure.Api.Common.IntegrationTests/TestingWebApiSpec.cs @@ -19,7 +19,7 @@ public TestingWebApiSpec(WebApplicationFactory factory) : base(factory) } [Fact] - public async Task WhenGetTestingOnlyUnvalidatedRequest_ThenReturns200() + public async Task WhenGetUnvalidatedRequest_ThenReturns200() { var result = await Api.GetAsync("/testingonly/1/unvalidated"); @@ -27,15 +27,15 @@ public async Task WhenGetTestingOnlyUnvalidatedRequest_ThenReturns200() } [Fact] - public async Task WhenGetTestingOnlyUnvalidatedRequest_ThenReturnsJsonByDefault() + public async Task WhenGetUnvalidatedRequest_ThenReturnsJsonByDefault() { - var result = await Api.GetFromJsonAsync("/testingonly/1/unvalidated"); + var result = await Api.GetFromJsonAsync("/testingonly/1/unvalidated"); result?.Message.Should().Be("amessage1"); } [Fact] - public async Task WhenGetTestingOnlyValidatedRequestWithInvalidId_ThenReturnsValidationError() + public async Task WhenGetValidatedRequestWithInvalidId_ThenReturnsValidationError() { var result = await Api.GetAsync("/testingonly/1234/validated"); @@ -56,9 +56,9 @@ public async Task WhenGetTestingOnlyValidatedRequestWithInvalidId_ThenReturnsVal } [Fact] - public async Task WhenGetTestingOnlyException_ThenReturnsServerError() + public async Task WhenGetThrowsException_ThenReturnsServerError() { - var result = await Api.GetAsync("/testingonly/exception"); + var result = await Api.GetAsync("/testingonly/throws"); result.StatusCode.Should().Be(HttpStatusCode.InternalServerError); var json = await result.Content.ReadAsStringAsync(); @@ -69,13 +69,13 @@ public async Task WhenGetTestingOnlyException_ThenReturnsServerError() "\"title\":\"An unexpected error occurred\"," + "\"status\":500," + "\"detail\":\"amessage\"," + - "\"instance\":\"http://localhost/testingonly/exception\"," + + "\"instance\":\"http://localhost/testingonly/throws\"," + "\"exception\":\"System.InvalidOperationException: amessage"); } [Fact] - public async Task WhenGetTestingOnlyWithNoAcceptAndNoFormat_ThenReturnsJsonResponse() + public async Task WhenGetWithNoAcceptAndNoFormat_ThenReturnsJsonResponse() { var result = await Api.GetAsync("/testingonly/1/unvalidated"); @@ -86,7 +86,7 @@ public async Task WhenGetTestingOnlyWithNoAcceptAndNoFormat_ThenReturnsJsonRespo } [Fact] - public async Task WhenGetTestingOnlyWithAcceptForJson_ThenReturnsJsonResponse() + public async Task WhenGetWithAcceptForJson_ThenReturnsJsonResponse() { var request = new HttpRequestMessage { @@ -103,7 +103,7 @@ public async Task WhenGetTestingOnlyWithAcceptForJson_ThenReturnsJsonResponse() } [Fact] - public async Task WhenGetTestingOnlyWithAcceptForXml_ThenReturnsXmlResponse() + public async Task WhenGetWithAcceptForXml_ThenReturnsXmlResponse() { var request = new HttpRequestMessage { @@ -117,13 +117,13 @@ public async Task WhenGetTestingOnlyWithAcceptForXml_ThenReturnsXmlResponse() var content = await result.Content.ReadAsStringAsync(); content.Should().Be($"{Environment.NewLine}" + - $"{Environment.NewLine}" + + $"{Environment.NewLine}" + $" amessage1{Environment.NewLine}" + - ""); + ""); } [Fact] - public async Task WhenGetTestingOnlyWithAcceptForUnsupported_ThenReturns415() + public async Task WhenGetWithAcceptForUnsupported_ThenReturns415() { var request = new HttpRequestMessage { @@ -140,7 +140,7 @@ public async Task WhenGetTestingOnlyWithAcceptForUnsupported_ThenReturns415() } [Fact] - public async Task WhenGetTestingOnlyWithFormatForJson_ThenReturnsJsonResponse() + public async Task WhenGetWithFormatForJson_ThenReturnsJsonResponse() { var result = await Api.GetAsync("/testingonly/1/unvalidated?format=json"); @@ -151,7 +151,7 @@ public async Task WhenGetTestingOnlyWithFormatForJson_ThenReturnsJsonResponse() } [Fact] - public async Task WhenGetTestingOnlyWithFormatForXml_ThenReturnsXmlResponse() + public async Task WhenGetWithFormatForXml_ThenReturnsXmlResponse() { var result = await Api.GetAsync("/testingonly/1/unvalidated?format=xml"); @@ -159,13 +159,13 @@ public async Task WhenGetTestingOnlyWithFormatForXml_ThenReturnsXmlResponse() var content = await result.Content.ReadAsStringAsync(); content.Should().Be($"{Environment.NewLine}" + - $"{Environment.NewLine}" + + $"{Environment.NewLine}" + $" amessage1{Environment.NewLine}" + - ""); + ""); } [Fact] - public async Task WhenGetTestingOnlyWithFormatForUnsupported_ThenReturns415() + public async Task WhenGetWithFormatForUnsupported_ThenReturns415() { var result = await Api.GetAsync("/testingonly/1/unvalidated?format=unsupported"); diff --git a/src/Infrastructure.WebApi.Common.UnitTests/ServiceCollectionExtensionsSpec.cs b/src/Infrastructure.WebApi.Common.UnitTests/ServiceCollectionExtensionsSpec.cs index 7aea48fe..48e35baf 100644 --- a/src/Infrastructure.WebApi.Common.UnitTests/ServiceCollectionExtensionsSpec.cs +++ b/src/Infrastructure.WebApi.Common.UnitTests/ServiceCollectionExtensionsSpec.cs @@ -15,7 +15,7 @@ public void WhenRegisterValidators_ThenRegistersInContainer() { var services = new ServiceCollection(); - services.RegisterValidators(new[] { typeof(ServiceCollectionExtensionsSpec).Assembly }, out var validators); + services.RegisterValidators(new[] { typeof(ServiceCollectionExtensionsSpec).Assembly }, out _); services.Should().ContainSingle(service => service.ImplementationType == typeof(TestRequestValidator)); } diff --git a/src/Infrastructure.WebApi.Common.UnitTests/SubDomainModulesSpec.cs b/src/Infrastructure.WebApi.Common.UnitTests/SubDomainModulesSpec.cs index 339cda03..c38575b9 100644 --- a/src/Infrastructure.WebApi.Common.UnitTests/SubDomainModulesSpec.cs +++ b/src/Infrastructure.WebApi.Common.UnitTests/SubDomainModulesSpec.cs @@ -116,6 +116,8 @@ public TestModule() } public Assembly ApiAssembly { get; init; } + public Action MinimalApiRegistrationFunction { get; init; } + public Action? RegisterServicesFunction { get; init; } } \ No newline at end of file diff --git a/src/Infrastructure.WebApi.Common/ContentNegotiationFilter.cs b/src/Infrastructure.WebApi.Common/ContentNegotiationFilter.cs index 86073315..e1c32c16 100644 --- a/src/Infrastructure.WebApi.Common/ContentNegotiationFilter.cs +++ b/src/Infrastructure.WebApi.Common/ContentNegotiationFilter.cs @@ -70,7 +70,7 @@ public class ContentNegotiationFilter : IEndpointFilter contentType = await ConvertToJsonAsync(value, context, content); break; case NegotiatedMimeType.Xml: - contentType = await ConvertToXmlAsync(value, context, content); + contentType = await ConvertToXmlAsync(value, content); break; default: return Results.StatusCode((int)HttpStatusCode.UnsupportedMediaType); @@ -105,8 +105,7 @@ static JsonSerializerOptions GetJsonOptionsFromRequest(HttpContext httpContext) } } - private static async Task ConvertToXmlAsync(object value, EndpointFilterInvocationContext context, - Stream content) + private static async Task ConvertToXmlAsync(object value, Stream content) { await Task.CompletedTask; var resultType = value.GetType(); diff --git a/src/Infrastructure.WebApi.Common/ISubDomainModule.cs b/src/Infrastructure.WebApi.Common/ISubDomainModule.cs index d4a97207..647f6b95 100644 --- a/src/Infrastructure.WebApi.Common/ISubDomainModule.cs +++ b/src/Infrastructure.WebApi.Common/ISubDomainModule.cs @@ -10,5 +10,6 @@ public interface ISubDomainModule public Assembly ApiAssembly { get; } public Action MinimalApiRegistrationFunction { get; } + public Action? RegisterServicesFunction { get; } } \ No newline at end of file diff --git a/src/Infrastructure.WebApi.Common/Resources.resx b/src/Infrastructure.WebApi.Common/Resources.resx index b1b32e2e..b1f0bb6b 100644 --- a/src/Infrastructure.WebApi.Common/Resources.resx +++ b/src/Infrastructure.WebApi.Common/Resources.resx @@ -1,7 +1,8 @@ - diff --git a/src/Infrastructure.WebApi.Generators/WebApiProjectVisitor.cs b/src/Infrastructure.WebApi.Generators/WebApiProjectVisitor.cs index cd5aae4c..6c06c6c0 100644 --- a/src/Infrastructure.WebApi.Generators/WebApiProjectVisitor.cs +++ b/src/Infrastructure.WebApi.Generators/WebApiProjectVisitor.cs @@ -124,6 +124,8 @@ public override void VisitNamedType(INamedTypeSymbol symbol) nestedType.Accept(this); } + return; + bool IsServiceClass() { if (IsNotClass(symbol)) @@ -355,11 +357,15 @@ bool HasWrongSetOfParameters(IMethodSymbol method) public record ApiServiceOperationRegistration { public required ApiServiceClassRegistration Class { get; set; } + public required string RoutePath { get; set; } + public required WebApiOperation OperationType { get; set; } public required bool IsTestingOnly { get; set; } + public required TypeName RequestDtoType { get; set; } + public string? MethodBody { get; set; } } @@ -374,9 +380,16 @@ public TypeName(string @namespace, string name) } public string FullName => $"{Namespace}.{Name}"; + public string Name { get; } + public string Namespace { get; } + public override int GetHashCode() + { + return HashCode.Combine(Namespace, Name); + } + public virtual bool Equals(TypeName? other) { if (ReferenceEquals(null, other)) @@ -391,23 +404,21 @@ public virtual bool Equals(TypeName? other) return Name == other.Name && Namespace == other.Namespace; } - - public override int GetHashCode() - { - return HashCode.Combine(Namespace, Name); - } } public record ApiServiceClassRegistration { public required TypeName TypeName { get; set; } + public IEnumerable CtorParameters { get; set; } = new List(); + public IEnumerable UsingNamespaces { get; set; } = new List(); } public record ConstructorParameter { public required TypeName TypeName { get; set; } + public required string VariableName { get; set; } } } \ No newline at end of file diff --git a/src/Infrastructure.WebApi.Interfaces/IWebRequest.cs b/src/Infrastructure.WebApi.Interfaces/IWebRequest.cs index 8d4c9c81..efeb5d5c 100644 --- a/src/Infrastructure.WebApi.Interfaces/IWebRequest.cs +++ b/src/Infrastructure.WebApi.Interfaces/IWebRequest.cs @@ -3,6 +3,7 @@ namespace Infrastructure.WebApi.Interfaces; +// ReSharper disable once UnusedTypeParameter public interface IWebRequest : IRequest where TResponse : IWebResponse { } \ No newline at end of file diff --git a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyExceptionRequest.cs b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyExceptionRequest.cs deleted file mode 100644 index 105f1bdb..00000000 --- a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyExceptionRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -#if TESTINGONLY -namespace Infrastructure.WebApi.Interfaces.Operations.TestingOnly; - -public class GetTestingOnlyExceptionRequest : IWebRequest -{ -} -#endif \ No newline at end of file diff --git a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyUnvalidatedRequest.cs b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyUnvalidatedRequest.cs deleted file mode 100644 index 5802bb26..00000000 --- a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyUnvalidatedRequest.cs +++ /dev/null @@ -1,8 +0,0 @@ -#if TESTINGONLY -namespace Infrastructure.WebApi.Interfaces.Operations.TestingOnly; - -public class GetTestingOnlyUnvalidatedRequest : IWebRequest -{ - public string? Id { get; set; } -} -#endif \ No newline at end of file diff --git a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyValidatedRequest.cs b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetWithValidatorTestingOnlyRequest.cs similarity index 59% rename from src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyValidatedRequest.cs rename to src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetWithValidatorTestingOnlyRequest.cs index b7c453a6..14f7dd2b 100644 --- a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyValidatedRequest.cs +++ b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetWithValidatorTestingOnlyRequest.cs @@ -1,10 +1,15 @@ #if TESTINGONLY +using JetBrains.Annotations; + namespace Infrastructure.WebApi.Interfaces.Operations.TestingOnly; -public class GetTestingOnlyValidatedRequest : IWebRequest +[UsedImplicitly] +public class GetWithValidatorTestingOnlyRequest : IWebRequest { public string? Id { get; set; } + public string? Field1 { get; set; } + public string? Field2 { get; set; } } #endif \ No newline at end of file diff --git a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetWithoutValidatorTestingOnlyRequest.cs b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetWithoutValidatorTestingOnlyRequest.cs new file mode 100644 index 00000000..6c9b5c63 --- /dev/null +++ b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetWithoutValidatorTestingOnlyRequest.cs @@ -0,0 +1,11 @@ +#if TESTINGONLY +using JetBrains.Annotations; + +namespace Infrastructure.WebApi.Interfaces.Operations.TestingOnly; + +[UsedImplicitly] +public class GetWithoutValidatorTestingOnlyRequest : IWebRequest +{ + public string? Id { get; set; } +} +#endif \ No newline at end of file diff --git a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyResponse.cs b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/StringMessageTestingOnlyResponse.cs similarity index 68% rename from src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyResponse.cs rename to src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/StringMessageTestingOnlyResponse.cs index eee2ff9a..29014cc5 100644 --- a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/GetTestingOnlyResponse.cs +++ b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/StringMessageTestingOnlyResponse.cs @@ -1,7 +1,7 @@ #if TESTINGONLY namespace Infrastructure.WebApi.Interfaces.Operations.TestingOnly; -public class GetTestingOnlyResponse : IWebResponse +public class StringMessageTestingOnlyResponse : IWebResponse { public string? Message { get; set; } } diff --git a/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/ThrowsExceptionTestingOnlyRequest.cs b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/ThrowsExceptionTestingOnlyRequest.cs new file mode 100644 index 00000000..4cbc5717 --- /dev/null +++ b/src/Infrastructure.WebApi.Interfaces/Operations/TestingOnly/ThrowsExceptionTestingOnlyRequest.cs @@ -0,0 +1,10 @@ +#if TESTINGONLY +using JetBrains.Annotations; + +namespace Infrastructure.WebApi.Interfaces.Operations.TestingOnly; + +[UsedImplicitly] +public class ThrowsExceptionTestingOnlyRequest : IWebRequest +{ +} +#endif \ No newline at end of file diff --git a/src/Infrastructure.WebApi.Interfaces/WebApiRouteAttribute.cs b/src/Infrastructure.WebApi.Interfaces/WebApiRouteAttribute.cs index 9dd5de90..1f9d7da3 100644 --- a/src/Infrastructure.WebApi.Interfaces/WebApiRouteAttribute.cs +++ b/src/Infrastructure.WebApi.Interfaces/WebApiRouteAttribute.cs @@ -20,6 +20,8 @@ public WebApiRouteAttribute([StringSyntax("Route")] string routeTemplate, WebApi } public string RouteTemplate { get; } + public WebApiOperation Operation { get; } + public bool IsTestingOnly { get; } } \ No newline at end of file diff --git a/src/IntegrationTesting.WebApi.Common/WebApiSpecSetup.cs b/src/IntegrationTesting.WebApi.Common/WebApiSpecSetup.cs index f1e611d3..15375266 100644 --- a/src/IntegrationTesting.WebApi.Common/WebApiSpecSetup.cs +++ b/src/IntegrationTesting.WebApi.Common/WebApiSpecSetup.cs @@ -10,7 +10,7 @@ public abstract class WebApiSpecSetup : IClassFixture factory) { Api = factory - .WithWebHostBuilder(builder => builder.ConfigureServices(services => + .WithWebHostBuilder(builder => builder.ConfigureServices(_ => { //TODO: swap out dependencies //services.AddScoped(); diff --git a/src/SaaStack.sln.DotSettings b/src/SaaStack.sln.DotSettings index 6d477230..ad40c7b0 100644 --- a/src/SaaStack.sln.DotSettings +++ b/src/SaaStack.sln.DotSettings @@ -1,8 +1,222 @@  + WARNING + WARNING + WARNING Required Required Required Required + 1 + 1 + 1 + 1 + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="Non-reorderable types"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + <HasAttribute Name="JetBrains.Annotations.NoReorderAttribute" /> + <HasAttribute Name="JetBrains.Annotations.NoReorder" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasMember> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" Inherited="True" /> + <HasAttribute Name="Xunit.TheoryAttribute" Inherited="True" /> + </And> + </HasMember> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <Or> + <Kind Is="Constructor" /> + <And> + <Kind Is="Method" /> + <ImplementsInterface Name="System.IDisposable" /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constructor" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Destructors"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry Priority="200" DisplayName="Dispose Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Name Is="Dispose"/> + </And> + </Entry.Match> + <Entry.SortBy> + <Access /> + </Entry.SortBy> + </Entry> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" /> + <HasAttribute Name="Xunit.TheoryAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members" /> + </TypePattern> + + <TypePattern DisplayName="Default Pattern"> + <Entry Priority="100" DisplayName="Public Delegates"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Delegate" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry Priority="100" DisplayName="Public Enums"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Enum" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Static Fields and Constants"> + <Entry.Match> + <Or> + <Kind Is="Constant" /> + <And> + <Kind Is="Field" /> + <Static /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constant Field" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Fields"> + <Entry.Match> + <And> + <Kind Is="Field" /> + <Not> + <Static /> + </Not> + </And> + </Entry.Match> + <Entry.SortBy> + <Readonly /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Construction Factories"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static/> + <Kind Is="Method" /> + <Name Is="Create"/> + </And> + </Entry.Match> + <Entry.SortBy> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Constructors"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + <Entry.SortBy> + <Static /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Destructors"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry Priority="200" DisplayName="Dispose Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Name Is="Dispose"/> + </And> + </Entry.Match> + <Entry.SortBy> + <Access /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Properties, Indexers"> + <Entry.Match> + <Or> + <Kind Is="Property" /> + <Kind Is="Indexer" /> + </Or> + </Entry.Match> + </Entry> + <Entry DisplayName="Public Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Access Is="Public" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Protected Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Access Is="Protected" /> + </And> + </Entry.Match> + </Entry> + <Entry Priority="100" DisplayName="Interface Implementations"> + <Entry.Match> + <And> + <Kind Is="Member" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <ImplementsInterface Immediate="True" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Nested Types"> + <Entry.Match> + <Kind Is="Type" /> + </Entry.Match> + </Entry> + </TypePattern> +</Patterns> True True A new test class (xUnit) @@ -72,6 +286,7 @@ public void When$condition$_Then$outcome$() { $END$ } + True True True True \ No newline at end of file diff --git a/src/UnitTesting.Common/UnitTesting.Common.csproj b/src/UnitTesting.Common/UnitTesting.Common.csproj index 1b8e115f..1b00fc29 100644 --- a/src/UnitTesting.Common/UnitTesting.Common.csproj +++ b/src/UnitTesting.Common/UnitTesting.Common.csproj @@ -3,7 +3,7 @@ net7.0 - +