From ea24c7e41520ffc7790d65e9f3de2206f418b317 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 19 Feb 2026 12:22:43 +0100 Subject: [PATCH] Update tests (#1742) --- .../Alerts/ServerValidationErrors.razor | 10 +- .../Services/Database/DbService.cs | 105 ++++++++++++------ .../Shard3/Abstracts/TwoFactorAuthBase.cs | 6 +- .../Tests/Client/Shard3/TwoFactorAuthTests.cs | 6 +- .../Tests/Client/Shard4/AuthTests.cs | 7 +- 5 files changed, 88 insertions(+), 46 deletions(-) diff --git a/apps/server/AliasVault.Client/Main/Components/Alerts/ServerValidationErrors.razor b/apps/server/AliasVault.Client/Main/Components/Alerts/ServerValidationErrors.razor index 7777532b6..b9919f0d4 100644 --- a/apps/server/AliasVault.Client/Main/Components/Alerts/ServerValidationErrors.razor +++ b/apps/server/AliasVault.Client/Main/Components/Alerts/ServerValidationErrors.razor @@ -1,9 +1,11 @@ @if (_errors.Any()) { - @foreach (var error in _errors) - { - - } +
+ @foreach (var error in _errors) + { + + } +
} @code { diff --git a/apps/server/AliasVault.Client/Services/Database/DbService.cs b/apps/server/AliasVault.Client/Services/Database/DbService.cs index c9a3433cd..41f174504 100644 --- a/apps/server/AliasVault.Client/Services/Database/DbService.cs +++ b/apps/server/AliasVault.Client/Services/Database/DbService.cs @@ -40,6 +40,7 @@ public sealed class DbService : IDisposable private readonly ILogger _logger; private readonly GlobalNotificationService _globalNotificationService; private readonly IStringLocalizer _sharedLocalizer; + private readonly CancellationTokenSource _backgroundSyncCts = new(); private SettingsService _settingsService = new(); private SqliteConnection? _sqlConnection; private AliasClientDbContext _dbContext; @@ -210,44 +211,79 @@ public sealed class DbService : IDisposable // Set state to indicate background sync is pending _state.UpdateState(DbServiceState.DatabaseStatus.BackgroundSyncPending); + // Capture cancellation token for this background operation + var cancellationToken = _backgroundSyncCts.Token; + // Fire and forget the background save operation - _ = Task.Run(async () => - { - try + _ = Task.Run( + async () => { - // Prune expired items from trash before saving. - await PruneExpiredTrashItemsAsync(); - - // Make sure a public/private RSA encryption key exists before saving the database. - await GetOrCreateEncryptionKeyAsync(); - - var encryptedBase64String = await GetEncryptedDatabaseBase64String(); - - // Update state to show we're actively syncing - _state.UpdateState(DbServiceState.DatabaseStatus.SavingToServer); - - // Save to webapi. - var success = await SaveToServerAsync(encryptedBase64String); - if (success) + try { - _logger.LogInformation("Database successfully saved to server (background sync)."); + if (cancellationToken.IsCancellationRequested || _disposed) + { + return; + } + + // Prune expired items from trash before saving. + await PruneExpiredTrashItemsAsync(); + + if (cancellationToken.IsCancellationRequested || _disposed) + { + return; + } + + // Make sure a public/private RSA encryption key exists before saving the database. + await GetOrCreateEncryptionKeyAsync(); + + if (cancellationToken.IsCancellationRequested || _disposed) + { + return; + } + + var encryptedBase64String = await GetEncryptedDatabaseBase64String(); + + if (cancellationToken.IsCancellationRequested || _disposed) + { + return; + } + + // Update state to show we're actively syncing + _state.UpdateState(DbServiceState.DatabaseStatus.SavingToServer); + + // Save to webapi. + var success = await SaveToServerAsync(encryptedBase64String); + if (success) + { + _logger.LogInformation("Database successfully saved to server (background sync)."); + _state.UpdateState(DbServiceState.DatabaseStatus.Ready); + } + else + { + _logger.LogWarning("Background sync to server failed."); + _globalNotificationService.AddErrorMessage( + "Failed to sync changes to server. Your changes are saved locally and will be synced on next refresh."); + _state.UpdateState(DbServiceState.DatabaseStatus.Ready); + } + } + catch (OperationCanceledException) + { + // Background sync was cancelled (e.g., during logout), this is expected + _logger.LogDebug("Background database sync was cancelled."); + } + catch (Exception ex) when (_disposed || cancellationToken.IsCancellationRequested) + { + // Service was disposed during sync, silently ignore + _logger.LogDebug(ex, "Background database sync aborted due to disposal."); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error during background database sync."); + _globalNotificationService.AddErrorMessage(_sharedLocalizer["ErrorUnknown"]); _state.UpdateState(DbServiceState.DatabaseStatus.Ready); } - else - { - _logger.LogWarning("Background sync to server failed."); - _globalNotificationService.AddErrorMessage( - "Failed to sync changes to server. Your changes are saved locally and will be synced on next refresh."); - _state.UpdateState(DbServiceState.DatabaseStatus.Ready); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error during background database sync."); - _globalNotificationService.AddErrorMessage(_sharedLocalizer["ErrorUnknown"]); - _state.UpdateState(DbServiceState.DatabaseStatus.Ready); - } - }); + }, + cancellationToken); } /// @@ -1127,6 +1163,9 @@ public sealed class DbService : IDisposable if (disposing) { + // Cancel any pending background sync operations first + _backgroundSyncCts.Cancel(); + _backgroundSyncCts.Dispose(); _sqlConnection?.Dispose(); } diff --git a/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard3/Abstracts/TwoFactorAuthBase.cs b/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard3/Abstracts/TwoFactorAuthBase.cs index 0c1f63a0a..6e6124d32 100644 --- a/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard3/Abstracts/TwoFactorAuthBase.cs +++ b/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard3/Abstracts/TwoFactorAuthBase.cs @@ -42,16 +42,16 @@ public class TwoFactorAuthBase : ClientPlaywrightTest // Press the confirm disable button as well. await confirmButton.ClickAsync(); - // Check if the success message is displayed. + // Check if the success message is displayed var expectedMessage = "Two-factor authentication is now successfully disabled."; - var successMessageLocator = Page.Locator($"div[role='alert']:has-text('{expectedMessage}')"); + var successMessageLocator = Page.Locator($".messages-container div[role='alert']:has-text('{expectedMessage}')"); await successMessageLocator.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 5000, }); - var message = await Page.TextContentAsync("div[role='alert']"); + var message = await successMessageLocator.TextContentAsync(); Assert.That(message, Does.Contain("Two-factor authentication is now successfully disabled."), "No two-factor auth disable success message displayed."); } } diff --git a/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard3/TwoFactorAuthTests.cs b/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard3/TwoFactorAuthTests.cs index d3ee4db69..352922706 100644 --- a/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard3/TwoFactorAuthTests.cs +++ b/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard3/TwoFactorAuthTests.cs @@ -27,14 +27,14 @@ public class TwoFactorAuthTests : TwoFactorAuthBase await DisableTwoFactorIfEnabled(); var (totpCode, _) = await EnableTwoFactor(); - // Check if the success message is displayed. - var successMessage = Page.Locator("div[role='alert']"); + // Check if the success message is displayed (target the app notification area, not the error UI in index.html). + var successMessage = Page.Locator(".messages-container div[role='alert']"); await successMessage.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 5000, }); - var message = await Page.TextContentAsync("div[role='alert']"); + var message = await successMessage.TextContentAsync(); Assert.That(message, Does.Contain("Two-factor authentication is now successfully enabled."), "No success message displayed."); await Logout(); diff --git a/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard4/AuthTests.cs b/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard4/AuthTests.cs index 3e752d67f..3f99259aa 100644 --- a/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard4/AuthTests.cs +++ b/apps/server/Tests/AliasVault.E2ETests/Tests/Client/Shard4/AuthTests.cs @@ -160,7 +160,8 @@ public class AuthTests : ClientPlaywrightTest await deleteButton.ClickAsync(); // Check for error message about wrong username - var warning = await Page.TextContentAsync("div[role='alert']"); + var warningLocator = Page.Locator(".messages-container div[role='alert']"); + var warning = await warningLocator.TextContentAsync(); Assert.That(warning, Does.Contain("The username you entered does not match your current username"), "No warning shown when attempting to delete account with wrong username."); // Try with correct username @@ -178,7 +179,7 @@ public class AuthTests : ClientPlaywrightTest await confirmButton.ClickAsync(); // Check for error message about wrong password - warning = await Page.TextContentAsync("div[role='alert']"); + warning = await warningLocator.TextContentAsync(); Assert.That(warning, Does.Contain("The provided password does not match"), "No warning shown when attempting to delete account with wrong password."); // Fill in correct password @@ -201,7 +202,7 @@ public class AuthTests : ClientPlaywrightTest var loginButton = await WaitForAndGetElement("button[type='submit']"); await loginButton.ClickAsync(); - warning = await Page.TextContentAsync("div[role='alert']"); + warning = await warningLocator.TextContentAsync(); Assert.That(warning, Does.Contain("Invalid username or password"), "No error shown when attempting to login with deleted account."); }