From 2596b5df18c379db338e9d5cbb87a10f29405a95 Mon Sep 17 00:00:00 2001 From: ncoronel Date: Tue, 25 Aug 2026 14:10:38 -0300 Subject: [PATCH] test(auth): cover password reset expiration --- ...eateResetPasswordAttemptControllerTest.php | 4 ++++ .../Feature/Auth/ResetPasswordAttemptTest.php | 4 ++++ .../Auth/ResetPasswordControllerTest.php | 24 +++++++++++++++++++ ...dateResetPasswordAttemptControllerTest.php | 21 ++++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/tests/Feature/Auth/CreateResetPasswordAttemptControllerTest.php b/tests/Feature/Auth/CreateResetPasswordAttemptControllerTest.php index bbba493..cd7e241 100644 --- a/tests/Feature/Auth/CreateResetPasswordAttemptControllerTest.php +++ b/tests/Feature/Auth/CreateResetPasswordAttemptControllerTest.php @@ -69,6 +69,10 @@ class CreateResetPasswordAttemptControllerTest extends TestCase $this->assertTrue($attempt->user->is($user)); $this->assertMatchesRegularExpression('/^\d{4}$/', $attempt->codigo); $this->assertSame(ResetPasswordAttempt::STATUS_PENDING, $attempt->status); + $this->assertTrue($attempt->expires_at->between( + now()->addMinutes(59), + now()->addMinutes(60), + )); Event::assertDispatched( PasswordResetRequested::class, fn (PasswordResetRequested $event): bool => $event->attemptId === $attempt->id diff --git a/tests/Feature/Auth/ResetPasswordAttemptTest.php b/tests/Feature/Auth/ResetPasswordAttemptTest.php index eb8c003..e9ef95f 100644 --- a/tests/Feature/Auth/ResetPasswordAttemptTest.php +++ b/tests/Feature/Auth/ResetPasswordAttemptTest.php @@ -18,7 +18,9 @@ class ResetPasswordAttemptTest extends TestCase 'id', 'user_id', 'codigo', + 'reason', 'status', + 'expires_at', ], Schema::getColumnListing('reset_password_attempts')); } @@ -28,12 +30,14 @@ class ResetPasswordAttemptTest extends TestCase $attempt = $user->resetPasswordAttempts()->create([ 'codigo' => '123456', + 'expires_at' => now()->addHour(), ]); $this->assertSame(ResetPasswordAttempt::STATUS_PENDING, $attempt->status); $this->assertTrue($attempt->user->is($user)); $this->assertTrue($user->resetPasswordAttempts->contains($attempt)); $this->assertFalse($attempt->usesTimestamps()); + $this->assertTrue($attempt->expires_at->isFuture()); $this->assertArrayNotHasKey('codigo', $attempt->toArray()); } diff --git a/tests/Feature/Auth/ResetPasswordControllerTest.php b/tests/Feature/Auth/ResetPasswordControllerTest.php index 81b0201..57f085f 100644 --- a/tests/Feature/Auth/ResetPasswordControllerTest.php +++ b/tests/Feature/Auth/ResetPasswordControllerTest.php @@ -99,6 +99,30 @@ class ResetPasswordControllerTest extends TestCase $this->assertSame(ResetPasswordAttempt::STATUS_USED, $attempt->fresh()->status); } + public function test_an_expired_validated_attempt_cannot_reset_the_password(): void + { + $user = User::factory()->create([ + 'email' => 'ada@example.com', + 'password' => 'OldSecret!123', + ]); + $attempt = $user->resetPasswordAttempts()->create([ + 'codigo' => '1234', + 'status' => ResetPasswordAttempt::STATUS_VALIDATED, + 'expires_at' => now()->subSecond(), + ]); + + $this->postJson('/api/password/reset', [ + 'email' => 'ada@example.com', + 'codigo' => '1234', + 'password' => 'NewSecret!456', + 'password_confirmation' => 'NewSecret!456', + ])->assertUnprocessable() + ->assertJsonValidationErrors('codigo'); + + $this->assertTrue(Hash::check('OldSecret!123', $user->fresh()->password)); + $this->assertSame(ResetPasswordAttempt::STATUS_EXPIRED, $attempt->fresh()->status); + } + public function test_it_validates_password_confirmation_and_strength(): void { $this->postJson('/api/password/reset', [ diff --git a/tests/Feature/Auth/ValidateResetPasswordAttemptControllerTest.php b/tests/Feature/Auth/ValidateResetPasswordAttemptControllerTest.php index 444f81d..d6bd514 100644 --- a/tests/Feature/Auth/ValidateResetPasswordAttemptControllerTest.php +++ b/tests/Feature/Auth/ValidateResetPasswordAttemptControllerTest.php @@ -49,6 +49,27 @@ class ValidateResetPasswordAttemptControllerTest extends TestCase ); } + public function test_it_expires_an_attempt_and_returns_the_expired_code_message(): void + { + $user = User::factory()->create(['email' => 'ada@example.com']); + $attempt = $user->resetPasswordAttempts()->create([ + 'codigo' => '1234', + 'expires_at' => now()->subSecond(), + ]); + + $this->postJson('/api/password/reset-attempts/validate', [ + 'email' => 'ada@example.com', + 'codigo' => '1234', + ])->assertUnprocessable() + ->assertJsonValidationErrors('codigo') + ->assertJsonPath('errors.codigo.0', __('api.auth.reset_code_expired')); + + $this->assertSame( + ResetPasswordAttempt::STATUS_EXPIRED, + $attempt->fresh()->status, + ); + } + public function test_it_rejects_an_expired_or_already_validated_attempt(): void { $user = User::factory()->create(['email' => 'ada@example.com']);