7e8f4859cd
- Add ADR-036 unified OCR architecture (typhoon-ocr via Ollama) - Extend AI execution profiles for OCR sandbox configuration - Add comprehensive frontend test coverage (components, hooks, services) - Add backend test coverage for document-numbering services - Update OCR sidecar with typhoon-ocr integration - Add AI policy service and execution profile management - Update AGENTS.md and architecture documentation
113 lines
4.1 KiB
TypeScript
113 lines
4.1 KiB
TypeScript
// File: backend/src/modules/ai/tests/ocr.service.spec.ts
|
|
// Change Log:
|
|
// - 2026-06-13: Initial unit tests for OCR parameter wiring (T066)
|
|
import { Test, TestingModule } from '@nestjs/testing';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
import { OcrService } from '../services/ocr.service';
|
|
import { VramMonitorService } from '../services/vram-monitor.service';
|
|
import { AiPolicyService } from '../services/ai-policy.service';
|
|
import { OcrCacheService } from '../services/ocr-cache.service';
|
|
import { SystemSetting } from '../entities/system-setting.entity';
|
|
import { AiAuditLog } from '../entities/ai-audit-log.entity';
|
|
import axios from 'axios';
|
|
import * as fs from 'fs';
|
|
jest.mock('axios');
|
|
jest.mock('fs');
|
|
describe('OcrService Parameter Wiring (T066)', () => {
|
|
let service: OcrService;
|
|
const mockConfigService = {
|
|
get: jest.fn((key: string, defaultValue?: unknown): unknown => {
|
|
const config: Record<string, unknown> = {
|
|
OCR_CHAR_THRESHOLD: 100,
|
|
OCR_API_URL: 'http://localhost:8765',
|
|
OCR_SIDECAR_API_KEY: 'test-key',
|
|
VRAM_HEADROOM_THRESHOLD_MB: 3000,
|
|
OCR_RESIDENCY_WINDOW_SECONDS: 120,
|
|
GPU_MAIN_MODEL_PRESSURE_THRESHOLD_MB: 12000,
|
|
};
|
|
return config[key] ?? defaultValue;
|
|
}),
|
|
};
|
|
const mockSystemSettingRepo = {
|
|
findOne: jest.fn().mockResolvedValue({
|
|
settingValue: '019505a1-7c3e-7000-8000-abc123def002',
|
|
}),
|
|
};
|
|
const mockAiAuditLogRepo = {
|
|
create: jest.fn().mockReturnValue({}),
|
|
save: jest.fn().mockResolvedValue({}),
|
|
};
|
|
const mockOcrCacheService = {};
|
|
const mockVramMonitorService = {
|
|
getVramHeadroom: jest.fn().mockResolvedValue({
|
|
totalMb: 16384,
|
|
usedMb: 4000,
|
|
availableMb: 12384,
|
|
querySuccess: true,
|
|
mainModelVramMb: 4000,
|
|
}),
|
|
hasVramCapacity: jest.fn().mockResolvedValue(true),
|
|
};
|
|
const mockAiPolicyService = {};
|
|
const mockRedis = {
|
|
get: jest.fn().mockResolvedValue(null),
|
|
set: jest.fn().mockResolvedValue('OK'),
|
|
del: jest.fn().mockResolvedValue(1),
|
|
};
|
|
beforeEach(async () => {
|
|
const module: TestingModule = await Test.createTestingModule({
|
|
providers: [
|
|
OcrService,
|
|
{ provide: ConfigService, useValue: mockConfigService },
|
|
{
|
|
provide: getRepositoryToken(SystemSetting),
|
|
useValue: mockSystemSettingRepo,
|
|
},
|
|
{
|
|
provide: getRepositoryToken(AiAuditLog),
|
|
useValue: mockAiAuditLogRepo,
|
|
},
|
|
{ provide: OcrCacheService, useValue: mockOcrCacheService },
|
|
{ provide: VramMonitorService, useValue: mockVramMonitorService },
|
|
{ provide: AiPolicyService, useValue: mockAiPolicyService },
|
|
{
|
|
provide: 'default_IORedisModuleConnectionToken',
|
|
useValue: mockRedis,
|
|
},
|
|
],
|
|
}).compile();
|
|
service = module.get<OcrService>(OcrService);
|
|
jest.clearAllMocks();
|
|
(fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('PDF content'));
|
|
(axios.post as jest.Mock).mockResolvedValue({
|
|
data: { text: 'OCR Result Text' },
|
|
});
|
|
});
|
|
it('ควรส่ง parameter temperature, topP, repeatPenalty ไปยัง sidecar ผ่าน FormData เมื่อเรียก detectAndExtract', async () => {
|
|
await service.detectAndExtract({
|
|
pdfPath: '/path/to/test.pdf',
|
|
documentPublicId: 'doc-123',
|
|
typhoonOptions: {
|
|
temperature: 0.15,
|
|
topP: 0.65,
|
|
repeatPenalty: 1.15,
|
|
},
|
|
});
|
|
expect(axios.post).toHaveBeenCalled();
|
|
const mockPost = axios.post as jest.Mock<
|
|
Promise<unknown>,
|
|
[string, FormData, unknown]
|
|
>;
|
|
const postCallArgs = mockPost.mock.calls[0];
|
|
const url = postCallArgs[0];
|
|
const formData = postCallArgs[1];
|
|
expect(url).toBe('http://localhost:8765/ocr-upload');
|
|
expect(formData).toBeInstanceOf(FormData);
|
|
expect(formData.get('engine')).toBe('typhoon-np-dms-ocr');
|
|
expect(formData.get('temperature')).toBe('0.15');
|
|
expect(formData.get('topP')).toBe('0.65');
|
|
expect(formData.get('repeatPenalty')).toBe('1.15');
|
|
});
|
|
});
|