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 } }; const mocks: Record = { "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([]); });