Files
scadalink-design/tests/ScadaLink.CentralUI.PlaywrightTests/LoginTests.cs
Joseph Doherty b07f43a308 feat(centralui): rebrand web UI to ScadaBridge + technical-light theme
Rename the user-facing product name from ScadaLink to ScadaBridge across
the six display strings (browser title, sidebar brand, login + not-authorized
headings, dashboard welcome/subtitle). Namespaces, assemblies, config keys,
and _content/ScadaLink.CentralUI asset routes are unchanged.

Apply the technical-light design system: vendor theme.css + IBM Plex fonts
into the CentralUI RCL, include theme.css globally (after Bootstrap so its
--bs-* token overrides win), and restyle the layout chrome to a light
sidebar — white surface, hairline rules, ink text, accent-blue active item,
the brand accent mark. Page markup stays Bootstrap and inherits the warm
paper background, Plex type, accent, and hairline borders via the tokens.

Tests: build 0 warnings; bUnit 542 passed; Playwright 64 passed.
2026-05-22 07:03:46 -04:00

116 lines
3.8 KiB
C#

using System.Text.Json;
using Microsoft.Playwright;
namespace ScadaLink.CentralUI.PlaywrightTests;
[Collection("Playwright")]
public class LoginTests
{
private readonly PlaywrightFixture _fixture;
public LoginTests(PlaywrightFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task UnauthenticatedUser_RedirectsToLogin()
{
var page = await _fixture.NewPageAsync();
await page.GotoAsync(PlaywrightFixture.BaseUrl);
Assert.Contains("/login", page.Url);
await Expect(page.Locator("h4")).ToHaveTextAsync("ScadaBridge");
await Expect(page.Locator("#username")).ToBeVisibleAsync();
await Expect(page.Locator("#password")).ToBeVisibleAsync();
await Expect(page.Locator("button[type='submit']")).ToHaveTextAsync("Sign In");
}
[Fact]
public async Task ValidCredentials_AuthenticatesSuccessfully()
{
var page = await _fixture.NewAuthenticatedPageAsync();
// Should be on the dashboard, not the login page
Assert.DoesNotContain("/login", page.Url);
}
[Fact]
public async Task InvalidCredentials_ShowsError()
{
var page = await _fixture.NewPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/login");
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
// POST invalid credentials via fetch
var status = await page.EvaluateAsync<int>(@"
async () => {
const resp = await fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'username=baduser&password=badpass',
redirect: 'follow'
});
return resp.status;
}
");
// The login endpoint redirects to /login?error=... on failure.
// Reload the page to see the error state.
await page.ReloadAsync();
Assert.Equal(200, status); // redirect followed to login page
}
[Fact]
public async Task TokenEndpoint_ReturnsJwt()
{
var page = await _fixture.NewPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/login");
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
var result = await page.EvaluateAsync<JsonElement>(@"
async () => {
const resp = await fetch('/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'username=multi-role&password=password'
});
const json = await resp.json();
return { status: resp.status, hasToken: !!json.access_token, username: json.username || '' };
}
");
Assert.Equal(200, result.GetProperty("status").GetInt32());
Assert.True(result.GetProperty("hasToken").GetBoolean());
Assert.Equal("multi-role", result.GetProperty("username").GetString());
}
[Fact]
public async Task TokenEndpoint_InvalidCredentials_Returns401()
{
var page = await _fixture.NewPageAsync();
await page.GotoAsync($"{PlaywrightFixture.BaseUrl}/login");
await page.WaitForLoadStateAsync(LoadState.DOMContentLoaded);
var status = await page.EvaluateAsync<int>(@"
async () => {
const resp = await fetch('/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'username=baduser&password=badpass'
});
return resp.status;
}
");
Assert.Equal(401, status);
}
private static ILocatorAssertions Expect(ILocator locator) =>
Assertions.Expect(locator);
}