Files
ai-drama-platform/scripts/smoke-model-connectors.mjs

118 lines
5.7 KiB
JavaScript

import assert from "node:assert/strict";
import { createServer } from "node:http";
import { withTransaction, dbRun } from "../server/db.mjs";
const api = process.env.AI_DRAMA_API_BASE || "http://127.0.0.1:8787";
const runnerPort = Number(process.env.AI_DRAMA_SMOKE_MODEL_PORT || 8792);
const secretEnv = "AI_DRAMA_SMOKE_MISSING_SECRET";
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 runner = createServer((req, res) => {
if (req.url === "/v1/health") {
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ status: "ok" }));
return;
}
res.writeHead(404, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "not_found" }));
});
await new Promise((resolve) => runner.listen(runnerPort, "127.0.0.1", resolve));
let modelId = "";
try {
const owner = { ...(await login("producer@local.test")), ...scope };
const writer = { ...(await login("writer@local.test")), ...scope };
const writerModels = await request("/api/platform/models", writer);
assert.equal(writerModels.response.status, 403, "普通用户不应访问模型中台");
const publicLocal = await request("/api/platform/models/register", owner, {
method: "POST",
body: JSON.stringify({ label: "smoke invalid local", endpoint: "https://example.com/v1", kind: "http-json", capability: ["image"], costMode: "local" })
});
assert.equal(publicLocal.response.status, 403, "local 连接器指向公网时必须被拒绝");
assert.equal(publicLocal.payload.error, "local_only_endpoint_required", "local 公网地址错误码不正确");
const externalWithoutApproval = await request("/api/platform/models/register", owner, {
method: "POST",
body: JSON.stringify({ label: "smoke invalid external", endpoint: "https://example.com/v1", kind: "openai-compatible", capability: ["image"], costMode: "mixed", approvalRequired: false })
});
assert.equal(externalWithoutApproval.response.status, 400, "混合连接器未开启审批时必须被拒绝");
assert.equal(externalWithoutApproval.payload.error, "external_connector_approval_required", "外部审批错误码不正确");
const created = await request("/api/platform/models/register", owner, {
method: "POST",
body: JSON.stringify({
label: `Smoke Editable Runner ${Date.now()}`,
endpoint: `http://127.0.0.1:${runnerPort}/v1`,
kind: "http-json",
capability: ["text-to-image", "single-frame"],
costMode: "local",
authEnv: secretEnv,
protocol: { healthRoute: "health" }
})
});
assert.equal(created.response.status, 201, "本地自定义 Runner 注册失败");
modelId = created.payload.model.id;
assert.equal(created.payload.model.approvalRequired, false, "本地连接器不应被强制标记为外部审批");
assert.equal(created.payload.model.secretPolicy.authEnv, secretEnv, "连接器必须只返回密钥环境变量名");
assert.equal(created.payload.model.secretPolicy.status, "missing", "未设置的密钥环境变量必须显示为 missing");
assert.equal(created.payload.model.endpointPolicy.localOnlySafe, true, "本地连接器 endpoint 应被识别为 local-only safe");
const edited = await request(`/api/platform/models/${encodeURIComponent(modelId)}`, owner, {
method: "PATCH",
body: JSON.stringify({ label: "Smoke Edited Local Runner", capability: ["text-to-image", "single-frame", "continuity-lock"], protocol: { healthRoute: "health" } })
});
assert.equal(edited.response.ok, true, "本地连接器编辑失败");
assert.equal(edited.payload.model.label, "Smoke Edited Local Runner", "连接器名称编辑未保存");
assert.ok(edited.payload.model.capability.includes("continuity-lock"), "连接器能力标签编辑未保存");
const invalidEditEndpoint = await request(`/api/platform/models/${encodeURIComponent(modelId)}`, owner, {
method: "PATCH",
body: JSON.stringify({ endpoint: "https://example.com/v1", costMode: "local" })
});
assert.equal(invalidEditEndpoint.response.status, 403, "编辑时 local 公网地址必须被拒绝");
const invalidEditApproval = await request(`/api/platform/models/${encodeURIComponent(modelId)}`, owner, {
method: "PATCH",
body: JSON.stringify({ endpoint: "https://example.com/v1", costMode: "mixed", approvalRequired: false })
});
assert.equal(invalidEditApproval.response.status, 400, "编辑时 mixed 未审批必须被拒绝");
const probed = await request(`/api/platform/models/${encodeURIComponent(modelId)}/probe`, owner, { method: "POST", body: "{}" });
assert.equal(probed.response.ok, true, `本地连接器探活失败:${JSON.stringify(probed.payload)}`);
assert.equal(probed.payload.model.status, "ready", "本地连接器探活后必须进入 ready");
console.log(`model connector smoke passed: RBAC, local-only, approval gate, edit, probe (${modelId})`);
} finally {
await new Promise((resolve) => runner.close(resolve));
if (modelId) {
withTransaction(() => {
dbRun("DELETE FROM model_connectors WHERE id = ?", [modelId]);
dbRun("DELETE FROM audit_logs WHERE target_id = ?", [modelId]);
});
}
}