import assert from "node:assert/strict"; import test from "node:test"; import { AuditChangeModel, AuditEventModel } from "@/infrastructure/audit/sequelize"; import { buildCompanyHeaders, createTestAccount, createTestCompany, createTestMembership, loginByApi, } from "@/testing/http/http-test-fixtures"; import { createHttpTestRuntime } from "@/testing/http/http-test-runtime"; function event(id: string, scopeId: string | null) { return { id, kind: "event", occurred_at: new Date("2026-08-25T10:00:00.000Z"), source_type: "application", source_id: "uecko-erp", operation_id: `op-${id}`, correlation_id: `cor-${id}`, origin: "http", actor_type: "user", actor_id: "actor", actor_label: "Actor", scope_type: scopeId ? "company" : null, scope_id: scopeId, scope_label: null, action: "customer.updated", target_type: "customer", target_id: id, target_label: id, }; } test("Audit HTTP aplica tenant A/B y valida el DTO estricto", async (t) => { const runtime = await createHttpTestRuntime(); t.after(async () => runtime.stop()); const account = await createTestAccount(runtime.database, { email: "audit-reader@test.local", password: "Password123!", }); const companyA = await createTestCompany(runtime.database, { legalName: "Company A" }); const companyB = await createTestCompany(runtime.database, { legalName: "Company B" }); await createTestMembership(runtime.database, { accountId: account.id, companyId: companyA.id }); await AuditEventModel.bulkCreate([ event("entry-a", companyA.id), event("entry-b", companyB.id), event("entry-global", null), ]); await AuditChangeModel.create({ audit_event_id: "entry-a", position: 0, path: "name", kind: "change", before_value: "Before", after_value: "After", }); const login = await loginByApi({ request: runtime.request, email: account.email, password: account.password, }); assert.equal(login.status, 200); const token = (login.body as { access_token: string }).access_token; const headers = buildCompanyHeaders({ accessToken: token, companyId: companyA.id }); const list = await runtime.request("/api/v1/audit", { headers }); assert.equal(list.status, 200); const body = list.body as { items: { id: string }[]; page: number; per_page: number; total_pages: number; total_items: number; }; assert.deepEqual( body.items.map((item) => item.id), ["entry-a"] ); assert.equal(body.page, 0); assert.equal(body.per_page, 50); assert.deepEqual(Object.keys(body).sort(), [ "items", "page", "per_page", "total_items", "total_pages", ]); assert.deepEqual(Object.keys(body.items[0] ?? {}).sort(), [ "action", "actor", "changes_count", "id", "kind", "occurred_at", "operation", "scope", "source", "target", ]); const detailA = await runtime.request("/api/v1/audit/entry-a", { headers }); assert.equal(detailA.status, 200); const detailBody = detailA.body as { changes: Record[] }; assert.deepEqual(Object.keys(detailBody).sort(), [ "action", "actor", "changes", "changes_count", "id", "kind", "occurred_at", "operation", "scope", "source", "target", ]); assert.deepEqual(Object.keys(detailBody.changes[0] ?? {}).sort(), [ "after", "before", "kind", "path", "position", ]); const requestedB = await runtime.request(`/api/v1/audit?scopeId=${companyB.id}`, { headers }); assert.equal(requestedB.status, 200); assert.equal((requestedB.body as { items: unknown[] }).items.length, 0); assert.equal((await runtime.request("/api/v1/audit/entry-b", { headers })).status, 404); assert.equal((await runtime.request("/api/v1/audit/entry-global", { headers })).status, 404); assert.equal((await runtime.request("/api/v1/audit/entry-missing", { headers })).status, 404); for (const query of [ "unknownField=x", "pageNumber=-1", "pageSize=101", "kind=invalid", "orderBy=invalid", "order=invalid", "from=invalid", "to=invalid", ]) assert.equal((await runtime.request(`/api/v1/audit?${query}`, { headers })).status, 422); });