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

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