feat(audit): локальный журнал и push в auth-portal
Таблица audit_log, recordAudit на CRUD, GET /api/v1/audit и dual-write source_app=cfdm. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
import { buildApp } from "../src/app.js";
|
||||
import { loadConfig } from "../src/config.js";
|
||||
import * as auditPortalPush from "../src/services/audit-portal-push.js";
|
||||
|
||||
describe("audit log", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("GET /api/v1/audit returns local entries", async () => {
|
||||
const pushSpy = vi
|
||||
.spyOn(auditPortalPush, "pushAuditEvents")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
const app = await buildApp({
|
||||
config: {
|
||||
...loadConfig(),
|
||||
staticDir: null,
|
||||
authAuditIngestSecret: null,
|
||||
},
|
||||
memory: true,
|
||||
});
|
||||
|
||||
const login = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/auth/login",
|
||||
payload: { username: "admin", password: "admin" },
|
||||
});
|
||||
expect(login.statusCode).toBe(200);
|
||||
const token = (login.json() as { token: string }).token;
|
||||
|
||||
const create = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/groups",
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { name: "Audit Test", slug: "audit-test" },
|
||||
});
|
||||
expect(create.statusCode).toBe(200);
|
||||
|
||||
const list = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/v1/audit?action=group.create",
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
});
|
||||
expect(list.statusCode).toBe(200);
|
||||
const entries = list.json() as { action: string; source_app: string }[];
|
||||
expect(entries.length).toBeGreaterThanOrEqual(1);
|
||||
expect(entries[0]?.action).toBe("group.create");
|
||||
expect(entries[0]?.source_app).toBe("cfdm");
|
||||
|
||||
expect(pushSpy).not.toHaveBeenCalled();
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("recordAudit pushes to portal when secret configured", async () => {
|
||||
const pushSpy = vi
|
||||
.spyOn(auditPortalPush, "pushAuditEvents")
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
const app = await buildApp({
|
||||
config: {
|
||||
...loadConfig(),
|
||||
staticDir: null,
|
||||
authPortalUrl: "http://portal.test",
|
||||
authAuditIngestSecret: "test-ingest-secret",
|
||||
},
|
||||
memory: true,
|
||||
});
|
||||
|
||||
const login = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/auth/login",
|
||||
payload: { username: "admin", password: "admin" },
|
||||
});
|
||||
const token = (login.json() as { token: string }).token;
|
||||
|
||||
await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/groups",
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: { name: "Portal Push", slug: "portal-push" },
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(pushSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
const [portalUrl, secret, events] = pushSpy.mock.calls[0]!;
|
||||
expect(portalUrl).toBe("http://portal.test");
|
||||
expect(secret).toBe("test-ingest-secret");
|
||||
expect(events[0]?.source_app).toBe("cfdm");
|
||||
expect(events[0]?.action).toBe("group.create");
|
||||
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user