fix(quiz): skip legacy questions with missing type instead of throwing
Legacy Runtime test data may contain questions without an explicit type field. For missing type -> skip with warning (consistent with missing stem/answer handling). For non-empty illegal type -> throw BadRequestException (fail-closed). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f1eb99b6fa
commit
5570c30e0e
@ -581,7 +581,7 @@ describe('RuntimeInternalService', () => {
|
||||
});
|
||||
|
||||
it('persists quiz_generation output (creates quiz + questions)', async () => {
|
||||
const qDto = { ...dto, validatedOutput: { questions: [{ stem: 'Q1?', answer: 'A1' }] } };
|
||||
const qDto = { ...dto, validatedOutput: { questions: [{ type: 'choice', stem: 'Q1?', answer: 'A1' }] } };
|
||||
const qJob = { ...job, jobType: 'quiz_generation' as const, targetType: 'knowledge_base' as const, targetId: 'kb1' };
|
||||
mockAiRuntimeJob.findUnique.mockResolvedValue(qJob);
|
||||
mockAiRuntimeResult.findFirst.mockResolvedValue(null);
|
||||
@ -902,7 +902,7 @@ describe('RuntimeInternalService', () => {
|
||||
|
||||
await service.submitResult('j1', {
|
||||
runtimeInstanceId: 'rt-1', schemaVersion: 'ov1', status: 'succeeded',
|
||||
validatedOutput: { questions: [{ stem: 'Valid', answer: 'A' }, { stem: '', answer: '' }] },
|
||||
validatedOutput: { questions: [{ type: 'choice', stem: 'Valid', answer: 'A' }, { stem: '', answer: '' }] },
|
||||
attemptNo: 1, outputHash: 'h1',
|
||||
});
|
||||
|
||||
@ -914,7 +914,7 @@ describe('RuntimeInternalService', () => {
|
||||
|
||||
await service.submitResult('j1', {
|
||||
runtimeInstanceId: 'rt-1', schemaVersion: 'ov1', status: 'succeeded',
|
||||
validatedOutput: { questions: [{ stem: '', answer: '' }] },
|
||||
validatedOutput: { questions: [{ type: 'choice', stem: '', answer: '' }] },
|
||||
attemptNo: 1, outputHash: 'h2',
|
||||
});
|
||||
|
||||
@ -932,8 +932,8 @@ describe('RuntimeInternalService', () => {
|
||||
runtimeInstanceId: 'rt-1', schemaVersion: 'ov1', status: 'succeeded',
|
||||
validatedOutput: {
|
||||
questions: [
|
||||
{ stem: 'Duplicate?', answer: 'A' },
|
||||
{ stem: 'Unique?', answer: 'B' },
|
||||
{ type: 'choice', stem: 'Duplicate?', answer: 'A' },
|
||||
{ type: 'choice', stem: 'Unique?', answer: 'B' },
|
||||
],
|
||||
},
|
||||
attemptNo: 1, outputHash: 'h3',
|
||||
@ -951,7 +951,7 @@ describe('RuntimeInternalService', () => {
|
||||
|
||||
await service.submitResult('j1', {
|
||||
runtimeInstanceId: 'rt-1', schemaVersion: 'ov1', status: 'succeeded',
|
||||
validatedOutput: { questions: [{ stem: 'Q?', answer: 'A' }] },
|
||||
validatedOutput: { questions: [{ type: 'choice', stem: 'Q?', answer: 'A' }] },
|
||||
attemptNo: 1, outputHash: 'h4',
|
||||
});
|
||||
|
||||
|
||||
@ -481,11 +481,19 @@ export class RuntimeInternalService {
|
||||
continue;
|
||||
}
|
||||
|
||||
// M-QUIZ-01-CLEANUP-01: 缺失 type → 跳过 + 警告(兼容旧数据);非法非空 type → 拒绝
|
||||
if (!q.type) {
|
||||
this.logger.warn(`convertQuizCandidates: skipping question with missing type for job=${job.id}`);
|
||||
continue;
|
||||
}
|
||||
if (!isValidQuestionType(q.type)) {
|
||||
throw new BadRequestException(`Invalid question type: ${q.type}`);
|
||||
}
|
||||
|
||||
await this.prisma.quizQuestion.create({
|
||||
data: {
|
||||
quizId: quiz.id,
|
||||
// M-QUIZ-01-04: fail-closed — 非法 type 记录错误并拒绝
|
||||
type: isValidQuestionType(q.type) ? q.type! : (() => { throw new BadRequestException(`Invalid question type: ${q.type}`); })(),
|
||||
type: q.type,
|
||||
stem,
|
||||
options: q.options ?? undefined,
|
||||
answer: q.answer ?? q.correctAnswer,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user