完整的前后端图片分析应用,包含: - 后端: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>
152 lines
5.4 KiB
TypeScript
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
|
|
});
|
|
});
|
|
});
|