import assert from "node:assert/strict"; import { rm } from "node:fs/promises"; import { dirname, resolve } from "node:path"; import { dbRun, withTransaction } from "../server/db.mjs"; const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787"; const scope = { "x-organization-id": "org-studio-lab", "x-workspace-id": "ws-local-aidrama", "x-project-id": "thunder-mouth" }; 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 }; } function expectOk(result, label) { assert.equal(result.response.ok, true, `${label}: ${result.response.status} ${result.payload.detail || result.payload.error || ""}`); return result.payload; } const login = expectOk(await request("/api/auth/login", { method: "POST", body: JSON.stringify({ email: "producer@local.test", password: "Demo@123456" }) }), "production controls login"); const headers = { authorization: `Bearer ${login.session.token}`, ...scope }; const graph = expectOk(await request("/api/production/graph", { headers }), "read production graph"); const shotId = graph.graph.shots[0]?.id; assert.ok(shotId, "production graph must have a shot"); let createdVersionId = ""; let deliveryId = ""; const batchIds = []; const manifestPaths = []; try { const beforeVersions = expectOk(await request(`/api/production/shots/${encodeURIComponent(shotId)}/versions`, { headers }), "read shot versions"); const originalVersion = beforeVersions.versions.at(-1) || beforeVersions.versions[0]; assert.ok(originalVersion?.id, "shot version history must include a version"); const saved = expectOk(await request(`/api/production/shots/${encodeURIComponent(shotId)}/prompt-versions`, { method: "POST", headers, body: JSON.stringify({ imagePrompt: "smoke version prompt", negativePrompt: "no collage", videoPrompt: "smoke version motion" }) }), "create shot version"); createdVersionId = saved.shot.versionId; assert.notEqual(createdVersionId, originalVersion.id, "saving a prompt must create a new current version"); const restored = expectOk(await request(`/api/production/shots/${encodeURIComponent(shotId)}/versions/${encodeURIComponent(originalVersion.id)}/restore`, { method: "POST", headers, body: "{}" }), "restore shot version"); assert.equal(restored.shot.versionId, originalVersion.id, "restore must move the current version pointer"); const createdDelivery = expectOk(await request("/api/production/deliveries", { method: "POST", headers, body: JSON.stringify({ version: `smoke-${Date.now()}`, channel: "internal" }) }), "create delivery"); deliveryId = createdDelivery.delivery.id; const firstBatch = expectOk(await request(`/api/production/deliveries/${encodeURIComponent(deliveryId)}/batches`, { method: "POST", headers, body: JSON.stringify({ label: "Smoke Batch 1" }) }), "create first delivery batch"); batchIds.push(firstBatch.batch.id); manifestPaths.push(firstBatch.batch.manifest_path); assert.equal(firstBatch.batch.result.manifestWritten, true, "delivery batch must write a real manifest"); assert.ok(firstBatch.batch.manifest_path.startsWith("storage/"), "delivery manifest must be inside storage"); const secondBatch = expectOk(await request(`/api/production/deliveries/${encodeURIComponent(deliveryId)}/batches`, { method: "POST", headers, body: JSON.stringify({ label: "Smoke Batch 2" }) }), "create second delivery batch"); batchIds.push(secondBatch.batch.id); manifestPaths.push(secondBatch.batch.manifest_path); assert.equal(secondBatch.batch.result.manifestWritten, true, "second delivery batch must write a real manifest"); expectOk(await request(`/api/production/delivery-batches/${encodeURIComponent(firstBatch.batch.id)}/activate`, { method: "POST", headers, body: "{}" }), "activate first delivery batch"); expectOk(await request(`/api/production/delivery-batches/${encodeURIComponent(secondBatch.batch.id)}/activate`, { method: "POST", headers, body: "{}" }), "activate second delivery batch"); const rolledBack = expectOk(await request(`/api/production/delivery-batches/${encodeURIComponent(secondBatch.batch.id)}/rollback`, { method: "POST", headers, body: "{}" }), "rollback second delivery batch"); assert.equal(rolledBack.restoredBatch.id, firstBatch.batch.id, "rollback must restore the previous active batch"); const mediaQa = expectOk(await request("/api/production/qa/media/run", { method: "POST", headers, body: "{}" }), "run media QA"); assert.ok(mediaQa.report?.totals && Number.isInteger(mediaQa.report.totals.gates), "media QA must return measurable gate totals"); console.log(`production controls smoke passed: ${shotId}, ${deliveryId}`); } finally { withTransaction(() => { if (createdVersionId) dbRun("DELETE FROM shot_versions WHERE id = ?", [createdVersionId]); if (shotId) dbRun("UPDATE shots SET current_version_id = (SELECT id FROM shot_versions WHERE shot_id = ? ORDER BY version_number ASC LIMIT 1) WHERE id = ?", [shotId, shotId]); for (const batchId of batchIds) dbRun("DELETE FROM delivery_batch_items WHERE batch_id = ?", [batchId]); for (const batchId of batchIds) dbRun("DELETE FROM delivery_batches WHERE id = ?", [batchId]); if (deliveryId) dbRun("DELETE FROM deliveries WHERE id = ?", [deliveryId]); }); for (const manifestPath of manifestPaths) await rm(dirname(resolve(import.meta.dirname, "..", manifestPath)), { recursive: true, force: true }); }