ai-chatbot/tests/routes/canvas-text-detect.test.ts

76 lines
2.9 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { resolve } from "node:path";
import { runInNewContext } from "node:vm";
import ts from "typescript";
const filename = resolve("app/api/canvas/text-detect/route.ts");
const requireModule = createRequire(filename);
const source = ts.transpileModule(readFileSync(filename, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, esModuleInterop: true },
}).outputText;
function createRoute(params: {
enableOcr: boolean;
recognize: () => unknown;
qwen: () => unknown;
fetchResult: () => Response;
}) {
const module = { exports: {} as { POST: (request: Request) => Promise<Response> } };
const mocks: Record<string, unknown> = {
"next/server": { NextResponse: Response },
"drizzle-orm": { and() {}, inArray() {}, eq() {} },
"@/app/(auth)/auth": { auth: async () => ({ user: { id: "signed-in" } }) },
"@/lib/ai/qwen3-vl": { callQwen3Vl: params.qwen },
"@/lib/ai/vip98k-runtime": {
getVip98kRuntimeProviders: async () => [{ id: "98k" }],
executeConfiguredVip98kTask: params.recognize,
},
"@/lib/db": {
db: { query: { aiModel: { findFirst: async () => params.enableOcr ? { providerId: "98k" } : undefined } } },
},
"@/lib/db/schema": { aiModel: {} },
"@/lib/observability/logger": { createLogger: () => ({ debug() {}, warn() {}, error() {} }) },
};
runInNewContext(source, {
exports: module.exports,
require: (id: string) => mocks[id] ?? requireModule(id),
fetch: params.fetchResult,
AbortSignal,
});
return module.exports.POST;
}
const request = () => new Request("http://test/api/canvas/text-detect", {
method: "POST",
body: JSON.stringify({ imageUrl: "https://assets.test/item.png" }),
});
test("configured OCR returns numbered product labels without sending a second model request", async () => {
const post = createRoute({
enableOcr: true,
recognize: () => ({ outputUrls: ["https://assets.test/result"] }),
fetchResult: () => new Response("\uFEFFSKU 731\r\n60 CM", { headers: { "Content-Type": "text/plain" } }),
qwen: () => { throw new Error("The working OCR tool must not call Qwen"); },
});
const response = await post(request());
expect(response.status).toBe(200);
expect(await response.json()).toEqual({
success: true,
data: { model: "ocr", items: [{ id: "1", text: "SKU 731" }, { id: "2", text: "60 CM" }] },
});
});
test("a valid empty OCR response shows no editable text", async () => {
const post = createRoute({
enableOcr: false,
recognize: () => { throw new Error("Disabled OCR must not submit"); },
fetchResult: () => { throw new Error("No OCR file expected"); },
qwen: () => ({ model: "qwen", text: '{"items":[]}' }),
});
const response = await post(request());
expect(response.status).toBe(200);
expect((await response.json()).data.items).toEqual([]);
});