Follow-ups from the code review of the test-quality batches: - Consolidate duplicated helpers into e2e/helpers/: seed.ts (seedUpload, seedComment, listComments, findFeedRow) and sse.ts (mintSseTicket, openStream, trackStreamOpens). Refactor authorization-deep, xss-injection, like-comment, sse-ticket-abuse, ddos, sse-realtime, multi-tab, and SseListener to use them — the upload/comment/ticket-flow contracts now live in one place each instead of being re-inlined across 3–7 specs. - xss-injection display-name loop: it navigated to /feed (which renders uploader names, not the viewer's) so "nothing fired" passed vacuously — the payload was never rendered. Now navigate to /account (the actual sink) and add a render guard asserting the payload reached the DOM as escaped text before checking __xssFired. - sse-realtime reconnect: snapshot the stream-open count AFTER backgrounding, so the "new connection" assertion is attributable to the foreground event and can't be satisfied by a spurious native/error reconnect before the toggle. - recover-page: correct the comment (auto-submit is the onPinInput handler, not an $effect). 44 affected specs verified green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
/**
|
|
* Shared SSE-flow helpers. The stream auth flow (mint a single-use ticket, open
|
|
* with `?ticket=`) is security-sensitive and recently changed from `?token=`, so
|
|
* it lives in one place instead of being re-inlined per spec.
|
|
*/
|
|
import type { Page } from '@playwright/test';
|
|
|
|
const BASE = process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101';
|
|
|
|
/** Exchange a JWT for a single-use SSE ticket via POST /api/v1/stream/ticket. */
|
|
export async function mintSseTicket(jwt: string): Promise<string> {
|
|
const res = await fetch(`${BASE}/api/v1/stream/ticket`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${jwt}` },
|
|
});
|
|
if (res.status !== 200) throw new Error(`mintSseTicket failed: ${res.status} ${await res.text()}`);
|
|
return (await res.json()).ticket;
|
|
}
|
|
|
|
/** Open the SSE stream with a ticket, return the HTTP status, and tear the stream down. */
|
|
export async function openStream(ticket: string): Promise<number> {
|
|
const c = new AbortController();
|
|
try {
|
|
const res = await fetch(`${BASE}/api/v1/stream?ticket=${encodeURIComponent(ticket)}`, {
|
|
signal: c.signal,
|
|
});
|
|
return res.status;
|
|
} finally {
|
|
c.abort();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Count EventSource opens (GET /api/v1/stream?ticket=…) on a page — NOT the ticket
|
|
* POST. Returns a getter for the running count.
|
|
*/
|
|
export function trackStreamOpens(page: Page): () => number {
|
|
let n = 0;
|
|
page.on('request', (req) => {
|
|
if (req.method() === 'GET' && req.url().includes('/api/v1/stream?')) n++;
|
|
});
|
|
return () => n;
|
|
}
|