test(e2e): make vacuous feed/SSE tests assert real behavior

Several tests ran green while asserting nothing. Replace them with real
assertions, and fix the SSE helper they depend on.

sse-listener: exchange the JWT for a single-use ticket (POST /stream/ticket) and
connect via ?ticket= — the helper still used the dead ?token= scheme, so every
SSE-based assertion would have silently failed to receive events.

like-comment:
- "like is idempotent" asserted nothing (void feed; void b) → now seeds a real
  upload and pins the like contract: counted once per user, toggles off on repeat
  (guards double-count), and a second user's like is counted independently.
- "comment → SSE to B" asserted length >= 0 (always true) → B now subscribes to
  the stream, A comments, and B must receive the new-comment event for that upload
  (comment_count === 1). ~30s due to reverse-proxy SSE buffering; timeout raised.

sse-realtime: only checked a nav link was visible → now counts EventSource opens
and asserts a fresh stream connection after hidden→visible (also fixes the sim,
which set visibilityState but not document.hidden, so the close never fired).

multi-tab "SSE delivers to both": only checked nav links → now asserts each tab
opens its own stream connection (delivery isn't asserted — it hinges on the ~30s
proxy buffering; connection establishment is the reliable, honest signal).

safe-area: delete the /join probe whose only assertion was Array.isArray(x) ===
true (always true); the real sheet-level env() check already exists below it.

All verified green against the live backend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
fabi
2026-07-01 19:05:53 +02:00
parent d4aa7b4932
commit 136417d6b4
5 changed files with 155 additions and 54 deletions

View File

@@ -3,8 +3,9 @@
* `like-update` arrived for upload X within 5 seconds" without driving a
* second browser tab.
*
* The backend authenticates the SSE endpoint via `?token=` query param
* (the EventSource API can't set headers).
* The backend authenticates the SSE endpoint via a single-use `?ticket=` minted
* at POST /api/v1/stream/ticket (the raw JWT is never put in the URL). This helper
* does that exchange internally, so callers still just pass a JWT to `start()`.
*/
export type SseEvent = { type: string; data: any; receivedAt: number };
@@ -17,7 +18,17 @@ export class SseListener {
constructor(private baseUrl: string = process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') {}
async start(token: string): Promise<void> {
const url = `${this.baseUrl}/api/v1/stream?token=${encodeURIComponent(token)}`;
// Exchange the JWT for a single-use SSE ticket (the stream endpoint no longer
// accepts ?token=).
const ticketRes = await fetch(`${this.baseUrl}/api/v1/stream/ticket`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
});
if (ticketRes.status !== 200) {
throw new Error(`SSE ticket mint failed: ${ticketRes.status} ${await ticketRes.text()}`);
}
const { ticket } = await ticketRes.json();
const url = `${this.baseUrl}/api/v1/stream?ticket=${encodeURIComponent(ticket)}`;
// Use fetch with streaming since Node has no EventSource by default.
const res = await fetch(url, { signal: this.controller.signal });
if (!res.body) throw new Error('SSE response has no body');