import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; import { mkdir, rm, stat } from "node:fs/promises"; import { resolve } from "node:path"; import { dbRun } from "../server/db.mjs"; const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787"; const projectRoot = resolve(import.meta.dirname, ".."); const scope = { "x-organization-id": "org-studio-lab", "x-workspace-id": "ws-local-aidrama", "x-project-id": "thunder-mouth" }; const rootRelative = "storage/compositions/smoke-compose-evidence"; const rootAbsolute = resolve(projectRoot, rootRelative); const clips = [`${rootRelative}/a.mp4`, `${rootRelative}/b.mp4`]; const outputPath = `${rootRelative}/final.mp4`; async function request(path, options = {}) { const response = await fetch(`${api}${path}`, { ...options, headers: { "content-type": "application/json", ...(options.headers || {}) } }); const payload = await response.json().catch(() => ({})); return { response, payload }; } const login = await request("/api/auth/login", { method: "POST", body: JSON.stringify({ email: "producer@local.test", password: "Demo@123456" }) }); assert.equal(login.response.ok, true, "compose smoke login must pass"); const headers = { authorization: `Bearer ${login.payload.session.token}`, ...scope }; let compositionId = ""; try { await mkdir(rootAbsolute, { recursive: true }); for (const [index, clip] of clips.entries()) { execFileSync("ffmpeg", ["-y", "-v", "error", "-f", "lavfi", "-i", `color=c=${index ? "0x3d7f54" : "0x7f4b2f"}:s=320x568:d=0.6`, "-c:v", "libx264", "-pix_fmt", "yuv420p", resolve(projectRoot, clip)], { stdio: "pipe" }); } const composed = await request("/api/production/compose", { method: "POST", headers, body: JSON.stringify({ version: "smoke-compose-evidence", clips, outputPath }) }); assert.equal(composed.response.ok, true, `compose failed: ${composed.payload.detail || ""}`); compositionId = composed.payload.composition.id; assert.equal(composed.payload.composition.status, "completed", "FFmpeg 合成必须完成"); assert.equal(composed.payload.composition.result.artifact.status, "inspected", "合成结果必须登记媒体证据"); assert.ok(composed.payload.composition.result.artifact.sha256, "合成结果必须登记 SHA-256"); assert.ok(composed.payload.composition.result.artifact.last_frame_path, "合成结果必须提取实际末帧"); await stat(resolve(projectRoot, outputPath)); console.log(`compose evidence smoke passed: ${outputPath} -> ${composed.payload.composition.result.artifact.last_frame_path}`); } finally { if (compositionId) { dbRun("DELETE FROM media_artifacts WHERE composition_id = ?", [compositionId]); dbRun("DELETE FROM media_compositions WHERE id = ?", [compositionId]); } await rm(rootAbsolute, { recursive: true, force: true }); }