0
0
mirror of https://github.com/alex289/CleanArchitecture.git synced 2025-06-30 02:31:08 +00:00

chore: Code Cleanup

This commit is contained in:
alex289 2023-08-30 23:36:48 +02:00
parent a3152580a2
commit 61bcab6d77
No known key found for this signature in database
GPG Key ID: 573F77CD2D87F863
98 changed files with 487 additions and 486 deletions

View File

@ -7,26 +7,26 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.ApplicationStatus" Version="7.0.0" /> <PackageReference Include="AspNetCore.HealthChecks.ApplicationStatus" Version="7.0.0"/>
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="7.0.0" /> <PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="7.0.0"/>
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="7.1.0" /> <PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="7.1.0"/>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.10" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.10"/>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.10" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10"/>
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.10" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.10"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0" /> <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.5.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" /> <ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
<ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj" /> <ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" /> <ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -18,14 +18,14 @@ namespace CleanArchitecture.Api.Controllers;
public sealed class TenantController : ApiController public sealed class TenantController : ApiController
{ {
private readonly ITenantService _tenantService; private readonly ITenantService _tenantService;
public TenantController( public TenantController(
INotificationHandler<DomainNotification> notifications, INotificationHandler<DomainNotification> notifications,
ITenantService tenantService) : base(notifications) ITenantService tenantService) : base(notifications)
{ {
_tenantService = tenantService; _tenantService = tenantService;
} }
[HttpGet] [HttpGet]
[SwaggerOperation("Get a list of all tenants")] [SwaggerOperation("Get a list of all tenants")]
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<IEnumerable<TenantViewModel>>))] [SwaggerResponse(200, "Request successful", typeof(ResponseMessage<IEnumerable<TenantViewModel>>))]
@ -34,7 +34,7 @@ public sealed class TenantController : ApiController
var tenants = await _tenantService.GetAllTenantsAsync(); var tenants = await _tenantService.GetAllTenantsAsync();
return Response(tenants); return Response(tenants);
} }
[HttpGet("{id:guid}")] [HttpGet("{id:guid}")]
[SwaggerOperation("Get a tenant by id")] [SwaggerOperation("Get a tenant by id")]
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<TenantViewModel>))] [SwaggerResponse(200, "Request successful", typeof(ResponseMessage<TenantViewModel>))]
@ -45,7 +45,7 @@ public sealed class TenantController : ApiController
var tenant = await _tenantService.GetTenantByIdAsync(id, isDeleted); var tenant = await _tenantService.GetTenantByIdAsync(id, isDeleted);
return Response(tenant); return Response(tenant);
} }
[HttpPost] [HttpPost]
[SwaggerOperation("Create a new tenant")] [SwaggerOperation("Create a new tenant")]
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))] [SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))]
@ -54,7 +54,7 @@ public sealed class TenantController : ApiController
var tenantId = await _tenantService.CreateTenantAsync(tenant); var tenantId = await _tenantService.CreateTenantAsync(tenant);
return Response(tenantId); return Response(tenantId);
} }
[HttpPut] [HttpPut]
[SwaggerOperation("Update an existing tenant")] [SwaggerOperation("Update an existing tenant")]
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<UpdateTenantViewModel>))] [SwaggerResponse(200, "Request successful", typeof(ResponseMessage<UpdateTenantViewModel>))]
@ -63,7 +63,7 @@ public sealed class TenantController : ApiController
await _tenantService.UpdateTenantAsync(tenant); await _tenantService.UpdateTenantAsync(tenant);
return Response(tenant); return Response(tenant);
} }
[HttpDelete("{id:guid}")] [HttpDelete("{id:guid}")]
[SwaggerOperation("Delete an existing tenant")] [SwaggerOperation("Delete an existing tenant")]
[SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))] [SwaggerResponse(200, "Request successful", typeof(ResponseMessage<Guid>))]

View File

@ -60,9 +60,12 @@ public static class ServiceCollectionExtension
services.AddHttpContextAccessor(); services.AddHttpContextAccessor();
services.AddAuthentication( services.AddAuthentication(
options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; })
.AddJwtBearer( .AddJwtBearer(
jwtOptions => { jwtOptions.TokenValidationParameters = CreateTokenValidationParameters(configuration); }); jwtOptions =>
{
jwtOptions.TokenValidationParameters = CreateTokenValidationParameters(configuration);
});
services services
.AddOptions<TokenSettings>() .AddOptions<TokenSettings>()
@ -90,4 +93,4 @@ public static class ServiceCollectionExtension
return result; return result;
} }
} }

View File

@ -4,9 +4,7 @@ namespace CleanArchitecture.Api.Models;
public sealed class DetailedError public sealed class DetailedError
{ {
[JsonPropertyName("code")] [JsonPropertyName("code")] public string Code { get; init; } = string.Empty;
public string Code { get; init; } = string.Empty;
[JsonPropertyName("data")] [JsonPropertyName("data")] public object? Data { get; init; }
public object? Data { get; init; }
} }

View File

@ -6,15 +6,12 @@ namespace CleanArchitecture.Api.Models;
public sealed class ResponseMessage<T> public sealed class ResponseMessage<T>
{ {
[JsonPropertyName("success")] [JsonPropertyName("success")] public bool Success { get; init; }
public bool Success { get; init; }
[JsonPropertyName("errors")] [JsonPropertyName("errors")] public IEnumerable<string>? Errors { get; init; }
public IEnumerable<string>? Errors { get; init; }
[JsonPropertyName("detailedErrors")] [JsonPropertyName("detailedErrors")]
public IEnumerable<DetailedError> DetailedErrors { get; init; } = Enumerable.Empty<DetailedError>(); public IEnumerable<DetailedError> DetailedErrors { get; init; } = Enumerable.Empty<DetailedError>();
[JsonPropertyName("data")] [JsonPropertyName("data")] public T? Data { get; init; }
public T? Data { get; init; }
} }

View File

@ -61,9 +61,9 @@ var app = builder.Build();
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
{ {
var services = scope.ServiceProvider; var services = scope.ServiceProvider;
ApplicationDbContext appDbContext = services.GetRequiredService<ApplicationDbContext>(); var appDbContext = services.GetRequiredService<ApplicationDbContext>();
EventStoreDbContext storeDbContext = services.GetRequiredService<EventStoreDbContext>(); var storeDbContext = services.GetRequiredService<EventStoreDbContext>();
DomainNotificationStoreDbContext domainStoreDbContext = services.GetRequiredService<DomainNotificationStoreDbContext>(); var domainStoreDbContext = services.GetRequiredService<DomainNotificationStoreDbContext>();
appDbContext.EnsureMigrationsApplied(); appDbContext.EnsureMigrationsApplied();

View File

@ -8,11 +8,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0"/>
<PackageReference Include="MockQueryable.NSubstitute" Version="7.0.0" /> <PackageReference Include="MockQueryable.NSubstitute" Version="7.0.0"/>
<PackageReference Include="NSubstitute" Version="5.0.0" /> <PackageReference Include="NSubstitute" Version="5.0.0"/>
<PackageReference Include="xunit" Version="2.5.0" /> <PackageReference Include="xunit" Version="2.5.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -24,8 +24,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" /> <ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -11,9 +11,9 @@ public class QueryHandlerBaseFixture
public QueryHandlerBaseFixture VerifyExistingNotification(string key, string errorCode, string message) public QueryHandlerBaseFixture VerifyExistingNotification(string key, string errorCode, string message)
{ {
Bus.Received(1).RaiseEventAsync(Arg.Is<DomainNotification>(notification => Bus.Received(1).RaiseEventAsync(Arg.Is<DomainNotification>(notification =>
notification.Key == key && notification.Key == key &&
notification.Code == errorCode && notification.Code == errorCode &&
notification.Value == message)); notification.Value == message));
return this; return this;
} }

View File

@ -10,16 +10,16 @@ namespace CleanArchitecture.Application.Tests.Fixtures.Queries.Tenants;
public sealed class GetAllTenantsTestFixture : QueryHandlerBaseFixture public sealed class GetAllTenantsTestFixture : QueryHandlerBaseFixture
{ {
public GetAllTenantsQueryHandler QueryHandler { get; }
private ITenantRepository TenantRepository { get; }
public GetAllTenantsTestFixture() public GetAllTenantsTestFixture()
{ {
TenantRepository = Substitute.For<ITenantRepository>(); TenantRepository = Substitute.For<ITenantRepository>();
QueryHandler = new(TenantRepository); QueryHandler = new GetAllTenantsQueryHandler(TenantRepository);
} }
public GetAllTenantsQueryHandler QueryHandler { get; }
private ITenantRepository TenantRepository { get; }
public Tenant SetupTenant(bool deleted = false) public Tenant SetupTenant(bool deleted = false)
{ {
var tenant = new Tenant(Guid.NewGuid(), "Tenant 1"); var tenant = new Tenant(Guid.NewGuid(), "Tenant 1");
@ -31,7 +31,7 @@ public sealed class GetAllTenantsTestFixture : QueryHandlerBaseFixture
var tenantList = new List<Tenant> { tenant }.BuildMock(); var tenantList = new List<Tenant> { tenant }.BuildMock();
TenantRepository.GetAllNoTracking().Returns(tenantList); TenantRepository.GetAllNoTracking().Returns(tenantList);
return tenant; return tenant;
} }
} }

View File

@ -10,18 +10,18 @@ namespace CleanArchitecture.Application.Tests.Fixtures.Queries.Tenants;
public sealed class GetTenantByIdTestFixture : QueryHandlerBaseFixture public sealed class GetTenantByIdTestFixture : QueryHandlerBaseFixture
{ {
public GetTenantByIdQueryHandler QueryHandler { get; }
private ITenantRepository TenantRepository { get; }
public GetTenantByIdTestFixture() public GetTenantByIdTestFixture()
{ {
TenantRepository = Substitute.For<ITenantRepository>(); TenantRepository = Substitute.For<ITenantRepository>();
QueryHandler = new( QueryHandler = new GetTenantByIdQueryHandler(
TenantRepository, TenantRepository,
Bus); Bus);
} }
public GetTenantByIdQueryHandler QueryHandler { get; }
private ITenantRepository TenantRepository { get; }
public Tenant SetupTenant(bool deleted = false) public Tenant SetupTenant(bool deleted = false)
{ {
var tenant = new Tenant(Guid.NewGuid(), "Tenant 1"); var tenant = new Tenant(Guid.NewGuid(), "Tenant 1");
@ -33,7 +33,7 @@ public sealed class GetTenantByIdTestFixture : QueryHandlerBaseFixture
var tenantList = new List<Tenant> { tenant }.BuildMock(); var tenantList = new List<Tenant> { tenant }.BuildMock();
TenantRepository.GetAllNoTracking().Returns(tenantList); TenantRepository.GetAllNoTracking().Returns(tenantList);
return tenant; return tenant;
} }
} }

View File

@ -24,13 +24,13 @@ public sealed class GetAllUsersTestFixture : QueryHandlerBaseFixture
public void SetupUserAsync() public void SetupUserAsync()
{ {
var user = new User( var user = new User(
ExistingUserId, ExistingUserId,
Guid.NewGuid(), Guid.NewGuid(),
"max@mustermann.com", "max@mustermann.com",
"Max", "Max",
"Mustermann", "Mustermann",
"Password", "Password",
UserRole.User); UserRole.User);
var query = new[] { user }.BuildMock(); var query = new[] { user }.BuildMock();
@ -40,13 +40,13 @@ public sealed class GetAllUsersTestFixture : QueryHandlerBaseFixture
public void SetupDeletedUserAsync() public void SetupDeletedUserAsync()
{ {
var user = new User( var user = new User(
ExistingUserId, ExistingUserId,
Guid.NewGuid(), Guid.NewGuid(),
"max@mustermann.com", "max@mustermann.com",
"Max", "Max",
"Mustermann", "Mustermann",
"Password", "Password",
UserRole.User); UserRole.User);
user.Delete(); user.Delete();

View File

@ -25,13 +25,13 @@ public sealed class GetUserByIdTestFixture : QueryHandlerBaseFixture
public void SetupUserAsync() public void SetupUserAsync()
{ {
var user = new User( var user = new User(
ExistingUserId, ExistingUserId,
Guid.NewGuid(), Guid.NewGuid(),
"max@mustermann.com", "max@mustermann.com",
"Max", "Max",
"Mustermann", "Mustermann",
"Password", "Password",
UserRole.User); UserRole.User);
var query = new[] { user }.BuildMock(); var query = new[] { user }.BuildMock();
@ -41,13 +41,13 @@ public sealed class GetUserByIdTestFixture : QueryHandlerBaseFixture
public void SetupDeletedUserAsync() public void SetupDeletedUserAsync()
{ {
var user = new User( var user = new User(
ExistingUserId, ExistingUserId,
Guid.NewGuid(), Guid.NewGuid(),
"max@mustermann.com", "max@mustermann.com",
"Max", "Max",
"Mustermann", "Mustermann",
"Password", "Password",
UserRole.User); UserRole.User);
user.Delete(); user.Delete();

View File

@ -10,12 +10,12 @@ namespace CleanArchitecture.Application.Tests.Queries.Tenants;
public sealed class GetAllTenantsQueryHandlerTests public sealed class GetAllTenantsQueryHandlerTests
{ {
private readonly GetAllTenantsTestFixture _fixture = new(); private readonly GetAllTenantsTestFixture _fixture = new();
[Fact] [Fact]
public async Task Should_Get_Existing_Tenant() public async Task Should_Get_Existing_Tenant()
{ {
var tenant = _fixture.SetupTenant(); var tenant = _fixture.SetupTenant();
var result = await _fixture.QueryHandler.Handle( var result = await _fixture.QueryHandler.Handle(
new GetAllTenantsQuery(), new GetAllTenantsQuery(),
default); default);
@ -24,12 +24,12 @@ public sealed class GetAllTenantsQueryHandlerTests
tenant.Should().BeEquivalentTo(result.First()); tenant.Should().BeEquivalentTo(result.First());
} }
[Fact] [Fact]
public async Task Should_Not_Get_Deleted_Tenant() public async Task Should_Not_Get_Deleted_Tenant()
{ {
_fixture.SetupTenant(true); _fixture.SetupTenant(true);
var result = await _fixture.QueryHandler.Handle( var result = await _fixture.QueryHandler.Handle(
new GetAllTenantsQuery(), new GetAllTenantsQuery(),
default); default);

View File

@ -15,7 +15,7 @@ public sealed class GetTenantByIdQueryHandlerTests
public async Task Should_Get_Existing_Tenant() public async Task Should_Get_Existing_Tenant()
{ {
var tenant = _fixture.SetupTenant(); var tenant = _fixture.SetupTenant();
var result = await _fixture.QueryHandler.Handle( var result = await _fixture.QueryHandler.Handle(
new GetTenantByIdQuery(tenant.Id, false), new GetTenantByIdQuery(tenant.Id, false),
default); default);
@ -24,12 +24,12 @@ public sealed class GetTenantByIdQueryHandlerTests
tenant.Should().BeEquivalentTo(result); tenant.Should().BeEquivalentTo(result);
} }
[Fact] [Fact]
public async Task Should_Get_Deleted_Tenant() public async Task Should_Get_Deleted_Tenant()
{ {
var tenant = _fixture.SetupTenant(true); var tenant = _fixture.SetupTenant(true);
var result = await _fixture.QueryHandler.Handle( var result = await _fixture.QueryHandler.Handle(
new GetTenantByIdQuery(tenant.Id, true), new GetTenantByIdQuery(tenant.Id, true),
default); default);
@ -38,12 +38,12 @@ public sealed class GetTenantByIdQueryHandlerTests
tenant.Should().BeEquivalentTo(result); tenant.Should().BeEquivalentTo(result);
} }
[Fact] [Fact]
public async Task Should_Not_Get_Deleted_Tenant() public async Task Should_Not_Get_Deleted_Tenant()
{ {
var tenant = _fixture.SetupTenant(true); var tenant = _fixture.SetupTenant(true);
var result = await _fixture.QueryHandler.Handle( var result = await _fixture.QueryHandler.Handle(
new GetTenantByIdQuery(tenant.Id, false), new GetTenantByIdQuery(tenant.Id, false),
default); default);

View File

@ -6,14 +6,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Proto\CleanArchitecture.Proto.csproj" /> <ProjectReference Include="..\CleanArchitecture.Proto\CleanArchitecture.Proto.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -30,8 +30,9 @@ public static class ServiceCollectionExtension
// Tenant // Tenant
services.AddScoped<IRequestHandler<GetTenantByIdQuery, TenantViewModel?>, GetTenantByIdQueryHandler>(); services.AddScoped<IRequestHandler<GetTenantByIdQuery, TenantViewModel?>, GetTenantByIdQueryHandler>();
services.AddScoped<IRequestHandler<GetAllTenantsQuery, IEnumerable<TenantViewModel>>, GetAllTenantsQueryHandler>(); services
.AddScoped<IRequestHandler<GetAllTenantsQuery, IEnumerable<TenantViewModel>>, GetAllTenantsQueryHandler>();
return services; return services;
} }
} }

View File

@ -4,4 +4,4 @@ using MediatR;
namespace CleanArchitecture.Application.Queries.Tenants.GetAll; namespace CleanArchitecture.Application.Queries.Tenants.GetAll;
public sealed record GetAllTenantsQuery() : IRequest<IEnumerable<TenantViewModel>>; public sealed record GetAllTenantsQuery : IRequest<IEnumerable<TenantViewModel>>;

View File

@ -14,8 +14,8 @@ namespace CleanArchitecture.Application.Queries.Tenants.GetTenantById;
public sealed class GetTenantByIdQueryHandler : public sealed class GetTenantByIdQueryHandler :
IRequestHandler<GetTenantByIdQuery, TenantViewModel?> IRequestHandler<GetTenantByIdQuery, TenantViewModel?>
{ {
private readonly ITenantRepository _tenantRepository;
private readonly IMediatorHandler _bus; private readonly IMediatorHandler _bus;
private readonly ITenantRepository _tenantRepository;
public GetTenantByIdQueryHandler(ITenantRepository tenantRepository, IMediatorHandler bus) public GetTenantByIdQueryHandler(ITenantRepository tenantRepository, IMediatorHandler bus)
{ {

View File

@ -24,11 +24,11 @@ public sealed class TenantService : ITenantService
public async Task<Guid> CreateTenantAsync(CreateTenantViewModel tenant) public async Task<Guid> CreateTenantAsync(CreateTenantViewModel tenant)
{ {
var tenantId = Guid.NewGuid(); var tenantId = Guid.NewGuid();
await _bus.SendCommandAsync(new CreateTenantCommand( await _bus.SendCommandAsync(new CreateTenantCommand(
tenantId, tenantId,
tenant.Name)); tenant.Name));
return tenantId; return tenantId;
} }

View File

@ -11,7 +11,7 @@ public sealed class TenantViewModel
public Guid Id { get; set; } public Guid Id { get; set; }
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public IEnumerable<UserViewModel> Users { get; set; } = new List<UserViewModel>(); public IEnumerable<UserViewModel> Users { get; set; } = new List<UserViewModel>();
public static TenantViewModel FromTenant(Tenant tenant) public static TenantViewModel FromTenant(Tenant tenant)
{ {
return new TenantViewModel return new TenantViewModel

View File

@ -23,7 +23,7 @@ public sealed class TenantsApiImplementation : TenantsApi.TenantsApiBase
ServerCallContext context) ServerCallContext context)
{ {
var idsAsGuids = new List<Guid>(request.Ids.Count); var idsAsGuids = new List<Guid>(request.Ids.Count);
foreach (var id in request.Ids) foreach (var id in request.Ids)
{ {
if (Guid.TryParse(id, out var parsed)) if (Guid.TryParse(id, out var parsed))
@ -44,7 +44,7 @@ public sealed class TenantsApiImplementation : TenantsApi.TenantsApiBase
.ToListAsync(); .ToListAsync();
var result = new GetTenantsByIdsResult(); var result = new GetTenantsByIdsResult();
result.Tenants.AddRange(tenants); result.Tenants.AddRange(tenants);
return result; return result;

View File

@ -8,11 +8,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3"/>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0"/>
<PackageReference Include="NSubstitute" Version="5.0.0" /> <PackageReference Include="NSubstitute" Version="5.0.0"/>
<PackageReference Include="xunit" Version="2.5.0" /> <PackageReference Include="xunit" Version="2.5.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -24,7 +24,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -26,12 +26,12 @@ public sealed class CreateTenantCommandHandlerTests
x.AggregateId == command.AggregateId && x.AggregateId == command.AggregateId &&
x.Name == command.Name); x.Name == command.Name);
} }
[Fact] [Fact]
public void Should_Not_Create_Tenant_Insufficient_Permissions() public void Should_Not_Create_Tenant_Insufficient_Permissions()
{ {
_fixture.SetupUser(); _fixture.SetupUser();
var command = new CreateTenantCommand( var command = new CreateTenantCommand(
Guid.NewGuid(), Guid.NewGuid(),
"Test Tenant"); "Test Tenant");
@ -46,14 +46,14 @@ public sealed class CreateTenantCommandHandlerTests
ErrorCodes.InsufficientPermissions, ErrorCodes.InsufficientPermissions,
$"No permission to create tenant {command.AggregateId}"); $"No permission to create tenant {command.AggregateId}");
} }
[Fact] [Fact]
public void Should_Not_Create_Tenant_Already_Exists() public void Should_Not_Create_Tenant_Already_Exists()
{ {
var command = new CreateTenantCommand( var command = new CreateTenantCommand(
Guid.NewGuid(), Guid.NewGuid(),
"Test Tenant"); "Test Tenant");
_fixture.SetupExistingTenant(command.AggregateId); _fixture.SetupExistingTenant(command.AggregateId);
_fixture.CommandHandler.Handle(command, default!).Wait(); _fixture.CommandHandler.Handle(command, default!).Wait();

View File

@ -8,15 +8,11 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.Tenant.CreateTenant;
public sealed class CreateTenantCommandTestFixture : CommandHandlerFixtureBase public sealed class CreateTenantCommandTestFixture : CommandHandlerFixtureBase
{ {
public CreateTenantCommandHandler CommandHandler { get;}
private ITenantRepository TenantRepository { get; }
public CreateTenantCommandTestFixture() public CreateTenantCommandTestFixture()
{ {
TenantRepository = Substitute.For<ITenantRepository>(); TenantRepository = Substitute.For<ITenantRepository>();
CommandHandler = new( CommandHandler = new CreateTenantCommandHandler(
Bus, Bus,
UnitOfWork, UnitOfWork,
NotificationHandler, NotificationHandler,
@ -24,6 +20,10 @@ public sealed class CreateTenantCommandTestFixture : CommandHandlerFixtureBase
User); User);
} }
public CreateTenantCommandHandler CommandHandler { get; }
private ITenantRepository TenantRepository { get; }
public void SetupUser() public void SetupUser()
{ {
User.GetUserRole().Returns(UserRole.User); User.GetUserRole().Returns(UserRole.User);

View File

@ -11,7 +11,7 @@ public sealed class CreateTenantCommandValidationTests :
public CreateTenantCommandValidationTests() : base(new CreateTenantCommandValidation()) public CreateTenantCommandValidationTests() : base(new CreateTenantCommandValidation())
{ {
} }
[Fact] [Fact]
public void Should_Be_Valid() public void Should_Be_Valid()
{ {
@ -19,7 +19,7 @@ public sealed class CreateTenantCommandValidationTests :
ShouldBeValid(command); ShouldBeValid(command);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Tenant_Id() public void Should_Be_Invalid_For_Empty_Tenant_Id()
{ {
@ -30,7 +30,7 @@ public sealed class CreateTenantCommandValidationTests :
DomainErrorCodes.Tenant.TenantEmptyId, DomainErrorCodes.Tenant.TenantEmptyId,
"Tenant id may not be empty"); "Tenant id may not be empty");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Tenant_Name() public void Should_Be_Invalid_For_Empty_Tenant_Name()
{ {
@ -46,7 +46,7 @@ public sealed class CreateTenantCommandValidationTests :
Guid? id = null, Guid? id = null,
string? name = null) string? name = null)
{ {
return new( return new CreateTenantCommand(
id ?? Guid.NewGuid(), id ?? Guid.NewGuid(),
name ?? "Test Tenant"); name ?? "Test Tenant");
} }

View File

@ -16,7 +16,7 @@ public sealed class DeleteTenantCommandHandlerTests
var tenant = _fixture.SetupTenant(); var tenant = _fixture.SetupTenant();
var command = new DeleteTenantCommand(tenant.Id); var command = new DeleteTenantCommand(tenant.Id);
_fixture.CommandHandler.Handle(command, default).Wait(); _fixture.CommandHandler.Handle(command, default).Wait();
_fixture _fixture
@ -24,14 +24,14 @@ public sealed class DeleteTenantCommandHandlerTests
.VerifyCommit() .VerifyCommit()
.VerifyRaisedEvent<TenantDeletedEvent>(x => x.AggregateId == tenant.Id); .VerifyRaisedEvent<TenantDeletedEvent>(x => x.AggregateId == tenant.Id);
} }
[Fact] [Fact]
public void Should_Not_Delete_Non_Existing_Tenant() public void Should_Not_Delete_Non_Existing_Tenant()
{ {
_fixture.SetupTenant(); _fixture.SetupTenant();
var command = new DeleteTenantCommand(Guid.NewGuid()); var command = new DeleteTenantCommand(Guid.NewGuid());
_fixture.CommandHandler.Handle(command, default).Wait(); _fixture.CommandHandler.Handle(command, default).Wait();
_fixture _fixture

View File

@ -7,17 +7,12 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.Tenant.DeleteTenant;
public sealed class DeleteTenantCommandTestFixture : CommandHandlerFixtureBase public sealed class DeleteTenantCommandTestFixture : CommandHandlerFixtureBase
{ {
public DeleteTenantCommandHandler CommandHandler { get;}
private ITenantRepository TenantRepository { get; }
private IUserRepository UserRepository { get; }
public DeleteTenantCommandTestFixture() public DeleteTenantCommandTestFixture()
{ {
TenantRepository = Substitute.For<ITenantRepository>(); TenantRepository = Substitute.For<ITenantRepository>();
UserRepository = Substitute.For<IUserRepository>(); UserRepository = Substitute.For<IUserRepository>();
CommandHandler = new( CommandHandler = new DeleteTenantCommandHandler(
Bus, Bus,
UnitOfWork, UnitOfWork,
NotificationHandler, NotificationHandler,
@ -26,14 +21,19 @@ public sealed class DeleteTenantCommandTestFixture : CommandHandlerFixtureBase
User); User);
} }
public DeleteTenantCommandHandler CommandHandler { get; }
private ITenantRepository TenantRepository { get; }
private IUserRepository UserRepository { get; }
public Entities.Tenant SetupTenant() public Entities.Tenant SetupTenant()
{ {
var tenant = new Entities.Tenant(Guid.NewGuid(), "TestTenant"); var tenant = new Entities.Tenant(Guid.NewGuid(), "TestTenant");
TenantRepository TenantRepository
.GetByIdAsync(Arg.Is<Guid>(y => y == tenant.Id)) .GetByIdAsync(Arg.Is<Guid>(y => y == tenant.Id))
.Returns(tenant); .Returns(tenant);
return tenant; return tenant;
} }
} }

View File

@ -11,7 +11,7 @@ public sealed class DeleteTenantCommandValidationTests :
public DeleteTenantCommandValidationTests() : base(new DeleteTenantCommandValidation()) public DeleteTenantCommandValidationTests() : base(new DeleteTenantCommandValidation())
{ {
} }
[Fact] [Fact]
public void Should_Be_Valid() public void Should_Be_Valid()
{ {
@ -19,7 +19,7 @@ public sealed class DeleteTenantCommandValidationTests :
ShouldBeValid(command); ShouldBeValid(command);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Tenant_Id() public void Should_Be_Invalid_For_Empty_Tenant_Id()
{ {
@ -30,9 +30,9 @@ public sealed class DeleteTenantCommandValidationTests :
DomainErrorCodes.Tenant.TenantEmptyId, DomainErrorCodes.Tenant.TenantEmptyId,
"Tenant id may not be empty"); "Tenant id may not be empty");
} }
private static DeleteTenantCommand CreateTestCommand(Guid? tenantId = null) private static DeleteTenantCommand CreateTestCommand(Guid? tenantId = null)
{ {
return new(tenantId ?? Guid.NewGuid()); return new DeleteTenantCommand(tenantId ?? Guid.NewGuid());
} }
} }

View File

@ -16,7 +16,7 @@ public sealed class UpdateTenantCommandHandlerTests
var command = new UpdateTenantCommand( var command = new UpdateTenantCommand(
Guid.NewGuid(), Guid.NewGuid(),
"Tenant Name"); "Tenant Name");
_fixture.SetupExistingTenant(command.AggregateId); _fixture.SetupExistingTenant(command.AggregateId);
_fixture.CommandHandler.Handle(command, default!).Wait(); _fixture.CommandHandler.Handle(command, default!).Wait();
@ -28,14 +28,14 @@ public sealed class UpdateTenantCommandHandlerTests
x.AggregateId == command.AggregateId && x.AggregateId == command.AggregateId &&
x.Name == command.Name); x.Name == command.Name);
} }
[Fact] [Fact]
public void Should_Not_Update_Tenant_Insufficient_Permissions() public void Should_Not_Update_Tenant_Insufficient_Permissions()
{ {
var command = new UpdateTenantCommand( var command = new UpdateTenantCommand(
Guid.NewGuid(), Guid.NewGuid(),
"Tenant Name"); "Tenant Name");
_fixture.SetupUser(); _fixture.SetupUser();
_fixture.CommandHandler.Handle(command, default!).Wait(); _fixture.CommandHandler.Handle(command, default!).Wait();
@ -48,14 +48,14 @@ public sealed class UpdateTenantCommandHandlerTests
ErrorCodes.InsufficientPermissions, ErrorCodes.InsufficientPermissions,
$"No permission to update tenant {command.AggregateId}"); $"No permission to update tenant {command.AggregateId}");
} }
[Fact] [Fact]
public void Should_Not_Update_Tenant_Not_Existing() public void Should_Not_Update_Tenant_Not_Existing()
{ {
var command = new UpdateTenantCommand( var command = new UpdateTenantCommand(
Guid.NewGuid(), Guid.NewGuid(),
"Tenant Name"); "Tenant Name");
_fixture.CommandHandler.Handle(command, default!).Wait(); _fixture.CommandHandler.Handle(command, default!).Wait();
_fixture _fixture

View File

@ -1,7 +1,6 @@
using System; using System;
using CleanArchitecture.Domain.Commands.Tenants.UpdateTenant; using CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Enums;
using CleanArchitecture.Domain.Interfaces;
using CleanArchitecture.Domain.Interfaces.Repositories; using CleanArchitecture.Domain.Interfaces.Repositories;
using NSubstitute; using NSubstitute;
@ -9,22 +8,22 @@ namespace CleanArchitecture.Domain.Tests.CommandHandler.Tenant.UpdateTenant;
public sealed class UpdateTenantCommandTestFixture : CommandHandlerFixtureBase public sealed class UpdateTenantCommandTestFixture : CommandHandlerFixtureBase
{ {
public UpdateTenantCommandHandler CommandHandler { get;}
private ITenantRepository TenantRepository { get; }
public UpdateTenantCommandTestFixture() public UpdateTenantCommandTestFixture()
{ {
TenantRepository = Substitute.For<ITenantRepository>(); TenantRepository = Substitute.For<ITenantRepository>();
CommandHandler = new( CommandHandler = new UpdateTenantCommandHandler(
Bus, Bus,
UnitOfWork, UnitOfWork,
NotificationHandler, NotificationHandler,
TenantRepository, TenantRepository,
User); User);
} }
public UpdateTenantCommandHandler CommandHandler { get; }
private ITenantRepository TenantRepository { get; }
public void SetupUser() public void SetupUser()
{ {
User.GetUserRole().Returns(UserRole.User); User.GetUserRole().Returns(UserRole.User);

View File

@ -11,7 +11,7 @@ public sealed class UpdateTenantCommandValidationTests :
public UpdateTenantCommandValidationTests() : base(new UpdateTenantCommandValidation()) public UpdateTenantCommandValidationTests() : base(new UpdateTenantCommandValidation())
{ {
} }
[Fact] [Fact]
public void Should_Be_Valid() public void Should_Be_Valid()
{ {
@ -19,7 +19,7 @@ public sealed class UpdateTenantCommandValidationTests :
ShouldBeValid(command); ShouldBeValid(command);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Tenant_Id() public void Should_Be_Invalid_For_Empty_Tenant_Id()
{ {
@ -30,7 +30,7 @@ public sealed class UpdateTenantCommandValidationTests :
DomainErrorCodes.Tenant.TenantEmptyId, DomainErrorCodes.Tenant.TenantEmptyId,
"Tenant id may not be empty"); "Tenant id may not be empty");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Tenant_Name() public void Should_Be_Invalid_For_Empty_Tenant_Name()
{ {
@ -46,7 +46,7 @@ public sealed class UpdateTenantCommandValidationTests :
Guid? id = null, Guid? id = null,
string? name = null) string? name = null)
{ {
return new( return new UpdateTenantCommand(
id ?? Guid.NewGuid(), id ?? Guid.NewGuid(),
name ?? "Test Tenant"); name ?? "Test Tenant");
} }

View File

@ -90,7 +90,7 @@ public sealed class ChangePasswordCommandValidationTests :
private static ChangePasswordCommand CreateTestCommand( private static ChangePasswordCommand CreateTestCommand(
string? password = null, string? newPassword = null) string? password = null, string? newPassword = null)
{ {
return new( return new ChangePasswordCommand(
password ?? "z8]tnayvd5FNLU9:]AQm", password ?? "z8]tnayvd5FNLU9:]AQm",
newPassword ?? "z8]tnayvd5FNLU9:]AQw"); newPassword ?? "z8]tnayvd5FNLU9:]AQw");
} }

View File

@ -15,7 +15,7 @@ public sealed class CreateUserCommandHandlerTests
{ {
// Todo: Fix tests // Todo: Fix tests
_fixture.SetupCurrentUser(); _fixture.SetupCurrentUser();
var user = _fixture.SetupUser(); var user = _fixture.SetupUser();
_fixture.SetupTenant(user.TenantId); _fixture.SetupTenant(user.TenantId);
@ -39,7 +39,7 @@ public sealed class CreateUserCommandHandlerTests
public void Should_Not_Create_Already_Existing_User() public void Should_Not_Create_Already_Existing_User()
{ {
_fixture.SetupCurrentUser(); _fixture.SetupCurrentUser();
var user = _fixture.SetupUser(); var user = _fixture.SetupUser();
var command = new CreateUserCommand( var command = new CreateUserCommand(
@ -60,12 +60,12 @@ public sealed class CreateUserCommandHandlerTests
DomainErrorCodes.User.UserAlreadyExists, DomainErrorCodes.User.UserAlreadyExists,
$"There is already a user with Id {command.UserId}"); $"There is already a user with Id {command.UserId}");
} }
[Fact] [Fact]
public void Should_Not_Create_User_Tenant_Does_Not_Exist() public void Should_Not_Create_User_Tenant_Does_Not_Exist()
{ {
_fixture.SetupCurrentUser(); _fixture.SetupCurrentUser();
_fixture.SetupUser(); _fixture.SetupUser();
var command = new CreateUserCommand( var command = new CreateUserCommand(
@ -86,7 +86,7 @@ public sealed class CreateUserCommandHandlerTests
ErrorCodes.ObjectNotFound, ErrorCodes.ObjectNotFound,
$"There is no tenant with Id {command.TenantId}"); $"There is no tenant with Id {command.TenantId}");
} }
[Fact] [Fact]
public void Should_Not_Create_User_Insufficient_Permissions() public void Should_Not_Create_User_Insufficient_Permissions()
{ {

View File

@ -176,7 +176,7 @@ public sealed class CreateUserCommandValidationTests :
ShouldHaveSingleError(command, DomainErrorCodes.User.UserLongPassword); ShouldHaveSingleError(command, DomainErrorCodes.User.UserLongPassword);
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Tenant_Id() public void Should_Be_Invalid_For_Empty_Tenant_Id()
{ {
@ -193,7 +193,7 @@ public sealed class CreateUserCommandValidationTests :
string? lastName = null, string? lastName = null,
string? password = null) string? password = null)
{ {
return new( return new CreateUserCommand(
userId ?? Guid.NewGuid(), userId ?? Guid.NewGuid(),
tenantId ?? Guid.NewGuid(), tenantId ?? Guid.NewGuid(),
email ?? "test@email.com", email ?? "test@email.com",

View File

@ -33,6 +33,6 @@ public sealed class DeleteUserCommandValidationTests :
private static DeleteUserCommand CreateTestCommand(Guid? userId = null) private static DeleteUserCommand CreateTestCommand(Guid? userId = null)
{ {
return new(userId ?? Guid.NewGuid()); return new DeleteUserCommand(userId ?? Guid.NewGuid());
} }
} }

View File

@ -125,7 +125,7 @@ public sealed class LoginUserCommandValidationTests :
string? email = null, string? email = null,
string? password = null) string? password = null)
{ {
return new( return new LoginUserCommand(
email ?? "test@email.com", email ?? "test@email.com",
password ?? "Po=PF]PC6t.?8?ks)A6W"); password ?? "Po=PF]PC6t.?8?ks)A6W");
} }

View File

@ -109,7 +109,7 @@ public sealed class UpdateUserCommandValidationTests :
DomainErrorCodes.User.UserLastNameExceedsMaxLength, DomainErrorCodes.User.UserLastNameExceedsMaxLength,
$"LastName may not be longer than {MaxLengths.User.LastName} characters"); $"LastName may not be longer than {MaxLengths.User.LastName} characters");
} }
[Fact] [Fact]
public void Should_Be_Invalid_For_Empty_Tenant_Id() public void Should_Be_Invalid_For_Empty_Tenant_Id()
{ {
@ -129,7 +129,7 @@ public sealed class UpdateUserCommandValidationTests :
string? lastName = null, string? lastName = null,
UserRole? role = null) UserRole? role = null)
{ {
return new( return new UpdateUserCommand(
userId ?? Guid.NewGuid(), userId ?? Guid.NewGuid(),
email ?? "test@email.com", email ?? "test@email.com",
firstName ?? "test", firstName ?? "test",

View File

@ -59,17 +59,20 @@ public sealed class ApiUser : IUser
{ {
return _name; return _name;
} }
var identity = _httpContextAccessor.HttpContext?.User.Identity; var identity = _httpContextAccessor.HttpContext?.User.Identity;
if (identity is null) if (identity is null)
{ {
_name = string.Empty; _name = string.Empty;
return string.Empty; return string.Empty;
} }
if (!string.IsNullOrWhiteSpace(identity.Name)) if (!string.IsNullOrWhiteSpace(identity.Name))
{ {
_name = identity.Name; _name = identity.Name;
return identity.Name; return identity.Name;
} }
var claim = _httpContextAccessor.HttpContext!.User.Claims var claim = _httpContextAccessor.HttpContext!.User.Claims
.FirstOrDefault(c => string.Equals(c.Type, ClaimTypes.Name, StringComparison.OrdinalIgnoreCase))? .FirstOrDefault(c => string.Equals(c.Type, ClaimTypes.Name, StringComparison.OrdinalIgnoreCase))?
.Value; .Value;

View File

@ -6,14 +6,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.3"/>
<PackageReference Include="FluentValidation" Version="11.7.1" /> <PackageReference Include="FluentValidation" Version="11.7.1"/>
<PackageReference Include="MediatR" Version="12.1.1" /> <PackageReference Include="MediatR" Version="12.1.1"/>
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" /> <PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1"/>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.1" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.32.1"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -9,9 +9,9 @@ namespace CleanArchitecture.Domain.Commands;
public abstract class CommandHandlerBase public abstract class CommandHandlerBase
{ {
protected readonly IMediatorHandler Bus;
private readonly DomainNotificationHandler _notifications; private readonly DomainNotificationHandler _notifications;
private readonly IUnitOfWork _unitOfWork; private readonly IUnitOfWork _unitOfWork;
protected readonly IMediatorHandler Bus;
protected CommandHandlerBase( protected CommandHandlerBase(
IMediatorHandler bus, IMediatorHandler bus,

View File

@ -5,14 +5,14 @@ namespace CleanArchitecture.Domain.Commands.Tenants.CreateTenant;
public sealed class CreateTenantCommand : CommandBase public sealed class CreateTenantCommand : CommandBase
{ {
private static readonly CreateTenantCommandValidation s_validation = new(); private static readonly CreateTenantCommandValidation s_validation = new();
public string Name { get; }
public CreateTenantCommand(Guid tenantId, string name) : base(tenantId) public CreateTenantCommand(Guid tenantId, string name) : base(tenantId)
{ {
Name = name; Name = name;
} }
public string Name { get; }
public override bool IsValid() public override bool IsValid()
{ {
ValidationResult = s_validation.Validate(this); ValidationResult = s_validation.Validate(this);

View File

@ -34,7 +34,7 @@ public sealed class CreateTenantCommandHandler : CommandHandlerBase,
{ {
return; return;
} }
if (_user.GetUserRole() != UserRole.Admin) if (_user.GetUserRole() != UserRole.Admin)
{ {
await NotifyAsync( await NotifyAsync(
@ -60,7 +60,7 @@ public sealed class CreateTenantCommandHandler : CommandHandlerBase,
var tenant = new Tenant( var tenant = new Tenant(
request.AggregateId, request.AggregateId,
request.Name); request.Name);
_tenantRepository.Add(tenant); _tenantRepository.Add(tenant);
if (await CommitAsync()) if (await CommitAsync())

View File

@ -11,7 +11,7 @@ public sealed class CreateTenantCommandValidation : AbstractValidator<CreateTena
AddRuleForId(); AddRuleForId();
AddRuleForName(); AddRuleForName();
} }
private void AddRuleForId() private void AddRuleForId()
{ {
RuleFor(cmd => cmd.AggregateId) RuleFor(cmd => cmd.AggregateId)
@ -19,7 +19,7 @@ public sealed class CreateTenantCommandValidation : AbstractValidator<CreateTena
.WithErrorCode(DomainErrorCodes.Tenant.TenantEmptyId) .WithErrorCode(DomainErrorCodes.Tenant.TenantEmptyId)
.WithMessage("Tenant id may not be empty"); .WithMessage("Tenant id may not be empty");
} }
private void AddRuleForName() private void AddRuleForName()
{ {
RuleFor(cmd => cmd.Name) RuleFor(cmd => cmd.Name)

View File

@ -5,7 +5,7 @@ namespace CleanArchitecture.Domain.Commands.Tenants.DeleteTenant;
public sealed class DeleteTenantCommand : CommandBase public sealed class DeleteTenantCommand : CommandBase
{ {
private static readonly DeleteTenantCommandValidation s_validation = new(); private static readonly DeleteTenantCommandValidation s_validation = new();
public DeleteTenantCommand(Guid tenantId) : base(tenantId) public DeleteTenantCommand(Guid tenantId) : base(tenantId)
{ {
} }

View File

@ -15,8 +15,8 @@ public sealed class DeleteTenantCommandHandler : CommandHandlerBase,
IRequestHandler<DeleteTenantCommand> IRequestHandler<DeleteTenantCommand>
{ {
private readonly ITenantRepository _tenantRepository; private readonly ITenantRepository _tenantRepository;
private readonly IUserRepository _userRepository;
private readonly IUser _user; private readonly IUser _user;
private readonly IUserRepository _userRepository;
public DeleteTenantCommandHandler( public DeleteTenantCommandHandler(
IMediatorHandler bus, IMediatorHandler bus,
@ -37,7 +37,7 @@ public sealed class DeleteTenantCommandHandler : CommandHandlerBase,
{ {
return; return;
} }
// Todo: Test following // Todo: Test following
if (_user.GetUserRole() != UserRole.Admin) if (_user.GetUserRole() != UserRole.Admin)
@ -67,9 +67,9 @@ public sealed class DeleteTenantCommandHandler : CommandHandlerBase,
var tenantUsers = _userRepository var tenantUsers = _userRepository
.GetAll() .GetAll()
.Where(x => x.TenantId == request.AggregateId); .Where(x => x.TenantId == request.AggregateId);
_userRepository.RemoveRange(tenantUsers); _userRepository.RemoveRange(tenantUsers);
_tenantRepository.Remove(tenant); _tenantRepository.Remove(tenant);
if (await CommitAsync()) if (await CommitAsync())

View File

@ -9,7 +9,7 @@ public sealed class DeleteTenantCommandValidation : AbstractValidator<DeleteTena
{ {
AddRuleForId(); AddRuleForId();
} }
private void AddRuleForId() private void AddRuleForId()
{ {
RuleFor(cmd => cmd.AggregateId) RuleFor(cmd => cmd.AggregateId)

View File

@ -5,14 +5,14 @@ namespace CleanArchitecture.Domain.Commands.Tenants.UpdateTenant;
public sealed class UpdateTenantCommand : CommandBase public sealed class UpdateTenantCommand : CommandBase
{ {
private static readonly UpdateTenantCommandValidation s_validation = new(); private static readonly UpdateTenantCommandValidation s_validation = new();
public string Name { get; }
public UpdateTenantCommand(Guid tenantId, string name) : base(tenantId) public UpdateTenantCommand(Guid tenantId, string name) : base(tenantId)
{ {
Name = name; Name = name;
} }
public string Name { get; }
public override bool IsValid() public override bool IsValid()
{ {
ValidationResult = s_validation.Validate(this); ValidationResult = s_validation.Validate(this);

View File

@ -33,7 +33,7 @@ public sealed class UpdateTenantCommandHandler : CommandHandlerBase,
{ {
return; return;
} }
if (_user.GetUserRole() != UserRole.Admin) if (_user.GetUserRole() != UserRole.Admin)
{ {
await NotifyAsync( await NotifyAsync(
@ -57,9 +57,9 @@ public sealed class UpdateTenantCommandHandler : CommandHandlerBase,
return; return;
} }
tenant.SetName(request.Name); tenant.SetName(request.Name);
if (await CommitAsync()) if (await CommitAsync())
{ {
await Bus.RaiseEventAsync(new TenantUpdatedEvent( await Bus.RaiseEventAsync(new TenantUpdatedEvent(

View File

@ -11,7 +11,7 @@ public sealed class UpdateTenantCommandValidation : AbstractValidator<UpdateTena
AddRuleForId(); AddRuleForId();
AddRuleForName(); AddRuleForName();
} }
private void AddRuleForId() private void AddRuleForId()
{ {
RuleFor(cmd => cmd.AggregateId) RuleFor(cmd => cmd.AggregateId)
@ -19,7 +19,7 @@ public sealed class UpdateTenantCommandValidation : AbstractValidator<UpdateTena
.WithErrorCode(DomainErrorCodes.Tenant.TenantEmptyId) .WithErrorCode(DomainErrorCodes.Tenant.TenantEmptyId)
.WithMessage("Tenant id may not be empty"); .WithMessage("Tenant id may not be empty");
} }
private void AddRuleForName() private void AddRuleForName()
{ {
RuleFor(cmd => cmd.Name) RuleFor(cmd => cmd.Name)

View File

@ -15,9 +15,9 @@ namespace CleanArchitecture.Domain.Commands.Users.CreateUser;
public sealed class CreateUserCommandHandler : CommandHandlerBase, public sealed class CreateUserCommandHandler : CommandHandlerBase,
IRequestHandler<CreateUserCommand> IRequestHandler<CreateUserCommand>
{ {
private readonly IUserRepository _userRepository;
private readonly ITenantRepository _tenantRepository; private readonly ITenantRepository _tenantRepository;
private readonly IUser _user; private readonly IUser _user;
private readonly IUserRepository _userRepository;
public CreateUserCommandHandler( public CreateUserCommandHandler(
IMediatorHandler bus, IMediatorHandler bus,
@ -38,9 +38,9 @@ public sealed class CreateUserCommandHandler : CommandHandlerBase,
{ {
return; return;
} }
var currentUser = await _userRepository.GetByIdAsync(_user.GetUserId()); var currentUser = await _userRepository.GetByIdAsync(_user.GetUserId());
if (currentUser is null || currentUser.Role != UserRole.Admin) if (currentUser is null || currentUser.Role != UserRole.Admin)
{ {
await NotifyAsync( await NotifyAsync(

View File

@ -75,7 +75,7 @@ public sealed class UpdateUserCommandHandler : CommandHandlerBase,
if (_user.GetUserRole() == UserRole.Admin) if (_user.GetUserRole() == UserRole.Admin)
{ {
user.SetRole(request.Role); user.SetRole(request.Role);
// Todo: Test // Todo: Test
// Todo: Check if tenant exists first // Todo: Check if tenant exists first
user.SetTenant(request.TenantId); user.SetTenant(request.TenantId);

View File

@ -23,7 +23,7 @@ public sealed class UpdateUserCommandValidation : AbstractValidator<UpdateUserCo
.WithErrorCode(DomainErrorCodes.User.UserEmptyId) .WithErrorCode(DomainErrorCodes.User.UserEmptyId)
.WithMessage("User id may not be empty"); .WithMessage("User id may not be empty");
} }
private void AddRuleForTenantId() private void AddRuleForTenantId()
{ {
RuleFor(cmd => cmd.TenantId) RuleFor(cmd => cmd.TenantId)

View File

@ -19,4 +19,4 @@ public abstract class Message : IRequest
public Guid AggregateId { get; private set; } public Guid AggregateId { get; private set; }
public string MessageType { get; protected set; } public string MessageType { get; protected set; }
} }

View File

@ -4,11 +4,6 @@ namespace CleanArchitecture.Domain.DomainEvents;
public class StoredDomainEvent : DomainEvent public class StoredDomainEvent : DomainEvent
{ {
public Guid Id { get; private set; }
public string Data { get; private set; } = string.Empty;
public string User { get; private set; } = string.Empty;
public string CorrelationId { get; private set; } = string.Empty;
public StoredDomainEvent( public StoredDomainEvent(
DomainEvent domainEvent, DomainEvent domainEvent,
string data, string data,
@ -24,5 +19,11 @@ public class StoredDomainEvent : DomainEvent
// EF Constructor // EF Constructor
protected StoredDomainEvent() : base(Guid.NewGuid()) protected StoredDomainEvent() : base(Guid.NewGuid())
{ } {
} }
public Guid Id { get; private set; }
public string Data { get; private set; } = string.Empty;
public string User { get; private set; } = string.Empty;
public string CorrelationId { get; private set; } = string.Empty;
}

View File

@ -5,21 +5,16 @@ namespace CleanArchitecture.Domain.DomainNotifications;
public class StoredDomainNotification : DomainNotification public class StoredDomainNotification : DomainNotification
{ {
public Guid Id { get; private set; }
public string SerializedData { get; private set; } = string.Empty;
public string User { get; private set; } = string.Empty;
public string CorrelationId { get; private set; } = string.Empty;
public StoredDomainNotification( public StoredDomainNotification(
DomainNotification domainNotification, DomainNotification domainNotification,
string data, string data,
string user, string user,
string correlationId) : base( string correlationId) : base(
domainNotification.Key, domainNotification.Key,
domainNotification.Value, domainNotification.Value,
domainNotification.Code, domainNotification.Code,
null, null,
domainNotification.AggregateId) domainNotification.AggregateId)
{ {
Id = Guid.NewGuid(); Id = Guid.NewGuid();
User = user; User = user;
@ -31,5 +26,11 @@ public class StoredDomainNotification : DomainNotification
// EF Constructor // EF Constructor
protected StoredDomainNotification() : base(string.Empty, string.Empty, string.Empty) protected StoredDomainNotification() : base(string.Empty, string.Empty, string.Empty)
{ } {
} }
public Guid Id { get; private set; }
public string SerializedData { get; private set; } = string.Empty;
public string User { get; private set; } = string.Empty;
public string CorrelationId { get; private set; } = string.Empty;
}

View File

@ -5,17 +5,17 @@ namespace CleanArchitecture.Domain.Entities;
public class Tenant : Entity public class Tenant : Entity
{ {
public string Name { get; private set; }
public virtual ICollection<User> Users { get; private set; } = new HashSet<User>();
public Tenant( public Tenant(
Guid id, Guid id,
string name) : base(id) string name) : base(id)
{ {
Name = name; Name = name;
} }
public string Name { get; private set; }
public virtual ICollection<User> Users { get; private set; } = new HashSet<User>();
public void SetName(string name) public void SetName(string name)
{ {
Name = name; Name = name;

View File

@ -1,22 +1,10 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Enums;
namespace CleanArchitecture.Domain.Entities; namespace CleanArchitecture.Domain.Entities;
public class User : Entity public class User : Entity
{ {
public string Email { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string Password { get; private set; }
public UserRole Role { get; private set; }
public string FullName => $"{FirstName}, {LastName}";
public Guid TenantId { get; private set; }
public virtual Tenant Tenant { get; private set; } = null!;
public User( public User(
Guid id, Guid id,
Guid tenantId, Guid tenantId,
@ -34,6 +22,17 @@ public class User : Entity
Role = role; Role = role;
} }
public string Email { get; private set; }
public string FirstName { get; private set; }
public string LastName { get; private set; }
public string Password { get; private set; }
public UserRole Role { get; private set; }
public string FullName => $"{FirstName}, {LastName}";
public Guid TenantId { get; private set; }
public virtual Tenant Tenant { get; private set; } = null!;
public void SetEmail(string email) public void SetEmail(string email)
{ {
Email = email; Email = email;
@ -58,7 +57,7 @@ public class User : Entity
{ {
Role = role; Role = role;
} }
public void SetTenant(Guid tenantId) public void SetTenant(Guid tenantId)
{ {
TenantId = tenantId; TenantId = tenantId;

View File

@ -5,10 +5,10 @@ namespace CleanArchitecture.Domain.Events.Tenant;
public sealed class TenantCreatedEvent : DomainEvent public sealed class TenantCreatedEvent : DomainEvent
{ {
public string Name { get; set; }
public TenantCreatedEvent(Guid tenantId, string name) : base(tenantId) public TenantCreatedEvent(Guid tenantId, string name) : base(tenantId)
{ {
Name = name; Name = name;
} }
public string Name { get; set; }
} }

View File

@ -5,10 +5,10 @@ namespace CleanArchitecture.Domain.Events.Tenant;
public sealed class TenantUpdatedEvent : DomainEvent public sealed class TenantUpdatedEvent : DomainEvent
{ {
public string Name { get; set; }
public TenantUpdatedEvent(Guid tenantId, string name) : base(tenantId) public TenantUpdatedEvent(Guid tenantId, string name) : base(tenantId)
{ {
Name = name; Name = name;
} }
public string Name { get; set; }
} }

View File

@ -41,7 +41,7 @@ public static class ServiceCollectionExtension
services.AddScoped<INotificationHandler<UserUpdatedEvent>, UserEventHandler>(); services.AddScoped<INotificationHandler<UserUpdatedEvent>, UserEventHandler>();
services.AddScoped<INotificationHandler<UserDeletedEvent>, UserEventHandler>(); services.AddScoped<INotificationHandler<UserDeletedEvent>, UserEventHandler>();
services.AddScoped<INotificationHandler<PasswordChangedEvent>, UserEventHandler>(); services.AddScoped<INotificationHandler<PasswordChangedEvent>, UserEventHandler>();
// Tenant // Tenant
services.AddScoped<INotificationHandler<TenantCreatedEvent>, TenantEventHandler>(); services.AddScoped<INotificationHandler<TenantCreatedEvent>, TenantEventHandler>();
services.AddScoped<INotificationHandler<TenantUpdatedEvent>, TenantEventHandler>(); services.AddScoped<INotificationHandler<TenantUpdatedEvent>, TenantEventHandler>();

View File

@ -8,10 +8,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0"/>
<PackageReference Include="NSubstitute" Version="5.0.0" /> <PackageReference Include="NSubstitute" Version="5.0.0"/>
<PackageReference Include="xunit" Version="2.5.0" /> <PackageReference Include="xunit" Version="2.5.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -23,7 +23,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" /> <ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -6,19 +6,19 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MediatR" Version="12.1.1" /> <PackageReference Include="MediatR" Version="12.1.1"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.10"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.10">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -22,4 +22,4 @@ public sealed class StoredDomainEventConfiguration : IEntityTypeConfiguration<St
.HasMaxLength(100) .HasMaxLength(100)
.HasColumnType("nvarchar(100)"); .HasColumnType("nvarchar(100)");
} }
} }

View File

@ -39,4 +39,4 @@ public sealed class StoredDomainNotificationConfiguration : IEntityTypeConfigura
builder.Property(c => c.SerializedData) builder.Property(c => c.SerializedData)
.HasColumnName("Data"); .HasColumnName("Data");
} }
} }

View File

@ -1,4 +1,3 @@
using System;
using CleanArchitecture.Domain.Constants; using CleanArchitecture.Domain.Constants;
using CleanArchitecture.Domain.Entities; using CleanArchitecture.Domain.Entities;
using CleanArchitecture.Domain.Enums; using CleanArchitecture.Domain.Enums;

View File

@ -18,4 +18,4 @@ public class EventStoreDbContext : DbContext
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
} }
} }

View File

@ -9,9 +9,9 @@ namespace CleanArchitecture.Infrastructure.EventSourcing;
public sealed class DomainEventStore : IDomainEventStore public sealed class DomainEventStore : IDomainEventStore
{ {
private readonly EventStoreDbContext _eventStoreDbContext;
private readonly DomainNotificationStoreDbContext _domainNotificationStoreDbContext;
private readonly IEventStoreContext _context; private readonly IEventStoreContext _context;
private readonly DomainNotificationStoreDbContext _domainNotificationStoreDbContext;
private readonly EventStoreDbContext _eventStoreDbContext;
public DomainEventStore( public DomainEventStore(
EventStoreDbContext eventStoreDbContext, EventStoreDbContext eventStoreDbContext,
@ -53,4 +53,4 @@ public sealed class DomainEventStore : IDomainEventStore
break; break;
} }
} }
} }

View File

@ -14,7 +14,8 @@ public sealed class EventStoreContext : IEventStoreContext
_user = user; _user = user;
if (httpContextAccessor?.HttpContext is null || if (httpContextAccessor?.HttpContext is null ||
!httpContextAccessor.HttpContext.Request.Headers.TryGetValue("X-CLEAN-ARCHITECTURE-CORRELATION-ID", out var id)) !httpContextAccessor.HttpContext.Request.Headers.TryGetValue("X-CLEAN-ARCHITECTURE-CORRELATION-ID",
out var id))
{ {
_correlationId = $"internal - {Guid.NewGuid()}"; _correlationId = $"internal - {Guid.NewGuid()}";
} }
@ -24,7 +25,13 @@ public sealed class EventStoreContext : IEventStoreContext
} }
} }
public string GetCorrelationId() => _correlationId; public string GetCorrelationId()
{
return _correlationId;
}
public string GetUserEmail() => _user?.GetUserEmail() ?? string.Empty; public string GetUserEmail()
} {
return _user?.GetUserEmail() ?? string.Empty;
}
}

View File

@ -4,4 +4,4 @@ public interface IEventStoreContext
{ {
public string GetUserEmail(); public string GetUserEmail();
public string GetCorrelationId(); public string GetCorrelationId();
} }

View File

@ -16,18 +16,18 @@ public static class ServiceCollectionExtensions
{ {
public static IServiceCollection AddInfrastructure( public static IServiceCollection AddInfrastructure(
this IServiceCollection services, this IServiceCollection services,
IConfiguration configuration, IConfiguration configuration,
string migrationsAssemblyName, string migrationsAssemblyName,
string connectionStringName = "DefaultConnection") string connectionStringName = "DefaultConnection")
{ {
// Add event store db context // Add event store db context
services.AddDbContext<EventStoreDbContext>( services.AddDbContext<EventStoreDbContext>(
options => options =>
{ {
options.UseSqlServer( options.UseSqlServer(
configuration.GetConnectionString(connectionStringName), configuration.GetConnectionString(connectionStringName),
b => b.MigrationsAssembly(migrationsAssemblyName)); b => b.MigrationsAssembly(migrationsAssemblyName));
}); });
services.AddDbContext<DomainNotificationStoreDbContext>( services.AddDbContext<DomainNotificationStoreDbContext>(
options => options =>

View File

@ -5,6 +5,12 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0161:Convert to file-scoped namespace", Justification = "<Pending>", Scope = "namespace", Target = "~N:CleanArchitecture.Infrastructure.Migrations")] [assembly:
[assembly: SuppressMessage("Style", "IDE0161:Convert to file-scoped namespace", Justification = "<Pending>", Scope = "namespace", Target = "~N:CleanArchitecture.Infrastructure.Migrations.EventStoreDb")] SuppressMessage("Style", "IDE0161:Convert to file-scoped namespace", Justification = "<Pending>",
[assembly: SuppressMessage("Style", "IDE0161:Convert to file-scoped namespace", Justification = "<Pending>", Scope = "namespace", Target = "~N:CleanArchitecture.Infrastructure.Migrations.DomainNotificationStoreDb")] Scope = "namespace", Target = "~N:CleanArchitecture.Infrastructure.Migrations")]
[assembly:
SuppressMessage("Style", "IDE0161:Convert to file-scoped namespace", Justification = "<Pending>",
Scope = "namespace", Target = "~N:CleanArchitecture.Infrastructure.Migrations.EventStoreDb")]
[assembly:
SuppressMessage("Style", "IDE0161:Convert to file-scoped namespace", Justification = "<Pending>",
Scope = "namespace", Target = "~N:CleanArchitecture.Infrastructure.Migrations.DomainNotificationStoreDb")]

View File

@ -8,8 +8,8 @@ namespace CleanArchitecture.Infrastructure;
public sealed class InMemoryBus : IMediatorHandler public sealed class InMemoryBus : IMediatorHandler
{ {
private readonly IMediator _mediator;
private readonly IDomainEventStore _domainEventStore; private readonly IDomainEventStore _domainEventStore;
private readonly IMediator _mediator;
public InMemoryBus( public InMemoryBus(
IMediator mediator, IMediator mediator,

View File

@ -1,43 +1,39 @@
using System; #nullable disable
using System;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable namespace CleanArchitecture.Infrastructure.Migrations.DomainNotificationStoreDb;
namespace CleanArchitecture.Infrastructure.Migrations.DomainNotificationStoreDb /// <inheritdoc />
public partial class AddDomainNotificationStore : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class AddDomainNotificationStore : Migration protected override void Up(MigrationBuilder migrationBuilder)
{ {
/// <inheritdoc /> migrationBuilder.CreateTable(
protected override void Up(MigrationBuilder migrationBuilder) "StoredDomainNotifications",
{ table => new
migrationBuilder.CreateTable( {
name: "StoredDomainNotifications", Id = table.Column<Guid>("uniqueidentifier", nullable: false),
columns: table => new Data = table.Column<string>("nvarchar(max)", nullable: false),
{ User = table.Column<string>("nvarchar(100)", maxLength: 100, nullable: false),
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), CorrelationId = table.Column<string>("nvarchar(100)", maxLength: 100, nullable: false),
Data = table.Column<string>(type: "nvarchar(max)", nullable: false), AggregateId = table.Column<Guid>("uniqueidentifier", nullable: false),
User = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), MessageType = table.Column<string>("nvarchar(100)", maxLength: 100, nullable: false),
CorrelationId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), Timestamp = table.Column<DateTime>("datetime2", nullable: false),
AggregateId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Key = table.Column<string>("nvarchar(100)", maxLength: 100, nullable: false),
MessageType = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), Value = table.Column<string>("nvarchar(1024)", maxLength: 1024, nullable: false),
Timestamp = table.Column<DateTime>(type: "datetime2", nullable: false), Code = table.Column<string>("nvarchar(100)", maxLength: 100, nullable: false),
Key = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), Version = table.Column<int>("int", nullable: false)
Value = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: false), },
Code = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), constraints: table => { table.PrimaryKey("PK_StoredDomainNotifications", x => x.Id); });
Version = table.Column<int>(type: "int", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_StoredDomainNotifications", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "StoredDomainNotifications");
}
} }
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
"StoredDomainNotifications");
}
}

View File

@ -1,39 +1,35 @@
using System; #nullable disable
using System;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable namespace CleanArchitecture.Infrastructure.Migrations.EventStoreDb;
namespace CleanArchitecture.Infrastructure.Migrations.EventStoreDb /// <inheritdoc />
public partial class AddEventStore : Migration
{ {
/// <inheritdoc /> /// <inheritdoc />
public partial class AddEventStore : Migration protected override void Up(MigrationBuilder migrationBuilder)
{ {
/// <inheritdoc /> migrationBuilder.CreateTable(
protected override void Up(MigrationBuilder migrationBuilder) "StoredDomainEvents",
{ table => new
migrationBuilder.CreateTable( {
name: "StoredDomainEvents", Id = table.Column<Guid>("uniqueidentifier", nullable: false),
columns: table => new Data = table.Column<string>("nvarchar(max)", nullable: false),
{ User = table.Column<string>("nvarchar(100)", maxLength: 100, nullable: false),
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), CorrelationId = table.Column<string>("nvarchar(100)", maxLength: 100, nullable: false),
Data = table.Column<string>(type: "nvarchar(max)", nullable: false), AggregateId = table.Column<Guid>("uniqueidentifier", nullable: false),
User = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), Action = table.Column<string>("varchar(100)", nullable: false),
CorrelationId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), CreationDate = table.Column<DateTime>("datetime2", nullable: false)
AggregateId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), },
Action = table.Column<string>(type: "varchar(100)", nullable: false), constraints: table => { table.PrimaryKey("PK_StoredDomainEvents", x => x.Id); });
CreationDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_StoredDomainEvents", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "StoredDomainEvents");
}
} }
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
"StoredDomainEvents");
}
}

View File

@ -72,7 +72,7 @@ public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : Enti
DbSet.Update(entity); DbSet.Update(entity);
} }
} }
public void RemoveRange(IEnumerable<TEntity> entities, bool hardDelete = false) public void RemoveRange(IEnumerable<TEntity> entities, bool hardDelete = false)
{ {
if (hardDelete) if (hardDelete)

View File

@ -8,14 +8,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0"/>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.10" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.10"/>
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.10" /> <PackageReference Include="Microsoft.Data.Sqlite.Core" Version="7.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0"/>
<PackageReference Include="xunit" Version="2.5.0" /> <PackageReference Include="xunit" Version="2.5.0"/>
<PackageReference Include="Xunit.Priority" Version="1.1.6" /> <PackageReference Include="Xunit.Priority" Version="1.1.6"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -27,8 +27,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Api\CleanArchitecture.Api.csproj" /> <ProjectReference Include="..\CleanArchitecture.Api\CleanArchitecture.Api.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj" /> <ProjectReference Include="..\CleanArchitecture.Infrastructure\CleanArchitecture.Infrastructure.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -30,17 +30,17 @@ public sealed class TenantControllerTests : IClassFixture<TenantTestFixture>
await _fixture.AuthenticateUserAsync(); await _fixture.AuthenticateUserAsync();
var response = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}"); var response = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}");
response.StatusCode.Should().Be(HttpStatusCode.OK); response.StatusCode.Should().Be(HttpStatusCode.OK);
var message = await response.Content.ReadAsJsonAsync<TenantViewModel>(); var message = await response.Content.ReadAsJsonAsync<TenantViewModel>();
message?.Data.Should().NotBeNull(); message?.Data.Should().NotBeNull();
message!.Data!.Id.Should().Be(_fixture.CreatedTenantId); message!.Data!.Id.Should().Be(_fixture.CreatedTenantId);
message.Data.Name.Should().Be("Test Tenant"); message.Data.Name.Should().Be("Test Tenant");
} }
[Fact] [Fact]
[Priority(5)] [Priority(5)]
public async Task Should_Get_All_Tenants() public async Task Should_Get_All_Tenants()
@ -48,89 +48,89 @@ public sealed class TenantControllerTests : IClassFixture<TenantTestFixture>
await _fixture.AuthenticateUserAsync(); await _fixture.AuthenticateUserAsync();
var response = await _fixture.ServerClient.GetAsync("api/v1/Tenant"); var response = await _fixture.ServerClient.GetAsync("api/v1/Tenant");
response.StatusCode.Should().Be(HttpStatusCode.OK); response.StatusCode.Should().Be(HttpStatusCode.OK);
var message = await response.Content.ReadAsJsonAsync<IEnumerable<TenantViewModel>>(); var message = await response.Content.ReadAsJsonAsync<IEnumerable<TenantViewModel>>();
message?.Data.Should().NotBeEmpty(); message?.Data.Should().NotBeEmpty();
message!.Data.Should().HaveCountGreaterOrEqualTo(2); message!.Data.Should().HaveCountGreaterOrEqualTo(2);
message.Data! message.Data!
.FirstOrDefault(x => x.Id == _fixture.CreatedTenantId) .FirstOrDefault(x => x.Id == _fixture.CreatedTenantId)
.Should().NotBeNull(); .Should().NotBeNull();
} }
[Fact] [Fact]
[Priority(10)] [Priority(10)]
public async Task Should_Create_Tenant() public async Task Should_Create_Tenant()
{ {
await _fixture.AuthenticateUserAsync(); await _fixture.AuthenticateUserAsync();
var request = new CreateTenantViewModel("Test Tenant 2"); var request = new CreateTenantViewModel("Test Tenant 2");
var response = await _fixture.ServerClient.PostAsJsonAsync("/api/v1/Tenant", request); var response = await _fixture.ServerClient.PostAsJsonAsync("/api/v1/Tenant", request);
response.StatusCode.Should().Be(HttpStatusCode.OK); response.StatusCode.Should().Be(HttpStatusCode.OK);
var message = await response.Content.ReadAsJsonAsync<Guid>(); var message = await response.Content.ReadAsJsonAsync<Guid>();
var tenantId = message?.Data; var tenantId = message?.Data;
// Check if tenant exists // Check if tenant exists
var tenantResponse = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{tenantId}"); var tenantResponse = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{tenantId}");
tenantResponse.StatusCode.Should().Be(HttpStatusCode.OK); tenantResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var tenantMessage = await tenantResponse.Content.ReadAsJsonAsync<TenantViewModel>(); var tenantMessage = await tenantResponse.Content.ReadAsJsonAsync<TenantViewModel>();
tenantMessage?.Data.Should().NotBeNull(); tenantMessage?.Data.Should().NotBeNull();
tenantMessage!.Data!.Id.Should().Be(tenantId!.Value); tenantMessage!.Data!.Id.Should().Be(tenantId!.Value);
tenantMessage.Data.Name.Should().Be(request.Name); tenantMessage.Data.Name.Should().Be(request.Name);
} }
[Fact] [Fact]
[Priority(15)] [Priority(15)]
public async Task Should_Update_Tenant() public async Task Should_Update_Tenant()
{ {
await _fixture.AuthenticateUserAsync(); await _fixture.AuthenticateUserAsync();
var request = new UpdateTenantViewModel(_fixture.CreatedTenantId, "Test Tenant 3"); var request = new UpdateTenantViewModel(_fixture.CreatedTenantId, "Test Tenant 3");
var response = await _fixture.ServerClient.PutAsJsonAsync("/api/v1/Tenant", request); var response = await _fixture.ServerClient.PutAsJsonAsync("/api/v1/Tenant", request);
response.StatusCode.Should().Be(HttpStatusCode.OK); response.StatusCode.Should().Be(HttpStatusCode.OK);
var message = await response.Content.ReadAsJsonAsync<UpdateTenantViewModel>(); var message = await response.Content.ReadAsJsonAsync<UpdateTenantViewModel>();
message?.Data.Should().NotBeNull(); message?.Data.Should().NotBeNull();
message!.Data.Should().BeEquivalentTo(request); message!.Data.Should().BeEquivalentTo(request);
// Check if tenant is updated // Check if tenant is updated
var tenantResponse = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}"); var tenantResponse = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}");
tenantResponse.StatusCode.Should().Be(HttpStatusCode.OK); tenantResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var tenantMessage = await response.Content.ReadAsJsonAsync<TenantViewModel>(); var tenantMessage = await response.Content.ReadAsJsonAsync<TenantViewModel>();
tenantMessage?.Data.Should().NotBeNull(); tenantMessage?.Data.Should().NotBeNull();
tenantMessage!.Data!.Id.Should().Be(_fixture.CreatedTenantId); tenantMessage!.Data!.Id.Should().Be(_fixture.CreatedTenantId);
tenantMessage.Data.Name.Should().Be(request.Name); tenantMessage.Data.Name.Should().Be(request.Name);
} }
[Fact] [Fact]
[Priority(20)] [Priority(20)]
public async Task Should_Delete_Tenant() public async Task Should_Delete_Tenant()
{ {
await _fixture.AuthenticateUserAsync(); await _fixture.AuthenticateUserAsync();
var response = await _fixture.ServerClient.DeleteAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}"); var response = await _fixture.ServerClient.DeleteAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}");
response.StatusCode.Should().Be(HttpStatusCode.OK); response.StatusCode.Should().Be(HttpStatusCode.OK);
// Check if tenant is deleted // Check if tenant is deleted
var tenantResponse = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}"); var tenantResponse = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}");
tenantResponse.StatusCode.Should().Be(HttpStatusCode.NotFound); tenantResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
} }
} }

View File

@ -26,13 +26,13 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
} }
// Todo: Refactor tests to work alone // Todo: Refactor tests to work alone
[Fact] [Fact]
[Priority(0)] [Priority(0)]
public async Task Should_Create_User() public async Task Should_Create_User()
{ {
await _fixture.AuthenticateUserAsync(); await _fixture.AuthenticateUserAsync();
var user = new CreateUserViewModel( var user = new CreateUserViewModel(
_fixture.CreatedUserEmail, _fixture.CreatedUserEmail,
"Test", "Test",
@ -237,7 +237,7 @@ public sealed class UserControllerTests : IClassFixture<UserTestFixture>
var content = message!.Data; var content = message!.Data;
content.Should().Be(_fixture.CreatedUserId); content.Should().Be(_fixture.CreatedUserId);
// Todo: Check if stuff is done // Todo: Check if stuff is done
} }
} }

View File

@ -7,7 +7,7 @@ namespace CleanArchitecture.IntegrationTests.Fixtures;
public sealed class TenantTestFixture : TestFixtureBase public sealed class TenantTestFixture : TestFixtureBase
{ {
public Guid CreatedTenantId { get; } = Guid.NewGuid(); public Guid CreatedTenantId { get; } = Guid.NewGuid();
protected override void SeedTestData(ApplicationDbContext context) protected override void SeedTestData(ApplicationDbContext context)
{ {
context.Tenants.Add(new Tenant( context.Tenants.Add(new Tenant(

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using CleanArchitecture.Application.ViewModels.Users; using CleanArchitecture.Application.ViewModels.Users;
@ -36,7 +35,7 @@ public class TestFixtureBase
IServiceProvider scopedServices) IServiceProvider scopedServices)
{ {
} }
// Todo: Fix auth // Todo: Fix auth
public virtual async Task AuthenticateUserAsync() public virtual async Task AuthenticateUserAsync()
{ {

View File

@ -7,9 +7,6 @@ namespace CleanArchitecture.IntegrationTests.Fixtures.gRPC;
public sealed class GetTenantsByIdsTestFixture : TestFixtureBase public sealed class GetTenantsByIdsTestFixture : TestFixtureBase
{ {
public GrpcChannel GrpcChannel { get; }
public Guid CreatedTenantId { get; } = Guid.NewGuid();
public GetTenantsByIdsTestFixture() public GetTenantsByIdsTestFixture()
{ {
GrpcChannel = GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions GrpcChannel = GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions
@ -18,6 +15,9 @@ public sealed class GetTenantsByIdsTestFixture : TestFixtureBase
}); });
} }
public GrpcChannel GrpcChannel { get; }
public Guid CreatedTenantId { get; } = Guid.NewGuid();
protected override void SeedTestData(ApplicationDbContext context) protected override void SeedTestData(ApplicationDbContext context)
{ {
base.SeedTestData(context); base.SeedTestData(context);
@ -30,7 +30,7 @@ public sealed class GetTenantsByIdsTestFixture : TestFixtureBase
public Tenant CreateTenant() public Tenant CreateTenant()
{ {
return new( return new Tenant(
CreatedTenantId, CreatedTenantId,
"Test Tenant"); "Test Tenant");
} }

View File

@ -9,9 +9,6 @@ namespace CleanArchitecture.IntegrationTests.Fixtures.gRPC;
public sealed class GetUsersByIdsTestFixture : TestFixtureBase public sealed class GetUsersByIdsTestFixture : TestFixtureBase
{ {
public GrpcChannel GrpcChannel { get; }
public Guid CreatedUserId { get; } = Guid.NewGuid();
public GetUsersByIdsTestFixture() public GetUsersByIdsTestFixture()
{ {
GrpcChannel = GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions GrpcChannel = GrpcChannel.ForAddress("http://localhost", new GrpcChannelOptions
@ -20,6 +17,9 @@ public sealed class GetUsersByIdsTestFixture : TestFixtureBase
}); });
} }
public GrpcChannel GrpcChannel { get; }
public Guid CreatedUserId { get; } = Guid.NewGuid();
protected override void SeedTestData(ApplicationDbContext context) protected override void SeedTestData(ApplicationDbContext context)
{ {
base.SeedTestData(context); base.SeedTestData(context);

View File

@ -47,19 +47,19 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto
services.SetupTestDatabase<EventStoreDbContext>(_connection); services.SetupTestDatabase<EventStoreDbContext>(_connection);
services.SetupTestDatabase<DomainNotificationStoreDbContext>(_connection); services.SetupTestDatabase<DomainNotificationStoreDbContext>(_connection);
ServiceProvider sp = services.BuildServiceProvider(); var sp = services.BuildServiceProvider();
using IServiceScope scope = sp.CreateScope(); using var scope = sp.CreateScope();
IServiceProvider scopedServices = scope.ServiceProvider; var scopedServices = scope.ServiceProvider;
ApplicationDbContext applicationDbContext = scopedServices.GetRequiredService<ApplicationDbContext>(); var applicationDbContext = scopedServices.GetRequiredService<ApplicationDbContext>();
EventStoreDbContext storeDbContext = scopedServices.GetRequiredService<EventStoreDbContext>(); var storeDbContext = scopedServices.GetRequiredService<EventStoreDbContext>();
DomainNotificationStoreDbContext domainStoreDbContext = scopedServices.GetRequiredService<DomainNotificationStoreDbContext>(); var domainStoreDbContext = scopedServices.GetRequiredService<DomainNotificationStoreDbContext>();
applicationDbContext.EnsureMigrationsApplied(); applicationDbContext.EnsureMigrationsApplied();
var creator2 = (RelationalDatabaseCreator)storeDbContext.Database var creator2 = (RelationalDatabaseCreator)storeDbContext.Database
.GetService<IRelationalDatabaseCreator>(); .GetService<IRelationalDatabaseCreator>();
creator2.CreateTables(); creator2.CreateTables();
var creator3 = (RelationalDatabaseCreator)domainStoreDbContext var creator3 = (RelationalDatabaseCreator)domainStoreDbContext

View File

@ -26,9 +26,9 @@ public sealed class GetTenantsByIdsTests : IClassFixture<GetTenantsByIdsTestFixt
request.Ids.Add(_fixture.CreatedTenantId.ToString()); request.Ids.Add(_fixture.CreatedTenantId.ToString());
var response = await client.GetByIdsAsync(request); var response = await client.GetByIdsAsync(request);
response.Tenants.Should().HaveCount(1); response.Tenants.Should().HaveCount(1);
var tenant = response.Tenants.First(); var tenant = response.Tenants.First();
var createdTenant = _fixture.CreateTenant(); var createdTenant = _fixture.CreateTenant();

View File

@ -6,16 +6,16 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Protobuf Include="Users\Models.proto" GrpcServices="Both" /> <Protobuf Include="Users\Models.proto" GrpcServices="Both"/>
<Protobuf Include="Users\UsersApi.proto" GrpcServices="Both" /> <Protobuf Include="Users\UsersApi.proto" GrpcServices="Both"/>
<Protobuf Include="Tenants\Models.proto" GrpcServices="Both" /> <Protobuf Include="Tenants\Models.proto" GrpcServices="Both"/>
<Protobuf Include="Tenants\TenantsApi.proto" GrpcServices="Both" /> <Protobuf Include="Tenants\TenantsApi.proto" GrpcServices="Both"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.24.0" /> <PackageReference Include="Google.Protobuf" Version="3.24.0"/>
<PackageReference Include="Google.Protobuf.Tools" Version="3.24.0" /> <PackageReference Include="Google.Protobuf.Tools" Version="3.24.0"/>
<PackageReference Include="Grpc.AspNetCore" Version="2.55.0" /> <PackageReference Include="Grpc.AspNetCore" Version="2.55.0"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

@ -7,4 +7,4 @@ public sealed record UserViewModel(
string Email, string Email,
string FirstName, string FirstName,
string LastName, string LastName,
bool IsDeleted); bool IsDeleted);

View File

@ -8,11 +8,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="FluentAssertions" Version="6.11.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0"/>
<PackageReference Include="MockQueryable.NSubstitute" Version="7.0.0" /> <PackageReference Include="MockQueryable.NSubstitute" Version="7.0.0"/>
<PackageReference Include="NSubstitute" Version="5.0.0" /> <PackageReference Include="NSubstitute" Version="5.0.0"/>
<PackageReference Include="xunit" Version="2.5.0" /> <PackageReference Include="xunit" Version="2.5.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -24,9 +24,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj" /> <ProjectReference Include="..\CleanArchitecture.Application\CleanArchitecture.Application.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
<ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj" /> <ProjectReference Include="..\CleanArchitecture.gRPC\CleanArchitecture.gRPC.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -10,24 +10,24 @@ namespace CleanArchitecture.gRPC.Tests.Fixtures;
public sealed class TenantTestFixture public sealed class TenantTestFixture
{ {
public TenantsApiImplementation TenantsApiImplementation { get; }
private ITenantRepository TenantRepository { get; }
public IEnumerable<Tenant> ExistingTenants { get; }
public TenantTestFixture() public TenantTestFixture()
{ {
TenantRepository = Substitute.For<ITenantRepository>(); TenantRepository = Substitute.For<ITenantRepository>();
ExistingTenants = new List<Tenant> ExistingTenants = new List<Tenant>
{ {
new Tenant(Guid.NewGuid(), "Tenant 1"), new(Guid.NewGuid(), "Tenant 1"),
new Tenant(Guid.NewGuid(), "Tenant 2"), new(Guid.NewGuid(), "Tenant 2"),
new Tenant(Guid.NewGuid(), "Tenant 3"), new(Guid.NewGuid(), "Tenant 3")
}; };
TenantRepository.GetAllNoTracking().Returns(ExistingTenants.BuildMock()); TenantRepository.GetAllNoTracking().Returns(ExistingTenants.BuildMock());
TenantsApiImplementation = new(TenantRepository); TenantsApiImplementation = new TenantsApiImplementation(TenantRepository);
} }
public TenantsApiImplementation TenantsApiImplementation { get; }
private ITenantRepository TenantRepository { get; }
public IEnumerable<Tenant> ExistingTenants { get; }
} }

View File

@ -24,7 +24,7 @@ public sealed class GetTenantsByIdsTests : IClassFixture<TenantTestFixture>
var result = await _fixture.TenantsApiImplementation.GetByIds( var result = await _fixture.TenantsApiImplementation.GetByIds(
SetupRequest(Enumerable.Empty<Guid>()), SetupRequest(Enumerable.Empty<Guid>()),
default!); default!);
result.Tenants.Should().HaveCount(0); result.Tenants.Should().HaveCount(0);
} }
@ -37,36 +37,36 @@ public sealed class GetTenantsByIdsTests : IClassFixture<TenantTestFixture>
.Take(2) .Take(2)
.Select(tenant => tenant.Id) .Select(tenant => tenant.Id)
.ToList(); .ToList();
ids.Add(nonExistingId); ids.Add(nonExistingId);
var result = await _fixture.TenantsApiImplementation.GetByIds( var result = await _fixture.TenantsApiImplementation.GetByIds(
SetupRequest(ids), SetupRequest(ids),
default!); default!);
result.Tenants.Should().HaveCount(2); result.Tenants.Should().HaveCount(2);
foreach (var tenant in result.Tenants) foreach (var tenant in result.Tenants)
{ {
var tenantId = Guid.Parse(tenant.Id); var tenantId = Guid.Parse(tenant.Id);
tenantId.Should().NotBe(nonExistingId); tenantId.Should().NotBe(nonExistingId);
var mockTenant = _fixture.ExistingTenants.First(t => t.Id == tenantId); var mockTenant = _fixture.ExistingTenants.First(t => t.Id == tenantId);
mockTenant.Should().NotBeNull(); mockTenant.Should().NotBeNull();
tenant.Name.Should().Be(mockTenant.Name); tenant.Name.Should().Be(mockTenant.Name);
} }
} }
private static GetTenantsByIdsRequest SetupRequest(IEnumerable<Guid> ids) private static GetTenantsByIdsRequest SetupRequest(IEnumerable<Guid> ids)
{ {
var request = new GetTenantsByIdsRequest(); var request = new GetTenantsByIdsRequest();
request.Ids.AddRange(ids.Select(id => id.ToString())); request.Ids.AddRange(ids.Select(id => id.ToString()));
request.Ids.Add("Not a guid"); request.Ids.Add("Not a guid");
return request; return request;
} }
} }

View File

@ -4,18 +4,15 @@ namespace CleanArchitecture.gRPC;
public sealed class CleanArchitecture : ICleanArchitecture public sealed class CleanArchitecture : ICleanArchitecture
{ {
private readonly IUsersContext _users;
private readonly ITenantsContext _tenants;
public IUsersContext Users => _users;
public ITenantsContext Tenants => _tenants;
public CleanArchitecture( public CleanArchitecture(
IUsersContext users, IUsersContext users,
ITenantsContext tenants) ITenantsContext tenants)
{ {
_users = users; Users = users;
_tenants = tenants; Tenants = tenants;
} }
}
public IUsersContext Users { get; }
public ITenantsContext Tenants { get; }
}

View File

@ -6,9 +6,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj" /> <ProjectReference Include="..\CleanArchitecture.Domain\CleanArchitecture.Domain.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Proto\CleanArchitecture.Proto.csproj" /> <ProjectReference Include="..\CleanArchitecture.Proto\CleanArchitecture.Proto.csproj"/>
<ProjectReference Include="..\CleanArchitecture.Shared\CleanArchitecture.Shared.csproj" /> <ProjectReference Include="..\CleanArchitecture.Shared\CleanArchitecture.Shared.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -20,11 +20,11 @@ public sealed class TenantsContext : ITenantsContext
public async Task<IEnumerable<TenantViewModel>> GetTenantsByIds(IEnumerable<Guid> ids) public async Task<IEnumerable<TenantViewModel>> GetTenantsByIds(IEnumerable<Guid> ids)
{ {
var request = new GetTenantsByIdsRequest(); var request = new GetTenantsByIdsRequest();
request.Ids.AddRange(ids.Select(id => id.ToString())); request.Ids.AddRange(ids.Select(id => id.ToString()));
var result = await _client.GetByIdsAsync(request); var result = await _client.GetByIdsAsync(request);
return result.Tenants.Select(tenant => new TenantViewModel( return result.Tenants.Select(tenant => new TenantViewModel(
Guid.Parse(tenant.Id), Guid.Parse(tenant.Id),
tenant.Name)); tenant.Name));

View File

@ -32,4 +32,4 @@ public sealed class UsersContext : IUsersContext
user.LastName, user.LastName,
user.IsDeleted)); user.IsDeleted));
} }
} }

View File

@ -53,7 +53,7 @@ public static class ServiceCollectionExtensions
services.AddSingleton<IUsersContext, UsersContext>(); services.AddSingleton<IUsersContext, UsersContext>();
services.AddSingleton<ITenantsContext, TenantsContext>(); services.AddSingleton<ITenantsContext, TenantsContext>();
return services; return services;
} }
} }

View File

@ -6,4 +6,4 @@ public interface ICleanArchitecture
{ {
IUsersContext Users { get; } IUsersContext Users { get; }
ITenantsContext Tenants { get; } ITenantsContext Tenants { get; }
} }

View File

@ -8,4 +8,4 @@ namespace CleanArchitecture.gRPC.Interfaces;
public interface IUsersContext public interface IUsersContext
{ {
Task<IEnumerable<UserViewModel>> GetUsersByIds(IEnumerable<Guid> ids); Task<IEnumerable<UserViewModel>> GetUsersByIds(IEnumerable<Guid> ids);
} }

View File

@ -3,4 +3,4 @@
public sealed class GRPCSettings public sealed class GRPCSettings
{ {
public string CleanArchitectureUrl { get; set; } = string.Empty; public string CleanArchitectureUrl { get; set; } = string.Empty;
} }