Files
collective-lists/apps/web/tests/fixtures/users.ts
Oier Bravo Urtasun f5c8cb68be feat(fase-7): collective-flow E2E coverage + fix 3 latent bugs
12 new Playwright tests closing the UI coverage gap in the collective
lifecycle (onboarding → invitation → admin manage). Writing them surfaced
three product-code bugs that have silently been present since Fase 1;
fixed as part of this phase.

Tests:
- tests/e2e/onboarding.test.ts       O-01..O-03 (Eva auto-redirect, happy
                                     path create, empty-name submit-disabled)
- tests/e2e/invitation.test.ts       I-01..I-05 (token generation, accept
                                     logged-out + logged-in, expired, used)
- tests/e2e/manage-collective.test.ts MC-02..MC-05 (promote, demote, remove
                                     member, generate pending invite)
- tests/fixtures/db.ts               resetEva, seedExpiredInvitation,
                                     seedUsedInvitation, restoreSeedMembership,
                                     countMembership — all via raw SQL so they
                                     don't depend on SUPABASE_SERVICE_ROLE_KEY

Bugs found & fixed:
- supabase/migrations/012_create_collective_rpc.sql: atomic
  `create_collective(name, emoji)` SECURITY DEFINER RPC. Fase 1 onboarding
  used `.insert(...).select().single()` which triggers the SELECT policy on
  the fresh row — creator isn't a member yet, so the INSERT was rejected with
  "row-level security" even though the INSERT policy passed. onboarding now
  calls the RPC which inserts both the collective AND the creator-as-admin
  membership in one transaction, bypassing RLS. F-08 in rls-isolation.test.ts
  has been silently masking this bug (only the spoof branch was asserted).

- routes/+layout.svelte: post-login redirect now reads sessionStorage
  `pendingInvitationToken` and routes back to /invitation/<token> instead of
  defaulting to /onboarding or /lists. The invitation page already stashed
  the token before kicking off Keycloak, but nothing read it back.

- routes/(app)/collective/manage/+page.svelte: loadMembers now subscribes
  to currentCollective so it re-runs when the store resolves after cold
  navigation. onMount alone races with the auth listener's first emit and
  the member list stayed empty on direct URL hits.

- routes/invitation/[token]/+page.svelte: awaits authLoading before deciding
  whether to redirect to login. Previously raced with the auth listener and
  triggered a redundant Keycloak round-trip for freshly-authenticated users.

Stable selectors added (Playwright):
- Onboarding: onboarding-create-tab, onboarding-join-tab,
  collective-name-input, collective-submit
- Manage: member-row-<uid>, role-select-<uid>, remove-member-<uid>,
  generate-invite, invite-link
- Invitation: invitation-accept, invitation-error (+ data-error-key attr)

Scope dropped from the plan — UI does not exist, would be feature work:
- O-04: "+ new collective" affordance in the sidebar.
- MC-01: rename-collective input in /collective/manage.
Both flagged as follow-ups in plan/fase-7-collective-flow-tests.md.

Test totals: 34 pgTAP + 140 Vitest integration + 15 Vitest unit + 58
Playwright + 1 gated rate-limit. 3 skipped (2 Realtime presence, 1 gated).
2026-04-14 22:33:33 +02:00

51 lines
1.4 KiB
TypeScript

// Seed user constants for E2E tests.
// Matches supabase/seed.sql and keycloak/realm-export.json.
export const USERS = {
ana: {
id: '11111111-1111-1111-1111-111111111111',
email: 'ana@dev.local',
password: 'test1234',
displayName: 'Ana García',
role: 'admin' as const,
storageState: 'tests/.auth/ana.json'
},
borja: {
id: '22222222-2222-2222-2222-222222222222',
email: 'borja@dev.local',
password: 'test1234',
displayName: 'Borja López',
role: 'member' as const,
storageState: 'tests/.auth/borja.json'
},
carmen: {
id: '33333333-3333-3333-3333-333333333333',
email: 'carmen@dev.local',
password: 'test1234',
displayName: 'Carmen Martínez',
role: 'member' as const,
storageState: 'tests/.auth/carmen.json'
},
david: {
id: '44444444-4444-4444-4444-444444444444',
email: 'david@dev.local',
password: 'test1234',
displayName: 'David Fernández',
role: 'guest' as const,
storageState: 'tests/.auth/david.json'
},
eva: {
id: '55555555-5555-5555-5555-555555555555',
email: 'eva@dev.local',
password: 'test1234',
displayName: 'Eva Ruiz',
role: 'none' as const, // no collective
storageState: 'tests/.auth/eva.json'
}
} as const;
export type UserKey = keyof typeof USERS;
export const COLLECTIVE_NAME = 'Casa García-López';
export const KEYCLOAK_URL = process.env.PUBLIC_KEYCLOAK_URL ?? 'http://localhost:8080';
export const KEYCLOAK_REALM = process.env.PUBLIC_KEYCLOAK_REALM ?? 'colectivo';