Files
familysync/apps/api/tests/routes/authMode.test.ts
T
Lucas BergerandClaude Opus 4.8 b6490feff4
CI / changes (pull_request) Successful in 9s
CI / api (pull_request) Successful in 3m2s
CI / fast-checks (pull_request) Successful in 4m20s
CI / security (pull_request) Successful in 1m14s
CI / harness (pull_request) Successful in 6m56s
CI / gate (pull_request) Successful in 2s
fix(19): satisfy CI fast-checks + secret scan
Lint (eslint --max-warnings 0):
- index.ts: disable no-unsafe-argument on the type-only Context mismatch when
  delegating to the OIDC handler inside the local-session skip wrapper
- localAuth.ts: handleLogout is sync (no await) — drop async (require-await)
- devBypass.ts: disable detect-possible-timing-attacks on the public well-known
  dev-placeholder string compare (not a secret comparison)
- remove dead code / unused bindings flagged by no-unused-vars: makeTestApp
  (localSession.test), makeUnauthContext + BrowserContext import (login.spec),
  unused memberId (admin.test), unused txSelectCount counter (me.test)
- localAuthMiddleware.test / me.test: fix unused + reflow-detached
  eslint-disable directives

Format: prettier --write across the 20 Phase-19 files that were never formatted.

Secret scan (gitleaks): allowlist two false positives — the synthetic >=32-char
TEST_SECRET in localSession.test.ts, and .planning/ design prose (a generic-api-key
regex hit on "credential atomically, 409-equivalent"). Neither is a real secret.

Verified locally: format:check, lint, typecheck, md:lint, gitleaks (no leaks),
PWA 266/266, API 452/452.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 23:05:15 -04:00

124 lines
4.4 KiB
TypeScript

/**
* GET /api/auth/mode — unit tests (Plan 19-03, TDD RED → GREEN).
*
* Covers:
* Test 5: returns { localEnabled:true, oidcEnabled:false } when no oidc_issuer in env or app_config
* Test 6: returns { oidcEnabled:true } when OIDC_ISSUER env var is set
* Test 6b: returns { oidcEnabled:true } when oidc_issuer is in app_config (no env var)
*
* Pre-auth surface: reachable without OIDC session (same as /api/setup/status).
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
// ---------------------------------------------------------------------------
// DB mock — controls app_config rows returned for oidc_issuer
// ---------------------------------------------------------------------------
let mockAppConfigOidcIssuer: string | null = null;
vi.mock('../../src/db/client.js', () => ({
db: {
execute: vi.fn().mockResolvedValue([[{ '1': 1 }]]),
select: vi.fn().mockImplementation(() => ({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockImplementation(() => {
if (mockAppConfigOidcIssuer) {
return Promise.resolve([{ value: mockAppConfigOidcIssuer }]);
}
return Promise.resolve([]);
}),
}),
}),
})),
insert: vi.fn().mockReturnValue({
values: vi.fn().mockReturnValue({
onDuplicateKeyUpdate: vi.fn().mockResolvedValue(undefined),
}),
}),
},
}));
vi.mock('@hono/oidc-auth', () => ({
oidcAuthMiddleware: () => async (_c: unknown, next: () => Promise<void>) => next(),
processOAuthCallback: () => async (c: { json: (v: unknown) => unknown }) => c.json({ ok: true }),
getAuth: vi.fn().mockResolvedValue(null),
}));
vi.mock('../../src/auth/devBypass.js', () => ({
devAuthBypass: () => async (_c: unknown, next: () => Promise<void>) => next(),
// Phase 19 Option C: devSessionCookieMiddleware is a no-op in tests
devSessionCookieMiddleware: () => async (_c: unknown, next: () => Promise<void>) => next(),
}));
vi.mock('../../src/auth/localAuthMiddleware.js', () => ({
localAuthMiddleware: () => async (_c: unknown, next: () => Promise<void>) => next(),
}));
// ---------------------------------------------------------------------------
// Env snapshot
// ---------------------------------------------------------------------------
const originalOidcIssuer = process.env.OIDC_ISSUER;
beforeEach(() => {
mockAppConfigOidcIssuer = null;
delete process.env.OIDC_ISSUER;
vi.resetModules();
});
afterEach(() => {
if (originalOidcIssuer === undefined) {
delete process.env.OIDC_ISSUER;
} else {
process.env.OIDC_ISSUER = originalOidcIssuer;
}
vi.resetModules();
});
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe('GET /api/auth/mode', () => {
it('Test 5: returns { localEnabled:true, oidcEnabled:false } when no oidc_issuer in env or app_config', async () => {
delete process.env.OIDC_ISSUER;
mockAppConfigOidcIssuer = null;
const { app } = await import('../../src/index.js');
const res = await app.request('/api/auth/mode');
expect(res.status).toBe(200);
const body = (await res.json()) as { localEnabled: boolean; oidcEnabled: boolean };
expect(body.localEnabled).toBe(true);
expect(body.oidcEnabled).toBe(false);
});
it('Test 6: returns { oidcEnabled:true } when OIDC_ISSUER env var is set', async () => {
process.env.OIDC_ISSUER = 'https://auth.example.com';
mockAppConfigOidcIssuer = null;
const { app } = await import('../../src/index.js');
const res = await app.request('/api/auth/mode');
expect(res.status).toBe(200);
const body = (await res.json()) as { localEnabled: boolean; oidcEnabled: boolean };
expect(body.localEnabled).toBe(true);
expect(body.oidcEnabled).toBe(true);
});
it('Test 6b: returns { oidcEnabled:true } when oidc_issuer is in app_config (no env var)', async () => {
delete process.env.OIDC_ISSUER;
mockAppConfigOidcIssuer = 'https://auth-from-config.example.com';
const { app } = await import('../../src/index.js');
const res = await app.request('/api/auth/mode');
expect(res.status).toBe(200);
const body = (await res.json()) as { localEnabled: boolean; oidcEnabled: boolean };
expect(body.localEnabled).toBe(true);
expect(body.oidcEnabled).toBe(true);
});
});