create(['two_factor_secret' => 'S', 'two_factor_confirmed_at' => now()]); $user->replaceRecoveryCodes(); Livewire::actingAs($user)->test(Security::class)->call('disableTwoFactor'); $fresh = $user->fresh(); $this->assertFalse($fresh->hasTotp()); $this->assertFalse($fresh->hasRecoveryCodes(), 'last factor removed → codes cleared'); } public function test_removing_totp_keeps_codes_when_a_key_remains(): void { $user = User::factory()->create(['two_factor_secret' => 'S', 'two_factor_confirmed_at' => now()]); WebauthnCredential::create(['user_id' => $user->id, 'name' => 'k', 'credential_id' => 'cid', 'public_key' => '{}', 'sign_count' => 0]); $user->replaceRecoveryCodes(); Livewire::actingAs($user->fresh())->test(Security::class)->call('disableTwoFactor'); $fresh = $user->fresh(); $this->assertFalse($fresh->hasTotp()); $this->assertTrue($fresh->hasWebauthnCredentials()); $this->assertTrue($fresh->hasRecoveryCodes(), 'key remains → codes kept'); } public function test_a_key_can_be_the_first_factor_without_existing_2fa(): void { $user = User::factory()->create(); $svc = Mockery::mock(WebauthnService::class); $svc->shouldReceive('available')->andReturnTrue(); $svc->shouldReceive('registrationOptions')->andReturn(['ok' => true]); app()->instance(WebauthnService::class, $svc); Livewire::actingAs($user)->test(WebauthnKeys::class) ->set('newName', 'YubiKey') ->call('options', $svc) ->assertReturned(['ok' => true]); } public function test_removing_the_last_key_clears_codes(): void { $user = User::factory()->create(); $cred = WebauthnCredential::create(['user_id' => $user->id, 'name' => 'k', 'credential_id' => 'cid', 'public_key' => '{}', 'sign_count' => 0]); $user->replaceRecoveryCodes(); $svc = Mockery::mock(WebauthnService::class); $svc->shouldReceive('available')->andReturnTrue(); app()->instance(WebauthnService::class, $svc); Livewire::actingAs($user->fresh())->test(WebauthnKeys::class)->call('remove', $cred->id, $svc); $this->assertFalse($user->fresh()->hasRecoveryCodes()); } }