create(); Livewire::actingAs($user)->test(LoginProtection::class) ->set('enabled', false)->set('maxretry', 7)->set('findtime', 15)->set('bantime', 120) ->set('whitelist', '203.0.113.0/24') ->call('save'); $this->assertSame('0', Setting::get('bruteforce_enabled')); $this->assertSame('7', Setting::get('bruteforce_maxretry')); $this->assertSame('203.0.113.0/24', Setting::get('bruteforce_whitelist')); } public function test_invalid_whitelist_is_rejected(): void { $user = User::factory()->create(); Livewire::actingAs($user)->test(LoginProtection::class) ->set('whitelist', 'junk-not-a-cidr')->call('save') ->assertHasErrors('whitelist'); $this->assertNull(Setting::get('bruteforce_maxretry')); } public function test_table_lists_active_bans_and_unban_removes_one(): void { $user = User::factory()->create(); BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]); // Must authenticate before issuing so the token's uid matches Auth::id() at consume time. $this->actingAs($user); $token = ConfirmToken::issue('banCleared', ['ip' => '203.0.113.5']); ConfirmToken::confirm($token); Livewire::actingAs($user)->test(LoginProtection::class) ->assertSee('203.0.113.5') ->call('unban', $token); $this->assertDatabaseCount('banned_ips', 0); $this->assertDatabaseHas('audit_events', ['action' => 'auth.ip_unbanned', 'target' => '203.0.113.5']); } public function test_unban_all_clears_every_ban(): void { $user = User::factory()->create(); BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]); BannedIp::create(['ip' => '203.0.113.6', 'banned_until' => now()->addHour(), 'attempts' => 10]); $this->actingAs($user); $token = ConfirmToken::issue('bansClearedAll'); ConfirmToken::confirm($token); Livewire::actingAs($user)->test(LoginProtection::class)->call('unbanAll', $token); $this->assertDatabaseCount('banned_ips', 0); $this->assertDatabaseHas('audit_events', ['action' => 'auth.ip_unbanned', 'target' => 'all']); } public function test_widening_whitelist_clears_now_exempt_bans(): void { $user = User::factory()->create(); BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]); Livewire::actingAs($user)->test(LoginProtection::class) ->set('whitelist', '203.0.113.0/24')->call('save'); $this->assertDatabaseCount('banned_ips', 0); // 203.0.113.5 now exempt → purged } public function test_whitelist_my_ip_adds_current_ip(): void { $user = User::factory()->create(); // Inject the public IP into every request bound to the container, // using the same rebinding mechanism as BruteforceHooksTest (Task 6). app()->rebinding('request', static function ($app, $request) { $request->server->set('REMOTE_ADDR', '198.51.100.7'); }); Livewire::actingAs($user)->test(LoginProtection::class) ->call('whitelistMyIp') ->assertSet('whitelist', fn ($w) => str_contains($w, '198.51.100.7')); } public function test_unban_rejects_a_forged_token(): void { $user = User::factory()->create(); BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]); Livewire::actingAs($user)->test(LoginProtection::class)->call('unban', 'not-a-real-token'); $this->assertDatabaseCount('banned_ips', 1); // forged token → no-op, ban remains } }