76 lines
2.9 KiB
TypeScript
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([]);
|
|
});
|