kick forever
CI/CD / test_and_check_app (push) Successful in 2m19s
CI/CD / Publish immutable image (push) Successful in 1m16s

This commit is contained in:
2026-09-24 14:38:18 +02:00
parent b190ff719e
commit 4dfe79ce5c
22 changed files with 271 additions and 113 deletions
+94
View File
@@ -0,0 +1,94 @@
<?php
use App\Enums\RoomStateChange;
use App\Events\RoomStateChanged;
use App\Jobs\RemoveAbsentPlayers;
use App\Models\Room;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema;
test('the player table no longer stores an inactive status', function () {
expect(Schema::hasColumn('players', 'inactive_at'))->toBeFalse();
});
test('the migration removes old inactive and absent players while keeping present players', function () {
Schema::table('players', function (Blueprint $table): void {
$table->timestamp('inactive_at')->nullable();
});
$room = Room::create([
'title' => 'Planning',
'deck_config' => ['name' => 'Fibonacci', 'cards' => []],
]);
$previouslyInactive = $room->players()->create([
'name' => 'Inactive',
'last_seen_at' => now(),
]);
$absent = $room->players()->create([
'name' => 'Nico',
'last_seen_at' => now()->subHours(2),
]);
$present = $room->players()->create([
'name' => 'Anna',
'last_seen_at' => now(),
]);
DB::table('players')->where('id', $previouslyInactive->id)->update(['inactive_at' => now()]);
$migration = require database_path('migrations/2026_09_24_123048_remove_previously_inactive_players.php');
$migration->up();
$this->assertModelMissing($previouslyInactive);
$this->assertModelMissing($absent);
$this->assertModelExists($present);
expect(Schema::hasColumn('players', 'inactive_at'))->toBeFalse();
});
test('the timeout removes absent players and their votes but keeps recent players', function () {
$room = Room::create([
'title' => 'Planning',
'current_round' => 1,
'deck_config' => ['name' => 'Fibonacci', 'cards' => []],
]);
$absentPlayer = $room->players()->create([
'name' => 'Nico',
'last_seen_at' => now()->subHour(),
]);
$recentPlayer = $room->players()->create([
'name' => 'Anna',
'last_seen_at' => now()->subHour()->addSecond(),
]);
$vote = $absentPlayer->votes()->create([
'room_id' => $room->id,
'round' => $room->current_round,
'value' => '3',
]);
$previousVersion = $room->fresh()->state_version;
Event::fake([RoomStateChanged::class]);
app()->call([new RemoveAbsentPlayers, 'handle']);
app()->call([new RemoveAbsentPlayers, 'handle']);
$this->assertModelMissing($absentPlayer);
$this->assertModelMissing($vote);
$this->assertModelExists($recentPlayer);
expect($room->fresh()->state_version)->toBe($previousVersion + 1);
Event::assertDispatched(
RoomStateChanged::class,
fn (RoomStateChanged $event): bool => $event->change === RoomStateChange::PlayerKicked
&& $event->affectedPlayerId === $absentPlayer->id
&& collect($event->state['players'])->doesntContain('id', $absentPlayer->id),
);
Event::assertDispatchedTimes(RoomStateChanged::class, 1);
$this->post(route('room.code'), [
'join_code' => $room->join_code,
'player_name' => 'Nico',
])->assertRedirect(route('room.show', $room));
expect($room->players()->where('name', 'Nico')->sole()->id)->not->toBe($absentPlayer->id);
});