94 lines
3.5 KiB
TypeScript
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());
|
|
});
|
|
});
|