| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import type { AlertActivity, SecretFinding, Vulnerability } from "@g1t/contracts"; |
| 5 | |
| 6 | import { |
| 7 | alertActivity, |
| 8 | alertCapability, |
| 9 | compareVersions, |
| 10 | countByState, |
| 11 | groupByPackage, |
| 12 | highestFix, |
| 13 | latestUpdate, |
| 14 | parseAlertState, |
| 15 | splitSecrets, |
| 16 | tabOf, |
| 17 | worstSeverity, |
| 18 | } from "./security-alerts.ts"; |
| 19 | |
| 20 | const secret = (over: Partial<SecretFinding> = {}): SecretFinding => ({ |
| 21 | id: "sec_1", |
| 22 | repoId: "r", |
| 23 | kind: "aws_access_key", |
| 24 | label: "an AWS access key", |
| 25 | path: "src/config.ts", |
| 26 | line: 4, |
| 27 | commit: "abcdef1234", |
| 28 | preview: "AKIA…WXYZ", |
| 29 | status: "open", |
| 30 | source: "history", |
| 31 | foundBy: null, |
| 32 | foundAt: "2026-10-01T10:00:00Z", |
| 33 | decidedBy: null, |
| 34 | reason: null, |
| 35 | decidedAt: null, |
| 36 | state: "open", |
| 37 | ...over, |
| 38 | }); |
| 39 | |
| 40 | const vuln = (over: Partial<Vulnerability> = {}): Vulnerability => ({ |
| 41 | id: "vul_1", |
| 42 | repoId: "r", |
| 43 | ecosystem: "npm", |
| 44 | package: "lodash", |
| 45 | version: "4.17.20", |
| 46 | manifest: "package-lock.json", |
| 47 | advisory: "GHSA-1", |
| 48 | osvId: "GHSA-1", |
| 49 | summary: "Prototype pollution", |
| 50 | severity: "high", |
| 51 | fixedVersion: "4.17.21", |
| 52 | status: "open", |
| 53 | issue: null, |
| 54 | foundAt: "2026-10-01T10:00:00Z", |
| 55 | fixedAt: null, |
| 56 | state: "open", |
| 57 | ...over, |
| 58 | }); |
| 59 | |
| 60 | test("the state parameter falls back to open", () => { |
| 61 | assert.equal(parseAlertState(null), "open"); |
| 62 | assert.equal(parseAlertState("dismissed"), "dismissed"); |
| 63 | assert.equal(parseAlertState("fixed"), "fixed"); |
| 64 | assert.equal(parseAlertState("nonsense"), "open"); |
| 65 | }); |
| 66 | |
| 67 | test("ids say which tab and which role an alert takes", () => { |
| 68 | assert.equal(tabOf("sec_9"), "secrets"); |
| 69 | assert.equal(tabOf("vul_9"), "dependencies"); |
| 70 | assert.equal(alertCapability("sec_9"), "manage_integrations"); |
| 71 | assert.equal(alertCapability("vul_9"), "push"); |
| 72 | }); |
| 73 | |
| 74 | test("alerts are counted by state", () => { |
| 75 | assert.deepEqual(countByState([secret(), secret({ state: "dismissed" }), secret({ state: "fixed" }), secret()]), { |
| 76 | open: 2, |
| 77 | dismissed: 1, |
| 78 | fixed: 1, |
| 79 | }); |
| 80 | }); |
| 81 | |
| 82 | test("likely test values are listed apart from real secrets", () => { |
| 83 | const real = secret({ id: "sec_real" }); |
| 84 | const fake = secret({ id: "sec_fake", testValue: "AWS's documented example key" }); |
| 85 | const { real: shown, tests } = splitSecrets([fake, real]); |
| 86 | assert.deepEqual(shown.map((s) => s.id), ["sec_real"]); |
| 87 | assert.deepEqual(tests.map((s) => s.id), ["sec_fake"]); |
| 88 | }); |
| 89 | |
| 90 | test("packages group their alerts and take the worst severity", () => { |
| 91 | const groups = groupByPackage([ |
| 92 | vuln({ id: "vul_1", severity: "medium" }), |
| 93 | vuln({ id: "vul_2", package: "express" }), |
| 94 | vuln({ id: "vul_3", severity: "critical", fixedVersion: "4.17.3" }), |
| 95 | ]); |
| 96 | assert.deepEqual(groups.map((g) => g.name), ["lodash", "express"]); |
| 97 | assert.equal(worstSeverity(groups[0].vulns), "critical"); |
| 98 | assert.equal(highestFix(groups[0].vulns), "4.17.21"); |
| 99 | }); |
| 100 | |
| 101 | test("versions compare number by number", () => { |
| 102 | assert.ok(compareVersions("4.17.10", "4.17.9") > 0); |
| 103 | assert.ok(compareVersions("1.2.0", "1.10.0") < 0); |
| 104 | assert.equal(highestFix([vuln({ fixedVersion: null })]), null); |
| 105 | }); |
| 106 | |
| 107 | test("the newest security update wins", () => { |
| 108 | const older = { state: "superseded" as const, target: "4.17.20", branch: null, pull: 3, issue: null, error: null, updatedAt: "2026-10-01T00:00:00Z" }; |
| 109 | const newer = { ...older, state: "open" as const, target: "4.17.21", pull: 14, updatedAt: "2026-10-02T00:00:00Z" }; |
| 110 | assert.equal(latestUpdate([vuln({ update: older }), vuln({ update: newer }), vuln()])?.pull, 14); |
| 111 | assert.equal(latestUpdate([vuln()]), null); |
| 112 | }); |
| 113 | |
| 114 | const row = (over: Partial<AlertActivity>): AlertActivity => ({ |
| 115 | id: "act_1", |
| 116 | alertId: "sec_1", |
| 117 | action: "dismissed", |
| 118 | actor: "syntaqx", |
| 119 | reason: "used_in_tests", |
| 120 | comment: "A fixture.", |
| 121 | number: null, |
| 122 | at: "2026-10-03T00:00:00Z", |
| 123 | ...over, |
| 124 | }); |
| 125 | |
| 126 | test("a secret's activity starts with when it was found", () => { |
| 127 | const entries = alertActivity(secret({ source: "push", status: "blocked", foundBy: "ana" }), [ |
| 128 | row({}), |
| 129 | row({ id: "act_2", alertId: "sec_other" }), |
| 130 | ]); |
| 131 | assert.deepEqual( |
| 132 | entries.map((e) => [e.actor, e.text]), |
| 133 | [ |
| 134 | ["ana", "pushed it, and the push was refused"], |
| 135 | ["syntaqx", "dismissed it"], |
| 136 | ], |
| 137 | ); |
| 138 | assert.equal(entries[1].reason, "used_in_tests"); |
| 139 | }); |
| 140 | |
| 141 | test("an older decision without a row is shown from the alert itself", () => { |
| 142 | const entries = alertActivity( |
| 143 | secret({ status: "allowed", state: "dismissed", decidedBy: "ana", decidedAt: "2026-10-02T00:00:00Z", reason: "Docs example." }), |
| 144 | [], |
| 145 | ); |
| 146 | assert.equal(entries.length, 2); |
| 147 | assert.equal(entries[0].text, "Found in the history"); |
| 148 | assert.equal(entries[0].actor, null); |
| 149 | assert.deepEqual([entries[1].actor, entries[1].comment, entries[1].reason], ["ana", "Docs example.", "false_positive"]); |
| 150 | // A row for the dismissal replaces it. |
| 151 | const covered = alertActivity( |
| 152 | secret({ status: "allowed", state: "dismissed", decidedBy: "ana", decidedAt: "2026-10-02T00:00:00Z" }), |
| 153 | [row({ actor: "ana" })], |
| 154 | ); |
| 155 | assert.equal(covered.filter((e) => e.text === "dismissed it").length, 1); |
| 156 | }); |
| 157 | |
| 158 | test("a dependency's activity links its pull request and issue", () => { |
| 159 | const entries = alertActivity(vuln({ state: "fixed", status: "fixed", fixedAt: "2026-10-05T00:00:00Z" }), [ |
| 160 | row({ id: "a", alertId: "vul_1", action: "update_opened", actor: "g1t", reason: null, comment: null, number: 14, at: "2026-10-02T00:00:00Z" }), |
| 161 | row({ id: "b", alertId: "vul_1", action: "update_needs_code", actor: "g1t", reason: null, comment: null, number: 15, at: "2026-10-03T00:00:00Z" }), |
| 162 | row({ id: "c", alertId: "vul_1", action: "update_merged", actor: "g1t", reason: null, comment: null, number: 14, at: "2026-10-04T00:00:00Z" }), |
| 163 | ]); |
| 164 | assert.deepEqual( |
| 165 | entries.map((e) => e.ref), |
| 166 | [null, { kind: "pull", number: 14 }, { kind: "issue", number: 15 }, { kind: "pull", number: 14 }], |
| 167 | ); |
| 168 | // A merged update already says it was fixed. |
| 169 | assert.ok(!entries.some((e) => e.text === "found it no longer vulnerable")); |
| 170 | }); |