@fabasoad/sarif-to-slack 1.3.5 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/security.yml +1 -0
- package/.github/workflows/send-sarif-to-slack.yml +39 -15
- package/README.md +8 -7
- package/dist/Logger.js +40 -30
- package/dist/SarifToSlackClient.d.ts +0 -1
- package/dist/SarifToSlackClient.d.ts.map +1 -1
- package/dist/SarifToSlackClient.js +11 -8
- package/dist/globalState.d.ts +2 -0
- package/dist/globalState.d.ts.map +1 -0
- package/dist/globalState.js +2 -0
- package/dist/index.cjs +111 -81
- package/dist/index.d.ts +40 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/model/Finding.js +7 -5
- package/dist/model/FindingArray.js +1 -1
- package/dist/model/SendIf.js +1 -1
- package/dist/model/SlackMessage.js +6 -6
- package/dist/model/color/Color.d.ts.map +1 -1
- package/dist/model/color/Color.js +1 -1
- package/dist/model/color/ColorIdentification.js +5 -5
- package/dist/model/color/ColorOptions.d.ts.map +1 -1
- package/dist/processors/CodeQLProcessor.js +1 -1
- package/dist/processors/CommonProcessor.js +1 -1
- package/dist/processors/ProcessorFactory.js +1 -1
- package/dist/processors/SnykProcessor.js +2 -1
- package/dist/representations/CompactGroupByRepresentation.js +1 -1
- package/dist/representations/CompactGroupByRunPerLevelRepresentation.js +1 -1
- package/dist/representations/CompactGroupByRunPerSeverityRepresentation.js +1 -1
- package/dist/representations/CompactGroupByRunRepresentation.js +1 -1
- package/dist/representations/CompactGroupBySarifPerLevelRepresentation.js +1 -1
- package/dist/representations/CompactGroupBySarifPerSeverityRepresentation.js +1 -1
- package/dist/representations/CompactGroupBySarifRepresentation.js +1 -1
- package/dist/representations/CompactGroupByToolNamePerLevelRepresentation.js +1 -1
- package/dist/representations/CompactGroupByToolNamePerSeverityRepresentation.js +1 -1
- package/dist/representations/CompactGroupByToolNameRepresentation.js +1 -1
- package/dist/representations/CompactTotalPerLevelRepresentation.js +1 -1
- package/dist/representations/CompactTotalPerSeverityRepresentation.js +1 -1
- package/dist/representations/CompactTotalRepresentation.js +1 -1
- package/dist/representations/Representation.js +1 -1
- package/dist/representations/RepresentationFactory.js +1 -1
- package/dist/representations/TableGroupByRunPerLevelRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupByRunPerLevelRepresentation.js +1 -1
- package/dist/representations/TableGroupByRunPerSeverityRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupByRunPerSeverityRepresentation.js +1 -1
- package/dist/representations/TableGroupByRunRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupByRunRepresentation.js +1 -1
- package/dist/representations/TableGroupBySarifPerLevelRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupBySarifPerLevelRepresentation.js +1 -1
- package/dist/representations/TableGroupBySarifPerSeverityRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupBySarifPerSeverityRepresentation.js +1 -1
- package/dist/representations/TableGroupBySarifRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupBySarifRepresentation.js +1 -1
- package/dist/representations/TableGroupByToolNamePerLevelRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupByToolNamePerLevelRepresentation.js +1 -1
- package/dist/representations/TableGroupByToolNamePerSeverityRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupByToolNamePerSeverityRepresentation.js +1 -1
- package/dist/representations/TableGroupByToolNameRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupByToolNameRepresentation.js +1 -1
- package/dist/representations/TableGroupRepresentation.d.ts +0 -1
- package/dist/representations/TableGroupRepresentation.d.ts.map +1 -1
- package/dist/representations/TableGroupRepresentation.js +3 -3
- package/dist/representations/table/Cell.d.ts.map +1 -1
- package/dist/representations/table/Cell.js +1 -1
- package/dist/representations/table/Column.d.ts +0 -1
- package/dist/representations/table/Column.d.ts.map +1 -1
- package/dist/representations/table/Column.js +4 -3
- package/dist/representations/table/Row.d.ts +0 -1
- package/dist/representations/table/Row.d.ts.map +1 -1
- package/dist/representations/table/Row.js +3 -3
- package/dist/representations/table/Table.d.ts.map +1 -1
- package/dist/representations/table/Table.js +1 -1
- package/dist/system.js +5 -5
- package/dist/tsdoc-metadata.json +1 -1
- package/dist/types.d.ts +29 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -1
- package/dist/utils/Comparators.js +1 -1
- package/dist/utils/ExtendedArray.js +1 -1
- package/dist/utils/FileUtils.js +2 -2
- package/dist/utils/SarifUtils.js +1 -1
- package/dist/utils/StringUtils.js +1 -1
- package/etc/sarif-to-slack.api.md +21 -1
- package/jest.config.json +4 -4
- package/package.json +5 -4
- package/src/Logger.ts +50 -34
- package/src/SarifToSlackClient.ts +73 -68
- package/src/globalState.ts +11 -0
- package/src/index.ts +23 -12
- package/src/model/Finding.ts +36 -35
- package/src/model/FindingArray.ts +5 -5
- package/src/model/SendIf.ts +25 -25
- package/src/model/SlackMessage.ts +49 -49
- package/src/model/color/Color.ts +7 -7
- package/src/model/color/ColorIdentification.ts +77 -77
- package/src/model/color/ColorOptions.ts +1 -1
- package/src/processors/CodeQLProcessor.ts +3 -3
- package/src/processors/CommonProcessor.ts +24 -24
- package/src/processors/ProcessorFactory.ts +9 -9
- package/src/processors/SnykProcessor.ts +3 -2
- package/src/representations/CompactGroupByRepresentation.ts +20 -20
- package/src/representations/CompactGroupByRunPerLevelRepresentation.ts +2 -2
- package/src/representations/CompactGroupByRunPerSeverityRepresentation.ts +2 -2
- package/src/representations/CompactGroupByRunRepresentation.ts +10 -10
- package/src/representations/CompactGroupBySarifPerLevelRepresentation.ts +2 -2
- package/src/representations/CompactGroupBySarifPerSeverityRepresentation.ts +2 -2
- package/src/representations/CompactGroupBySarifRepresentation.ts +11 -11
- package/src/representations/CompactGroupByToolNamePerLevelRepresentation.ts +2 -2
- package/src/representations/CompactGroupByToolNamePerSeverityRepresentation.ts +2 -2
- package/src/representations/CompactGroupByToolNameRepresentation.ts +10 -10
- package/src/representations/CompactTotalPerLevelRepresentation.ts +2 -2
- package/src/representations/CompactTotalPerSeverityRepresentation.ts +2 -2
- package/src/representations/CompactTotalRepresentation.ts +5 -5
- package/src/representations/Representation.ts +8 -8
- package/src/representations/RepresentationFactory.ts +32 -32
- package/src/representations/TableGroupByRunPerLevelRepresentation.ts +3 -3
- package/src/representations/TableGroupByRunPerSeverityRepresentation.ts +3 -3
- package/src/representations/TableGroupByRunRepresentation.ts +5 -5
- package/src/representations/TableGroupBySarifPerLevelRepresentation.ts +3 -3
- package/src/representations/TableGroupBySarifPerSeverityRepresentation.ts +3 -3
- package/src/representations/TableGroupBySarifRepresentation.ts +9 -9
- package/src/representations/TableGroupByToolNamePerLevelRepresentation.ts +3 -3
- package/src/representations/TableGroupByToolNamePerSeverityRepresentation.ts +3 -3
- package/src/representations/TableGroupByToolNameRepresentation.ts +4 -4
- package/src/representations/TableGroupRepresentation.ts +32 -32
- package/src/representations/table/Cell.ts +8 -8
- package/src/representations/table/Column.ts +13 -13
- package/src/representations/table/Row.ts +17 -17
- package/src/representations/table/Table.ts +21 -21
- package/src/system.ts +5 -5
- package/src/types.ts +43 -13
- package/src/utils/Comparators.ts +6 -6
- package/src/utils/ExtendedArray.ts +1 -1
- package/src/utils/FileUtils.ts +3 -3
- package/src/utils/SarifUtils.ts +6 -6
- package/src/utils/StringUtils.ts +3 -3
- package/tests/integration/SendSarifToSlack.spec.ts +73 -67
- package/tests/representations/CompactGroupByRunPerLevelRepresentation.spec.ts +121 -0
- package/tests/representations/CompactGroupByRunPerSeverityRepresentation.spec.ts +122 -0
- package/tests/representations/CompactGroupBySarifPerLevelRepresentation.spec.ts +132 -0
- package/tests/representations/CompactGroupBySarifPerSeverityRepresentation.spec.ts +133 -0
- package/tsconfig.json +1 -1
package/src/utils/SarifUtils.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Result, Run, ToolComponent } from 'sarif'
|
|
1
|
+
import type { Result, Run, ToolComponent } from 'sarif';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Returns {@link ToolComponent} instance for the given {@link Run}. It does not
|
|
@@ -7,7 +7,7 @@ import type { Result, Run, ToolComponent } from 'sarif'
|
|
|
7
7
|
* @internal
|
|
8
8
|
*/
|
|
9
9
|
export function findToolComponentDriver(run: Run): ToolComponent {
|
|
10
|
-
return run.tool.driver
|
|
10
|
+
return run.tool.driver;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
@@ -17,11 +17,11 @@ export function findToolComponentDriver(run: Run): ToolComponent {
|
|
|
17
17
|
* @internal
|
|
18
18
|
*/
|
|
19
19
|
export function tryFindToolComponentExtension(run: Run, result: Result): ToolComponent | undefined {
|
|
20
|
-
let tool: ToolComponent | undefined
|
|
20
|
+
let tool: ToolComponent | undefined;
|
|
21
21
|
if (result.rule?.toolComponent?.index != null) {
|
|
22
|
-
tool = run.tool.extensions?.[result.rule.toolComponent.index]
|
|
22
|
+
tool = run.tool.extensions?.[result.rule.toolComponent.index];
|
|
23
23
|
}
|
|
24
|
-
return tool
|
|
24
|
+
return tool;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -31,5 +31,5 @@ export function tryFindToolComponentExtension(run: Run, result: Result): ToolCom
|
|
|
31
31
|
* @internal
|
|
32
32
|
*/
|
|
33
33
|
export function findToolComponent(run: Run, result: Result): ToolComponent {
|
|
34
|
-
return tryFindToolComponentExtension(run, result) ?? findToolComponentDriver(run)
|
|
34
|
+
return tryFindToolComponentExtension(run, result) ?? findToolComponentDriver(run);
|
|
35
35
|
}
|
package/src/utils/StringUtils.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export function randomAlphabetic(length: number): string {
|
|
2
|
-
const alphabet = 'abcdefghijklmnopqrstuvwxyz'
|
|
2
|
+
const alphabet = 'abcdefghijklmnopqrstuvwxyz';
|
|
3
3
|
return Array.from(
|
|
4
4
|
{ length },
|
|
5
|
-
(): string => alphabet[Math.floor(Math.random() * alphabet.length)]
|
|
6
|
-
).join('')
|
|
5
|
+
(): string => alphabet[Math.floor(Math.random() * alphabet.length)],
|
|
6
|
+
).join('');
|
|
7
7
|
}
|
|
@@ -1,105 +1,103 @@
|
|
|
1
1
|
import { z, ZodSafeParseResult } from 'zod';
|
|
2
2
|
import {
|
|
3
3
|
Color,
|
|
4
|
-
|
|
4
|
+
LogLevel,
|
|
5
|
+
LogLevelItems,
|
|
6
|
+
RepresentationType,
|
|
7
|
+
SarifFileExtensionItems,
|
|
5
8
|
SarifToSlackClient,
|
|
6
|
-
SendIf
|
|
9
|
+
SendIf,
|
|
7
10
|
} from '../../src';
|
|
8
11
|
|
|
9
12
|
describe('(integration): SendSarifToSlack', (): void => {
|
|
10
|
-
function processSarifExtension(extension: string): SarifFileExtension {
|
|
11
|
-
const allowed: string[] = ['sarif', 'json']
|
|
12
|
-
if (allowed.includes(extension)) {
|
|
13
|
-
return extension as SarifFileExtension
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
throw new Error(`Unknown extension: ${extension}`)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
13
|
function processRepresentationType(representation?: string): RepresentationType | undefined {
|
|
20
14
|
if (representation == null) {
|
|
21
|
-
return undefined
|
|
15
|
+
return undefined;
|
|
22
16
|
}
|
|
23
17
|
|
|
24
18
|
switch (representation.toLowerCase()) {
|
|
25
19
|
case 'compact-group-by-run-per-level':
|
|
26
|
-
return RepresentationType.CompactGroupByRunPerLevel
|
|
20
|
+
return RepresentationType.CompactGroupByRunPerLevel;
|
|
27
21
|
case 'compact-group-by-run-per-severity':
|
|
28
|
-
return RepresentationType.CompactGroupByRunPerSeverity
|
|
22
|
+
return RepresentationType.CompactGroupByRunPerSeverity;
|
|
29
23
|
case 'compact-group-by-tool-name-per-level':
|
|
30
|
-
return RepresentationType.CompactGroupByToolNamePerLevel
|
|
24
|
+
return RepresentationType.CompactGroupByToolNamePerLevel;
|
|
31
25
|
case 'compact-group-by-tool-name-per-severity':
|
|
32
|
-
return RepresentationType.CompactGroupByToolNamePerSeverity
|
|
26
|
+
return RepresentationType.CompactGroupByToolNamePerSeverity;
|
|
33
27
|
case 'compact-group-by-sarif-per-level':
|
|
34
|
-
return RepresentationType.CompactGroupBySarifPerLevel
|
|
28
|
+
return RepresentationType.CompactGroupBySarifPerLevel;
|
|
35
29
|
case 'compact-group-by-sarif-per-severity':
|
|
36
|
-
return RepresentationType.CompactGroupBySarifPerSeverity
|
|
30
|
+
return RepresentationType.CompactGroupBySarifPerSeverity;
|
|
37
31
|
case 'compact-total-per-level':
|
|
38
|
-
return RepresentationType.CompactTotalPerLevel
|
|
32
|
+
return RepresentationType.CompactTotalPerLevel;
|
|
39
33
|
case 'compact-total-per-severity':
|
|
40
|
-
return RepresentationType.CompactTotalPerSeverity
|
|
34
|
+
return RepresentationType.CompactTotalPerSeverity;
|
|
41
35
|
case 'table-group-by-run-per-level':
|
|
42
|
-
return RepresentationType.TableGroupByRunPerLevel
|
|
36
|
+
return RepresentationType.TableGroupByRunPerLevel;
|
|
43
37
|
case 'table-group-by-run-per-severity':
|
|
44
|
-
return RepresentationType.TableGroupByRunPerSeverity
|
|
38
|
+
return RepresentationType.TableGroupByRunPerSeverity;
|
|
45
39
|
case 'table-group-by-tool-name-per-level':
|
|
46
|
-
return RepresentationType.TableGroupByToolNamePerLevel
|
|
40
|
+
return RepresentationType.TableGroupByToolNamePerLevel;
|
|
47
41
|
case 'table-group-by-tool-name-per-severity':
|
|
48
|
-
return RepresentationType.TableGroupByToolNamePerSeverity
|
|
42
|
+
return RepresentationType.TableGroupByToolNamePerSeverity;
|
|
49
43
|
case 'table-group-by-sarif-per-level':
|
|
50
|
-
return RepresentationType.TableGroupBySarifPerLevel
|
|
44
|
+
return RepresentationType.TableGroupBySarifPerLevel;
|
|
51
45
|
case 'table-group-by-sarif-per-severity':
|
|
52
|
-
return RepresentationType.TableGroupBySarifPerSeverity
|
|
46
|
+
return RepresentationType.TableGroupBySarifPerSeverity;
|
|
53
47
|
default:
|
|
54
|
-
return undefined
|
|
48
|
+
return undefined;
|
|
55
49
|
}
|
|
56
50
|
}
|
|
57
51
|
|
|
58
52
|
function processSendIf(sendIf?: string): SendIf | undefined {
|
|
59
53
|
if (sendIf == null) {
|
|
60
|
-
return undefined
|
|
54
|
+
return undefined;
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
switch (sendIf.toLowerCase()) {
|
|
64
|
-
case 'severity-critical': return SendIf.SeverityCritical
|
|
65
|
-
case 'severity-high': return SendIf.SeverityHigh
|
|
66
|
-
case 'severity-high-or-higher': return SendIf.SeverityHighOrHigher
|
|
67
|
-
case 'severity-medium': return SendIf.SeverityMedium
|
|
68
|
-
case 'severity-medium-or-higher': return SendIf.SeverityMediumOrHigher
|
|
69
|
-
case 'severity-low': return SendIf.SeverityLow
|
|
70
|
-
case 'severity-low-or-higher': return SendIf.SeverityLowOrHigher
|
|
71
|
-
case 'severity-none': return SendIf.SeverityNone
|
|
72
|
-
case 'severity-none-or-higher': return SendIf.SeverityNoneOrHigher
|
|
73
|
-
case 'severity-unknown': return SendIf.SeverityUnknown
|
|
74
|
-
case 'severity-unknown-or-higher': return SendIf.SeverityUnknownOrHigher
|
|
75
|
-
case 'level-error': return SendIf.LevelError
|
|
76
|
-
case 'level-warning': return SendIf.LevelWarning
|
|
77
|
-
case 'level-warning-or-higher': return SendIf.LevelWarningOrHigher
|
|
78
|
-
case 'level-note': return SendIf.LevelNote
|
|
79
|
-
case 'level-note-or-higher': return SendIf.LevelNoteOrHigher
|
|
80
|
-
case 'level-none': return SendIf.LevelNone
|
|
81
|
-
case 'level-none-or-higher': return SendIf.LevelNoneOrHigher
|
|
82
|
-
case 'level-unknown': return SendIf.LevelUnknown
|
|
83
|
-
case 'level-unknown-or-higher': return SendIf.LevelUnknownOrHigher
|
|
84
|
-
case 'always': return SendIf.Always
|
|
85
|
-
case 'some': return SendIf.Some
|
|
86
|
-
case 'empty': return SendIf.Empty
|
|
87
|
-
case 'never': return SendIf.Never
|
|
88
|
-
default: return undefined
|
|
58
|
+
case 'severity-critical': return SendIf.SeverityCritical;
|
|
59
|
+
case 'severity-high': return SendIf.SeverityHigh;
|
|
60
|
+
case 'severity-high-or-higher': return SendIf.SeverityHighOrHigher;
|
|
61
|
+
case 'severity-medium': return SendIf.SeverityMedium;
|
|
62
|
+
case 'severity-medium-or-higher': return SendIf.SeverityMediumOrHigher;
|
|
63
|
+
case 'severity-low': return SendIf.SeverityLow;
|
|
64
|
+
case 'severity-low-or-higher': return SendIf.SeverityLowOrHigher;
|
|
65
|
+
case 'severity-none': return SendIf.SeverityNone;
|
|
66
|
+
case 'severity-none-or-higher': return SendIf.SeverityNoneOrHigher;
|
|
67
|
+
case 'severity-unknown': return SendIf.SeverityUnknown;
|
|
68
|
+
case 'severity-unknown-or-higher': return SendIf.SeverityUnknownOrHigher;
|
|
69
|
+
case 'level-error': return SendIf.LevelError;
|
|
70
|
+
case 'level-warning': return SendIf.LevelWarning;
|
|
71
|
+
case 'level-warning-or-higher': return SendIf.LevelWarningOrHigher;
|
|
72
|
+
case 'level-note': return SendIf.LevelNote;
|
|
73
|
+
case 'level-note-or-higher': return SendIf.LevelNoteOrHigher;
|
|
74
|
+
case 'level-none': return SendIf.LevelNone;
|
|
75
|
+
case 'level-none-or-higher': return SendIf.LevelNoneOrHigher;
|
|
76
|
+
case 'level-unknown': return SendIf.LevelUnknown;
|
|
77
|
+
case 'level-unknown-or-higher': return SendIf.LevelUnknownOrHigher;
|
|
78
|
+
case 'always': return SendIf.Always;
|
|
79
|
+
case 'some': return SendIf.Some;
|
|
80
|
+
case 'empty': return SendIf.Empty;
|
|
81
|
+
case 'never': return SendIf.Never;
|
|
82
|
+
default: return undefined;
|
|
89
83
|
}
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
test('should send Sarif to Slack', async () => {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
87
|
+
const parseBoolean = <T>(envVar: string | undefined, defaultVal: T): boolean | T => {
|
|
88
|
+
const parseResult: ZodSafeParseResult<boolean> = z
|
|
89
|
+
.string()
|
|
90
|
+
.transform((val: string): string => val.toLowerCase())
|
|
91
|
+
.refine((val: string): val is "true" | "false" => val === "true" || val === "false")
|
|
92
|
+
.transform((val: "true" | "false"): val is "true" => val === "true")
|
|
93
|
+
.safeParse(envVar);
|
|
94
|
+
return parseResult.success ? parseResult.data : defaultVal;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const logLevelParseResult: ZodSafeParseResult<LogLevel> =
|
|
98
|
+
z.enum(LogLevelItems).safeParse(process.env.SARIF_TO_SLACK_LOG_LEVEL);
|
|
99
|
+
const logFunctionNameOnPositionParseResult: ZodSafeParseResult<number> =
|
|
100
|
+
z.coerce.number().safeParse(process.env.SARIF_TO_SLACK_LOG_FUNCTION_NAME_ON_POSITION);
|
|
103
101
|
|
|
104
102
|
const client: SarifToSlackClient = await SarifToSlackClient.create(
|
|
105
103
|
process.env.SARIF_TO_SLACK_WEBHOOK_URL as string,
|
|
@@ -127,8 +125,8 @@ describe('(integration): SendSarifToSlack', (): void => {
|
|
|
127
125
|
},
|
|
128
126
|
sarif: {
|
|
129
127
|
path: process.env.SARIF_TO_SLACK_SARIF_PATH as string,
|
|
130
|
-
recursive:
|
|
131
|
-
extension:
|
|
128
|
+
recursive: parseBoolean(process.env.SARIF_TO_SLACK_SARIF_PATH_RECURSIVE, false),
|
|
129
|
+
extension: z.enum(SarifFileExtensionItems).parse(process.env.SARIF_TO_SLACK_SARIF_FILE_EXTENSION),
|
|
132
130
|
},
|
|
133
131
|
header: {
|
|
134
132
|
include: process.env.SARIF_TO_SLACK_HEADER !== 'skip',
|
|
@@ -143,11 +141,19 @@ describe('(integration): SendSarifToSlack', (): void => {
|
|
|
143
141
|
value: process.env.SARIF_TO_SLACK_ACTOR,
|
|
144
142
|
},
|
|
145
143
|
run: {
|
|
146
|
-
include:
|
|
144
|
+
include: parseBoolean(process.env.SARIF_TO_SLACK_INCLUDE_RUN, false),
|
|
147
145
|
},
|
|
148
146
|
representation: processRepresentationType(process.env.SARIF_TO_SLACK_REPRESENTATION),
|
|
149
147
|
sendIf: processSendIf(process.env.SARIF_TO_SLACK_SEND_IF),
|
|
150
|
-
|
|
148
|
+
loggerOptions: {
|
|
149
|
+
logFunctionName: parseBoolean(process.env.SARIF_TO_SLACK_LOG_FUNCTION_NAME, undefined),
|
|
150
|
+
logFunctionNameOnPosition: logFunctionNameOnPositionParseResult.success ? logFunctionNameOnPositionParseResult.data : undefined,
|
|
151
|
+
minLevel: logLevelParseResult.success ? logLevelParseResult.data : undefined,
|
|
152
|
+
name: 'integration-test',
|
|
153
|
+
stylePrettyLogs: parseBoolean(process.env.SARIF_TO_SLACK_STYLE_PRETTY_LOGS, undefined),
|
|
154
|
+
prettyLogTemplate: process.env.SARIF_TO_SLACK_LOG_TEMPLATE,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
151
157
|
);
|
|
152
158
|
await client.send();
|
|
153
159
|
})
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import CompactGroupByRunPerLevelRepresentation from '../../src/representations/CompactGroupByRunPerLevelRepresentation';
|
|
2
|
+
import FindingArray from '../../src/model/FindingArray';
|
|
3
|
+
import type Finding from '../../src/model/Finding';
|
|
4
|
+
import { SecurityLevel, SecuritySeverity } from '../../src/types';
|
|
5
|
+
import type { RunData, SarifModel } from '../../src/types';
|
|
6
|
+
|
|
7
|
+
function mockFinding(opts: {
|
|
8
|
+
sarifPath?: string,
|
|
9
|
+
runId?: number,
|
|
10
|
+
toolName?: string,
|
|
11
|
+
level?: SecurityLevel,
|
|
12
|
+
severity?: SecuritySeverity,
|
|
13
|
+
}): Finding {
|
|
14
|
+
const finding: Finding = {
|
|
15
|
+
get sarifPath() { return opts.sarifPath ?? '/default.sarif' },
|
|
16
|
+
get runId() { return opts.runId ?? 1 },
|
|
17
|
+
get toolName() { return opts.toolName ?? 'Tool' },
|
|
18
|
+
get cvssScore() { return undefined },
|
|
19
|
+
get level() { return opts.level ?? SecurityLevel.Unknown },
|
|
20
|
+
get severity() { return opts.severity ?? SecuritySeverity.Unknown },
|
|
21
|
+
clone() { return mockFinding(opts) },
|
|
22
|
+
};
|
|
23
|
+
return finding;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildModel(
|
|
27
|
+
runs: Array<{ id: number; toolName: string }>,
|
|
28
|
+
findings: Finding[],
|
|
29
|
+
): SarifModel {
|
|
30
|
+
const arr = new FindingArray();
|
|
31
|
+
findings.forEach(f => arr.push(f));
|
|
32
|
+
return {
|
|
33
|
+
sarifFiles: [],
|
|
34
|
+
runs: runs.map(r => ({ id: r.id, toolName: r.toolName, run: {} as RunData['run'] })),
|
|
35
|
+
findings: arr,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('(unit): CompactGroupByRunPerLevelRepresentation', (): void => {
|
|
40
|
+
describe('compose()', (): void => {
|
|
41
|
+
test('should return "No vulnerabilities found" when there are no runs', (): void => {
|
|
42
|
+
const repr = new CompactGroupByRunPerLevelRepresentation(buildModel([], []));
|
|
43
|
+
expect(repr.compose()).toBe('No vulnerabilities found');
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('should return group header with "No vulnerabilities found" when run has no findings', (): void => {
|
|
47
|
+
const repr = new CompactGroupByRunPerLevelRepresentation(
|
|
48
|
+
buildModel([{ id: 1, toolName: 'Grype' }], []),
|
|
49
|
+
);
|
|
50
|
+
expect(repr.compose()).toBe('_[Run 1]_ *Grype*\nNo vulnerabilities found');
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('should compose single finding with correct level label', (): void => {
|
|
54
|
+
const repr = new CompactGroupByRunPerLevelRepresentation(
|
|
55
|
+
buildModel(
|
|
56
|
+
[{ id: 1, toolName: 'Grype' }],
|
|
57
|
+
[mockFinding({ runId: 1, level: SecurityLevel.Error })],
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
expect(repr.compose()).toBe('_[Run 1]_ *Grype*\n*Error*: 1');
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('should group and sort findings by level descending', (): void => {
|
|
64
|
+
const findings = [
|
|
65
|
+
mockFinding({ runId: 1, level: SecurityLevel.Note }),
|
|
66
|
+
mockFinding({ runId: 1, level: SecurityLevel.Error }),
|
|
67
|
+
mockFinding({ runId: 1, level: SecurityLevel.Warning }),
|
|
68
|
+
mockFinding({ runId: 1, level: SecurityLevel.Warning }),
|
|
69
|
+
];
|
|
70
|
+
const repr = new CompactGroupByRunPerLevelRepresentation(
|
|
71
|
+
buildModel([{ id: 1, toolName: 'Trivy' }], findings),
|
|
72
|
+
);
|
|
73
|
+
expect(repr.compose()).toBe('_[Run 1]_ *Trivy*\n*Error*: 1, *Warning*: 2, *Note*: 1');
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('should compose multiple runs each with their own findings', (): void => {
|
|
77
|
+
const findings = [
|
|
78
|
+
mockFinding({ runId: 1, level: SecurityLevel.Error }),
|
|
79
|
+
mockFinding({ runId: 2, level: SecurityLevel.Warning }),
|
|
80
|
+
];
|
|
81
|
+
const repr = new CompactGroupByRunPerLevelRepresentation(
|
|
82
|
+
buildModel(
|
|
83
|
+
[{ id: 1, toolName: 'Grype' }, { id: 2, toolName: 'Trivy' }],
|
|
84
|
+
findings,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
expect(repr.compose()).toBe(
|
|
88
|
+
'_[Run 1]_ *Grype*\n*Error*: 1\n\n_[Run 2]_ *Trivy*\n*Warning*: 1',
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('should show "No vulnerabilities found" for a run that has no matching findings', (): void => {
|
|
93
|
+
const findings = [mockFinding({ runId: 1, level: SecurityLevel.Error })];
|
|
94
|
+
const repr = new CompactGroupByRunPerLevelRepresentation(
|
|
95
|
+
buildModel(
|
|
96
|
+
[{ id: 1, toolName: 'Grype' }, { id: 2, toolName: 'Trivy' }],
|
|
97
|
+
findings,
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
expect(repr.compose()).toBe(
|
|
101
|
+
'_[Run 1]_ *Grype*\n*Error*: 1\n\n_[Run 2]_ *Trivy*\nNo vulnerabilities found',
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('should handle all level variants correctly', (): void => {
|
|
106
|
+
const findings = [
|
|
107
|
+
mockFinding({ runId: 1, level: SecurityLevel.Error }),
|
|
108
|
+
mockFinding({ runId: 1, level: SecurityLevel.Warning }),
|
|
109
|
+
mockFinding({ runId: 1, level: SecurityLevel.Note }),
|
|
110
|
+
mockFinding({ runId: 1, level: SecurityLevel.None }),
|
|
111
|
+
mockFinding({ runId: 1, level: SecurityLevel.Unknown }),
|
|
112
|
+
];
|
|
113
|
+
const repr = new CompactGroupByRunPerLevelRepresentation(
|
|
114
|
+
buildModel([{ id: 1, toolName: 'Scanner' }], findings),
|
|
115
|
+
);
|
|
116
|
+
expect(repr.compose()).toBe(
|
|
117
|
+
'_[Run 1]_ *Scanner*\n*Error*: 1, *Warning*: 1, *Note*: 1, *None*: 1, *Unknown*: 1',
|
|
118
|
+
);
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
})
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import CompactGroupByRunPerSeverityRepresentation from '../../src/representations/CompactGroupByRunPerSeverityRepresentation';
|
|
2
|
+
import FindingArray from '../../src/model/FindingArray';
|
|
3
|
+
import type Finding from '../../src/model/Finding';
|
|
4
|
+
import { SecurityLevel, SecuritySeverity } from '../../src/types';
|
|
5
|
+
import type { RunData, SarifModel } from '../../src/types';
|
|
6
|
+
|
|
7
|
+
function mockFinding(opts: {
|
|
8
|
+
sarifPath?: string,
|
|
9
|
+
runId?: number,
|
|
10
|
+
toolName?: string,
|
|
11
|
+
level?: SecurityLevel,
|
|
12
|
+
severity?: SecuritySeverity,
|
|
13
|
+
}): Finding {
|
|
14
|
+
const finding: Finding = {
|
|
15
|
+
get sarifPath() { return opts.sarifPath ?? '/default.sarif' },
|
|
16
|
+
get runId() { return opts.runId ?? 1 },
|
|
17
|
+
get toolName() { return opts.toolName ?? 'Tool' },
|
|
18
|
+
get cvssScore() { return undefined },
|
|
19
|
+
get level() { return opts.level ?? SecurityLevel.Unknown },
|
|
20
|
+
get severity() { return opts.severity ?? SecuritySeverity.Unknown },
|
|
21
|
+
clone() { return mockFinding(opts) },
|
|
22
|
+
};
|
|
23
|
+
return finding;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildModel(
|
|
27
|
+
runs: Array<{ id: number; toolName: string }>,
|
|
28
|
+
findings: Finding[],
|
|
29
|
+
): SarifModel {
|
|
30
|
+
const arr = new FindingArray();
|
|
31
|
+
findings.forEach(f => arr.push(f));
|
|
32
|
+
return {
|
|
33
|
+
sarifFiles: [],
|
|
34
|
+
runs: runs.map(r => ({ id: r.id, toolName: r.toolName, run: {} as RunData['run'] })),
|
|
35
|
+
findings: arr,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('(unit): CompactGroupByRunPerSeverityRepresentation', (): void => {
|
|
40
|
+
describe('compose()', (): void => {
|
|
41
|
+
test('should return "No vulnerabilities found" when there are no runs', (): void => {
|
|
42
|
+
const repr = new CompactGroupByRunPerSeverityRepresentation(buildModel([], []));
|
|
43
|
+
expect(repr.compose()).toBe('No vulnerabilities found');
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('should return group header with "No vulnerabilities found" when run has no findings', (): void => {
|
|
47
|
+
const repr = new CompactGroupByRunPerSeverityRepresentation(
|
|
48
|
+
buildModel([{ id: 1, toolName: 'Grype' }], []),
|
|
49
|
+
);
|
|
50
|
+
expect(repr.compose()).toBe('_[Run 1]_ *Grype*\nNo vulnerabilities found');
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('should compose single finding with correct severity label', (): void => {
|
|
54
|
+
const repr = new CompactGroupByRunPerSeverityRepresentation(
|
|
55
|
+
buildModel(
|
|
56
|
+
[{ id: 1, toolName: 'Grype' }],
|
|
57
|
+
[mockFinding({ runId: 1, severity: SecuritySeverity.Critical })],
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
expect(repr.compose()).toBe('_[Run 1]_ *Grype*\n*Critical*: 1');
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('should group and sort findings by severity descending', (): void => {
|
|
64
|
+
const findings = [
|
|
65
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.Low }),
|
|
66
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.Critical }),
|
|
67
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.High }),
|
|
68
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.High }),
|
|
69
|
+
];
|
|
70
|
+
const repr = new CompactGroupByRunPerSeverityRepresentation(
|
|
71
|
+
buildModel([{ id: 1, toolName: 'Trivy' }], findings),
|
|
72
|
+
);
|
|
73
|
+
expect(repr.compose()).toBe('_[Run 1]_ *Trivy*\n*Critical*: 1, *High*: 2, *Low*: 1');
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('should compose multiple runs each with their own findings', (): void => {
|
|
77
|
+
const findings = [
|
|
78
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.High }),
|
|
79
|
+
mockFinding({ runId: 2, severity: SecuritySeverity.Medium }),
|
|
80
|
+
];
|
|
81
|
+
const repr = new CompactGroupByRunPerSeverityRepresentation(
|
|
82
|
+
buildModel(
|
|
83
|
+
[{ id: 1, toolName: 'Grype' }, { id: 2, toolName: 'Trivy' }],
|
|
84
|
+
findings,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
expect(repr.compose()).toBe(
|
|
88
|
+
'_[Run 1]_ *Grype*\n*High*: 1\n\n_[Run 2]_ *Trivy*\n*Medium*: 1',
|
|
89
|
+
);
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('should show "No vulnerabilities found" for a run that has no matching findings', (): void => {
|
|
93
|
+
const findings = [mockFinding({ runId: 1, severity: SecuritySeverity.Critical })];
|
|
94
|
+
const repr = new CompactGroupByRunPerSeverityRepresentation(
|
|
95
|
+
buildModel(
|
|
96
|
+
[{ id: 1, toolName: 'Grype' }, { id: 2, toolName: 'Trivy' }],
|
|
97
|
+
findings,
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
expect(repr.compose()).toBe(
|
|
101
|
+
'_[Run 1]_ *Grype*\n*Critical*: 1\n\n_[Run 2]_ *Trivy*\nNo vulnerabilities found',
|
|
102
|
+
);
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('should handle all severity variants correctly', (): void => {
|
|
106
|
+
const findings = [
|
|
107
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.Critical }),
|
|
108
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.High }),
|
|
109
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.Medium }),
|
|
110
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.Low }),
|
|
111
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.None }),
|
|
112
|
+
mockFinding({ runId: 1, severity: SecuritySeverity.Unknown }),
|
|
113
|
+
];
|
|
114
|
+
const repr = new CompactGroupByRunPerSeverityRepresentation(
|
|
115
|
+
buildModel([{ id: 1, toolName: 'Scanner' }], findings),
|
|
116
|
+
);
|
|
117
|
+
expect(repr.compose()).toBe(
|
|
118
|
+
'_[Run 1]_ *Scanner*\n*Critical*: 1, *High*: 1, *Medium*: 1, *Low*: 1, *None*: 1, *Unknown*: 1',
|
|
119
|
+
);
|
|
120
|
+
})
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import CompactGroupBySarifPerLevelRepresentation from '../../src/representations/CompactGroupBySarifPerLevelRepresentation';
|
|
2
|
+
import FindingArray from '../../src/model/FindingArray';
|
|
3
|
+
import type Finding from '../../src/model/Finding';
|
|
4
|
+
import { SecurityLevel, SecuritySeverity } from '../../src/types';
|
|
5
|
+
import type { RunData, SarifModel } from '../../src/types';
|
|
6
|
+
|
|
7
|
+
function mockFinding(opts: {
|
|
8
|
+
sarifPath?: string,
|
|
9
|
+
runId?: number,
|
|
10
|
+
toolName?: string,
|
|
11
|
+
level?: SecurityLevel,
|
|
12
|
+
severity?: SecuritySeverity,
|
|
13
|
+
}): Finding {
|
|
14
|
+
const finding: Finding = {
|
|
15
|
+
get sarifPath() { return opts.sarifPath ?? '/default.sarif' },
|
|
16
|
+
get runId() { return opts.runId ?? 1 },
|
|
17
|
+
get toolName() { return opts.toolName ?? 'Tool' },
|
|
18
|
+
get cvssScore() { return undefined },
|
|
19
|
+
get level() { return opts.level ?? SecurityLevel.Unknown },
|
|
20
|
+
get severity() { return opts.severity ?? SecuritySeverity.Unknown },
|
|
21
|
+
clone() { return mockFinding(opts) },
|
|
22
|
+
};
|
|
23
|
+
return finding;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildModel(
|
|
27
|
+
sarifFiles: string[],
|
|
28
|
+
findings: Finding[],
|
|
29
|
+
): SarifModel {
|
|
30
|
+
const arr = new FindingArray();
|
|
31
|
+
findings.forEach(f => arr.push(f));
|
|
32
|
+
return {
|
|
33
|
+
sarifFiles,
|
|
34
|
+
runs: [{ id: 1, toolName: 'Tool', run: {} as RunData['run'] }],
|
|
35
|
+
findings: arr,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('(unit): CompactGroupBySarifPerLevelRepresentation', (): void => {
|
|
40
|
+
describe('compose()', (): void => {
|
|
41
|
+
test('should return "No vulnerabilities found" when there are no sarif files', (): void => {
|
|
42
|
+
const repr = new CompactGroupBySarifPerLevelRepresentation(buildModel([], []));
|
|
43
|
+
expect(repr.compose()).toBe('No vulnerabilities found');
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('should return group header with "No vulnerabilities found" when file has no findings', (): void => {
|
|
47
|
+
const repr = new CompactGroupBySarifPerLevelRepresentation(
|
|
48
|
+
buildModel(['/path/to/results.sarif'], []),
|
|
49
|
+
);
|
|
50
|
+
expect(repr.compose()).toBe('_[File 1]_ *results.sarif*\nNo vulnerabilities found');
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('should compose single finding with correct level label', (): void => {
|
|
54
|
+
const repr = new CompactGroupBySarifPerLevelRepresentation(
|
|
55
|
+
buildModel(
|
|
56
|
+
['/path/to/grype.sarif'],
|
|
57
|
+
[mockFinding({ sarifPath: '/path/to/grype.sarif', level: SecurityLevel.Error })],
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
expect(repr.compose()).toBe('_[File 1]_ *grype.sarif*\n*Error*: 1');
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('should group and sort findings by level descending', (): void => {
|
|
64
|
+
const sarifPath = '/scans/results.sarif';
|
|
65
|
+
const findings = [
|
|
66
|
+
mockFinding({ sarifPath, level: SecurityLevel.Note }),
|
|
67
|
+
mockFinding({ sarifPath, level: SecurityLevel.Error }),
|
|
68
|
+
mockFinding({ sarifPath, level: SecurityLevel.Warning }),
|
|
69
|
+
mockFinding({ sarifPath, level: SecurityLevel.Warning }),
|
|
70
|
+
];
|
|
71
|
+
const repr = new CompactGroupBySarifPerLevelRepresentation(
|
|
72
|
+
buildModel([sarifPath], findings),
|
|
73
|
+
);
|
|
74
|
+
expect(repr.compose()).toBe('_[File 1]_ *results.sarif*\n*Error*: 1, *Warning*: 2, *Note*: 1');
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('should compose multiple sarif files with incrementing indices', (): void => {
|
|
78
|
+
const file1 = '/scans/grype-01.sarif';
|
|
79
|
+
const file2 = '/scans/grype-02.sarif';
|
|
80
|
+
const findings = [
|
|
81
|
+
mockFinding({ sarifPath: file1, level: SecurityLevel.Error }),
|
|
82
|
+
mockFinding({ sarifPath: file2, level: SecurityLevel.Warning }),
|
|
83
|
+
];
|
|
84
|
+
const repr = new CompactGroupBySarifPerLevelRepresentation(
|
|
85
|
+
buildModel([file1, file2], findings),
|
|
86
|
+
);
|
|
87
|
+
expect(repr.compose()).toBe(
|
|
88
|
+
'_[File 1]_ *grype-01.sarif*\n*Error*: 1\n\n_[File 2]_ *grype-02.sarif*\n*Warning*: 1',
|
|
89
|
+
);
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('should show "No vulnerabilities found" for a file that has no matching findings', (): void => {
|
|
93
|
+
const file1 = '/scans/grype-01.sarif';
|
|
94
|
+
const file2 = '/scans/grype-02.sarif';
|
|
95
|
+
const findings = [mockFinding({ sarifPath: file1, level: SecurityLevel.Error })];
|
|
96
|
+
const repr = new CompactGroupBySarifPerLevelRepresentation(
|
|
97
|
+
buildModel([file1, file2], findings),
|
|
98
|
+
);
|
|
99
|
+
expect(repr.compose()).toBe(
|
|
100
|
+
'_[File 1]_ *grype-01.sarif*\n*Error*: 1\n\n_[File 2]_ *grype-02.sarif*\nNo vulnerabilities found',
|
|
101
|
+
);
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('should use only basename for the group title', (): void => {
|
|
105
|
+
const sarifPath = '/very/long/path/to/nested/scan-results.sarif';
|
|
106
|
+
const repr = new CompactGroupBySarifPerLevelRepresentation(
|
|
107
|
+
buildModel(
|
|
108
|
+
[sarifPath],
|
|
109
|
+
[mockFinding({ sarifPath, level: SecurityLevel.Note })],
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
expect(repr.compose()).toBe('_[File 1]_ *scan-results.sarif*\n*Note*: 1');
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('should handle all level variants correctly', (): void => {
|
|
116
|
+
const sarifPath = '/scans/all-levels.sarif';
|
|
117
|
+
const findings = [
|
|
118
|
+
mockFinding({ sarifPath, level: SecurityLevel.Error }),
|
|
119
|
+
mockFinding({ sarifPath, level: SecurityLevel.Warning }),
|
|
120
|
+
mockFinding({ sarifPath, level: SecurityLevel.Note }),
|
|
121
|
+
mockFinding({ sarifPath, level: SecurityLevel.None }),
|
|
122
|
+
mockFinding({ sarifPath, level: SecurityLevel.Unknown }),
|
|
123
|
+
];
|
|
124
|
+
const repr = new CompactGroupBySarifPerLevelRepresentation(
|
|
125
|
+
buildModel([sarifPath], findings),
|
|
126
|
+
);
|
|
127
|
+
expect(repr.compose()).toBe(
|
|
128
|
+
'_[File 1]_ *all-levels.sarif*\n*Error*: 1, *Warning*: 1, *Note*: 1, *None*: 1, *Unknown*: 1',
|
|
129
|
+
);
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
})
|