import { existsSync } from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; import { getHandler } from "../../src/engine/registry"; import type { CatalogComponent, FlowNode } from "../../src/types"; import type { RunContext } from "../../src/engine/types"; import "../../src/capabilities/remaining-core"; import "../../src/capabilities/captcha-caps"; // last-wins:覆盖 remaining-core 的通用实现 import { cleanCodeText, parseCalculation } from "../../src/capabilities/captcha-caps"; // 测试图随仓库走(__fixtures__/captcha,PIL 生成),真实 OCR 模型来自 npm 依赖 const fixturesDir = path.join(import.meta.dirname, "__fixtures__", "captcha"); const fixture = (name: string) => path.join(fixturesDir, `${name}.png`); const ctx = { signal: new AbortController().signal, log: () => {}, vars: {}, evidenceDir: fixturesDir } as unknown as RunContext; const node: FlowNode = { id: "smoke", componentId: "smoke", position: { x: 0, y: 0 }, values: {}, options: {} }; describe("parseCalculation 解析层(纯逻辑)", () => { it("基础四则与等号问号", () => { expect(parseCalculation("3+5=?")).toEqual({ expression: "3+5", result: 8 }); }); it("x/X/* 归一为乘", () => { expect(parseCalculation("6x4=?")!.result).toBe(24); expect(parseCalculation("9X9=?")!.result).toBe(81); }); it("全角与杂字符", () => { expect(parseCalculation("12+7=?")!.result).toBe(19); }); it("长破折号/Unicode 减号", () => { expect(parseCalculation("12—7")!.result).toBe(5); }); it("除法整除与小数", () => { expect(parseCalculation("8/2")!.result).toBe(4); expect(parseCalculation("7/2")!.result).toBe(3.5); }); it("无算式返回 null", () => { expect(parseCalculation("ab12cd")).toBeNull(); }); it("字符验证码清洗", () => { expect(cleanCodeText("a b 7 X ? = ")).toBe("ab7X"); }); }); describe.skipIf(!existsSync(fixture("captcha_0_3p5_q")))("OCR_CalculationCode 端到端(真实模型)", () => { const cases: Array<[string, string]> = [ ["captcha_0_3p5_q", "8"], ["captcha_1_12m7_q", "5"], ["captcha_2_6t4_q", "24"], ["captcha_3_15p28_q", "43"], ["captcha_4_9t9_q", "81"], ]; for (const [file, expected] of cases) { it(`${file} -> ${expected}`, async () => { const handler = getHandler("OCR_CalculationCode"); expect(handler).toBeTruthy(); const out = await handler!(ctx, node, { path: fixture(file) }, {} as CatalogComponent); console.log(`[${file}]`, String(out)); expect(String(out)).toBe(expected); }, 300000); } });