From b9308a89719643b6a62711d4fc5765ce8ed0dc45 Mon Sep 17 00:00:00 2001 From: SunMar Date: Sun, 3 Jul 2022 23:35:59 +0200 Subject: [PATCH] Misc improvements (see full commit message): - allow configuration of guards per package (#17) - in case of multiple licenses, accept any instead of require all (#18) - symfony/debug and symfony/var-dumper are no longer whitelisted, only the description/keyword guard will not trigger on them (if they ever get abandoned, or a license is changed, those guards will trigger) - add symfony/error-handler to the ignore list of the description/keyword guard (is a production package, pulled in by f.ex. the Laravel framework) - fix mikey179/vfsstream not being detected as dev package (#19) - update test dependencies to the latest versions - add CodeSniffer test for PHP compatibility with version >= 7.0 --- .gitignore | 4 +- README.md | 5 +- composer.json | 30 +++-- phpunit.xml.dist | 18 +-- src/Guard.php | 50 ++++---- .../ByPackageDescriptionInspector.php | 11 ++ src/Inspectors/ByPackageLicenseInspector.php | 26 +++-- src/Inspectors/ByPackageNameInspector.php | 2 +- src/Inspectors/StubInspector.php | 14 --- src/Settings.php | 108 ++++++++++++++++++ src/Whitelist.php | 12 +- tests/GuardTest.php | 104 +++++++++++------ .../ByPackageAbandonedInspectorTest.php | 3 +- .../ByPackageDescriptionInspectorTest.php | 45 +++++--- .../ByPackageLicenseInspectorTest.php | 38 ++++-- .../Inspectors/ByPackageNameInspectorTest.php | 10 +- .../Inspectors/ByPackageTypeInspectorTest.php | 7 +- tests/Inspectors/StubInspectorTest.php | 18 --- tests/SettingsTest.php | 93 +++++++++++++++ .../FromComposerLockSupplierTest.php | 5 +- .../FromComposerManifestSupplierTest.php | 4 +- tests/WhitelistTest.php | 18 ++- tests/data/activate-additional-features.json | 16 ++- .../for-settings-test-malformed-boolean.json | 14 +++ .../for-settings-test-malformed-list.json | 15 +++ .../for-settings-test-malformed-option.json | 15 +++ .../for-settings-test-unknown-setting.json | 14 +++ tests/data/for-settings-test.json | 26 +++++ 28 files changed, 549 insertions(+), 176 deletions(-) delete mode 100644 src/Inspectors/StubInspector.php create mode 100644 src/Settings.php delete mode 100644 tests/Inspectors/StubInspectorTest.php create mode 100644 tests/SettingsTest.php create mode 100644 tests/data/for-settings-test-malformed-boolean.json create mode 100644 tests/data/for-settings-test-malformed-list.json create mode 100644 tests/data/for-settings-test-malformed-option.json create mode 100644 tests/data/for-settings-test-unknown-setting.json create mode 100644 tests/data/for-settings-test.json diff --git a/.gitignore b/.gitignore index 3d44b7f..a64900c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ Vagrantfile *.log *.phar /*.lock -build \ No newline at end of file +build +.phpunit.result.cache +/.idea \ No newline at end of file diff --git a/README.md b/README.md index 14878a0..53b7985 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,9 @@ Additional guard checks can be enabled in the top-level composer.json file: "white-list:vendor/package-two", "accept-license:MIT", - "accept-license:proprietary" + "accept-license:proprietary", + + "package-guards:vendor/package-three:abandoned,description" ] } } @@ -40,6 +42,7 @@ Additional guard checks can be enabled in the top-level composer.json file: - `check-abandoned` enables abandoned packages checking - `check-license` enables license checking (packages must provide license information) - `accept-license:...` specifies which licenses should be accepted (if the setting omitted, any license incl. proprietary) +- `package-guards::...` specifies the guards per package (comma separated list, guards: dev-package-name, dev-package-type, license*, abandoned*, description*). * = only available if enabled via the check-... option # Usage diff --git a/composer.json b/composer.json index 43e4014..3b11ffe 100644 --- a/composer.json +++ b/composer.json @@ -10,16 +10,19 @@ "role": "maintainer" }], "require": { - "php": "^7.0|^8.0", - "ext-json": "*", - "composer-plugin-api": "^1.0|^2.0" + "php": ">=7.0", + "ext-json": "*", + "composer-plugin-api": "^1.0|^2.0" }, "require-dev": { - "ext-xdebug": "*", - "composer/composer": "^1.0|^2.0", - "phpunit/phpunit": "^6.5", - "infection/infection": "^0.9", - "rregeer/phpunit-coverage-check": "^0.1" + "ext-xdebug": "*", + "composer/composer": "^1.0|^2.0", + "phpunit/phpunit": "^9.5", + "infection/infection": "^0.26", + "rregeer/phpunit-coverage-check": "^0.3", + "squizlabs/php_codesniffer": "^3.7", + "phpcompatibility/php-compatibility": "^9.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2" }, "autoload": { "psr-4": { @@ -36,9 +39,16 @@ }, "scripts": { "test": [ - "./vendor/bin/phpunit --coverage-html ./build --coverage-clover ./build/phpunit.clover.xml", + "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html ./build --coverage-clover ./build/phpunit.clover.xml", "./vendor/bin/coverage-check ./build/phpunit.clover.xml 100", - "./vendor/bin/infection --only-covered --min-msi=100 --quiet" + "./vendor/bin/infection --min-msi=100 --quiet", + "./vendor/bin/phpcs --standard=PHPCompatibility --extensions=php --ignore=./vendor,./tests --runtime-set testVersion 7.0- ." ] + }, + "config": { + "allow-plugins": { + "infection/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } } } \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7973d82..1289f23 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,26 +1,20 @@ - - ./tests + ./tests - - + + ./src - - + + diff --git a/src/Guard.php b/src/Guard.php index ed8ba12..d9aa25e 100644 --- a/src/Guard.php +++ b/src/Guard.php @@ -4,7 +4,6 @@ use Composer\Composer; use Composer\EventDispatcher\EventSubscriberInterface as EventSubscriberContract; -use Composer\Factory; use Composer\IO\IOInterface; use Composer\Package\CompletePackageInterface; use Composer\Plugin\PluginInterface as ComposerPluginContract; @@ -14,18 +13,13 @@ use Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageLicenseInspector; use Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageNameInspector; use Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageTypeInspector; -use Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\StubInspector; +use Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\InspectorInterface as InspectorContract; use Kalessil\Composer\Plugins\ProductionDependenciesGuard\Suppliers\FromComposerLockSupplier; use Kalessil\Composer\Plugins\ProductionDependenciesGuard\Suppliers\FromComposerManifestSupplier; use Kalessil\Composer\Plugins\ProductionDependenciesGuard\Suppliers\SupplierInterface as SupplierContract; final class Guard implements ComposerPluginContract, EventSubscriberContract { - const CHECK_LOCK_FILE = 'check-lock-file'; - const CHECK_DESCRIPTION = 'check-description'; - const CHECK_LICENSE = 'check-license'; - const CHECK_ABANDONED = 'check-abandoned'; - /** @var bool */ private $useLockFile; /** @var Composer */ @@ -36,26 +30,35 @@ final class Guard implements ComposerPluginContract, EventSubscriberContract private $whitelist; /** @var SupplierContract */ private $supplier; + /** @var array> */ + private $packageInspectors; public function activate(Composer $composer, IOInterface $io) { - $manifest = json_decode(file_get_contents(Factory::getComposerFile()), true); - $settings = $manifest['extra']['production-dependencies-guard'] ?? []; + $settings = new Settings(); - $checkLicense = \in_array(self::CHECK_LICENSE, $settings, true); - $checkAbandoned = \in_array(self::CHECK_ABANDONED, $settings, true); - $checkDescription = \in_array(self::CHECK_DESCRIPTION, $settings, true); $this->inspectors = [ - 'dev-package-name' => new ByPackageNameInspector(), - 'dev-package-type' => new ByPackageTypeInspector(), - 'license' => $checkLicense ? new ByPackageLicenseInspector($settings) : new StubInspector(), - 'abandoned' => $checkAbandoned ? new ByPackageAbandonedInspector() : new StubInspector(), - 'description-keywords' => $checkDescription ? new ByPackageDescriptionInspector() : new StubInspector(), + 'dev-package-name' => new ByPackageNameInspector(), + 'dev-package-type' => new ByPackageTypeInspector(), ]; + if ($settings->checkLicense()) { + $this->inspectors['license'] = new ByPackageLicenseInspector($settings->acceptLicense()); + } + + if ($settings->checkAbandoned()) { + $this->inspectors['abandoned'] = new ByPackageAbandonedInspector(); + } + + if ($settings->checkDescription()) { + $this->inspectors['description'] = new ByPackageDescriptionInspector(); + } + + $this->packageInspectors = $settings->packageGuards(); + $this->composer = $composer; - $this->whitelist = new Whitelist($settings); - $this->useLockFile = \in_array(self::CHECK_LOCK_FILE, $settings, true); + $this->whitelist = new Whitelist($settings->whiteList()); + $this->useLockFile = $settings->checkLockFile(); $this->supplier = $this->useLockFile ? new FromComposerLockSupplier() : new FromComposerManifestSupplier(); } @@ -75,8 +78,10 @@ private function check(SupplierContract $supplier, CompletePackageInterface... $ $packageId = sprintf('%s (via %s)', $packageName, implode(', ', $supplier->why($packageName))); $violations[$packageId] = []; foreach ($this->inspectors as $rule => $inspector) { - if (! $inspector->canUse($package)) { - $violations[$packageId] []= $rule; + if (! isset($this->packageInspectors[$packageName]) || in_array($rule, $this->packageInspectors[$packageName], true)) { + if (! $inspector->canUse($package)) { + $violations[$packageId][] = $rule; + } } } } @@ -93,7 +98,8 @@ private function check(SupplierContract $supplier, CompletePackageInterface... $ } /** @return array */ - private function find(string... $packages): array { + private function find(string ...$packages): array { + /* @infection-ignore-all */ return array_filter( array_filter( $this->composer->getRepositoryManager()->getLocalRepository()->getPackages(), diff --git a/src/Inspectors/ByPackageDescriptionInspector.php b/src/Inspectors/ByPackageDescriptionInspector.php index 040bd82..41291e4 100644 --- a/src/Inspectors/ByPackageDescriptionInspector.php +++ b/src/Inspectors/ByPackageDescriptionInspector.php @@ -7,6 +7,13 @@ final class ByPackageDescriptionInspector implements InspectorContract { + /* ignore reason: see https://github.com/symfony/symfony/issues/31379 */ + const IGNORE_PACKAGES = [ + 'symfony/debug', + 'symfony/var-dumper', + 'symfony/error-handler', + ]; + private function hasDebugKeyword(CompletePackageInterface $package): bool { $callback = static function (string $term): bool { return strtolower($term) === 'debug'; }; @@ -21,6 +28,10 @@ private function hasAnalyzerDescription(CompletePackageInterface $package): bool public function canUse(CompletePackageInterface $package): bool { + if (true === in_array(strtolower($package->getName()), self::IGNORE_PACKAGES, true)) { + return true; + } + return ! $this->hasDebugKeyword($package) && ! $this->hasAnalyzerDescription($package); } } \ No newline at end of file diff --git a/src/Inspectors/ByPackageLicenseInspector.php b/src/Inspectors/ByPackageLicenseInspector.php index 1d86428..83d77b8 100644 --- a/src/Inspectors/ByPackageLicenseInspector.php +++ b/src/Inspectors/ByPackageLicenseInspector.php @@ -7,26 +7,28 @@ final class ByPackageLicenseInspector implements InspectorContract { - /** @var array */ + /** @var array */ private $allowed; - public function __construct(array $settings) + /** + * @param array $allowed + */ + public function __construct(array $allowed) { - $this->allowed = array_map( - static function (string $setting): string { return str_replace('accept-license:', '', $setting); }, - array_filter( - array_map('strtolower', array_map('trim', $settings)), - static function (string $setting): bool { return strncmp($setting, 'accept-license:', 15) === 0; } - ) - ); + $this->allowed = $allowed; } public function canUse(CompletePackageInterface $package): bool { - $hasLicense = ! empty($package->getLicense()); + $licenses = $package->getLicense(); + $hasLicense = ! empty($licenses); if ($hasLicense && $this->allowed !== []) { - $unfit = array_diff(array_map('strtolower', array_map('trim', (array) $package->getLicense())), $this->allowed); - return $unfit === []; + return array_intersect( + array_map(static function (string $license): string { + return strtolower(trim($license)); + }, $licenses), + $this->allowed + ) !== []; } return $hasLicense; diff --git a/src/Inspectors/ByPackageNameInspector.php b/src/Inspectors/ByPackageNameInspector.php index 9d20b31..86ec074 100644 --- a/src/Inspectors/ByPackageNameInspector.php +++ b/src/Inspectors/ByPackageNameInspector.php @@ -49,7 +49,7 @@ final class ByPackageNameInspector implements InspectorContract 'humbug/humbug', 'infection/infection', 'mockery/mockery', - 'mikey179/vfsStream', + 'mikey179/vfsstream', 'phing/phing', /* SCA and code quality tools */ diff --git a/src/Inspectors/StubInspector.php b/src/Inspectors/StubInspector.php deleted file mode 100644 index 8724f6c..0000000 --- a/src/Inspectors/StubInspector.php +++ /dev/null @@ -1,14 +0,0 @@ -settings[$settingName] = true; + } elseif (true === in_array($settingName, self::LIST_SETTINGS, true)) { + if (count($settingParts) !== 1) { + throw new \InvalidArgumentException('Malformed setting, expected only one colon: ' . $settingName); + } + + $this->settings[$settingName][] = strtolower(trim($settingParts[0])); + } elseif (true === in_array($settingName, self::OPTION_SETTINGS, true)) { + if (count($settingParts) !== 2) { + throw new \InvalidArgumentException('Malformed setting, expected two colons: ' . $settingName); + } + + $this->settings[$settingName][strtolower(trim($settingParts[0]))] = array_map(static function (string $guard): string { + return strtolower(trim($guard)); + }, explode(',', $settingParts[1])); + } else { + throw new \InvalidArgumentException('Unknown setting: ' . $settingName); + } + } + } + + public function checkAbandoned(): bool + { + return $this->settings[self::CHECK_ABANDONED] ?? false; + } + + public function checkDescription(): bool + { + return $this->settings[self::CHECK_DESCRIPTION] ?? false; + } + + public function checkLicense(): bool + { + return $this->settings[self::CHECK_LICENSE] ?? false; + } + + public function checkLockFile(): bool + { + return $this->settings[self::CHECK_LOCK_FILE] ?? false; + } + + /** @return array */ + public function whiteList(): array + { + return $this->settings[self::WHITE_LIST] ?? []; + } + + /** @return array */ + public function acceptLicense(): array + { + return $this->settings[self::ACCEPT_LICENSE] ?? []; + } + + /** @return array> */ + public function packageGuards(): array + { + return $this->settings[self::PACKAGE_GUARDS] ?? []; + } +} diff --git a/src/Whitelist.php b/src/Whitelist.php index b189996..7e8d46a 100644 --- a/src/Whitelist.php +++ b/src/Whitelist.php @@ -10,17 +10,9 @@ final class Whitelist implements InspectorContract /** @var array */ private $whitelist; - public function __construct(array $settings) + public function __construct(array $whitelist) { - $this->whitelist = array_map( - static function (string $setting): string { return str_replace('white-list:', '', $setting); }, - array_filter( - array_map('strtolower', array_map('trim', $settings)), - static function (string $setting): bool { return strncmp($setting, 'white-list:', 11) === 0; } - ) - ); - /* whitelisting reason: see https://github.com/symfony/symfony/issues/31379 */ - array_push($this->whitelist, 'symfony/debug', 'symfony/var-dumper'); + $this->whitelist = $whitelist; } public function canUse(CompletePackageInterface $package): bool diff --git a/tests/GuardTest.php b/tests/GuardTest.php index b7335f0..c1e902d 100644 --- a/tests/GuardTest.php +++ b/tests/GuardTest.php @@ -5,56 +5,96 @@ use Composer\Composer; use Composer\IO\IOInterface; use Composer\Package\CompletePackageInterface; +use Composer\Repository\InstalledRepositoryInterface; +use Composer\Repository\RepositoryManager; use PHPUnit\Framework\TestCase; final class GuardTest extends TestCase { - /** @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Guard:: */ - public function testSubscribedEvents() { - $this->assertCount(2, Guard::getSubscribedEvents()); + public function testSubscribedEvents(): void + { + $this->assertSame([ + 'post-install-cmd' => ['checkGeneric'], + 'post-update-cmd' => ['checkGeneric'], + ], Guard::getSubscribedEvents()); } - /** - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Guard:: - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Guard:: - */ - public function testGenericBehaviourReporting() { + public function testGenericBehaviourReporting(): void + { $composer = $this->getMockBuilder(Composer::class) - ->setMethods(['getRepositoryManager', 'getLocalRepository', 'getPackages']) + ->disableOriginalConstructor() + ->onlyMethods(['getRepositoryManager']) + ->getMock(); + $repositoryManager = $this->getMockBuilder(RepositoryManager::class) + ->disableOriginalConstructor() + ->onlyMethods(['getLocalRepository']) ->getMock(); - $composer->expects($this->atLeastOnce())->method('getRepositoryManager')->willReturn($composer); - $composer->expects($this->atLeastOnce())->method('getLocalRepository')->willReturn($composer); - $composer->expects($this->atLeastOnce())->method('getPackages')->willReturnCallback(function (): array { + $installedRepository = $this->getMockBuilder(InstalledRepositoryInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['getPackages']) + ->getMockForAbstractClass(); + $composer->expects($this->atLeastOnce())->method('getRepositoryManager')->willReturn($repositoryManager); + $repositoryManager->expects($this->atLeastOnce())->method('getLocalRepository')->willReturn($installedRepository); + $installedRepository->expects($this->atLeastOnce())->method('getPackages')->willReturnCallback(function (): array { $pass = $this->createMock(CompletePackageInterface::class); $pass->expects($this->atLeastOnce())->method('getName')->willReturn('kalessil/kalessil'); $pass->expects($this->atLeastOnce())->method('getType')->willReturn('library'); $decline = $this->createMock(CompletePackageInterface::class); - $decline->expects($this->atLeastOnce())->method('getName')->willReturn('phpunit/phpunit'); + $decline->expects($this->atLeastOnce())->method('getName')->willReturn('PHPUnit/phpunit'); $decline->expects($this->atLeastOnce())->method('getType')->willReturn('phpcodesniffer-standard'); - return [$pass, $decline]; + $abandoned = $this->createMock(CompletePackageInterface::class); + $abandoned->expects($this->atLeastOnce())->method('getName')->willReturn('vendor/abandoned'); + $abandoned->expects($this->atLeastOnce())->method('getType')->willReturn('library'); + $abandoned->expects($this->atLeastOnce())->method('isAbandoned')->willReturn(true); + + $debug = $this->createMock(CompletePackageInterface::class); + $debug->expects($this->atLeastOnce())->method('getName')->willReturn('vendor/debug'); + $debug->expects($this->atLeastOnce())->method('getType')->willReturn('library'); + $debug->expects($this->atLeastOnce())->method('getDescription')->willReturn('debug'); + + $guards = $this->createMock(CompletePackageInterface::class); + $guards->expects($this->atLeastOnce())->method('getName')->willReturn('vendor/guards'); + $guards->method('isAbandoned')->willReturn(true); + $guards->expects($this->atLeastOnce())->method('getLicense')->willReturn(['MIT']); + + return [$pass, $decline, $abandoned, $debug, $guards]; }); $this->expectException(\RuntimeException::class); - $this->expectExceptionMessageRegExp('/violations.+ phpunit\/phpunit \(via manifest\): dev-package-name, dev-package-type\s*$/ims'); + $this->expectExceptionMessage(<<<'___EOM___' +Dependencies guard has found violations in require-dependencies (source: manifest): + - kalessil/kalessil (via manifest): license + - phpunit/phpunit (via manifest): dev-package-name, dev-package-type, license + - vendor/abandoned (via manifest): license, abandoned + - vendor/debug (via manifest): license, description +___EOM___ + ); - putenv(sprintf('COMPOSER=%s/data/activate-none-features.json', __DIR__)); + putenv(sprintf('COMPOSER=%s/data/activate-additional-features.json', __DIR__)); $component = new Guard(); $component->activate($composer, $this->createMock(IOInterface::class)); $component->checkGeneric(); } - /** - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Guard:: - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Guard:: - */ - public function testGenericBehaviourPassing() { + + public function testGenericBehaviourPassing(): void + { $composer = $this->getMockBuilder(Composer::class) - ->setMethods(['getRepositoryManager', 'getLocalRepository', 'getPackages']) + ->disableOriginalConstructor() + ->onlyMethods(['getRepositoryManager']) + ->getMock(); + $repositoryManager = $this->getMockBuilder(RepositoryManager::class) + ->disableOriginalConstructor() + ->onlyMethods(['getLocalRepository']) ->getMock(); - $composer->expects($this->atLeastOnce())->method('getRepositoryManager')->willReturn($composer); - $composer->expects($this->atLeastOnce())->method('getLocalRepository')->willReturn($composer); - $composer->expects($this->atLeastOnce())->method('getPackages')->willReturnCallback(function (): array { + $installedRepository = $this->getMockBuilder(InstalledRepositoryInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['getPackages']) + ->getMockForAbstractClass(); + $composer->expects($this->atLeastOnce())->method('getRepositoryManager')->willReturn($repositoryManager); + $repositoryManager->expects($this->atLeastOnce())->method('getLocalRepository')->willReturn($installedRepository); + $installedRepository->expects($this->atLeastOnce())->method('getPackages')->willReturnCallback(function (): array { $pass = $this->createMock(CompletePackageInterface::class); $pass->expects($this->atLeastOnce())->method('getName')->willReturn('kalessil/kalessil'); $pass->expects($this->atLeastOnce())->method('getType')->willReturn('library'); @@ -67,10 +107,9 @@ public function testGenericBehaviourPassing() { $component->activate($composer, $this->createMock(IOInterface::class)); $component->checkGeneric(); } - /** - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Guard:: - */ - public function testDeactivate() { + + public function testDeactivate(): void + { $composer = $this->getMockBuilder(Composer::class) ->disableOriginalConstructor() ->getMock(); @@ -79,10 +118,9 @@ public function testDeactivate() { $component = new Guard(); $component->deactivate($composer, $this->createMock(IOInterface::class)); } - /** - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Guard:: - */ - public function testUninstall() { + + public function testUninstall(): void + { $composer = $this->getMockBuilder(Composer::class) ->disableOriginalConstructor() ->getMock(); diff --git a/tests/Inspectors/ByPackageAbandonedInspectorTest.php b/tests/Inspectors/ByPackageAbandonedInspectorTest.php index 9ca7f93..a8454b0 100644 --- a/tests/Inspectors/ByPackageAbandonedInspectorTest.php +++ b/tests/Inspectors/ByPackageAbandonedInspectorTest.php @@ -7,8 +7,7 @@ final class ByPackageAbandonedInspectorTest extends TestCase { - /** @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageAbandonedInspector:: */ - public function testComponent() + public function testComponent(): void { $mock = $this->createMock(PackageContract::class); $mock->expects($this->atLeastOnce())->method('isAbandoned')->willReturn(true, false); diff --git a/tests/Inspectors/ByPackageDescriptionInspectorTest.php b/tests/Inspectors/ByPackageDescriptionInspectorTest.php index e856cef..670f1ed 100644 --- a/tests/Inspectors/ByPackageDescriptionInspectorTest.php +++ b/tests/Inspectors/ByPackageDescriptionInspectorTest.php @@ -7,14 +7,15 @@ final class ByPackageDescriptionInspectorTest extends TestCase { - /** - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageDescriptionInspector:: - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageDescriptionInspector:: - */ - public function testWithKeywords() + public function testWithKeywords(): void { $mock = $this->createMock(PackageContract::class); - $mock->expects($this->atLeastOnce())->method('getKeywords')->willReturn([], ['keyword'], ['debug'], ['DEBUG']); + $mock->expects($this->atLeastOnce())->method('getKeywords')->willReturn( + [], + ['keyword'], + ['debug'], + ['DEBUG'] + ); $component = new ByPackageDescriptionInspector(); $this->assertTrue($component->canUse($mock)); @@ -23,14 +24,10 @@ public function testWithKeywords() $this->assertFalse($component->canUse($mock)); } - /** - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageDescriptionInspector:: - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageDescriptionInspector:: - */ - public function testWithDescription() + public function testWithDescription(): void { $mock = $this->createMock(PackageContract::class); - $mock->expects($this->atLeastOnce())->method('getDescription')->willReturn(...[ + $mock->expects($this->atLeastOnce())->method('getDescription')->willReturn( '', '...', ' debug ', @@ -40,9 +37,10 @@ public function testWithDescription() 'static analyzer', 'static code analyzer', 'STATIC CODE ANALYZER', - ]); + ); $component = new ByPackageDescriptionInspector(); + $this->assertTrue($component->canUse($mock)); $this->assertTrue($component->canUse($mock)); $this->assertFalse($component->canUse($mock)); @@ -53,4 +51,25 @@ public function testWithDescription() $this->assertFalse($component->canUse($mock)); $this->assertFalse($component->canUse($mock)); } + + public function testIgnoredPackages(): void + { + $mock = $this->createMock(PackageContract::class); + $mock->expects($this->atLeastOnce())->method('getDescription')->willReturn('debug'); + $mock->expects($this->atLeastOnce())->method('getName')->willReturn( + 'vendor/package', + 'symfony/DEBUG', + 'symfony/debug', + 'symfony/var-dumper', + 'symfony/error-handler', + ); + + $component = new ByPackageDescriptionInspector(); + + $this->assertFalse($component->canUse($mock)); + $this->assertTrue($component->canUse($mock)); + $this->assertTrue($component->canUse($mock)); + $this->assertTrue($component->canUse($mock)); + $this->assertTrue($component->canUse($mock)); + } } \ No newline at end of file diff --git a/tests/Inspectors/ByPackageLicenseInspectorTest.php b/tests/Inspectors/ByPackageLicenseInspectorTest.php index 2343632..40251f6 100644 --- a/tests/Inspectors/ByPackageLicenseInspectorTest.php +++ b/tests/Inspectors/ByPackageLicenseInspectorTest.php @@ -7,24 +7,48 @@ final class ByPackageLicenseInspectorTest extends TestCase { - /** @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageLicenseInspector:: */ - public function testComponent() + public function testComponentWithAcceptedLicenses(): void { $mock = $this->createMock(PackageContract::class); - $mock->expects($this->atLeastOnce())->method('getLicense')->willReturn(...[ - '', + $mock->expects($this->atLeastOnce())->method('getLicense')->willReturn( + [''], [], - 'MIT', + ['MIT '], + ['MIT'], + ['Apache'], ['mit', 'apache'], ['mit', 'proprietary', 'apache'], - ]); + ['gpl', 'proprietary'] + ); - $component = new ByPackageLicenseInspector(['accept-license:mit', 'accept-license:apache']); + $component = new ByPackageLicenseInspector([ + 'mit', + 'apache', + ]); $this->assertFalse($component->canUse($mock)); $this->assertFalse($component->canUse($mock)); $this->assertTrue($component->canUse($mock)); $this->assertTrue($component->canUse($mock)); + $this->assertTrue($component->canUse($mock)); + $this->assertTrue($component->canUse($mock)); + $this->assertTrue($component->canUse($mock)); $this->assertFalse($component->canUse($mock)); } + + public function testComponentWithoutAcceptedLicenses(): void + { + $mock = $this->createMock(PackageContract::class); + $mock->expects($this->atLeastOnce())->method('getLicense')->willReturn( + [''], + [], + ['MIT'], + ); + + $component = new ByPackageLicenseInspector([]); + + $this->assertTrue($component->canUse($mock)); + $this->assertFalse($component->canUse($mock)); + $this->assertTrue($component->canUse($mock)); + } } \ No newline at end of file diff --git a/tests/Inspectors/ByPackageNameInspectorTest.php b/tests/Inspectors/ByPackageNameInspectorTest.php index 27cfa05..208381d 100644 --- a/tests/Inspectors/ByPackageNameInspectorTest.php +++ b/tests/Inspectors/ByPackageNameInspectorTest.php @@ -7,22 +7,20 @@ final class ByPackageNameInspectorTest extends TestCase { - /** - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageNameInspector:: - * @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageNameInspector:: - */ - public function testComponent() + public function testComponent(): void { $mock = $this->createMock(PackageContract::class); $mock->expects($this->atLeastOnce())->method('getName')->willReturn(...[ '', 'phpunit/phpunit', - 'roave/security-advisories' + 'roave/security-advisories', + 'Roave/Security-Advisories', ]); $component = new ByPackageNameInspector(); $this->assertTrue($component->canUse($mock)); $this->assertFalse($component->canUse($mock)); $this->assertFalse($component->canUse($mock)); + $this->assertFalse($component->canUse($mock)); } } \ No newline at end of file diff --git a/tests/Inspectors/ByPackageTypeInspectorTest.php b/tests/Inspectors/ByPackageTypeInspectorTest.php index 32dee73..3a7393c 100644 --- a/tests/Inspectors/ByPackageTypeInspectorTest.php +++ b/tests/Inspectors/ByPackageTypeInspectorTest.php @@ -7,19 +7,20 @@ final class ByPackageTypeInspectorTest extends TestCase { - /** @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Inspectors\ByPackageTypeInspector:: */ - public function testComponent() + public function testComponent(): void { $mock = $this->createMock(PackageContract::class); $mock->expects($this->atLeastOnce())->method('getType')->willReturn(...[ '', 'library', - 'phpcodesniffer-standard' + 'phpcodesniffer-standard', + 'PhpCodeSniffer-standard' ]); $component = new ByPackageTypeInspector(); $this->assertTrue($component->canUse($mock)); $this->assertTrue($component->canUse($mock)); $this->assertFalse($component->canUse($mock)); + $this->assertFalse($component->canUse($mock)); } } \ No newline at end of file diff --git a/tests/Inspectors/StubInspectorTest.php b/tests/Inspectors/StubInspectorTest.php deleted file mode 100644 index fb281ec..0000000 --- a/tests/Inspectors/StubInspectorTest.php +++ /dev/null @@ -1,18 +0,0 @@ - */ - public function testComponent() - { - $mock = $this->createMock(PackageContract::class); - $mock->expects($this->never())->method($this->anything()); - - $this->assertTrue((new StubInspector())->canUse($mock)); - } -} \ No newline at end of file diff --git a/tests/SettingsTest.php b/tests/SettingsTest.php new file mode 100644 index 0000000..76e49d3 --- /dev/null +++ b/tests/SettingsTest.php @@ -0,0 +1,93 @@ + [ 'abandoned' ], + 'vendor/package2' => [ 'description', 'license' ], + ]; + + $this->assertTrue($settings->checkAbandoned()); + $this->assertTrue($settings->checkDescription()); + $this->assertTrue($settings->checkLicense()); + $this->assertTrue($settings->checkLockFile()); + $this->assertSame($expectedWhiteList, $settings->whiteList()); + $this->assertSame($expectedAcceptLicense, $settings->acceptLicense()); + $this->assertSame($expectedPackageGuards, $settings->packageGuards()); + } + + public function testActivateNoneFeatures(): void + { + putenv(sprintf('COMPOSER=%s/data/activate-none-features.json', __DIR__)); + + $settings = new Settings(); + + $this->assertFalse($settings->checkAbandoned()); + $this->assertFalse($settings->checkDescription()); + $this->assertFalse($settings->checkLicense()); + $this->assertFalse($settings->checkLockFile()); + $this->assertEmpty($settings->whiteList()); + $this->assertEmpty($settings->acceptLicense()); + $this->assertEmpty($settings->packageGuards()); + } + + public function testMalformedBoolean(): void + { + putenv(sprintf('COMPOSER=%s/data/for-settings-test-malformed-boolean.json', __DIR__)); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Malformed setting, found unexpected colon: check-description'); + + new Settings(); + } + + public function testMalformedList(): void + { + putenv(sprintf('COMPOSER=%s/data/for-settings-test-malformed-list.json', __DIR__)); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Malformed setting, expected only one colon: white-list'); + + new Settings(); + } + + public function testMalformedOption(): void + { + putenv(sprintf('COMPOSER=%s/data/for-settings-test-malformed-option.json', __DIR__)); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Malformed setting, expected two colons: package-guards'); + + new Settings(); + } + + public function testUnknownSetting(): void + { + putenv(sprintf('COMPOSER=%s/data/for-settings-test-unknown-setting.json', __DIR__)); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Unknown setting: foobar'); + + new Settings(); + } +} \ No newline at end of file diff --git a/tests/Suppliers/FromComposerLockSupplierTest.php b/tests/Suppliers/FromComposerLockSupplierTest.php index c37855e..fa5b082 100644 --- a/tests/Suppliers/FromComposerLockSupplierTest.php +++ b/tests/Suppliers/FromComposerLockSupplierTest.php @@ -6,12 +6,13 @@ final class FromComposerLockSupplierTest extends TestCase { - /** @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Suppliers\FromComposerLockSupplier:: */ - public function testComponent() { + public function testComponent(): void + { putenv(sprintf('COMPOSER=%s/../data/composer.json', __DIR__)); $component= new FromComposerLockSupplier(); $this->assertSame(['kalessil/production-dependencies-guard-lock'], $component->packages()); $this->assertSame(['kalessil/production-dependencies-guard-lock'], $component->why('phpunit/phpunit')); + $this->assertSame(['manifest'], $component->why('vendor/package')); putenv('COMPOSER='); } } \ No newline at end of file diff --git a/tests/Suppliers/FromComposerManifestSupplierTest.php b/tests/Suppliers/FromComposerManifestSupplierTest.php index b4d5fc7..3916153 100644 --- a/tests/Suppliers/FromComposerManifestSupplierTest.php +++ b/tests/Suppliers/FromComposerManifestSupplierTest.php @@ -6,8 +6,8 @@ final class FromComposerManifestSupplierTest extends TestCase { - /** @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Suppliers\FromComposerManifestSupplier:: */ - public function testComponent() { + public function testComponent(): void + { putenv(sprintf('COMPOSER=%s/../data/composer.json', __DIR__)); $component = new FromComposerManifestSupplier(); $this->assertSame(['kalessil/production-dependencies-guard-manifest'], $component->packages()); diff --git a/tests/WhitelistTest.php b/tests/WhitelistTest.php index f88add1..1f3470a 100644 --- a/tests/WhitelistTest.php +++ b/tests/WhitelistTest.php @@ -7,16 +7,26 @@ final class WhitelistTest extends TestCase { - /** @covers \Kalessil\Composer\Plugins\ProductionDependenciesGuard\Whitelist:: */ - public function testComponent() + public function testComponent(): void { $package = $this->createMock(CompletePackageInterface::class); - $package->expects($this->atLeastOnce())->method('getName')->willReturn('package1', 'package2', 'package3'); + $package->expects($this->atLeastOnce())->method('getName')->willReturn( + 'package1', + 'Package2', + 'package3', + '...', + 'vendor/package', + ); - $component = new Whitelist(['', '...', 'white-list:package1', 'white-list:package2']); + $component = new Whitelist([ + 'package1', + 'package2', + ]); $this->assertTrue($component->canUse($package)); $this->assertTrue($component->canUse($package)); $this->assertFalse($component->canUse($package)); + $this->assertFalse($component->canUse($package)); + $this->assertFalse($component->canUse($package)); } } \ No newline at end of file diff --git a/tests/data/activate-additional-features.json b/tests/data/activate-additional-features.json index 5a26425..2dfb4c3 100644 --- a/tests/data/activate-additional-features.json +++ b/tests/data/activate-additional-features.json @@ -2,11 +2,21 @@ "name": "package/featured", "require": { - "phpunit/phpunit": "*", - "kalessil/kalessil": "*" + "phpunit/phpunit": "*", + "kalessil/kalessil": "*", + "symfony/var-dumper": "*", + "vendor/abandoned": "*", + "vendor/debug": "*", + "vendor/guards": "*" }, "extra": { - "production-dependencies-guard": ["check-description", "check-license", "check-abandoned"] + "production-dependencies-guard": [ + "check-description", + "check-license", + "check-abandoned", + "accept-license:MIT", + "package-guards:vendor/guards:license" + ] } } \ No newline at end of file diff --git a/tests/data/for-settings-test-malformed-boolean.json b/tests/data/for-settings-test-malformed-boolean.json new file mode 100644 index 0000000..b27afcd --- /dev/null +++ b/tests/data/for-settings-test-malformed-boolean.json @@ -0,0 +1,14 @@ +{ + "name": "package/featured", + + "require": { + "phpunit/phpunit": "*", + "kalessil/kalessil": "*" + }, + + "extra": { + "production-dependencies-guard": [ + "check-description:" + ] + } +} \ No newline at end of file diff --git a/tests/data/for-settings-test-malformed-list.json b/tests/data/for-settings-test-malformed-list.json new file mode 100644 index 0000000..d61a939 --- /dev/null +++ b/tests/data/for-settings-test-malformed-list.json @@ -0,0 +1,15 @@ +{ + "name": "package/featured", + + "require": { + "phpunit/phpunit": "*", + "kalessil/kalessil": "*" + }, + + "extra": { + "production-dependencies-guard": [ + "white-list", + "white-list::" + ] + } +} \ No newline at end of file diff --git a/tests/data/for-settings-test-malformed-option.json b/tests/data/for-settings-test-malformed-option.json new file mode 100644 index 0000000..de99cc9 --- /dev/null +++ b/tests/data/for-settings-test-malformed-option.json @@ -0,0 +1,15 @@ +{ + "name": "package/featured", + + "require": { + "phpunit/phpunit": "*", + "kalessil/kalessil": "*" + }, + + "extra": { + "production-dependencies-guard": [ + "package-guards:", + "package-guards:::" + ] + } +} \ No newline at end of file diff --git a/tests/data/for-settings-test-unknown-setting.json b/tests/data/for-settings-test-unknown-setting.json new file mode 100644 index 0000000..c2367e1 --- /dev/null +++ b/tests/data/for-settings-test-unknown-setting.json @@ -0,0 +1,14 @@ +{ + "name": "package/featured", + + "require": { + "phpunit/phpunit": "*", + "kalessil/kalessil": "*" + }, + + "extra": { + "production-dependencies-guard": [ + "foobar" + ] + } +} \ No newline at end of file diff --git a/tests/data/for-settings-test.json b/tests/data/for-settings-test.json new file mode 100644 index 0000000..ad8ce3e --- /dev/null +++ b/tests/data/for-settings-test.json @@ -0,0 +1,26 @@ +{ + "name": "package/featured", + + "require": { + "phpunit/phpunit": "*", + "kalessil/kalessil": "*", + "symfony/var-dumper": "*", + "vendor/abandoned": "*", + "vendor/debug": "*" + }, + + "extra": { + "production-dependencies-guard": [ + "check-Description", + "check-License ", + " check-abandoned ", + "check-lock-file", + "White-list: vendor/trim ", + "white-list:vendor/CaPiTaLiZaTiOn", + "accept-License: trim ", + "accept-license:CaPiTaLiZaTiOn", + "package-guards: vendor/package1:abandoned", + "package-guards:Vendor/Package2:description ,LiCense" + ] + } +} \ No newline at end of file