ai-chatbot/scripts/qa/video-canvas-production-gate.cjs

62 lines
4.5 KiB
JavaScript

// Run against a temporary localhost Next process with an intentionally unavailable
// SMART_EDIT_RENDER_URL. Creates and deletes its own canvas; no model work starts.
const assert = require("node:assert/strict");
const { randomUUID } = require("node:crypto");
const base = process.env.CANVAS_QA_URL || "http://127.0.0.1:3141";
assert(["localhost", "127.0.0.1"].includes(new URL(base).hostname));
const cookies = new Map();
async function request(route, body, method = body ? "POST" : "GET") {
const response = await fetch(base + route, { method, redirect: "manual",
headers: { "content-type": "application/json", cookie: [...cookies].map(([key, value]) => `${key}=${value}`).join("; ") },
body: body ? JSON.stringify(body) : undefined, signal: AbortSignal.timeout(20000) });
for (const cookie of response.headers.getSetCookie()) {
const part = cookie.split(";")[0], equal = part.indexOf("=");
cookies.set(part.slice(0, equal), part.slice(equal + 1));
}
return { status: response.status, body: await response.json().catch(() => null) };
}
(async () => {
let projectId;
try {
const auth = await request("/api/auth/guest?redirectUrl=" + encodeURIComponent(base + "/video-canvas"));
assert([302, 303, 307].includes(auth.status), `guest login ${auth.status}`);
const project = await request("/api/canvas", { title: "QA 服务故障零生成验收", workspaceType: "video" });
assert.equal(project.status, 200);
projectId = project.body.data.id;
const node = { id: "qa-render", title: "QA", kind: "media", inputType: "video", x: 0, y: 0, width: 400, height: 300 };
const asset = { id: "asset", name: "QA", url: "https://example.com/qa.mp4", duration: 2e6,
width: 1280, height: 720, hasAudio: false, hash: "a".repeat(64), subtitles: [] };
const plan = { version: 1, title: "QA", assets: [asset], clips: [{ id: "clip", assetId: "asset", sourceIn: 0, sourceOut: 1e6 }],
ratio: "16:9", resolution: "720p", transition: 0, subtitles: false };
const render = { requestId: randomUUID(), projectId, kind: "smart-edit", node, params: { operation: "render", plan }, edges: [] };
const result = await request("/api/canvas/generation-tasks", render);
assert.equal(result.status, 503, JSON.stringify(result));
assert.equal(result.body.code, "CANVAS_SERVICE_UNAVAILABLE");
const runId = randomUUID(), groupId = "qa-group";
const image = { requestId: randomUUID(), projectId, kind: "image", node: { ...node, id: "qa-image", inputType: "image" },
params: { prompt: "Must never run while renderer is down" }, workflow: { runId, groupId, inputs: [] }, edges: [] };
const groupedRender = { ...render, requestId: randomUUID(), workflow: { runId, groupId, inputs: [] } };
const batch = await request("/api/canvas/group-runs", [image, groupedRender]);
assert.equal(batch.status, 503, JSON.stringify(batch));
assert.equal(batch.body.code, "CANVAS_SERVICE_UNAVAILABLE");
const tasks = await request("/api/canvas/generation-tasks?projectId=" + projectId);
assert.equal(tasks.status, 200);
assert.equal(tasks.body.tasks.length, 0, "failure must not create any upstream paid tasks");
if (process.env.CANVAS_QA_STALE_VIDEO_MODEL) {
const video = { requestId: randomUUID(), projectId, kind: "video", node: { ...node, id: "qa-video" },
params: { prompt: "Must never run with an invalid published contract", preferredModelRef: process.env.CANVAS_QA_STALE_VIDEO_MODEL,
ratio: "9:16", resolution: "720p", duration: 4 }, workflow: { runId: randomUUID(), groupId: "qa-stale-video", inputs: [] }, edges: [] };
const stale = await request("/api/canvas/group-runs", [{ ...image, requestId: randomUUID(), workflow: video.workflow }, video]);
assert.equal(stale.status, 503, JSON.stringify(stale));
assert.equal(stale.body.code, "CANVAS_SERVICE_UNAVAILABLE");
assert.match(stale.body.message || stale.body.error || "", /视频模型线路检查未通过/);
const after = await request("/api/canvas/generation-tasks?projectId=" + projectId);
assert.equal(after.body.tasks.length, 0, "stale video specs must not start paid keyframes");
console.log("PASS: live supplier contract rejects stale video specs before paid keyframes start");
}
console.log("PASS: real authentication + database + HTTP; single and group outage rejection; zero generation tasks");
} finally {
if (projectId) assert.equal((await request("/api/canvas/" + projectId, undefined, "DELETE")).status, 200);
}
})().catch(error => { console.error(error.message); process.exitCode = 1; });