P1-3: grader/interviewer node unit tests (24 passing)

grader.node.spec.ts — 13 tests: LLM mock validation, breakout logic
(shorts/IDontKnow), error handling, scoring/indexing, zh/ja language support

interviewer.node.spec.ts — 11 tests: empty questions, index bounds,
standard presentation, follow-up mode, zh/ja/en localization
This commit is contained in:
Developer
2026-05-19 09:30:19 +08:00
parent b139ae18b7
commit 33e48f6d4e
3 changed files with 229 additions and 1 deletions
@@ -0,0 +1,104 @@
import { interviewerNode } from './interviewer.node';
import { AIMessage } from '@langchain/core/messages';
function baseState(overrides: any = {}) {
return {
questions: [{ id: 'q1', questionText: 'What is JS?', keyPoints: ['point1'], dimension: 'llm' }],
currentQuestionIndex: 0,
shouldFollowUp: false,
language: 'en',
...overrides,
} as any;
}
describe('interviewerNode', () => {
describe('empty questions handling', () => {
it('should return apology message when questions array is empty', async () => {
const state = baseState({ questions: [] });
const result = await interviewerNode(state);
expect(result.messages).toBeDefined();
expect((result.messages as any)[0].content).toContain("sorry");
});
it('should return apology message when questions is undefined', async () => {
const state = baseState({ questions: undefined });
const result = await interviewerNode(state);
expect(result.messages).toBeDefined();
expect((result.messages as any)[0].content).toContain("sorry");
});
it('should return Chinese apology when language is zh', async () => {
const state = baseState({ questions: [], language: 'zh' });
const result = await interviewerNode(state);
expect((result.messages as any)[0].content).toContain('抱歉');
});
it('should return Japanese apology when language is ja', async () => {
const state = baseState({ questions: [], language: 'ja' });
const result = await interviewerNode(state);
expect((result.messages as any)[0].content).toContain('申し訳');
});
});
describe('question index range checks', () => {
it('should return shouldFollowUp: false when currentQuestionIndex >= questions.length', async () => {
const state = baseState({ currentQuestionIndex: 5, questions: [{ id: 'q1', questionText: 'Q', keyPoints: ['k'], dimension: 'llm' }] });
const result = await interviewerNode(state);
expect(result.shouldFollowUp).toBe(false);
});
});
describe('standard question presentation', () => {
it('should present the current question', async () => {
const result = await interviewerNode(baseState());
expect(result.messages).toBeDefined();
const msg = (result.messages as any)[0].content as string;
expect(msg).toContain('Question 1');
expect(msg).toContain('What is JS?');
});
it('should include answer instruction', async () => {
const result = await interviewerNode(baseState());
const msg = (result.messages as any)[0].content as string;
expect(msg).toContain('answer');
});
it('should use Chinese labels when language is zh', async () => {
const state = baseState({ language: 'zh' });
const result = await interviewerNode(state);
const msg = (result.messages as any)[0].content as string;
expect(msg).toContain('问题');
expect(msg).toContain('回答');
});
it('should use Japanese labels when language is ja', async () => {
const state = baseState({ language: 'ja' });
const result = await interviewerNode(state);
const msg = (result.messages as any)[0].content as string;
expect(msg).toContain('質問');
expect(msg).toContain('回答');
});
});
describe('follow-up mode', () => {
it('should include feedback in follow-up prompt', async () => {
const state = baseState({
shouldFollowUp: true,
feedbackHistory: [new AIMessage('Feedback: You need more details')],
});
const result = await interviewerNode(state);
const msg = (result.messages as any)[0].content as string;
expect(msg).toContain('Follow-up');
expect(msg).toContain('You need more details');
});
it('should reset shouldFollowUp to false after processing', async () => {
const state = baseState({
shouldFollowUp: true,
feedbackHistory: [new AIMessage('Feedback: More info needed')],
});
const result = await interviewerNode(state);
expect(result.shouldFollowUp).toBe(false);
});
});
});