Fix plugin update crashing after the rows are refreshed

Plugin::refreshRows() drops the Sushi connection, and only a newly
constructed model boots it again. updatePlugin() called refresh() on the
instance it was given, which queries through the dropped connection and
fails with "Call to a member function query() on null". Load the plugin
again with findOrFail(), like the plugin jobs already do.
This commit is contained in:
dualfroz committed 2026-09-24 18:46:25 +02:00
1 parent 4494b7409f
commit 7e79170bf9
2 files changed
+59 -1

No files matched your search

+1 -1
View File
@@ -321,7 +321,7 @@ class PluginService
$this->downloadPluginFromUrl($downloadUrl, $plugin->id); $this->downloadPluginFromUrl($downloadUrl, $plugin->id);
Plugin::refreshRows(); Plugin::refreshRows();
$plugin = $plugin->refresh(); $plugin = Plugin::findOrFail($plugin->id);
$this->installPlugin($plugin, false); $this->installPlugin($plugin, false);
@@ -2,6 +2,8 @@
namespace App\Tests\Integration\Services; namespace App\Tests\Integration\Services;
use App\Enums\PluginStatus;
use App\Models\Plugin;
use App\Services\Helpers\PluginService; use App\Services\Helpers\PluginService;
use App\Tests\Integration\IntegrationTestCase; use App\Tests\Integration\IntegrationTestCase;
use Exception; use Exception;
@@ -9,6 +11,7 @@ use Illuminate\Filesystem\Filesystem;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Process;
use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DataProvider;
use Spatie\TemporaryDirectory\TemporaryDirectory; use Spatie\TemporaryDirectory\TemporaryDirectory;
use ZipArchive; use ZipArchive;
@@ -43,6 +46,9 @@ class PluginServiceTest extends IntegrationTestCase
File::deleteDirectory($leftover); 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. // TemporaryDirectory::make() does not clean up on its own; remove the upload fixtures.
foreach ($this->uploadDirs as $dir) { foreach ($this->uploadDirs as $dir) {
$dir->delete(); $dir->delete();
@@ -388,6 +394,44 @@ class PluginServiceTest extends IntegrationTestCase
$this->assertDirectoryDoesNotExist(plugin_path('.test-clean-plugin.bak')); $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');
$this->service->updatePlugin($plugin);
$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 private function installedVersion(string $id): string
{ {
return File::json(plugin_path($id, 'plugin.json'))['version']; return File::json(plugin_path($id, 'plugin.json'))['version'];
@@ -398,6 +442,20 @@ class PluginServiceTest extends IntegrationTestCase
return json_encode(['id' => $id, 'version' => $version]); 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. * Build a zip on disk from the given [path => contents] map and wrap it in an UploadedFile.
* *