create(['two_factor_secret' => 'S', 'two_factor_confirmed_at' => now()]); } private function keyUser(): User { $u = User::factory()->create(); WebauthnCredential::create(['user_id' => $u->id, 'name' => 'k', 'credential_id' => 'cid', 'public_key' => '{}', 'sign_count' => 0]); return $u->fresh(); } public function test_has_totp_only_reflects_totp(): void { $this->assertTrue($this->totpUser()->hasTotp()); $this->assertFalse($this->keyUser()->hasTotp()); } public function test_has_two_factor_enabled_is_either_factor(): void { $this->assertTrue($this->totpUser()->hasTwoFactorEnabled()); $this->assertTrue($this->keyUser()->hasTwoFactorEnabled()); $this->assertFalse(User::factory()->create()->hasTwoFactorEnabled()); } public function test_security_onboarded_is_password_rotation_only(): void { $this->assertTrue(User::factory()->create(['must_change_password' => false])->securityOnboarded()); $this->assertFalse(User::factory()->create(['must_change_password' => true])->securityOnboarded()); } public function test_reset_if_no_factor_clears_codes_only_when_no_factor_remains(): void { $totp = $this->totpUser(); $totp->replaceRecoveryCodes(); $totp->resetIfNoFactor(); $this->assertTrue($totp->fresh()->hasRecoveryCodes(), 'codes kept while a factor remains'); $none = User::factory()->create(); $none->replaceRecoveryCodes(); $none->resetIfNoFactor(); $this->assertFalse($none->fresh()->hasRecoveryCodes(), 'codes cleared when no factor remains'); } }