import path from "path"; import test from "ava"; import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import { AnalysisKind, CodeScanning, compatibilityMatrix, RiskAssessment, getAnalysisConfig, getAnalysisKinds, parseAnalysisKinds, supportedAnalysisKinds, } from "./analyses"; import { EnvVar } from "./environment"; import { getRunnerLogger } from "./logging"; import { createFeatures, setupTests } from "./testing-utils"; import { AssessmentPayload } from "./upload-lib/types"; import { ConfigurationError } from "./util"; setupTests(test); test("All known analysis kinds can be parsed successfully", async (t) => { for (const analysisKind of supportedAnalysisKinds) { t.deepEqual(await parseAnalysisKinds(analysisKind), [analysisKind]); } }); test("Parsing analysis kinds returns unique results", async (t) => { const analysisKinds = await parseAnalysisKinds( "code-scanning,code-quality,code-scanning", ); t.deepEqual(analysisKinds, [ AnalysisKind.CodeScanning, AnalysisKind.CodeQuality, ]); }); test("Parsing an unknown analysis kind fails with a configuration error", async (t) => { await t.throwsAsync(parseAnalysisKinds("code-scanning,foo"), { instanceOf: ConfigurationError, }); }); test("Parsing analysis kinds requires at least one analysis kind", async (t) => { await t.throwsAsync(parseAnalysisKinds(","), { instanceOf: ConfigurationError, }); }); test.serial( "getAnalysisKinds - returns expected analysis kinds for `analysis-kinds` input", async (t) => { process.env[EnvVar.TEST_MODE] = "true"; const features = createFeatures([]); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); requiredInputStub .withArgs("analysis-kinds") .returns("code-scanning,code-quality"); const result = await getAnalysisKinds( getRunnerLogger(true), features, true, ); t.assert(result.includes(AnalysisKind.CodeScanning)); t.assert(result.includes(AnalysisKind.CodeQuality)); }, ); test.serial( "getAnalysisKinds - throws for multiple analysis kinds outside of test mode", async (t) => { process.env[EnvVar.TEST_MODE] = "false"; const features = createFeatures([]); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); requiredInputStub .withArgs("analysis-kinds") .returns("code-scanning,code-quality"); await t.throwsAsync( getAnalysisKinds(getRunnerLogger(true), features, true), { instanceOf: ConfigurationError, }, ); }, ); test.serial( "getAnalysisKinds - includes `code-quality` when deprecated `quality-queries` input is used", async (t) => { process.env[EnvVar.TEST_MODE] = "true"; const features = createFeatures([]); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); requiredInputStub.withArgs("analysis-kinds").returns("code-scanning"); const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput"); optionalInputStub.withArgs("quality-queries").returns("code-quality"); const result = await getAnalysisKinds( getRunnerLogger(true), features, true, ); t.assert(result.includes(AnalysisKind.CodeScanning)); t.assert(result.includes(AnalysisKind.CodeQuality)); }, ); test.serial( "getAnalysisKinds - throws if `analysis-kinds` input is invalid", async (t) => { const features = createFeatures([]); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing"); await t.throwsAsync( getAnalysisKinds(getRunnerLogger(true), features, true), ); }, ); // Test the compatibility matrix by looping through all analysis kinds. const analysisKinds = Object.values(AnalysisKind); for (let i = 0; i < analysisKinds.length; i++) { const analysisKind = analysisKinds[i]; for (let j = i + 1; j < analysisKinds.length; j++) { const otherAnalysis = analysisKinds[j]; if (analysisKind === otherAnalysis) continue; if (compatibilityMatrix[analysisKind].has(otherAnalysis)) { test.serial( `getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`, async (t) => { process.env[EnvVar.TEST_MODE] = "true"; const features = createFeatures([]); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); requiredInputStub .withArgs("analysis-kinds") .returns([analysisKind, otherAnalysis].join(",")); const result = await getAnalysisKinds( getRunnerLogger(true), features, true, ); t.is(result.length, 2); }, ); } else { test.serial( `getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`, async (t) => { const features = createFeatures([]); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); requiredInputStub .withArgs("analysis-kinds") .returns([analysisKind, otherAnalysis].join(",")); await t.throwsAsync( getAnalysisKinds(getRunnerLogger(true), features, true), { instanceOf: ConfigurationError, message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`, }, ); }, ); } } } test("Code Scanning configuration does not accept other SARIF extensions", (t) => { for (const analysisKind of supportedAnalysisKinds) { if (analysisKind === AnalysisKind.CodeScanning) continue; const analysis = getAnalysisConfig(analysisKind); const sarifPath = path.join("path", "to", `file${analysis.sarifExtension}`); // The Code Scanning configuration's `sarifPredicate` should not accept a path which // ends in a different configuration's `sarifExtension`. t.false(CodeScanning.sarifPredicate(sarifPath)); } }); test.serial( "Risk Assessment configuration transforms SARIF upload payload", (t) => { process.env[EnvVar.RISK_ASSESSMENT_ID] = "1"; const payload = RiskAssessment.transformPayload({ commit_oid: "abc", sarif: "sarif", ref: "ref", workflow_run_attempt: 1, workflow_run_id: 1, checkout_uri: "uri", tool_names: [], }) as AssessmentPayload; const expected: AssessmentPayload = { sarif: "sarif", assessment_id: 1 }; t.deepEqual(expected, payload); }, ); test.serial( "Risk Assessment configuration throws for negative assessment IDs", (t) => { process.env[EnvVar.RISK_ASSESSMENT_ID] = "-1"; t.throws( () => RiskAssessment.transformPayload({ commit_oid: "abc", sarif: "sarif", ref: "ref", workflow_run_attempt: 1, workflow_run_id: 1, checkout_uri: "uri", tool_names: [], }), { instanceOf: Error, message: (msg) => msg.startsWith(`${EnvVar.RISK_ASSESSMENT_ID} must not be negative: `), }, ); }, ); test.serial("Risk Assessment configuration throws for invalid IDs", (t) => { process.env[EnvVar.RISK_ASSESSMENT_ID] = "foo"; t.throws( () => RiskAssessment.transformPayload({ commit_oid: "abc", sarif: "sarif", ref: "ref", workflow_run_attempt: 1, workflow_run_id: 1, checkout_uri: "uri", tool_names: [], }), { instanceOf: Error, message: (msg) => msg.startsWith(`${EnvVar.RISK_ASSESSMENT_ID} must not be NaN: `), }, ); });