import { describe, it, expect, vi, beforeEach } from 'vitest'; import { NextRequest } from 'next/server'; vi.mock('@/lib/db/queries', () => ({ getMasteryMap: vi.fn(), })); import { GET } from '../route'; import { getMasteryMap } from '@/lib/db/queries'; const mockGet = vi.mocked(getMasteryMap); const NOW = new Date('2026-06-21T10:00:00Z'); const LATER = new Date('2026-06-28T10:00:00Z'); const ENTRIES = [ { conceptId: 'c1', conceptName: 'Closures', blueprintId: 'bp1', blueprintTitle: 'JavaScript', blueprintIntentKey: 'javascript', score: 0.8, lastSeen: NOW, nextReview: LATER, }, { conceptId: 'c2', conceptName: 'Promises', blueprintId: 'bp1', blueprintTitle: 'JavaScript', blueprintIntentKey: 'javascript', score: 0.45, lastSeen: NOW, nextReview: NOW, }, ]; function makeRequest(userId?: string) { const url = userId ? `http://localhost:3000/api/mastery?userId=${userId}` : 'http://localhost:3000/api/mastery'; return new NextRequest(url); } describe('GET /api/mastery', () => { beforeEach(() => vi.clearAllMocks()); it('returns 400 when userId is missing', async () => { const res = await GET(makeRequest()); expect(res.status).toBe(400); }); it('returns 200 with empty groups when no mastery', async () => { mockGet.mockResolvedValue([]); const res = await GET(makeRequest('u1')); expect(res.status).toBe(200); const body = await res.json(); expect(body.groups).toHaveLength(0); }); it('groups entries by blueprintTitle', async () => { mockGet.mockResolvedValue(ENTRIES); const res = await GET(makeRequest('u1')); expect(res.status).toBe(200); const body = await res.json(); expect(body.groups).toHaveLength(1); expect(body.groups[0].blueprintTitle).toBe('JavaScript'); expect(body.groups[0].concepts).toHaveLength(2); }); it('includes score in each concept entry', async () => { mockGet.mockResolvedValue(ENTRIES); const res = await GET(makeRequest('u1')); const body = await res.json(); const concepts = body.groups[0].concepts; expect(concepts[0].score).toBe(0.8); expect(concepts[1].score).toBe(0.45); }); });