diff --git a/app/Actions/Service/RestartService.php b/app/Actions/Service/RestartService.php index d38ef54d6..6acd3b0a4 100644 --- a/app/Actions/Service/RestartService.php +++ b/app/Actions/Service/RestartService.php @@ -13,8 +13,10 @@ class RestartService public function handle(Service $service, bool $pullLatestImages) { - StopService::run($service); - - return StartService::run($service, $pullLatestImages); + return StartService::run( + service: $service, + pullLatestImages: $pullLatestImages, + stopBeforeStart: true, + ); } } diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index d3d99ff78..89817506d 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -19,7 +19,7 @@ class StartService public function handle(Service $service, bool $pullLatestImages = false, bool $stopBeforeStart = false) { $service->parse(); - if ($stopBeforeStart) { + if ($this->shouldStopBeforeStarting($pullLatestImages, $stopBeforeStart)) { StopService::run(service: $service, dockerCleanup: false); } $service->saveComposeConfigs(); @@ -53,4 +53,9 @@ class StartService return remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged'); } + + private function shouldStopBeforeStarting(bool $pullLatestImages, bool $stopBeforeStart): bool + { + return $stopBeforeStart && ! $pullLatestImages; + } } diff --git a/package-lock.json b/package-lock.json index bcacecc8b..9d495c412 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1261,8 +1261,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/clsx": { "version": "2.1.1", @@ -1752,7 +1751,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -1946,8 +1944,7 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -1992,7 +1989,6 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/resources/views/livewire/settings-dropdown.blade.php b/resources/views/livewire/settings-dropdown.blade.php index 0c0c000d5..d40d6778e 100644 --- a/resources/views/livewire/settings-dropdown.blade.php +++ b/resources/views/livewire/settings-dropdown.blade.php @@ -198,7 +198,7 @@ @if ($showWhatsNewModal) -
diff --git a/tests/Feature/SettingsDropdownTest.php b/tests/Feature/SettingsDropdownTest.php new file mode 100644 index 000000000..db6c70111 --- /dev/null +++ b/tests/Feature/SettingsDropdownTest.php @@ -0,0 +1,44 @@ + 'test@example.com']); + $user->id = 1; + + Auth::setUser($user); + + app()->instance(ChangelogService::class, new class extends ChangelogService + { + public function getEntriesForUser(User $user): Collection + { + return collect([ + (object) [ + 'tag_name' => 'v1.0.0', + 'title' => 'Test Release', + 'content' => 'Release notes', + 'content_html' => '

Release notes

', + 'published_at' => Carbon::parse('2026-05-01'), + 'is_read' => false, + ], + ]); + } + + public function getUnreadCountForUser(User $user): int + { + return 1; + } + }); + + Livewire::test(SettingsDropdown::class, ['trigger' => 'changelog-sidebar']) + ->call('openWhatsNewModal') + ->assertSee('Changelog') + ->assertSee('z-[60]', false) + ->assertSee('closeWhatsNewModal', false); +}); diff --git a/tests/Unit/Service/StartServicePullLatestRestartTest.php b/tests/Unit/Service/StartServicePullLatestRestartTest.php new file mode 100644 index 000000000..ce138ba65 --- /dev/null +++ b/tests/Unit/Service/StartServicePullLatestRestartTest.php @@ -0,0 +1,36 @@ +invoke(new StartService, pullLatestImages: true, stopBeforeStart: true))->toBeFalse(); +}); + +it('still stops a service before a regular restart', function () { + $method = new ReflectionMethod(StartService::class, 'shouldStopBeforeStarting'); + + expect($method->invoke(new StartService, pullLatestImages: false, stopBeforeStart: true))->toBeTrue() + ->and($method->invoke(new StartService, pullLatestImages: false, stopBeforeStart: false))->toBeFalse(); +}); + +it('routes service restart actions through start service with deferred stop semantics', function () { + $service = Mockery::mock(Service::class); + + $stopService = Mockery::mock(StopService::class); + $stopService->shouldNotReceive('handle'); + app()->instance(StopService::class, $stopService); + + $startService = Mockery::mock(StartService::class); + $startService->shouldReceive('handle') + ->once() + ->with($service, true, true) + ->andReturn('restart queued'); + app()->instance(StartService::class, $startService); + + expect(RestartService::run($service, true))->toBe('restart queued'); +});