/** * Tests for GET /api/events — windowed endpoint (Plan 02 GREEN state). * * Contracts: * 1. Missing/malformed start or end params → 400 (input validation guard) * 2. Window wider than 90 days → 400 (DoS cap) * 3. Valid window returns { occurrences: [] } each with color + isShared fields when DB is empty */ import { describe, it, expect, vi } from 'vitest' // Mock @hono/oidc-auth so tests do not need a live Authelia instance. // The mock makes oidcAuthMiddleware a no-op passthrough. 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, })) // Mock DB to avoid real DB connections in unit tests vi.mock('../../src/db/client.js', () => ({ db: { select: vi.fn().mockReturnValue({ from: vi.fn().mockReturnValue({ innerJoin: vi.fn().mockReturnValue({ innerJoin: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue([]), }), }), where: vi.fn().mockResolvedValue([]), }), }), }, })) describe('GET /api/events', () => { it('returns 400 when start param is missing', async () => { const { app } = await import('../../src/index.js') const res = await app.request('/api/events?end=2026-07-01') expect(res.status).toBe(400) }) it('returns 400 when end param is missing', async () => { const { app } = await import('../../src/index.js') const res = await app.request('/api/events?start=2026-06-01') expect(res.status).toBe(400) }) it('returns 400 when start param is malformed (not YYYY-MM-DD)', async () => { const { app } = await import('../../src/index.js') const res = await app.request('/api/events?start=not-a-date&end=2026-07-01') expect(res.status).toBe(400) }) it('returns occurrences with color and isShared fields for valid window', async () => { const { app } = await import('../../src/index.js') const res = await app.request('/api/events?start=2026-06-01&end=2026-07-01') // Route not yet evolved — currently returns raw events without windowing or color join. // This test will pass once Plan 02 implements the windowed query + expansion. expect(res.status).toBe(200) const body = await res.json() as { occurrences: Array<{ color: string; isShared: boolean }> } expect(body).toHaveProperty('occurrences') expect(Array.isArray(body.occurrences)).toBe(true) // Each occurrence must carry color + isShared (may be empty array if DB is mocked empty) for (const occ of body.occurrences) { expect(occ).toHaveProperty('color') expect(typeof occ.color).toBe('string') expect(occ).toHaveProperty('isShared') expect(typeof occ.isShared).toBe('boolean') } }) })