create([ 'path' => 'test/reset-header.png', 'filename' => 'header.png', 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); $footer = Attachment::query()->create([ 'path' => 'test/reset-footer.png', 'filename' => 'footer.png', 'type' => AttachmentType::Image, 'mime_type' => 'image/png', ]); $this->tenant = Tenant::query()->create([ 'codigo' => 'reset-tenant', 'nombre' => 'Reset Tenant', 'dominio' => 'reset.local', 'primary_color' => '#112233', 'secondary_color' => '#000000', 'danger_color' => '#000000', 'success_color' => '#000000', 'header_bg_color' => '#000000', 'footer_bg_color' => '#000000', 'header_logo_id' => $header->id, 'footer_logo_id' => $footer->id, ]); } public function test_it_creates_a_pending_attempt_for_a_registered_email(): void { $user = User::factory()->create(['email' => 'ada@example.com']); $response = $this->postJson('/api/password/reset-attempts', [ 'tenant_codigo' => $this->tenant->codigo, 'email' => ' ADA@EXAMPLE.COM ', ]); $response ->assertAccepted() ->assertJsonPath('status', ResetPasswordAttempt::STATUS_PENDING); $attempt = ResetPasswordAttempt::query()->sole(); $this->assertTrue($attempt->user->is($user)); $this->assertMatchesRegularExpression('/^\d{4}$/', $attempt->codigo); $this->assertSame(ResetPasswordAttempt::STATUS_PENDING, $attempt->status); Event::assertDispatched( PasswordResetRequested::class, fn (PasswordResetRequested $event): bool => $event->attemptId === $attempt->id && $event->tenantCode === $this->tenant->codigo, ); } public function test_it_expires_previous_pending_and_validated_attempts(): void { $user = User::factory()->create(['email' => 'ada@example.com']); $pendingAttempt = $user->resetPasswordAttempts()->create([ 'codigo' => '1234', ]); $validatedAttempt = $user->resetPasswordAttempts()->create([ 'codigo' => '5678', 'status' => ResetPasswordAttempt::STATUS_VALIDATED, ]); $this->postJson('/api/password/reset-attempts', [ 'tenant_codigo' => $this->tenant->codigo, 'email' => 'ada@example.com', ])->assertAccepted(); $this->assertSame(ResetPasswordAttempt::STATUS_EXPIRED, $pendingAttempt->fresh()->status); $this->assertSame(ResetPasswordAttempt::STATUS_EXPIRED, $validatedAttempt->fresh()->status); $this->assertSame(1, $user->resetPasswordAttempts() ->where('status', ResetPasswordAttempt::STATUS_PENDING) ->count()); } public function test_unknown_email_gets_the_same_response_without_creating_an_attempt(): void { $response = $this->postJson('/api/password/reset-attempts', [ 'tenant_codigo' => $this->tenant->codigo, 'email' => 'unknown@example.com', ]); $response ->assertAccepted() ->assertJsonPath('status', ResetPasswordAttempt::STATUS_PENDING); $this->assertDatabaseCount('reset_password_attempts', 0); Event::assertNotDispatched(PasswordResetRequested::class); } public function test_it_validates_the_email(): void { $this->postJson('/api/password/reset-attempts', [ 'tenant_codigo' => $this->tenant->codigo, 'email' => 'invalid-email', ])->assertUnprocessable()->assertJsonValidationErrors('email'); } }