feat: Token 支持多项目关联且全部为 MCP 令牌;配置操作全量审计,支持按项目/键查询
build-and-deploy / build-and-deploy (push) Successful in 40s
build-and-deploy / build-and-deploy (push) Successful in 40s
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 操作记录(审计)单元测试
|
||||
* 覆盖:appendAuditLog/loadAuditLogs 读写与过滤、auditService.log 静默容错、getClientIp 支持 nginx 转发
|
||||
* 运行:npm test
|
||||
*/
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const fs = require("node:fs");
|
||||
const os = require("node:os");
|
||||
const path = require("node:path");
|
||||
|
||||
// 使用临时数据目录,避免污染真实 data/
|
||||
const tmpDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "registry-audit-test-"));
|
||||
process.env.REGISTRY_DATA_DIR = tmpDataDir;
|
||||
|
||||
const duckdbAdapter = require("../src/persistence/duckdbAdapter");
|
||||
const { log: auditLog, getClientIp } = require("../src/services/auditService");
|
||||
|
||||
test.before(async () => {
|
||||
await duckdbAdapter.init();
|
||||
});
|
||||
|
||||
test("记录一条配置操作并可按项目/键查询", async () => {
|
||||
await auditLog({
|
||||
source: "web",
|
||||
actor: "admin",
|
||||
ip: "203.0.113.9",
|
||||
projectId: "proj-a",
|
||||
key: "db/host",
|
||||
action: "update",
|
||||
value: "db.internal",
|
||||
detail: { from: { key: "db/host", value: "old" }, to: { key: "db/host", value: "db.internal" } },
|
||||
});
|
||||
|
||||
const byProject = await duckdbAdapter.loadAuditLogs({ projectId: "proj-a" });
|
||||
assert.strictEqual(byProject.length, 1);
|
||||
const byKey = await duckdbAdapter.loadAuditLogs({ projectId: "proj-a", key: "db/host" });
|
||||
assert.strictEqual(byKey.length, 1);
|
||||
// 其它项目/键查不到
|
||||
assert.strictEqual((await duckdbAdapter.loadAuditLogs({ projectId: "proj-b" })).length, 0);
|
||||
assert.strictEqual((await duckdbAdapter.loadAuditLogs({ projectId: "proj-a", key: "other" })).length, 0);
|
||||
|
||||
const rec = byKey[0];
|
||||
assert.strictEqual(rec.source, "web");
|
||||
assert.strictEqual(rec.actor, "admin");
|
||||
assert.strictEqual(rec.ip, "203.0.113.9");
|
||||
assert.strictEqual(rec.action, "update");
|
||||
assert.strictEqual(rec.value, "db.internal");
|
||||
assert.strictEqual(rec.detail.from.value, "old");
|
||||
assert.ok(rec.createdAt);
|
||||
});
|
||||
|
||||
test("记录按时间倒序返回,MCP 来源也正确记录 Token 名", async () => {
|
||||
await auditLog({ source: "mcp", actor: "ci-token", ip: "10.0.0.1", projectId: "proj-a", key: "k/1", action: "read", value: "x" });
|
||||
await auditLog({ source: "mcp", actor: "ci-token", ip: "10.0.0.1", projectId: "proj-a", key: "k/1", action: "create", value: "x" });
|
||||
|
||||
const logs = await duckdbAdapter.loadAuditLogs({ projectId: "proj-a", key: "k/1" });
|
||||
assert.strictEqual(logs.length, 2);
|
||||
assert.ok(logs[0].createdAt >= logs[1].createdAt, "最新的应在前");
|
||||
assert.ok(logs.every((l) => l.source === "mcp" && l.actor === "ci-token"));
|
||||
});
|
||||
|
||||
test("getClientIp 优先取 X-Forwarded-For,其次 remoteAddress", () => {
|
||||
assert.strictEqual(getClientIp({ headers: { "x-forwarded-for": "203.0.113.50, 10.0.0.5" } }), "203.0.113.50");
|
||||
assert.strictEqual(getClientIp({ headers: {}, socket: { remoteAddress: "127.0.0.1" } }), "127.0.0.1");
|
||||
assert.strictEqual(getClientIp(null), "");
|
||||
});
|
||||
Reference in New Issue
Block a user