- Waitlist per session: entries for pick-sessions tickets name a session; offers use that session's free places, never run past its start, and claim links tick it. Held offers count towards session capacity. - At the door: the Select items pop-up has the session picker, with today's session preselected for new attendees. - On pick-sessions events every ticket shows the session list straight away. "All sessions" tickets record the ticked sessions (RegistrationOption.sessionIds) at one price; scanning, attendance, certificates, reminders and capacity follow them. - Fix: per-session stock limits weren't enforced (takenCount filtered WaitlistEntry by a sessionId column that didn't exist, and the error was swallowed), and the public event page lost its stock info. - Fix: editing a door registration with picked sessions couldn't be saved. - Fix: the offline-scanning service worker served /_next/static cache-first forever, so dev browsers that had opened the scanner ran stale code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49 lines
1.9 KiB
TypeScript
49 lines
1.9 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 };
|
|
};
|
|
|
|
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 };
|