diff --git a/app/Models/Team.php b/app/Models/Team.php index d5d564444..0fbcfe0c6 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -233,6 +233,9 @@ public function subscriptionEnded() 'is_reachable' => false, ]); ServerReachabilityChanged::dispatch($server); + $server->unreachable_count = 3; + $server->unreachable_notification_sent = true; + $server->save(); } } @@ -344,5 +347,4 @@ public function webhookNotificationSettings() { return $this->hasOne(WebhookNotificationSettings::class); } - } diff --git a/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php b/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php index c67987e67..77fcf96a1 100644 --- a/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php +++ b/database/migrations/2025_12_24_133707_add_predefined_server_variables_to_existing_servers.php @@ -11,7 +11,7 @@ */ public function up(): void { - Server::query()->chunk(100, function ($servers) { + Server::query()->whereHas('team')->chunk(100, function ($servers) { foreach ($servers as $server) { $existingKeys = SharedEnvironmentVariable::where('type', 'server') ->where('server_id', $server->id) diff --git a/database/migrations/2026_03_23_101720_add_uuid_to_local_persistent_volumes_table.php b/database/migrations/2026_03_23_101720_add_uuid_to_local_persistent_volumes_table.php index 6b4fb690d..ab279c592 100644 --- a/database/migrations/2026_03_23_101720_add_uuid_to_local_persistent_volumes_table.php +++ b/database/migrations/2026_03_23_101720_add_uuid_to_local_persistent_volumes_table.php @@ -10,14 +10,15 @@ { public function up(): void { - Schema::table('local_persistent_volumes', function (Blueprint $table) { - $table->string('uuid')->nullable()->after('id'); - }); + if (! Schema::hasColumn('local_persistent_volumes', 'uuid')) { + Schema::table('local_persistent_volumes', function (Blueprint $table) { + $table->string('uuid')->nullable()->after('id'); + }); + } DB::table('local_persistent_volumes') ->whereNull('uuid') - ->orderBy('id') - ->chunk(1000, function ($volumes) { + ->chunkById(1000, function ($volumes) { foreach ($volumes as $volume) { DB::table('local_persistent_volumes') ->where('id', $volume->id) diff --git a/tests/Feature/CleanupUnreachableServersTest.php b/tests/Feature/CleanupUnreachableServersTest.php index edfd0511c..c06944969 100644 --- a/tests/Feature/CleanupUnreachableServersTest.php +++ b/tests/Feature/CleanupUnreachableServersTest.php @@ -30,7 +30,7 @@ 'updated_at' => now()->subDays(8), ]); - $originalIp = $server->ip; + $originalIp = (string) $server->ip; $this->artisan('cleanup:unreachable-servers')->assertSuccessful(); @@ -47,7 +47,7 @@ 'updated_at' => now()->subDays(3), ]); - $originalIp = $server->ip; + $originalIp = (string) $server->ip; $this->artisan('cleanup:unreachable-servers')->assertSuccessful(); @@ -64,7 +64,7 @@ 'updated_at' => now()->subDays(8), ]); - $originalIp = $server->ip; + $originalIp = (string) $server->ip; $this->artisan('cleanup:unreachable-servers')->assertSuccessful(); diff --git a/tests/Feature/CleanupUnsubscribedServersTest.php b/tests/Feature/CleanupUnsubscribedServersTest.php new file mode 100644 index 000000000..ee3fdb02d --- /dev/null +++ b/tests/Feature/CleanupUnsubscribedServersTest.php @@ -0,0 +1,78 @@ +create(); + Subscription::create([ + 'team_id' => $team->id, + 'stripe_invoice_paid' => true, + ]); + $server = Server::factory()->create([ + 'team_id' => $team->id, + 'unreachable_count' => 0, + 'unreachable_notification_sent' => false, + ]); + + $team->subscriptionEnded(); + + $server->refresh(); + expect($server->unreachable_count)->toBe(3); + expect($server->unreachable_notification_sent)->toBeTrue(); +}); + +it('cleans up unsubscribed server IP after 7 days via cleanup command', function () { + $team = Team::factory()->create(); + $server = Server::factory()->create([ + 'team_id' => $team->id, + 'unreachable_count' => 3, + 'unreachable_notification_sent' => true, + 'updated_at' => now()->subDays(8), + ]); + + $this->artisan('cleanup:unreachable-servers')->assertSuccessful(); + + $server->refresh(); + expect($server->ip)->toBe('1.2.3.4'); +}); + +it('does not clean up unsubscribed server IP within 7 day grace period', function () { + $team = Team::factory()->create(); + $server = Server::factory()->create([ + 'team_id' => $team->id, + 'unreachable_count' => 3, + 'unreachable_notification_sent' => true, + 'updated_at' => now()->subDays(3), + ]); + + $originalIp = (string) $server->ip; + + $this->artisan('cleanup:unreachable-servers')->assertSuccessful(); + + $server->refresh(); + expect((string) $server->ip)->toBe($originalIp); +}); + +it('does not affect servers with active subscriptions', function () { + $team = Team::factory()->create(); + Subscription::create([ + 'team_id' => $team->id, + 'stripe_invoice_paid' => true, + ]); + $server = Server::factory()->create([ + 'team_id' => $team->id, + 'unreachable_count' => 0, + 'unreachable_notification_sent' => false, + ]); + + $originalCount = $server->unreachable_count; + $originalNotification = $server->unreachable_notification_sent; + + expect($originalCount)->toBe(0); + expect($originalNotification)->toBeFalse(); +});