seed(RolePermissionSeeder::class); $this->seed(IdentityMessageDefinitionSeeder::class); } public function test_forgot_password_flow_produces_password_reset_token(): void { User::factory()->create([ 'email' => 'user@example.com', 'status' => UserStatus::Active->value, ]); $response = $this->postJson('/api/auth/forgot-password', [ 'email' => 'user@example.com', ]); $response->assertStatus(200)->assertJson(['success' => true]); // 기존 password_reset_tokens 는 여전히 채워져야 함 (사용자 체감 동일) $this->assertDatabaseHas('password_reset_tokens', [ 'email' => 'user@example.com', ]); } public function test_forgot_password_for_pending_user_can_still_request_but_reset_is_blocked(): void { User::factory()->create([ 'email' => 'pending@example.com', 'status' => UserStatus::PendingVerification->value, ]); // 요청 자체는 통과 (토큰 발송). 실제 재설정 시 RejectPasswordResetForPendingUser 가 차단 $response = $this->postJson('/api/auth/forgot-password', [ 'email' => 'pending@example.com', ]); $response->assertStatus(200)->assertJson(['success' => true]); } public function test_forgot_password_fails_for_unregistered_email(): void { $response = $this->postJson('/api/auth/forgot-password', [ 'email' => 'nonexistent@example.com', ]); $response->assertStatus(422)->assertJsonValidationErrors(['email']); } public function test_idv_challenge_can_be_issued_with_password_reset_purpose(): void { // IDV 인프라가 password_reset purpose 를 지원하는지 직접 확인 // (forgot-password 엔드포인트와 별개로 IDV 인프라 자체의 호환성) User::factory()->create([ 'email' => 'idv@example.com', 'status' => UserStatus::Active->value, ]); $response = $this->postJson('/api/identity/challenges', [ 'purpose' => 'password_reset', 'target' => ['email' => 'idv@example.com'], ]); $response->assertStatus(201); // IDV 인프라의 log 행이 생성되었는지 (MailProvider 는 password_reset 시 link 모드) $this->assertDatabaseHas('identity_verification_logs', [ 'purpose' => 'password_reset', 'status' => IdentityVerificationStatus::Sent->value, ]); } }