Files
ai-drama-platform/scripts/smoke-production-controls.mjs
T

103 lines
5.5 KiB
JavaScript

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 });
}