82 lines
3.6 KiB
JavaScript
82 lines
3.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
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"
|
|
};
|
|
|
|
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() {
|
|
const result = await request("/api/auth/login", {}, { method: "POST", body: JSON.stringify({ email: "producer@local.test", password: "Demo@123456" }) });
|
|
assert.equal(result.response.ok, true, "限流回归登录失败");
|
|
return { authorization: `Bearer ${result.payload.session.token}` };
|
|
}
|
|
|
|
const owner = { ...(await login()), ...scope };
|
|
const restoreOwner = { ...(await login()), ...scope };
|
|
let clientId = "";
|
|
let originalLimit = 120;
|
|
let changedLimit = false;
|
|
|
|
try {
|
|
const config = await request("/api/system/config", owner);
|
|
assert.equal(config.response.ok, true, "读取系统配置失败");
|
|
const setting = config.payload.settings.find((item) => item.key === "api.rate_limit_per_minute");
|
|
originalLimit = Number(setting?.value ?? 120);
|
|
|
|
const created = await request("/api/system/api-clients", owner, {
|
|
method: "POST",
|
|
body: JSON.stringify({ name: `Smoke Rate Limit Client ${Date.now()}`, scopes: ["jobs:read"] })
|
|
});
|
|
assert.equal(created.response.status, 201, "限流回归 API 客户端创建失败");
|
|
clientId = created.payload.client.id;
|
|
const clientHeaders = { authorization: `Bearer ${created.payload.clientKey}`, ...scope };
|
|
|
|
const updated = await request("/api/system/config", owner, {
|
|
method: "POST",
|
|
body: JSON.stringify({ settings: [{ key: "api.rate_limit_per_minute", value: 2 }] })
|
|
});
|
|
assert.equal(updated.response.ok, true, "降低 API 限流配置失败");
|
|
changedLimit = true;
|
|
|
|
const first = await request("/api/jobs", clientHeaders);
|
|
const second = await request("/api/jobs", clientHeaders);
|
|
const third = await request("/api/jobs", clientHeaders);
|
|
assert.equal(first.response.status, 200, "限流窗口内第一次请求不应被阻断");
|
|
assert.equal(second.response.status, 200, "限流窗口内第二次请求不应被阻断");
|
|
assert.equal(first.response.headers.get("x-ratelimit-limit"), "2", "响应必须返回限流上限");
|
|
assert.equal(second.response.headers.get("x-ratelimit-remaining"), "0", "第二次请求后剩余额度应为 0");
|
|
assert.equal(third.response.status, 429, "超过限流上限必须返回 429");
|
|
assert.equal(third.payload.error, "rate_limit_exceeded", "限流错误码必须稳定");
|
|
assert.ok(Number(third.response.headers.get("retry-after")) > 0, "429 必须返回 Retry-After");
|
|
assert.equal(third.response.headers.get("x-ratelimit-remaining"), "0", "429 响应剩余额度应为 0");
|
|
|
|
console.log("api rate-limit smoke passed: configured limit, headers, 429, retry-after");
|
|
} finally {
|
|
if (changedLimit) {
|
|
const restored = await request("/api/system/config", restoreOwner, {
|
|
method: "POST",
|
|
body: JSON.stringify({ settings: [{ key: "api.rate_limit_per_minute", value: originalLimit }] })
|
|
});
|
|
assert.equal(restored.response.ok, true, "限流回归未能恢复原配置");
|
|
}
|
|
if (clientId) {
|
|
withTransaction(() => {
|
|
dbRun("DELETE FROM api_clients WHERE id = ?", [clientId]);
|
|
dbRun("DELETE FROM audit_logs WHERE target_id = ?", [clientId]);
|
|
});
|
|
}
|
|
}
|
|
|