google2fa = new Google2FA(); } public function test_setup_totp_returns_qr_code(): void { $user = User::factory()->create(); $response = $this->actingAs($user)->postJson('/api/v1/auth/mfa/setup/totp'); $response->assertOk() ->assertJsonStructure([ 'data' => ['secret', 'qr_code_url', 'provisioning_uri'], ]); } public function test_confirm_totp_returns_backup_codes(): void { $user = User::factory()->create(); // Start setup $setupResponse = $this->actingAs($user)->postJson('/api/v1/auth/mfa/setup/totp'); $secret = $setupResponse->json('data.secret'); $validCode = $this->google2fa->getCurrentOtp($secret); // Confirm $response = $this->actingAs($user)->postJson('/api/v1/auth/mfa/setup/totp/confirm', [ 'code' => $validCode, ]); $response->assertOk() ->assertJson([ 'data' => [ 'mfa_enabled' => true, 'method' => 'totp', ], ]) ->assertJsonStructure([ 'data' => ['backup_codes'], ]); $this->assertCount(10, $response->json('data.backup_codes')); } public function test_confirm_totp_with_invalid_code_fails(): void { $user = User::factory()->create(); $this->actingAs($user)->postJson('/api/v1/auth/mfa/setup/totp'); $response = $this->actingAs($user)->postJson('/api/v1/auth/mfa/setup/totp/confirm', [ 'code' => '000000', ]); $response->assertStatus(422) ->assertJson(['message' => 'Ongeldige verificatiecode.']); } public function test_disable_requires_valid_code(): void { $user = $this->createUserWithTotp(); $secret = decrypt($user->mfa_secret); $validCode = $this->google2fa->getCurrentOtp($secret); $response = $this->actingAs($user)->postJson('/api/v1/auth/mfa/disable', [ 'code' => $validCode, 'method' => 'totp', ]); $response->assertOk(); $user->refresh(); $this->assertFalse($user->mfa_enabled); } public function test_disable_with_invalid_code_fails(): void { $user = $this->createUserWithTotp(); $response = $this->actingAs($user)->postJson('/api/v1/auth/mfa/disable', [ 'code' => '000000', 'method' => 'totp', ]); $response->assertStatus(422) ->assertJson(['message' => 'Ongeldige verificatiecode.']); $user->refresh(); $this->assertTrue($user->mfa_enabled); } public function test_regenerate_backup_codes_requires_mfa_enabled(): void { $user = User::factory()->create(); $response = $this->actingAs($user)->postJson('/api/v1/auth/mfa/backup-codes', [ 'code' => '123456', ]); $response->assertStatus(422) ->assertJson(['message' => 'MFA is niet ingeschakeld.']); } public function test_regenerate_backup_codes_with_valid_totp(): void { $user = $this->createUserWithTotp(); $secret = decrypt($user->mfa_secret); $validCode = $this->google2fa->getCurrentOtp($secret); $response = $this->actingAs($user)->postJson('/api/v1/auth/mfa/backup-codes', [ 'code' => $validCode, ]); $response->assertOk() ->assertJsonStructure(['data' => ['backup_codes']]); $this->assertCount(10, $response->json('data.backup_codes')); } public function test_status_returns_correct_state(): void { $user = User::factory()->create(); $response = $this->actingAs($user)->getJson('/api/v1/auth/mfa/status'); $response->assertOk() ->assertJson([ 'data' => [ 'enabled' => false, 'method' => null, 'confirmed_at' => null, 'backup_codes_remaining' => 0, ], ]); } public function test_status_returns_enabled_state(): void { $user = $this->createUserWithTotp(); $response = $this->actingAs($user)->getJson('/api/v1/auth/mfa/status'); $response->assertOk() ->assertJson([ 'data' => [ 'enabled' => true, 'method' => 'totp', 'backup_codes_remaining' => 10, ], ]); } public function test_unauthenticated_cannot_access_setup(): void { $this->postJson('/api/v1/auth/mfa/setup/totp') ->assertUnauthorized(); } private function createUserWithTotp(): User { $user = User::factory()->create(); $mfaService = app(MfaService::class); $setupResult = $mfaService->setupTotp($user); $secret = $setupResult['secret']; $validCode = $this->google2fa->getCurrentOtp($secret); $mfaService->confirmTotp($user, $validCode); return $user->refresh(); } }