// tests/unit/build/check-codeql-ratchet.test.ts // TDD unit tests for scripts/check/check-codeql-ratchet.mjs — Task 7.3 CodeQL ratchet. // // Strategy: test the exported pure function without calling the GitHub API. // All fixtures are synthetic GitHub API responses. // - parseCodeQLAlerts() — filters + counts open, non-dismissed CodeQL alerts // (Hard Rule #14: dismissed alerts do not count) import test from "node:test"; import assert from "node:assert/strict"; // @ts-expect-error — .mjs helper has no type declarations; runtime shape is known. import { parseCodeQLAlerts, evaluateCodeqlRatchet, } from "../../../scripts/check/check-codeql-ratchet.mjs"; type RatchetVerdict = { regressed: boolean; improved: boolean }; const evaluate = evaluateCodeqlRatchet as (current: number, baseline: number) => RatchetVerdict; // --------------------------------------------------------------------------- // Fixtures — synthetic GitHub code-scanning/alerts API responses // --------------------------------------------------------------------------- /** Helper to build a minimal alert object. */ function makeAlert( overrides: { number?: number; state?: "open" | "dismissed" | "fixed"; tool?: string; ruleId?: string; severity?: string; securitySeverity?: string; dismissedReason?: string | null; } = {} ) { return { number: overrides.number ?? 1, state: overrides.state ?? "open", dismissed_reason: overrides.dismissedReason ?? null, dismissed_at: overrides.dismissedReason ? "2026-01-01T00:00:00Z" : null, tool: { name: overrides.tool ?? "CodeQL", guid: null, version: "2.16.0", }, rule: { id: overrides.ruleId ?? "js/sql-injection", name: overrides.ruleId ?? "SQL Injection", severity: overrides.severity ?? "error", security_severity_level: overrides.securitySeverity ?? "high", description: "Vulnerability description", }, most_recent_instance: { ref: "refs/heads/main", state: overrides.state ?? "open", }, created_at: "2026-01-01T00:00:00Z", updated_at: "2026-01-02T00:00:00Z", url: "https://api.github.com/repos/owner/repo/code-scanning/alerts/1", html_url: "https://github.com/owner/repo/security/code-scanning/1", }; } // --------------------------------------------------------------------------- // parseCodeQLAlerts — input inválido // --------------------------------------------------------------------------- test("parseCodeQLAlerts: null retorna alertCount=0", () => { const result = parseCodeQLAlerts(null); assert.equal(result.alertCount, 0); assert.deepEqual(result.bySeverity, {}); assert.deepEqual(result.byRule, {}); }); test("parseCodeQLAlerts: undefined retorna alertCount=0", () => { const result = parseCodeQLAlerts(undefined as unknown as null); assert.equal(result.alertCount, 0); }); test("parseCodeQLAlerts: objeto (não-array) retorna alertCount=0", () => { const result = parseCodeQLAlerts({ number: 1, state: "open" } as unknown as null); assert.equal(result.alertCount, 0); }); test("parseCodeQLAlerts: string retorna alertCount=0", () => { const result = parseCodeQLAlerts("open" as unknown as null); assert.equal(result.alertCount, 0); }); // --------------------------------------------------------------------------- // parseCodeQLAlerts — array vazio // --------------------------------------------------------------------------- test("parseCodeQLAlerts: array vazio retorna alertCount=0", () => { const result = parseCodeQLAlerts([]); assert.equal(result.alertCount, 0); }); // --------------------------------------------------------------------------- // parseCodeQLAlerts — Hard Rule #14: dismissed alerts don't count // --------------------------------------------------------------------------- test("parseCodeQLAlerts: alerta dismissed NÃO conta (Hard Rule #14)", () => { const alerts = [makeAlert({ state: "dismissed", dismissedReason: "false positive" })]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 0, "dismissed alert must not be counted"); }); test("parseCodeQLAlerts: alerta dismissed com razão 'used in tests' NÃO conta", () => { const alerts = [makeAlert({ state: "dismissed", dismissedReason: "used in tests" })]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 0); }); test("parseCodeQLAlerts: alerta dismissed independente da razão NÃO conta", () => { const alerts = [ makeAlert({ state: "dismissed", dismissedReason: "wont fix" }), makeAlert({ number: 2, state: "dismissed", dismissedReason: "false positive" }), makeAlert({ number: 3, state: "dismissed", dismissedReason: "used in tests" }), ]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 0, "all dismissed states must be excluded"); }); // --------------------------------------------------------------------------- // parseCodeQLAlerts — estado fixed não conta // --------------------------------------------------------------------------- test("parseCodeQLAlerts: alerta fixed NÃO conta", () => { const alerts = [makeAlert({ state: "fixed" as "open" | "dismissed" | "fixed" })]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 0, "fixed alerts are not open, must not count"); }); // --------------------------------------------------------------------------- // parseCodeQLAlerts — somente alertas CodeQL (filtra outras ferramentas) // --------------------------------------------------------------------------- test("parseCodeQLAlerts: alertas de outras ferramentas são ignorados", () => { const alerts = [ makeAlert({ tool: "Semgrep", ruleId: "semgrep-rule-1" }), makeAlert({ number: 2, tool: "ESLint", ruleId: "eslint-rule-1" }), ]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 0, "only CodeQL alerts should be counted"); }); test("parseCodeQLAlerts: tool 'CodeQL' (maiúsculas) conta", () => { const alerts = [makeAlert({ tool: "CodeQL" })]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 1); }); test("parseCodeQLAlerts: tool 'codeql' (minúsculas) conta (case-insensitive)", () => { const alerts = [makeAlert({ tool: "codeql" })]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 1); }); test("parseCodeQLAlerts: tool 'CodeQL Community' também conta (contém 'codeql')", () => { const alerts = [makeAlert({ tool: "CodeQL Community" })]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 1); }); // --------------------------------------------------------------------------- // parseCodeQLAlerts — contagem de alertas open // --------------------------------------------------------------------------- test("parseCodeQLAlerts: 1 alerta open CodeQL retorna alertCount=1", () => { const alerts = [makeAlert({ state: "open" })]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 1); }); test("parseCodeQLAlerts: 3 alertas open retorna alertCount=3", () => { const alerts = [ makeAlert({ number: 1, state: "open" }), makeAlert({ number: 2, state: "open", ruleId: "js/xss" }), makeAlert({ number: 3, state: "open", ruleId: "js/path-injection" }), ]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 3); }); test("parseCodeQLAlerts: mix de open, dismissed, fixed — conta só open", () => { const alerts = [ makeAlert({ number: 1, state: "open" }), makeAlert({ number: 2, state: "dismissed", dismissedReason: "false positive" }), makeAlert({ number: 3, state: "fixed" as "open" | "dismissed" | "fixed" }), makeAlert({ number: 4, state: "open", ruleId: "js/xss" }), ]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 2, "only 2 open, non-dismissed alerts"); }); test("parseCodeQLAlerts: mix de CodeQL e outras ferramentas — conta só CodeQL", () => { const alerts = [ makeAlert({ number: 1, tool: "CodeQL", state: "open" }), makeAlert({ number: 2, tool: "Semgrep", state: "open" }), makeAlert({ number: 3, tool: "CodeQL", state: "open", ruleId: "js/xss" }), ]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 2, "only CodeQL open alerts"); }); // --------------------------------------------------------------------------- // parseCodeQLAlerts — severidade // --------------------------------------------------------------------------- test("parseCodeQLAlerts: coleta bySeverity de security_severity_level", () => { const alerts = [ makeAlert({ number: 1, securitySeverity: "critical" }), makeAlert({ number: 2, securitySeverity: "high" }), makeAlert({ number: 3, securitySeverity: "medium" }), makeAlert({ number: 4, securitySeverity: "high" }), // segundo high ]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 4); assert.equal(result.bySeverity["critical"], 1); assert.equal(result.bySeverity["high"], 2); assert.equal(result.bySeverity["medium"], 1); }); test("parseCodeQLAlerts: alerta sem security_severity_level usa rule.severity", () => { const alerts = [ { ...makeAlert({ number: 1 }), rule: { id: "js/unused-local-variable", name: "Unused variable", severity: "warning", // sem security_severity_level description: "Local var not used", }, }, ]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 1); assert.ok( "warning" in result.bySeverity || "unknown" in result.bySeverity, "severity should be captured" ); }); // --------------------------------------------------------------------------- // parseCodeQLAlerts — byRule // --------------------------------------------------------------------------- test("parseCodeQLAlerts: agrupa por rule.id em byRule", () => { const alerts = [ makeAlert({ number: 1, ruleId: "js/sql-injection" }), makeAlert({ number: 2, ruleId: "js/xss" }), makeAlert({ number: 3, ruleId: "js/sql-injection" }), // segundo do mesmo rule ]; const result = parseCodeQLAlerts(alerts); assert.equal(result.byRule["js/sql-injection"], 2); assert.equal(result.byRule["js/xss"], 1); }); test("parseCodeQLAlerts: alerta sem rule retorna byRule com 'unknown'", () => { const alert = { number: 1, state: "open", dismissed_reason: null, dismissed_at: null, tool: { name: "CodeQL", guid: null, version: "2.16.0" }, // sem rule }; const result = parseCodeQLAlerts([alert]); assert.equal(result.alertCount, 1); assert.ok("unknown" in result.byRule); }); // --------------------------------------------------------------------------- // parseCodeQLAlerts — dismissed não contamina contagens // --------------------------------------------------------------------------- test("parseCodeQLAlerts: dismissed com mesmo ruleId que open — dismissed não aparece em byRule", () => { const alerts = [ makeAlert({ number: 1, state: "open", ruleId: "js/sql-injection" }), makeAlert({ number: 2, state: "dismissed", ruleId: "js/sql-injection", dismissedReason: "false positive", }), ]; const result = parseCodeQLAlerts(alerts); assert.equal(result.alertCount, 1); assert.equal(result.byRule["js/sql-injection"], 1, "only the open alert should appear in byRule"); }); // --------------------------------------------------------------------------- // evaluateCodeqlRatchet — ratchet direction:down (Task 7.3 promote to blocking) // Mirror of evaluateDeadCode: regression when measured > baseline; the baseline // is 0 (clean), so ANY open CodeQL alert is a regression that blocks. // --------------------------------------------------------------------------- test("evaluateCodeqlRatchet: equal to baseline passes (0 vs 0 — clean)", () => { const r = evaluate(0, 0); assert.equal(r.regressed, false); assert.equal(r.improved, false); }); test("evaluateCodeqlRatchet: one more alert than baseline 0 is a regression", () => { const r = evaluate(1, 0); assert.equal(r.regressed, true, "a single new open CodeQL alert must block"); assert.equal(r.improved, false); }); test("evaluateCodeqlRatchet: fewer alerts than a non-zero baseline is an improvement", () => { const r = evaluate(2, 5); assert.equal(r.regressed, false); assert.equal(r.improved, true); }); test("evaluateCodeqlRatchet: zero alerts against a non-zero baseline is a maximum improvement", () => { const r = evaluate(0, 5); assert.equal(r.regressed, false); assert.equal(r.improved, true); }); test("evaluateCodeqlRatchet: strict integer comparison — any increase regresses", () => { assert.equal(evaluate(6, 5).regressed, true); assert.equal(evaluate(5, 5).regressed, false); assert.equal(evaluate(4, 5).regressed, false); });