feat: expand commercial ai drama platform

This commit is contained in:
xz
2026-08-31 21:09:48 +08:00
parent f06e583c9b
commit 8caf947b80
35 changed files with 10181 additions and 188 deletions
+184
View File
@@ -0,0 +1,184 @@
import assert from "node:assert/strict";
import { createServer } from "node:http";
import { rm } from "node:fs/promises";
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"
};
const onePixelPng = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
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 };
}
async function login(email = "producer@local.test") {
const result = await request("/api/auth/login", {
method: "POST",
body: JSON.stringify({ email, password: "Demo@123456" })
});
assert.equal(result.response.ok, true, `${email} 登录失败:${result.payload.detail || result.payload.error || ""}`);
return { authorization: `Bearer ${result.payload.session.token}`, ...scope };
}
const mockRunner = createServer((req, res) => {
if (req.method === "POST" && req.url === "/generate") {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({
data: [{ b64_json: onePixelPng, mimeType: "image/png" }],
metadata: { source: "local-smoke-runner", jobId: req.headers["x-ai-drama-job-id"] || "" }
}));
return;
}
res.writeHead(404, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "not_found" }));
});
await new Promise((resolve) => mockRunner.listen(0, "127.0.0.1", resolve));
const runnerPort = mockRunner.address().port;
const suffix = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
const connectorId = `smoke-approval-connector-${suffix}`;
const catalogId = `smoke-approval-catalog-${suffix}`;
const routeId = `smoke-approval-route-${suffix}`;
let approvalId = "";
let jobId = "";
function seedApprovalRoute() {
const timestamp = new Date().toISOString();
withTransaction(() => {
dbRun(
`INSERT INTO model_connectors(
id, organization_id, workspace_id, label, kind, capabilities_json, endpoint,
status, cost_mode, approval_required, protocol_json, auth_env, created_by, created_at, updated_at
) VALUES (?, 'org-studio-lab', 'ws-local-aidrama', 'Smoke Mixed Approval Runner', 'http-json', ?, ?, 'ready', 'mixed', 1, '{}', '', 'u-owner', ?, ?)`,
[connectorId, JSON.stringify(["text-to-image", "single-frame"]), `http://127.0.0.1:${runnerPort}/generate`, timestamp, timestamp]
);
dbRun(
`INSERT INTO model_catalog_entries(
id, organization_id, workspace_id, connector_id, model_key, display_name, family,
capabilities_json, context_window, max_output_tokens, cost_json, status,
approval_status, metadata_json, created_by, created_at, updated_at
) VALUES (?, 'org-studio-lab', 'ws-local-aidrama', ?, 'smoke-single-frame', 'Smoke 单画面审批模型', 'smoke', ?, 4096, 1024, ?, 'active', 'approved', '{}', 'u-owner', ?, ?)`,
[catalogId, connectorId, JSON.stringify(["text-to-image", "single-frame"]), JSON.stringify({ mode: "per-image", estimatedCny: 0.02, locality: "mixed" }), timestamp, timestamp]
);
dbRun(
`INSERT INTO model_routing_policies(
id, organization_id, workspace_id, name, workflow_key, operation_key,
primary_model_id, fallback_model_id, policy_mode, approval_mode,
budget_limit_cny, status, policy_json, created_by, created_at, updated_at
) VALUES (?, 'org-studio-lab', 'ws-local-aidrama', 'Smoke 审批路由', 'smoke-approval', 'image-keyframe', ?, NULL, 'prefer-approved', 'explicit-review', 1, 'active', ?, 'u-owner', ?, ?)`,
[routeId, catalogId, JSON.stringify({ qaGate: "single-frame", smoke: true }), timestamp, timestamp]
);
});
}
function cleanup() {
withTransaction(() => {
if (jobId) {
dbRun("DELETE FROM job_dependencies WHERE job_id = ? OR depends_on_job_id = ?", [jobId, jobId]);
dbRun("DELETE FROM job_attempts WHERE job_id = ?", [jobId]);
dbRun("DELETE FROM media_artifacts WHERE job_id = ?", [jobId]);
dbRun("DELETE FROM usage_events WHERE metadata_json LIKE ?", [`%${jobId}%`]);
dbRun("DELETE FROM generation_jobs WHERE id = ?", [jobId]);
}
if (approvalId) dbRun("DELETE FROM model_route_approval_requests WHERE id = ?", [approvalId]);
dbRun("DELETE FROM audit_logs WHERE target_id IN (?, ?, ?, ?)", [approvalId || "none", jobId || "none", routeId, connectorId]);
dbRun("DELETE FROM model_routing_policies WHERE id = ?", [routeId]);
dbRun("DELETE FROM model_catalog_entries WHERE id = ?", [catalogId]);
dbRun("DELETE FROM model_connectors WHERE id = ?", [connectorId]);
});
}
try {
seedApprovalRoute();
const owner = await login("producer@local.test");
const writer = await login("writer@local.test");
const approvalBody = {
workflowKey: "smoke-approval",
operationKey: "image-keyframe",
kind: "单画面关键帧",
localOnly: false,
estimatedCostCny: 0.02,
reason: "smoke: 需要一次受控混合成本模型执行审批"
};
const withoutApproval = await request("/api/jobs", {
method: "POST",
headers: writer,
body: JSON.stringify({ ...approvalBody, shotId: "shot-01", adapter: "owned-model-platform" })
});
assert.equal(withoutApproval.response.status, 403, "未携带审批单时,普通编剧不能创建受控模型任务");
assert.ok(["external_connector_requires_approval", "model_route_requires_approval"].includes(withoutApproval.payload.error), "未审批阻断错误码不正确");
const submitted = await request("/api/platform/model-route-approvals", {
method: "POST",
headers: writer,
body: JSON.stringify(approvalBody)
});
assert.equal(submitted.response.status, 201, `审批单提交失败:${submitted.payload.detail || submitted.payload.error || ""}`);
approvalId = submitted.payload.approval.id;
assert.equal(submitted.payload.approval.status, "submitted", "审批单初始状态必须是 submitted");
assert.equal(submitted.payload.approval.routeId, routeId, "审批单必须绑定命中的路由");
assert.equal(submitted.payload.approval.connectorId, connectorId, "审批单必须绑定命中的连接器");
const writerDecision = await request(`/api/platform/model-route-approvals/${encodeURIComponent(approvalId)}/decision`, {
method: "POST",
headers: writer,
body: JSON.stringify({ status: "approved" })
});
assert.equal(writerDecision.response.status, 403, "普通编剧不能审批模型路由申请");
const approved = await request(`/api/platform/model-route-approvals/${encodeURIComponent(approvalId)}/decision`, {
method: "POST",
headers: owner,
body: JSON.stringify({ status: "approved", expiresHours: 24, note: "smoke approval" })
});
assert.equal(approved.response.status, 200, `管理员审批失败:${approved.payload.detail || approved.payload.error || ""}`);
assert.equal(approved.payload.approval.status, "approved", "审批后状态必须是 approved");
assert.ok(approved.payload.approval.expiresAt, "审批通过必须写入过期时间");
const created = await request("/api/jobs", {
method: "POST",
headers: writer,
body: JSON.stringify({ ...approvalBody, shotId: "shot-01", adapter: "owned-model-platform", approvalRequestId: approvalId })
});
assert.equal(created.response.status, 201, `审批后任务创建失败:${created.payload.detail || created.payload.error || ""}`);
jobId = created.payload.job.id;
assert.equal(created.payload.job.status, "queued", "审批后的 ready 连接器任务应进入队列");
assert.equal(created.payload.job.modelRouteApprovalId, approvalId, "任务必须保存审批单 ID");
assert.equal(created.payload.job.request.preflight.modelRouteApproval.id, approvalId, "请求合同必须携带审批证据");
const executed = await request(`/api/jobs/${encodeURIComponent(jobId)}/run`, {
method: "POST",
headers: writer,
body: JSON.stringify({})
});
assert.equal(executed.response.status, 200, `审批任务执行失败:${executed.payload.detail || executed.payload.error || ""}`);
assert.equal(executed.payload.job.status, "completed", "本地 mock runner 应完成审批任务");
const approvalsAfterRun = await request("/api/platform/model-route-approvals", { headers: owner });
const consumed = approvalsAfterRun.payload.approvals.find((item) => item.id === approvalId);
assert.ok(consumed?.consumedAt, "执行后审批单必须被消耗");
const reused = await request("/api/jobs", {
method: "POST",
headers: writer,
body: JSON.stringify({ ...approvalBody, shotId: "shot-01", adapter: "owned-model-platform", approvalRequestId: approvalId })
});
assert.equal(reused.response.status, 403, "已消耗审批单不能复用创建下一次任务");
assert.equal(reused.payload.error, "model_route_approval_consumed", "审批单复用错误码不正确");
console.log(`model approval smoke passed: request, RBAC decision, job binding, execution consume (${approvalId})`);
} finally {
cleanup();
if (jobId) await rm(new URL(`../storage/jobs/${jobId}`, import.meta.url), { recursive: true, force: true });
await new Promise((resolve) => mockRunner.close(resolve));
}