CluPilotCloud/tests/Feature/Admin/PasswordConfirmationGuardTe...

89 lines
4.3 KiB
PHP

<?php
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))));
it('confirms a console password against the operator record, not a portal user', function () {
$operator = Operator::factory()->role('Owner')->create(['password' => 'operator-passwort']);
Livewire::actingAs($operator, 'operator')->test(Secrets::class)
->set('confirmablePassword', 'operator-passwort')
->call('confirmPassword')
->assertHasNoErrors();
});
it('rejects a password that belongs to a portal user with the same address', function () {
// The trap: two tables, one address. Confirming against the wrong one would
// let a customer's password unlock the console.
$operator = Operator::factory()->role('Owner')->create([
'email' => 'doppelt@clupilot.test', 'password' => 'operator-passwort',
]);
User::factory()->create(['email' => 'doppelt@clupilot.test', 'password' => 'kunden-passwort']);
Livewire::actingAs($operator, 'operator')->test(Secrets::class)
->set('confirmablePassword', 'kunden-passwort')
->call('confirmPassword')
->assertHasErrors('confirmablePassword');
});
it('still confirms a portal password on the web guard', function () {
$user = User::factory()->create(['password' => 'kunden-passwort']);
Livewire::actingAs($user)->test(App\Livewire\Settings::class)
->set('confirmablePassword', 'kunden-passwort')
->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();
});