diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php index 3abd55e9c..6da4dd4c6 100644 --- a/app/Models/ServerSetting.php +++ b/app/Models/ServerSetting.php @@ -79,11 +79,11 @@ protected static function booted() }); static::updated(function ($settings) { if ( - $settings->isDirty('sentinel_token') || - $settings->isDirty('sentinel_custom_url') || - $settings->isDirty('sentinel_metrics_refresh_rate_seconds') || - $settings->isDirty('sentinel_metrics_history_days') || - $settings->isDirty('sentinel_push_interval_seconds') + $settings->wasChanged('sentinel_token') || + $settings->wasChanged('sentinel_custom_url') || + $settings->wasChanged('sentinel_metrics_refresh_rate_seconds') || + $settings->wasChanged('sentinel_metrics_history_days') || + $settings->wasChanged('sentinel_push_interval_seconds') ) { $settings->server->restartSentinel(); } diff --git a/app/Traits/DeletesUserSessions.php b/app/Traits/DeletesUserSessions.php index a4d3a7cfd..e9ec0d946 100644 --- a/app/Traits/DeletesUserSessions.php +++ b/app/Traits/DeletesUserSessions.php @@ -26,7 +26,7 @@ protected static function bootDeletesUserSessions() { static::updated(function ($user) { // Check if password was changed - if ($user->isDirty('password')) { + if ($user->wasChanged('password')) { $user->deleteAllSessions(); } }); diff --git a/tests/Feature/DeletesUserSessionsTest.php b/tests/Feature/DeletesUserSessionsTest.php new file mode 100644 index 000000000..a2bde2eb2 --- /dev/null +++ b/tests/Feature/DeletesUserSessionsTest.php @@ -0,0 +1,136 @@ +create([ + 'password' => Hash::make('old-password'), + ]); + + // Create fake session records for the user + DB::table('sessions')->insert([ + [ + 'id' => 'session-1', + 'user_id' => $user->id, + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Test Browser', + 'payload' => base64_encode('test-payload-1'), + 'last_activity' => now()->timestamp, + ], + [ + 'id' => 'session-2', + 'user_id' => $user->id, + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Test Browser', + 'payload' => base64_encode('test-payload-2'), + 'last_activity' => now()->timestamp, + ], + ]); + + // Verify sessions exist + expect(DB::table('sessions')->where('user_id', $user->id)->count())->toBe(2); + + // Change password + $user->password = Hash::make('new-password'); + $user->save(); + + // Verify all sessions for this user were deleted + expect(DB::table('sessions')->where('user_id', $user->id)->count())->toBe(0); +}); + +it('does not invalidate sessions when password is unchanged', function () { + // Create a user + $user = User::factory()->create([ + 'password' => Hash::make('password'), + ]); + + // Create fake session records for the user + DB::table('sessions')->insert([ + [ + 'id' => 'session-1', + 'user_id' => $user->id, + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Test Browser', + 'payload' => base64_encode('test-payload'), + 'last_activity' => now()->timestamp, + ], + ]); + + // Update other user fields (not password) + $user->name = 'New Name'; + $user->save(); + + // Verify session still exists + expect(DB::table('sessions')->where('user_id', $user->id)->count())->toBe(1); +}); + +it('does not invalidate sessions when password is set to same value', function () { + // Create a user with a specific password + $hashedPassword = Hash::make('password'); + $user = User::factory()->create([ + 'password' => $hashedPassword, + ]); + + // Create fake session records for the user + DB::table('sessions')->insert([ + [ + 'id' => 'session-1', + 'user_id' => $user->id, + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Test Browser', + 'payload' => base64_encode('test-payload'), + 'last_activity' => now()->timestamp, + ], + ]); + + // Set password to the same value + $user->password = $hashedPassword; + $user->save(); + + // Verify session still exists (password didn't actually change) + expect(DB::table('sessions')->where('user_id', $user->id)->count())->toBe(1); +}); + +it('invalidates sessions only for the user whose password changed', function () { + // Create two users + $user1 = User::factory()->create([ + 'password' => Hash::make('password1'), + ]); + $user2 = User::factory()->create([ + 'password' => Hash::make('password2'), + ]); + + // Create sessions for both users + DB::table('sessions')->insert([ + [ + 'id' => 'session-user1', + 'user_id' => $user1->id, + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Test Browser', + 'payload' => base64_encode('test-payload-1'), + 'last_activity' => now()->timestamp, + ], + [ + 'id' => 'session-user2', + 'user_id' => $user2->id, + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Test Browser', + 'payload' => base64_encode('test-payload-2'), + 'last_activity' => now()->timestamp, + ], + ]); + + // Change password for user1 only + $user1->password = Hash::make('new-password1'); + $user1->save(); + + // Verify user1's sessions were deleted but user2's remain + expect(DB::table('sessions')->where('user_id', $user1->id)->count())->toBe(0); + expect(DB::table('sessions')->where('user_id', $user2->id)->count())->toBe(1); +}); diff --git a/tests/Feature/InstanceSettingsHelperVersionTest.php b/tests/Feature/InstanceSettingsHelperVersionTest.php new file mode 100644 index 000000000..e731fa8b4 --- /dev/null +++ b/tests/Feature/InstanceSettingsHelperVersionTest.php @@ -0,0 +1,81 @@ +create(); + $team = $user->teams()->first(); + Server::factory()->count(3)->create(['team_id' => $team->id]); + + $settings = InstanceSettings::firstOrCreate([], ['helper_version' => 'v1.0.0']); + + // Change helper_version + $settings->helper_version = 'v1.2.3'; + $settings->save(); + + // Verify PullHelperImageJob was dispatched for all servers + Queue::assertPushed(PullHelperImageJob::class, 3); +}); + +it('does not dispatch PullHelperImageJob when helper_version is unchanged', function () { + Queue::fake(); + + // Create user and servers + $user = User::factory()->create(); + $team = $user->teams()->first(); + Server::factory()->count(3)->create(['team_id' => $team->id]); + + $settings = InstanceSettings::firstOrCreate([], ['helper_version' => 'v1.0.0']); + $currentVersion = $settings->helper_version; + + // Set to same value + $settings->helper_version = $currentVersion; + $settings->save(); + + // Verify no jobs were dispatched + Queue::assertNotPushed(PullHelperImageJob::class); +}); + +it('does not dispatch PullHelperImageJob when other fields change', function () { + Queue::fake(); + + // Create user and servers + $user = User::factory()->create(); + $team = $user->teams()->first(); + Server::factory()->count(3)->create(['team_id' => $team->id]); + + $settings = InstanceSettings::firstOrCreate([], ['helper_version' => 'v1.0.0']); + + // Change different field + $settings->is_auto_update_enabled = ! $settings->is_auto_update_enabled; + $settings->save(); + + // Verify no jobs were dispatched + Queue::assertNotPushed(PullHelperImageJob::class); +}); + +it('detects helper_version changes with wasChanged', function () { + $changeDetected = false; + + InstanceSettings::updated(function ($settings) use (&$changeDetected) { + if ($settings->wasChanged('helper_version')) { + $changeDetected = true; + } + }); + + $settings = InstanceSettings::firstOrCreate([], ['helper_version' => 'v1.0.0']); + $settings->helper_version = 'v2.0.0'; + $settings->save(); + + expect($changeDetected)->toBeTrue(); +}); diff --git a/tests/Feature/ServerSettingSentinelRestartTest.php b/tests/Feature/ServerSettingSentinelRestartTest.php new file mode 100644 index 000000000..7a1c333ca --- /dev/null +++ b/tests/Feature/ServerSettingSentinelRestartTest.php @@ -0,0 +1,139 @@ +create(); + $this->team = $user->teams()->first(); + + // Create server with the team + $this->server = Server::factory()->create([ + 'team_id' => $this->team->id, + ]); +}); + +it('detects sentinel_token changes with wasChanged', function () { + $changeDetected = false; + + // Register a test listener that will be called after the model's booted listeners + ServerSetting::updated(function ($settings) use (&$changeDetected) { + if ($settings->wasChanged('sentinel_token')) { + $changeDetected = true; + } + }); + + $settings = $this->server->settings; + $settings->sentinel_token = 'new-token-value'; + $settings->save(); + + expect($changeDetected)->toBeTrue(); +}); + +it('detects sentinel_custom_url changes with wasChanged', function () { + $changeDetected = false; + + ServerSetting::updated(function ($settings) use (&$changeDetected) { + if ($settings->wasChanged('sentinel_custom_url')) { + $changeDetected = true; + } + }); + + $settings = $this->server->settings; + $settings->sentinel_custom_url = 'https://new-url.com'; + $settings->save(); + + expect($changeDetected)->toBeTrue(); +}); + +it('detects sentinel_metrics_refresh_rate_seconds changes with wasChanged', function () { + $changeDetected = false; + + ServerSetting::updated(function ($settings) use (&$changeDetected) { + if ($settings->wasChanged('sentinel_metrics_refresh_rate_seconds')) { + $changeDetected = true; + } + }); + + $settings = $this->server->settings; + $settings->sentinel_metrics_refresh_rate_seconds = 60; + $settings->save(); + + expect($changeDetected)->toBeTrue(); +}); + +it('detects sentinel_metrics_history_days changes with wasChanged', function () { + $changeDetected = false; + + ServerSetting::updated(function ($settings) use (&$changeDetected) { + if ($settings->wasChanged('sentinel_metrics_history_days')) { + $changeDetected = true; + } + }); + + $settings = $this->server->settings; + $settings->sentinel_metrics_history_days = 14; + $settings->save(); + + expect($changeDetected)->toBeTrue(); +}); + +it('detects sentinel_push_interval_seconds changes with wasChanged', function () { + $changeDetected = false; + + ServerSetting::updated(function ($settings) use (&$changeDetected) { + if ($settings->wasChanged('sentinel_push_interval_seconds')) { + $changeDetected = true; + } + }); + + $settings = $this->server->settings; + $settings->sentinel_push_interval_seconds = 30; + $settings->save(); + + expect($changeDetected)->toBeTrue(); +}); + +it('does not detect changes when unrelated field is changed', function () { + $changeDetected = false; + + ServerSetting::updated(function ($settings) use (&$changeDetected) { + if ( + $settings->wasChanged('sentinel_token') || + $settings->wasChanged('sentinel_custom_url') || + $settings->wasChanged('sentinel_metrics_refresh_rate_seconds') || + $settings->wasChanged('sentinel_metrics_history_days') || + $settings->wasChanged('sentinel_push_interval_seconds') + ) { + $changeDetected = true; + } + }); + + $settings = $this->server->settings; + $settings->is_reachable = ! $settings->is_reachable; + $settings->save(); + + expect($changeDetected)->toBeFalse(); +}); + +it('does not detect changes when sentinel field is set to same value', function () { + $changeDetected = false; + + ServerSetting::updated(function ($settings) use (&$changeDetected) { + if ($settings->wasChanged('sentinel_token')) { + $changeDetected = true; + } + }); + + $settings = $this->server->settings; + $currentToken = $settings->sentinel_token; + $settings->sentinel_token = $currentToken; + $settings->save(); + + expect($changeDetected)->toBeFalse(); +}); diff --git a/tests/Feature/ServerSettingWasChangedTest.php b/tests/Feature/ServerSettingWasChangedTest.php new file mode 100644 index 000000000..ea7987a4b --- /dev/null +++ b/tests/Feature/ServerSettingWasChangedTest.php @@ -0,0 +1,64 @@ +create(); + $team = $user->teams()->first(); + $server = Server::factory()->create(['team_id' => $team->id]); + + $settings = $server->settings; + + // Change a field + $settings->is_reachable = ! $settings->is_reachable; + $settings->save(); + + // In the updated hook, wasChanged should return true + expect($settings->wasChanged('is_reachable'))->toBeTrue(); +}); + +it('isDirty returns false after saving', function () { + // Create user and server + $user = User::factory()->create(); + $team = $user->teams()->first(); + $server = Server::factory()->create(['team_id' => $team->id]); + + $settings = $server->settings; + + // Change a field + $settings->is_reachable = ! $settings->is_reachable; + $settings->save(); + + // After save, isDirty returns false (this is the bug) + expect($settings->isDirty('is_reachable'))->toBeFalse(); +}); + +it('can detect sentinel_token changes with wasChanged', function () { + // Create user and server + $user = User::factory()->create(); + $team = $user->teams()->first(); + $server = Server::factory()->create(['team_id' => $team->id]); + + $settings = $server->settings; + $originalToken = $settings->sentinel_token; + + // Create a tracking variable using model events + $tokenWasChanged = false; + ServerSetting::updated(function ($model) use (&$tokenWasChanged) { + if ($model->wasChanged('sentinel_token')) { + $tokenWasChanged = true; + } + }); + + // Change the token + $settings->sentinel_token = 'new-token-value-for-testing'; + $settings->save(); + + expect($tokenWasChanged)->toBeTrue(); +});