Surface net for the class of bug we hit in #282 — a POI test silently relied on a live Overpass server for months because its `page.route` mock was targeting a URL the browser no longer hit directly. CI stayed green on coincidence (real OSM data happened to satisfy the "any marker renders" assertion) until upstream behaviour drifted. Add a shared Playwright fixture (e2e/fixtures/test.ts) that installs a catch-all `page.route("**", ...)` before each test. Requests to localhost + known tile CDNs pass through via `route.continue()`; anything else is aborted and accumulated. At test teardown, a non-empty blocked list throws, failing the test with the offending URLs and a pointer to the fixture. All seven e2e *.test.ts files updated to import test/expect (+ types) from ./fixtures/test. Full suite passes with this fixture in place (50/50 at --workers=2; catch-all never fires, meaning no test currently relies on an unallowlisted external service). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
116 lines
5 KiB
TypeScript
116 lines
5 KiB
TypeScript
import { test, expect } from "./fixtures/test";
|
|
import postgres from "postgres";
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
// Demo-bot UX: a browser-side verification that a pre-seeded `bruno`
|
|
// profile renders the demo badge and surfaces his public content to an
|
|
// anonymous visitor. We seed directly via SQL rather than running the
|
|
// pg-boss handler, because the handler depends on BRouter and on the
|
|
// demo flag being live in the server process. The point of this test
|
|
// is the *public surface*, not the generation path (that's covered in
|
|
// app/lib/demo-bot.integration.test.ts).
|
|
|
|
const CONN = process.env.DATABASE_URL ?? "postgres://trails:trails@localhost:5432/trails";
|
|
|
|
async function seedBrunoWithSyntheticRoute(): Promise<{ username: string; routeName: string }> {
|
|
const sql = postgres(CONN, { max: 1 });
|
|
const username = `bruno-e2e-${Date.now()}`;
|
|
const routeName = `Bruno's ${Date.now()} walk`;
|
|
const userId = randomUUID();
|
|
const routeId = randomUUID();
|
|
try {
|
|
await sql`
|
|
INSERT INTO journal.users (id, email, username, display_name, bio, domain, terms_accepted_at, terms_version)
|
|
VALUES (${userId}, ${`${username}@example.com`}, ${username}, 'Bruno', 'Professional park inspector.', 'localhost', now(), '2026-04-19')
|
|
`;
|
|
await sql`
|
|
INSERT INTO journal.routes (id, owner_id, name, description, visibility, synthetic, distance, created_at, updated_at)
|
|
VALUES (${routeId}, ${userId}, ${routeName}, 'Sniffed three bushes.', 'public', true, 3200, now(), now())
|
|
`;
|
|
} finally {
|
|
await sql.end();
|
|
}
|
|
return { username, routeName };
|
|
}
|
|
|
|
async function cleanupBruno(username: string) {
|
|
const sql = postgres(CONN, { max: 1 });
|
|
try {
|
|
const [row] = await sql<{ id: string }[]>`SELECT id FROM journal.users WHERE username = ${username}`;
|
|
if (!row) return;
|
|
await sql`DELETE FROM journal.activities WHERE owner_id = ${row.id}`;
|
|
await sql`DELETE FROM journal.routes WHERE owner_id = ${row.id}`;
|
|
await sql`DELETE FROM journal.users WHERE id = ${row.id}`;
|
|
} finally {
|
|
await sql.end();
|
|
}
|
|
}
|
|
|
|
test.describe.configure({ mode: "serial" });
|
|
|
|
test.describe("Demo-bot UX", () => {
|
|
test("seeded bruno profile shows demo badge + public route to anon visitor", async ({ browser }) => {
|
|
const { username, routeName } = await seedBrunoWithSyntheticRoute();
|
|
try {
|
|
const anonCtx = await browser.newContext();
|
|
const anon = await anonCtx.newPage();
|
|
const resp = await anon.goto(`/users/${username}`);
|
|
expect(resp?.status()).toBe(200);
|
|
|
|
await expect(anon.getByRole("heading", { name: /Bruno/ })).toBeVisible();
|
|
// The demo badge is only rendered when username === 'bruno' — our
|
|
// seeded username includes a timestamp suffix, so the badge
|
|
// shouldn't appear. This is an intentional guard: only the *real*
|
|
// bruno gets the badge. The rest of the page still works.
|
|
await expect(anon.getByText("🐕 Demo account")).toHaveCount(0);
|
|
|
|
// Public synthetic route is listed on the profile.
|
|
await expect(anon.getByRole("link", { name: new RegExp(routeName) })).toBeVisible();
|
|
await anonCtx.close();
|
|
} finally {
|
|
await cleanupBruno(username);
|
|
}
|
|
});
|
|
|
|
test("profile rendered at /users/bruno shows the demo badge when username is exactly 'bruno'", async ({ browser }) => {
|
|
const sql = postgres(CONN, { max: 1 });
|
|
const userId = randomUUID();
|
|
const routeId = randomUUID();
|
|
// Use ON CONFLICT so a pre-existing prod `bruno` user doesn't block
|
|
// the test; we don't clean up a row we didn't create.
|
|
const created = await sql`
|
|
INSERT INTO journal.users (id, email, username, display_name, bio, domain, terms_accepted_at, terms_version)
|
|
VALUES (${userId}, 'bruno@localhost', 'bruno', 'Bruno', 'Professional park inspector.', 'localhost', now(), '2026-04-19')
|
|
ON CONFLICT (username) DO NOTHING
|
|
RETURNING id
|
|
`;
|
|
const weCreatedIt = created.length > 0;
|
|
const effectiveUserId = weCreatedIt ? userId : (await sql`SELECT id FROM journal.users WHERE username='bruno'`)[0]!.id as string;
|
|
|
|
await sql`
|
|
INSERT INTO journal.routes (id, owner_id, name, description, visibility, synthetic, distance, created_at, updated_at)
|
|
VALUES (${routeId}, ${effectiveUserId}, 'Grunewald patrol', 'Sniffed three bushes.', 'public', true, 3200, now(), now())
|
|
`;
|
|
await sql.end();
|
|
|
|
try {
|
|
const anonCtx = await browser.newContext();
|
|
const anon = await anonCtx.newPage();
|
|
const resp = await anon.goto(`/users/bruno`);
|
|
expect(resp?.status()).toBe(200);
|
|
await expect(anon.getByText(/Demo account|Demo-Konto/)).toBeVisible();
|
|
await expect(anon.getByRole("link", { name: /Grunewald patrol/ })).toBeVisible();
|
|
await anonCtx.close();
|
|
} finally {
|
|
const cleanup = postgres(CONN, { max: 1 });
|
|
try {
|
|
await cleanup`DELETE FROM journal.routes WHERE id = ${routeId}`;
|
|
if (weCreatedIt) {
|
|
await cleanup`DELETE FROM journal.users WHERE id = ${userId}`;
|
|
}
|
|
} finally {
|
|
await cleanup.end();
|
|
}
|
|
}
|
|
});
|
|
});
|