40227-vm/frontend/tests/e2e/rbac-access.seeded.e2e.ts
2026-06-12 06:55:35 +02:00

94 lines
3.5 KiB
TypeScript

import { expect, type Page, test } from '@playwright/test';
/**
* Authenticated RBAC access e2e (Workstream 8 / §3.6). Uses the seeded per-role
* fixture users (Workstream 4). Requires the backend running with the database
* migrated + seeded, and the seed passwords in the environment.
*/
const ADMIN_PASSWORD = 'flatlogicAdmin123!';
const USER_PASSWORD = 'flatlogicUser123!';
const ADMIN_EMAIL = 'admin@flatlogic.com';
const BACKEND_API_URL =
process.env.VITE_BACKEND_API_URL || 'http://localhost:8080/api';
// Seeded fixture user ids (see backend `shared/constants/seed-fixtures.ts`).
const OWNER_ID = 'b1a7c0de-0000-4000-8000-000000000012';
/** Denied responses may be 400 (permission middleware) or 403 (relational policy). */
const DENIED = [400, 401, 403];
async function login(page: Page, email: string, password: string): Promise<void> {
await page.goto('/login');
await page.getByPlaceholder('you@school.edu').fill(email);
await page.getByPlaceholder('Enter your password').fill(password);
await page.getByRole('button', { name: 'Sign In', exact: true }).click();
// AuthGuard admits the shell; IndexRedirect lands on the first accessible module.
await page.waitForURL((url) => !url.pathname.startsWith('/login'), {
timeout: 10_000,
});
}
test.describe('RBAC route access', () => {
test('unauthenticated visitors are redirected to /login', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveURL(/\/login/);
});
test('the super admin can open the director dashboard', async ({ page }) => {
await login(page, ADMIN_EMAIL, ADMIN_PASSWORD);
await page.goto('/director-dashboard');
await expect(page).toHaveURL(/director-dashboard/);
await expect(page.getByText('404')).toHaveCount(0);
});
test('a teacher cannot open the director dashboard (404)', async ({ page }) => {
await login(page, 'teacher@flatlogic.com', USER_PASSWORD);
await page.goto('/director-dashboard');
await expect(page.getByText('404')).toBeVisible();
});
test('a student lands on an external page and is blocked from the dashboard', async ({
page,
}) => {
await login(page, 'student@flatlogic.com', USER_PASSWORD);
// Role-aware landing: the first module a student can access is Community.
await expect(page).toHaveURL(/community-partnerships/);
// Allowed external pages.
await page.goto('/vocational-opportunities');
await expect(page.getByText('404')).toHaveCount(0);
await page.goto('/esa-funding');
await expect(page.getByText('404')).toHaveCount(0);
// Staff-only page is forbidden.
await page.goto('/dashboard');
await expect(page.getByText('404')).toBeVisible();
});
});
test.describe('RBAC API enforcement', () => {
test('the super admin can list users', async ({ page }) => {
await login(page, ADMIN_EMAIL, ADMIN_PASSWORD);
const res = await page.request.get(`${BACKEND_API_URL}/users`);
expect(res.status()).toBe(200);
});
test('a teacher cannot create a user', async ({ page }) => {
await login(page, 'teacher@flatlogic.com', USER_PASSWORD);
const res = await page.request.post(`${BACKEND_API_URL}/users`, {
data: { data: { email: 'should-not-exist@example.com' } },
});
expect(DENIED).toContain(res.status());
});
test('a superintendent cannot delete the owner (relational policy)', async ({
page,
}) => {
await login(page, 'superintendent@flatlogic.com', USER_PASSWORD);
const res = await page.request.delete(`${BACKEND_API_URL}/users/${OWNER_ID}`);
expect(DENIED).toContain(res.status());
});
});