- /api/stats: add verifyCsrf middleware (defense-in-depth; no-op for GETs) - VerifyEmailPage: useRef guard to prevent React StrictMode double-fire of the single-use verify token in dev - router.tsx: route-level code splitting via React.lazy + Suspense; initial bundle drops from 397 KB to 224 KB with per-route chunks (0.3–14 KB each) - e2e: wait for verify-email completion before login; bump Account-menu timeout to handle Vite cold-chunk compile
52 lines
2.5 KiB
TypeScript
52 lines
2.5 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
async function fetchVerifyLink(email: string): Promise<string> {
|
|
for (let i = 0; i < 30; i++) {
|
|
const res = await fetch('http://localhost:8025/api/v1/messages?limit=20');
|
|
const data = await res.json() as { messages: { ID: string; To: { Address: string }[] }[] };
|
|
const msg = data.messages.find((m) => m.To.some((t) => t.Address === email));
|
|
if (msg) {
|
|
const body = await (await fetch(`http://localhost:8025/api/v1/message/${msg.ID}`)).json() as { Text: string };
|
|
const m = body.Text.match(/https?:\/\/[^\s]+verify-email\?token=[^\s]+/);
|
|
if (m) return m[0];
|
|
}
|
|
await new Promise((r) => setTimeout(r, 250));
|
|
}
|
|
throw new Error('no verify link for ' + email);
|
|
}
|
|
|
|
test('register → verify → login → create lesson → add card → practice once', async ({ page }) => {
|
|
const email = `user+${Date.now()}@example.com`;
|
|
const password = 'secretpass';
|
|
await page.goto('/register');
|
|
await page.getByLabel(/Naam/).fill('E2E User');
|
|
await page.getByLabel(/E-mailadres/).fill(email);
|
|
await page.getByLabel(/Wachtwoord/).fill(password);
|
|
await page.getByRole('button', { name: /Account aanmaken/ }).click();
|
|
await expect(page.getByText(/bevestigingsmail/i)).toBeVisible({ timeout: 10_000 });
|
|
|
|
const link = await fetchVerifyLink(email);
|
|
await page.goto(link);
|
|
// Wait for the verify POST to finish before logging in.
|
|
await expect(page.getByRole('link', { name: 'Naar inloggen' })).toBeVisible({ timeout: 10_000 });
|
|
|
|
await page.goto('/login');
|
|
await page.getByLabel(/E-mailadres/).fill(email);
|
|
await page.getByLabel(/Wachtwoord/).fill(password);
|
|
await page.getByRole('button', { name: 'Inloggen' }).click();
|
|
await expect(page.getByRole('button', { name: 'Account menu' })).toBeVisible({ timeout: 15_000 });
|
|
|
|
await page.goto('/admin');
|
|
await page.getByPlaceholder(/Nieuwe wortel-les/).fill('E2E les');
|
|
await page.getByRole('button', { name: /Toevoegen/ }).first().click();
|
|
await page.getByRole('link', { name: /E2E les/ }).first().click();
|
|
await page.getByPlaceholder('Nieuwe vraag').fill('q1');
|
|
await page.getByPlaceholder('Antwoord').fill('a1');
|
|
await page.getByRole('button', { name: 'Kaart toevoegen' }).click();
|
|
await page.getByRole('link', { name: /Start oefenen/ }).click();
|
|
await page.getByRole('button', { name: /Start sessie/ }).click();
|
|
await page.getByRole('button', { name: 'Toon antwoord' }).click();
|
|
await page.getByRole('button', { name: /Goed/ }).click();
|
|
await expect(page.getByText(/Sessie klaar/)).toBeVisible({ timeout: 8_000 });
|
|
});
|