Files
lcbp3/backend/src/modules/ai/ai-validation.service.spec.ts
T
admin 99c8d61856
CI / CD Pipeline / build (push) Successful in 4m30s
CI / CD Pipeline / deploy (push) Successful in 1m6s
690409:0953 Done Task-BE-AI-02
2026-04-09 09:53:57 +07:00

212 lines
7.5 KiB
TypeScript

// File: src/modules/ai/ai-validation.service.spec.ts
// Unit Tests สำหรับ AiValidationService — ตรวจสอบ Confidence Thresholds และ Validation Logic (ADR-020)
import { Test, TestingModule } from '@nestjs/testing';
import { AiValidationService } from './ai-validation.service';
import { AiCallbackDto } from './dto/ai-callback.dto';
import { AiAuditStatus } from './entities/ai-audit-log.entity';
describe('AiValidationService', () => {
let service: AiValidationService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AiValidationService],
}).compile();
service = module.get<AiValidationService>(AiValidationService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
// --- getConfidenceAction ---
describe('getConfidenceAction', () => {
it('ควรคืน auto_approve เมื่อ confidence >= 0.95', () => {
expect(service.getConfidenceAction(0.95)).toBe('auto_approve');
expect(service.getConfidenceAction(1.0)).toBe('auto_approve');
expect(service.getConfidenceAction(0.99)).toBe('auto_approve');
});
it('ควรคืน low_priority_review เมื่อ confidence 0.85-0.94', () => {
expect(service.getConfidenceAction(0.85)).toBe('low_priority_review');
expect(service.getConfidenceAction(0.9)).toBe('low_priority_review');
expect(service.getConfidenceAction(0.94)).toBe('low_priority_review');
});
it('ควรคืน high_priority_review เมื่อ confidence 0.60-0.84', () => {
expect(service.getConfidenceAction(0.6)).toBe('high_priority_review');
expect(service.getConfidenceAction(0.75)).toBe('high_priority_review');
expect(service.getConfidenceAction(0.84)).toBe('high_priority_review');
});
it('ควรคืน reject เมื่อ confidence < 0.60', () => {
expect(service.getConfidenceAction(0.59)).toBe('reject');
expect(service.getConfidenceAction(0.0)).toBe('reject');
expect(service.getConfidenceAction(0.3)).toBe('reject');
});
});
// --- validateAiOutput ---
describe('validateAiOutput', () => {
// สร้าง Payload ตัวอย่างที่ถูกต้อง
const buildPayload = (
overrides: Partial<AiCallbackDto> = {}
): AiCallbackDto => ({
migrationLogPublicId: '019505a1-7c3e-7000-8000-abc123def456',
aiModel: 'gemma4',
status: AiAuditStatus.SUCCESS,
confidenceScore: 0.9,
extractedMetadata: {
subject: 'ทดสอบ',
date: '2026-04-09',
discipline: 'Civil',
},
...overrides,
});
it('ควร valid เมื่อ confidence >= 0.60 และ status = SUCCESS', () => {
const result = service.validateAiOutput(
buildPayload({ confidenceScore: 0.9 })
);
expect(result.isValid).toBe(true);
expect(result.action).toBe('low_priority_review');
});
it('ควร invalid เมื่อ status = FAILED', () => {
const result = service.validateAiOutput(
buildPayload({ status: AiAuditStatus.FAILED })
);
expect(result.isValid).toBe(false);
expect(result.action).toBe('reject');
expect(result.reasons.length).toBeGreaterThan(0);
});
it('ควร invalid เมื่อ status = TIMEOUT', () => {
const result = service.validateAiOutput(
buildPayload({ status: AiAuditStatus.TIMEOUT })
);
expect(result.isValid).toBe(false);
});
it('ควร invalid เมื่อ confidence < 0.60', () => {
const result = service.validateAiOutput(
buildPayload({ confidenceScore: 0.55 })
);
expect(result.isValid).toBe(false);
expect(result.action).toBe('reject');
expect(result.reasons.some((r) => r.includes('0.55'))).toBe(true);
});
it('ควร valid พร้อม auto_approve เมื่อ confidence >= 0.95', () => {
const result = service.validateAiOutput(
buildPayload({ confidenceScore: 0.97 })
);
expect(result.isValid).toBe(true);
expect(result.action).toBe('auto_approve');
});
it('ควร invalid เมื่อ discipline ไม่ถูกต้อง', () => {
const result = service.validateAiOutput(
buildPayload({
confidenceScore: 0.9,
extractedMetadata: { discipline: 'InvalidDiscipline' },
})
);
expect(result.isValid).toBe(false);
expect(result.reasons.some((r) => r.includes('discipline'))).toBe(true);
});
it('ควรยอมรับ discipline ที่ถูกต้องทั้ง 4 ค่า', () => {
const validDisciplines = [
'Civil',
'Mechanical',
'Electrical',
'Architectural',
];
for (const discipline of validDisciplines) {
const result = service.validateAiOutput(
buildPayload({
confidenceScore: 0.9,
extractedMetadata: { discipline },
})
);
expect(result.isValid).toBe(true);
}
});
it('ควร invalid เมื่อ date format ผิด', () => {
const result = service.validateAiOutput(
buildPayload({
confidenceScore: 0.9,
extractedMetadata: { date: '09/04/2026' }, // รูปแบบผิด
})
);
expect(result.isValid).toBe(false);
expect(result.reasons.some((r) => r.includes('date'))).toBe(true);
});
it('ควร valid เมื่อไม่มี extractedMetadata (metadata เป็น optional)', () => {
const result = service.validateAiOutput(
buildPayload({ confidenceScore: 0.9, extractedMetadata: undefined })
);
expect(result.isValid).toBe(true);
});
it('ควร valid เมื่อ confidence = undefined (ถือว่า 0 — reject)', () => {
const result = service.validateAiOutput(
buildPayload({ confidenceScore: undefined })
);
expect(result.isValid).toBe(false);
expect(result.action).toBe('reject');
});
});
// --- buildAuditSummary ---
describe('buildAuditSummary', () => {
it('ควรสร้าง Summary string ที่มีข้อมูลครบถ้วน', () => {
const payload: AiCallbackDto = {
migrationLogPublicId: '019505a1-7c3e-7000-8000-abc123def456',
aiModel: 'gemma4',
status: AiAuditStatus.SUCCESS,
confidenceScore: 0.92,
};
const validationResult = {
isValid: true,
action: 'low_priority_review' as const,
confidence: 0.92,
reasons: [],
};
const summary = service.buildAuditSummary(payload, validationResult);
expect(summary).toContain('model=gemma4');
expect(summary).toContain('confidence=0.92');
expect(summary).toContain('action=low_priority_review');
expect(summary).toContain('valid=true');
});
it('ควรแสดง reasons เมื่อ validation ล้มเหลว', () => {
const payload: AiCallbackDto = {
migrationLogPublicId: '019505a1-7c3e-7000-8000-abc123def456',
aiModel: 'gemma4',
status: AiAuditStatus.FAILED,
};
const validationResult = {
isValid: false,
action: 'reject' as const,
confidence: 0,
reasons: ['AI processing failed with status: FAILED'],
};
const summary = service.buildAuditSummary(payload, validationResult);
expect(summary).toContain('reasons=');
expect(summary).toContain('FAILED');
});
});
});