113 lines
5.3 KiB
JavaScript
113 lines
5.3 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 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",
|
|
protocol: { healthRoute: "health" }
|
|
})
|
|
});
|
|
assert.equal(created.response.status, 201, "本地自定义 Runner 注册失败");
|
|
modelId = created.payload.model.id;
|
|
assert.equal(created.payload.model.approvalRequired, false, "本地连接器不应被强制标记为外部审批");
|
|
|
|
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]);
|
|
});
|
|
}
|
|
}
|