using System.Net; using Microsoft.Extensions.DependencyInjection; using ScadaLink.CentralUI.Auth; using ScadaLink.Security; namespace ScadaLink.IntegrationTests; /// /// WP-22: Auth flow integration tests. /// Tests that require a running LDAP server are marked with Integration trait. /// public class AuthFlowTests : IClassFixture { private readonly ScadaLinkWebApplicationFactory _factory; public AuthFlowTests(ScadaLinkWebApplicationFactory factory) { _factory = factory; } [Fact] public async Task LoginEndpoint_WithEmptyCredentials_RedirectsToLoginWithError() { var client = _factory.CreateClient(new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); var content = new FormUrlEncodedContent(new[] { new KeyValuePair("username", ""), new KeyValuePair("password", "") }); var response = await client.PostAsync("/auth/login", content); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); var location = response.Headers.Location?.ToString() ?? ""; Assert.Contains("/login", location); Assert.Contains("error", location, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task LogoutEndpoint_ClearsCookieAndRedirects() { var client = _factory.CreateClient(new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); var response = await client.PostAsync("/auth/logout", null); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); var location = response.Headers.Location?.ToString() ?? ""; Assert.Contains("/login", location); } [Fact] public void JwtTokenService_GenerateAndValidate_RoundTrips() { using var scope = _factory.Services.CreateScope(); var jwtService = scope.ServiceProvider.GetRequiredService(); var token = jwtService.GenerateToken( displayName: "Test User", username: "testuser", roles: new[] { "Admin", "Design" }, permittedSiteIds: null); Assert.NotNull(token); var principal = jwtService.ValidateToken(token); Assert.NotNull(principal); var displayName = principal!.FindFirst(JwtTokenService.DisplayNameClaimType)?.Value; var username = principal.FindFirst(JwtTokenService.UsernameClaimType)?.Value; var roles = principal.FindAll(JwtTokenService.RoleClaimType).Select(c => c.Value).ToList(); Assert.Equal("Test User", displayName); Assert.Equal("testuser", username); Assert.Contains("Admin", roles); Assert.Contains("Design", roles); } [Fact] public void JwtTokenService_WithSiteScopes_IncludesSiteIdClaims() { using var scope = _factory.Services.CreateScope(); var jwtService = scope.ServiceProvider.GetRequiredService(); var token = jwtService.GenerateToken( displayName: "Deployer", username: "deployer1", roles: new[] { "Deployment" }, permittedSiteIds: new[] { "1", "3" }); var principal = jwtService.ValidateToken(token); Assert.NotNull(principal); var siteIds = principal!.FindAll(JwtTokenService.SiteIdClaimType).Select(c => c.Value).ToList(); Assert.Contains("1", siteIds); Assert.Contains("3", siteIds); } [Trait("Category", "Integration")] [Fact] public async Task LoginEndpoint_WithValidLdapCredentials_SetsCookieAndRedirects() { // Requires GLAuth test LDAP server: docker compose -f infra/docker-compose.yml up -d glauth // GLAuth runs on localhost:3893, baseDN dc=scadalink,dc=local, all passwords "password" if (!await IsLdapAvailableAsync()) { // Skip gracefully if GLAuth not running — not a test failure return; } var client = _factory.CreateClient(new Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions { AllowAutoRedirect = false }); var content = new FormUrlEncodedContent(new[] { new KeyValuePair("username", "admin"), new KeyValuePair("password", "password") }); var response = await client.PostAsync("/auth/login", content); Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); var location = response.Headers.Location?.ToString() ?? ""; Assert.Equal("/", location); // Verify auth cookie was set var setCookieHeader = response.Headers.GetValues("Set-Cookie").FirstOrDefault(); Assert.NotNull(setCookieHeader); Assert.Contains("ScadaLink.Auth", setCookieHeader); } private static async Task IsLdapAvailableAsync() { try { using var tcp = new System.Net.Sockets.TcpClient(); await tcp.ConnectAsync("localhost", 3893).WaitAsync(TimeSpan.FromSeconds(2)); return true; } catch { return false; } } }