invalidate() to * sign a suspended or closed customer out — flush() + migrate(true) — which * discarded every attribute in the session, not only the 'web' guard's, so it * silently signed an operator elsewhere in that same browser out of the * console too. */ it("suspending a portal customer does not sign an operator out of a console session sharing the same browser", function () { $operator = Operator::factory()->role('Owner')->create(); $user = User::factory()->create(); Customer::factory()->create(['email' => $user->email, 'user_id' => $user->id, 'status' => 'suspended']); // Auth::guard(...)->login() rather than actingAs(): actingAs() only sets // an in-memory user on the guard object, it never writes to the session — // so it cannot tell session()->invalidate() and session()->regenerate() // apart. login() does the same session write a real sign-in performs. Auth::guard('operator')->login($operator); Auth::guard('web')->login($user); $this->get(route('dashboard'))->assertRedirect(route('login')); // Auth::guard('operator') is still the SAME cached guard instance from // the login() call above — its check() would say "authenticated" from // its own in-memory $user property regardless of what happened to the // session, which is exactly the blind spot that would make this // assertion pass whether the fix is there or not. forgetGuards() drops // that cache, forcing the next guard() call to rebuild from the CURRENT // session data — the same thing a genuinely new request would do. Auth::forgetGuards(); expect(Auth::guard('web')->check())->toBeFalse() ->and(Auth::guard('operator')->check())->toBeTrue() ->and(Auth::guard('operator')->id())->toBe($operator->id); });