ip() to a public IP for every Livewire request in this test. * * Livewire's RequestBroker creates a fresh Symfony request (REMOTE_ADDR=127.0.0.1) * for every HTTP call, then the kernel binds it via app()->instance('request', $req). * The rebinding callback fires on every such binding, letting us mutate the already- * created request before component code reads request()->ip(). * * The app container is re-created between test methods (RefreshDatabase + setUp * boot a fresh app each time), so rebinding callbacks do not leak across tests. */ private function spoofIp(string $ip): void { app()->rebinding('request', static function ($app, $request) use ($ip) { $request->server->set('REMOTE_ADDR', $ip); }); } public function test_failed_logins_ban_the_ip_at_threshold(): void { User::factory()->create(['email' => 'admin@clusev.local', 'password' => Hash::make('correct-horse')]); // Drive request()->ip() to a PUBLIC IP (203.0.113.5, RFC-5737 TEST-NET). // 127.0.0.1 is hard-exempt in BruteforceGuard; using a loopback IP would make // record() no-op and the ban assertion would never hold. $this->spoofIp('203.0.113.5'); for ($i = 0; $i < 3; $i++) { Livewire::test(Login::class) ->set('email', 'admin@clusev.local') ->set('password', 'wrong') ->call('authenticate'); } $this->assertDatabaseHas('banned_ips', ['ip' => '203.0.113.5', 'reason' => 'login']); $this->assertDatabaseHas('audit_events', ['action' => 'auth.login_failed']); } public function test_failed_2fa_codes_ban_the_ip(): void { $user = User::factory()->create([ 'two_factor_secret' => (new Google2FA)->generateSecretKey(), 'two_factor_confirmed_at' => now(), ]); // 203.0.113.6 — separate public IP to avoid cross-test cache collisions. // maxretry=3 (setUp) < 2FA per-(user+IP) rate-limit (5), so ban triggers first. $this->spoofIp('203.0.113.6'); for ($i = 0; $i < 3; $i++) { session()->put('2fa.user', $user->id); Livewire::test(TwoFactorChallenge::class) ->set('code', '000000') ->call('verify'); } $this->assertDatabaseHas('banned_ips', ['ip' => '203.0.113.6', 'reason' => '2fa']); $this->assertDatabaseHas('audit_events', ['action' => 'auth.2fa_failed']); } }