From fddb7b790909774039b6ebf6c9909c65219b04c3 Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Wed, 23 Sep 2026 03:49:57 -0300 Subject: [PATCH] test: verify team member commit and rollback --- .../TeamMemberCreateTransactionTests.cs | 121 ++++++++++++++---- 1 file changed, 99 insertions(+), 22 deletions(-) diff --git a/SeaHavenIndustries.Tests/TeamMemberCreateTransactionTests.cs b/SeaHavenIndustries.Tests/TeamMemberCreateTransactionTests.cs index bc7abe8..8c5e2bc 100644 --- a/SeaHavenIndustries.Tests/TeamMemberCreateTransactionTests.cs +++ b/SeaHavenIndustries.Tests/TeamMemberCreateTransactionTests.cs @@ -35,6 +35,85 @@ public sealed class TeamMemberCreateTransactionTests await using (var setup = new SqliteTeamMemberTestDbContext(options)) await setup.Database.EnsureCreatedAsync(); + await using var serviceProvider = BuildServiceProvider(connection, injectFailure: true); + ThrowingAfterPersistPermissionDataService failingPermissionData; + await using (var createScope = serviceProvider.CreateAsyncScope()) + { + var service = createScope.ServiceProvider.GetRequiredService(); + failingPermissionData = (ThrowingAfterPersistPermissionDataService)createScope.ServiceProvider + .GetRequiredService(); + var failure = await Record.ExceptionAsync(() => service.CreateAsync( + ValidRequest(), + Admin(), + CancellationToken.None)); + + Assert.IsType(failure); + Assert.Equal("injected mid-sequence failure", failure!.Message); + } + + Assert.NotNull(failingPermissionData.RowsAtFailure); + var rowsAtFailure = failingPermissionData.RowsAtFailure!.Value; + Assert.Equal(1, rowsAtFailure.Users); + Assert.Equal(1, rowsAtFailure.Roles); + Assert.Equal(1, rowsAtFailure.UserRoles); + Assert.Equal(2, rowsAtFailure.ServiceAreas); + Assert.Equal(1, rowsAtFailure.PermissionOverrides); + + await using var verifyScope = serviceProvider.CreateAsyncScope(); + var verify = verifyScope.ServiceProvider.GetRequiredService(); + Assert.Empty(await verify.Users.AsNoTracking().ToListAsync()); + Assert.Empty(await verify.Roles.AsNoTracking().ToListAsync()); + Assert.Empty(await verify.UserRoles.AsNoTracking().ToListAsync()); + Assert.Empty(await verify.UserServiceAreas.AsNoTracking().ToListAsync()); + Assert.Empty(await verify.UserPermissionOverrides.AsNoTracking().ToListAsync()); + } + + [Fact] + public async Task Create_SuccessfulTransaction_CommitsPendingMemberAndAssociations() + { + await using var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=True"); + await connection.OpenAsync(); + + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + await using (var setup = new SqliteTeamMemberTestDbContext(options)) + await setup.Database.EnsureCreatedAsync(); + + await using var serviceProvider = BuildServiceProvider(connection); + await using (var createScope = serviceProvider.CreateAsyncScope()) + { + var service = createScope.ServiceProvider.GetRequiredService(); + var outcome = await service.CreateAsync(ValidRequest(), Admin(), CancellationToken.None); + + Assert.True(outcome.Success); + Assert.True(outcome.Member!.PendingRegistration); + Assert.Equal("taylor@example.com", outcome.Member.Email); + } + + await using var verifyScope = serviceProvider.CreateAsyncScope(); + var verify = verifyScope.ServiceProvider.GetRequiredService(); + var user = await verify.Users.AsNoTracking().SingleAsync(user => user.Email == "taylor@example.com"); + Assert.True(user.PendingRegistration); + + var role = await verify.Roles.AsNoTracking().SingleAsync(role => role.Name == "Dispatcher"); + Assert.Contains(await verify.UserRoles.AsNoTracking().ToListAsync(), row => row.UserId == user.Id && row.RoleId == role.Id); + + var areas = await verify.UserServiceAreas.AsNoTracking() + .Where(area => area.UserId == user.Id) + .Select(area => area.Area) + .OrderBy(area => area) + .ToListAsync(); + Assert.Equal(new[] { "East", "West" }, areas); + + var permissionOverride = await verify.UserPermissionOverrides.AsNoTracking() + .SingleAsync(permission => permission.UserId == user.Id); + Assert.Equal("deleteSites", permissionOverride.PermissionKey); + } + + private static ServiceProvider BuildServiceProvider(SqliteConnection connection, bool injectFailure = false) + { var configuration = new ConfigurationBuilder().Build(); var services = new ServiceCollection(); services.AddLogging(); @@ -47,31 +126,17 @@ public sealed class TeamMemberCreateTransactionTests .AddDefaultTokenProviders(); services.AddDataServices(); services.AddBusinessServices(configuration); - services.AddScoped(); - services.AddScoped(provider => - new ThrowingAfterPersistPermissionDataService( - provider.GetRequiredService())); - await using var serviceProvider = services.BuildServiceProvider(); - await using (var createScope = serviceProvider.CreateAsyncScope()) + if (injectFailure) { - var service = createScope.ServiceProvider.GetRequiredService(); - var failure = await Record.ExceptionAsync(() => service.CreateAsync( - ValidRequest(), - Admin(), - CancellationToken.None)); - - Assert.IsType(failure); - Assert.Equal("injected mid-sequence failure", failure!.Message); + services.AddScoped(); + services.AddScoped(provider => + new ThrowingAfterPersistPermissionDataService( + provider.GetRequiredService(), + provider.GetRequiredService())); } - await using var verifyScope = serviceProvider.CreateAsyncScope(); - var verify = verifyScope.ServiceProvider.GetRequiredService(); - Assert.Empty(await verify.Users.AsNoTracking().ToListAsync()); - Assert.Empty(await verify.Roles.AsNoTracking().ToListAsync()); - Assert.Empty(await verify.UserRoles.AsNoTracking().ToListAsync()); - Assert.Empty(await verify.UserServiceAreas.AsNoTracking().ToListAsync()); - Assert.Empty(await verify.UserPermissionOverrides.AsNoTracking().ToListAsync()); + return services.BuildServiceProvider(); } private static CreateTeamMemberRequestDTO ValidRequest() => new() @@ -121,12 +186,18 @@ public sealed class TeamMemberCreateTransactionTests private sealed class ThrowingAfterPersistPermissionDataService : ITeamPermissionOverrideDataService { private readonly ITeamPermissionOverrideDataService _inner; + private readonly ApplicationDbContext _context; - public ThrowingAfterPersistPermissionDataService(ITeamPermissionOverrideDataService inner) + public ThrowingAfterPersistPermissionDataService( + ITeamPermissionOverrideDataService inner, + ApplicationDbContext context) { _inner = inner; + _context = context; } + public (int Users, int Roles, int UserRoles, int ServiceAreas, int PermissionOverrides)? RowsAtFailure { get; private set; } + public Task GetUserAsync(string userId, CancellationToken cancellationToken) => _inner.GetUserAsync(userId, cancellationToken); @@ -143,6 +214,12 @@ public sealed class TeamMemberCreateTransactionTests CancellationToken cancellationToken) { await _inner.SetOverridesAsync(userId, overrides, cancellationToken); + RowsAtFailure = ( + await _context.Users.CountAsync(cancellationToken), + await _context.Roles.CountAsync(cancellationToken), + await _context.UserRoles.CountAsync(cancellationToken), + await _context.UserServiceAreas.CountAsync(cancellationToken), + await _context.UserPermissionOverrides.CountAsync(cancellationToken)); throw new InvalidOperationException("injected mid-sequence failure"); }