create(); WebauthnCredential::create(['user_id' => $u->id, 'name' => 'k', 'credential_id' => 'cid', 'public_key' => '{}', 'sign_count' => 0]); return $u->fresh(); } public function test_totp_user_sees_the_field(): void { $user = User::factory()->create(['two_factor_secret' => 'S', 'two_factor_confirmed_at' => now()]); session(['2fa.user' => $user->id, '2fa.remember' => false]); Livewire::test(TwoFactorChallenge::class)->assertSet('pendingHasTotp', true); } public function test_totp_user_sees_the_backup_link_but_not_the_backup_field(): void { $user = User::factory()->create(['two_factor_secret' => 'S', 'two_factor_confirmed_at' => now()]); session(['2fa.user' => $user->id, '2fa.remember' => false]); Livewire::test(TwoFactorChallenge::class) ->assertSee('000000') // TOTP field present ->assertSee(__('auth.challenge_use_backup')) // backup link present ->assertDontSee(__('auth.challenge_backup_placeholder')); // no backup field here } public function test_key_only_user_without_secure_context_is_redirected_to_the_backup_view(): void { // No domain configured → webauthnAvailable() false, pendingHasTotp false: the backup code // is the only usable path, so mount() sends the user straight to the dedicated view. $user = $this->keyOnlyUser(); session(['2fa.user' => $user->id, '2fa.remember' => false]); Livewire::test(TwoFactorChallenge::class) ->assertRedirect(route('two-factor.challenge.backup')); } public function test_key_only_user_with_secure_context_stays_and_shows_key_plus_backup(): void { $svc = $this->mock(WebauthnService::class); $svc->shouldReceive('available')->andReturn(true); $user = $this->keyOnlyUser(); session(['2fa.user' => $user->id, '2fa.remember' => false]); Livewire::test(TwoFactorChallenge::class) ->assertNoRedirect() ->assertSee(__('auth.webauthn_login')) // security-key button ->assertSee(__('auth.challenge_use_backup')) // backup link ->assertDontSee('000000') // no TOTP field ->assertDontSee(__('auth.challenge_backup_placeholder')); // no inline backup field } }