- Retention policies (Admin -> Privacy -> Retention) applied by a nightly
`retention` job: inactive accounts anonymised after 5 years with a
30-day warning (on by default; never staff roles or people with an
upcoming registration), form answers, login history, payer details in
payment notifications, audit/access logs
- User.lastLoginAt (set on login, backfilled) and inactivityWarnedAt
- Breach register (SecurityBreach) with drafts for the people affected
and the Information Regulator
- Readiness checklist with a score
- Children's/medical flag in the access log; {retention} placeholder in
the privacy notice
- Unit tests (retention.test.js), e2e (privacy-retention.spec.ts); docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012mt6TCv85T9VKRq3cV7rXq
52 lines
2.0 KiB
TypeScript
52 lines
2.0 KiB
TypeScript
import { test as base, expect, type Page } from "@playwright/test";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
|
|
export type Role = "admin" | "supervisor" | "staff" | "user";
|
|
export type Seed = {
|
|
users: Record<Role, { id: string; name: string; email: string; token: string }>;
|
|
events: { free: string; paid: string; course: string; dropIn: string; conference: string };
|
|
course: { sessions: { id: string; label: string }[]; ticketId: string; qrCode: string };
|
|
unsubscribeToken: string;
|
|
newsPromptUser: { id: string; token: string };
|
|
retention: { toWarn: string; toAnonymise: string };
|
|
};
|
|
|
|
export const API = "http://localhost:5100";
|
|
|
|
// Written by e2e/support/prepare-db.js when the e2e backend starts, so read lazily.
|
|
let cached: Seed | null = null;
|
|
export function seed(): Seed {
|
|
if (!cached) cached = JSON.parse(fs.readFileSync(path.join(__dirname, ".data", "seed.json"), "utf8")) as Seed;
|
|
return cached;
|
|
}
|
|
|
|
/** Signs the page in as a seeded user (the token goes where the app keeps it). */
|
|
export async function loginAs(page: Page, role: Role) {
|
|
await page.addInitScript((token) => {
|
|
localStorage.setItem("hope_events_token", token);
|
|
}, seed().users[role].token);
|
|
}
|
|
|
|
export const test = base.extend({
|
|
page: async ({ page }, use) => {
|
|
// Skip the cookie notice so it never covers what the tests click.
|
|
await page.addInitScript(() => localStorage.setItem("hope_events_cookie_consent", "declined"));
|
|
await use(page);
|
|
},
|
|
});
|
|
|
|
/** Calls the e2e API as a seeded user. */
|
|
export async function api<T = unknown>(page: Page, role: Role, method: string, url: string, body?: unknown): Promise<T> {
|
|
const res = await page.request.fetch(`${API}${url}`, {
|
|
method,
|
|
headers: { Authorization: `Bearer ${seed().users[role].token}`, "Content-Type": "application/json" },
|
|
data: body === undefined ? undefined : JSON.stringify(body),
|
|
});
|
|
const text = await res.text();
|
|
if (!res.ok()) throw new Error(`${method} ${url} → ${res.status()}: ${text}`);
|
|
return (text ? JSON.parse(text) : null) as T;
|
|
}
|
|
|
|
export { expect };
|