import { test, expect } from "@playwright/test"; import { serverLoader } from "../helpers/canvas-download-server-loader"; const config = { type: "qiniu-kodo", isEnabled: true, publicUrl: "https://cdn.example" }; test("original downloads use the archived JPEG rather than the prepared PNG and transfer only HEAD metadata", async () => { const calls: Array<{ url: string; method: string }> = []; const loader = serverLoader({ "@/lib/db/queries": { getDefaultStorageConfig: async () => config }, "./image-archive-db": { findImageArchiveDownloadTask: async () => ({ targetUrl: "https://cdn.example/original.jpg", metadata: { downloadAsset: { status: "ready", url: "https://cdn.example/converted.png", contentType: "image/png" }, } }) }, fetch: async (url: URL, options: { method: string }) => { calls.push({ url: url.toString(), method: options.method }); return { ok: true, headers: new Headers({ "content-type": "image/jpeg", "content-length": "12345" }), arrayBuffer() { throw new Error("Image bytes must bypass the application"); } }; }, }); const result = await loader("lib/storage/original-image-download.ts").resolveOriginalCdnDownload("https://upstream.example/temporary.png", "产品.png", new AbortController().signal); expect(calls).toEqual([{ url: "https://cdn.example/original.jpg", method: "HEAD" }]); expect(result).toMatchObject({ delivery: "cdn", filename: "产品.jpg", bytes: 12345 }); expect(new URL(result.downloadUrl).pathname).toBe("/original.jpg"); }); test("signed original URLs retain the byte-preserving proxy without changing their signature", async () => { const module = serverLoader({ "@/lib/db/queries": { getDefaultStorageConfig: async () => config }, "./image-archive-db": { findImageArchiveDownloadTask: async () => null }, fetch() { throw new Error("Do not add filename parameters to a signed URL"); }, })("lib/storage/original-image-download.ts"); const result = await module.resolveOriginalCdnDownload("https://cdn.example/source.jpg?token=signature%2Fvalue&e=123", "image.jpg", new AbortController().signal); expect(result).toBeNull(); }); test("existing CDN objects bypass the archive scan and retain the MIME-derived format", async () => { const module = serverLoader({ "@/lib/db/queries": { getDefaultStorageConfig: async () => config }, "./image-archive-db": { findImageArchiveDownloadTask() { throw new Error("An existing CDN object needs no archive scan"); } }, fetch: async () => ({ ok: true, headers: new Headers({ "content-type": "image/webp" }) }), })("lib/storage/original-image-download.ts"); const result = await module.resolveOriginalCdnDownload("https://cdn.example/source.png", "source.png", new AbortController().signal); expect(result.filename).toBe("source.webp"); expect(new URL(result.downloadUrl).pathname).toBe("/source.png"); }); test("temporary third-party URLs are never checked with HEAD when no archive is ready", async () => { const module = serverLoader({ "@/lib/db/queries": { getDefaultStorageConfig: async () => config }, "./image-archive-db": { findImageArchiveDownloadTask: async () => null }, fetch() { throw new Error("The upstream file service does not support HEAD"); }, })("lib/storage/original-image-download.ts"); expect(await module.resolveOriginalCdnDownload("https://upstream.example/image.png", "image", new AbortController().signal)).toBeNull(); }); test("original download metadata reports missing sources with cause, advice and a feedback ID", async () => { const route = serverLoader({ "next/server": { NextResponse: Response }, "@/lib/storage/original-image-download": { resolveOriginalCdnDownload: async () => { throw new Error("HTTP 404"); } }, })("app/api/image-proxy/route.ts"); const request = { nextUrl: new URL("https://app.example/api/image-proxy?url=https%3A%2F%2Fcdn.example%2Fmissing.jpg&original=1&download=resolve"), signal: new AbortController().signal, headers: new Headers() }; const response = await route.GET(request); const data = await response.json(); expect(response.status).toBe(404); expect(data.failure).toMatchObject({ code: "SOURCE_NOT_FOUND", retryable: false }); expect(data.failure.recommendation).toBeTruthy(); expect(data.error).toContain(data.requestId); }); function browserMocks() { const frames: any[] = []; const links: any[] = []; return { frames, links, window: { location: { origin: "https://app.example" }, setTimeout() {} }, document: { createElement: (tag: string) => ({ tag, click() { links.push(this); }, remove() {} }), body: { appendChild: (el: any) => { if (el.tag === "iframe") frames.push(el); }, removeChild() {} } }, }; } test("chat original downloads use a native CDN download without fetching the image through CORS", async () => { const dom = browserMocks(); const calls: string[] = []; const client = serverLoader({ ...dom, fetch: async (url: string) => { calls.push(url); return Response.json({ delivery: "cdn", downloadUrl: "https://cdn.example/image.jpg?attname=original.jpg", filename: "original.jpg", bytes: 5000000 }); } })("lib/client/canvas-image-download.ts"); const result = await client.downloadOriginalImage({ url: "https://cdn.example/image.jpg" }); expect(result).toEqual({ filename: "original.jpg", bytes: 5000000 }); expect(calls).toHaveLength(1); expect(new URL(calls[0], "https://app.example").searchParams.get("download")).toBe("resolve"); expect(dom.frames[0].src).toBe("https://cdn.example/image.jpg?attname=original.jpg"); expect(dom.links).toHaveLength(0); }); test("original fallback preserves JPEG bytes and the MIME-derived filename", async () => { const dom = browserMocks(); const bytes = Buffer.from([255, 216, 255, 224, 0, 16, 74, 70, 73, 70, 255, 217]); let calls = 0; const client = serverLoader({ ...dom, fetch: async () => ++calls === 1 ? Response.json({ delivery: "proxy" }) : new Response(bytes, { headers: { "content-type": "image/jpeg" } }) })("lib/client/canvas-image-download.ts"); expect(await client.downloadOriginalImage({ url: "https://external.example/image.png", name: "original.png" })).toEqual({ filename: "original.jpg", bytes: bytes.length }); expect(calls).toBe(2); const actual = await fetch(dom.links[0].href).then(r=>r.arrayBuffer()); expect(Buffer.from(actual).equals(bytes)).toBe(true); URL.revokeObjectURL(dom.links[0].href); }); test("original downloads reject processed references before network access", async () => { const client = serverLoader({ ...browserMocks(), fetch() { throw new Error("Must not fetch a processed image as an original"); } })("lib/client/canvas-image-download.ts"); await expect(client.downloadOriginalImage({ url: "https://cdn.example/image.jpg?imageView2/2/w/320&token=signed" })).rejects.toThrow("无法确认原图"); }); test("chat downloads display the server cause and advice without losing its feedback ID", async () => { const failure = { code: "SOURCE_FORBIDDEN", message: "图片源站拒绝访问原图。", recommendation: "请联系管理员检查访问权限。", status: 403, retryable: false }; const client = serverLoader({ ...browserMocks(), fetch: async () => Response.json({ failure, requestId: "original-feedback" }, { status: 403 }) })("lib/client/canvas-image-download.ts"); let error: unknown; try { await client.downloadOriginalImage({ url: "https://cdn.example/image.jpg" }); } catch(e) { error=e; } expect(client.getCanvasImageDownloadErrorMessage(error)).toContain("下载编号:original-feedback"); expect(client.getCanvasImageDownloadErrorMessage(error)).toContain(failure.recommendation); });