Fix e2e fallout from default-private profile_visibility

Three places assumed the old default-public model:

1. e2e/public-content.test.ts: "profile 404 when user has no public
   content" → updated to assert the new locked-account stub renders
   200 with "This profile is private" and the private route doesn't
   appear. The two follow-on tests (public-route profile, unlisted
   route) now flip the owner to public via a setProfileVisibilityPublic
   helper before the anon-visit assertions, since new users default to
   private.

2. e2e/demo-bot.test.ts: bruno is seeded with profile_visibility =
   'public' on insert (timestamped seed) and the existing-bruno path
   gets a follow-up UPDATE so the test's anon-visitor assertion
   (profile renders, public route is listed) holds regardless of which
   default he was first created under.

3. apps/journal/app/lib/demo-bot.server.ts ensureDemoUser: also pins
   profile_visibility = 'public' on insert. The demo persona is
   discoverable by design — that's the whole point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-25 23:46:56 +02:00
parent 5da7ffa037
commit ff09775091
3 changed files with 41 additions and 7 deletions

View file

@ -279,6 +279,11 @@ export async function ensureDemoUser(
displayName: persona.displayName,
bio: persona.bio,
domain,
// The demo persona is meant to be discoverable to anyone — its
// entire purpose is to populate empty instances with public
// content. Force `public` even though new accounts default to
// `private` (locked-account model).
profileVisibility: "public",
termsAcceptedAt: new Date(),
termsVersion: TERMS_VERSION,
})

View file

@ -19,9 +19,12 @@ async function seedBrunoWithSyntheticRoute(): Promise<{ username: string; routeN
const userId = randomUUID();
const routeId = randomUUID();
try {
// Demo bot needs profile_visibility='public' so anon visitors see
// his profile in full (the locked-account default would render a
// stub instead).
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')
INSERT INTO journal.users (id, email, username, display_name, bio, domain, profile_visibility, terms_accepted_at, terms_version)
VALUES (${userId}, ${`${username}@example.com`}, ${username}, 'Bruno', 'Professional park inspector.', 'localhost', 'public', now(), '2026-04-19')
`;
await sql`
INSERT INTO journal.routes (id, owner_id, name, description, visibility, synthetic, distance, created_at, updated_at)
@ -79,13 +82,18 @@ test.describe("Demo-bot UX", () => {
// 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')
INSERT INTO journal.users (id, email, username, display_name, bio, domain, profile_visibility, terms_accepted_at, terms_version)
VALUES (${userId}, 'bruno@localhost', 'bruno', 'Bruno', 'Professional park inspector.', 'localhost', 'public', 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;
// Make sure bruno is public regardless of who created him — the
// demo profile must render in full for anon visitors. If he was
// pre-existing with `private` (e.g. on a fresh DB after the
// default flipped), force him public for this test's purposes.
await sql`UPDATE journal.users SET profile_visibility = 'public' WHERE id = ${effectiveUserId}`;
await sql`
INSERT INTO journal.routes (id, owner_id, name, description, visibility, synthetic, distance, created_at, updated_at)

View file

@ -47,6 +47,16 @@ async function setRouteVisibility(
await page.waitForURL(new RegExp(`/routes/${id}$`), { timeout: 10000 });
}
// New users default to profile_visibility='private' (locked-account
// model). Tests that need an anon visitor to see the profile in full
// have to flip the owner to public first.
async function setProfileVisibilityPublic(page: Page) {
await page.goto("/settings");
await page.getByLabel("Public").check();
await page.getByRole("button", { name: /^Save$/ }).first().click();
await page.waitForLoadState("networkidle");
}
// Registration + WebAuthn virtual authenticator can race under parallel
// Playwright workers on a shared local Postgres; run this file serially.
test.describe.configure({ mode: "serial" });
@ -111,19 +121,28 @@ test.describe("Public content visibility", () => {
await expect(page.getByRole("heading", { name: "My only route" })).toBeVisible();
});
test("profile 404 when user has no public content", async ({ page, browser }) => {
test("profile renders private stub when user has no public content (locked model)", async ({ page, browser }) => {
const cdp = await page.context().newCDPSession(page);
await setupVirtualAuthenticator(cdp);
const email = `pcv-empty-${Date.now()}@example.com`;
const username = `pcvempty${Date.now()}`;
await registerUser(page, email, username);
await createRoute(page, "Private thoughts"); // left private
// New users default to profile_visibility='private', so an anonymous
// visitor sees the locked-account stub (200, not 404). Their private
// route never appears.
const id = await createRoute(page, "Private thoughts"); // left private
const anonCtx = await browser.newContext();
const anon = await anonCtx.newPage();
const resp = await anon.goto(`/users/${username}`);
expect(resp?.status()).toBe(404);
expect(resp?.status()).toBe(200);
await expect(anon.getByText(/This profile is private/i)).toBeVisible();
await expect(anon.getByText("Private thoughts")).not.toBeVisible();
// Direct route URL still 404s for visitors because the route itself
// is private-visibility.
const direct = await anon.goto(`/routes/${id}`);
expect(direct?.status()).toBe(404);
await anonCtx.close();
});
@ -134,6 +153,7 @@ test.describe("Public content visibility", () => {
const email = `pcv-prof-${Date.now()}@example.com`;
const username = `pcvprof${Date.now()}`;
await registerUser(page, email, username);
await setProfileVisibilityPublic(page);
const id = await createRoute(page, "Bikepacking loop");
await setRouteVisibility(page, id, "public");
@ -154,6 +174,7 @@ test.describe("Public content visibility", () => {
const email = `pcv-unl-${Date.now()}@example.com`;
const username = `pcvunl${Date.now()}`;
await registerUser(page, email, username);
await setProfileVisibilityPublic(page);
// Two routes: one unlisted, one public — profile shows only the public one.
const publicId = await createRoute(page, "Public one");