122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import { expect, type Page, test } from '@playwright/test';
|
|
import {
|
|
createTestUser,
|
|
deleteOrganizations,
|
|
deleteTestUsers,
|
|
loginAsAdmin,
|
|
type CreatedTestUser,
|
|
} from './helpers/seeded-users';
|
|
|
|
/**
|
|
* 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';
|
|
|
|
/** 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', () => {
|
|
let testOwner: CreatedTestUser;
|
|
let testSuperintendent: CreatedTestUser;
|
|
|
|
test.beforeAll(async ({ request }) => {
|
|
const suffix = Date.now();
|
|
await loginAsAdmin(request);
|
|
testOwner = await createTestUser(request, {
|
|
roleName: 'owner',
|
|
email: `e2e-rbac-owner-${suffix}@example.com`,
|
|
organizationId: null,
|
|
});
|
|
expect(testOwner.organizationId, 'owner create should provision an organization').toBeTruthy();
|
|
testSuperintendent = await createTestUser(request, {
|
|
roleName: 'superintendent',
|
|
email: `e2e-rbac-superintendent-${suffix}@example.com`,
|
|
organizationId: testOwner.organizationId,
|
|
});
|
|
});
|
|
|
|
test.afterAll(async ({ request }) => {
|
|
await deleteTestUsers(request, [testSuperintendent, testOwner].filter(Boolean));
|
|
await deleteOrganizations(request, [testOwner?.organizationId]);
|
|
});
|
|
|
|
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, testSuperintendent.email, testSuperintendent.password);
|
|
const res = await page.request.delete(`${BACKEND_API_URL}/users/${testOwner.id}`);
|
|
expect(DENIED).toContain(res.status());
|
|
});
|
|
});
|