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 { 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()); }); });