From 6ead2787f6f333fa2a579989dad8a1b993aa94cf Mon Sep 17 00:00:00 2001 From: wangdl Date: Mon, 22 Jun 2026 21:48:59 +0800 Subject: [PATCH] test(M-AI-08-04): add LearningAnalysisValidator spec (20 tests) Coverage: schema (5), evidence (4), business (2), medical (4), personality (3), markdown (1), normal (1). Co-Authored-By: Claude --- .../learning-analysis-validator.spec.ts | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/modules/ai-job/learning-analysis-validator.spec.ts diff --git a/src/modules/ai-job/learning-analysis-validator.spec.ts b/src/modules/ai-job/learning-analysis-validator.spec.ts new file mode 100644 index 0000000..e7ab65e --- /dev/null +++ b/src/modules/ai-job/learning-analysis-validator.spec.ts @@ -0,0 +1,128 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { LearningAnalysisValidator, BusinessValidationError } from './learning-analysis-validator'; + +function validOutput(overrides?: any) { + return { + summary: '学习表现良好,复习完成率较高', + strengths: [ + { title: '复习完成率高', description: '完成率达到85%', evidenceRefs: [{ sourceType: 'review_metric' as const, metricKey: 'reviewAccuracy' }] }, + ], + weaknesses: [ + { title: '主动回忆不足', description: '近30天仅2次', knowledgePointId: null, evidenceRefs: [{ sourceType: 'active_recall' as const, metricKey: 'count' }] }, + ], + trends: [ + { metricKey: 'totalStudyDuration', direction: 'stable' as const, description: '学习时长稳定', evidenceRefs: [{ sourceType: 'study_metric' as const, metricKey: 'totalStudyDuration' }] }, + ], + risks: [ + { title: '遗忘风险', severity: 'medium' as const, description: '3个知识点已过期', evidenceRefs: [{ sourceType: 'review_metric' as const, metricKey: 'overdueCount' }] }, + ], + recommendations: [ + { actionType: 'active_recall', title: '增加主动回忆', reason: '最有效的策略', priority: 5, evidenceRefs: [{ sourceType: 'active_recall' as const, metricKey: 'count' }] }, + ], + confidence: 0.75, + insufficientData: false, + ...overrides, + }; +} + +describe('LearningAnalysisValidator', () => { + let validator: LearningAnalysisValidator; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [LearningAnalysisValidator], + }).compile(); + validator = module.get(LearningAnalysisValidator); + jest.spyOn(require('@nestjs/common').Logger.prototype, 'log').mockImplementation(() => {}); + jest.spyOn(require('@nestjs/common').Logger.prototype, 'warn').mockImplementation(() => {}); + }); + + describe('正常输出', () => { + it('合法输出通过', () => { + expect(() => validator.validate(validOutput())).not.toThrow(); + }); + }); + + describe('Schema 验证', () => { + it('summary 必填', () => { + expect(() => validator.validate(validOutput({ summary: '' }))).toThrow(BusinessValidationError); + }); + it('strengths 上限 3', () => { + const s = Array(4).fill({ title: 'S', description: 'D', evidenceRefs: [{ sourceType: 'study_metric', metricKey: 'm' }] }); + expect(() => validator.validate(validOutput({ strengths: s }))).toThrow(BusinessValidationError); + }); + it('weaknesses 上限 5', () => { + const w = Array(6).fill({ title: 'W', description: 'D', evidenceRefs: [{ sourceType: 'study_metric', metricKey: 'm' }] }); + expect(() => validator.validate(validOutput({ weaknesses: w }))).toThrow(BusinessValidationError); + }); + it('recommendations 非空', () => { + expect(() => validator.validate(validOutput({ recommendations: [] }))).toThrow(BusinessValidationError); + }); + it('confidence 超出 [0,1]', () => { + expect(() => validator.validate(validOutput({ confidence: 1.5 }))).toThrow(BusinessValidationError); + }); + }); + + describe('Evidence 验证', () => { + it('非法 sourceType', () => { + const s = [{ title: 'S', description: 'D', evidenceRefs: [{ sourceType: 'unknown_type', metricKey: 'x' }] }]; + expect(() => validator.validate(validOutput({ strengths: s }))).toThrow(BusinessValidationError); + }); + it('缺失 metricKey', () => { + const s = [{ title: 'S', description: 'D', evidenceRefs: [{ sourceType: 'study_metric', metricKey: '' }] }]; + expect(() => validator.validate(validOutput({ strengths: s }))).toThrow(BusinessValidationError); + }); + it('strength 无 evidenceRefs', () => { + const s = [{ title: 'S', description: 'D', evidenceRefs: [] }]; + expect(() => validator.validate(validOutput({ strengths: s }))).toThrow(BusinessValidationError); + }); + it('weakness 无 evidenceRefs', () => { + const w = [{ title: 'W', description: 'D', evidenceRefs: [] }]; + expect(() => validator.validate(validOutput({ weaknesses: w }))).toThrow(BusinessValidationError); + }); + }); + + describe('Business 验证', () => { + it('insufficientData=true 但 confidence>0.5', () => { + expect(() => validator.validate(validOutput({ insufficientData: true, confidence: 0.8 }))).toThrow(BusinessValidationError); + }); + it('insufficientData=true 且 confidence 低 → 通过', () => { + expect(() => validator.validate(validOutput({ insufficientData: true, confidence: 0.3 }))).not.toThrow(); + }); + }); + + describe('医疗诊断', () => { + it('summary 含 ADHD', () => { + expect(() => validator.validate(validOutput({ summary: '用户可能患有ADHD' }))).toThrow(BusinessValidationError); + }); + it('description 含 depression', () => { + const s = [{ title: 'S', description: 'signs of depression detected', evidenceRefs: [{ sourceType: 'study_metric', metricKey: 'm' }] }]; + expect(() => validator.validate(validOutput({ strengths: s }))).toThrow(BusinessValidationError); + }); + it('含 焦虑症', () => { + expect(() => validator.validate(validOutput({ summary: '表现出焦虑症倾向' }))).toThrow(BusinessValidationError); + }); + it('含 PTSD', () => { + expect(() => validator.validate(validOutput({ summary: '可能有PTSD症状' }))).toThrow(BusinessValidationError); + }); + }); + + describe('人格判断', () => { + it('含 "你是一个...的人"', () => { + expect(() => validator.validate(validOutput({ summary: '你是一个懒惰的人' }))).toThrow(BusinessValidationError); + }); + it('含 "你天生"', () => { + expect(() => validator.validate(validOutput({ summary: '你天生不适合学习' }))).toThrow(BusinessValidationError); + }); + it('含 "你太懒"', () => { + const r = [{ actionType: 'study', title: '加油', reason: '因为你太懒了', priority: 1, evidenceRefs: [{ sourceType: 'study_metric', metricKey: 'm' }] }]; + expect(() => validator.validate(validOutput({ recommendations: r }))).toThrow(BusinessValidationError); + }); + }); + + describe('Markdown 代码块', () => { + it('summary 含 code block', () => { + expect(() => validator.validate(validOutput({ summary: '分析结果:\n```json\n{}\n```' }))).toThrow(BusinessValidationError); + }); + }); +});