From 1754313503e32d476d17d63321f5940b97875bcc Mon Sep 17 00:00:00 2001 From: Sami Mazouz Date: Wed, 24 Nov 2021 18:25:43 +0100 Subject: [PATCH] test: Updates (#11) --- .../Controller/RemoveExtensionController.php | 16 +-- .../Api/Serializer/ExtensionSerializer.php | 39 ------ .../src/Exception/ExceptionHandler.php | 4 +- .../src/Settings/JsonSetting.php | 7 + .../src/Settings/LastUpdateRun.php | 8 +- .../package-manager/src/WhyNotValidator.php | 7 + .../integration/ChangeComposerConfig.php | 14 ++ .../tests/integration/DummyExtensions.php | 32 +++++ .../integration/RefreshComposerSetup.php | 37 +++++ .../tests/integration/SetupComposer.php | 47 +++++-- .../tests/integration/TestCase.php | 24 +++- .../tests/integration/api/MajorUpdateTest.php | 127 ++++++++++++++++++ .../tests/integration/api/MinorUpdateTest.php | 95 +++++++++++++ .../api/extensions/RequireExtensionTest.php | 4 +- 14 files changed, 391 insertions(+), 70 deletions(-) delete mode 100755 extensions/package-manager/src/Api/Serializer/ExtensionSerializer.php create mode 100644 extensions/package-manager/tests/integration/ChangeComposerConfig.php create mode 100644 extensions/package-manager/tests/integration/DummyExtensions.php create mode 100644 extensions/package-manager/tests/integration/api/MajorUpdateTest.php create mode 100644 extensions/package-manager/tests/integration/api/MinorUpdateTest.php diff --git a/extensions/package-manager/src/Api/Controller/RemoveExtensionController.php b/extensions/package-manager/src/Api/Controller/RemoveExtensionController.php index 8e9d6707b..28d144721 100755 --- a/extensions/package-manager/src/Api/Controller/RemoveExtensionController.php +++ b/extensions/package-manager/src/Api/Controller/RemoveExtensionController.php @@ -9,18 +9,17 @@ namespace Flarum\PackageManager\Api\Controller; -use Flarum\Api\Controller\AbstractDeleteController; use Flarum\Bus\Dispatcher; use Flarum\Http\RequestUtil; use Illuminate\Support\Arr; -use Flarum\PackageManager\Api\Serializer\ExtensionSerializer; +use Laminas\Diactoros\Response\EmptyResponse; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Flarum\PackageManager\Command\RemoveExtension; +use Psr\Http\Server\RequestHandlerInterface; -class RemoveExtensionController extends AbstractDeleteController +class RemoveExtensionController implements RequestHandlerInterface { - public $serializer = ExtensionSerializer::class; - /** * @var Dispatcher */ @@ -31,10 +30,7 @@ class RemoveExtensionController extends AbstractDeleteController $this->bus = $bus; } - /** - * @throws \Flarum\User\Exception\PermissionDeniedException - */ - protected function delete(ServerRequestInterface $request) + public function handle(ServerRequestInterface $request): ResponseInterface { $actor = RequestUtil::getActor($request); $extensionId = Arr::get($request->getQueryParams(), 'id'); @@ -42,5 +38,7 @@ class RemoveExtensionController extends AbstractDeleteController $this->bus->dispatch( new RemoveExtension($actor, $extensionId) ); + + return new EmptyResponse(); } } diff --git a/extensions/package-manager/src/Api/Serializer/ExtensionSerializer.php b/extensions/package-manager/src/Api/Serializer/ExtensionSerializer.php deleted file mode 100755 index 07d5b763d..000000000 --- a/extensions/package-manager/src/Api/Serializer/ExtensionSerializer.php +++ /dev/null @@ -1,39 +0,0 @@ -getId(); - } - - protected function getDefaultAttributes($model) - { - if (is_array($model)) { - return $model; - } - - if (! ($model instanceof Extension)) { - throw new InvalidArgumentException( - get_class($this).' can only serialize instances of '.Extension::class - ); - } - - return $model->toArray(); - } -} diff --git a/extensions/package-manager/src/Exception/ExceptionHandler.php b/extensions/package-manager/src/Exception/ExceptionHandler.php index 29a2b4971..6ef741867 100755 --- a/extensions/package-manager/src/Exception/ExceptionHandler.php +++ b/extensions/package-manager/src/Exception/ExceptionHandler.php @@ -24,9 +24,7 @@ class ExceptionHandler protected function errorDetails(ComposerCommandFailedException $e): array { - $details = [ - 'output' => $e->getMessage(), - ]; + $details = []; if ($guessedCause = $this->guessCause($e)) { $details['guessed_cause'] = $guessedCause; diff --git a/extensions/package-manager/src/Settings/JsonSetting.php b/extensions/package-manager/src/Settings/JsonSetting.php index eb214a1c7..834ab780d 100644 --- a/extensions/package-manager/src/Settings/JsonSetting.php +++ b/extensions/package-manager/src/Settings/JsonSetting.php @@ -1,5 +1,12 @@ settings->get(self::key()), true); + $lastUpdateRun = json_decode($this->settings->get(self::key()), true); + + if ($this->activeUpdate) { + return $lastUpdateRun[$this->activeUpdate]; + } + + return $lastUpdateRun; } public static function key(): string diff --git a/extensions/package-manager/src/WhyNotValidator.php b/extensions/package-manager/src/WhyNotValidator.php index d49a6dc4a..ee5b2ef0e 100644 --- a/extensions/package-manager/src/WhyNotValidator.php +++ b/extensions/package-manager/src/WhyNotValidator.php @@ -1,5 +1,12 @@ run(); + + $this->composer('install'); + } +} diff --git a/extensions/package-manager/tests/integration/DummyExtensions.php b/extensions/package-manager/tests/integration/DummyExtensions.php new file mode 100644 index 000000000..c5c48f75b --- /dev/null +++ b/extensions/package-manager/tests/integration/DummyExtensions.php @@ -0,0 +1,32 @@ +tmpDir() . "/packages/" . str_replace('/', '-', $name); + + if (! file_exists($dirName)) { + mkdir($dirName); + } + + file_put_contents($dirName."/composer.json", json_encode([ + 'name' => $name, + 'version' => '1.0.0', + 'require' => [ + 'flarum/core' => $coreVersions + ], + ], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + } +} diff --git a/extensions/package-manager/tests/integration/RefreshComposerSetup.php b/extensions/package-manager/tests/integration/RefreshComposerSetup.php index 8009f0386..3e4262d53 100644 --- a/extensions/package-manager/tests/integration/RefreshComposerSetup.php +++ b/extensions/package-manager/tests/integration/RefreshComposerSetup.php @@ -1,7 +1,19 @@ tmpDir().'/composer.lock'); + $this->deleteDummyExtensions(); + $composerSetup->run(); $this->composer('install'); parent::tearDown(); } + + private function deleteDummyExtensions(): void + { + $dir = $this->tmpDir().'/packages'; + + $it = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS); + $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST); + + foreach($files as $file) { + if ($file->isDir()){ + rmdir($file->getRealPath()); + } else { + unlink($file->getRealPath()); + } + } + + rmdir($dir); + } + + protected function forgetComposerApp(): void + { + $this->app()->getContainer()->instance(ComposerAdapter::class, null); + } } diff --git a/extensions/package-manager/tests/integration/SetupComposer.php b/extensions/package-manager/tests/integration/SetupComposer.php index 58ee1b2df..5112e9912 100644 --- a/extensions/package-manager/tests/integration/SetupComposer.php +++ b/extensions/package-manager/tests/integration/SetupComposer.php @@ -15,20 +15,43 @@ class SetupComposer { use UsesTmpDir; + private $config = [ + 'require' => [ + 'flarum/core' => '1.0.0', + 'flarum/tags' => '1.0.3', + 'flarum/lang-english' => '*', + ], + 'config' => [ + 'preferred-install' => 'dist', + 'sort-packages' => true, + ], + 'repositories' => [ + [ + 'type' => 'path', + 'url' => __DIR__.'/../tmp/packages/*', + ] + ] + ]; + + public function __construct(array $config = null) + { + $this->config = array_merge($this->config, $config ?? []); + } + public function run() { - $filePath = $this->tmpDir().'/composer.json'; + $composerJson = $this->tmpDir().'/composer.json'; + $composerLock = $this->tmpDir().'/composer.lock'; + $packages = $this->tmpDir().'/packages'; - file_put_contents($filePath, json_encode([ - 'require' => [ - 'flarum/core' => '1.0.0', - 'flarum/tags' => '1.0.3', - 'flarum/lang-english' => '*', - ], - 'config' => [ - 'preferred-install' => 'dist', - 'sort-packages' => true, - ], - ], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + file_put_contents($composerJson, json_encode($this->config, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); + + if (! file_exists($packages)) { + mkdir($packages); + } + + if (file_exists($composerLock)) { + unlink($composerLock); + } } } diff --git a/extensions/package-manager/tests/integration/TestCase.php b/extensions/package-manager/tests/integration/TestCase.php index 1adaa1de8..468b8368c 100644 --- a/extensions/package-manager/tests/integration/TestCase.php +++ b/extensions/package-manager/tests/integration/TestCase.php @@ -11,6 +11,7 @@ namespace Flarum\PackageManager\Tests\integration; use Flarum\Foundation\Paths; use Flarum\PackageManager\Composer\ComposerAdapter; +use Flarum\PackageManager\Composer\ComposerJson; use Flarum\PackageManager\Extension\ExtensionUtils; use Flarum\Testing\integration\RetrievesAuthorizedUsers; use Illuminate\Support\Arr; @@ -27,7 +28,7 @@ class TestCase extends \Flarum\Testing\integration\TestCase $this->extension('flarum-package-manager', 'flarum-tags'); - $tmp = $this->tmpDir(); + $tmp = realpath($this->tmpDir()); $this->app()->getContainer()->instance('flarum.paths', new Paths([ 'base' => $tmp, @@ -64,6 +65,14 @@ class TestCase extends \Flarum\Testing\integration\TestCase $this->assertExtension($id, false); } + protected function assertPackageVersion(string $packageName, string $version) + { + $composerJson = $this->app()->getContainer()->make(ComposerJson::class)->get(); + + $this->assertArrayHasKey($packageName, $composerJson['require'], "$packageName is not required."); + $this->assertEquals($version, $composerJson['require'][$packageName], "Expected $packageName to be $version, found {$composerJson['require'][$packageName]} instead."); + } + protected function requireExtension(string $package) { $this->composer("require $package"); @@ -81,10 +90,17 @@ class TestCase extends \Flarum\Testing\integration\TestCase $composer->run(new StringInput($command)); } - protected function guessedCause(ResponseInterface $response): ?string + protected function errorGuessedCause(ResponseInterface $response): ?string { - $json = json_decode($response->getBody()->getContents(), true); + $details = $this->errorDetails($response); - return $json['errors'] ? ($json['errors'][0]['guessed_cause'] ?? null) : null; + return $details['guessed_cause'] ?? null; + } + + protected function errorDetails(ResponseInterface $response): array + { + $json = json_decode((string) $response->getBody(), true); + + return $json['errors'] ? ($json['errors'][0] ?? []) : []; } } diff --git a/extensions/package-manager/tests/integration/api/MajorUpdateTest.php b/extensions/package-manager/tests/integration/api/MajorUpdateTest.php new file mode 100644 index 000000000..04fccc25e --- /dev/null +++ b/extensions/package-manager/tests/integration/api/MajorUpdateTest.php @@ -0,0 +1,127 @@ +makeDummyExtensionCompatibleWith("flarum/dummy-incompatible-extension", ">=0.1.0-beta.15 <=0.1.0-beta.16"); + $this->setComposerConfig([ + 'require' => [ + 'flarum/core' => '^0.1.0-beta.15', + 'flarum/tags' => '^0.1.0-beta.15', + 'flarum/dummy-incompatible-extension' => '^1.0.0' + ], + 'minimum-stability' => 'beta', + ]); + + $response = $this->send( + $this->request('POST', '/api/package-manager/major-update', [ + 'authenticatedAs' => 1, + ]) + ); + + $this->assertEquals(409, $response->getStatusCode()); + $this->assertEquals('no_new_major_version', $this->errorDetails($response)['code']); + } + + /** + * @test + */ + public function can_update_when_major_update_available() + { + $this->makeDummyExtensionCompatibleWith("flarum/dummy-compatible-extension", "^0.1.0-beta.15 | ^1.0.0"); + $this->setComposerConfig([ + 'require' => [ + 'flarum/core' => '^0.1.0-beta.15', + 'flarum/tags' => '^0.1.0-beta.15', + 'flarum/dummy-compatible-extension' => '^1.0.0' + ], + 'minimum-stability' => 'beta', + ]); + + $lastUpdateCheck = $this->send( + $this->request('POST', '/api/package-manager/check-for-updates', [ + 'authenticatedAs' => 1, + ]) + ); + + $this->forgetComposerApp(); + + $response = $this->send( + $this->request('POST', '/api/package-manager/major-update', [ + 'authenticatedAs' => 1, + ]) + ); + + $newMinorCoreVersion = array_filter( + json_decode((string) $lastUpdateCheck->getBody(), true)['updates']['installed'], + function ($package) { + return $package['name'] === 'flarum/core'; + } + )[0]['latest-major']; + + $this->assertEquals(204, $response->getStatusCode()); + $this->assertPackageVersion("flarum/core", str_replace('v', '^', $newMinorCoreVersion)); + $this->assertPackageVersion("flarum/tags", "*"); + $this->assertPackageVersion("flarum/dummy-compatible-extension", "*"); + } + + /** + * @test + */ + public function cannot_update_with_incompatible_extensions() + { + $this->makeDummyExtensionCompatibleWith("flarum/dummy-incompatible-extension-a", ">=0.1.0-beta.16 <0.1.0-beta.17"); + $this->makeDummyExtensionCompatibleWith("flarum/dummy-incompatible-extension-b", ">=0.1.0-beta.16 <=0.1.0-beta.17"); + $this->makeDummyExtensionCompatibleWith("flarum/dummy-incompatible-extension-c", "0.1.0-beta.16"); + $this->setComposerConfig([ + 'require' => [ + 'flarum/core' => '^0.1.0-beta.16', + 'flarum/tags' => '^0.1.0-beta.16', + 'flarum/dummy-incompatible-extension-a' => '^1.0.0', + 'flarum/dummy-incompatible-extension-b' => '^1.0.0', + 'flarum/dummy-incompatible-extension-c' => '^1.0.0', + ], + 'minimum-stability' => 'beta', + ]); + + $this->send( + $this->request('POST', '/api/package-manager/check-for-updates', [ + 'authenticatedAs' => 1, + ]) + ); + + $response = $this->send( + $this->request('POST', '/api/package-manager/major-update', [ + 'authenticatedAs' => 1, + ]) + ); + + $this->assertEquals(409, $response->getStatusCode()); + $this->assertEquals('extensions_incompatible_with_new_major', $this->errorDetails($response)['guessed_cause']); + $this->assertEquals([ + 'flarum/dummy-incompatible-extension-a', + 'flarum/dummy-incompatible-extension-b', + 'flarum/dummy-incompatible-extension-c' + ], $this->errorDetails($response)['incompatible_extensions']); + } +} diff --git a/extensions/package-manager/tests/integration/api/MinorUpdateTest.php b/extensions/package-manager/tests/integration/api/MinorUpdateTest.php new file mode 100644 index 000000000..cb7b77a01 --- /dev/null +++ b/extensions/package-manager/tests/integration/api/MinorUpdateTest.php @@ -0,0 +1,95 @@ +makeDummyExtensionCompatibleWith("flarum/dummy-compatible-extension", "^1.0.0"); + $this->setComposerConfig([ + 'require' => [ + // The only reason we don't set this to `^1.0.0` and let it update to latest minor, + // is because migrations that run DDL queries might be introduced in future versions, + // therefore breaking the test transaction. + 'flarum/core' => '>=1.0.0 <= 1.1.0', + // We leave tags fixed to a version, + // the update handler must be able to set it to `*`. + 'flarum/tags' => '1.0.3', + 'flarum/lang-english' => '*', + 'flarum/dummy-compatible-extension' => '^1.0.0' + ] + ]); + + $response = $this->send( + $this->request('POST', '/api/package-manager/minor-update', [ + 'authenticatedAs' => 1, + ]) + ); + + $this->assertEquals(204, $response->getStatusCode()); + $this->assertPackageVersion('flarum/tags', '*'); + $this->assertPackageVersion('flarum/dummy-compatible-extension', '*'); + } + + /** + * @test + */ + public function can_update_with_latest_ext_incompatible_with_latest_core() + { + $this->makeDummyExtensionCompatibleWith("flarum/dummy-extension", "1.0.0"); + $this->setComposerConfig([ + 'require' => [ + 'flarum/core' => '>=1.0.0 <=1.1.0', + 'flarum/tags' => '1.0.3', + 'flarum/lang-english' => '*', + 'flarum/dummy-extension' => '^1.0.0' + ] + ]); + + $this->send( + $this->request('POST', '/api/package-manager/check-for-updates', [ + 'authenticatedAs' => 1, + ]) + ); + + $this->forgetComposerApp(); + + $response = $this->send( + $this->request('POST', '/api/package-manager/minor-update', [ + 'authenticatedAs' => 1, + ]) + ); + + /** @var LastUpdateRun $lastUpdateRun */ + $lastUpdateRun = $this->app()->getContainer()->make(LastUpdateRun::class); + + $this->assertEquals(204, $response->getStatusCode()); + $this->assertPackageVersion("flarum/tags", "*"); + $this->assertPackageVersion("flarum/dummy-extension", "*"); + $this->assertEquals([ + 'flarum/core', + 'flarum/lang-english', + 'flarum/tags' + ], $lastUpdateRun->for(FlarumUpdated::MINOR)->get()['limitedPackages']); + } +} diff --git a/extensions/package-manager/tests/integration/api/extensions/RequireExtensionTest.php b/extensions/package-manager/tests/integration/api/extensions/RequireExtensionTest.php index 001e8a6eb..2da97b62e 100644 --- a/extensions/package-manager/tests/integration/api/extensions/RequireExtensionTest.php +++ b/extensions/package-manager/tests/integration/api/extensions/RequireExtensionTest.php @@ -100,7 +100,7 @@ class RequireExtensionTest extends TestCase ); $this->assertEquals(409, $response->getStatusCode()); - $this->assertEquals('extension_incompatible_with_instance', $this->guessedCause($response)); + $this->assertEquals('extension_incompatible_with_instance', $this->errorDetails($response)['guessed_cause']); } /** @@ -120,6 +120,6 @@ class RequireExtensionTest extends TestCase ); $this->assertEquals(409, $response->getStatusCode()); - $this->assertEquals('extension_incompatible_with_instance', $this->guessedCause($response)); + $this->assertEquals('extension_incompatible_with_instance', $this->errorDetails($response)['guessed_cause']); } }