Skip to content

Commit

Permalink
SDK-2492 add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeyspryker committed Aug 1, 2023
1 parent 9fcdd8c commit 4a6228e
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 5 deletions.
13 changes: 9 additions & 4 deletions src/Checker/NpmChecker/NpmAuditExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ class NpmAuditExecutor
protected const VULNERABILITIES_KEY = 'vulnerabilities';

/**
* @var array<string>
* @var array<string> to skip info level
*/
protected const SEVERITY_LEVELS = ['low', 'moderate', 'high', 'critical'];
protected const ALLOWED_SEVERITY_LEVELS = ['low', 'moderate', 'high', 'critical'];

/**
* @var string
*/
protected const DEFAULT_AUDIT_LEVEL = 'low';

/**
* @var string
Expand Down Expand Up @@ -70,7 +75,7 @@ public function __construct(ProcessRunnerInterface $processRunner)
*/
public function executeNpmAudit(): array
{
$process = $this->processRunner->run(['npm', 'audit', '--json']);
$process = $this->processRunner->run(['npm', 'audit', '--json', '--audit-level', static::DEFAULT_AUDIT_LEVEL]);

if ($process->isSuccessful()) {
return [];
Expand Down Expand Up @@ -111,7 +116,7 @@ protected function getViolationsFromReport(array $violations): array

$severity = $violation[static::SEVERITY_KEY];

if (!in_array($severity, static::SEVERITY_LEVELS, true)) {
if (!in_array($severity, static::ALLOWED_SEVERITY_LEVELS, true)) {
continue;
}

Expand Down
7 changes: 6 additions & 1 deletion src/Checker/NpmChecker/NpmChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ class NpmChecker extends AbstractChecker
*/
public const NAME = 'NPM_CHECKER';

/**
* @var string
*/
public const NPM_ISSUE_MESSAGE_PREFIX = 'Npm audit issue';

/**
* @var \SprykerSdk\Evaluator\Checker\NpmChecker\NpmInstallationValidator
*/
Expand Down Expand Up @@ -66,7 +71,7 @@ public function check(CheckerInputDataDto $inputData): CheckerResponseDto
try {
$violations = $this->npmAuditExecutor->executeNpmAudit();
} catch (NpmExecutorException $e) {
$violations = [new ViolationDto(sprintf('Npm issue: %s', $e->getMessage()))];
$violations = [new ViolationDto(sprintf('%s: %s', static::NPM_ISSUE_MESSAGE_PREFIX, $e->getMessage()))];
}

return new CheckerResponseDto($violations, $this->checkerDocUrl);
Expand Down
193 changes: 193 additions & 0 deletions tests/Unit/Checker/NpmChecker/NpmAuditExecutorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

declare(strict_types=1);

namespace SprykerSdkTest\Evaluator\Unit\Checker\NpmChecker;

use PHPUnit\Framework\TestCase;
use SprykerSdk\Evaluator\Checker\NpmChecker\NpmAuditExecutor;
use SprykerSdk\Evaluator\Checker\NpmChecker\NpmExecutorException;
use SprykerSdk\Evaluator\Process\ProcessRunnerInterface;
use Symfony\Component\Process\Process;

class NpmAuditExecutorTest extends TestCase
{
/**
* @return void
*/
public function testExecuteNpmAuditShouldReturnEmptyArrayWhenAuditIsSuccessful(): void
{
//Arrange
$processMock = $this->createProcessMock('', '', true);
$processRunnerMock = $this->createProcessRunnerMock($processMock);
$npmAuditExecutor = new NpmAuditExecutor($processRunnerMock);

//Act
$result = $npmAuditExecutor->executeNpmAudit();

//Assert
$this->assertEmpty($result);
}

/**
* @return void
*/
public function testExecuteNpmAuditShouldThrowExceptionWhenProcessReturnedError(): void
{
//Arrange
$processMock = $this->createProcessMock('', 'Some Error', false);
$processRunnerMock = $this->createProcessRunnerMock($processMock);
$npmAuditExecutor = new NpmAuditExecutor($processRunnerMock);

$this->expectException(NpmExecutorException::class);
$this->expectExceptionMessage('Some Error');

//Act
$npmAuditExecutor->executeNpmAudit();
}

/**
* @return void
*/
public function testExecuteNpmAuditShouldThrowExceptionWhenInvalidJsonReturned(): void
{
//Arrange
$processMock = $this->createProcessMock('{Invalid:json');
$processRunnerMock = $this->createProcessRunnerMock($processMock);
$npmAuditExecutor = new NpmAuditExecutor($processRunnerMock);

$this->expectException(NpmExecutorException::class);

//Act
$npmAuditExecutor->executeNpmAudit();
}

/**
* @dataProvider invalidJsonFormatReceivedDataProvider
*
* @param string $toolOutput
*
* @return void
*/
public function testExecuteNpmAuditShouldThrowExceptionWhenUnexpectedJsonReturned(string $toolOutput): void
{
//Arrange
$processMock = $this->createProcessMock($toolOutput);
$processRunnerMock = $this->createProcessRunnerMock($processMock);
$npmAuditExecutor = new NpmAuditExecutor($processRunnerMock);

$this->expectException(NpmExecutorException::class);

//Act
$npmAuditExecutor->executeNpmAudit();
}

/**
* @dataProvider vulnerabilitiesThatShouldBeSkippedDataProvider
*
* @param string $toolOutput
*
* @return void
*/
public function testExecuteNpmAuditShouldSkipVulnerabilities(string $toolOutput): void
{
//Arrange
$processMock = $this->createProcessMock($toolOutput);
$processRunnerMock = $this->createProcessRunnerMock($processMock);
$npmAuditExecutor = new NpmAuditExecutor($processRunnerMock);

//Act
$result = $npmAuditExecutor->executeNpmAudit();

//Assert
$this->assertEmpty($result);
}

/**
* @return void
*/
public function testExecuteNpmAuditShouldFetchUniqueDataFromNpmAudit(): void
{
//Arrange
$processMock = $this->createProcessMock(
'{"vulnerabilities": {
"datatables.net": {"name": "datatables.net", "severity": "critical", "via": [{"title": "Test violation", "url": "https://violation-url"}]},
"datatables.net": {"name": "datatables.net", "severity": "critical", "via": [{"title": "Test violation", "url": "https://violation-url"}]}}
}',
);
$processRunnerMock = $this->createProcessRunnerMock($processMock);
$npmAuditExecutor = new NpmAuditExecutor($processRunnerMock);

//Act
$result = $npmAuditExecutor->executeNpmAudit();

//Assert
$this->assertCount(1, $result);
$violationDto = $result[0];

$this->assertSame("[critical] Test violation\nhttps://violation-url", $violationDto->getMessage());
$this->assertSame('datatables.net', $violationDto->getTarget());
}

/**
* @return array<string, array<string>>
*/
public function invalidJsonFormatReceivedDataProvider(): array
{
return [
'missedVulnerabilitiesKey' => ['{}'],
'nonArrayVulnerabilitiesKey' => ['{"vulnerabilities": "non-array"}'],
'missedNameKey' => ['{"vulnerabilities": {"datatables.net": {"severity": "critical", "via": {"title": "someTitle"}}}}'],
'missedSeverityKey' => ['{"vulnerabilities": {"datatables.net": {"name": "datatables.net", "via": {"title": "someTitle"}}}}'],
'missedViaKey' => ['{"vulnerabilities": {"datatables.net": {"name": "datatables.net", "severity": "critical"}'],
'nonArrayViaKey' => ['{"vulnerabilities": {"datatables.net": {"name": "datatables.net", "severity": "critical", "via": "non-array"}}}'],
];
}

/**
* @return array<string, array<string>>
*/
public function vulnerabilitiesThatShouldBeSkippedDataProvider(): array
{
return [
'skipInfoVulnerability' => ['{"vulnerabilities": {"datatables.net": {"name": "datatables.net", "severity": "info", "via": [{"title": "Cross site scripting"}]}}}'],
'skipNoTitleVulnerability' => ['{"vulnerabilities": {"datatables.net": {"name": "datatables.net", "severity": "critical", "via": [{"url": "http://some-url"}]}}}'],
'skipNonRootVulnerability' => ['{"vulnerabilities": {"datatables.net": {"name": "datatables.net", "severity": "critical", "via": ["@spryker/oryx","autoprefixer"]}}}'],
];
}

/**
* @param string $stdOut
* @param string $stdErr
* @param bool $isSuccessful
*
* @return \Symfony\Component\Process\Process
*/
public function createProcessMock(string $stdOut, string $stdErr = '', bool $isSuccessful = false): Process
{
$process = $this->createMock(Process::class);
$process->method('isSuccessful')->willReturn($isSuccessful);
$process->method('getOutput')->willReturn($stdOut);
$process->method('getErrorOutput')->willReturn($stdErr);

return $process;
}

/**
* @param \Symfony\Component\Process\Process $process
*
* @return \SprykerSdk\Evaluator\Process\ProcessRunnerInterface
*/
public function createProcessRunnerMock(Process $process): ProcessRunnerInterface
{
$processRunner = $this->createMock(ProcessRunnerInterface::class);
$processRunner->method('run')->willReturn($process);

return $processRunner;
}
}
111 changes: 111 additions & 0 deletions tests/Unit/Checker/NpmChecker/NpmCheckerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

declare(strict_types=1);

namespace SprykerSdkTest\Evaluator\Unit\Checker\NpmChecker;

use PHPUnit\Framework\TestCase;
use SprykerSdk\Evaluator\Checker\NpmChecker\NpmAuditExecutor;
use SprykerSdk\Evaluator\Checker\NpmChecker\NpmChecker;
use SprykerSdk\Evaluator\Checker\NpmChecker\NpmExecutorException;
use SprykerSdk\Evaluator\Checker\NpmChecker\NpmInstallationValidator;
use SprykerSdk\Evaluator\Dto\CheckerInputDataDto;
use SprykerSdk\Evaluator\Dto\ViolationDto;

class NpmCheckerTest extends TestCase
{
/**
* @return void
*/
public function testIsApplicableShouldReturnIfNpmInstalled(): void
{
//Arrange
$npmInstallationValidatorMock = $this->createNpmInstallationValidatorMock();
$npmAuditExecutorMock = $this->createNpmAuditExecutorMock([]);
$npmChecker = new NpmChecker($npmInstallationValidatorMock, $npmAuditExecutorMock);

//Act
$result = $npmChecker->isApplicable();

//Assert
$this->assertTrue($result);
}

/**
* @return void
*/
public function testIsApplicableShouldReturnNpmIssueWhenThrownException(): void
{
//Arrange
$npmInstallationValidatorMock = $this->createNpmInstallationValidatorMock();
$npmAuditExecutorMock = $this->createNpmAuditExecutorMock([], true);
$inputData = new CheckerInputDataDto('');
$npmChecker = new NpmChecker($npmInstallationValidatorMock, $npmAuditExecutorMock);

//Act
$response = $npmChecker->check($inputData);

//Assert
$this->assertCount(1, $response->getViolations());
$violationDto = $response->getViolations()[0];

$this->assertStringStartsWith(NpmChecker::NPM_ISSUE_MESSAGE_PREFIX, $violationDto->getMessage());
}

/**
* @return void
*/
public function testIsApplicableShouldReturnValidResponse(): void
{
//Arrange
$npmInstallationValidatorMock = $this->createNpmInstallationValidatorMock();
$violationDto = new ViolationDto('some message', 'target');
$npmAuditExecutorMock = $this->createNpmAuditExecutorMock([$violationDto]);
$inputData = new CheckerInputDataDto('');
$npmChecker = new NpmChecker($npmInstallationValidatorMock, $npmAuditExecutorMock);

//Act
$response = $npmChecker->check($inputData);

//Assert
$this->assertCount(1, $response->getViolations());
$violationDto = $response->getViolations()[0];

$this->assertSame($violationDto, $violationDto);
}

/**
* @return \SprykerSdk\Evaluator\Checker\NpmChecker\NpmInstallationValidator
*/
public function createNpmInstallationValidatorMock(): NpmInstallationValidator
{
$npmInstallationValidator = $this->createMock(NpmInstallationValidator::class);
$npmInstallationValidator->method('isNpmInstalled')->willReturn(true);

return $npmInstallationValidator;
}

/**
* @param array<\SprykerSdk\Evaluator\Dto\ViolationDto> $violations
* @param bool $throwException
*
* @return \SprykerSdk\Evaluator\Checker\NpmChecker\NpmAuditExecutor
*/
public function createNpmAuditExecutorMock(array $violations, bool $throwException = false): NpmAuditExecutor
{
$npmAuditExecutor = $this->createMock(NpmAuditExecutor::class);

if ($throwException) {
$npmAuditExecutor->method('executeNpmAudit')->willThrowException(new NpmExecutorException('Some issue'));
}

$npmAuditExecutor->method('executeNpmAudit')->willReturn($violations);

return $npmAuditExecutor;
}
}
Loading

0 comments on commit 4a6228e

Please sign in to comment.