feat: bootstrap commercial AI drama platform

This commit is contained in:
xz
2026-08-24 10:24:34 +08:00
commit ffb27d845b
100 changed files with 35314 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
import assert from "node:assert/strict";
import { dbGet, dbRun, withTransaction } from "../server/db.mjs";
import { hashApiClientKey } from "../server/api-client-secrets.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, headers = {}, options = {}) {
const response = await fetch(`${api}${path}`, {
...options,
headers: { "content-type": "application/json", ...headers, ...(options.headers || {}) }
});
const payload = await response.json().catch(() => ({}));
return { response, payload };
}
async function login(email) {
const result = await request("/api/auth/login", {}, { method: "POST", body: JSON.stringify({ email, password: "Demo@123456" }) });
assert.equal(result.response.ok, true, `${email} 登录失败`);
return { authorization: `Bearer ${result.payload.session.token}` };
}
const owner = { ...(await login("producer@local.test")), ...scope };
const writer = { ...(await login("writer@local.test")), ...scope };
let clientId = "";
let firstKey = "";
let modelId = "";
try {
const denied = await request("/api/system/api-clients", writer);
assert.equal(denied.response.status, 403, "普通用户不应访问 API 客户端目录");
const invalidScopes = await request("/api/system/api-clients", owner, {
method: "POST",
body: JSON.stringify({ name: `Invalid Scope ${Date.now()}`, scopes: ["admin:all"] })
});
assert.equal(invalidScopes.response.status, 400, "不支持的 API scope 必须被拒绝");
assert.equal(invalidScopes.payload.error, "api_client_scopes_invalid", "不支持的 API scope 错误码必须稳定");
const created = await request("/api/system/api-clients", owner, {
method: "POST",
body: JSON.stringify({ name: `Smoke API Client ${Date.now()}`, scopes: ["jobs:read", "models:read"] })
});
assert.equal(created.response.status, 201, "系统管理员创建 API 客户端失败");
clientId = created.payload.client.id;
firstKey = created.payload.clientKey;
assert.ok(firstKey.startsWith("local-") && firstKey.length > 24, "新 API 密钥熵不足或格式错误");
const stored = dbGet("SELECT client_key, client_key_hash, client_key_prefix, key_version FROM api_clients WHERE id = ?", [clientId]);
assert.ok(stored, "新 API 客户端没有写入数据库");
assert.equal(stored.client_key_hash, hashApiClientKey(firstKey), "数据库中的 API 密钥摘要不匹配");
assert.equal(stored.key_version, 2, "API 客户端密钥版本没有升级");
assert.equal(stored.client_key_prefix, firstKey.slice(0, 12), "API 密钥预览前缀不匹配");
assert.notEqual(stored.client_key, firstKey, "数据库不能保存 API 密钥明文");
assert.ok(!String(stored.client_key).includes(firstKey), "数据库密钥字段不能包含可用密钥");
const listed = await request("/api/system/api-clients", owner);
assert.equal(listed.response.ok, true, "系统管理员读取 API 客户端目录失败");
const listedClient = listed.payload.apiClients.find((item) => item.id === clientId);
assert.ok(listedClient, "新 API 客户端没有出现在目录中");
assert.ok(!JSON.stringify(listedClient).includes(firstKey), "API 客户端目录不能返回完整密钥");
assert.deepEqual(listedClient.scopes, ["jobs:read", "models:read"], "API 客户端目录必须返回真实 scope");
const clientJobs = await request("/api/jobs", { authorization: `Bearer ${firstKey}`, ...scope });
assert.equal(clientJobs.response.ok, true, "新 API 密钥不能访问已授权任务读取接口");
const firstJobId = clientJobs.payload.jobs?.[0]?.id;
if (firstJobId) {
const clientJobDetail = await request(`/api/jobs/${encodeURIComponent(firstJobId)}`, { authorization: `Bearer ${firstKey}`, ...scope });
assert.equal(clientJobDetail.response.ok, true, "jobs:read 必须允许读取任务详情");
}
const readOnlyCreate = await request("/api/jobs", { authorization: `Bearer ${firstKey}`, ...scope }, { method: "POST", body: "{}" });
assert.equal(readOnlyCreate.response.status, 403, "jobs:read 不能创建生成任务");
assert.equal(readOnlyCreate.payload.error, "api_client_scope_denied", "缺少 jobs:write 时必须返回 scope 错误");
const modelRead = await request("/api/platform/models", { authorization: `Bearer ${firstKey}`, ...scope });
assert.equal(modelRead.response.ok, true, "models:read 必须允许读取模型连接器");
const modelWriteDenied = await request("/api/platform/models/owned-image", { authorization: `Bearer ${firstKey}`, ...scope }, { method: "PATCH", body: JSON.stringify({}) });
assert.equal(modelWriteDenied.response.status, 403, "models:read 不能修改模型连接器");
assert.equal(modelWriteDenied.payload.error, "api_client_scope_denied", "缺少 models:write 时必须返回 scope 错误");
const scopeUpdate = await request(`/api/system/api-clients/${encodeURIComponent(clientId)}`, owner, {
method: "PATCH",
body: JSON.stringify({ scopes: ["jobs:read", "jobs:write", "models:read", "models:write", "audit:read"] })
});
assert.equal(scopeUpdate.response.ok, true, "API 客户端 scope 更新失败");
assert.deepEqual(scopeUpdate.payload.client.scopes, ["jobs:read", "jobs:write", "models:read", "models:write", "audit:read"], "API 客户端 scope 更新结果不正确");
const writeKey = firstKey;
const dryRun = await request("/api/adapters/dry-run", { authorization: `Bearer ${writeKey}`, ...scope }, { method: "POST", body: JSON.stringify({ shotId: "shot-01", adapterId: "owned-image" }) });
assert.equal(dryRun.response.ok, true, "jobs:write 必须允许生成请求 dry-run");
const auditRead = await request("/api/audit", { authorization: `Bearer ${writeKey}`, ...scope });
assert.equal(auditRead.response.ok, true, "audit:read 必须允许读取审计日志");
modelId = `smoke-scope-model-${Date.now()}`;
const modelWrite = await request("/api/platform/models/register", { authorization: `Bearer ${writeKey}`, ...scope }, {
method: "POST",
body: JSON.stringify({ id: modelId, label: "Scope 回归本地模型", endpoint: "http://127.0.0.1:7879", kind: "http-json", capability: ["text-to-image"], costMode: "local" })
});
assert.equal(modelWrite.response.status, 201, "models:write 必须允许登记模型连接器");
const rotated = await request(`/api/system/api-clients/${encodeURIComponent(clientId)}/rotate`, owner, { method: "POST", body: "{}" });
assert.equal(rotated.response.ok, true, "API 密钥轮换失败");
assert.notEqual(rotated.payload.clientKey, firstKey, "轮换后的 API 密钥不能与旧密钥相同");
const oldKey = await request("/api/jobs", { authorization: `Bearer ${firstKey}`, ...scope });
assert.equal(oldKey.response.status, 401, "旧 API 密钥轮换后必须立即失效");
const newKey = await request("/api/jobs", { authorization: `Bearer ${rotated.payload.clientKey}`, ...scope });
assert.equal(newKey.response.ok, true, "轮换后的 API 密钥不能访问已授权任务读取接口");
console.log(`api client smoke passed: scoped access, one-time rotation, old-key revocation (${clientId})`);
} finally {
if (clientId) {
withTransaction(() => {
if (modelId) {
dbRun("DELETE FROM model_connectors WHERE id = ?", [modelId]);
dbRun("DELETE FROM audit_logs WHERE target_id = ?", [modelId]);
}
dbRun("DELETE FROM api_clients WHERE id = ?", [clientId]);
dbRun("DELETE FROM audit_logs WHERE target_id = ?", [clientId]);
});
}
}