using Bunit;
using Bunit.TestDoubles;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using ScadaLink.CentralUI.Components.Shared;
namespace ScadaLink.CentralUI.Tests.Auth;
///
/// Regression tests for CentralUI-020 and CentralUI-025. SessionExpiry
/// used to poll the Blazor AuthenticationStateProvider, which (via
/// CookieAuthenticationStateProvider) serves a frozen constructor-time
/// principal — so the polled state could never become "expired" and the
/// idle-logout redirect never fired. The component now polls the server
/// GET /auth/ping endpoint, which reflects the live cookie session: a
/// 401 response triggers a redirect to /login. These tests exercise that
/// redirect path directly (CentralUI-025: the path was previously untested).
///
public class SessionExpiryComponentTests : BunitContext
{
private const string ModulePath = "./_content/ScadaLink.CentralUI/js/session-expiry.js";
[Fact]
public async Task CheckSession_ExpiredSession_RedirectsToLogin()
{
// The server reports the cookie has lapsed: ping returns HTTP 401.
var module = JSInterop.SetupModule(ModulePath);
module.Setup("ping", "/auth/ping").SetResult(401);
var nav = Services.GetRequiredService();
var cut = Render();
await cut.InvokeAsync(() => cut.Instance.CheckSessionAsync());
Assert.EndsWith("/login", nav.Uri);
}
[Fact]
public async Task CheckSession_LiveSession_DoesNotRedirect()
{
// The server reports the session is still valid: ping returns HTTP 200.
var module = JSInterop.SetupModule(ModulePath);
module.Setup("ping", "/auth/ping").SetResult(200);
var nav = Services.GetRequiredService();
var before = nav.Uri;
var cut = Render();
await cut.InvokeAsync(() => cut.Instance.CheckSessionAsync());
Assert.Equal(before, nav.Uri);
Assert.DoesNotContain("/login", nav.Uri);
}
[Fact]
public async Task CheckSession_TransientNetworkFailure_DoesNotRedirect()
{
// A network blip surfaces as status 0 — inconclusive. The component must
// NOT log an authenticated user out on a transient failure.
var module = JSInterop.SetupModule(ModulePath);
module.Setup("ping", "/auth/ping").SetResult(0);
var nav = Services.GetRequiredService();
var before = nav.Uri;
var cut = Render();
await cut.InvokeAsync(() => cut.Instance.CheckSessionAsync());
Assert.Equal(before, nav.Uri);
}
[Fact]
public async Task CheckSession_OnLoginPage_DoesNotPingOrRedirect()
{
// On /login the component must neither poll nor redirect (a /login →
// /login redirect would loop). JSInterop is left in Strict mode with no
// module setup, so any ping call would throw and fail the test.
var nav = (BunitNavigationManager)Services
.GetRequiredService();
nav.NavigateTo("login");
var cut = Render();
await cut.InvokeAsync(() => cut.Instance.CheckSessionAsync());
// No JS module import was attempted and the URL is unchanged.
Assert.EndsWith("/login", nav.Uri);
}
}