From 99b8ee02372417d351c19970606631caef36bf5b Mon Sep 17 00:00:00 2001 From: Ofek Atar Date: Wed, 27 Jul 2022 18:56:30 +0300 Subject: [PATCH] chore: Fix IaC test exit code when issues are found --- src/lib/iac/test/v2/output.ts | 26 +++++ .../cli/commands/test/iac/v2/index.spec.ts | 104 ++++++++++++------ 2 files changed, 96 insertions(+), 34 deletions(-) diff --git a/src/lib/iac/test/v2/output.ts b/src/lib/iac/test/v2/output.ts index c3538ef852..c9e3003402 100644 --- a/src/lib/iac/test/v2/output.ts +++ b/src/lib/iac/test/v2/output.ts @@ -16,6 +16,7 @@ import { jsonStringifyLargeObject } from '../../../json'; import { IacOrgSettings } from '../../../../cli/commands/test/iac/local-execution/types'; import { SnykIacTestError } from './errors'; import { convertEngineToSarifResults } from './sarif'; +import { CustomError } from '../../../errors'; export function buildOutput({ scanResult, @@ -91,6 +92,11 @@ function buildTestCommandResultData({ responseData = buildTextOutput({ scanResult, projectName, orgSettings }); } + const isFoundIssues = !!scanResult.results?.vulnerabilities?.length; + if (isFoundIssues) { + throw new FoundIssuesError({ responseData, jsonData, sarifData }); + } + return { responseData, jsonData, sarifData }; } @@ -141,3 +147,23 @@ function buildTextOutput({ return response; } + +interface FoundIssuesErrorProps { + responseData: string; + jsonData: string; + sarifData: string; +} + +export class FoundIssuesError extends CustomError { + public jsonStringifiedResults: string; + public sarifStringifiedResults: string; + + constructor(props: FoundIssuesErrorProps) { + super(props.responseData); + this.code = 'VULNS' as any; + this.strCode = 'VULNS'; + this.userMessage = props.responseData; + this.jsonStringifiedResults = props.jsonData; + this.sarifStringifiedResults = props.sarifData; + } +} diff --git a/test/jest/unit/cli/commands/test/iac/v2/index.spec.ts b/test/jest/unit/cli/commands/test/iac/v2/index.spec.ts index 8ac365f221..4d3b853fef 100644 --- a/test/jest/unit/cli/commands/test/iac/v2/index.spec.ts +++ b/test/jest/unit/cli/commands/test/iac/v2/index.spec.ts @@ -10,6 +10,7 @@ import { test } from '../../../../../../../../src/cli/commands/test/iac/v2/index import { Options, TestOptions } from '../../../../../../../../src/lib/types'; import { isValidJSONString } from '../../../../../../acceptance/iac/helpers'; import { IacOrgSettings } from '../../../../../../../../src/cli/commands/test/iac/local-execution/types'; +import { FoundIssuesError } from '../../../../../../../../src/lib/iac/test/v2/output'; jest.setTimeout(1000 * 10); @@ -70,44 +71,79 @@ describe('test', () => { .spyOn(orgSettingsLib, 'getIacOrgSettings') .mockResolvedValue(orgSettings); - it('without any flags outputs the test results', async () => { - const result = await test(['path/to/test'], defaultOptions); - const output = result.getDisplayResults(); - - expect(output).toContain('Issues'); - expect(output).toContain('Medium Severity Issues: '); - expect(output).toContain('High Severity Issues: '); - expect(output).toContain(`Organization: ${orgSettings.meta.org}`); - expect(output).toContain(`Project name: ${path.basename(projectRoot)}`); - expect(output).toContain('Files without issues: 1'); - expect(output).toContain('Files with issues: 2'); - expect(output).toContain('Total issues: 3'); - expect(output).toContain('[ 0 critical, 2 high, 1 medium, 0 low ]'); + it('outputs the test results', async () => { + // Arrange + let output: string; + + // Act + try { + await test(['path/to/test'], defaultOptions); + } catch (error) { + output = error.message; + } + + // Assert + expect(output!).toContain('Issues'); + expect(output!).toContain('Medium Severity Issues: '); + expect(output!).toContain('High Severity Issues: '); + expect(output!).toContain(`Organization: ${orgSettings.meta.org}`); + expect(output!).toContain(`Project name: ${path.basename(projectRoot)}`); + expect(output!).toContain('Files without issues: 1'); + expect(output!).toContain('Files with issues: 2'); + expect(output!).toContain('Total issues: 3'); + expect(output!).toContain('[ 0 critical, 2 high, 1 medium, 0 low ]'); }); - it('with `--json` flag', async () => { - const result = ( - await test(['path/to/test'], { - ...defaultOptions, - json: true, - }) - ).getJsonResult(); + describe('with issues', () => { + it('throws the expected error', async () => { + // Act + Assert + await expect(test(['path/to/test'], defaultOptions)).rejects.toThrowError( + FoundIssuesError, + ); + }); + }); - expect(isValidJSONString(result)).toBe(true); - expect(result).toContain(`"ok": false`); + describe('with `--json` flag', () => { + it('outputs the test results in JSON format', async () => { + // Arrange + let result: string; + + // Act + try { + await test(['path/to/test'], { + ...defaultOptions, + json: true, + }); + } catch (error) { + result = error.jsonStringifiedResults; + } + + // Assert + expect(isValidJSONString(result!)).toBe(true); + expect(result!).toContain(`"ok": false`); + }); }); - it('with `--sarif` flag', async () => { - const result = ( - await test(['path/to/test'], { - ...defaultOptions, - sarif: true, - }) - ).getSarifResult(); - - expect(isValidJSONString(result)).toBe(true); - expect(result).toContain( - `"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"`, - ); + describe('with `--sarif` flag', () => { + it('outputs the test results in SARIF format', async () => { + // Arrange + let result: string; + + // Act + try { + await test(['path/to/test'], { + ...defaultOptions, + sarif: true, + }); + } catch (error) { + result = error.sarifStringifiedResults; + } + + // Assert + expect(isValidJSONString(result!)).toBe(true); + expect(result!).toContain( + `"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"`, + ); + }); }); });