diff --git a/app/Http/Controllers/Api/Application/Plugins/PluginController.php b/app/Http/Controllers/Api/Application/Plugins/PluginController.php index 22828e227f..7e74e44675 100644 --- a/app/Http/Controllers/Api/Application/Plugins/PluginController.php +++ b/app/Http/Controllers/Api/Application/Plugins/PluginController.php @@ -122,7 +122,7 @@ public function update(WritePluginRequest $request, Plugin $plugin): array { throw_unless($plugin->isUpdateAvailable(), new PanelException("Plugin doesn't need updating")); - $this->pluginService->updatePlugin($plugin); + $plugin = $this->pluginService->updatePlugin($plugin); return $this->response->item($plugin) ->transformWith(PluginData::class) diff --git a/app/Services/Helpers/PluginService.php b/app/Services/Helpers/PluginService.php index f794155059..a078209ceb 100644 --- a/app/Services/Helpers/PluginService.php +++ b/app/Services/Helpers/PluginService.php @@ -313,7 +313,7 @@ public function installPlugin(Plugin $plugin, bool $enable = true): void } /** @throws Exception */ - public function updatePlugin(Plugin $plugin): void + public function updatePlugin(Plugin $plugin): Plugin { $downloadUrl = $plugin->getDownloadUrlForUpdate(); throw_unless($downloadUrl, new Exception('No download url found.')); @@ -321,11 +321,13 @@ public function updatePlugin(Plugin $plugin): void $this->downloadPluginFromUrl($downloadUrl, $plugin->id); Plugin::refreshRows(); - $plugin = $plugin->refresh(); + $plugin = Plugin::findOrFail($plugin->id); $this->installPlugin($plugin, false); cache()->forget("plugins.$plugin->id.update"); + + return $plugin; } /** @throws Exception */ diff --git a/tests/Integration/Services/PluginServiceTest.php b/tests/Integration/Services/PluginServiceTest.php index 3d0c38f27c..6ab1633bc2 100644 --- a/tests/Integration/Services/PluginServiceTest.php +++ b/tests/Integration/Services/PluginServiceTest.php @@ -2,6 +2,8 @@ namespace App\Tests\Integration\Services; +use App\Enums\PluginStatus; +use App\Models\Plugin; use App\Services\Helpers\PluginService; use App\Tests\Integration\IntegrationTestCase; use Exception; @@ -9,6 +11,7 @@ use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Process; use PHPUnit\Framework\Attributes\DataProvider; use Spatie\TemporaryDirectory\TemporaryDirectory; use ZipArchive; @@ -43,6 +46,9 @@ protected function tearDown(): void File::deleteDirectory($leftover); } + // Don't leak the in-memory Sushi rows of the plugins removed above into later tests. + Plugin::refreshRows(); + // TemporaryDirectory::make() does not clean up on its own; remove the upload fixtures. foreach ($this->uploadDirs as $dir) { $dir->delete(); @@ -388,6 +394,45 @@ public function test_import_keeps_the_existing_plugin_when_the_move_fails(): voi $this->assertDirectoryDoesNotExist(plugin_path('.test-clean-plugin.bak')); } + public function test_update_downloads_and_reinstalls_the_new_version(): void + { + $this->importedPlugins[] = 'test-update-plugin'; + + // Updates are resolved against the panel version, which is null on canary. + config()->set('app.version', '1.0.0'); + Process::fake(); + + $updateUrl = 'https://example.test/test-update-plugin/update.json'; + $downloadUrl = 'https://example.test/test-update-plugin/2.0.0.zip'; + + $this->service->downloadPluginFromFile($this->makeUpload('test-update-plugin.zip', [ + 'test-update-plugin/plugin.json' => $this->updatableManifest('test-update-plugin', '1.0.0', $updateUrl), + ])); + + $update = file_get_contents($this->makeUpload('2.0.0.zip', [ + 'test-update-plugin/plugin.json' => $this->updatableManifest('test-update-plugin', '2.0.0', $updateUrl), + ])->getPathname()); + + Http::fake([ + $updateUrl => Http::response(['*' => ['version' => '2.0.0', 'download_url' => $downloadUrl]]), + $downloadUrl => Http::response($update), + ]); + + Plugin::refreshRows(); + $plugin = Plugin::findOrFail('test-update-plugin'); + + $updated = $this->service->updatePlugin($plugin); + + $this->assertSame('2.0.0', $updated->version); + $this->assertSame('2.0.0', $this->installedVersion('test-update-plugin')); + + // installPlugin() ran against the reloaded row and recorded the plugin as installed but disabled. + $this->assertSame(PluginStatus::Disabled->value, File::json(plugin_path('test-update-plugin', 'plugin.json'))['meta']['status']); + + Plugin::refreshRows(); + $this->assertSame('2.0.0', Plugin::findOrFail('test-update-plugin')->version); + } + private function installedVersion(string $id): string { return File::json(plugin_path($id, 'plugin.json'))['version']; @@ -398,6 +443,20 @@ private function manifest(string $id, string $version = '1.0.0'): string return json_encode(['id' => $id, 'version' => $version]); } + private function updatableManifest(string $id, string $version, string $updateUrl): string + { + return json_encode([ + 'id' => $id, + 'name' => 'Test Update Plugin', + 'author' => 'Pelican', + 'version' => $version, + 'category' => 'plugin', + 'update_url' => $updateUrl, + 'namespace' => 'TestUpdatePlugin', + 'class' => 'TestUpdatePlugin', + ]); + } + /** * Build a zip on disk from the given [path => contents] map and wrap it in an UploadedFile. *