文件
OmniCard/backend/test/mpuniapp/ocr-exchange.test.ts

417 行
14 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

/**
* OmniCard AI (CRMmp) - 移动端拍照上传纸质名片 OCR 识别入库测试套件 (Point 25)
* 严格按照 TDD 模式 (Red -> Green -> Refactor) 与 AGENTS.md 规范编写
*/
type ExchangeForm = {
name: string;
company: string;
phone: string;
email: string;
notes: string;
};
type ExtractedContactInfo = {
name: string;
company: string;
phone: string;
email: string;
title: string;
notes: string;
rawText: string;
};
describe('Point 25: 移动端拍照上传纸质名片 OCR 识别入库 (useOcrExchange.uts)', () => {
let toastCalls: Array<{ title: string; icon?: string }>;
beforeEach(() => {
toastCalls = [];
(global as unknown as { uni?: Record<string, unknown> }).uni = {
showToast: (options: { title: string; icon?: string }) => {
toastCalls.push(options);
},
chooseImage: jest.fn(),
};
});
afterEach(() => {
delete (global as unknown as { uni?: Record<string, unknown> }).uni;
});
function loadUseOcrExchange() {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('../../../mpuniapp/pages/portal/useOcrExchange.uts') as {
parseOcrCardText: (rawOcrText: string) => ExtractedContactInfo;
fillFormFromOcr: (
form: ExchangeForm,
extracted: ExtractedContactInfo,
overwrite?: boolean
) => ExchangeForm;
normalizeE164Phone: (rawPhone: string) => string;
createOcrScanState: () => {
scanning: boolean;
rawText: string;
lastExtracted: ExtractedContactInfo | null;
errorMessage: string;
};
simulateOcrScan: (sampleType?: string) => ExtractedContactInfo;
handleCardOcrUpload: (
form: ExchangeForm,
ocrState: ReturnType<typeof loadUseOcrExchange> extends never ? never : any,
options?: { mockText?: string }
) => Promise<boolean>;
};
}
describe('1. 纯文本识别提取 (Basic Plain Text Extraction)', () => {
it('1.1 提取带有标准冒号标签的名片文本', () => {
const { parseOcrCardText } = loadUseOcrExchange();
const rawText = `
姓名: 张伟
职务: 研发总监
公司: 深圳超维智能科技有限公司
电话: +86 138-0013-8000
邮箱: zhangwei@chaowei-ai.com
`;
const result = parseOcrCardText(rawText);
expect(result.name).toBe('张伟');
expect(result.title).toBe('研发总监');
expect(result.company).toBe('深圳超维智能科技有限公司');
expect(result.phone).toBe('+8613800138000');
expect(result.email).toBe('zhangwei@chaowei-ai.com');
});
it('1.2 提取无标签前缀的英文标准商务名片', () => {
const { parseOcrCardText } = loadUseOcrExchange();
const rawText = `
OmniCard AI Global Inc.
Alex Morgan
Chief Technology Officer
alex.morgan@omnicard.ai
+1 555-234-5678
`;
const result = parseOcrCardText(rawText);
expect(result.name).toBe('Alex Morgan');
expect(result.title).toBe('Chief Technology Officer');
expect(result.company).toBe('OmniCard AI Global Inc.');
expect(result.email).toBe('alex.morgan@omnicard.ai');
expect(result.phone).toBe('+15552345678');
});
});
describe('2. 国际标准 E.164 电话与多格式邮箱提取', () => {
it('2.1 正确识别与规范化中国、阿联酋、英国、美国等国际 E.164 电话', () => {
const { normalizeE164Phone, parseOcrCardText } = loadUseOcrExchange();
// 单独格式化函数验证
expect(normalizeE164Phone('+86 138 0013 8000')).toBe('+8613800138000');
expect(normalizeE164Phone('+971 50 123 4567')).toBe('+971501234567');
expect(normalizeE164Phone('+1 (555) 234-5678')).toBe('+15552345678');
expect(normalizeE164Phone('+44 20 7946 0919')).toBe('+442079460919');
expect(normalizeE164Phone('0086 13800138000')).toBe('+8613800138000');
expect(normalizeE164Phone('13912345678')).toBe('+8613912345678');
// 从 OCR 文本中识别中东阿联酋电话
const uaeCard = `
Tariq Mansour
Dubai Trade Center LLC
WhatsApp: +971 50 123 4567
tariq@dubaitrade.ae
`;
const uaeResult = parseOcrCardText(uaeCard);
expect(uaeResult.phone).toBe('+971501234567');
expect(uaeResult.email).toBe('tariq@dubaitrade.ae');
});
it('2.2 提取多级域名及包含加号/下划线的复杂邮箱地址', () => {
const { parseOcrCardText } = loadUseOcrExchange();
const rawText = `
Contact person: Sarah Connor
Organization: Cyberdyne Systems UK Ltd.
E-mail: sarah.connor+biz_dev@corp.subdomain.co.uk
Mobile: +44 7700 900123
`;
const result = parseOcrCardText(rawText);
expect(result.email).toBe('sarah.connor+biz_dev@corp.subdomain.co.uk');
expect(result.name).toBe('Sarah Connor');
expect(result.company).toBe('Cyberdyne Systems UK Ltd.');
expect(result.phone).toBe('+447700900123');
});
});
describe('3. 复杂多行格式、乱序与噪音干扰提取', () => {
it('3.1 识别同一行内的姓名与职位组合', () => {
const { parseOcrCardText } = loadUseOcrExchange();
const rawText = `
杭州智算科技有限公司
李小峰 / 首席架构师
Tel: +86-571-88889999
Email: lxf@zhisuan.cn
`;
const result = parseOcrCardText(rawText);
expect(result.name).toBe('李小峰');
expect(result.title).toBe('首席架构师');
expect(result.company).toBe('杭州智算科技有限公司');
expect(result.email).toBe('lxf@zhisuan.cn');
});
it('3.2 乱序排版及含有地址、网址、标语等干扰信息时准确定位核心字段', () => {
const { parseOcrCardText } = loadUseOcrExchange();
const messyCard = `
www.futurecloud.io
service@futurecloud.io
地址:北京市海淀区中关村南大街1号科技大厦B座18层
致力于打造全球顶尖分布式智算基础设施
+86 186 1111 2222
王建国
VP of Operations
北京未来云智科技有限公司
`;
const result = parseOcrCardText(messyCard);
expect(result.name).toBe('王建国');
expect(result.title).toBe('VP of Operations');
expect(result.company).toBe('北京未来云智科技有限公司');
expect(result.email).toBe('service@futurecloud.io');
expect(result.phone).toBe('+8618611112222');
});
});
describe('4. 空文本与极端异常容错 (Resilience & Edge Cases)', () => {
it('4.1 传入空文本或空白字符时返回安全的空字段结构且不报错', () => {
const { parseOcrCardText } = loadUseOcrExchange();
const emptyResult = parseOcrCardText('');
expect(emptyResult).toEqual({
name: '',
company: '',
phone: '',
email: '',
title: '',
notes: '',
rawText: '',
});
const whitespaceResult = parseOcrCardText(' \n\t \r\n ');
expect(whitespaceResult.name).toBe('');
expect(whitespaceResult.company).toBe('');
expect(whitespaceResult.phone).toBe('');
expect(whitespaceResult.email).toBe('');
});
it('4.2 传入无任何有效联系信息的文本时安全容错', () => {
const { parseOcrCardText } = loadUseOcrExchange();
const gibberish = '### !@@$ \n --- \n 12345 \n ?????';
const result = parseOcrCardText(gibberish);
expect(result.name).toBe('');
expect(result.company).toBe('');
expect(result.phone).toBe('');
expect(result.email).toBe('');
});
});
describe('5. fillFormFromOcr 智能填单与缺失字段保护', () => {
it('5.1 完整信息直接填充空表单', () => {
const { fillFormFromOcr } = loadUseOcrExchange();
const form: ExchangeForm = {
name: '',
company: '',
phone: '',
email: '',
notes: '',
};
const extracted: ExtractedContactInfo = {
name: '张三',
company: '极客互联',
phone: '+8613800138000',
email: 'zhangsan@geek.com',
title: '产品总监',
notes: '',
rawText: '...',
};
fillFormFromOcr(form, extracted);
expect(form.name).toBe('张三');
expect(form.company).toBe('极客互联');
expect(form.phone).toBe('+8613800138000');
expect(form.email).toBe('zhangsan@geek.com');
// 若原 notes 为空且 title 存在,智能将 title 放入 notes 方便跟进
expect(form.notes).toBe('产品总监');
});
it('5.2 缺失字段保护:OCR 缺失的字段绝不覆盖已有用户输入', () => {
const { fillFormFromOcr } = loadUseOcrExchange();
const form: ExchangeForm = {
name: '',
company: '已有预填公司',
phone: '',
email: '',
notes: '已有重要客户需求备注',
};
// OCR 仅识别到了姓名与手机号
const extracted: ExtractedContactInfo = {
name: '李四',
company: '',
phone: '+8613900001111',
email: '',
title: '',
notes: '',
rawText: '...',
};
fillFormFromOcr(form, extracted);
expect(form.name).toBe('李四');
expect(form.phone).toBe('+8613900001111');
// 原有非空内容受到保护,未被清空
expect(form.company).toBe('已有预填公司');
expect(form.notes).toBe('已有重要客户需求备注');
});
it('5.3 非覆盖模式 (overwrite: false) 仅填充空白字段', () => {
const { fillFormFromOcr } = loadUseOcrExchange();
const form: ExchangeForm = {
name: '手动输入的姓名',
company: '',
phone: '+8613777777777',
email: '',
notes: '',
};
const extracted: ExtractedContactInfo = {
name: 'OCR识别出的新姓名',
company: 'OCR公司',
phone: '+8613888888888',
email: 'new@corp.com',
title: '',
notes: '',
rawText: '...',
};
fillFormFromOcr(form, extracted, false);
// 手动输入的姓名与电话保持原样
expect(form.name).toBe('手动输入的姓名');
expect(form.phone).toBe('+8613777777777');
// 原为空的字段被填入
expect(form.company).toBe('OCR公司');
expect(form.email).toBe('new@corp.com');
});
});
describe('6. 客户端拍照上传流程辅助与模拟 (handleCardOcrUpload)', () => {
it('6.1 模拟名片识别成功后自动填充表单并提示用户', async () => {
const { createOcrScanState, handleCardOcrUpload } = loadUseOcrExchange();
const form: ExchangeForm = {
name: '',
company: '',
phone: '',
email: '',
notes: '',
};
const ocrState = createOcrScanState();
const mockText = `
Name: Tariq Mansour
Company: Al Futtaim Group LLC
Phone: +971 50 123 4567
Email: tariq.mansour@alfuttaim.ae
`;
const success = await handleCardOcrUpload(form, ocrState, { mockText });
expect(success).toBe(true);
expect(form.name).toBe('Tariq Mansour');
expect(form.company).toBe('Al Futtaim Group LLC');
expect(form.phone).toBe('+971501234567');
expect(form.email).toBe('tariq.mansour@alfuttaim.ae');
expect(ocrState.scanning).toBe(false);
expect(ocrState.lastExtracted).not.toBeNull();
expect(toastCalls.length).toBeGreaterThan(0);
expect(toastCalls[0].title).toContain('识别成功');
});
it('6.2 通过 chooseImage 成功拍照或选图后触发名片自动填充', async () => {
const { createOcrScanState, handleCardOcrUpload } = loadUseOcrExchange();
const form: ExchangeForm = {
name: '',
company: '',
phone: '',
email: '',
notes: '',
};
const ocrState = createOcrScanState();
(global as unknown as { uni: Record<string, unknown> }).uni.chooseImage = (opt: {
success?: (res: unknown) => void;
fail?: (err: unknown) => void;
}) => {
opt.success?.({ tempFilePaths: ['temp://photo.jpg'] });
};
const success = await handleCardOcrUpload(form, ocrState);
expect(success).toBe(true);
expect(form.name).toBe('张伟');
expect(form.phone).toBe('+8613800138000');
expect(form.company).toBe('深圳超维智能科技有限公司');
expect(ocrState.scanning).toBe(false);
expect(toastCalls.length).toBeGreaterThan(0);
});
it('6.3 用户取消拍照选图时优雅处理并不覆盖已有表单', async () => {
const { createOcrScanState, handleCardOcrUpload } = loadUseOcrExchange();
const form: ExchangeForm = {
name: '已有姓名',
company: '',
phone: '',
email: '',
notes: '',
};
const ocrState = createOcrScanState();
(global as unknown as { uni: Record<string, unknown> }).uni.chooseImage = (opt: {
success?: (res: unknown) => void;
fail?: (err: unknown) => void;
}) => {
opt.fail?.(new Error('用户取消操作'));
};
const success = await handleCardOcrUpload(form, ocrState);
expect(success).toBe(false);
expect(form.name).toBe('已有姓名');
expect(ocrState.scanning).toBe(false);
expect(ocrState.errorMessage).toContain('用户取消操作');
});
it('6.4 simulateOcrScan 针对国内外商务预设名片准确提取', () => {
const { simulateOcrScan } = loadUseOcrExchange();
const uaeSample = simulateOcrScan('uae');
expect(uaeSample.name).toBe('Tariq Mansour');
expect(uaeSample.phone).toBe('+971501234567');
expect(uaeSample.company).toBe('Dubai Trade Center LLC');
const globalSample = simulateOcrScan('global');
expect(globalSample.name).toBe('Alex Morgan');
expect(globalSample.phone).toBe('+15552345678');
expect(globalSample.company).toBe('OmniCard AI Global Inc.');
const defaultSample = simulateOcrScan();
expect(defaultSample.name).toBe('张伟');
expect(defaultSample.phone).toBe('+8613800138000');
});
});
});