addition of new dashboard

This commit is contained in:
2026-08-20 15:08:32 +00:00
parent 0622018d95
commit bf59249ed7
161 changed files with 13170 additions and 119 deletions

View File

@@ -0,0 +1,225 @@
import { jest } from "@jest/globals";
import { summarizeVerification, verifyMany } from "./verify.js";
// Build the record shape fetchAuditRecords produces.
const record = (auditId, sigCount = 1) => ({
auditId,
audit: { version: 1, hashType: "sha256", digest: `d${auditId}`, created: 100, eventId: "e1" },
signatures: Array.from({ length: sigCount }, (_, i) => ({ id: `alice@a`, jws: `x${i}` })),
});
// A verifier verdict as the canonical verifier returns it: the record we passed
// in, echoed back on `.audit`, plus its per-check results.
const verdict = (rec, results) => ({ audit: rec, results });
describe("summarizeVerification", () => {
it("reports no audit record when nothing was found", () => {
expect(summarizeVerification({ eventUuid: "e1", auditId: null }, [], null)).toEqual({
eventUuid: "e1",
auditId: null,
status: "no-audit",
verified: false,
signatureCount: 0,
auditCount: 0,
checks: null,
reason: "no audit record",
});
});
it("marks verified when the record's verdict is in the valid bucket", () => {
const r = record(9, 2);
const result = { data: { valid: [verdict(r, { idMatch: true, auditSig: true })], invalid: [] } };
expect(summarizeVerification({ eventUuid: null, auditId: 9 }, [r], result)).toEqual({
eventUuid: null,
auditId: 9,
status: "verified",
verified: true,
signatureCount: 2,
auditCount: 1,
checks: { idMatch: true, auditSig: true },
});
});
it("marks invalid and surfaces the failed checks", () => {
const r = record(9, 2);
const result = { data: { valid: [], invalid: [verdict(r, { auditSig: false })] } };
expect(summarizeVerification({ eventUuid: null, auditId: 9 }, [r], result)).toMatchObject({
status: "invalid",
verified: false,
checks: { auditSig: false },
});
});
// The regression this whole change is about: one event, two audit records.
describe("an event with several audit records", () => {
const produce = record(9);
const process = record(10);
const target = { eventUuid: "e1", auditId: null };
it("matches each verdict to its own record by audit id, not by position", () => {
// Deliberately out of order: `process` is valid, `produce` is not.
const result = {
data: {
valid: [verdict(process, { auditSig: true })],
invalid: [verdict(produce, { auditSig: false })],
},
};
const out = summarizeVerification(target, [produce, process], result);
expect(out).toMatchObject({ status: "invalid", verified: false, auditCount: 2 });
// The failing record's checks are what surface, not the first bucket's.
expect(out.checks).toEqual({ auditSig: false });
});
it("only reports verified when EVERY audit record passed", () => {
const allGood = {
data: { valid: [verdict(produce, { auditSig: true }), verdict(process, { auditSig: true })], invalid: [] },
};
expect(summarizeVerification(target, [produce, process], allGood)).toMatchObject({
status: "verified",
verified: true,
auditCount: 2,
signatureCount: 2,
});
});
});
});
// A fake pooled client that routes by SQL so we can exercise verifyMany's
// orchestration without a DB or the crypto engine. `entity` is matched first so
// the audit query's audit_signature JOIN can't shadow it.
function routingClient(routes) {
const query = jest.fn(async (sql, params) => {
if (/FROM entity/.test(sql)) return { rows: routes.entity ?? [] };
if (/FROM audit/.test(sql)) return { rows: routes.audit?.(sql, params) ?? [] };
return { rows: [] };
});
return { query, release: jest.fn(async () => {}) };
}
// One audit row as it comes back from Postgres (snake_case + aggregated JSON).
const auditRow = (auditId, eventId = "e1") => ({
audit_id: auditId,
version: 1,
event_id: eventId,
hash_type: "sha256",
digest: `d${auditId}`,
created: "100",
sender_id: "did:alice",
recipient_id: "did:bob",
signatures: [{ version: 1, id: "alice@a", signedon: "101", type: "audit", jws: "x" }],
});
describe("verifyMany", () => {
it("returns [] when there are no targets", async () => {
expect(await verifyMany(1, {})).toEqual([]);
expect(await verifyMany(1, { eventUuids: [], auditIds: [] })).toEqual([]);
});
it("verifies an event by assembling its audit records and calling the verifier", async () => {
const client = routingClient({
entity: [{ did_id: "did:alice", did_doc: { id: "did:alice" } }],
audit: () => [auditRow(9)],
});
const verify = jest.fn(async (input) => ({
data: { valid: [{ audit: input.audits[0], results: { ok: true } }], invalid: [] },
}));
const out = await verifyMany(1, { eventUuids: ["e1"] }, { acquire: async () => client, verify });
expect(out).toEqual([{
eventUuid: "e1",
auditId: null,
status: "verified",
verified: true,
signatureCount: 1,
auditCount: 1,
checks: { ok: true },
}]);
expect(verify).toHaveBeenCalledWith(
expect.objectContaining({
didDocs: [{ id: "did:alice" }],
audits: [expect.objectContaining({ auditId: 9, audit: expect.objectContaining({ eventId: "e1" }) })],
}),
1,
);
});
it("resolves only the DIDs the batch references, once, and never all entities", async () => {
const entityCalls = [];
const client = routingClient({
entity: [{ did_id: "did:alice", did_doc: { id: "did:alice" } }],
audit: (_sql, params) => [auditRow(params[1])],
});
const inner = client.query;
client.query = jest.fn(async (sql, params) => {
if (/FROM entity/.test(sql)) entityCalls.push(params);
return inner(sql, params);
});
const verify = jest.fn(async (input) => ({
data: { valid: [{ audit: input.audits[0], results: { ok: true } }], invalid: [] },
}));
await verifyMany(1, { auditIds: [9, 10] }, { acquire: async () => client, verify });
// One lookup for the batch, scoped to the two DIDs on those events.
expect(entityCalls).toHaveLength(1);
expect(entityCalls[0]).toEqual([1, ["did:alice", "did:bob"]]);
// Second target reused the cache rather than re-querying.
expect(verify.mock.calls[1][0].didDocs).toEqual([{ id: "did:alice" }]);
});
it("looks an audit-tab target up by audit_id, scoped to the user", async () => {
let seen;
const client = routingClient({
entity: [{ did_id: "did:alice", did_doc: { id: "did:alice" } }],
audit: (sql, params) => { seen = { sql, params }; return [auditRow(42)]; },
});
const verify = jest.fn(async (input) => ({
data: { valid: [{ audit: input.audits[0], results: { ok: true } }], invalid: [] },
}));
const out = await verifyMany(1, { auditIds: [42] }, { acquire: async () => client, verify });
expect(out[0]).toMatchObject({ auditId: 42, eventUuid: null, status: "verified" });
expect(seen.sql).toMatch(/a\.audit_id = \$2/);
expect(seen.sql).not.toMatch(/a\.event_id = \$2/);
// user_id is always $1 — an audit id alone never reaches another tenant's row.
expect(seen.params.slice(0, 2)).toEqual([1, 42]);
});
it("gives each audit record of one event its own independent result", async () => {
// Both rows belong to event e1; only audit 9 verifies.
const client = routingClient({
entity: [{ did_id: "did:alice", did_doc: { id: "did:alice" } }],
audit: (_sql, params) => [auditRow(params[1])],
});
const verify = jest.fn(async (input) => {
const rec = input.audits[0];
return rec.auditId === 9
? { data: { valid: [{ audit: rec, results: { ok: true } }], invalid: [] } }
: { data: { valid: [], invalid: [{ audit: rec, results: { ok: false } }] } };
});
const out = await verifyMany(1, { auditIds: [9, 10] }, { acquire: async () => client, verify });
expect(out).toHaveLength(2);
expect(out[0]).toMatchObject({ auditId: 9, verified: true });
expect(out[1]).toMatchObject({ auditId: 10, verified: false, status: "invalid" });
});
it("reports no audit record when the target has none", async () => {
const client = routingClient({ entity: [{ short_name: "alice@a" }], audit: () => [] });
const verify = jest.fn();
const out = await verifyMany(1, { eventUuids: ["missing"] }, { acquire: async () => client, verify });
expect(out[0]).toMatchObject({ status: "no-audit", verified: false, signatureCount: 0, reason: "no audit record" });
expect(verify).not.toHaveBeenCalled();
});
it("caps the batch at 200 targets regardless of input length", async () => {
const client = routingClient({ entity: [{ short_name: "a" }], audit: () => [] });
const eventUuids = Array.from({ length: 150 }, (_, i) => `id-${i}`);
const auditIds = Array.from({ length: 150 }, (_, i) => i + 1);
const out = await verifyMany(1, { eventUuids, auditIds }, { acquire: async () => client, verify: jest.fn() });
expect(out).toHaveLength(200);
});
});