feat: bootstrap commercial AI drama platform
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { dbAll, dbRun, withTransaction } from "../server/db.mjs";
|
||||
|
||||
const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787";
|
||||
const runId = Date.now();
|
||||
const scope = {
|
||||
"x-organization-id": "org-studio-lab",
|
||||
"x-workspace-id": "ws-local-aidrama",
|
||||
"x-project-id": "thunder-mouth"
|
||||
};
|
||||
|
||||
const inviteEmail = `ops-${runId}@local.test`;
|
||||
const outputPrefix = `qa/ops/${runId}/`;
|
||||
|
||||
function cleanup() {
|
||||
const users = dbAll("SELECT id FROM users WHERE email = ?", [inviteEmail]);
|
||||
const userIds = users.map((user) => user.id);
|
||||
const jobs = dbAll("SELECT id FROM generation_jobs WHERE output_path LIKE ?", [`${outputPrefix}%`]);
|
||||
const jobIds = jobs.map((job) => job.id);
|
||||
|
||||
withTransaction(() => {
|
||||
if (jobIds.length) {
|
||||
const placeholders = jobIds.map(() => "?").join(",");
|
||||
dbRun(`DELETE FROM media_artifacts WHERE job_id IN (${placeholders})`, jobIds);
|
||||
dbRun(`DELETE FROM job_dependencies WHERE job_id IN (${placeholders}) OR depends_on_job_id IN (${placeholders})`, [...jobIds, ...jobIds]);
|
||||
dbRun(`DELETE FROM job_attempts WHERE job_id IN (${placeholders})`, jobIds);
|
||||
dbRun("DELETE FROM usage_events WHERE metadata_json LIKE ?", [`%${runId}%`]);
|
||||
dbRun(`DELETE FROM audit_logs WHERE target_id IN (${placeholders})`, jobIds);
|
||||
dbRun(`DELETE FROM generation_jobs WHERE id IN (${placeholders})`, jobIds);
|
||||
}
|
||||
dbRun("DELETE FROM notification_deliveries WHERE request_json LIKE ?", [`%\"runId\":${runId}%`]);
|
||||
for (const userId of userIds) {
|
||||
dbRun("DELETE FROM invitations WHERE email = ? OR accepted_user_id = ?", [inviteEmail, userId]);
|
||||
dbRun("UPDATE audit_logs SET actor_user_id = NULL WHERE actor_user_id = ?", [userId]);
|
||||
dbRun("UPDATE usage_events SET user_id = NULL WHERE user_id = ?", [userId]);
|
||||
dbRun("DELETE FROM users WHERE id = ?", [userId]);
|
||||
}
|
||||
dbRun("DELETE FROM invitations WHERE email = ?", [inviteEmail]);
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
const login = expectOk(await request("/api/auth/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ email: "producer@local.test", password: "Demo@123456" })
|
||||
}), "owner login");
|
||||
const headers = { authorization: `Bearer ${login.session.token}`, ...scope };
|
||||
|
||||
const invite = expectOk(await request("/api/organizations/org-studio-lab/invitations", {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ email: inviteEmail, roleKey: "writer", workspaceId: "ws-local-aidrama" })
|
||||
}), "create invitation");
|
||||
assert.ok(invite.invitation?.inviteToken, "invitation must return a one-time registration token");
|
||||
|
||||
const preview = expectOk(await request(`/api/invitations/preview?token=${encodeURIComponent(invite.invitation.inviteToken)}`), "preview invitation");
|
||||
assert.equal(preview.invitation.email, inviteEmail, "invitation preview email mismatch");
|
||||
|
||||
const registration = expectOk(await request("/api/auth/register", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ inviteToken: invite.invitation.inviteToken, displayName: "运营测试用户", password: "Ops@123456" })
|
||||
}), "register invited user");
|
||||
assert.ok(registration.session?.token, "registration must create a session");
|
||||
assert.equal(registration.context.currentOrganization.id, "org-studio-lab", "registered user must enter invited organization");
|
||||
|
||||
const firstJob = expectOk(await request("/api/jobs", {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ adapter: "owned-image", kind: "依赖测试关键帧", shotId: "shot-01", output: `${outputPrefix}first.json` })
|
||||
}), "create first job");
|
||||
const dependentJob = expectOk(await request("/api/jobs", {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ adapter: "owned-image", kind: "依赖测试任务", shotId: "shot-02", dependsOnJobIds: [firstJob.job.id], output: `${outputPrefix}dependent.json` })
|
||||
}), "create dependent job");
|
||||
assert.equal(dependentJob.job.dependencies.length, 1, "dependent job must expose dependency records");
|
||||
assert.equal(dependentJob.job.status, "blocked", "dependent job must wait for incomplete dependency");
|
||||
const dependencyRun = await request(`/api/jobs/${encodeURIComponent(dependentJob.job.id)}/run`, { method: "POST", headers, body: "{}" });
|
||||
assert.equal(dependencyRun.response.status, 409, "incomplete dependency must block execution");
|
||||
assert.equal(dependencyRun.payload.error, "job_dependencies_unresolved", "dependency block must have a stable error code");
|
||||
|
||||
const storage = expectOk(await request("/api/usage/storage", { headers }), "storage usage");
|
||||
assert.ok(Number.isFinite(storage.storage.usedBytes), "storage usage must return measured bytes");
|
||||
assert.ok(Number.isFinite(storage.storage.limitBytes), "storage usage must return a byte quota");
|
||||
|
||||
const notificationTest = expectOk(await request("/api/system/notifications/test", {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ event: "job.failed", payload: { source: "smoke-ops", runId } })
|
||||
}), "notification test");
|
||||
assert.ok(notificationTest.deliveries?.length >= 1, "notification test must create delivery records");
|
||||
|
||||
const compose = expectOk(await request("/api/production/compose", {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ dryRun: true, clips: ["storage/jobs/missing-a.mp4", "storage/jobs/missing-b.mp4"], outputPath: `storage/compositions/${runId}.mp4` })
|
||||
}), "compose dry-run");
|
||||
assert.equal(compose.composition.dryRun, true, "compose dry-run must not claim a rendered video");
|
||||
assert.equal(compose.composition.tool, "ffmpeg", "local composition must use ffmpeg");
|
||||
|
||||
console.log(`ops smoke passed: ${api}`);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
Reference in New Issue
Block a user