103 lines
5.5 KiB
JavaScript
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 });
|
|
}
|