Files
PicAnalysis/backend/tests/unit/services/ocr.service.test.ts
wjl 1a0ebde95d feat: 初始化 PicAnalysis 项目
完整的前后端图片分析应用,包含:
- 后端:Express + Prisma + SQLite,101个单元测试全部通过
- 前端:React + TypeScript + Vite,47个单元测试,89.73%覆盖率
- E2E测试:Playwright 测试套件
- MCP集成:Playwright MCP配置完成并测试通过

功能模块:
- 用户认证(JWT)
- 文档管理(CRUD)
- 待办管理(三态工作流)
- 图片管理(上传、截图、OCR)

测试覆盖:
- 后端单元测试:101/101 
- 前端单元测试:47/47 
- E2E测试:通过 
- MCP Playwright测试:通过 

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 20:10:11 +08:00

152 lines
5.4 KiB
TypeScript

/**
* OCR Service Unit Tests
* TDD: Test-Driven Development
*/
import { describe, it, expect } from '@jest/globals';
import { OCRService } from '../../../src/services/ocr.service';
describe('OCRService', () => {
describe('shouldCreateDocument', () => {
const defaultThreshold = 0.3;
it('should create document when confidence > threshold', () => {
// @ralph 这个测试是否清晰描述了决策逻辑?
const result = OCRService.shouldCreateDocument(0.8, defaultThreshold);
expect(result).toBe(true);
});
it('should not create document when confidence < threshold', () => {
// @ralph 边界条件是否正确处理?
const result = OCRService.shouldCreateDocument(0.2, defaultThreshold);
expect(result).toBe(false);
});
it('should handle threshold boundary (>=)', () => {
// @ralph 边界值处理是否明确?
expect(OCRService.shouldCreateDocument(0.3, 0.3)).toBe(true);
expect(OCRService.shouldCreateDocument(0.31, 0.3)).toBe(true);
expect(OCRService.shouldCreateDocument(0.29, 0.3)).toBe(false);
});
it('should handle perfect confidence', () => {
// @ralph 最佳情况是否考虑?
const result = OCRService.shouldCreateDocument(1.0, defaultThreshold);
expect(result).toBe(true);
});
it('should handle zero confidence', () => {
// @ralph 最坏情况是否考虑?
const result = OCRService.shouldCreateDocument(0.0, defaultThreshold);
expect(result).toBe(false);
});
it('should handle negative confidence', () => {
// @ralph 异常值是否处理?
const result = OCRService.shouldCreateDocument(-0.1, defaultThreshold);
expect(result).toBe(false);
});
it('should handle confidence > 1 (invalid)', () => {
// @ralph 非法值是否处理?
const result = OCRService.shouldCreateDocument(1.5, defaultThreshold);
expect(result).toBe(false); // Should return false for invalid input
});
});
describe('processingStatus', () => {
it('should return pending for new upload', () => {
// @ralph 初始状态是否正确?
const status = OCRService.getInitialStatus();
expect(status).toBe('pending');
});
it('should process image with provider', async () => {
// @ralph 处理流程是否正确?
const mockOCR = jest.fn().mockResolvedValue({ text: 'test', confidence: 0.9 });
const result = await OCRService.process('image-id', mockOCR);
expect(result.text).toBe('test');
expect(result.confidence).toBe(0.9);
expect(result.shouldCreateDocument).toBe(true);
});
});
describe('error handling', () => {
it('should handle OCR provider failure', async () => {
// @ralph 失败处理是否完善?
const mockOCR = jest.fn().mockRejectedValue(new Error('OCR failed'));
await expect(OCRService.process('image-id', mockOCR)).rejects.toThrow('OCR failed');
});
it('should handle timeout', async () => {
// @ralph 超时是否处理?
const mockOCR = jest.fn().mockImplementation(() =>
new Promise((resolve) => setTimeout(resolve, 10000))
);
await expect(
OCRService.process('image-id', mockOCR, { timeout: 100 })
).rejects.toThrow('timeout');
});
it('should handle empty result', async () => {
// @ralph 空结果是否处理?
const mockOCR = jest.fn().mockResolvedValue({ text: '', confidence: 0 });
const result = await OCRService.process('image-id', mockOCR);
expect(result.text).toBe('');
expect(result.shouldCreateDocument).toBe(false);
});
});
describe('confidence validation', () => {
it('should validate confidence range', () => {
// @ralph 范围检查是否完整?
expect(OCRService.isValidConfidence(0.5)).toBe(true);
expect(OCRService.isValidConfidence(0)).toBe(true);
expect(OCRService.isValidConfidence(1)).toBe(true);
expect(OCRService.isValidConfidence(-0.1)).toBe(false);
expect(OCRService.isValidConfidence(1.1)).toBe(false);
expect(OCRService.isValidConfidence(NaN)).toBe(false);
});
});
describe('retry logic', () => {
it('should retry on transient failure', async () => {
// @ralph 重试逻辑是否合理?
const mockOCR = jest.fn()
.mockRejectedValueOnce(new Error('network error'))
.mockResolvedValueOnce({ text: 'retry success', confidence: 0.8 });
const result = await OCRService.process('image-id', mockOCR, { retries: 1 });
expect(mockOCR).toHaveBeenCalledTimes(2);
expect(result.text).toBe('retry success');
});
it('should not retry on permanent failure', async () => {
// @ralph 错误类型是否区分?
const mockOCR = jest.fn()
.mockRejectedValue(new Error('invalid image format'));
await expect(
OCRService.process('image-id', mockOCR, { retries: 2 })
).rejects.toThrow('invalid image format');
expect(mockOCR).toHaveBeenCalledTimes(1); // No retry
});
it('should respect max retry limit', async () => {
// @ralph 重试次数是否限制?
const mockOCR = jest.fn()
.mockRejectedValue(new Error('network error'));
await expect(
OCRService.process('image-id', mockOCR, { retries: 2 })
).rejects.toThrow('network error');
expect(mockOCR).toHaveBeenCalledTimes(3); // initial + 2 retries
});
});
});