From fc0345d054fee2f6f29c7e9166aa6680ed8e77e0 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 12 Jun 2024 16:10:09 +1000 Subject: [PATCH 1/9] refactor: rm other branches --- Appointer.sln | 34 ++++++---------- src/Domain/Domain.csproj | 9 ----- src/Infrastructure/Infrastructure.csproj | 20 ---------- .../Database}/AppointerDbContext.cs | 7 ++-- .../Database}/DependencyInjection.cs | 7 +--- .../Database}/UserAccountConfiguration.cs | 4 +- .../Domain/Accounts/UserAccount.cs | 2 +- .../Features/Accounts/CreateAccount.cs | 39 ++++++++++--------- src/Web.Api/Features/Accounts/GetAccount.cs | 3 +- src/Web.Api/Program.cs | 9 +++-- src/Web.Api/Web.Api.csproj | 12 ++++-- .../Infrastructure.IntegrationTests.csproj | 32 --------------- .../Infrastructure.IntegrationTests/Usings.cs | 1 - .../Authentication/TokenShould.cs | 2 +- .../Database}/AppointerDbContextShould.cs | 8 ++-- .../{Accounts => Features}/AccountsShould.cs | 4 +- .../Helpers/AppointerWebApplicationFactory.cs | 2 +- .../Helpers/MsSqlContainerStartup.cs | 4 +- .../Helpers/ServiceCollectionExtensions.cs | 4 +- 19 files changed, 67 insertions(+), 136 deletions(-) delete mode 100644 src/Domain/Domain.csproj delete mode 100644 src/Infrastructure/Infrastructure.csproj rename src/{Infrastructure/DbContext => Web.Api/Database}/AppointerDbContext.cs (73%) rename src/{Infrastructure => Web.Api/Database}/DependencyInjection.cs (64%) rename src/{Infrastructure/Configuration => Web.Api/Database}/UserAccountConfiguration.cs (91%) rename src/{ => Web.Api}/Domain/Accounts/UserAccount.cs (73%) delete mode 100644 tests/Infrastructure.IntegrationTests/Infrastructure.IntegrationTests.csproj delete mode 100644 tests/Infrastructure.IntegrationTests/Usings.cs rename tests/{Infrastructure.IntegrationTests => Web.Api.IntegrationTests}/Authentication/TokenShould.cs (98%) rename tests/{Infrastructure.IntegrationTests/DbContext => Web.Api.IntegrationTests/Database}/AppointerDbContextShould.cs (79%) rename tests/Web.Api.IntegrationTests/{Accounts => Features}/AccountsShould.cs (97%) rename tests/{Infrastructure.IntegrationTests => Web.Api.IntegrationTests}/Helpers/MsSqlContainerStartup.cs (92%) diff --git a/Appointer.sln b/Appointer.sln index f34f9d7..c834047 100644 --- a/Appointer.sln +++ b/Appointer.sln @@ -1,5 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34728.123 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0822B695-D2DF-4E72-BCAD-DD767655E9C4}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{5E191623-BFC0-4FEC-93A9-047999771EFE}" @@ -9,15 +12,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A5781BA3-7 README.md = README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{DC36BD6E-301B-4C03-970E-6869B7907B0F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web.Api", "src\Web.Api\Web.Api.csproj", "{F7E76506-B0E8-4277-9EF1-958FA786F3D5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.IntegrationTests", "tests\Infrastructure.IntegrationTests\Infrastructure.IntegrationTests.csproj", "{9514D439-B4C7-42F2-B9B9-7373C1BAAE0E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web.Api", "src\Web.Api\Web.Api.csproj", "{F7E76506-B0E8-4277-9EF1-958FA786F3D5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web.Api.IntegrationTests", "tests\Web.Api.IntegrationTests\Web.Api.IntegrationTests.csproj", "{1F302DCD-2100-42CA-850A-570C30230FDF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "src\Domain\Domain.csproj", "{E9158132-1CA0-4B10-B1FB-ACA60605246B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web.Api.IntegrationTests", "tests\Web.Api.IntegrationTests\Web.Api.IntegrationTests.csproj", "{1F302DCD-2100-42CA-850A-570C30230FDF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{4A648E15-FEF1-47BB-99FC-26CEE2516ACB}" ProjectSection(SolutionItems) = preProject @@ -30,14 +27,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {DC36BD6E-301B-4C03-970E-6869B7907B0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DC36BD6E-301B-4C03-970E-6869B7907B0F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DC36BD6E-301B-4C03-970E-6869B7907B0F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DC36BD6E-301B-4C03-970E-6869B7907B0F}.Release|Any CPU.Build.0 = Release|Any CPU - {9514D439-B4C7-42F2-B9B9-7373C1BAAE0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9514D439-B4C7-42F2-B9B9-7373C1BAAE0E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9514D439-B4C7-42F2-B9B9-7373C1BAAE0E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9514D439-B4C7-42F2-B9B9-7373C1BAAE0E}.Release|Any CPU.Build.0 = Release|Any CPU {F7E76506-B0E8-4277-9EF1-958FA786F3D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F7E76506-B0E8-4277-9EF1-958FA786F3D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7E76506-B0E8-4277-9EF1-958FA786F3D5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -46,16 +35,15 @@ Global {1F302DCD-2100-42CA-850A-570C30230FDF}.Debug|Any CPU.Build.0 = Debug|Any CPU {1F302DCD-2100-42CA-850A-570C30230FDF}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F302DCD-2100-42CA-850A-570C30230FDF}.Release|Any CPU.Build.0 = Release|Any CPU - {E9158132-1CA0-4B10-B1FB-ACA60605246B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E9158132-1CA0-4B10-B1FB-ACA60605246B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E9158132-1CA0-4B10-B1FB-ACA60605246B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E9158132-1CA0-4B10-B1FB-ACA60605246B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {DC36BD6E-301B-4C03-970E-6869B7907B0F} = {0822B695-D2DF-4E72-BCAD-DD767655E9C4} - {9514D439-B4C7-42F2-B9B9-7373C1BAAE0E} = {5E191623-BFC0-4FEC-93A9-047999771EFE} {F7E76506-B0E8-4277-9EF1-958FA786F3D5} = {0822B695-D2DF-4E72-BCAD-DD767655E9C4} {1F302DCD-2100-42CA-850A-570C30230FDF} = {5E191623-BFC0-4FEC-93A9-047999771EFE} - {E9158132-1CA0-4B10-B1FB-ACA60605246B} = {0822B695-D2DF-4E72-BCAD-DD767655E9C4} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C31453C5-23E9-43FB-A7A8-D30FBE018ACC} EndGlobalSection EndGlobal diff --git a/src/Domain/Domain.csproj b/src/Domain/Domain.csproj deleted file mode 100644 index 3a63532..0000000 --- a/src/Domain/Domain.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net8.0 - enable - enable - - - diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj deleted file mode 100644 index 72e6c00..0000000 --- a/src/Infrastructure/Infrastructure.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - - - - - - diff --git a/src/Infrastructure/DbContext/AppointerDbContext.cs b/src/Web.Api/Database/AppointerDbContext.cs similarity index 73% rename from src/Infrastructure/DbContext/AppointerDbContext.cs rename to src/Web.Api/Database/AppointerDbContext.cs index 2d2156a..dbb50cf 100644 --- a/src/Infrastructure/DbContext/AppointerDbContext.cs +++ b/src/Web.Api/Database/AppointerDbContext.cs @@ -1,9 +1,10 @@ -using Domain.Accounts; using Microsoft.EntityFrameworkCore; +using Web.Api.Domain.Accounts; +// ReSharper disable ConvertToPrimaryConstructor -namespace Infrastructure.DbContext; +namespace Web.Api.Database; -public class AppointerDbContext : Microsoft.EntityFrameworkCore.DbContext +public class AppointerDbContext : DbContext { public AppointerDbContext(DbContextOptions options) : base(options) { diff --git a/src/Infrastructure/DependencyInjection.cs b/src/Web.Api/Database/DependencyInjection.cs similarity index 64% rename from src/Infrastructure/DependencyInjection.cs rename to src/Web.Api/Database/DependencyInjection.cs index ef9d5b1..2709c79 100644 --- a/src/Infrastructure/DependencyInjection.cs +++ b/src/Web.Api/Database/DependencyInjection.cs @@ -1,9 +1,6 @@ -using Infrastructure.DbContext; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.EntityFrameworkCore; -namespace Infrastructure; +namespace Web.Api.Database; public static class DependencyInjection { diff --git a/src/Infrastructure/Configuration/UserAccountConfiguration.cs b/src/Web.Api/Database/UserAccountConfiguration.cs similarity index 91% rename from src/Infrastructure/Configuration/UserAccountConfiguration.cs rename to src/Web.Api/Database/UserAccountConfiguration.cs index d43976b..45b4c55 100644 --- a/src/Infrastructure/Configuration/UserAccountConfiguration.cs +++ b/src/Web.Api/Database/UserAccountConfiguration.cs @@ -1,8 +1,8 @@ -using Domain.Accounts; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Web.Api.Domain.Accounts; -namespace Infrastructure.Configuration; +namespace Web.Api.Database; public class UserAccountConfiguration : IEntityTypeConfiguration { diff --git a/src/Domain/Accounts/UserAccount.cs b/src/Web.Api/Domain/Accounts/UserAccount.cs similarity index 73% rename from src/Domain/Accounts/UserAccount.cs rename to src/Web.Api/Domain/Accounts/UserAccount.cs index a648c7a..2ead0d3 100644 --- a/src/Domain/Accounts/UserAccount.cs +++ b/src/Web.Api/Domain/Accounts/UserAccount.cs @@ -1,3 +1,3 @@ -namespace Domain.Accounts; +namespace Web.Api.Domain.Accounts; public record UserAccount(Guid Id, string FullName, bool IsActive = true, bool IsDeleted = false); \ No newline at end of file diff --git a/src/Web.Api/Features/Accounts/CreateAccount.cs b/src/Web.Api/Features/Accounts/CreateAccount.cs index 7e868ac..a99c210 100644 --- a/src/Web.Api/Features/Accounts/CreateAccount.cs +++ b/src/Web.Api/Features/Accounts/CreateAccount.cs @@ -1,10 +1,28 @@ using Carter; -using Domain.Accounts; -using Infrastructure.DbContext; using Mapster; using MediatR; +using Web.Api.Database; +using Web.Api.Domain.Accounts; using Web.Api.Shared; +namespace Web.Api.Features.Accounts; + +public class CreateAccountEndpoint : ICarterModule +{ + public void AddRoutes(IEndpointRouteBuilder app) + { + app.MapPost("api/accounts", async (CreateAccount.Command request, ISender sender, CancellationToken cancellationToken) => + { + var command = request.Adapt(); + var result = await sender.Send(command, cancellationToken); + + if (result.IsFailure) + return Results.BadRequest(result.Error); + + return Results.Ok(result.Value); + }); + } +} public static class CreateAccount { public class Command : IRequest> @@ -32,20 +50,3 @@ public async Task> Handle(Command request, Cancell } } } - -public class CreateAccountEndpoint : ICarterModule -{ - public void AddRoutes(IEndpointRouteBuilder app) - { - app.MapPost("api/accounts", async (CreateAccount.Command request, ISender sender, CancellationToken cancellationToken) => - { - var command = request.Adapt(); - var result = await sender.Send(command, cancellationToken); - - if (result.IsFailure) - return Results.BadRequest(result.Error); - - return Results.Ok(result.Value); - }); - } -} diff --git a/src/Web.Api/Features/Accounts/GetAccount.cs b/src/Web.Api/Features/Accounts/GetAccount.cs index 3dbc63e..4a05757 100644 --- a/src/Web.Api/Features/Accounts/GetAccount.cs +++ b/src/Web.Api/Features/Accounts/GetAccount.cs @@ -1,7 +1,6 @@ using Carter; -using Infrastructure.DbContext; using MediatR; -using Microsoft.EntityFrameworkCore; +using Web.Api.Database; using Web.Api.Shared; namespace Web.Api.Features.Accounts; diff --git a/src/Web.Api/Program.cs b/src/Web.Api/Program.cs index e8be1b3..008913e 100644 --- a/src/Web.Api/Program.cs +++ b/src/Web.Api/Program.cs @@ -1,5 +1,5 @@ using Carter; -using Infrastructure; +using Web.Api.Database; var builder = WebApplication.CreateBuilder(args); @@ -7,7 +7,7 @@ builder.Services.AddSwaggerGen(); builder.Services.AddInfrastructure(builder.Configuration); -var assembly = typeof(Program).Assembly; +var assembly = typeof(Web.Api.Program).Assembly; builder.Services.AddMediatR(config => config.RegisterServicesFromAssembly(assembly)); builder.Services.AddCarter(); @@ -23,4 +23,7 @@ app.UseHttpsRedirection(); app.Run(); -public partial class Program; \ No newline at end of file +namespace Web.Api +{ + public partial class Program; +} \ No newline at end of file diff --git a/src/Web.Api/Web.Api.csproj b/src/Web.Api/Web.Api.csproj index 314a38d..7fc43a8 100644 --- a/src/Web.Api/Web.Api.csproj +++ b/src/Web.Api/Web.Api.csproj @@ -7,12 +7,20 @@ Linux + + + + + + + + @@ -22,8 +30,4 @@ - - - - diff --git a/tests/Infrastructure.IntegrationTests/Infrastructure.IntegrationTests.csproj b/tests/Infrastructure.IntegrationTests/Infrastructure.IntegrationTests.csproj deleted file mode 100644 index 240e397..0000000 --- a/tests/Infrastructure.IntegrationTests/Infrastructure.IntegrationTests.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net8.0 - enable - enable - false - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/tests/Infrastructure.IntegrationTests/Usings.cs b/tests/Infrastructure.IntegrationTests/Usings.cs deleted file mode 100644 index 8c927eb..0000000 --- a/tests/Infrastructure.IntegrationTests/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Xunit; \ No newline at end of file diff --git a/tests/Infrastructure.IntegrationTests/Authentication/TokenShould.cs b/tests/Web.Api.IntegrationTests/Authentication/TokenShould.cs similarity index 98% rename from tests/Infrastructure.IntegrationTests/Authentication/TokenShould.cs rename to tests/Web.Api.IntegrationTests/Authentication/TokenShould.cs index a0ebff7..4347658 100644 --- a/tests/Infrastructure.IntegrationTests/Authentication/TokenShould.cs +++ b/tests/Web.Api.IntegrationTests/Authentication/TokenShould.cs @@ -4,7 +4,7 @@ using FluentAssertions; using Microsoft.IdentityModel.Tokens; -namespace Infrastructure.IntegrationTests.Authentication; +namespace Web.Api.IntegrationTests.Authentication; public class TokenShould { diff --git a/tests/Infrastructure.IntegrationTests/DbContext/AppointerDbContextShould.cs b/tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs similarity index 79% rename from tests/Infrastructure.IntegrationTests/DbContext/AppointerDbContextShould.cs rename to tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs index b338292..648b288 100644 --- a/tests/Infrastructure.IntegrationTests/DbContext/AppointerDbContextShould.cs +++ b/tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs @@ -1,10 +1,10 @@ -using Domain.Accounts; using FluentAssertions; -using Infrastructure.DbContext; -using Infrastructure.IntegrationTests.Helpers; using Microsoft.Extensions.DependencyInjection; +using Web.Api.Database; +using Web.Api.Domain.Accounts; +using Web.Api.IntegrationTests.Helpers; -namespace Infrastructure.IntegrationTests.DbContext; +namespace Web.Api.IntegrationTests.DbContext; public class AppointerDbContextShould : MsSqlContainerStartup { diff --git a/tests/Web.Api.IntegrationTests/Accounts/AccountsShould.cs b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs similarity index 97% rename from tests/Web.Api.IntegrationTests/Accounts/AccountsShould.cs rename to tests/Web.Api.IntegrationTests/Features/AccountsShould.cs index 0eba8fd..6ffa6b4 100644 --- a/tests/Web.Api.IntegrationTests/Accounts/AccountsShould.cs +++ b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs @@ -1,10 +1,10 @@ using System.Net; using System.Net.Http.Json; -using Domain.Accounts; using FluentAssertions; -using Infrastructure.DbContext; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; +using Web.Api.Database; +using Web.Api.Domain.Accounts; using Web.Api.Features.Accounts; using Web.Api.IntegrationTests.Helpers; diff --git a/tests/Web.Api.IntegrationTests/Helpers/AppointerWebApplicationFactory.cs b/tests/Web.Api.IntegrationTests/Helpers/AppointerWebApplicationFactory.cs index 5488e58..90c5e9a 100644 --- a/tests/Web.Api.IntegrationTests/Helpers/AppointerWebApplicationFactory.cs +++ b/tests/Web.Api.IntegrationTests/Helpers/AppointerWebApplicationFactory.cs @@ -1,10 +1,10 @@ -using Infrastructure.DbContext; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Testcontainers.MsSql; +using Web.Api.Database; namespace Web.Api.IntegrationTests.Helpers; diff --git a/tests/Infrastructure.IntegrationTests/Helpers/MsSqlContainerStartup.cs b/tests/Web.Api.IntegrationTests/Helpers/MsSqlContainerStartup.cs similarity index 92% rename from tests/Infrastructure.IntegrationTests/Helpers/MsSqlContainerStartup.cs rename to tests/Web.Api.IntegrationTests/Helpers/MsSqlContainerStartup.cs index d31e330..1c7429b 100644 --- a/tests/Infrastructure.IntegrationTests/Helpers/MsSqlContainerStartup.cs +++ b/tests/Web.Api.IntegrationTests/Helpers/MsSqlContainerStartup.cs @@ -1,9 +1,9 @@ -using Infrastructure.DbContext; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Testcontainers.MsSql; +using Web.Api.Database; -namespace Infrastructure.IntegrationTests.Helpers; +namespace Web.Api.IntegrationTests.Helpers; public class MsSqlContainerStartup : IAsyncLifetime { diff --git a/tests/Web.Api.IntegrationTests/Helpers/ServiceCollectionExtensions.cs b/tests/Web.Api.IntegrationTests/Helpers/ServiceCollectionExtensions.cs index 386aead..f0ff817 100644 --- a/tests/Web.Api.IntegrationTests/Helpers/ServiceCollectionExtensions.cs +++ b/tests/Web.Api.IntegrationTests/Helpers/ServiceCollectionExtensions.cs @@ -5,13 +5,13 @@ namespace Web.Api.IntegrationTests.Helpers; public static class ServiceCollectionExtensions { - public static void RemoveDbContext(this IServiceCollection services) where T : DbContext + public static void RemoveDbContext(this IServiceCollection services) where T : Microsoft.EntityFrameworkCore.DbContext { var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); if (descriptor != null) services.Remove(descriptor); } - public static void EnsureDbCreated(this IServiceCollection services) where T : DbContext + public static void EnsureDbCreated(this IServiceCollection services) where T : Microsoft.EntityFrameworkCore.DbContext { var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); From d073b843a4830f28c42d47f44664037898c6d283 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 13 Jun 2024 09:24:59 +1000 Subject: [PATCH 2/9] refactor: tidy vertical slices --- .../Features/Accounts/CreateAccount.cs | 10 ++--- src/Web.Api/Features/Accounts/GetAccount.cs | 42 +++++++++---------- .../Database/AppointerDbContextShould.cs | 2 +- .../Features/AccountsShould.cs | 2 +- 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/Web.Api/Features/Accounts/CreateAccount.cs b/src/Web.Api/Features/Accounts/CreateAccount.cs index a99c210..534874f 100644 --- a/src/Web.Api/Features/Accounts/CreateAccount.cs +++ b/src/Web.Api/Features/Accounts/CreateAccount.cs @@ -17,18 +17,18 @@ public void AddRoutes(IEndpointRouteBuilder app) var result = await sender.Send(command, cancellationToken); if (result.IsFailure) + { return Results.BadRequest(result.Error); + } return Results.Ok(result.Value); }); } } + public static class CreateAccount { - public class Command : IRequest> - { - public string FullName { get; set; } = string.Empty; - } + public record Command(string Fullname) : IRequest>; public record CreateAccountResponse(Guid Id, string FullName); @@ -43,7 +43,7 @@ public Handler(AppointerDbContext dbContext) public async Task> Handle(Command request, CancellationToken cancellationToken) { - var userAccount = new UserAccount(Guid.NewGuid(), request.FullName); + var userAccount = new UserAccount(Guid.NewGuid(), request.Fullname); await _dbContext.UserAccounts.AddAsync(userAccount, cancellationToken); await _dbContext.SaveChangesAsync(cancellationToken); return new CreateAccountResponse(userAccount.Id, userAccount.FullName); diff --git a/src/Web.Api/Features/Accounts/GetAccount.cs b/src/Web.Api/Features/Accounts/GetAccount.cs index 4a05757..852b3d9 100644 --- a/src/Web.Api/Features/Accounts/GetAccount.cs +++ b/src/Web.Api/Features/Accounts/GetAccount.cs @@ -5,12 +5,29 @@ namespace Web.Api.Features.Accounts; -public static class GetAccount +public class GetAccountEndpoint : ICarterModule { - public class Query : IRequest> + public void AddRoutes(IEndpointRouteBuilder app) { - public Guid Id { get; set; } + app.MapGet("api/accounts/{id}", async (Guid id, ISender sender, CancellationToken cancellationToken) => + { + var query = new GetAccount.Query(id); + + var result = await sender.Send(query, cancellationToken); + + if (result.IsFailure) + { + return Results.NotFound(result.Error); + } + + return Results.Ok(result.Value); + }); } +} + +public static class GetAccount +{ + public record Query(Guid Id) : IRequest>; public record GetAccountResponse(Guid Id, string FullName); @@ -39,22 +56,3 @@ public async Task> Handle(Query request, Cancellation } } -public class GetAccountEndpoint : ICarterModule -{ - public void AddRoutes(IEndpointRouteBuilder app) - { - app.MapGet("api/accounts/{id}", async (Guid id, ISender sender, CancellationToken cancellationToken) => - { - var query = new GetAccount.Query { Id = id }; - - var result = await sender.Send(query, cancellationToken); - - if (result.IsFailure) - { - return Results.NotFound(result.Error); - } - - return Results.Ok(result.Value); - }); - } -} diff --git a/tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs b/tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs index 648b288..c3490f8 100644 --- a/tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs +++ b/tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs @@ -4,7 +4,7 @@ using Web.Api.Domain.Accounts; using Web.Api.IntegrationTests.Helpers; -namespace Web.Api.IntegrationTests.DbContext; +namespace Web.Api.IntegrationTests.Database; public class AppointerDbContextShould : MsSqlContainerStartup { diff --git a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs index 6ffa6b4..beb4b37 100644 --- a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs +++ b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs @@ -8,7 +8,7 @@ using Web.Api.Features.Accounts; using Web.Api.IntegrationTests.Helpers; -namespace Web.Api.IntegrationTests.Accounts; +namespace Web.Api.IntegrationTests.Features; public class AccountsShould : IClassFixture> { From 03288db6d4dbfdc8ae83eb01899eee89cf35b308 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 13 Jun 2024 13:56:57 +1000 Subject: [PATCH 3/9] refactor: shared to common and use infra folder --- src/Web.Api/{Shared => Common}/Error.cs | 2 +- src/Web.Api/{Shared => Common}/Result.cs | 2 +- src/Web.Api/{Shared => Common}/ResultT.cs | 2 +- src/Web.Api/Features/Accounts/CreateAccount.cs | 13 +++++++++++-- src/Web.Api/Features/Accounts/GetAccount.cs | 5 ++--- .../Database/AppointerDbContext.cs | 2 +- .../Database/UserAccountConfiguration.cs | 2 +- .../DependencyInjection.cs | 3 ++- src/Web.Api/Program.cs | 2 +- .../Features/AccountsShould.cs | 4 ++-- .../Helpers/AppointerWebApplicationFactory.cs | 2 +- .../Helpers/MsSqlContainerStartup.cs | 3 ++- .../Authentication/TokenShould.cs | 4 ++-- .../Database/AppointerDbContextShould.cs | 4 ++-- 14 files changed, 30 insertions(+), 20 deletions(-) rename src/Web.Api/{Shared => Common}/Error.cs (92%) rename src/Web.Api/{Shared => Common}/Result.cs (97%) rename src/Web.Api/{Shared => Common}/ResultT.cs (94%) rename src/Web.Api/{ => Infrastructure}/Database/AppointerDbContext.cs (92%) rename src/Web.Api/{ => Infrastructure}/Database/UserAccountConfiguration.cs (94%) rename src/Web.Api/{Database => Infrastructure}/DependencyInjection.cs (84%) rename tests/Web.Api.IntegrationTests/{ => Infrastructure}/Authentication/TokenShould.cs (98%) rename tests/Web.Api.IntegrationTests/{ => Infrastructure}/Database/AppointerDbContextShould.cs (87%) diff --git a/src/Web.Api/Shared/Error.cs b/src/Web.Api/Common/Error.cs similarity index 92% rename from src/Web.Api/Shared/Error.cs rename to src/Web.Api/Common/Error.cs index 09fa851..ec5cfae 100644 --- a/src/Web.Api/Shared/Error.cs +++ b/src/Web.Api/Common/Error.cs @@ -1,4 +1,4 @@ -namespace Web.Api.Shared; +namespace Web.Api.Common; public record Error(string Code, string Message) { diff --git a/src/Web.Api/Shared/Result.cs b/src/Web.Api/Common/Result.cs similarity index 97% rename from src/Web.Api/Shared/Result.cs rename to src/Web.Api/Common/Result.cs index 1aee402..5bf703b 100644 --- a/src/Web.Api/Shared/Result.cs +++ b/src/Web.Api/Common/Result.cs @@ -1,4 +1,4 @@ -namespace Web.Api.Shared; +namespace Web.Api.Common; public class Result { diff --git a/src/Web.Api/Shared/ResultT.cs b/src/Web.Api/Common/ResultT.cs similarity index 94% rename from src/Web.Api/Shared/ResultT.cs rename to src/Web.Api/Common/ResultT.cs index 93d24c5..d01894e 100644 --- a/src/Web.Api/Shared/ResultT.cs +++ b/src/Web.Api/Common/ResultT.cs @@ -1,4 +1,4 @@ -namespace Web.Api.Shared; +namespace Web.Api.Common; public class Result : Result { diff --git a/src/Web.Api/Features/Accounts/CreateAccount.cs b/src/Web.Api/Features/Accounts/CreateAccount.cs index 534874f..3016d22 100644 --- a/src/Web.Api/Features/Accounts/CreateAccount.cs +++ b/src/Web.Api/Features/Accounts/CreateAccount.cs @@ -1,9 +1,10 @@ using Carter; +using FluentValidation; using Mapster; using MediatR; -using Web.Api.Database; +using Web.Api.Common; using Web.Api.Domain.Accounts; -using Web.Api.Shared; +using Web.Api.Infrastructure.Database; namespace Web.Api.Features.Accounts; @@ -32,6 +33,14 @@ public record Command(string Fullname) : IRequest> public record CreateAccountResponse(Guid Id, string FullName); + public class Validator : AbstractValidator + { + public Validator() + { + RuleFor(c => c.Fullname).NotEmpty(); + } + } + internal sealed class Handler : IRequestHandler> { private readonly AppointerDbContext _dbContext; diff --git a/src/Web.Api/Features/Accounts/GetAccount.cs b/src/Web.Api/Features/Accounts/GetAccount.cs index 852b3d9..9cd46b4 100644 --- a/src/Web.Api/Features/Accounts/GetAccount.cs +++ b/src/Web.Api/Features/Accounts/GetAccount.cs @@ -1,7 +1,7 @@ using Carter; using MediatR; -using Web.Api.Database; -using Web.Api.Shared; +using Web.Api.Common; +using Web.Api.Infrastructure.Database; namespace Web.Api.Features.Accounts; @@ -12,7 +12,6 @@ public void AddRoutes(IEndpointRouteBuilder app) app.MapGet("api/accounts/{id}", async (Guid id, ISender sender, CancellationToken cancellationToken) => { var query = new GetAccount.Query(id); - var result = await sender.Send(query, cancellationToken); if (result.IsFailure) diff --git a/src/Web.Api/Database/AppointerDbContext.cs b/src/Web.Api/Infrastructure/Database/AppointerDbContext.cs similarity index 92% rename from src/Web.Api/Database/AppointerDbContext.cs rename to src/Web.Api/Infrastructure/Database/AppointerDbContext.cs index dbb50cf..e9fbbb0 100644 --- a/src/Web.Api/Database/AppointerDbContext.cs +++ b/src/Web.Api/Infrastructure/Database/AppointerDbContext.cs @@ -2,7 +2,7 @@ using Web.Api.Domain.Accounts; // ReSharper disable ConvertToPrimaryConstructor -namespace Web.Api.Database; +namespace Web.Api.Infrastructure.Database; public class AppointerDbContext : DbContext { diff --git a/src/Web.Api/Database/UserAccountConfiguration.cs b/src/Web.Api/Infrastructure/Database/UserAccountConfiguration.cs similarity index 94% rename from src/Web.Api/Database/UserAccountConfiguration.cs rename to src/Web.Api/Infrastructure/Database/UserAccountConfiguration.cs index 45b4c55..e472e50 100644 --- a/src/Web.Api/Database/UserAccountConfiguration.cs +++ b/src/Web.Api/Infrastructure/Database/UserAccountConfiguration.cs @@ -2,7 +2,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using Web.Api.Domain.Accounts; -namespace Web.Api.Database; +namespace Web.Api.Infrastructure.Database; public class UserAccountConfiguration : IEntityTypeConfiguration { diff --git a/src/Web.Api/Database/DependencyInjection.cs b/src/Web.Api/Infrastructure/DependencyInjection.cs similarity index 84% rename from src/Web.Api/Database/DependencyInjection.cs rename to src/Web.Api/Infrastructure/DependencyInjection.cs index 2709c79..670f564 100644 --- a/src/Web.Api/Database/DependencyInjection.cs +++ b/src/Web.Api/Infrastructure/DependencyInjection.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; +using Web.Api.Infrastructure.Database; -namespace Web.Api.Database; +namespace Web.Api.Infrastructure; public static class DependencyInjection { diff --git a/src/Web.Api/Program.cs b/src/Web.Api/Program.cs index 008913e..b231e28 100644 --- a/src/Web.Api/Program.cs +++ b/src/Web.Api/Program.cs @@ -1,5 +1,5 @@ using Carter; -using Web.Api.Database; +using Web.Api.Infrastructure; var builder = WebApplication.CreateBuilder(args); diff --git a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs index beb4b37..fdae155 100644 --- a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs +++ b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs @@ -3,9 +3,9 @@ using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; -using Web.Api.Database; using Web.Api.Domain.Accounts; using Web.Api.Features.Accounts; +using Web.Api.Infrastructure.Database; using Web.Api.IntegrationTests.Helpers; namespace Web.Api.IntegrationTests.Features; @@ -26,7 +26,7 @@ public async Task BeCreatedAndPersisted() var client = _factory.CreateClient(); var url = "api/accounts"; var fullName = "Full Name"; - var request = new CreateAccount.Command { FullName = fullName }; + var request = new CreateAccount.Command(fullName); // act var result = await client.PostAsJsonAsync(url, request); diff --git a/tests/Web.Api.IntegrationTests/Helpers/AppointerWebApplicationFactory.cs b/tests/Web.Api.IntegrationTests/Helpers/AppointerWebApplicationFactory.cs index 90c5e9a..e77768f 100644 --- a/tests/Web.Api.IntegrationTests/Helpers/AppointerWebApplicationFactory.cs +++ b/tests/Web.Api.IntegrationTests/Helpers/AppointerWebApplicationFactory.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Testcontainers.MsSql; -using Web.Api.Database; +using Web.Api.Infrastructure.Database; namespace Web.Api.IntegrationTests.Helpers; diff --git a/tests/Web.Api.IntegrationTests/Helpers/MsSqlContainerStartup.cs b/tests/Web.Api.IntegrationTests/Helpers/MsSqlContainerStartup.cs index 1c7429b..79f3155 100644 --- a/tests/Web.Api.IntegrationTests/Helpers/MsSqlContainerStartup.cs +++ b/tests/Web.Api.IntegrationTests/Helpers/MsSqlContainerStartup.cs @@ -1,7 +1,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Testcontainers.MsSql; -using Web.Api.Database; +using Web.Api.Infrastructure; +using Web.Api.Infrastructure.Database; namespace Web.Api.IntegrationTests.Helpers; diff --git a/tests/Web.Api.IntegrationTests/Authentication/TokenShould.cs b/tests/Web.Api.IntegrationTests/Infrastructure/Authentication/TokenShould.cs similarity index 98% rename from tests/Web.Api.IntegrationTests/Authentication/TokenShould.cs rename to tests/Web.Api.IntegrationTests/Infrastructure/Authentication/TokenShould.cs index 4347658..d34df87 100644 --- a/tests/Web.Api.IntegrationTests/Authentication/TokenShould.cs +++ b/tests/Web.Api.IntegrationTests/Infrastructure/Authentication/TokenShould.cs @@ -4,7 +4,7 @@ using FluentAssertions; using Microsoft.IdentityModel.Tokens; -namespace Web.Api.IntegrationTests.Authentication; +namespace Web.Api.IntegrationTests.Infrastructure.Authentication; public class TokenShould { @@ -83,7 +83,7 @@ public void NotBeValidBySigningKey() // assert act.Should().Throw(); } - + [Fact] public void NotBeValidByAudience() { diff --git a/tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs b/tests/Web.Api.IntegrationTests/Infrastructure/Database/AppointerDbContextShould.cs similarity index 87% rename from tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs rename to tests/Web.Api.IntegrationTests/Infrastructure/Database/AppointerDbContextShould.cs index c3490f8..743eadf 100644 --- a/tests/Web.Api.IntegrationTests/Database/AppointerDbContextShould.cs +++ b/tests/Web.Api.IntegrationTests/Infrastructure/Database/AppointerDbContextShould.cs @@ -1,10 +1,10 @@ using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using Web.Api.Database; using Web.Api.Domain.Accounts; +using Web.Api.Infrastructure.Database; using Web.Api.IntegrationTests.Helpers; -namespace Web.Api.IntegrationTests.Database; +namespace Web.Api.IntegrationTests.Infrastructure.Database; public class AppointerDbContextShould : MsSqlContainerStartup { From 155536d7094d2361d85d344a7d118359a92fb2c2 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 13 Jun 2024 16:17:13 +1000 Subject: [PATCH 4/9] feat: add unit test for command validation --- Appointer.sln | 7 ++++ .../Features/CreateAccountShould.cs | 29 +++++++++++++++ Web.Api.UnitTests/Web.Api.UnitTests.csproj | 35 +++++++++++++++++++ .../Features/Accounts/CreateAccount.cs | 20 ++++++++--- src/Web.Api/Features/Accounts/GetAccount.cs | 2 +- .../Database/AppointerDbContext.cs | 3 +- src/Web.Api/Program.cs | 2 ++ src/Web.Api/Web.Api.csproj | 2 ++ .../Features/AccountsShould.cs | 4 +-- .../Web.Api.IntegrationTests.csproj | 8 ++--- 10 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 Web.Api.UnitTests/Features/CreateAccountShould.cs create mode 100644 Web.Api.UnitTests/Web.Api.UnitTests.csproj diff --git a/Appointer.sln b/Appointer.sln index c834047..6033ba1 100644 --- a/Appointer.sln +++ b/Appointer.sln @@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{4A648E15 .github\workflows\dotnet.yml = .github\workflows\dotnet.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web.Api.UnitTests", "Web.Api.UnitTests\Web.Api.UnitTests.csproj", "{CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {1F302DCD-2100-42CA-850A-570C30230FDF}.Debug|Any CPU.Build.0 = Debug|Any CPU {1F302DCD-2100-42CA-850A-570C30230FDF}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F302DCD-2100-42CA-850A-570C30230FDF}.Release|Any CPU.Build.0 = Release|Any CPU + {CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -42,6 +48,7 @@ Global GlobalSection(NestedProjects) = preSolution {F7E76506-B0E8-4277-9EF1-958FA786F3D5} = {0822B695-D2DF-4E72-BCAD-DD767655E9C4} {1F302DCD-2100-42CA-850A-570C30230FDF} = {5E191623-BFC0-4FEC-93A9-047999771EFE} + {CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA} = {5E191623-BFC0-4FEC-93A9-047999771EFE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C31453C5-23E9-43FB-A7A8-D30FBE018ACC} diff --git a/Web.Api.UnitTests/Features/CreateAccountShould.cs b/Web.Api.UnitTests/Features/CreateAccountShould.cs new file mode 100644 index 0000000..7af7ab5 --- /dev/null +++ b/Web.Api.UnitTests/Features/CreateAccountShould.cs @@ -0,0 +1,29 @@ +using FluentAssertions; +using FluentValidation; +using Moq; +using Web.Api.Features.Accounts; +using Web.Api.Infrastructure.Database; + +namespace Web.Api.UnitTests.Features +{ + public class CreateAccountShould + { + [Fact] + public async Task ValidateCommand() + { + // arrange + var validator = new CreateAccount.Validator(); + var mockDbContext = new Mock(); + var handler = new CreateAccount.Handler(mockDbContext.Object, validator); + var command = new CreateAccount.Command(""); + + // act + var result = await handler.Handle(command, default); + + // assert + result.IsFailure.Should().BeTrue(); + result.Error.Code.Should().Contain("Validation"); + result.Error.Message.Should().NotBeEmpty(); + } + } +} \ No newline at end of file diff --git a/Web.Api.UnitTests/Web.Api.UnitTests.csproj b/Web.Api.UnitTests/Web.Api.UnitTests.csproj new file mode 100644 index 0000000..a4aba69 --- /dev/null +++ b/Web.Api.UnitTests/Web.Api.UnitTests.csproj @@ -0,0 +1,35 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/src/Web.Api/Features/Accounts/CreateAccount.cs b/src/Web.Api/Features/Accounts/CreateAccount.cs index 3016d22..e41d2a8 100644 --- a/src/Web.Api/Features/Accounts/CreateAccount.cs +++ b/src/Web.Api/Features/Accounts/CreateAccount.cs @@ -29,7 +29,7 @@ public void AddRoutes(IEndpointRouteBuilder app) public static class CreateAccount { - public record Command(string Fullname) : IRequest>; + public record Command(string FullName) : IRequest>; public record CreateAccountResponse(Guid Id, string FullName); @@ -37,22 +37,32 @@ public class Validator : AbstractValidator { public Validator() { - RuleFor(c => c.Fullname).NotEmpty(); + RuleFor(c => c.FullName).NotEmpty(); } } - internal sealed class Handler : IRequestHandler> + public sealed class Handler : IRequestHandler> { private readonly AppointerDbContext _dbContext; + private readonly IValidator _validator; - public Handler(AppointerDbContext dbContext) + public Handler(AppointerDbContext dbContext, IValidator validator) { _dbContext = dbContext; + _validator = validator; } public async Task> Handle(Command request, CancellationToken cancellationToken) { - var userAccount = new UserAccount(Guid.NewGuid(), request.Fullname); + var validationResult = _validator.Validate(request); + if (!validationResult.IsValid) + { + return Result.Failure(new Error( + "CreateAccount.Validation", + validationResult.ToString())); + } + + var userAccount = new UserAccount(Guid.NewGuid(), request.FullName); await _dbContext.UserAccounts.AddAsync(userAccount, cancellationToken); await _dbContext.SaveChangesAsync(cancellationToken); return new CreateAccountResponse(userAccount.Id, userAccount.FullName); diff --git a/src/Web.Api/Features/Accounts/GetAccount.cs b/src/Web.Api/Features/Accounts/GetAccount.cs index 9cd46b4..67d4993 100644 --- a/src/Web.Api/Features/Accounts/GetAccount.cs +++ b/src/Web.Api/Features/Accounts/GetAccount.cs @@ -30,7 +30,7 @@ public record Query(Guid Id) : IRequest>; public record GetAccountResponse(Guid Id, string FullName); - internal sealed class Handler : IRequestHandler> + public sealed class Handler : IRequestHandler> { private readonly AppointerDbContext _dbContext; diff --git a/src/Web.Api/Infrastructure/Database/AppointerDbContext.cs b/src/Web.Api/Infrastructure/Database/AppointerDbContext.cs index e9fbbb0..154d1c3 100644 --- a/src/Web.Api/Infrastructure/Database/AppointerDbContext.cs +++ b/src/Web.Api/Infrastructure/Database/AppointerDbContext.cs @@ -6,6 +6,7 @@ namespace Web.Api.Infrastructure.Database; public class AppointerDbContext : DbContext { + public AppointerDbContext(){} public AppointerDbContext(DbContextOptions options) : base(options) { } @@ -16,5 +17,5 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } - public DbSet UserAccounts { get; set; } + public virtual DbSet UserAccounts { get; set; } } \ No newline at end of file diff --git a/src/Web.Api/Program.cs b/src/Web.Api/Program.cs index b231e28..2500302 100644 --- a/src/Web.Api/Program.cs +++ b/src/Web.Api/Program.cs @@ -1,4 +1,5 @@ using Carter; +using FluentValidation; using Web.Api.Infrastructure; var builder = WebApplication.CreateBuilder(args); @@ -10,6 +11,7 @@ var assembly = typeof(Web.Api.Program).Assembly; builder.Services.AddMediatR(config => config.RegisterServicesFromAssembly(assembly)); builder.Services.AddCarter(); +builder.Services.AddValidatorsFromAssembly(assembly); var app = builder.Build(); diff --git a/src/Web.Api/Web.Api.csproj b/src/Web.Api/Web.Api.csproj index 7fc43a8..0084168 100644 --- a/src/Web.Api/Web.Api.csproj +++ b/src/Web.Api/Web.Api.csproj @@ -15,8 +15,10 @@ + + diff --git a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs index fdae155..b8b243b 100644 --- a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs +++ b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs @@ -26,7 +26,7 @@ public async Task BeCreatedAndPersisted() var client = _factory.CreateClient(); var url = "api/accounts"; var fullName = "Full Name"; - var request = new CreateAccount.Command(fullName); + var request = new CreateAccount.Command(FullName: fullName); // act var result = await client.PostAsJsonAsync(url, request); @@ -42,7 +42,7 @@ public async Task BeCreatedAndPersisted() var dbContext = scope.ServiceProvider.GetRequiredService(); var newUserAccount = await dbContext.UserAccounts.FindAsync(response.Id); newUserAccount.Should().NotBeNull(); - newUserAccount.FullName.Should().Be(fullName); + newUserAccount?.FullName.Should().Be(fullName); } [Fact] diff --git a/tests/Web.Api.IntegrationTests/Web.Api.IntegrationTests.csproj b/tests/Web.Api.IntegrationTests/Web.Api.IntegrationTests.csproj index 0a53299..e4ae932 100644 --- a/tests/Web.Api.IntegrationTests/Web.Api.IntegrationTests.csproj +++ b/tests/Web.Api.IntegrationTests/Web.Api.IntegrationTests.csproj @@ -11,12 +11,12 @@ - - + + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all From 9f4d5ee68bc72296f39563b38beb9858b023fd9b Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 13 Jun 2024 16:57:52 +1000 Subject: [PATCH 5/9] feat: add architecture tests --- Appointer.sln | 7 ++++ .../Web.Api.ArchitectureTests/DomainTests.cs | 39 +++++++++++++++++++ .../InfrastructureTests.cs | 25 ++++++++++++ .../Web.Api.ArchitectureTests.csproj | 29 ++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 tests/Web.Api.ArchitectureTests/DomainTests.cs create mode 100644 tests/Web.Api.ArchitectureTests/InfrastructureTests.cs create mode 100644 tests/Web.Api.ArchitectureTests/Web.Api.ArchitectureTests.csproj diff --git a/Appointer.sln b/Appointer.sln index 6033ba1..d7a3386 100644 --- a/Appointer.sln +++ b/Appointer.sln @@ -23,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{4A648E15 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web.Api.UnitTests", "Web.Api.UnitTests\Web.Api.UnitTests.csproj", "{CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web.Api.ArchitectureTests", "tests\Web.Api.ArchitectureTests\Web.Api.ArchitectureTests.csproj", "{8C310E57-8D96-4B6A-B13A-B3AACC62F220}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,10 @@ Global {CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}.Release|Any CPU.Build.0 = Release|Any CPU + {8C310E57-8D96-4B6A-B13A-B3AACC62F220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C310E57-8D96-4B6A-B13A-B3AACC62F220}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C310E57-8D96-4B6A-B13A-B3AACC62F220}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C310E57-8D96-4B6A-B13A-B3AACC62F220}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -49,6 +55,7 @@ Global {F7E76506-B0E8-4277-9EF1-958FA786F3D5} = {0822B695-D2DF-4E72-BCAD-DD767655E9C4} {1F302DCD-2100-42CA-850A-570C30230FDF} = {5E191623-BFC0-4FEC-93A9-047999771EFE} {CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA} = {5E191623-BFC0-4FEC-93A9-047999771EFE} + {8C310E57-8D96-4B6A-B13A-B3AACC62F220} = {5E191623-BFC0-4FEC-93A9-047999771EFE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C31453C5-23E9-43FB-A7A8-D30FBE018ACC} diff --git a/tests/Web.Api.ArchitectureTests/DomainTests.cs b/tests/Web.Api.ArchitectureTests/DomainTests.cs new file mode 100644 index 0000000..3d5cfd0 --- /dev/null +++ b/tests/Web.Api.ArchitectureTests/DomainTests.cs @@ -0,0 +1,39 @@ +using System.Reflection; +using FluentAssertions; +using NetArchTest.Rules; + +namespace Web.Api.ArchitectureTests +{ + public class DomainTests + { + private readonly Assembly _assembly = typeof(Program).Assembly; + + [Fact] + public void DomainClasses_ShouldNotDependOnInfrastructure() + { + var result = Types.InAssembly(_assembly) + .That() + .ResideInNamespace("Web.Api.Domain") + .Should() + .NotHaveDependencyOn("Web.Api.Infrastructure") + .GetResult() + .IsSuccessful; + + result.Should().BeTrue(); + } + + [Fact] + public void DomainClasses_ShouldNotDependOnFeatures() + { + var result = Types.InAssembly(_assembly) + .That() + .ResideInNamespace("Web.Api.Domain") + .Should() + .NotHaveDependencyOn("Web.Api.Features") + .GetResult() + .IsSuccessful; + + result.Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/tests/Web.Api.ArchitectureTests/InfrastructureTests.cs b/tests/Web.Api.ArchitectureTests/InfrastructureTests.cs new file mode 100644 index 0000000..17b8f01 --- /dev/null +++ b/tests/Web.Api.ArchitectureTests/InfrastructureTests.cs @@ -0,0 +1,25 @@ +using System.Reflection; +using FluentAssertions; +using NetArchTest.Rules; + +namespace Web.Api.ArchitectureTests +{ + public class InfrastructureTests + { + private readonly Assembly _assembly = typeof(Program).Assembly; + + [Fact] + public void DomainClasses_ShouldNotDependOnInfrastructure() + { + var result = Types.InAssembly(_assembly) + .That() + .ResideInNamespace("Web.Api.Infrastructure") + .Should() + .NotHaveDependencyOn("Web.Api.Features") + .GetResult() + .IsSuccessful; + + result.Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/tests/Web.Api.ArchitectureTests/Web.Api.ArchitectureTests.csproj b/tests/Web.Api.ArchitectureTests/Web.Api.ArchitectureTests.csproj new file mode 100644 index 0000000..28ce442 --- /dev/null +++ b/tests/Web.Api.ArchitectureTests/Web.Api.ArchitectureTests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + From 51da0b88f2cd773c41e912a430fe97e3561a4d05 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 14 Jun 2024 12:48:53 +1000 Subject: [PATCH 6/9] feat: add domain event base --- .../Features/CreateAccountShould.cs | 3 +- src/Web.Api/Common/Entity.cs | 28 +++++++++++++++++++ src/Web.Api/Common/IDomainEvent.cs | 7 +++++ .../Features/Accounts/CreateAccount.cs | 5 ++-- .../Helpers/ServiceCollectionExtensions.cs | 4 +-- 5 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 src/Web.Api/Common/Entity.cs create mode 100644 src/Web.Api/Common/IDomainEvent.cs diff --git a/Web.Api.UnitTests/Features/CreateAccountShould.cs b/Web.Api.UnitTests/Features/CreateAccountShould.cs index 7af7ab5..24e688a 100644 --- a/Web.Api.UnitTests/Features/CreateAccountShould.cs +++ b/Web.Api.UnitTests/Features/CreateAccountShould.cs @@ -1,5 +1,4 @@ using FluentAssertions; -using FluentValidation; using Moq; using Web.Api.Features.Accounts; using Web.Api.Infrastructure.Database; @@ -23,7 +22,7 @@ public async Task ValidateCommand() // assert result.IsFailure.Should().BeTrue(); result.Error.Code.Should().Contain("Validation"); - result.Error.Message.Should().NotBeEmpty(); + result.Error.Message.Should().Contain("Full Name"); } } } \ No newline at end of file diff --git a/src/Web.Api/Common/Entity.cs b/src/Web.Api/Common/Entity.cs new file mode 100644 index 0000000..0631492 --- /dev/null +++ b/src/Web.Api/Common/Entity.cs @@ -0,0 +1,28 @@ +namespace Web.Api.Common; + +public abstract class Entity +{ + private readonly List _domainEvents = new(); + + protected Entity(Guid id) + { + Id = id; + } + protected Entity() + { + } + + public Guid Id { get; set; } + public List DomainEvents => _domainEvents.ToList(); + + public void ClearDomainEvents() + { + _domainEvents.Clear(); + } + + protected void Raise(IDomainEvent domainEvent) + { + _domainEvents.Add(domainEvent); + } + +} diff --git a/src/Web.Api/Common/IDomainEvent.cs b/src/Web.Api/Common/IDomainEvent.cs new file mode 100644 index 0000000..3a8699e --- /dev/null +++ b/src/Web.Api/Common/IDomainEvent.cs @@ -0,0 +1,7 @@ +using MediatR; + +namespace Web.Api.Common; + +public interface IDomainEvent : INotification +{ +} \ No newline at end of file diff --git a/src/Web.Api/Features/Accounts/CreateAccount.cs b/src/Web.Api/Features/Accounts/CreateAccount.cs index e41d2a8..c7d7eba 100644 --- a/src/Web.Api/Features/Accounts/CreateAccount.cs +++ b/src/Web.Api/Features/Accounts/CreateAccount.cs @@ -14,8 +14,7 @@ public void AddRoutes(IEndpointRouteBuilder app) { app.MapPost("api/accounts", async (CreateAccount.Command request, ISender sender, CancellationToken cancellationToken) => { - var command = request.Adapt(); - var result = await sender.Send(command, cancellationToken); + var result = await sender.Send(request, cancellationToken); if (result.IsFailure) { @@ -54,7 +53,7 @@ public Handler(AppointerDbContext dbContext, IValidator validator) public async Task> Handle(Command request, CancellationToken cancellationToken) { - var validationResult = _validator.Validate(request); + var validationResult = await _validator.ValidateAsync(request, cancellationToken); if (!validationResult.IsValid) { return Result.Failure(new Error( diff --git a/tests/Web.Api.IntegrationTests/Helpers/ServiceCollectionExtensions.cs b/tests/Web.Api.IntegrationTests/Helpers/ServiceCollectionExtensions.cs index f0ff817..386aead 100644 --- a/tests/Web.Api.IntegrationTests/Helpers/ServiceCollectionExtensions.cs +++ b/tests/Web.Api.IntegrationTests/Helpers/ServiceCollectionExtensions.cs @@ -5,13 +5,13 @@ namespace Web.Api.IntegrationTests.Helpers; public static class ServiceCollectionExtensions { - public static void RemoveDbContext(this IServiceCollection services) where T : Microsoft.EntityFrameworkCore.DbContext + public static void RemoveDbContext(this IServiceCollection services) where T : DbContext { var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); if (descriptor != null) services.Remove(descriptor); } - public static void EnsureDbCreated(this IServiceCollection services) where T : Microsoft.EntityFrameworkCore.DbContext + public static void EnsureDbCreated(this IServiceCollection services) where T : DbContext { var serviceProvider = services.BuildServiceProvider(); using var scope = serviceProvider.CreateScope(); From a1d9edf8f71084410a45794d1fb961c918b09c90 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 14 Jun 2024 13:07:47 +1000 Subject: [PATCH 7/9] feat: add factory method for user account --- src/Web.Api/Common/Entity.cs | 2 +- src/Web.Api/Domain/Accounts/UserAccount.cs | 31 ++++++++++++++++++- .../Accounts/UserAccountCreatedDomainEvent.cs | 5 +++ .../Features/Accounts/CreateAccount.cs | 2 +- .../Features/AccountsShould.cs | 2 +- .../Database/AppointerDbContextShould.cs | 2 +- 6 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 src/Web.Api/Domain/Accounts/UserAccountCreatedDomainEvent.cs diff --git a/src/Web.Api/Common/Entity.cs b/src/Web.Api/Common/Entity.cs index 0631492..a2998d0 100644 --- a/src/Web.Api/Common/Entity.cs +++ b/src/Web.Api/Common/Entity.cs @@ -20,7 +20,7 @@ public void ClearDomainEvents() _domainEvents.Clear(); } - protected void Raise(IDomainEvent domainEvent) + protected void RaiseDomainEvent(IDomainEvent domainEvent) { _domainEvents.Add(domainEvent); } diff --git a/src/Web.Api/Domain/Accounts/UserAccount.cs b/src/Web.Api/Domain/Accounts/UserAccount.cs index 2ead0d3..c1014c5 100644 --- a/src/Web.Api/Domain/Accounts/UserAccount.cs +++ b/src/Web.Api/Domain/Accounts/UserAccount.cs @@ -1,3 +1,32 @@ +using Web.Api.Common; + namespace Web.Api.Domain.Accounts; -public record UserAccount(Guid Id, string FullName, bool IsActive = true, bool IsDeleted = false); \ No newline at end of file +public sealed class UserAccount : Entity +{ + private UserAccount(Guid id, string fullName, bool isActive, bool isDeleted) + { + Id = id; + FullName = fullName; + IsActive = isActive; + IsDeleted = isDeleted; + } + + private UserAccount() + { + } + + public Guid Id { get; private set; } + public string FullName { get; private set; } + public bool IsActive { get; private set; } + public bool IsDeleted { get; private set; } + + public static UserAccount Create(string fullName) + { + var userAccount = new UserAccount(Guid.NewGuid(), fullName, true, false); + + userAccount.RaiseDomainEvent(new UserAccountCreatedDomainEvent(userAccount.Id)); + + return userAccount; + } +} diff --git a/src/Web.Api/Domain/Accounts/UserAccountCreatedDomainEvent.cs b/src/Web.Api/Domain/Accounts/UserAccountCreatedDomainEvent.cs new file mode 100644 index 0000000..a4790c4 --- /dev/null +++ b/src/Web.Api/Domain/Accounts/UserAccountCreatedDomainEvent.cs @@ -0,0 +1,5 @@ +using Web.Api.Common; + +namespace Web.Api.Domain.Accounts; + +public sealed record UserAccountCreatedDomainEvent(Guid UserAccountId) : IDomainEvent; \ No newline at end of file diff --git a/src/Web.Api/Features/Accounts/CreateAccount.cs b/src/Web.Api/Features/Accounts/CreateAccount.cs index c7d7eba..88262d2 100644 --- a/src/Web.Api/Features/Accounts/CreateAccount.cs +++ b/src/Web.Api/Features/Accounts/CreateAccount.cs @@ -61,7 +61,7 @@ public async Task> Handle(Command request, Cancell validationResult.ToString())); } - var userAccount = new UserAccount(Guid.NewGuid(), request.FullName); + var userAccount = UserAccount.Create(request.FullName); await _dbContext.UserAccounts.AddAsync(userAccount, cancellationToken); await _dbContext.SaveChangesAsync(cancellationToken); return new CreateAccountResponse(userAccount.Id, userAccount.FullName); diff --git a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs index b8b243b..1e2214a 100644 --- a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs +++ b/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs @@ -55,7 +55,7 @@ public async Task GetExistingAccount() using var scope = _factory.Services.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); - var userAccount = new UserAccount(Guid.NewGuid(), "Existing User"); + var userAccount = UserAccount.Create("John Doe"); await dbContext.UserAccounts.AddAsync(userAccount, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); diff --git a/tests/Web.Api.IntegrationTests/Infrastructure/Database/AppointerDbContextShould.cs b/tests/Web.Api.IntegrationTests/Infrastructure/Database/AppointerDbContextShould.cs index 743eadf..52e1b71 100644 --- a/tests/Web.Api.IntegrationTests/Infrastructure/Database/AppointerDbContextShould.cs +++ b/tests/Web.Api.IntegrationTests/Infrastructure/Database/AppointerDbContextShould.cs @@ -13,7 +13,7 @@ public async Task PersistUserAccount() { // arrange var dbContext = Services.GetRequiredService(); - var userAccount = new UserAccount(Guid.NewGuid(), "Full Name"); + var userAccount = UserAccount.Create("John Doe"); // act dbContext.UserAccounts.Add(userAccount); From 4a0bf7935687268483b8378c43edaaf3cd592a36 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 14 Jun 2024 15:49:17 +1000 Subject: [PATCH 8/9] refactor: move unit tests to test folder --- Appointer.sln | 2 +- .../Web.Api.UnitTests}/Features/CreateAccountShould.cs | 0 .../Web.Api.UnitTests}/Web.Api.UnitTests.csproj | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename {Web.Api.UnitTests => tests/Web.Api.UnitTests}/Features/CreateAccountShould.cs (100%) rename {Web.Api.UnitTests => tests/Web.Api.UnitTests}/Web.Api.UnitTests.csproj (94%) diff --git a/Appointer.sln b/Appointer.sln index d7a3386..e4f4fa3 100644 --- a/Appointer.sln +++ b/Appointer.sln @@ -21,7 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{4A648E15 .github\workflows\dotnet.yml = .github\workflows\dotnet.yml EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web.Api.UnitTests", "Web.Api.UnitTests\Web.Api.UnitTests.csproj", "{CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web.Api.UnitTests", "tests\Web.Api.UnitTests\Web.Api.UnitTests.csproj", "{CBDC7AB2-99B8-4D88-BF1A-B416E2C004FA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web.Api.ArchitectureTests", "tests\Web.Api.ArchitectureTests\Web.Api.ArchitectureTests.csproj", "{8C310E57-8D96-4B6A-B13A-B3AACC62F220}" EndProject diff --git a/Web.Api.UnitTests/Features/CreateAccountShould.cs b/tests/Web.Api.UnitTests/Features/CreateAccountShould.cs similarity index 100% rename from Web.Api.UnitTests/Features/CreateAccountShould.cs rename to tests/Web.Api.UnitTests/Features/CreateAccountShould.cs diff --git a/Web.Api.UnitTests/Web.Api.UnitTests.csproj b/tests/Web.Api.UnitTests/Web.Api.UnitTests.csproj similarity index 94% rename from Web.Api.UnitTests/Web.Api.UnitTests.csproj rename to tests/Web.Api.UnitTests/Web.Api.UnitTests.csproj index a4aba69..18922ae 100644 --- a/Web.Api.UnitTests/Web.Api.UnitTests.csproj +++ b/tests/Web.Api.UnitTests/Web.Api.UnitTests.csproj @@ -25,7 +25,7 @@ - + From 008c181d4adef6807210de93718d3766af5aca53 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 14 Jun 2024 16:27:08 +1000 Subject: [PATCH 9/9] feat: add calendar entity --- src/Web.Api/Domain/Accounts/UserAccount.cs | 2 + src/Web.Api/Domain/Calendars/Calendar.cs | 31 ++++++++++++ .../Database/CalendarConfiguration.cs | 30 +++++++++++ .../Database/UserAccountConfiguration.cs | 7 +++ ...countsShould.cs => CreateAccountShould.cs} | 36 +++++++------ .../Features/GetAccountShould.cs | 50 +++++++++++++++++++ 6 files changed, 137 insertions(+), 19 deletions(-) create mode 100644 src/Web.Api/Domain/Calendars/Calendar.cs create mode 100644 src/Web.Api/Infrastructure/Database/CalendarConfiguration.cs rename tests/Web.Api.IntegrationTests/Features/{AccountsShould.cs => CreateAccountShould.cs} (66%) create mode 100644 tests/Web.Api.IntegrationTests/Features/GetAccountShould.cs diff --git a/src/Web.Api/Domain/Accounts/UserAccount.cs b/src/Web.Api/Domain/Accounts/UserAccount.cs index c1014c5..40a8af6 100644 --- a/src/Web.Api/Domain/Accounts/UserAccount.cs +++ b/src/Web.Api/Domain/Accounts/UserAccount.cs @@ -1,4 +1,5 @@ using Web.Api.Common; +using Web.Api.Domain.Calendars; namespace Web.Api.Domain.Accounts; @@ -18,6 +19,7 @@ private UserAccount() public Guid Id { get; private set; } public string FullName { get; private set; } + public List Calendars { get; private set; } = new List(); public bool IsActive { get; private set; } public bool IsDeleted { get; private set; } diff --git a/src/Web.Api/Domain/Calendars/Calendar.cs b/src/Web.Api/Domain/Calendars/Calendar.cs new file mode 100644 index 0000000..a0174e2 --- /dev/null +++ b/src/Web.Api/Domain/Calendars/Calendar.cs @@ -0,0 +1,31 @@ +using Web.Api.Common; +using Web.Api.Domain.Accounts; + +namespace Web.Api.Domain.Calendars; + +public sealed class Calendar : Entity +{ + private Calendar(Guid id, string name) + { + Id = id; + Name = name; + } + + private Calendar() + { + } + + public Guid Id { get; private set; } + public string Name { get; private set; } + public Guid UserAccountId { get; private set; } + public UserAccount UserAccount { get; private set; } = null!; + + public static Calendar Create(string fullName) + { + var calendar = new Calendar(Guid.NewGuid(), fullName); + + // userAccount.RaiseDomainEvent(new UserAccountCreatedDomainEvent(userAccount.Id)); + + return calendar; + } +} diff --git a/src/Web.Api/Infrastructure/Database/CalendarConfiguration.cs b/src/Web.Api/Infrastructure/Database/CalendarConfiguration.cs new file mode 100644 index 0000000..ab2156d --- /dev/null +++ b/src/Web.Api/Infrastructure/Database/CalendarConfiguration.cs @@ -0,0 +1,30 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Web.Api.Domain.Accounts; +using Web.Api.Domain.Calendars; + +namespace Web.Api.Infrastructure.Database; + +public class CalendarConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder + .HasKey(x => x.Id); + + builder + .Property(x => x.Id) + .ValueGeneratedNever(); + + builder + .Property(x => x.Name) + .IsRequired(); + + builder + .HasOne(x => x.UserAccount) + .WithMany(x => x.Calendars) + .HasForeignKey(x => x.UserAccountId) + .IsRequired(); + + } +} \ No newline at end of file diff --git a/src/Web.Api/Infrastructure/Database/UserAccountConfiguration.cs b/src/Web.Api/Infrastructure/Database/UserAccountConfiguration.cs index e472e50..891b3e1 100644 --- a/src/Web.Api/Infrastructure/Database/UserAccountConfiguration.cs +++ b/src/Web.Api/Infrastructure/Database/UserAccountConfiguration.cs @@ -26,5 +26,12 @@ public void Configure(EntityTypeBuilder builder) builder .Property(x => x.IsDeleted) .HasDefaultValue(false); + + builder + .HasMany(x => x.Calendars) + .WithOne(x => x.UserAccount) + .HasForeignKey(x => x.UserAccountId) + .IsRequired(); + } } \ No newline at end of file diff --git a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs b/tests/Web.Api.IntegrationTests/Features/CreateAccountShould.cs similarity index 66% rename from tests/Web.Api.IntegrationTests/Features/AccountsShould.cs rename to tests/Web.Api.IntegrationTests/Features/CreateAccountShould.cs index 1e2214a..b1068c1 100644 --- a/tests/Web.Api.IntegrationTests/Features/AccountsShould.cs +++ b/tests/Web.Api.IntegrationTests/Features/CreateAccountShould.cs @@ -3,24 +3,23 @@ using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; -using Web.Api.Domain.Accounts; using Web.Api.Features.Accounts; using Web.Api.Infrastructure.Database; using Web.Api.IntegrationTests.Helpers; namespace Web.Api.IntegrationTests.Features; -public class AccountsShould : IClassFixture> +public class CreateAccountShould : IClassFixture> { private readonly WebApplicationFactory _factory; - public AccountsShould(AppointerWebApplicationFactory factory) + public CreateAccountShould(AppointerWebApplicationFactory factory) { _factory = factory; } [Fact] - public async Task BeCreatedAndPersisted() + public async Task CreateAndPersistAccount() { // arrange var client = _factory.CreateClient(); @@ -46,30 +45,29 @@ public async Task BeCreatedAndPersisted() } [Fact] - public async Task GetExistingAccount() + public async Task CreateDefaultCalendar() { // arrange - var cts = new CancellationTokenSource(); - cts.CancelAfter(TimeSpan.FromSeconds(30)); - var cancellationToken = cts.Token; - - using var scope = _factory.Services.CreateScope(); - var dbContext = scope.ServiceProvider.GetRequiredService(); - var userAccount = UserAccount.Create("John Doe"); - await dbContext.UserAccounts.AddAsync(userAccount, cancellationToken); - await dbContext.SaveChangesAsync(cancellationToken); - var client = _factory.CreateClient(); - var url = "api/accounts/" + userAccount.Id; + var url = "api/accounts"; + var fullName = "Default Calendar" + Guid.NewGuid().ToString(); + var request = new CreateAccount.Command(FullName: fullName); // act - var result = await client.GetAsync(url, cancellationToken); + var result = await client.PostAsJsonAsync(url, request); // assert result.StatusCode.Should().Be(HttpStatusCode.OK); - var response = await result.Content.ReadFromJsonAsync(cancellationToken); + var response = await result.Content.ReadFromJsonAsync(); response.Should().NotBeNull(); response!.Id.Should().NotBeEmpty(); - response.FullName.Should().Be(userAccount.FullName); + + using var scope = _factory.Services.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var newUserAccount = await dbContext.UserAccounts.FindAsync(response.Id); + newUserAccount.Should().NotBeNull(); + newUserAccount?.FullName.Should().Be(fullName); + + } } \ No newline at end of file diff --git a/tests/Web.Api.IntegrationTests/Features/GetAccountShould.cs b/tests/Web.Api.IntegrationTests/Features/GetAccountShould.cs new file mode 100644 index 0000000..b6771d5 --- /dev/null +++ b/tests/Web.Api.IntegrationTests/Features/GetAccountShould.cs @@ -0,0 +1,50 @@ +using System.Net; +using System.Net.Http.Json; +using FluentAssertions; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Web.Api.Domain.Accounts; +using Web.Api.Domain.Calendars; +using Web.Api.Features.Accounts; +using Web.Api.Infrastructure.Database; +using Web.Api.IntegrationTests.Helpers; + +namespace Web.Api.IntegrationTests.Features; + +public class GetAccountShould : IClassFixture> +{ + private readonly WebApplicationFactory _factory; + + public GetAccountShould(AppointerWebApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetExistingAccount() + { + // arrange + var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromSeconds(30)); + var cancellationToken = cts.Token; + + using var scope = _factory.Services.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var userAccount = UserAccount.Create("John Doe"); + await dbContext.UserAccounts.AddAsync(userAccount, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + + var client = _factory.CreateClient(); + var url = "api/accounts/" + userAccount.Id; + + // act + var result = await client.GetAsync(url, cancellationToken); + + // assert + result.StatusCode.Should().Be(HttpStatusCode.OK); + var response = await result.Content.ReadFromJsonAsync(cancellationToken); + response.Should().NotBeNull(); + response!.Id.Should().NotBeEmpty(); + response.FullName.Should().Be(userAccount.FullName); + } +} \ No newline at end of file