9e8a6315e7
* fix: keep topical stock non-depleting in planner and reports * test: stabilize e2e selectors for updated medication semantics * fix(backend): add missing planner translation keys
170 lines
6.1 KiB
TypeScript
170 lines
6.1 KiB
TypeScript
import { expect, type Page, test } from "@playwright/test";
|
|
|
|
interface AuthStateResponse {
|
|
authEnabled: boolean;
|
|
formLoginEnabled: boolean;
|
|
oidcEnabled: boolean;
|
|
oidcProviderName: string;
|
|
registrationEnabled: boolean;
|
|
}
|
|
|
|
async function getAuthState(page: Page): Promise<AuthStateResponse | null> {
|
|
try {
|
|
const response = await page.request.get("/api/auth/state");
|
|
if (!response.ok()) return null;
|
|
return (await response.json()) as AuthStateResponse;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function isAuthEnabled(page: Page): Promise<boolean> {
|
|
const state = await getAuthState(page);
|
|
return state?.authEnabled !== false;
|
|
}
|
|
|
|
/**
|
|
* Authentication E2E Tests
|
|
*
|
|
* Tests the login/register UI when not authenticated.
|
|
* Uses empty storage state to simulate unauthenticated access.
|
|
*
|
|
* NOTE: This file intentionally imports `test` from @playwright/test
|
|
* (not from fixtures) because auth tests use empty storageState and
|
|
* must NOT have the auth-me caching interceptor.
|
|
*/
|
|
test.describe("Authentication", () => {
|
|
test.use({ storageState: { cookies: [], origins: [] } });
|
|
|
|
test("should show login page for unauthenticated users", async ({ page }) => {
|
|
test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment");
|
|
|
|
await page.goto("/");
|
|
await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 });
|
|
|
|
// Should have the app title
|
|
await expect(page.locator(".auth-title")).toContainText("MedAssist-ng");
|
|
});
|
|
|
|
test("should have username and password fields", async ({ page }) => {
|
|
test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment");
|
|
|
|
await page.goto("/");
|
|
await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 });
|
|
|
|
const usernameField = page.locator("#username");
|
|
const passwordField = page.locator("#password");
|
|
|
|
await expect(usernameField).toBeVisible();
|
|
await expect(usernameField).toBeEnabled();
|
|
await expect(passwordField).toBeVisible();
|
|
await expect(passwordField).toBeEnabled();
|
|
});
|
|
|
|
test("should have a submit button", async ({ page }) => {
|
|
test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment");
|
|
|
|
await page.goto("/");
|
|
await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 });
|
|
|
|
const submitButton = page.locator('button.auth-submit[type="submit"]');
|
|
await expect(submitButton).toBeVisible();
|
|
await expect(submitButton).toBeEnabled();
|
|
});
|
|
|
|
test("should not navigate to dashboard without credentials", async ({ page }) => {
|
|
test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment");
|
|
|
|
await page.goto("/dashboard");
|
|
|
|
// Should NOT show the app header (redirected to login)
|
|
await expect(page.locator("header.hero")).not.toBeVisible({ timeout: 10000 });
|
|
|
|
// Should show auth form instead
|
|
await expect(page.locator(".auth-container")).toBeVisible();
|
|
});
|
|
|
|
test("should show error for invalid credentials", async ({ page }) => {
|
|
test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment");
|
|
|
|
await page.goto("/");
|
|
await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 });
|
|
|
|
// Fill in invalid credentials
|
|
await page.locator("#username").fill("nonexistent-user");
|
|
await page.locator("#password").fill("wrongpassword");
|
|
await page.locator('button.auth-submit[type="submit"]').click();
|
|
|
|
// Should show an error message
|
|
await expect(page.locator(".auth-error")).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test("should toggle between login and register forms", async ({ page }) => {
|
|
test.skip(!(await isAuthEnabled(page)), "Auth is disabled in this environment");
|
|
|
|
await page.goto("/");
|
|
await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 });
|
|
|
|
const toggleButton = page.locator("button.auth-link-btn");
|
|
test.skip(
|
|
!(await toggleButton.isVisible().catch(() => false)),
|
|
"Registration toggle is unavailable in this environment"
|
|
);
|
|
|
|
// Check current subtitle text
|
|
const subtitle = page.locator(".auth-subtitle");
|
|
const initialText = await subtitle.textContent();
|
|
|
|
// Click the toggle link (Create account / Already have an account)
|
|
await toggleButton.click();
|
|
|
|
// Subtitle should change
|
|
const newText = await subtitle.textContent();
|
|
expect(newText).not.toBe(initialText);
|
|
});
|
|
|
|
test("should show SSO button when OIDC is enabled", async ({ page }) => {
|
|
const state = await getAuthState(page);
|
|
test.skip(!state?.authEnabled, "Auth is disabled in this environment");
|
|
test.skip(!state?.oidcEnabled, "OIDC is not enabled in this environment");
|
|
|
|
await page.goto("/");
|
|
await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 });
|
|
|
|
const ssoButton = page.locator("button.sso-btn");
|
|
await expect(ssoButton).toBeVisible();
|
|
await expect(ssoButton).toContainText(state.oidcProviderName || "SSO");
|
|
});
|
|
|
|
test("should hide form login when formLoginEnabled is false", async ({ page }) => {
|
|
const state = await getAuthState(page);
|
|
test.skip(!state?.authEnabled, "Auth is disabled in this environment");
|
|
test.skip(state?.formLoginEnabled !== false, "Form login is enabled — cannot test hidden state");
|
|
|
|
await page.goto("/");
|
|
await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 });
|
|
|
|
// Username/password fields should not be visible
|
|
await expect(page.locator("#username")).not.toBeVisible();
|
|
await expect(page.locator("#password")).not.toBeVisible();
|
|
|
|
// SSO button should be the only login method
|
|
await expect(page.locator("button.sso-btn")).toBeVisible();
|
|
});
|
|
|
|
test("should show both login methods when OIDC and form login are enabled", async ({ page }) => {
|
|
const state = await getAuthState(page);
|
|
test.skip(!state?.authEnabled, "Auth is disabled in this environment");
|
|
test.skip(!state?.oidcEnabled, "OIDC is not enabled");
|
|
test.skip(!state?.formLoginEnabled, "Form login is not enabled");
|
|
|
|
await page.goto("/");
|
|
await expect(page.locator(".auth-container")).toBeVisible({ timeout: 15000 });
|
|
|
|
// Both login methods visible
|
|
await expect(page.locator("#username")).toBeVisible();
|
|
await expect(page.locator("#password")).toBeVisible();
|
|
await expect(page.locator("button.sso-btn")).toBeVisible();
|
|
});
|
|
});
|