fix(centralui): SessionExpiry skips redirect after dispose (review 2026-10-21 07/C-3)

This commit is contained in:
Joseph Doherty
2026-10-22 08:35:10 -04:00
parent 1e3765ba6e
commit d36f360d35
2 changed files with 24 additions and 1 deletions
@@ -87,7 +87,11 @@
if (status == Unauthorized)
{
await InvokeAsync(() => Navigation.NavigateTo("/login", forceLoad: true));
// Dispose cancels the CTS but cannot cancel an in-flight ping; skip the redirect
// (and swallow a racing disposal) so the fire-and-forget poll task never faults.
if (_cts is { IsCancellationRequested: true }) return;
try { await InvokeAsync(() => Navigation.NavigateTo("/login", forceLoad: true)); }
catch (ObjectDisposedException) { }
}
}
@@ -37,6 +37,25 @@ public class SessionExpiryComponentTests : BunitContext
Assert.EndsWith("/login", nav.Uri);
}
/// <summary>Verifies a 401 landing after disposal neither redirects nor throws (review 2026-10-21 07/C-3).</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CheckSession_ExpiredAfterDispose_DoesNotRedirectOrThrow()
{
var module = JSInterop.SetupModule(ModulePath);
module.Setup<int>("ping", "/auth/ping").SetResult(401);
var nav = Services.GetRequiredService<NavigationManager>();
var before = nav.Uri;
var cut = Render<SessionExpiry>();
var instance = cut.Instance;
await cut.InvokeAsync(() => ((IDisposable)instance).Dispose());
await instance.CheckSessionAsync();
Assert.Equal(before, nav.Uri);
}
/// <summary>Verifies that a live session does not redirect.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]