From 34d98a9bec0a3830db95f19d91490ac21b1d8dd2 Mon Sep 17 00:00:00 2001 From: chaewon-huh Date: Fri, 3 Jul 2026 18:30:47 +0900 Subject: [PATCH] Ignore API resource URLs during media downloads --- src/lib/download.ts | 16 ++++++++- test/download.test.ts | 81 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/lib/download.ts b/src/lib/download.ts index d4fc4df..9b96cd8 100644 --- a/src/lib/download.ts +++ b/src/lib/download.ts @@ -117,7 +117,12 @@ function visit(value: unknown, filename: string | undefined, refs: MediaRef[]) { "title", ]) ?? filename; for (const [key, entry] of Object.entries(record)) { - if (typeof entry === "string" && isDownloadableUrlKey(key) && isHttpsUrl(entry)) { + if ( + typeof entry === "string" && + isDownloadableUrlKey(key) && + isHttpsUrl(entry) && + !isSumeApiResourceUrl(entry) + ) { refs.push({ url: entry, filename: ownFilename }); } else { visit(entry, ownFilename, refs); @@ -159,6 +164,15 @@ function isHttpsUrl(value: string) { } } +function isSumeApiResourceUrl(value: string) { + try { + const url = new URL(value); + return url.hostname === "api.sume.com" && url.pathname.startsWith("/v1/"); + } catch { + return false; + } +} + function firstString(record: Record, keys: string[]) { for (const key of keys) { const value = record[key]; diff --git a/test/download.test.ts b/test/download.test.ts index 531821c..8089a14 100644 --- a/test/download.test.ts +++ b/test/download.test.ts @@ -25,6 +25,87 @@ describe("media downloads", () => { ]); }); + it("ignores API resource URLs when a final media artifact is downloadable", async () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "sume-download-")); + try { + const requested: string[] = []; + const result = await downloadMediaFromValue( + { + data: { + result: { + avatar_video: { + id: "avatar_video_123", + url: "https://api.sume.com/v1/avatar-videos/avatar_video_123", + }, + artifacts: [ + { + content_type: "video/mp4", + filename: "avatar-video.mp4", + url: "https://media.sume.com/artifacts/avatar-video.mp4", + }, + ], + }, + }, + }, + { + outputDir: tempDir, + fetchImpl: (async (input: RequestInfo | URL) => { + const url = String(input); + requested.push(url); + if (url.includes("/v1/avatar-videos/")) { + return new Response("unauthorized", { status: 401 }); + } + return new Response(new Uint8Array([1, 2, 3]), { + headers: { "content-type": "video/mp4" }, + }); + }) as typeof fetch, + }, + ); + expect(requested).toEqual(["https://media.sume.com/artifacts/avatar-video.mp4"]); + expect(result).toMatchObject({ + source_count: 1, + failed: [], + downloaded: [ + expect.objectContaining({ + filename: "avatar-video.mp4", + content_type: "video/mp4", + }), + ], + }); + expect(JSON.stringify(result)).not.toContain("https://"); + } finally { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + + it("reports a real download failure when every media candidate fails", async () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "sume-download-")); + try { + const result = await downloadMediaFromValue( + { + data: { + result: { + video_url: "https://media.sume.com/missing.mp4", + }, + }, + }, + { + outputDir: tempDir, + fetchImpl: (async () => + new Response("not found", { status: 404 })) as typeof fetch, + }, + ); + expect(result).toMatchObject({ + source_count: 1, + downloaded: [], + failed: [{ reason: "HTTP 404" }], + }); + expect(JSON.stringify(result)).not.toContain("https://"); + } finally { + fs.rmSync(tempDir, { recursive: true, force: true }); + } + }); + it("downloads files without returning remote URLs", async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "sume-download-")); try {