/** * Setup wizard route tests — Wave-0 scaffold (Plan 12-01). * * Covers SETUP-01, SETUP-02, SETUP-03, SETUP-04 + the 423 guard (Pitfall 8) + D-10 * effective-config branch. All tests are it.todo() placeholders at this stage — * they are intentionally RED so the happy-path implementation (Plan 02) can be * built TDD-green against them. * * The 423-guard test (SETUP-04) MUST be RED before the happy path is implemented. * This matches the Wave-0 requirement in 12-RESEARCH.md §Validation Architecture. * * Architecture: * - Tests import `app` (not setupRouter directly — mirrors admin.test.ts Pitfall 9 pattern) * - Real DB integration against familysync_test (vitest globalSetup provisions it) * - credentialSync.js and broker mocks isolate CalDAV calls * * Route surfaces tested: * GET /api/setup/status * POST /api/setup/validate/vapid * POST /api/setup/validate/oidc * POST /api/setup/credential * POST /api/setup/complete * * SETUP-04 / Pitfall 8 — 423 guard: * POST /api/setup/complete twice → first 200, second 423 * POST /api/setup/* when effectively configured (member_credentials + VAPID env) → 423 */ import { describe, it } from 'vitest'; // --------------------------------------------------------------------------- // Mocks (mirrors admin.test.ts conventions — must be registered before `app` import) // --------------------------------------------------------------------------- // CalDAV credential validation — mock to avoid live Fastmail calls // vi.mock('../../src/broker/credentialSync.js', () => ({ // validateEncryptAndStoreCredential: vi.fn(), // CredentialValidationError: class extends Error {}, // })); // // CalDAV client — mock to avoid live network calls // vi.mock('../../src/broker/client.js', () => ({ // createFastmailClient: vi.fn(), // })); // // Outbox worker — avoid side-effects during tests // vi.mock('../../src/broker/outboxWorker.js', () => ({ // startOutboxWorker: vi.fn(), // initOutboxTrigger: vi.fn(), // scheduleOutboxDrain: vi.fn(), // runOutboxDrain: vi.fn(), // __resetDrainState: vi.fn(), // stopOutboxTrigger: vi.fn(), // })); // // Dev-auth bypass — simulate unauthenticated requests to pre-auth /api/setup routes // (unlike other routes, setup/* is pre-auth so no user injection needed for most tests) // vi.mock('../../src/auth/devBypass.js', () => ({ // devAuthBypass: () => async (_c: unknown, next: () => Promise) => next(), // })); // // OIDC auth middleware — skip for pre-auth surface // vi.mock('@hono/oidc-auth', () => ({ // oidcAuthMiddleware: () => async (_c: unknown, next: () => Promise) => next(), // processOAuthCallback: () => async (c: { json: (v: unknown) => unknown }) => c.json({ ok: true }), // getAuth: () => null, // })); // --------------------------------------------------------------------------- // SETUP-01: GET /api/setup/status // --------------------------------------------------------------------------- describe('GET /api/setup/status', () => { it.todo('returns { setupComplete: false } when no setup has been run (fresh state)'); it.todo('returns { setupComplete: true } after POST /api/setup/complete succeeds'); }); // --------------------------------------------------------------------------- // SETUP-02: POST /api/setup/validate/vapid // --------------------------------------------------------------------------- describe('POST /api/setup/validate/vapid', () => { it.todo('returns 200 { ok: true } for a valid VAPID key pair from generate-secrets'); it.todo( 'returns 400 { ok: false } for a truncated/invalid VAPID public key (structural validation)', ); it.todo('returns 400 for a missing privateKey field'); }); // --------------------------------------------------------------------------- // SETUP-02: POST /api/setup/validate/oidc // --------------------------------------------------------------------------- describe('POST /api/setup/validate/oidc', () => { it.todo( 'returns 400 { ok: false } when the OIDC issuer is unreachable (mocked network failure)', ); it.todo('returns 400 for a missing issuer field'); }); // --------------------------------------------------------------------------- // SETUP-01: POST /api/setup/credential — CalDAV PROPFIND validation // --------------------------------------------------------------------------- describe('POST /api/setup/credential', () => { it.todo( 'returns 400 when PROPFIND fails (simulates bad Fastmail app password or network error)', ); it.todo( 'returns 200 and stores encrypted credential when PROPFIND succeeds (mocked fetchCalendars)', ); it.todo( 'returns 400 with no echoed password when credential validation fails (Pitfall 7 / no-echo)', ); }); // --------------------------------------------------------------------------- // SETUP-04: POST /api/setup/complete — 423 guard (Pitfall 8, must be RED before plan 02) // --------------------------------------------------------------------------- describe('POST /api/setup/complete — 423 guard (SETUP-04 / Pitfall 8)', () => { it.todo('returns 200 on first call (fresh setup, wizard not yet locked)'); // This test is the load-bearing RED test — the 423 must be verified before // the happy-path POST /api/setup/complete is implemented. (SETUP-04 requirement) it.todo('returns 423 on second call — setup already complete, wizard locked (Pitfall 8)'); }); // --------------------------------------------------------------------------- // D-10 effective-config branch: any /api/setup/* → 423 when effectively configured // (member_credentials row exists AND VAPID env vars present) // --------------------------------------------------------------------------- describe('/api/setup/* — 423 when effectively configured (D-10 effective-config branch)', () => { it.todo( 'any /api/setup/* route returns 423 when member_credentials row exists AND VAPID_PRIVATE_KEY + VAPID_PUBLIC_KEY env are set', ); it.todo( 'does NOT return 423 when member_credentials row exists but VAPID env is absent (not fully configured)', ); it.todo( 'does NOT return 423 when VAPID env is set but no member_credentials row exists (not fully configured)', ); });