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 { await page.request.post(`${BACKEND_API_URL}/auth/signout`); await page.context().clearCookies(); } async function login(page: Page, email: string): Promise { 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()); }); });