mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
test: realign catalog snapshot tests to current deliberate catalog state (#8386)
* 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
This commit is contained in:
committed by
GitHub
parent
875de01de7
commit
fbea867d11
@@ -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.",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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`);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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/<family>.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<string, object>).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", () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user