diff --git a/tests/ScadaLink.CentralUI.PlaywrightTests/PlaywrightFixture.cs b/tests/ScadaLink.CentralUI.PlaywrightTests/PlaywrightFixture.cs index 0a8ba97..995462e 100644 --- a/tests/ScadaLink.CentralUI.PlaywrightTests/PlaywrightFixture.cs +++ b/tests/ScadaLink.CentralUI.PlaywrightTests/PlaywrightFixture.cs @@ -49,11 +49,17 @@ public class PlaywrightFixture : IAsyncLifetime } /// - /// Create a new page and log in with the test user. + /// Create a new page and log in with the default multi-role test user. + /// + public Task NewAuthenticatedPageAsync() => + NewAuthenticatedPageAsync(TestUsername, TestPassword); + + /// + /// Create a new page and log in with specific credentials. /// Uses JavaScript fetch() to POST to /auth/login from within the browser, /// which sets the auth cookie in the browser context. Then navigates to the dashboard. /// - public async Task NewAuthenticatedPageAsync() + public async Task NewAuthenticatedPageAsync(string username, string password) { var page = await NewPageAsync(); @@ -63,24 +69,21 @@ public class PlaywrightFixture : IAsyncLifetime // POST to /auth/login via fetch() inside the browser. // This sets the auth cookie in the browser context automatically. - // Use redirect: 'follow' so the browser follows the 302 and the cookie is stored. var finalUrl = await page.EvaluateAsync(@" - async () => { + async ([u, p]) => { const resp = await fetch('/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: 'username=' + encodeURIComponent('" + TestUsername + @"') - + '&password=' + encodeURIComponent('" + TestPassword + @"'), + body: 'username=' + encodeURIComponent(u) + '&password=' + encodeURIComponent(p), redirect: 'follow' }); return resp.url; } - "); + ", new object[] { username, password }); - // The fetch followed the redirect. If it ended on /login, auth failed. if (finalUrl.Contains("/login")) { - throw new InvalidOperationException($"Login failed — redirected back to login: {finalUrl}"); + throw new InvalidOperationException($"Login failed for '{username}' — redirected back to login: {finalUrl}"); } // Navigate to the dashboard — cookie authenticates us diff --git a/tests/ScadaLink.CentralUI.PlaywrightTests/RoleNavigationTests.cs b/tests/ScadaLink.CentralUI.PlaywrightTests/RoleNavigationTests.cs new file mode 100644 index 0000000..0e85017 --- /dev/null +++ b/tests/ScadaLink.CentralUI.PlaywrightTests/RoleNavigationTests.cs @@ -0,0 +1,226 @@ +using Microsoft.Playwright; + +namespace ScadaLink.CentralUI.PlaywrightTests; + +/// +/// Verifies that navigation sections and links are shown/hidden based on the user's role. +/// +/// LDAP test users (all passwords: "password"): +/// admin → Admin only +/// designer → Design only +/// deployer → Deployment only +/// multi-role → Admin + Design + Deployment +/// +/// Nav structure (from NavMenu.razor): +/// All authenticated: Dashboard, Monitoring (Health Dashboard, Event Logs, Parked Messages) +/// Admin: LDAP Mappings, Sites, Data Connections, API Keys, Audit Log +/// Design: Templates, Shared Scripts, External Systems, Areas +/// Deployment: Instances, Deployments, Debug View +/// +[Collection("Playwright")] +public class RoleNavigationTests +{ + private readonly PlaywrightFixture _fixture; + + public RoleNavigationTests(PlaywrightFixture fixture) + { + _fixture = fixture; + } + + // ── Admin-only user ───────────────────────────────────────────── + + [Fact] + public async Task AdminUser_SeesAdminSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("admin", "password"); + + await AssertNavLinkVisible(page, "Sites"); + await AssertNavLinkVisible(page, "Data Connections"); + await AssertNavLinkVisible(page, "API Keys"); + await AssertNavLinkVisible(page, "LDAP Mappings"); + await AssertNavLinkVisible(page, "Audit Log"); + } + + [Fact] + public async Task AdminUser_DoesNotSeeDesignSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("admin", "password"); + + await AssertNavLinkHidden(page, "Templates"); + await AssertNavLinkHidden(page, "Shared Scripts"); + await AssertNavLinkHidden(page, "External Systems"); + } + + [Fact] + public async Task AdminUser_DoesNotSeeDeploymentSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("admin", "password"); + + await AssertNavLinkHidden(page, "Instances"); + await AssertNavLinkHidden(page, "Deployments"); + await AssertNavLinkHidden(page, "Debug View"); + } + + [Fact] + public async Task AdminUser_SeesMonitoringSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("admin", "password"); + + await AssertNavLinkVisible(page, "Health Dashboard"); + await AssertNavLinkVisible(page, "Event Logs"); + await AssertNavLinkVisible(page, "Parked Messages"); + } + + // ── Design-only user ──────────────────────────────────────────── + + [Fact] + public async Task DesignUser_SeesDesignSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("designer", "password"); + + await AssertNavLinkVisible(page, "Templates"); + await AssertNavLinkVisible(page, "Shared Scripts"); + await AssertNavLinkVisible(page, "External Systems"); + await AssertNavLinkVisible(page, "Areas"); + } + + [Fact] + public async Task DesignUser_DoesNotSeeAdminSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("designer", "password"); + + await AssertNavLinkHidden(page, "Sites"); + await AssertNavLinkHidden(page, "Data Connections"); + await AssertNavLinkHidden(page, "API Keys"); + await AssertNavLinkHidden(page, "LDAP Mappings"); + await AssertNavLinkHidden(page, "Audit Log"); + } + + [Fact] + public async Task DesignUser_DoesNotSeeDeploymentSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("designer", "password"); + + await AssertNavLinkHidden(page, "Instances"); + await AssertNavLinkHidden(page, "Deployments"); + await AssertNavLinkHidden(page, "Debug View"); + } + + [Fact] + public async Task DesignUser_SeesMonitoringButNotAuditLog() + { + var page = await _fixture.NewAuthenticatedPageAsync("designer", "password"); + + await AssertNavLinkVisible(page, "Health Dashboard"); + await AssertNavLinkVisible(page, "Event Logs"); + await AssertNavLinkVisible(page, "Parked Messages"); + await AssertNavLinkHidden(page, "Audit Log"); + } + + // ── Deployment-only user ──────────────────────────────────────── + + [Fact] + public async Task DeploymentUser_SeesDeploymentSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("deployer", "password"); + + await AssertNavLinkVisible(page, "Instances"); + await AssertNavLinkVisible(page, "Deployments"); + await AssertNavLinkVisible(page, "Debug View"); + } + + [Fact] + public async Task DeploymentUser_DoesNotSeeAdminSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("deployer", "password"); + + await AssertNavLinkHidden(page, "Sites"); + await AssertNavLinkHidden(page, "Data Connections"); + await AssertNavLinkHidden(page, "API Keys"); + await AssertNavLinkHidden(page, "LDAP Mappings"); + await AssertNavLinkHidden(page, "Audit Log"); + } + + [Fact] + public async Task DeploymentUser_DoesNotSeeDesignSection() + { + var page = await _fixture.NewAuthenticatedPageAsync("deployer", "password"); + + await AssertNavLinkHidden(page, "Templates"); + await AssertNavLinkHidden(page, "Shared Scripts"); + await AssertNavLinkHidden(page, "External Systems"); + } + + [Fact] + public async Task DeploymentUser_SeesMonitoringButNotAuditLog() + { + var page = await _fixture.NewAuthenticatedPageAsync("deployer", "password"); + + await AssertNavLinkVisible(page, "Health Dashboard"); + await AssertNavLinkVisible(page, "Event Logs"); + await AssertNavLinkVisible(page, "Parked Messages"); + await AssertNavLinkHidden(page, "Audit Log"); + } + + // ── Multi-role user (Admin + Design + Deployment) ─────────────── + + [Fact] + public async Task MultiRoleUser_SeesAllSections() + { + var page = await _fixture.NewAuthenticatedPageAsync("multi-role", "password"); + + // Admin + await AssertNavLinkVisible(page, "Sites"); + await AssertNavLinkVisible(page, "Data Connections"); + await AssertNavLinkVisible(page, "API Keys"); + await AssertNavLinkVisible(page, "LDAP Mappings"); + await AssertNavLinkVisible(page, "Audit Log"); + + // Design + await AssertNavLinkVisible(page, "Templates"); + await AssertNavLinkVisible(page, "Shared Scripts"); + await AssertNavLinkVisible(page, "External Systems"); + await AssertNavLinkVisible(page, "Areas"); + + // Deployment + await AssertNavLinkVisible(page, "Instances"); + await AssertNavLinkVisible(page, "Deployments"); + await AssertNavLinkVisible(page, "Debug View"); + + // Monitoring (all authenticated) + await AssertNavLinkVisible(page, "Health Dashboard"); + await AssertNavLinkVisible(page, "Event Logs"); + await AssertNavLinkVisible(page, "Parked Messages"); + } + + // ── All users see Dashboard ───────────────────────────────────── + + [Theory] + [InlineData("admin")] + [InlineData("designer")] + [InlineData("deployer")] + [InlineData("multi-role")] + public async Task AllUsers_SeeDashboardLink(string username) + { + var page = await _fixture.NewAuthenticatedPageAsync(username, "password"); + + var dashboardLink = page.GetByRole(AriaRole.Link, new() { Name = "Dashboard", Exact = true }); + await Assertions.Expect(dashboardLink).ToBeVisibleAsync(); + } + + // ── Helpers ───────────────────────────────────────────────────── + + private static async Task AssertNavLinkVisible(IPage page, string linkText) + { + var locator = page.Locator($"nav a:has-text('{linkText}')"); + var count = await locator.CountAsync(); + Assert.True(count > 0, $"Expected nav link '{linkText}' to be visible, but it was not found"); + } + + private static async Task AssertNavLinkHidden(IPage page, string linkText) + { + var locator = page.Locator($"nav a:has-text('{linkText}')"); + var count = await locator.CountAsync(); + Assert.True(count == 0, $"Expected nav link '{linkText}' to be hidden, but it was found"); + } +}