diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31607fa..b9c8d36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 6.0.x + dotnet-version: 8.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/.gitignore b/.gitignore index dfcfd56..d04cdf4 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,6 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + +# JetBrains folders +.idea/* \ No newline at end of file diff --git a/Api/Api.csproj b/Api/Api.csproj index 9424ce0..cd7a723 100644 --- a/Api/Api.csproj +++ b/Api/Api.csproj @@ -1,16 +1,17 @@  - net6.0 + net8.0 enable true - - - + + + + \ No newline at end of file diff --git a/Api/Domain/Company.cs b/Api/Domain/Company.cs index 23eaa8c..1ae8b2c 100644 --- a/Api/Domain/Company.cs +++ b/Api/Domain/Company.cs @@ -1,13 +1,12 @@ -namespace Api.Domain +namespace Api.Domain; + +public class Company { - public class Company - { - public string Name { get; set; } + public string Name { get; set; } - public string Address { get; set; } + public string Address { get; set; } - public string RegistrationNumber { get; set; } + public string RegistrationNumber { get; set; } - public CompanyStatus CompanyStatus { get; set; } - } -} + public CompanyStatus CompanyStatus { get; set; } +} \ No newline at end of file diff --git a/Api/Domain/CompanyService.cs b/Api/Domain/CompanyService.cs index 177b091..b8f5103 100644 --- a/Api/Domain/CompanyService.cs +++ b/Api/Domain/CompanyService.cs @@ -1,25 +1,25 @@ using Api.Infrastructure; -namespace Api.Domain +namespace Api.Domain; + +public class CompanyService : ICompanyService { - public class CompanyService : ICompanyService - { - private readonly IProxy _proxy; + private readonly IProxy _proxy; - public CompanyService(IProxy proxy) - { + public CompanyService(IProxy proxy) + { _proxy = proxy; } - public async Task GetCompanyAsync(string registrationNumber, CancellationToken cancellationToken = default) - { + public async Task GetCompanyAsync(string registrationNumber, CancellationToken cancellationToken = default) + { var company = await _proxy.GetCompanyAsync(registrationNumber, cancellationToken); CheckEligibilityRules(company); return company; } - private static void CheckEligibilityRules(Company company) - { + private static void CheckEligibilityRules(Company company) + { if (company.CompanyStatus != CompanyStatus.Active) { throw DomainException.InvalidCompanyStatus(); @@ -30,5 +30,4 @@ private static void CheckEligibilityRules(Company company) throw DomainException.InvalidCompanyAddress(); } } - } -} +} \ No newline at end of file diff --git a/Api/Infrastructure/Proxy.cs b/Api/Infrastructure/Proxy.cs index b83bfa9..b053e82 100644 --- a/Api/Infrastructure/Proxy.cs +++ b/Api/Infrastructure/Proxy.cs @@ -1,53 +1,52 @@ using Api.Domain; -namespace Api.Infrastructure +namespace Api.Infrastructure; + +public class Proxy : IProxy { - public class Proxy : IProxy + public Task GetCompanyAsync(string registrationNumber, CancellationToken cancellationToken = default) { - public Task GetCompanyAsync(string registrationNumber, CancellationToken cancellationToken = default) - { - var nextValue = Randomize.Next(); + var nextValue = Randomize.Next(); - return nextValue switch + return nextValue switch + { + < 300 => Task.FromResult(new Company { - < 300 => Task.FromResult(new Company - { - Name = Randomize.RandomString(10), - RegistrationNumber = registrationNumber, - Address = Randomize.RandomString(20), - CompanyStatus = CompanyStatus.Active - }), - < 400 => Task.FromResult(new Company - { - Name = Randomize.RandomString(10), - RegistrationNumber = registrationNumber, - Address = Randomize.RandomString(20), - CompanyStatus = CompanyStatus.Delisted - }), - < 600 => Task.FromResult(new Company - { - Name = Randomize.RandomString(10), - RegistrationNumber = registrationNumber, - CompanyStatus = CompanyStatus.Active - }), - < 700 => throw InfrastructureException.PartnerWebServiceIsDown(), - < 800 => throw InfrastructureException.PartnerWebServiceIsTakingTooLongToRespond(), - _ => throw InfrastructureException.PartnerWebServiceReceivingTooManyRequests() - }; - } + Name = Randomize.RandomString(10), + RegistrationNumber = registrationNumber, + Address = Randomize.RandomString(20), + CompanyStatus = CompanyStatus.Active + }), + < 400 => Task.FromResult(new Company + { + Name = Randomize.RandomString(10), + RegistrationNumber = registrationNumber, + Address = Randomize.RandomString(20), + CompanyStatus = CompanyStatus.Delisted + }), + < 600 => Task.FromResult(new Company + { + Name = Randomize.RandomString(10), + RegistrationNumber = registrationNumber, + CompanyStatus = CompanyStatus.Active + }), + < 700 => throw InfrastructureException.PartnerWebServiceIsDown(), + < 800 => throw InfrastructureException.PartnerWebServiceIsTakingTooLongToRespond(), + _ => throw InfrastructureException.PartnerWebServiceReceivingTooManyRequests() + }; + } - public class Randomize - { - private static readonly Random Random = new(Guid.NewGuid().GetHashCode()); + private sealed class Randomize + { + private static readonly Random Random = new(Guid.NewGuid().GetHashCode()); - public static int Next() => Random.Next(1, 1000); + public static int Next() => Random.Next(1, 1000); - public static string RandomString(int length) - { - const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - return new string(Enumerable.Repeat(chars, length) - .Select(s => s[Random.Next(s.Length)]).ToArray()); - } + public static string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[Random.Next(s.Length)]).ToArray()); } } -} +} \ No newline at end of file diff --git a/Api/Presentation/Validators/CompanyRequestDtoValidator.cs b/Api/Presentation/Validators/CompanyRequestDtoValidator.cs index d319a3d..9720153 100644 --- a/Api/Presentation/Validators/CompanyRequestDtoValidator.cs +++ b/Api/Presentation/Validators/CompanyRequestDtoValidator.cs @@ -1,16 +1,15 @@ using Api.Presentation.ViewModels; using FluentValidation; -namespace Api.Presentation.Validators +namespace Api.Presentation.Validators; + +public class CompanyRequestDtoValidator : AbstractValidator { - public class CompanyRequestDtoValidator : AbstractValidator + public CompanyRequestDtoValidator() { - public CompanyRequestDtoValidator() - { RuleFor(x => x.RegistrationNumber) .NotEmpty() .MinimumLength(5) .MaximumLength(10); } - } -} +} \ No newline at end of file diff --git a/Api/Presentation/ViewModels/CompanyResponseDto.cs b/Api/Presentation/ViewModels/CompanyResponseDto.cs index 90598f2..fe545d2 100644 --- a/Api/Presentation/ViewModels/CompanyResponseDto.cs +++ b/Api/Presentation/ViewModels/CompanyResponseDto.cs @@ -1,20 +1,19 @@ using Api.Domain; -namespace Api.Presentation.ViewModels +namespace Api.Presentation.ViewModels; + +public class CompanyResponseDto { - public class CompanyResponseDto - { - public string Name { get; set; } + public string Name { get; set; } - public string Address { get; set; } + public string Address { get; set; } - public string RegistrationNumber { get; set; } + public string RegistrationNumber { get; set; } - public CompanyResponseDto(Company company) - { + public CompanyResponseDto(Company company) + { Name = company.Name; Address = company.Address; RegistrationNumber = company.RegistrationNumber; } - } -} +} \ No newline at end of file diff --git a/Api/Startup.cs b/Api/Startup.cs index e53da94..f8ea481 100644 --- a/Api/Startup.cs +++ b/Api/Startup.cs @@ -2,67 +2,68 @@ using Api.Infrastructure; using Api.Presentation.Middlewares; using Api.Presentation.Validators; +using FluentValidation; using FluentValidation.AspNetCore; using Microsoft.AspNetCore.Mvc; -namespace Api +namespace Api; + +public class Startup { - public class Startup + public Startup(IConfiguration configuration) { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } + Configuration = configuration; + } - public IConfiguration Configuration { get; } + public IConfiguration Configuration { get; } - public void ConfigureServices(IServiceCollection services) - { - services - .AddControllers() - .ConfigureApiBehaviorOptions(options => + public void ConfigureServices(IServiceCollection services) + { + services + .AddControllers() + .ConfigureApiBehaviorOptions(options => + { + options.InvalidModelStateResponseFactory = context => { - options.InvalidModelStateResponseFactory = context => + var problemDetails = new ValidationProblemDetails(context.ModelState); + return new BadRequestObjectResult(problemDetails) { - var problemDetails = new ValidationProblemDetails(context.ModelState); - return new BadRequestObjectResult(problemDetails) - { - ContentTypes = { "application/problem+json" } - }; + ContentTypes = { "application/problem+json" } }; - }); + }; + }); - services.AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining()); + services.AddFluentValidationAutoValidation(); + services.AddValidatorsFromAssemblyContaining(); - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(); + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); - services.AddScoped(); - services.AddScoped(); - } + services.AddScoped(); + services.AddScoped(); + } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } + app.UseDeveloperExceptionPage(); + } - app.UseExceptionHandlingMiddleware(); + app.UseExceptionHandlingMiddleware(); - app.UseHttpsRedirection(); + app.UseHttpsRedirection(); - app.UseRouting(); + app.UseRouting(); - app.UseAuthorization(); + app.UseAuthorization(); - app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwagger(); + app.UseSwaggerUI(); - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); } -} +} \ No newline at end of file diff --git a/ExceptionHandlingMiddlewareDemo.sln b/ExceptionHandlingMiddlewareDemo.sln index 126493f..4abede4 100644 --- a/ExceptionHandlingMiddlewareDemo.sln +++ b/ExceptionHandlingMiddlewareDemo.sln @@ -9,6 +9,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .github\workflows\ci.yml = .github\workflows\ci.yml README.md = README.md + global.json = global.json EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{F13C9ADF-5755-4273-94C9-97C197B7CB8C}" diff --git a/README.md b/README.md index efa67c1..fce3514 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,4 @@ Exceptions are thrown by : Exceptions are catched by the exception middleware and formatted using [problem details specification](https://datatracker.ietf.org/doc/html/rfc7807) -**`Tools`** : vs22, net 6.0, xunit \ No newline at end of file +**`Tools`** : net 8.0, xunit \ No newline at end of file diff --git a/Tests/ExceptionHandlingMiddlewareTests.cs b/Tests/ExceptionHandlingMiddlewareTests.cs index 6466920..74d31a1 100644 --- a/Tests/ExceptionHandlingMiddlewareTests.cs +++ b/Tests/ExceptionHandlingMiddlewareTests.cs @@ -8,43 +8,43 @@ using Microsoft.Extensions.Logging.Abstractions; using Xunit; -namespace Tests +namespace Tests; + +public class ExceptionHandlingMiddlewareTests { - public class ExceptionHandlingMiddlewareTests + [Theory] + [ClassData(typeof(TestCases))] + public async Task Should_Return_Valid_StatusCode(RequestDelegate next, int expectedStatusCode) { - [Theory] - [ClassData(typeof(TestCases))] - public async Task Should_Return_Valid_StatusCode(RequestDelegate next, int expectedStatusCode) + // arrange + using var stream = new MemoryStream(); + var logger = NullLogger.Instance; + var context = new DefaultHttpContext { - // arrange - var logger = NullLogger.Instance; - var context = new DefaultHttpContext + Response = { - Response = - { - Body = new MemoryStream() - } - }; - var middleware = new ExceptionHandlingMiddleware(next, logger); + Body = stream + } + }; + var middleware = new ExceptionHandlingMiddleware(next, logger); - // act - await middleware.Invoke(context); + // act + await middleware.Invoke(context); - // assert - context.Response.StatusCode.Should().Be(expectedStatusCode); - } + // assert + context.Response.StatusCode.Should().Be(expectedStatusCode); + } - private class TestCases : TheoryData + private class TestCases : TheoryData + { + public TestCases() { - public TestCases() - { - Add(_ => Task.CompletedTask, 200); - Add(_ => throw DomainException.InvalidCompanyStatus(), 501); - Add(_ => throw DomainException.InvalidCompanyAddress(), 501); - Add(_ => throw InfrastructureException.PartnerWebServiceIsDown(), 503); - Add(_ => throw InfrastructureException.PartnerWebServiceIsTakingTooLongToRespond(), 503); - Add(_ => throw InfrastructureException.PartnerWebServiceReceivingTooManyRequests(), 503); - } + Add(_ => Task.CompletedTask, 200); + Add(_ => throw DomainException.InvalidCompanyStatus(), 501); + Add(_ => throw DomainException.InvalidCompanyAddress(), 501); + Add(_ => throw InfrastructureException.PartnerWebServiceIsDown(), 503); + Add(_ => throw InfrastructureException.PartnerWebServiceIsTakingTooLongToRespond(), 503); + Add(_ => throw InfrastructureException.PartnerWebServiceReceivingTooManyRequests(), 503); } } } \ No newline at end of file diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 6dc171e..3f17ce7 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,21 +1,20 @@ - net6.0 + net8.0 enable - false - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/global.json b/global.json new file mode 100644 index 0000000..b5b37b6 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestMajor", + "allowPrerelease": false + } +} \ No newline at end of file