513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
131 lines
5.2 KiB
C#
131 lines
5.2 KiB
C#
using System.Text.Json;
|
|
using Microsoft.Playwright;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests;
|
|
|
|
[Collection("Playwright")]
|
|
public class LoginTests
|
|
{
|
|
private readonly PlaywrightFixture _fixture;
|
|
|
|
/// <summary>Initializes the tests with the shared Playwright fixture.</summary>
|
|
/// <param name="fixture">The shared Playwright fixture.</param>
|
|
public LoginTests(PlaywrightFixture fixture)
|
|
{
|
|
_fixture = fixture;
|
|
}
|
|
|
|
/// <summary>Verifies that an unauthenticated user is redirected to the login page.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task UnauthenticatedUser_RedirectsToLogin()
|
|
{
|
|
var page = await _fixture.NewPageAsync();
|
|
|
|
await page.GotoAsync(PlaywrightFixture.BaseUrl);
|
|
|
|
Assert.Contains("/login", page.Url);
|
|
// The kit's LoginCard renders the heading "<Product> — sign in" with the
|
|
// product token in its own .login-product span; the submit button reads
|
|
// "Sign in".
|
|
await Expect(page.Locator(".login-product")).ToHaveTextAsync("ScadaBridge");
|
|
await Expect(page.Locator("#username")).ToBeVisibleAsync();
|
|
await Expect(page.Locator("#password")).ToBeVisibleAsync();
|
|
await Expect(page.Locator("button[type='submit']")).ToHaveTextAsync("Sign in");
|
|
}
|
|
|
|
/// <summary>Verifies that valid credentials authenticate the user successfully.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>Verifies that invalid credentials show an error on the login page.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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&__RequestVerificationToken=' + encodeURIComponent(document.querySelector('input[name=__RequestVerificationToken]').value),
|
|
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
|
|
}
|
|
|
|
/// <summary>Verifies that the token endpoint returns a JWT for valid credentials.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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());
|
|
}
|
|
|
|
/// <summary>Verifies that the token endpoint returns HTTP 401 for invalid credentials.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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);
|
|
}
|