Spaces:
Runtime error
Runtime error
| // 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); | |
| }); | |