From c9464fc172e3acd1dc2bd6e881683750dd256dca Mon Sep 17 00:00:00 2001 From: Brian Henry Date: Wed, 27 May 2020 21:40:32 -0700 Subject: [PATCH 1/4] Add graceful error handling when composer/extra/mozart are missing --- phpunit.xml | 4 +- src/Console/Commands/Compose.php | 22 ++- tests/Console/Commands/ComposeTest.php | 225 +++++++++++++++++++++++++ 3 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 tests/Console/Commands/ComposeTest.php diff --git a/phpunit.xml b/phpunit.xml index e55ef1c1..6f7b5b5c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -9,8 +9,8 @@ processIsolation="false" stopOnFailure="false"> - - ./tests/replacers/ + + ./tests/ \ No newline at end of file diff --git a/src/Console/Commands/Compose.php b/src/Console/Commands/Compose.php index 10b6ec25..0008c8a5 100644 --- a/src/Console/Commands/Compose.php +++ b/src/Console/Commands/Compose.php @@ -37,8 +37,26 @@ protected function execute(InputInterface $input, OutputInterface $output) $workingDir = getcwd(); $this->workingDir = $workingDir; - $config = json_decode(file_get_contents($workingDir . '/composer.json')); - $config = $config->extra->mozart; + $composerFile = $workingDir . '/composer.json'; + if (!file_exists($composerFile)) { + $output->write('No composer.json found at current directory: ' . $workingDir); + return 1; + } + + $composer = json_decode(file_get_contents($composerFile)); + // If the json was malformed. + if (!is_object($composer)) { + $output->write('Unable to parse composer.json read at: ' . $workingDir); + return 1; + } + + // if `extra` is missing or not an object or if it does not have a `mozart` key which is an object. + if (!isset($composer->extra) || !is_object($composer->extra) + || !isset($composer->extra->mozart) || !is_object($composer->extra->mozart)) { + $output->write('Mozart config not readable in composer.json at extra->mozart'); + return 1; + } + $config = $composer->extra->mozart; $config->dep_namespace = preg_replace("/\\\{2,}$/", "\\", "$config->dep_namespace\\"); diff --git a/tests/Console/Commands/ComposeTest.php b/tests/Console/Commands/ComposeTest.php new file mode 100644 index 00000000..0203fae4 --- /dev/null +++ b/tests/Console/Commands/ComposeTest.php @@ -0,0 +1,225 @@ +createMock(InputInterface::class); + $outputInterfaceMock = $this->createMock(OutputInterface::class); + + $outputInterfaceMock->expects($this->exactly(1)) + ->method('write'); + + $compose = new class( $inputInterfaceMock, $outputInterfaceMock ) extends Compose { + public function __construct($inputInterfaceMock, $outputInterfaceMock) + { + parent::__construct(); + + $this->execute($inputInterfaceMock, $outputInterfaceMock); + } + }; + } + + /** + * When json_decode fails, instead of + * "Trying to get property 'extra' of non-object" + * a better message should be written to the OutputInterface. + * + * @test + */ + public function it_handles_malformed_json_with_grace(): void + { + + $badComposerJson = '{ "name": "coenjacobs/mozart", }'; + + file_put_contents(__DIR__ . '/composer.json', $badComposerJson); + + $inputInterfaceMock = $this->createMock(InputInterface::class); + $outputInterfaceMock = $this->createMock(OutputInterface::class); + + $outputInterfaceMock->expects($this->exactly(1)) + ->method('write'); + + $compose = new class( $inputInterfaceMock, $outputInterfaceMock ) extends Compose { + public function __construct($inputInterfaceMock, $outputInterfaceMock) + { + parent::__construct(); + + $this->execute($inputInterfaceMock, $outputInterfaceMock); + } + }; + } + + /** + * When composer.json->extra is absent, instead of + * "Undefined property: stdClass::$extra" + * a better message should be written to the OutputInterface. + * + * @test + */ + public function it_handles_absent_extra_config_with_grace(): void + { + + $badComposerJson = '{ "name": "coenjacobs/mozart" }'; + + file_put_contents(__DIR__ . '/composer.json', $badComposerJson); + + $inputInterfaceMock = $this->createMock(InputInterface::class); + $outputInterfaceMock = $this->createMock(OutputInterface::class); + + $outputInterfaceMock->expects($this->exactly(1)) + ->method('write'); + + $compose = new class( $inputInterfaceMock, $outputInterfaceMock ) extends Compose { + public function __construct($inputInterfaceMock, $outputInterfaceMock) + { + parent::__construct(); + + $this->execute($inputInterfaceMock, $outputInterfaceMock); + } + }; + } + + + /** + * When composer.json->extra is not an object, instead of + * "Trying to get property 'mozart' of non-object" + * a better message should be written to the OutputInterface. + * + * @test + */ + public function it_handles_malformed_extra_config_with_grace(): void + { + + $badComposerJson = '{ "name": "coenjacobs/mozart", "extra": [] }'; + + file_put_contents(__DIR__ . '/composer.json', $badComposerJson); + + $inputInterfaceMock = $this->createMock(InputInterface::class); + $outputInterfaceMock = $this->createMock(OutputInterface::class); + + $outputInterfaceMock->expects($this->exactly(1)) + ->method('write'); + + $compose = new class( $inputInterfaceMock, $outputInterfaceMock ) extends Compose { + public function __construct($inputInterfaceMock, $outputInterfaceMock) + { + parent::__construct(); + + $this->execute($inputInterfaceMock, $outputInterfaceMock); + } + }; + } + + /** + * When composer.json->extra->mozart is absent, instead of + * "Undefined property: stdClass::$mozart" + * a better message should be written to the OutputInterface. + * + * @test + */ + public function it_handles_absent_mozart_config_with_grace(): void + { + + $badComposerJson = '{ "name": "coenjacobs/mozart", "extra": { "moozart": {} } }'; + + file_put_contents(__DIR__ . '/composer.json', $badComposerJson); + + $inputInterfaceMock = $this->createMock(InputInterface::class); + $outputInterfaceMock = $this->createMock(OutputInterface::class); + + $outputInterfaceMock->expects($this->exactly(1)) + ->method('write'); + + $compose = new class( $inputInterfaceMock, $outputInterfaceMock ) extends Compose { + public function __construct($inputInterfaceMock, $outputInterfaceMock) + { + parent::__construct(); + + $this->execute($inputInterfaceMock, $outputInterfaceMock); + } + }; + } + + /** + * When composer.json->extra->mozart is malformed, instead of + * "Undefined property: stdClass::$mozart" + * a better message should be written to the OutputInterface. + * + * is_object() added. + * + * @test + */ + public function it_handles_malformed_mozart_config__with_grace(): void + { + + $badComposerJson = '{ "name": "coenjacobs/mozart", "extra": { "mozart": [] } }'; + + file_put_contents(__DIR__ . '/composer.json', $badComposerJson); + + $inputInterfaceMock = $this->createMock(InputInterface::class); + $outputInterfaceMock = $this->createMock(OutputInterface::class); + + $outputInterfaceMock->expects($this->exactly(1)) + ->method('write'); + + $compose = new class( $inputInterfaceMock, $outputInterfaceMock ) extends Compose { + public function __construct($inputInterfaceMock, $outputInterfaceMock) + { + parent::__construct(); + + $this->execute($inputInterfaceMock, $outputInterfaceMock); + } + }; + } + + public function tearDown(): void + { + parent::tearDown(); + + $composer_json = __DIR__ . '/composer.json'; + if (file_exists($composer_json)) { + unlink($composer_json); + } + } + + public static function tearDownAfterClass(): void + { + parent::tearDownAfterClass(); + chdir(self::$cwd); + } +} From f7b4498a655d96566a7c7a9223c24c1e59110ab5 Mon Sep 17 00:00:00 2001 From: Brian Henry Date: Wed, 27 May 2020 21:41:06 -0700 Subject: [PATCH 2/4] Use composer->require when extra->mozart->packages is missing Or empty array if both are missing. --- src/Console/Commands/Compose.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Console/Commands/Compose.php b/src/Console/Commands/Compose.php index 0008c8a5..26dfafed 100644 --- a/src/Console/Commands/Compose.php +++ b/src/Console/Commands/Compose.php @@ -65,7 +65,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->mover = new Mover($workingDir, $config); $this->replacer = new Replacer($workingDir, $config); - $require = empty($config->packages) ? array_keys(get_object_vars($this->config->require)) : $config->packages; + $require = array(); + if (isset($config->packages) && is_array($config->packages)) { + $require = $config->packages; + } elseif (isset($composer->require) && is_array($composer->require)) { + $require = $composer->require; + } $packages = $this->findPackages($require); From b723b0b5968904ed7c613f6b04facb99c524ea6f Mon Sep 17 00:00:00 2001 From: Brian Henry Date: Wed, 27 May 2020 21:59:34 -0700 Subject: [PATCH 3/4] I forgot require was an object not an array --- src/Console/Commands/Compose.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Console/Commands/Compose.php b/src/Console/Commands/Compose.php index 26dfafed..8b5f6863 100644 --- a/src/Console/Commands/Compose.php +++ b/src/Console/Commands/Compose.php @@ -68,8 +68,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $require = array(); if (isset($config->packages) && is_array($config->packages)) { $require = $config->packages; - } elseif (isset($composer->require) && is_array($composer->require)) { - $require = $composer->require; + } elseif (isset($composer->require) && is_object($composer->require)) { + $require = array_keys(get_object_vars($composer->require)); } $packages = $this->findPackages($require); From d842c28dae13d499be45a5fddeef5ca4638db10f Mon Sep 17 00:00:00 2001 From: Brian Henry Date: Wed, 27 May 2020 22:08:34 -0700 Subject: [PATCH 4/4] phpcbf --- src/Console/Commands/Compose.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/Commands/Compose.php b/src/Console/Commands/Compose.php index 8b5f6863..f06b7026 100644 --- a/src/Console/Commands/Compose.php +++ b/src/Console/Commands/Compose.php @@ -69,7 +69,7 @@ protected function execute(InputInterface $input, OutputInterface $output) if (isset($config->packages) && is_array($config->packages)) { $require = $config->packages; } elseif (isset($composer->require) && is_object($composer->require)) { - $require = array_keys(get_object_vars($composer->require)); + $require = array_keys(get_object_vars($composer->require)); } $packages = $this->findPackages($require);