Milestone v1.0: FamilySync MVP #1
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* GET /api/me — regression tests for dev-auth bypass path.
|
||||
*
|
||||
* Covers:
|
||||
* 1. DEV_AUTH_BYPASS=true (non-production): GET /api/me returns 200 with the injected
|
||||
* DEV_USER identity (id=1, displayName='Dev User', color='#4A90D9').
|
||||
* The OIDC guard must NOT be enforced — no Authelia env vars needed.
|
||||
* 2. Without DEV_AUTH_BYPASS: the OIDC middleware is still wired on /api/*.
|
||||
* Verified structurally by asserting oidcAuthMiddleware is called during app init
|
||||
* (the mock intercepts it and acts as a passthrough, confirming the mount path).
|
||||
*
|
||||
* Architecture note:
|
||||
* devAuthBypass() and devBypassActive in index.ts both evaluate env vars at module
|
||||
* load time. Tests must set process.env BEFORE importing the app module.
|
||||
* vitest.resetModules() ensures each test gets a fresh module registry.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shared mock: DB — avoids real DB connections across all tests in this file.
|
||||
// This mock is hoisted by Vitest and applies to every dynamic import below.
|
||||
// ---------------------------------------------------------------------------
|
||||
vi.mock('../../src/db/client.js', () => ({
|
||||
db: {
|
||||
execute: vi.fn().mockResolvedValue([[{ '1': 1 }]]),
|
||||
select: vi.fn().mockReturnValue({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue([]),
|
||||
innerJoin: vi.fn().mockReturnValue({
|
||||
innerJoin: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockResolvedValue([]),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Track whether oidcAuthMiddleware was registered on the app.
|
||||
// The spy is set up fresh per test via beforeEach/afterEach.
|
||||
// ---------------------------------------------------------------------------
|
||||
const oidcMiddlewareSpy = vi.fn(
|
||||
() => async (_c: unknown, next: () => Promise<void>) => next(),
|
||||
)
|
||||
|
||||
vi.mock('@hono/oidc-auth', () => ({
|
||||
oidcAuthMiddleware: () => oidcMiddlewareSpy(),
|
||||
processOAuthCallback: () => async (c: { json: (v: unknown) => unknown }) =>
|
||||
c.json({ ok: true }),
|
||||
getAuth: vi.fn().mockResolvedValue(null),
|
||||
}))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Env snapshot — restored after each test to avoid cross-test pollution.
|
||||
// ---------------------------------------------------------------------------
|
||||
const originalNodeEnv = process.env.NODE_ENV
|
||||
const originalBypassFlag = process.env.DEV_AUTH_BYPASS
|
||||
|
||||
afterEach(() => {
|
||||
process.env.NODE_ENV = originalNodeEnv
|
||||
if (originalBypassFlag === undefined) {
|
||||
delete process.env.DEV_AUTH_BYPASS
|
||||
} else {
|
||||
process.env.DEV_AUTH_BYPASS = originalBypassFlag
|
||||
}
|
||||
vi.resetModules()
|
||||
oidcMiddlewareSpy.mockClear()
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('GET /api/me — dev-auth bypass (DEV_AUTH_BYPASS=true)', () => {
|
||||
beforeEach(() => {
|
||||
process.env.NODE_ENV = 'test'
|
||||
process.env.DEV_AUTH_BYPASS = 'true'
|
||||
})
|
||||
|
||||
it('returns 200 with the injected dev user identity', async () => {
|
||||
// Import AFTER setting env — index.ts reads env at module load time.
|
||||
const { app } = await import('../../src/index.js')
|
||||
const { DEV_USER } = await import('../../src/auth/devBypass.js')
|
||||
|
||||
const res = await app.request('/api/me')
|
||||
expect(res.status).toBe(200)
|
||||
|
||||
const body = await res.json() as { user: { id: number; displayName: string; color: string } }
|
||||
expect(body).toHaveProperty('user')
|
||||
expect(body.user.id).toBe(DEV_USER.id)
|
||||
expect(body.user.displayName).toBe(DEV_USER.displayName)
|
||||
expect(body.user.color).toBe(DEV_USER.color)
|
||||
})
|
||||
|
||||
it('returns id=1 and color=#4A90D9 (first palette slot)', async () => {
|
||||
const { app } = await import('../../src/index.js')
|
||||
|
||||
const res = await app.request('/api/me')
|
||||
expect(res.status).toBe(200)
|
||||
|
||||
const body = await res.json() as { user: { id: number; color: string } }
|
||||
expect(body.user.id).toBe(1)
|
||||
expect(body.user.color).toBe('#4A90D9')
|
||||
})
|
||||
|
||||
it('does not invoke oidcAuthMiddleware on /api/* when bypass is active', async () => {
|
||||
const { app } = await import('../../src/index.js')
|
||||
|
||||
// Hit any /api/* route to trigger the middleware stack.
|
||||
await app.request('/api/me')
|
||||
|
||||
// oidcAuthMiddleware() factory must NOT have been called — index.ts skips it.
|
||||
expect(oidcMiddlewareSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /api/me — OIDC path (no DEV_AUTH_BYPASS)', () => {
|
||||
beforeEach(() => {
|
||||
process.env.NODE_ENV = 'test'
|
||||
delete process.env.DEV_AUTH_BYPASS
|
||||
})
|
||||
|
||||
it('wires oidcAuthMiddleware on /api/* when bypass is not active', async () => {
|
||||
// Import app — devBypassActive will be false, so oidcAuthMiddleware() is called
|
||||
// during app construction (index.ts registers it via app.use('/api/*', ...)).
|
||||
await import('../../src/index.js')
|
||||
|
||||
// The spy wraps the oidcAuthMiddleware() factory call in index.ts.
|
||||
// It must have been called exactly once (one app.use registration).
|
||||
expect(oidcMiddlewareSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('returns 401 when no OIDC session is present (getAuth returns null)', async () => {
|
||||
const { app } = await import('../../src/index.js')
|
||||
|
||||
// oidcAuthMiddleware is mocked as a passthrough; getAuth is mocked to return null.
|
||||
// me.ts falls through to the getAuth path and returns 401.
|
||||
const res = await app.request('/api/me')
|
||||
expect(res.status).toBe(401)
|
||||
const body = await res.json() as { error: string }
|
||||
expect(body.error).toBe('Unauthorized')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user