From fbea867d11efe0eba0c1b24ee25b25d1e1bebbfa Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza Date: Fri, 24 Jul 2026 09:59:08 -0300 Subject: [PATCH] test: realign catalog snapshot tests to current deliberate catalog state (#8386) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: realign catalog snapshot tests to current deliberate catalog state Six catalog/snapshot tests drifted behind deliberate catalog changes that were already validated by newer sibling tests. No production code touched; every change aligns a stale snapshot to behavior already validated by newer sibling tests. Root causes (all confirmed against the current code before editing): - tests/unit/providers-constants-split.test.ts: APIKEY_PROVIDERS grew from 187 to 195 entries via #8077 (clova-studio/internlm/ant-ling, regional), #8161 (sarvam/plamo → regional, writer → frontier-labs) and #8170 (typhoon → regional, inception → frontier-labs). Family counts verified to sum to 195 (gateways 60, frontier-labs 24, inference-hosts 28, enterprise-cloud 17, regional 40, specialty-media 26) with no duplicates. Updated the two assertions and extended the changelog comment. - tests/unit/qianfan-provider.test.ts: the expected Baidu Qianfan website URL was the pre-#8128 wenxinworkshop path. #8128/#6271 moved it to https://cloud.baidu.com/product-s/qianfan_home, already locked by the sibling regression test tests/unit/baidu-qianfan-website-urls-6271.test.ts. - tests/unit/t31-t33-t34-t38-model-specs.test.ts and tests/unit/auto-combo-credentialed-model-pool.test.ts: the Antigravity catalog refactor (#8013) retired gemini-3-pro-preview/claude-sonnet-5 and renamed the Gemini 3.5 Flash tiers (low/medium/high -> extra-low/low/gemini-3-flash-agent), confirmed against ANTIGRAVITY_PUBLIC_MODELS and tests/unit/antigravity-retired-public-models.test.ts. Swapped the retired IDs for currently-registered ones (gemini-3.6-flash-high, claude-sonnet-4-6, gemini-3-flash-agent, gemini-3.5-flash-low/extra-low) and moved the wildcard-exclusion prefix test from the now-2-tier "gemini-3.5-*" group to "gemini-3.6-*", which has 3 real tiers today (same >=3 semantics, just pointed at a prefix that still has 3 members). - tests/unit/model-alias-seed.test.ts: getModelInfo("gemini-3.1-pro") now canonicalizes through ALIAS_TO_PROVIDER_ID["agy"] = "antigravity" (#8050), the same pattern already applied to opencode -> opencode-zen. Updated the expected provider id. - tests/unit/video-dashscope.test.ts (deleted, 216 lines): #8266 reorganized the Alibaba video catalog so the flat wan2.7-t2v id no longer exists under the plain "alibaba" provider (only the dated wan2.7-t2v-2026-06-12 does); the flat id now lives only under "qwen-cloud". All 6 tests in the file failed because they built requests against alibaba/wan2.7-t2v, which the new allowlist now rejects with 400 ("unsupported alibaba video model") - verified directly against VIDEO_PROVIDERS in open-sse/config/videoRegistry.ts. Coverage already exists and was confirmed passing pre-deletion in tests/unit/alibaba-video-media.test.ts (including an explicit "Alibaba rejects video models outside its own allowlist" case for this exact id) and tests/unit/qwen-cloud-video-media.test.ts (covers the same id under qwen-cloud). Note: the deleted file's DashScope upstream error-path assertions (401 missing credentials, 502 missing task_id, 502 FAILED status, 504 poll timeout) don't have a byte-for-byte equivalent in the two replacement files, though the shared dashscopeHandler.ts code path they exercise remains covered by several sibling *-media.test.ts files for the happy path and local validation. - tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts: #7892 added /api/vnc-session to the SPAWN_CAPABLE_PREFIXES deny-list (Hard Rules #15/#17 hardening). Bumped the expected length 10 -> 11 and added the entry to the test's named list for documentation. Refs #8013, #8050, #8266, #7892, #8128 * chore(quality): allowlist the video-dashscope.test.ts deletion with its replacements check:test-masking (pr-test-policy CI gate) requires a _deletedWithReplacement entry for any deleted test file, even when the deletion is a verified-legitimate supersession. Documents the same #8266 rationale from the prior commit in the machine-checked allowlist so the deletion is not flagged as unexplained masking. Refs #8266 --- config/quality/test-masking-allowlist.json | 4 + ...spawn-capable-prefixes-client-safe.test.ts | 3 +- ...auto-combo-credentialed-model-pool.test.ts | 26 +-- tests/unit/model-alias-seed.test.ts | 2 +- tests/unit/providers-constants-split.test.ts | 17 +- tests/unit/qianfan-provider.test.ts | 2 +- .../unit/t31-t33-t34-t38-model-specs.test.ts | 8 +- tests/unit/video-dashscope.test.ts | 216 ------------------ 8 files changed, 35 insertions(+), 243 deletions(-) delete mode 100644 tests/unit/video-dashscope.test.ts diff --git a/config/quality/test-masking-allowlist.json b/config/quality/test-masking-allowlist.json index bd31fb2bbe..c44a2a501f 100644 --- a/config/quality/test-masking-allowlist.json +++ b/config/quality/test-masking-allowlist.json @@ -36,6 +36,10 @@ "tests/unit/free-provider-rankings-configured-filter.test.ts": { "replacement": "tests/unit/freeProviderRankings-filters.test.ts", "reason": "v3.8.45 #6251 supersede #6245: a página Free Provider Rankings migrou do toggle client-side 'Configured Only' (#6245, configuredProviderIds no cliente) para filtros server-side configuredOnly/availableOnly (#6251). O teste antigo pinava a implementação removida (7 asserts quebrados contra código que não existe); o replacement cobre o contrato novo com 11 casos (server-side, lib helper). Verificado legítimo — supersessão documentada no CHANGELOG do #6251." + }, + "tests/unit/video-dashscope.test.ts": { + "replacement": "tests/unit/alibaba-video-media.test.ts", + "reason": "v3.8.49 #8266 reorganized the Alibaba video catalog: the flat wan2.7-t2v id moved out of the plain \"alibaba\" provider (which now only exposes the dated wan2.7-t2v-2026-06-12) and lives only under \"qwen-cloud\" today. All 6 tests in the deleted file built requests against alibaba/wan2.7-t2v, which the new allowlist now rejects with HTTP 400 (verified directly against VIDEO_PROVIDERS in open-sse/config/videoRegistry.ts before deletion). Coverage for this exact id already exists and was confirmed green pre-deletion in tests/unit/alibaba-video-media.test.ts (including an explicit \"Alibaba rejects video models outside its own allowlist\" case for alibaba/wan2.7-t2v) and tests/unit/qwen-cloud-video-media.test.ts (covers the same flat id under qwen-cloud/wan2.7-t2v). Verified legitimate, not masking." } }, "tests/unit/catalog-updates-v3x.test.ts": "v3.8.45 #6248: fix(providers) remove deprecated MiMo V2 entries — os 5 asserts removidos pinavam specs de modelos mimo-v2-* que deixaram de existir no catálogo (54→49). Asserts seguem a remoção dos modelos, não enfraquecimento. Verificado legítimo. Prune após v3.8.45 mergear para main.", diff --git a/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts b/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts index 2522c659a1..dc8782b993 100644 --- a/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts +++ b/tests/unit/authz/spawn-capable-prefixes-client-safe.test.ts @@ -80,11 +80,12 @@ test("SPAWN_CAPABLE_PREFIXES is defined in the server-free constants leaf with t "/api/skills/collect/", "/api/headroom/start", "/api/headroom/stop", + "/api/vnc-session", // #7892: spawns Docker containers via child_process.spawn ]) { assert.ok( SPAWN_CAPABLE_PREFIXES.includes(prefix), `SPAWN_CAPABLE_PREFIXES lost the spawn-capable prefix "${prefix}" during extraction` ); } - assert.equal(SPAWN_CAPABLE_PREFIXES.length, 10); + assert.equal(SPAWN_CAPABLE_PREFIXES.length, 11); }); diff --git a/tests/unit/auto-combo-credentialed-model-pool.test.ts b/tests/unit/auto-combo-credentialed-model-pool.test.ts index 1515b6a9b9..149f83c5df 100644 --- a/tests/unit/auto-combo-credentialed-model-pool.test.ts +++ b/tests/unit/auto-combo-credentialed-model-pool.test.ts @@ -86,10 +86,10 @@ test("credentialed providers expose one logical candidate per visible registry m "provider/model candidates must not be duplicated per connection" ); for (const model of [ - "antigravity/claude-sonnet-5", + "antigravity/claude-sonnet-4-6", + "antigravity/gemini-3-flash-agent", "antigravity/gemini-3.5-flash-low", - "antigravity/gemini-3.5-flash-medium", - "antigravity/gemini-3.5-flash-high", + "antigravity/gemini-3.5-flash-extra-low", ]) { assert.ok(modelStrings.includes(model), `${model} should be eligible for auto routing`); } @@ -110,7 +110,7 @@ test("candidate transparency expands a logical model into per-account rows", asy const result = await candidateHandler.getAutoComboCandidates("auto", null); const sonnetRows = result.candidates.filter( (candidate) => - candidate.provider === "antigravity" && candidate.model === "antigravity/claude-sonnet-5" + candidate.provider === "antigravity" && candidate.model === "antigravity/claude-sonnet-4-6" ); assert.deepEqual( @@ -121,36 +121,36 @@ test("candidate transparency expands a logical model into per-account rows", asy }); test("connection model exclusions narrow only that model's account allowlist", async () => { - const { first, second } = await seedConnections(["gemini-3.5-*"]); + const { first, second } = await seedConnections(["gemini-3.6-*"]); const combo = await virtualFactory.createVirtualAutoCombo(undefined); const candidates = antigravityCandidates(combo); const geminiCandidates = candidates.filter((candidate) => - candidate.model.startsWith("antigravity/gemini-3.5-") + candidate.model.startsWith("antigravity/gemini-3.6-") ); - assert.ok(geminiCandidates.length >= 3, "Gemini 3.5 candidates should remain available"); + assert.ok(geminiCandidates.length >= 3, "Gemini 3.6 candidates should remain available"); for (const candidate of geminiCandidates) { assert.deepEqual(candidate.allowedConnectionIds, [second.id]); } - const sonnet = candidates.find((candidate) => candidate.model === "antigravity/claude-sonnet-5"); - assert.ok(sonnet, "Claude Sonnet 5 should remain in the candidate pool"); + const sonnet = candidates.find((candidate) => candidate.model === "antigravity/claude-sonnet-4-6"); + assert.ok(sonnet, "Claude Sonnet 4.6 should remain in the candidate pool"); assert.deepEqual([...(sonnet.allowedConnectionIds ?? [])].sort(), [first.id, second.id].sort()); }); test("hiding the first registry model does not drop the credentialed provider", async () => { await seedConnections(); - modelsDb.setModelIsHidden("antigravity", "claude-sonnet-5", true); + modelsDb.setModelIsHidden("antigravity", "claude-sonnet-4-6", true); const combo = await virtualFactory.createVirtualAutoCombo(undefined); const modelStrings = antigravityCandidates(combo).map((candidate) => candidate.model); - assert.equal(modelStrings.includes("antigravity/claude-sonnet-5"), false); + assert.equal(modelStrings.includes("antigravity/claude-sonnet-4-6"), false); for (const model of [ + "antigravity/gemini-3-flash-agent", "antigravity/gemini-3.5-flash-low", - "antigravity/gemini-3.5-flash-medium", - "antigravity/gemini-3.5-flash-high", + "antigravity/gemini-3.5-flash-extra-low", ]) { assert.ok(modelStrings.includes(model), `${model} should remain after Sonnet is hidden`); } diff --git a/tests/unit/model-alias-seed.test.ts b/tests/unit/model-alias-seed.test.ts index e706d565a2..d2f3f67008 100644 --- a/tests/unit/model-alias-seed.test.ts +++ b/tests/unit/model-alias-seed.test.ts @@ -43,7 +43,7 @@ test("default model alias seed writes missing aliases and is idempotent", async const routed = await sseModelService.getModelInfo("gemini-3.1-pro"); assert.deepEqual(routed, { - provider: "agy", + provider: "antigravity", model: "gemini-pro-agent", extendedContext: false, }); diff --git a/tests/unit/providers-constants-split.test.ts b/tests/unit/providers-constants-split.test.ts index c1918b90e6..23cfb7f871 100644 --- a/tests/unit/providers-constants-split.test.ts +++ b/tests/unit/providers-constants-split.test.ts @@ -1,7 +1,7 @@ // Characterization of the providers.ts catalog split (god-file decomposition): the host became a // barrel that re-exports 10 data catalogs now living under constants/providers/*, and APIKEY is // merged from 6 semantic family files (apikey/.ts). Locks: the public surface (every catalog -// + helpers still exported), the spread-merge integrity (187 APIKEY entries, no loss/dup), and that +// + helpers still exported), the spread-merge integrity (195 APIKEY entries, no loss/dup), and that // load-time Zod validation still runs. Pure-data move → behavior must be identical. // Count was 171 before obsolete provider removals (PR #6675: glhf/kluster/cablyai/inclusionai etc., // 171->167) plus #6126 (ClinePass dual-auth): the API-key-only APIKEY_PROVIDERS_GATEWAYS entry was @@ -12,7 +12,10 @@ // 5 new sub-path entries — g4f-groq/g4f-gemini/g4f-pollinations/g4f-ollama/g4f-nvidia) brought it // to 177, then 2 more provider additions in the v3.8.49 cycle brought it to 179, the free-catalog // expansion (#7840, navy) to 180, the Alibaba/Qwen Cloud regional additions (#7882) to 182, and -// #7887 (5 free-tier providers: ainative/aion/sealion/routeway/nara) to 187. +// #7887 (5 free-tier providers: ainative/aion/sealion/routeway/nara) to 187, then #8077 (clova-studio/ +// internlm/ant-ling, all regional) to 190, then #8161 (sarvam/writer/plamo — writer in frontier-labs, +// sarvam+plamo in regional) to 193, then #8170 (inception/typhoon — inception in frontier-labs, +// typhoon in regional) to 195. import { test } from "node:test"; import assert from "node:assert/strict"; @@ -41,12 +44,12 @@ test("barrel still exports every catalog + key helpers", () => { } }); -test("APIKEY_PROVIDERS merges the 6 family files into 187 entries (no loss / no dup)", async () => { +test("APIKEY_PROVIDERS merges the 6 family files into 195 entries (no loss / no dup)", async () => { const keys = Object.keys((P as Record).APIKEY_PROVIDERS); - assert.equal(keys.length, 187); - assert.equal(new Set(keys).size, 187, "duplicate keys after spread-merge"); + assert.equal(keys.length, 195); + assert.equal(new Set(keys).size, 195, "duplicate keys after spread-merge"); // the merged object's entry-count equals the sum of the 6 semantic family files; families are a - // strict partition (every provider in exactly one), so the sum must be exactly 187. + // strict partition (every provider in exactly one), so the sum must be exactly 195. const families: [string, string][] = [ ["gateways", "APIKEY_PROVIDERS_GATEWAYS"], ["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"], @@ -66,7 +69,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 187 entries (no loss / no seen.add(k); } } - assert.equal(famTotal, 187, "families must partition all 187 providers"); + assert.equal(famTotal, 195, "families must partition all 195 providers"); }); test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => { diff --git a/tests/unit/qianfan-provider.test.ts b/tests/unit/qianfan-provider.test.ts index 3ed73cec05..16633705a5 100644 --- a/tests/unit/qianfan-provider.test.ts +++ b/tests/unit/qianfan-provider.test.ts @@ -26,7 +26,7 @@ test("qianfan registers Baidu ERNIE as an OpenAI-compatible API key provider", ( assert.ok(APIKEY_PROVIDERS.qianfan, "qianfan should be visible in API key providers"); assert.equal(APIKEY_PROVIDERS.qianfan.name, "Baidu Qianfan"); - assert.equal(APIKEY_PROVIDERS.qianfan.website, "https://cloud.baidu.com/product/wenxinworkshop"); + assert.equal(APIKEY_PROVIDERS.qianfan.website, "https://cloud.baidu.com/product-s/qianfan_home"); assert.equal(APIKEY_PROVIDERS.qianfan.passthroughModels, undefined); assert.equal(PROVIDERS.qianfan.baseUrl, registryEntry.baseUrl); diff --git a/tests/unit/t31-t33-t34-t38-model-specs.test.ts b/tests/unit/t31-t33-t34-t38-model-specs.test.ts index c27b301f68..998a2d6157 100644 --- a/tests/unit/t31-t33-t34-t38-model-specs.test.ts +++ b/tests/unit/t31-t33-t34-t38-model-specs.test.ts @@ -15,11 +15,11 @@ const { capThinkingBudget, } = await import("../../src/shared/constants/modelSpecs.ts"); -test("T31: antigravity static catalog exposes client-visible Gemini preview IDs", () => { - // Antigravity exposes preview aliases to clients even though the upstream - // still accepts its internal model identifiers. +test("T31: antigravity static catalog exposes client-visible Gemini model IDs", () => { + // Antigravity's static catalog surfaces the live, client-visible model IDs returned by + // the model selector (#8013 refactor: gemini-3.6-flash-high is the current defaultAgentModelId). const staticIds = (getStaticModelsForProvider("antigravity") || []).map((m) => m.id); - assert.ok(staticIds.includes("gemini-3-pro-preview")); + assert.ok(staticIds.includes("gemini-3.6-flash-high")); // #3303 (agy parity, discussion #3184): the Gemini + Claude budget tiers ARE // client-visible on the Antigravity OAuth backend (Claude was never removed). assert.ok(staticIds.includes("gemini-3.1-pro-low")); diff --git a/tests/unit/video-dashscope.test.ts b/tests/unit/video-dashscope.test.ts deleted file mode 100644 index 907be50c21..0000000000 --- a/tests/unit/video-dashscope.test.ts +++ /dev/null @@ -1,216 +0,0 @@ -import test from "node:test"; -import assert from "node:assert/strict"; -import { mkdtempSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-video-dashscope-")); - -const { handleVideoGeneration } = await import("../../open-sse/handlers/videoGeneration.ts"); -const { VIDEO_PROVIDERS } = await import("../../open-sse/config/videoRegistry.ts"); - -// Makes poll-interval waits resolve instantly so tests don't sleep. -function immediateTimeout(callback, _ms, ...args) { - if (typeof callback === "function") callback(...args); - return 0; -} - -const CREATE_URL = - "https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/video-generation/video-synthesis"; -const POLL_URL_PREFIX = "https://dashscope-intl.aliyuncs.com/api/v1/tasks/"; - -function jsonResponse(body, status = 200) { - return new Response(JSON.stringify(body), { - status, - headers: { "content-type": "application/json" }, - }); -} - -test("VIDEO_PROVIDERS exposes the alibaba dashscope-video entry", () => { - assert.ok(VIDEO_PROVIDERS.alibaba, "alibaba video provider is registered"); - assert.equal(VIDEO_PROVIDERS.alibaba.format, "dashscope-video"); - assert.ok( - VIDEO_PROVIDERS.alibaba.models.some((m) => m.id === "wan2.7-t2v"), - "wan2.7-t2v is listed" - ); -}); - -test("handleVideoGeneration creates + polls a DashScope task and returns mp4 URL", async () => { - const originalFetch = globalThis.fetch; - const originalSetTimeout = globalThis.setTimeout; - let createRequest; - - globalThis.setTimeout = immediateTimeout; - globalThis.fetch = async (url, options = {}) => { - const stringUrl = String(url); - - if (stringUrl === CREATE_URL) { - createRequest = { - url: stringUrl, - headers: options.headers, - body: JSON.parse(String(options.body || "{}")), - }; - return jsonResponse({ - output: { task_id: "ds-task-1", task_status: "PENDING" }, - request_id: "req-1", - }); - } - - if (stringUrl.startsWith(POLL_URL_PREFIX)) { - return jsonResponse({ - output: { - task_status: "SUCCEEDED", - video_url: "https://dashscope-cdn.example.com/wan-out.mp4", - }, - request_id: "req-2", - usage: { video_count: 1 }, - }); - } - - throw new Error(`Unexpected URL: ${stringUrl}`); - }; - - try { - const result = await handleVideoGeneration({ - body: { - model: "alibaba/wan2.7-t2v", - prompt: "a neon city in the rain", - negative_prompt: "blurry", - aspect_ratio: "16:9", - duration: 5, - }, - credentials: { apiKey: "dashscope-key" }, - log: null, - }); - - // Create request shape - assert.equal(createRequest.headers["X-DashScope-Async"], "enable"); - assert.equal(createRequest.headers["Authorization"], "Bearer dashscope-key"); - assert.equal(createRequest.body.model, "wan2.7-t2v"); - assert.equal(createRequest.body.input.prompt, "a neon city in the rain"); - assert.equal(createRequest.body.input.negative_prompt, "blurry"); - // aspect_ratio "16:9" → DashScope size "1280*720" - assert.equal(createRequest.body.parameters.size, "1280*720"); - assert.equal(createRequest.body.parameters.duration, 5); - - // Response shape - assert.equal(result.success, true); - assert.equal(result.data.data[0].url, "https://dashscope-cdn.example.com/wan-out.mp4"); - assert.equal(result.data.data[0].format, "mp4"); - } finally { - globalThis.fetch = originalFetch; - globalThis.setTimeout = originalSetTimeout; - } -}); - -test("handleVideoGeneration rejects DashScope requests without credentials", async () => { - const result = await handleVideoGeneration({ - body: { model: "alibaba/wan2.7-t2v", prompt: "x" }, - credentials: null, - log: null, - }); - - assert.equal(result.success, false); - assert.equal(result.status, 401); - assert.match(result.error, /DashScope API key is required/); -}); - -test("handleVideoGeneration surfaces a 502 when DashScope returns no task_id", async () => { - const originalFetch = globalThis.fetch; - globalThis.fetch = async () => jsonResponse({ message: "Invalid API key", request_id: "x" }, 401); - - try { - const result = await handleVideoGeneration({ - body: { model: "alibaba/wan2.7-t2v", prompt: "x" }, - credentials: { apiKey: "bad-key" }, - log: null, - }); - - assert.equal(result.success, false); - assert.equal(result.status, 502); - assert.equal(result.error, "Invalid API key"); - } finally { - globalThis.fetch = originalFetch; - } -}); - -test("handleVideoGeneration returns 502 when the DashScope task FAILED", async () => { - const originalFetch = globalThis.fetch; - const originalSetTimeout = globalThis.setTimeout; - globalThis.setTimeout = immediateTimeout; - - globalThis.fetch = async (url) => { - const stringUrl = String(url); - if (stringUrl === CREATE_URL) { - return jsonResponse({ output: { task_id: "ds-fail", task_status: "PENDING" } }); - } - if (stringUrl.startsWith(POLL_URL_PREFIX)) { - return jsonResponse({ - output: { task_status: "FAILED", message: "content policy violation" }, - }); - } - throw new Error(`Unexpected URL: ${stringUrl}`); - }; - - try { - const result = await handleVideoGeneration({ - body: { model: "alibaba/wan2.7-t2v", prompt: "x" }, - credentials: { apiKey: "dashscope-key" }, - log: null, - }); - - assert.equal(result.success, false); - assert.equal(result.status, 502); - assert.equal(result.error, "content policy violation"); - } finally { - globalThis.fetch = originalFetch; - globalThis.setTimeout = originalSetTimeout; - } -}); - -test("handleVideoGeneration returns 504 when the DashScope task never completes", async () => { - const originalFetch = globalThis.fetch; - const originalSetTimeout = globalThis.setTimeout; - const originalNow = Date.now; - globalThis.setTimeout = immediateTimeout; - - // Deterministic clock: start at 1000, allow exactly one poll iteration, then - // jump past the deadline so the while-loop exits on the next check. - let nowCalls = 0; - Date.now = () => { - nowCalls += 1; - return nowCalls === 1 ? 1000 : nowCalls === 2 ? 2000 : 1_000_000; - }; - - globalThis.fetch = async (url) => { - const stringUrl = String(url); - if (stringUrl === CREATE_URL) { - return jsonResponse({ output: { task_id: "ds-stuck", task_status: "PENDING" } }); - } - if (stringUrl.startsWith(POLL_URL_PREFIX)) { - return jsonResponse({ output: { task_status: "RUNNING" } }); - } - throw new Error(`Unexpected URL: ${stringUrl}`); - }; - - try { - const result = await handleVideoGeneration({ - body: { - model: "alibaba/wan2.7-t2v", - prompt: "x", - timeout_ms: 5000, - poll_interval_ms: 100, - }, - credentials: { apiKey: "dashscope-key" }, - log: null, - }); - - assert.equal(result.success, false); - assert.equal(result.status, 504); - assert.match(result.error, /timed out/); - } finally { - globalThis.fetch = originalFetch; - globalThis.setTimeout = originalSetTimeout; - Date.now = originalNow; - } -});