From ba66c1396c2847b344fdd488b8b7e1a3e575d365 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:47:40 -0600 Subject: [PATCH] tests(api.tests): pass cancellation token through delegate --- .../Integration/IntegrationTest.cs | 29 +++++----- .../Integration/LoginTests.cs | 18 +++--- .../Integration/RefreshTests.cs | 55 ++++++++++--------- 3 files changed, 52 insertions(+), 50 deletions(-) diff --git a/tests/FiscalOS.API.Tests/Integration/IntegrationTest.cs b/tests/FiscalOS.API.Tests/Integration/IntegrationTest.cs index dd89de9..b89211b 100644 --- a/tests/FiscalOS.API.Tests/Integration/IntegrationTest.cs +++ b/tests/FiscalOS.API.Tests/Integration/IntegrationTest.cs @@ -6,48 +6,47 @@ public abstract class IntegrationTest(TestApi testApi) : IClassFixture, public async ValueTask InitializeAsync() { - await ExecuteDbContextAsync(static async context => + await ExecuteAsync(static async (context, ct) => { - await context.Database.EnsureCreatedAsync(); - }); + await context.Database.EnsureCreatedAsync(ct); + }, TestContext.Current.CancellationToken); } - protected async Task ExecuteDbContextAsync(Func action) + protected async Task ExecuteAsync(Func action, CancellationToken ct) { await using var scope = testApi.Services.CreateAsyncScope(); var context = scope.ServiceProvider.GetRequiredService(); - await action(context); + await action(context, ct); } - protected async Task ExecuteDbContextAsync(Func action) + protected async Task ExecuteAsync(Func action, CancellationToken ct) { await using var scope = testApi.Services.CreateAsyncScope(); var context = scope.ServiceProvider.GetRequiredService(); - await action(context, scope.ServiceProvider); + await action(context, ct, scope.ServiceProvider); } - protected async Task ExecuteDbContextAsync(Func> action) + protected async Task ExecuteAsync(Func> action, CancellationToken ct) { await using var scope = testApi.Services.CreateAsyncScope(); var context = scope.ServiceProvider.GetRequiredService(); - return await action(context); + return await action(context, ct); } - protected async Task ExecuteDbContextAsync(Func> action) + protected async Task ExecuteAsync(Func> action, CancellationToken ct) { await using var scope = testApi.Services.CreateAsyncScope(); var context = scope.ServiceProvider.GetRequiredService(); - return await action(context, scope.ServiceProvider); + return await action(context, ct, scope.ServiceProvider); } public async ValueTask DisposeAsync() { - await ExecuteDbContextAsync(static async context => + await ExecuteAsync(static async (context, ct) => { - await context.Database.EnsureDeletedAsync(); - }); + await context.Database.EnsureDeletedAsync(ct); + }, TestContext.Current.CancellationToken); GC.SuppressFinalize(this); } - } \ No newline at end of file diff --git a/tests/FiscalOS.API.Tests/Integration/LoginTests.cs b/tests/FiscalOS.API.Tests/Integration/LoginTests.cs index eaed4af..439de77 100644 --- a/tests/FiscalOS.API.Tests/Integration/LoginTests.cs +++ b/tests/FiscalOS.API.Tests/Integration/LoginTests.cs @@ -38,16 +38,16 @@ public class LoginTests(TestApi testApi) : IntegrationTest(testApi) [Fact] public async Task Login_WhenUserExistsButPasswordIsIncorrect_ItShouldReturn401WithProblemDetails() { - await ExecuteDbContextAsync(static async (context, sp) => + await ExecuteAsync(static async (context, ct, sp) => { var passwordHasher = sp.GetRequiredService(); var encryptor = sp.GetRequiredService(); - var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(TestContext.Current.CancellationToken); + var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(ct); context.Add(User.From("Stevan", passwordHasher.Hash("@Password1"), userEncryptionKey)); - await context.SaveChangesAsync(TestContext.Current.CancellationToken); - }); + await context.SaveChangesAsync(ct); + }, TestContext.Current.CancellationToken); var req = new { @@ -63,16 +63,16 @@ public class LoginTests(TestApi testApi) : IntegrationTest(testApi) [Fact] public async Task Login_WhenUserExistsAndPasswordIsCorrect_ItShouldReturn200WithJwtTokenAndSetRefreshCookie() { - await ExecuteDbContextAsync(static async (context, sp) => + await ExecuteAsync(static async (context, ct, sp) => { var passwordHasher = sp.GetRequiredService(); var encryptor = sp.GetRequiredService(); - var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(TestContext.Current.CancellationToken); + var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(ct); context.Add(User.From("Stevan", passwordHasher.Hash("@Password1"), userEncryptionKey)); - await context.SaveChangesAsync(TestContext.Current.CancellationToken); - }); + await context.SaveChangesAsync(ct); + }, TestContext.Current.CancellationToken); var req = new { @@ -169,4 +169,4 @@ public record LoginValidationTestCase : IXunitSerializable info.AddValue(nameof(Password), Password); info.AddValue(nameof(ExpectedErrors), ExpectedErrors); } -} +} \ No newline at end of file diff --git a/tests/FiscalOS.API.Tests/Integration/RefreshTests.cs b/tests/FiscalOS.API.Tests/Integration/RefreshTests.cs index e589297..72b224c 100644 --- a/tests/FiscalOS.API.Tests/Integration/RefreshTests.cs +++ b/tests/FiscalOS.API.Tests/Integration/RefreshTests.cs @@ -31,14 +31,14 @@ public class RefreshTests(TestApi testApi) : IntegrationTest(testApi) [Fact] public async Task Refresh_WhenCalledWithTokenBelongingToDifferentUser_ItShouldReturn403WithProblemDetails() { - var (users, refreshToken) = await ExecuteDbContextAsync(static async (context, sp) => + var (users, refreshToken) = await ExecuteAsync(static async (context, ct, sp) => { var passwordHasher = sp.GetRequiredService(); var tokenGenerator = sp.GetRequiredService(); var encryptor = sp.GetRequiredService(); - var user1EncryptionKey = await encryptor.GenerateEncryptedKeyAsync(TestContext.Current.CancellationToken); - var user2EncryptionKey = await encryptor.GenerateEncryptedKeyAsync(TestContext.Current.CancellationToken); + var user1EncryptionKey = await encryptor.GenerateEncryptedKeyAsync(ct); + var user2EncryptionKey = await encryptor.GenerateEncryptedKeyAsync(ct); var user1 = User.From("User1", passwordHasher.Hash("@Password1"), user1EncryptionKey); var user2 = User.From("User2", passwordHasher.Hash("@Password2"), user2EncryptionKey); var refreshToken1 = tokenGenerator.GenerateRefreshToken(user2); @@ -49,9 +49,9 @@ public class RefreshTests(TestApi testApi) : IntegrationTest(testApi) context.Add(user1); context.Add(user2); - await context.SaveChangesAsync(TestContext.Current.CancellationToken); + await context.SaveChangesAsync(ct); return (new User[] { user1, user2 }, refreshToken1); - }); + }, TestContext.Current.CancellationToken); var jwt = JwtTokenBuilder.New() .WithClaim(JwtRegisteredClaimNames.Sub, users[0].Id.ToString()) @@ -65,11 +65,12 @@ public class RefreshTests(TestApi testApi) : IntegrationTest(testApi) await response.Should().BeProblemDetails(HttpStatusCode.Forbidden); - var unrevokedTokensCountForUser2 = await ExecuteDbContextAsync( - async context => await context.Set() + var unrevokedTokensCountForUser2 = await ExecuteAsync( + async (context, ct) => await context.Set() .Include(t => t.User) .Where(t => t.UserId == users[1].Id && t.Revoked == false) - .CountAsync() + .CountAsync(ct), + TestContext.Current.CancellationToken ); unrevokedTokensCountForUser2.Should().Be(0); @@ -78,13 +79,13 @@ public class RefreshTests(TestApi testApi) : IntegrationTest(testApi) [Fact] public async Task Refresh_WhenCalledWithRevokedRefreshToken_ItShouldReturn400WithProblemDetails() { - var (user, refreshToken) = await ExecuteDbContextAsync(static async (context, sp) => + var (user, refreshToken) = await ExecuteAsync(static async (context, ct, sp) => { var passwordHasher = sp.GetRequiredService(); var tokenGenerator = sp.GetRequiredService(); var encryptor = sp.GetRequiredService(); - var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(TestContext.Current.CancellationToken); + var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(ct); var user = User.From("User1", passwordHasher.Hash("@Password1"), userEncryptionKey); var refreshToken = tokenGenerator.GenerateRefreshToken(user); refreshToken.Revoke(); @@ -93,9 +94,9 @@ public class RefreshTests(TestApi testApi) : IntegrationTest(testApi) context.Add(user); - await context.SaveChangesAsync(TestContext.Current.CancellationToken); + await context.SaveChangesAsync(ct); return (user, refreshToken); - }); + }, TestContext.Current.CancellationToken); var jwt = JwtTokenBuilder.New() .WithClaim(JwtRegisteredClaimNames.Sub, user.Id.ToString()) @@ -113,23 +114,23 @@ public class RefreshTests(TestApi testApi) : IntegrationTest(testApi) [Fact] public async Task Refresh_WhenCalledWithExpiredRefreshToken_ItShouldReturn400WithProblemDetails() { - var (user, refreshToken) = await ExecuteDbContextAsync(static async (context, sp) => + var (user, refreshToken) = await ExecuteAsync(static async (context, ct, sp) => { var passwordHasher = sp.GetRequiredService(); var tokenGenerator = sp.GetRequiredService(); var timeProvider = sp.GetRequiredService(); var encryptor = sp.GetRequiredService(); - var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(TestContext.Current.CancellationToken); + var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(ct); var user = User.From("User1", passwordHasher.Hash("@Password1"), userEncryptionKey); var refreshToken = RefreshToken.From(user.Id, "expiredtoken", timeProvider.GetUtcNow().AddHours(-1)); user.AddRefreshToken(refreshToken); context.Add(user); - await context.SaveChangesAsync(TestContext.Current.CancellationToken); + await context.SaveChangesAsync(ct); return (user, refreshToken); - }); + }, TestContext.Current.CancellationToken); var jwt = JwtTokenBuilder.New() .WithClaim(JwtRegisteredClaimNames.Sub, user.Id.ToString()) @@ -149,23 +150,23 @@ public class RefreshTests(TestApi testApi) : IntegrationTest(testApi) [InlineData(-5)] public async Task Refresh_WhenCalledWithValidRefreshTokenAndExpiredOrNotExpiredAccessToken_ItShouldReturn200WithNewTokensAndSetRefreshCookie(int accessTokenExpiresAtOffset) { - var (user, refreshToken) = await ExecuteDbContextAsync(static async (context, sp) => + var (user, refreshToken) = await ExecuteAsync(static async (context, ct, sp) => { var passwordHasher = sp.GetRequiredService(); var tokenGenerator = sp.GetRequiredService(); var timeProvider = sp.GetRequiredService(); var encryptor = sp.GetRequiredService(); - var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(TestContext.Current.CancellationToken); + var userEncryptionKey = await encryptor.GenerateEncryptedKeyAsync(ct); var user = User.From("User1", passwordHasher.Hash("@Password1"), userEncryptionKey); var refreshToken = tokenGenerator.GenerateRefreshToken(user); user.AddRefreshToken(refreshToken); context.Add(user); - await context.SaveChangesAsync(TestContext.Current.CancellationToken); + await context.SaveChangesAsync(ct); return (user, refreshToken); - }); + }, TestContext.Current.CancellationToken); var jwt = JwtTokenBuilder.New() .WithClaim(JwtRegisteredClaimNames.Sub, user.Id.ToString()) @@ -181,20 +182,22 @@ public class RefreshTests(TestApi testApi) : IntegrationTest(testApi) response.Should().HaveSetCookieHeader("fiscalos_refresh_cookie"); await response.Should().BeJsonContentOfType(HttpStatusCode.OK); - var oldRefreshTokenInDb = await ExecuteDbContextAsync( - async context => await context.Set() + var oldRefreshTokenInDb = await ExecuteAsync( + async (context, ct) => await context.Set() .Include(t => t.User) .Where(t => t.UserId == user.Id && t.Token == refreshToken.Token && t.Revoked == true) - .SingleOrDefaultAsync() + .SingleOrDefaultAsync(ct), + TestContext.Current.CancellationToken ); oldRefreshTokenInDb.Should().NotBeNull(); - var newRefreshTokenInDb = await ExecuteDbContextAsync( - async context => await context.Set() + var newRefreshTokenInDb = await ExecuteAsync( + async (context, ct) => await context.Set() .Include(t => t.User) .Where(t => t.UserId == user.Id && t.Revoked == false && t.Token != refreshToken.Token) - .SingleOrDefaultAsync() + .SingleOrDefaultAsync(ct), + TestContext.Current.CancellationToken ); newRefreshTokenInDb.Should().NotBeNull();