Files
vscode-codeql/extensions/ql-vscode/test/pure-tests/sarif-processing.test.ts
Koen Vlaswinkel f5c39d7931 Improve error message for empty array
When `jest-codemods` was run, it replaced the error message `array.join`
by a comment for the error message. Since Jest does not support custom
error messages out-of-the-box, this will instead do an equality check
with an empty array, which will ensure that the received array is
printed.
2022-11-22 15:54:22 +01:00

693 lines
18 KiB
TypeScript

import * as sarif from "sarif";
import {
extractAnalysisAlerts,
tryGetRule,
tryGetSeverity,
} from "../../src/remote-queries/sarif-processing";
import {
AnalysisMessage,
AnalysisMessageLocationToken,
} from "../../src/remote-queries/shared/analysis-result";
describe("SARIF processing", () => {
describe("tryGetRule", () => {
describe("Using the tool driver", () => {
it("should return undefined if no rule has been set on the result", () => {
const result = {
message: "msg",
// Rule is missing here.
} as sarif.Result;
const sarifRun = {
results: [result],
} as sarif.Run;
const rule = tryGetRule(sarifRun, result);
expect(rule).toBeUndefined();
});
it("should return undefined if rule missing from tool driver", () => {
const result = {
message: "msg",
rule: {
id: "NonExistentRule",
},
} as sarif.Result;
const sarifRun = {
results: [result],
tool: {
driver: {
rules: [
// No rule with id 'NonExistentRule' is set here.
{
id: "A",
},
{
id: "B",
},
],
},
},
} as sarif.Run;
const rule = tryGetRule(sarifRun, result);
expect(rule).toBeUndefined();
});
it("should return rule if it has been set on the tool driver", () => {
const result = {
message: "msg",
rule: {
id: "B",
},
} as sarif.Result;
const sarifRun = {
results: [result],
tool: {
driver: {
rules: [
{
id: "A",
},
result.rule,
],
},
},
} as sarif.Run;
const rule = tryGetRule(sarifRun, result);
expect(rule).toBeTruthy();
expect(rule!.id).toBe(result!.rule!.id);
});
});
describe("Using the tool extensions", () => {
it("should return undefined if rule index not set", () => {
const result = {
message: "msg",
rule: {
// The rule index should be set here.
toolComponent: {
index: 1,
},
},
} as sarif.Result;
const sarifRun = {
results: [result],
tool: {
extensions: [
{
name: "foo",
rules: [
{
id: "A",
},
{
id: "B",
},
],
},
{
name: "bar",
rules: [
{
id: "C",
},
{
id: "D",
},
],
},
],
},
} as sarif.Run;
const rule = tryGetRule(sarifRun, result);
expect(rule).toBeUndefined();
});
it("should return undefined if tool component index not set", () => {
const result = {
message: "msg",
rule: {
index: 1,
toolComponent: {
// The tool component index should be set here.
},
},
} as sarif.Result;
const sarifRun = {
results: [result],
tool: {
extensions: [
{
name: "foo",
rules: [
{
id: "A",
},
{
id: "B",
},
],
},
{
name: "bar",
rules: [
{
id: "C",
},
{
id: "D",
},
],
},
],
},
} as sarif.Run;
const rule = tryGetRule(sarifRun, result);
expect(rule).toBeUndefined();
});
it("should return undefined if tool extensions not set", () => {
const result = {
message: "msg",
rule: {
index: 1,
toolComponent: {
index: 1,
},
},
} as sarif.Result;
const sarifRun = {
results: [result],
tool: {
// Extensions should be set here.
},
} as sarif.Run;
const rule = tryGetRule(sarifRun, result);
expect(rule).toBeUndefined();
});
it("should return undefined if tool extensions do not contain index", () => {
const result = {
message: "msg",
rule: {
index: 1,
toolComponent: {
index: 1,
},
},
} as sarif.Result;
const sarifRun = {
results: [result],
tool: {
extensions: [
{
name: "foo",
rules: [
{
id: "A",
},
{
id: "B",
},
],
},
// There should be one more extension here (index 1).
],
},
} as sarif.Run;
const rule = tryGetRule(sarifRun, result);
expect(rule).toBeUndefined();
});
it("should return rule if all information is defined", () => {
const result = {
message: "msg",
ruleIndex: 1,
rule: {
index: 1,
toolComponent: {
index: 1,
},
},
} as sarif.Result;
const sarifRun = {
results: [result],
tool: {
extensions: [
{
name: "foo",
rules: [
{
id: "A",
},
{
id: "B",
},
],
},
{
name: "bar",
rules: [
{
id: "C",
},
{
id: "D",
},
],
},
],
},
} as sarif.Run;
const rule = tryGetRule(sarifRun, result);
expect(rule).toBeTruthy();
expect(rule!.id).toBe("D");
});
});
});
describe("tryGetSeverity", () => {
it("should return undefined if no rule set", () => {
const result = {
message: "msg",
} as sarif.Result;
// The rule should be set here.
const rule: sarif.ReportingDescriptor | undefined = undefined;
const sarifRun = {
results: [result],
} as sarif.Run;
const severity = tryGetSeverity(sarifRun, result, rule);
expect(severity).toBeUndefined();
});
it("should return undefined if severity not set on rule", () => {
const result = {
message: "msg",
rule: {
id: "A",
},
} as sarif.Result;
const rule = {
id: "A",
properties: {
// Severity not set
},
} as sarif.ReportingDescriptor;
const sarifRun = {
results: [result],
tool: {
driver: {
rules: [rule, result.rule],
},
},
} as sarif.Run;
const severity = tryGetSeverity(sarifRun, result, rule);
expect(severity).toBeUndefined();
});
const severityMap = {
recommendation: "Recommendation",
warning: "Warning",
error: "Error",
};
Object.entries(severityMap).forEach(([sarifSeverity, parsedSeverity]) => {
it(`should get ${parsedSeverity} severity`, () => {
const result = {
message: "msg",
rule: {
id: "A",
},
} as sarif.Result;
const rule = {
id: "A",
properties: {
"problem.severity": sarifSeverity,
},
} as sarif.ReportingDescriptor;
const sarifRun = {
results: [result],
tool: {
driver: {
rules: [rule, result.rule],
},
},
} as sarif.Run;
const severity = tryGetSeverity(sarifRun, result, rule);
expect(severity).toBe(parsedSeverity);
});
});
});
describe("extractAnalysisAlerts", () => {
const fakefileLinkPrefix = "https://example.com";
it("should not return any results if no runs found in the SARIF", () => {
const sarif = {
// Runs are missing here.
} as sarif.Log;
const result = extractAnalysisAlerts(sarif, fakefileLinkPrefix);
expect(result).toBeTruthy();
expect(result.alerts.length).toBe(0);
});
it("should not return any results for runs that have no results", () => {
const sarif = {
runs: [
{
results: [],
},
{
// Results are missing here.
},
],
} as sarif.Log;
const result = extractAnalysisAlerts(sarif, fakefileLinkPrefix);
expect(result).toBeTruthy();
expect(result.alerts.length).toBe(0);
});
it("should return errors for results that have no message", () => {
const sarif = buildValidSarifLog();
sarif.runs![0]!.results![0]!.message.text = undefined;
const result = extractAnalysisAlerts(sarif, fakefileLinkPrefix);
expect(result).toBeTruthy();
expect(result.errors.length).toBe(1);
expectResultParsingError(result.errors[0]);
});
it("should not return errors for result locations with no snippet", () => {
const sarif = buildValidSarifLog();
sarif.runs![0]!.results![0]!.locations![0]!.physicalLocation!.contextRegion!.snippet =
undefined;
const result = extractAnalysisAlerts(sarif, fakefileLinkPrefix);
const expectedCodeSnippet = {
startLine: result.alerts[0].codeSnippet!.startLine,
endLine: result.alerts[0].codeSnippet!.endLine,
text: "",
};
const actualCodeSnippet = result.alerts[0].codeSnippet;
expect(result).toBeTruthy();
expectNoParsingError(result);
expect(actualCodeSnippet).toEqual(expectedCodeSnippet);
});
it("should use highlightedRegion for result locations with no contextRegion", () => {
const sarif = buildValidSarifLog();
sarif.runs![0]!.results![0]!.locations![0]!.physicalLocation!.contextRegion =
undefined;
const result = extractAnalysisAlerts(sarif, fakefileLinkPrefix);
const expectedCodeSnippet = {
startLine: result.alerts[0].highlightedRegion!.startLine,
endLine: result.alerts[0].highlightedRegion!.endLine,
text: "",
};
const actualCodeSnippet = result.alerts[0].codeSnippet;
expect(result).toBeTruthy();
expectNoParsingError(result);
expect(actualCodeSnippet).toEqual(expectedCodeSnippet);
});
it("should not return errors for result locations with no region", () => {
const sarif = buildValidSarifLog();
sarif.runs![0]!.results![0]!.locations![0]!.physicalLocation!.region =
undefined;
const result = extractAnalysisAlerts(sarif, fakefileLinkPrefix);
expect(result).toBeTruthy();
expect(result.alerts.length).toBe(1);
expectNoParsingError(result);
});
it("should return errors for result locations with no physical location", () => {
const sarif = buildValidSarifLog();
sarif.runs![0]!.results![0]!.locations![0]!.physicalLocation!.artifactLocation =
undefined;
const result = extractAnalysisAlerts(sarif, fakefileLinkPrefix);
expect(result).toBeTruthy();
expect(result.errors.length).toBe(1);
expectResultParsingError(result.errors[0]);
});
it("should return results for all alerts", () => {
const sarif = {
version: "0.0.1" as sarif.Log.version,
runs: [
{
results: [
{
message: {
text: "msg1",
},
locations: [
{
physicalLocation: {
contextRegion: {
startLine: 10,
endLine: 12,
snippet: {
text: "foo",
},
},
region: {
startLine: 10,
startColumn: 1,
endColumn: 3,
},
artifactLocation: {
uri: "foo.js",
},
},
},
{
physicalLocation: {
contextRegion: {
startLine: 10,
endLine: 12,
snippet: {
text: "bar",
},
},
region: {
startLine: 10,
startColumn: 1,
endColumn: 3,
},
artifactLocation: {
uri: "bar.js",
},
},
},
],
},
{
message: {
text: "msg2",
},
locations: [
{
physicalLocation: {
contextRegion: {
startLine: 10,
endLine: 12,
snippet: {
text: "baz",
},
},
region: {
startLine: 10,
startColumn: 1,
endColumn: 3,
},
artifactLocation: {
uri: "baz.js",
},
},
},
],
},
],
},
],
} as sarif.Log;
const result = extractAnalysisAlerts(sarif, fakefileLinkPrefix);
expect(result).toBeTruthy();
expect(result.errors.length).toBe(0);
expect(result.alerts.length).toBe(3);
expect(
result.alerts.find(
(a) =>
getMessageText(a.message) === "msg1" &&
a.codeSnippet!.text === "foo",
),
).toBeTruthy();
expect(
result.alerts.find(
(a) =>
getMessageText(a.message) === "msg1" &&
a.codeSnippet!.text === "bar",
),
).toBeTruthy();
expect(
result.alerts.find(
(a) =>
getMessageText(a.message) === "msg2" &&
a.codeSnippet!.text === "baz",
),
).toBeTruthy();
expect(result.alerts.every((a) => a.severity === "Warning")).toBe(true);
});
it("should deal with complex messages", () => {
const sarif = buildValidSarifLog();
const messageText =
"This shell command depends on an uncontrolled [absolute path](1).";
sarif.runs![0]!.results![0]!.message!.text = messageText;
sarif.runs![0]!.results![0].relatedLocations = [
{
id: 1,
physicalLocation: {
artifactLocation: {
uri: "npm-packages/meteor-installer/config.js",
},
region: {
startLine: 35,
startColumn: 20,
endColumn: 60,
},
},
},
];
const result = extractAnalysisAlerts(sarif, fakefileLinkPrefix);
expect(result).toBeTruthy();
expect(result.errors.length).toBe(0);
expect(result.alerts.length).toBe(1);
const message = result.alerts[0].message;
expect(message.tokens.length).toBe(3);
expect(message.tokens[0].t).toBe("text");
expect(message.tokens[0].text).toBe(
"This shell command depends on an uncontrolled ",
);
expect(message.tokens[1].t).toBe("location");
expect(message.tokens[1].text).toBe("absolute path");
expect(
(message.tokens[1] as AnalysisMessageLocationToken).location,
).toEqual({
fileLink: {
fileLinkPrefix: fakefileLinkPrefix,
filePath: "npm-packages/meteor-installer/config.js",
},
highlightedRegion: {
startLine: 35,
startColumn: 20,
endLine: 35,
endColumn: 60,
},
});
expect(message.tokens[2].t).toBe("text");
expect(message.tokens[2].text).toBe(".");
});
});
function expectResultParsingError(msg: string) {
expect(msg.startsWith("Error when processing SARIF result")).toBe(true);
}
function expectNoParsingError(result: { errors: string[] }) {
const array = result.errors;
expect(array).toEqual([]);
}
function buildValidSarifLog(): sarif.Log {
return {
version: "2.1.0",
runs: [
{
results: [
{
message: {
text: "msg",
},
locations: [
{
physicalLocation: {
contextRegion: {
startLine: 10,
endLine: 12,
snippet: {
text: "Foo",
},
},
region: {
startLine: 10,
startColumn: 1,
endColumn: 3,
},
artifactLocation: {
uri: "foo.js",
},
},
},
],
},
],
},
],
} as sarif.Log;
}
function getMessageText(message: AnalysisMessage) {
return message.tokens.map((t) => t.text).join("");
}
});