diff --git a/tests/Feature/Admin/PasswordConfirmationGuardTest.php b/tests/Feature/Admin/PasswordConfirmationGuardTest.php index 42976af..256a611 100644 --- a/tests/Feature/Admin/PasswordConfirmationGuardTest.php +++ b/tests/Feature/Admin/PasswordConfirmationGuardTest.php @@ -3,6 +3,7 @@ use App\Livewire\Admin\Secrets; use App\Models\Operator; use App\Models\User; +use Illuminate\Support\Facades\Auth; use Livewire\Livewire; beforeEach(fn () => config()->set('admin_access.secrets_key', 'base64:'.base64_encode(random_bytes(32)))); @@ -38,3 +39,50 @@ it('still confirms a portal password on the web guard', function () { ->call('confirmPassword') ->assertHasNoErrors(); }); + +it('resolves the operator explicitly by guard, not by whichever guard happens to be the ambient default', function () { + // actingAs()/Livewire::actingAs() both call Auth::shouldUse($guard) as a + // side effect, which makes the named guard the DEFAULT guard — and under + // that, auth()->user() and auth()->guard('operator')->user() resolve + // identically, so every OTHER test in this file passes just the same + // whether confirmPassword() asks for the right guard explicitly or just + // reads the ambient default. That is not a hypothetical: reverting the + // trait's guard(...)->user() call back to a bare auth()->user() passes + // the entire 834-test suite, this file included — the gap this test + // closes, by making that distinction observable. + // + // Signing in with Auth::guard('operator')->login() alone (no actingAs) + // does NOT reach this test: it proves confirmationGuard is genuinely + // consulted, but Secrets::mount()'s own $this->authorize('secrets.manage') + // ALSO reads the ambient default guard (Laravel's Gate resolves the + // current user the same bare way) — so mount() itself 403s before + // confirmPassword() is ever reached, on the very account this test means + // to prove works. Confirmed directly: Auth::guard('web')->check() is + // false, Auth::guard('operator')->check() is true, and still + // Gate::check('secrets.manage') is false in that state. + // + // So: mount through actingAs() as usual — authorize() runs exactly once, + // while shouldUse('operator') is still in effect from actingAs, and + // legitimately passes. mount() does not run again on a later ->call(). + // Only THEN, before touching confirmPassword() specifically, undo the + // masking by hand: Auth::shouldUse('web') switches the ambient default + // back to 'web' — genuinely empty, not merely relabelled — while the + // operator's own session on the 'operator' guard is untouched (setUser() + // on a guard does not depend on which guard is "default"). That is + // exactly the real shape of a console request: a genuine operator sign-in + // (App\Livewire\Auth\OperatorLogin::completeLogin()) never touches the + // web guard, default or not. + $operator = Operator::factory()->role('Owner')->create(['password' => 'operator-passwort']); + + $page = Livewire::actingAs($operator, 'operator')->test(Secrets::class); + + Auth::shouldUse('web'); + + expect(Auth::guard('web')->check())->toBeFalse() + ->and(Auth::guard('operator')->check())->toBeTrue() + ->and(auth()->check())->toBeFalse(); + + $page->set('confirmablePassword', 'operator-passwort') + ->call('confirmPassword') + ->assertHasNoErrors(); +});