Files
cloudflare-domain-manager/apps/api/test/audit.test.ts
T
DenozordecandCursor 859ad23006
Build and Push CFDM Docker Image / build-and-push (push) Successful in 2m9s
Build and Push CFDM Docker Image / create-release (push) Skipped
Build and Push CFDM Docker Image / update-wiki (push) Successful in 5s
feat(audit): локальный журнал и push в auth-portal
Таблица audit_log, recordAudit на CRUD, GET /api/v1/audit и dual-write source_app=cfdm.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 13:24:32 +07:00

98 lines
2.8 KiB
TypeScript

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();
});
});