test: add tests and questions service tests
Made-with: Cursor
This commit is contained in:
117
tests/services/question.service.test.ts
Normal file
117
tests/services/question.service.test.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { QuestionService } from '../../src/services/questions/question.service.js';
|
||||
import {
|
||||
createMockDb,
|
||||
selectChainOrdered,
|
||||
selectChainSimple,
|
||||
insertChain,
|
||||
updateChain,
|
||||
} from '../test-utils.js';
|
||||
|
||||
const mockLlmQuestions = [
|
||||
{
|
||||
questionBankId: 'qb-new',
|
||||
type: 'single_choice' as const,
|
||||
questionText: 'New question?',
|
||||
options: [{ key: 'a', text: 'A' }, { key: 'b', text: 'B' }],
|
||||
correctAnswer: 'a',
|
||||
explanation: 'Explanation',
|
||||
stack: 'js' as const,
|
||||
level: 'beginner' as const,
|
||||
},
|
||||
];
|
||||
|
||||
describe('QuestionService', () => {
|
||||
let mockDb: ReturnType<typeof createMockDb>;
|
||||
let mockLlmService: { generateQuestions: ReturnType<typeof vi.fn> };
|
||||
let questionService: QuestionService;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockDb = createMockDb();
|
||||
mockLlmService = {
|
||||
generateQuestions: vi.fn().mockResolvedValue({
|
||||
questions: mockLlmQuestions,
|
||||
meta: {
|
||||
llmModel: 'test-model',
|
||||
promptHash: 'hash',
|
||||
generationTimeMs: 100,
|
||||
rawResponse: {},
|
||||
},
|
||||
}),
|
||||
};
|
||||
questionService = new QuestionService(mockDb as never, mockLlmService as never);
|
||||
});
|
||||
|
||||
describe('getQuestionsForTest', () => {
|
||||
it('returns questions from bank when enough approved exist', async () => {
|
||||
const approvedRows = [
|
||||
{
|
||||
id: 'qb-1',
|
||||
type: 'single_choice',
|
||||
questionText: 'Q1?',
|
||||
options: [{ key: 'a', text: 'A' }],
|
||||
correctAnswer: 'a',
|
||||
explanation: 'e',
|
||||
},
|
||||
];
|
||||
(mockDb.select as ReturnType<typeof vi.fn>)
|
||||
.mockReturnValueOnce(selectChainOrdered(approvedRows))
|
||||
.mockReturnValueOnce(selectChainSimple([]));
|
||||
(mockDb.insert as ReturnType<typeof vi.fn>).mockReturnValueOnce(insertChain([]));
|
||||
(mockDb.update as ReturnType<typeof vi.fn>).mockReturnValueOnce(updateChain([]));
|
||||
|
||||
const result = await questionService.getQuestionsForTest(
|
||||
'user-1',
|
||||
'js',
|
||||
'beginner',
|
||||
1
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].questionBankId).toBe('qb-1');
|
||||
expect(mockLlmService.generateQuestions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls LLM when not enough questions in bank', async () => {
|
||||
const approvedRows: unknown[] = [];
|
||||
const seenIds: unknown[] = [];
|
||||
(mockDb.select as ReturnType<typeof vi.fn>)
|
||||
.mockReturnValueOnce(selectChainOrdered(approvedRows))
|
||||
.mockReturnValueOnce(selectChainSimple(seenIds));
|
||||
(mockDb.insert as ReturnType<typeof vi.fn>)
|
||||
.mockReturnValueOnce(insertChain([{ id: 'qb-new', ...mockLlmQuestions[0] }]))
|
||||
.mockReturnValueOnce(insertChain([]))
|
||||
.mockReturnValueOnce(insertChain([]));
|
||||
(mockDb.update as ReturnType<typeof vi.fn>).mockReturnValueOnce(updateChain([]));
|
||||
|
||||
const result = await questionService.getQuestionsForTest(
|
||||
'user-1',
|
||||
'js',
|
||||
'beginner',
|
||||
1
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(mockLlmService.generateQuestions).toHaveBeenCalledWith({
|
||||
stack: 'js',
|
||||
level: 'beginner',
|
||||
count: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when not enough questions available', async () => {
|
||||
(mockDb.select as ReturnType<typeof vi.fn>)
|
||||
.mockReturnValueOnce(selectChainOrdered([]))
|
||||
.mockReturnValueOnce(selectChainSimple([]));
|
||||
mockLlmService.generateQuestions.mockResolvedValueOnce({
|
||||
questions: [],
|
||||
meta: { llmModel: 'x', promptHash: 'y', generationTimeMs: 0, rawResponse: {} },
|
||||
});
|
||||
|
||||
await expect(
|
||||
questionService.getQuestionsForTest('user-1', 'js', 'beginner', 5)
|
||||
).rejects.toMatchObject({ code: 'QUESTIONS_UNAVAILABLE' });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user