diff --git a/src/Commands/RunScan.spec.ts b/src/Commands/RunScan.spec.ts index 1ff0ba23..7f119d93 100644 --- a/src/Commands/RunScan.spec.ts +++ b/src/Commands/RunScan.spec.ts @@ -1,7 +1,26 @@ import 'reflect-metadata'; import { Logger, logger } from '../Utils'; import { RunScan } from './RunScan'; -import { anything, reset, spy, verify, when } from 'ts-mockito'; +import { + AttackParamLocation, + Exclusions, + Module, + Scans, + ScanWarning, + TestType +} from '../Scan'; +import { + anything, + instance, + mock, + objectContaining, + reset, + spy, + verify, + when +} from 'ts-mockito'; +import { container } from 'tsyringe'; +import { Arguments } from 'yargs'; describe('RunScan', () => { let processSpy!: NodeJS.Process; @@ -67,4 +86,129 @@ describe('RunScan', () => { expect(act).toThrowError(SyntaxError); }); }); + + describe('handler', () => { + let runScan!: RunScan; + const mockedScans = mock(); + + beforeEach(() => { + container.registerInstance(Scans, instance(mockedScans)); + runScan = new RunScan(); + }); + + afterEach(() => { + container.clearInstances(); + reset(mockedScans); + }); + + it('should correctly pass scan config from args', async () => { + // arrange + const args = { + test: ['test1', 'test2'], + name: 'test-scan', + module: 'test-module', + auth: 'test-auth', + project: 'test-project', + template: 'test-template', + bucket: ['test-bucket'], + hostFilter: ['test-host'], + header: ['header1', 'header2'], + crawler: ['test-crawler'], + archive: 'test-archive', + repeater: ['test-repeater'], + smart: true, + param: ['param1', 'param2'], + excludeEntryPoint: ['exclude-entry-point'], + excludeParam: ['exclude-param'], + _: [], + $0: '' + } as Arguments; + + when(processSpy.exit(anything())).thenReturn(undefined); + when( + mockedScans.create( + objectContaining({ + tests: args.test as TestType[], + name: args.name as string, + module: args.module as Module, + authObjectId: args.auth as string, + projectId: args.project as string, + templateId: args.template as string, + buckets: args.bucket as string[], + hostsFilter: args.hostFilter as string[], + crawlerUrls: args.crawler as string[], + fileId: args.archive as string, + repeaters: args.repeater as string[], + smart: args.smart as boolean, + attackParamLocations: args.param as AttackParamLocation[], + exclusions: { + requests: args.excludeEntryPoint, + params: args.excludeParam + } as Exclusions + }) + ) + ).thenResolve({ id: 'test-scan-id', warnings: [] }); + + // act + await runScan.handler(args); + + // assert + verify(processSpy.exit(0)).once(); + verify(loggerSpy.error(anything())).never(); + verify(loggerSpy.warn(anything())).never(); + }); + + it('should throw an error on create request fall', async () => { + // arrange + const args = { + name: 'test-scan', + _: [], + $0: '' + } as Arguments; + const errMessage = 'request error'; + + when(processSpy.exit(anything())).thenReturn(undefined); + when( + mockedScans.create( + objectContaining({ + name: args.name as string + }) + ) + ).thenReject(new Error(errMessage)); + + // act + await runScan.handler(args); + + // assert + verify(processSpy.exit(1)).once(); + verify(loggerSpy.error(`Error during "scan:run": ${errMessage}`)).once(); + }); + + it('should display warnings when present', async () => { + // arrange + const args = { + name: 'test-scan', + _: [], + $0: '' + } as Arguments; + + const warnings: ScanWarning[] = [ + { message: 'Warning message 1', code: '123' }, + { message: 'Warning message 2', code: '124' } + ]; + + when(processSpy.exit(anything())).thenReturn(undefined); + when(mockedScans.create(anything())).thenResolve({ + id: 'test-scan-id', + warnings + }); + + // act + await runScan.handler(args); + + // assert + verify(processSpy.exit(0)).once(); + verify(loggerSpy.warn('Warning message 1\nWarning message 2\n')).once(); + }); + }); }); diff --git a/src/Commands/RunScan.ts b/src/Commands/RunScan.ts index 36bc21ce..51832fd8 100644 --- a/src/Commands/RunScan.ts +++ b/src/Commands/RunScan.ts @@ -174,7 +174,7 @@ export class RunScan implements CommandModule { try { const scanManager: Scans = container.resolve(Scans); - const scanId: string = await scanManager.create({ + const { id: scanId, warnings = [] } = await scanManager.create({ tests: args.test, name: args.name, module: args.module, @@ -198,6 +198,12 @@ export class RunScan implements CommandModule { // eslint-disable-next-line no-console console.log(scanId); + if (warnings.length) { + logger.warn( + `${warnings.map((warning) => warning.message).join('\n')}\n` + ); + } + process.exit(0); } catch (e) { logger.error(`Error during "scan:run": ${e.error || e.message}`); diff --git a/src/Scan/RestScans.spec.ts b/src/Scan/RestScans.spec.ts index e727b03f..39704404 100644 --- a/src/Scan/RestScans.spec.ts +++ b/src/Scan/RestScans.spec.ts @@ -95,7 +95,7 @@ describe('RestScans', () => { const result = await restScans.create(scanConfig); // assert - expect(result).toEqual(postResponse.id); + expect(result).toEqual({ id: postResponse.id }); expect(parsedBody).toMatchObject({ discoveryTypes: expect.arrayContaining([expected]) }); diff --git a/src/Scan/RestScans.ts b/src/Scan/RestScans.ts index 79078003..3090c0e7 100644 --- a/src/Scan/RestScans.ts +++ b/src/Scan/RestScans.ts @@ -7,7 +7,8 @@ import { SourceType, StorageFile, SCAN_TESTS_TO_RUN_BY_DEFAULT, - ATTACK_PARAM_LOCATIONS_DEFAULT + ATTACK_PARAM_LOCATIONS_DEFAULT, + ScanCreateResponse } from './Scans'; import { CliInfo } from '../Config'; import { ProxyFactory } from '../Utils'; @@ -56,15 +57,15 @@ export class RestScans implements Scans { }); } - public async create(body: ScanConfig): Promise { + public async create(body: ScanConfig): Promise { const scanConfig = await this.prepareScanConfig({ ...body }); - const res = await this.client.post<{ id: string }>( + const res = await this.client.post( '/api/v1/scans', scanConfig ); - return res.data.id; + return res.data; } public async retest(scanId: string): Promise { diff --git a/src/Scan/Scans.ts b/src/Scan/Scans.ts index 8daba20c..6996a705 100644 --- a/src/Scan/Scans.ts +++ b/src/Scan/Scans.ts @@ -195,8 +195,18 @@ export interface StorageFile { type: SourceType; } +export interface ScanWarning { + code: string; + message: string; +} + +export interface ScanCreateResponse { + id: string; + warnings: ScanWarning[]; +} + export interface Scans { - create(body: ScanConfig): Promise; + create(body: ScanConfig): Promise; retest(scanId: string): Promise;