89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
import { expect, type Page, test } from '@playwright/test';
|
|
|
|
/**
|
|
* Daily Zone check-in e2e (Workstream 16). Proves the contract: campus staff
|
|
* (ZONE_CHECKIN) record/clear today's zone and read it back; "today" is
|
|
* server-computed from the campus timezone; invalid zones are rejected; and
|
|
* external roles are locked out.
|
|
*
|
|
* Requires the backend running with the database migrated + seeded (the Tigers
|
|
* campus is seeded with `America/Phoenix`).
|
|
*/
|
|
const USER_PASSWORD = 'flatlogicUser123!';
|
|
|
|
const BACKEND_API_URL =
|
|
process.env.VITE_BACKEND_API_URL || 'http://localhost:8080/api';
|
|
|
|
const ZONE_CHECKINS = `${BACKEND_API_URL}/zone_checkins`;
|
|
const ZONE_CHECKINS_TODAY = `${ZONE_CHECKINS}/today`;
|
|
|
|
const TEACHER = 'teacher@flatlogic.com';
|
|
const SUPPORT_STAFF = 'support_staff@flatlogic.com';
|
|
const GUARDIAN = 'guardian@flatlogic.com';
|
|
|
|
const DENIED = [400, 401, 403];
|
|
|
|
interface TodayPayload {
|
|
readonly date: string;
|
|
readonly zone: string | null;
|
|
readonly isCheckedInToday: boolean;
|
|
}
|
|
|
|
async function logout(page: Page): Promise<void> {
|
|
await page.request.post(`${BACKEND_API_URL}/auth/signout`);
|
|
await page.context().clearCookies();
|
|
}
|
|
|
|
async function login(page: Page, email: string): Promise<void> {
|
|
await logout(page);
|
|
await page.goto('/login');
|
|
await page.getByPlaceholder('you@school.edu').fill(email);
|
|
await page.getByPlaceholder('Enter your password').fill(USER_PASSWORD);
|
|
await page.getByRole('button', { name: 'Sign In', exact: true }).click();
|
|
await page.waitForURL((url) => !url.pathname.startsWith('/login'), {
|
|
timeout: 10_000,
|
|
});
|
|
}
|
|
|
|
test.describe('Daily Zone check-in', () => {
|
|
test('a teacher records, reads back, and clears today (campus-local)', async ({ page }) => {
|
|
await login(page, TEACHER);
|
|
|
|
// Clean slate, then check in.
|
|
await page.request.delete(ZONE_CHECKINS_TODAY);
|
|
const created = await page.request.post(ZONE_CHECKINS, {
|
|
data: { data: { zone: 'green' } },
|
|
});
|
|
expect(created.ok()).toBe(true);
|
|
|
|
const today = (await (await page.request.get(ZONE_CHECKINS_TODAY)).json()) as TodayPayload;
|
|
expect(today.isCheckedInToday).toBe(true);
|
|
expect(today.zone).toBe('green');
|
|
// "today" is a YYYY-MM-DD date (server-computed in the campus timezone).
|
|
expect(today.date).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
|
|
const cleared = await page.request.delete(ZONE_CHECKINS_TODAY);
|
|
expect(cleared.ok()).toBe(true);
|
|
const after = (await (await page.request.get(ZONE_CHECKINS_TODAY)).json()) as TodayPayload;
|
|
expect(after.isCheckedInToday).toBe(false);
|
|
expect(after.zone).toBeNull();
|
|
});
|
|
|
|
test('an invalid zone is rejected', async ({ page }) => {
|
|
await login(page, SUPPORT_STAFF);
|
|
const res = await page.request.post(ZONE_CHECKINS, {
|
|
data: { data: { zone: 'purple' } },
|
|
});
|
|
expect(DENIED).toContain(res.status());
|
|
});
|
|
|
|
test('external roles cannot check in', async ({ page }) => {
|
|
await login(page, GUARDIAN);
|
|
expect(DENIED).toContain((await page.request.get(ZONE_CHECKINS_TODAY)).status());
|
|
const create = await page.request.post(ZONE_CHECKINS, {
|
|
data: { data: { zone: 'green' } },
|
|
});
|
|
expect(DENIED).toContain(create.status());
|
|
});
|
|
});
|