import { describe, it, expect, vi, beforeEach } from 'vitest'; import { isHttpError } from '@sveltejs/kit'; import { _clearRateLimits } from '$lib/server/v2-rate-limit'; vi.mock('$lib/db', () => ({ prisma: { licenseKey: { findUnique: vi.fn() } } })); vi.mock('$lib/server/schedule', () => ({ getScheduleForLicense: vi.fn(), scheduleBlocksToInput: vi.fn() })); import { prisma } from '$lib/db'; import { getScheduleForLicense, scheduleBlocksToInput } from '$lib/server/schedule'; import { GET } from './+server'; type GETEvent = Parameters[0]; const mockFindUnique = prisma.licenseKey.findUnique as ReturnType; const mockGetSchedule = getScheduleForLicense as ReturnType; const mockToInput = scheduleBlocksToInput as ReturnType; const KEY = 'ABCD-EFGH-IJKL-MNOP'; function event(ifNoneMatch?: string): GETEvent { const headers = new Headers(); if (ifNoneMatch) headers.set('if-none-match', ifNoneMatch); return { request: new Request(`http://localhost/api/v2/licenses/${KEY}/schedule`, { headers }), params: { licenseKey: KEY } } as GETEvent; } beforeEach(() => { vi.clearAllMocks(); _clearRateLimits(); }); describe('GET /api/v2/licenses/[licenseKey]/schedule', () => { it('returns 404 for an unknown license', async () => { mockFindUnique.mockResolvedValue(null); await expect(GET(event())).rejects.toSatisfy( (e: unknown) => isHttpError(e) && e.status === 404 && e.body.message === 'unknown_license' ); }); it('returns type null with empty lists when there is no schedule', async () => { mockFindUnique.mockResolvedValue({ id: 'lk1' }); mockGetSchedule.mockResolvedValue(null); const res = await GET(event()); expect(res.status).toBe(200); const body = (await res.json()) as Record; expect(body.type).toBeNull(); expect(body.daily).toEqual([]); expect(body.blocks).toEqual([]); expect(body.version).toBeTruthy(); expect(res.headers.get('etag')).toBe(`"${body.version}"`); }); it('returns the daily payload with an ETag', async () => { mockFindUnique.mockResolvedValue({ id: 'lk1' }); mockGetSchedule.mockResolvedValue({ type: 'daily' }); mockToInput.mockReturnValue([ { id: 'b1', day: null, startMinutes: 360, endMinutes: 420, dynamicAudio: true, proclamationIds: ['p1'] } ]); const res = await GET(event()); expect(res.status).toBe(200); const body = (await res.json()) as { type: string; daily: unknown[]; blocks: unknown[] }; expect(body.type).toBe('daily'); expect(body.daily).toHaveLength(1); expect(body.blocks).toHaveLength(1); }); it('returns 304 when If-None-Match matches the ETag', async () => { mockFindUnique.mockResolvedValue({ id: 'lk1' }); mockGetSchedule.mockResolvedValue(null); const first = await GET(event()); const etag = first.headers.get('etag') as string; const second = await GET(event(etag)); expect(second.status).toBe(304); expect(second.headers.get('etag')).toBe(etag); }); it('returns 200 when If-None-Match does not match', async () => { mockFindUnique.mockResolvedValue({ id: 'lk1' }); mockGetSchedule.mockResolvedValue(null); const res = await GET(event('"stale"')); expect(res.status).toBe(200); }); });