flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/apps/web/app/lib/audit.test.ts

103 lines4,097 bytesCodeBlame
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { AuditEntry } from "@g1t/contracts";
5
6import {
7 actorLabel,
8 exportName,
9 filterHref,
10 parseFilters,
11 retainedSince,
12 ruleLabel,
13 targetLabel,
14 toCsv,
15 toQuery,
16 visibilityFor,
17} from "./audit.ts";
18
19const entry: AuditEntry = {
20 id: "aud_1",
21 time: "2026-10-04T12:00:00.000Z",
22 actorKind: "agent",
23 actor: "g1t-agent",
24 actorId: "usr_g1t_agent",
25 agent: "g1t-agent",
26 onBehalfOf: "syntaqx",
27 runId: "run_1",
28 runKind: "implement",
29 credentialId: "tok_1",
30 action: "merge_pull_request",
31 surface: "mcp",
32 workspace: "acme",
33 repo: "acme/rocket",
34 number: 12,
35 gitRef: null,
36 path: null,
37 outcome: "denied",
38 rule: "never",
39 result: "forbidden",
40 message: 'A g1t agent\'s token can never use merge_pull_request: "merging" is for people, too.',
41 requestId: "8c1f",
42};
43
44test("owners see everything, members their projects, others nothing", () => {
45 assert.deepEqual(visibilityFor("owner", "ana"), { kind: "all" });
46 assert.deepEqual(visibilityFor("member", "ana"), { kind: "projects", username: "ana" });
47 assert.equal(visibilityFor(null, "ana"), null);
48});
49
50test("filters are read from the address, and nonsense is dropped", () => {
51 const filters = parseFilters(
52 new URLSearchParams("actor=syntaqx&outcome=maybe&kind=agent&from=2026-10-01&to=yesterday&project=rocket"),
53 );
54 assert.equal(filters.actor, "syntaqx");
55 assert.equal(filters.outcome, "");
56 assert.equal(filters.kind, "agent");
57 assert.equal(filters.from, "2026-10-01");
58 assert.equal(filters.to, "");
59 const query = toQuery("acme", { kind: "all" }, { ...filters, to: "2026-10-04" }, 100);
60 assert.equal(query.repo, "acme/rocket");
61 assert.equal(query.since, "2026-10-01T00:00:00.000Z");
62 // The end day is inclusive.
63 assert.equal(query.until, "2026-10-05T00:00:00.000Z");
64 assert.equal(query.actorKind, "agent");
65 assert.equal(query.outcome, null);
66});
67
68test("links keep the other filters", () => {
69 const filters = parseFilters(new URLSearchParams("actor=ana&outcome=denied"));
70 assert.equal(filterHref("/acme/-/audit", filters, { before: "aud_9" }), "/acme/-/audit?actor=ana&outcome=denied&before=aud_9");
71 assert.equal(filterHref("/acme/-/audit", parseFilters(new URLSearchParams())), "/acme/-/audit");
72});
73
74test("an agent is shown with whom it acted for", () => {
75 assert.equal(actorLabel(entry), "g1t-agent on behalf of syntaqx");
76 assert.equal(actorLabel({ actor: "ana", agent: null, onBehalfOf: null }), "ana");
77 assert.equal(targetLabel(entry), "acme/rocket#12");
78 assert.equal(targetLabel({ ...entry, number: null, gitRef: "refs/heads/fix" }), "acme/rocket refs/heads/fix");
79 assert.equal(ruleLabel("never"), "never allowed for agents");
80 assert.equal(ruleLabel("run:implement/tools"), "implement run tools");
81 assert.equal(ruleLabel("run:update/runner:push"), "update run runner (push)");
82});
83
84test("the CSV quotes what it must and keeps formulas as text", () => {
85 const csv = toCsv([entry, { ...entry, id: "aud_2", path: "=HYPERLINK(1)", message: null }]);
86 const lines = csv.trimEnd().split("\r\n");
87 assert.equal(lines.length, 3);
88 assert.ok(lines[0].startsWith("id,time,workspace,actorKind,actor"));
89 assert.ok(lines[1].includes('"A g1t agent\'s token can never use merge_pull_request: ""merging"" is for people, too."'));
90 assert.ok(lines[2].includes("'=HYPERLINK(1)"));
91 assert.equal(exportName("acme", "csv", new Date("2026-10-04T23:00:00Z")), "acme-audit-2026-10-04.csv");
92});
93
94test("the log reads back only as far as the plan keeps it", () => {
95 const now = Date.parse("2026-10-31T00:00:00.000Z");
96 // 30 days without Team.
97 assert.equal(retainedSince(null, 30, now), "2026-10-01T00:00:00.000Z");
98 assert.equal(retainedSince("2026-01-01T00:00:00.000Z", 30, now), "2026-10-01T00:00:00.000Z");
99 // A later start than the window is kept.
100 assert.equal(retainedSince("2026-10-20T00:00:00.000Z", 30, now), "2026-10-20T00:00:00.000Z");
101 // A year on Team.
102 assert.equal(retainedSince("2026-01-01T00:00:00.000Z", 365, now), "2026-01-01T00:00:00.000Z");
103});