import assert from "node:assert/strict"; import { createServer } from "node:http"; import { execFileSync } from "node:child_process"; import { mkdir, rm } from "node:fs/promises"; import { resolve } from "node:path"; import { dbGet, 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 fixtureRelative = "storage/jobs/api-media-smoke/fixture.mp4"; const fixtureAbsolute = resolve(projectRoot, fixtureRelative); 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, "media API smoke login must pass"); const headers = { authorization: `Bearer ${login.payload.session.token}`, ...scope }; const connector = dbGet("SELECT * FROM model_connectors WHERE id = 'owned-i2v'"); const originalConnector = { endpoint: connector.endpoint, status: connector.status, errorMessage: connector.error_message }; const server = createServer(async (req, res) => { if (req.method !== "POST") { res.writeHead(405); res.end(); return; } const jobId = req.headers["x-ai-drama-job-id"]; assert.ok(jobId, "adapter request must carry job id"); const outputRelative = `storage/jobs/${jobId}/output.mp4`; await mkdir(resolve(projectRoot, `storage/jobs/${jobId}`), { recursive: true }); execFileSync("cp", [fixtureAbsolute, resolve(projectRoot, outputRelative)]); res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ outputPath: outputRelative, mimeType: "video/mp4", provider: "local-smoke" })); }); await new Promise((resolveListen) => server.listen(0, "127.0.0.1", resolveListen)); const address = server.address(); const endpoint = `http://127.0.0.1:${address.port}/generate`; const createdJobIds = []; try { await mkdir(resolve(projectRoot, "storage/jobs/api-media-smoke"), { recursive: true }); execFileSync("ffmpeg", ["-y", "-v", "error", "-f", "lavfi", "-i", "color=c=0x8b3d59:s=320x568:d=1", "-c:v", "libx264", "-pix_fmt", "yuv420p", fixtureAbsolute], { stdio: "pipe" }); dbRun("UPDATE model_connectors SET endpoint = ?, status = 'ready', error_message = '', updated_at = ? WHERE id = 'owned-i2v'", [endpoint, new Date().toISOString()]); const created = await request("/api/jobs", { method: "POST", headers, body: JSON.stringify({ adapter: "owned-i2v", kind: "视频片段", shotId: "shot-01", output: "storage/jobs/api-media-smoke/request-output.mp4" }) }); assert.equal(created.response.ok, true, `job create failed: ${created.payload.detail || ""}`); const jobId = created.payload.job.id; createdJobIds.push(jobId); const run = await request(`/api/jobs/${encodeURIComponent(jobId)}/run`, { method: "POST", headers, body: "{}" }); assert.equal(run.response.ok, true, `job run failed: ${run.payload.detail || ""}`); assert.equal(run.payload.job.status, "completed", "HTTP generation job must complete"); assert.equal(run.payload.job.result.artifacts?.[0]?.status, "inspected", "completed job must return inspected media evidence"); assert.ok(run.payload.job.result.artifacts?.[0]?.sha256, "completed job must return SHA-256 evidence"); const artifacts = await request(`/api/production/media-artifacts?jobId=${encodeURIComponent(jobId)}`, { headers }); assert.equal(artifacts.response.ok, true, "media artifact API must be readable"); assert.equal(artifacts.payload.artifacts?.[0]?.job_id, jobId, "artifact must be linked to the generation job"); console.log(`api media smoke passed: ${jobId} -> ${artifacts.payload.artifacts[0].last_frame_path}`); } finally { for (const jobId of createdJobIds) { dbRun("DELETE FROM media_artifacts WHERE job_id = ?", [jobId]); dbRun("DELETE FROM generation_jobs WHERE id = ?", [jobId]); await rm(resolve(projectRoot, `storage/jobs/${jobId}`), { recursive: true, force: true }); } dbRun("UPDATE model_connectors SET endpoint = ?, status = ?, error_message = ?, updated_at = ? WHERE id = 'owned-i2v'", [originalConnector.endpoint, originalConnector.status, originalConnector.errorMessage, new Date().toISOString()]); await new Promise((resolveClose) => server.close(resolveClose)); await rm(resolve(projectRoot, "storage/jobs/api-media-smoke"), { recursive: true, force: true }); }