diff --git a/src/modules/ai-runtime/internal/runtime-internal.service.spec.ts b/src/modules/ai-runtime/internal/runtime-internal.service.spec.ts index bb44534..376b655 100644 --- a/src/modules/ai-runtime/internal/runtime-internal.service.spec.ts +++ b/src/modules/ai-runtime/internal/runtime-internal.service.spec.ts @@ -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', }); diff --git a/src/modules/ai-runtime/internal/runtime-internal.service.ts b/src/modules/ai-runtime/internal/runtime-internal.service.ts index 67c8a60..22588c9 100644 --- a/src/modules/ai-runtime/internal/runtime-internal.service.ts +++ b/src/modules/ai-runtime/internal/runtime-internal.service.ts @@ -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,