- zValidator enforces YYYY-MM-DD regex on start/end (T-02b-01) - 90-day window cap prevents DoS (T-02b-02) - innerJoin calendarEvents→calendars→users for color + isShared + ownerUserId - SQL pre-filter includes hasRrule=true rows regardless of dtstartUtc range - expandOccurrences() called per row; shared calendar uses #F25C7A rose color - events.test.ts: added @hono/oidc-auth mock; 4/4 assertions green
74 lines
2.8 KiB
TypeScript
74 lines
2.8 KiB
TypeScript
/**
|
|
* 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<void>) => 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')
|
|
}
|
|
})
|
|
})
|