test: add admin routes tests

Made-with: Cursor
This commit is contained in:
Anton
2026-03-04 16:07:44 +03:00
parent 39be02e1ca
commit cacb9e0c9b
3 changed files with 207 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
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<ReturnType<typeof buildAdminTestApp>>;
let mockDb: ReturnType<typeof createMockDb>;
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<typeof vi.fn>)
.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<typeof vi.fn>).mockReturnValueOnce(
updateChainReturning([{ id: 'q-1' }])
);
(mockDb.insert as ReturnType<typeof vi.fn>).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<typeof vi.fn>).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<typeof vi.fn>).mockReturnValueOnce(
updateChainReturning([{ id: 'q-1' }])
);
(mockDb.insert as ReturnType<typeof vi.fn>).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<typeof vi.fn>).mockReturnValueOnce(
updateChainReturning([updatedQuestion])
);
(mockDb.insert as ReturnType<typeof vi.fn>).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?');
});
});
});