import { describe, it, expect, vi, beforeEach } from 'vitest'; import { buildAdminTestApp } from '../helpers/build-admin-test-app.js'; import { createMockDb, selectChainOrderedLimitOffset, selectChainWhere, updateChainReturning, insertChain, } from '../test-utils.js'; describe('Admin routes integration', () => { let app: Awaited>; let mockDb: ReturnType; beforeEach(async () => { vi.clearAllMocks(); mockDb = createMockDb(); app = await buildAdminTestApp(mockDb as never); }); describe('GET /admin/questions/pending', () => { it('returns pending questions list', async () => { const pendingQuestions = [ { id: 'q-1', stack: 'js', level: 'beginner', type: 'single_choice', questionText: 'Test question?', options: [{ key: 'a', text: 'A' }], correctAnswer: 'a', explanation: 'Exp', source: 'manual', createdAt: new Date(), }, ]; (mockDb.select as ReturnType) .mockReturnValueOnce(selectChainOrderedLimitOffset(pendingQuestions)) .mockReturnValueOnce(selectChainWhere([{ count: 1 }])); const res = await app.inject({ method: 'GET', url: '/admin/questions/pending', headers: { authorization: 'Bearer any-token' }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body); expect(body.questions).toHaveLength(1); expect(body.questions[0].questionText).toBe('Test question?'); expect(body.total).toBe(1); }); }); describe('POST /admin/questions/:questionId/approve', () => { it('returns 204 on success', async () => { (mockDb.update as ReturnType).mockReturnValueOnce( updateChainReturning([{ id: 'q-1' }]) ); (mockDb.insert as ReturnType).mockReturnValueOnce( insertChain([]) ); const res = await app.inject({ method: 'POST', url: '/admin/questions/11111111-1111-1111-1111-111111111111/approve', headers: { authorization: 'Bearer any-token' }, }); expect(res.statusCode).toBe(204); }); it('returns 404 when question not found', async () => { (mockDb.update as ReturnType).mockReturnValueOnce( updateChainReturning([]) ); const res = await app.inject({ method: 'POST', url: '/admin/questions/11111111-1111-1111-1111-111111111111/approve', headers: { authorization: 'Bearer any-token' }, }); expect(res.statusCode).toBe(404); }); }); describe('POST /admin/questions/:questionId/reject', () => { it('returns 204 on success', async () => { (mockDb.update as ReturnType).mockReturnValueOnce( updateChainReturning([{ id: 'q-1' }]) ); (mockDb.insert as ReturnType).mockReturnValueOnce( insertChain([]) ); const res = await app.inject({ method: 'POST', url: '/admin/questions/11111111-1111-1111-1111-111111111111/reject', headers: { authorization: 'Bearer any-token' }, }); expect(res.statusCode).toBe(204); }); }); describe('PATCH /admin/questions/:questionId', () => { it('returns updated question', async () => { const updatedQuestion = { id: 'q-1', stack: 'js', level: 'intermediate', type: 'single_choice', questionText: 'Updated?', options: [{ key: 'a', text: 'A' }], correctAnswer: 'a', explanation: 'Updated exp', source: 'manual', createdAt: new Date(), }; (mockDb.update as ReturnType).mockReturnValueOnce( updateChainReturning([updatedQuestion]) ); (mockDb.insert as ReturnType).mockReturnValueOnce( insertChain([]) ); const res = await app.inject({ method: 'PATCH', url: '/admin/questions/11111111-1111-1111-1111-111111111111', headers: { authorization: 'Bearer any-token' }, payload: { questionText: 'Updated?', explanation: 'Updated exp' }, }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body); expect(body.questionText).toBe('Updated?'); }); }); });