getProperty('resolvedSettings'); $prop->setAccessible(true); $prop->setValue(null, null); Cache::flush(); }); // ── Happy path ──────────────────────────────────────────────────────────────── test('password update succeeds with valid current password', function () { $user = User::factory()->create(['password' => Hash::make('current-pass')]); $this->actingAs($user) ->from('/profile') ->put('/password', [ 'current_password' => 'current-pass', 'password' => 'New-Pass-123', 'password_confirmation' => 'New-Pass-123', ]) ->assertRedirect('/profile') ->assertSessionHasNoErrors(); expect(Hash::check('New-Pass-123', $user->fresh()->password))->toBeTrue(); }); test('password update stamps password_changed_at', function () { app(SystemConfigService::class)->update(['password_history_count' => 0]); $user = User::factory()->create([ 'password' => Hash::make('current-pass'), 'password_changed_at' => null, ]); $this->actingAs($user)->put('/password', [ 'current_password' => 'current-pass', 'password' => 'New-Pass-456', 'password_confirmation' => 'New-Pass-456', ]); expect($user->fresh()->password_changed_at)->not->toBeNull(); }); // ── Validation ──────────────────────────────────────────────────────────────── test('wrong current password is rejected', function () { $user = User::factory()->create(['password' => Hash::make('real-pass')]); $this->actingAs($user) ->from('/profile') ->put('/password', [ 'current_password' => 'wrong-pass', 'password' => 'New-Pass-789', 'password_confirmation' => 'New-Pass-789', ]) ->assertSessionHasErrorsIn('updatePassword', 'current_password'); expect(Hash::check('real-pass', $user->fresh()->password))->toBeTrue(); }); test('mismatched confirmation is rejected', function () { $user = User::factory()->create(['password' => Hash::make('correct')]); $this->actingAs($user) ->from('/profile') ->put('/password', [ 'current_password' => 'correct', 'password' => 'New-Pass-Abc', 'password_confirmation' => 'Different-Xyz', ]) ->assertSessionHasErrors(); }); // ── History enforcement ─────────────────────────────────────────────────────── test('reusing a recent password is rejected with history enabled', function () { app(SystemConfigService::class)->update(['password_history_count' => 3]); $oldHash = Hash::make('OldPassword1!'); $user = User::factory()->create(['password' => Hash::make('current-pass')]); PasswordHistory::create(['user_id' => $user->id, 'password' => $oldHash]); $this->actingAs($user) ->from('/profile') ->put('/password', [ 'current_password' => 'current-pass', 'password' => 'OldPassword1!', 'password_confirmation' => 'OldPassword1!', ]) ->assertSessionHasErrors(); }); test('history check is skipped when history_count is zero', function () { app(SystemConfigService::class)->update(['password_history_count' => 0]); $oldHash = Hash::make('Recycled-Pass!1'); $user = User::factory()->create(['password' => Hash::make('current-pass')]); PasswordHistory::create(['user_id' => $user->id, 'password' => $oldHash]); $this->actingAs($user) ->from('/profile') ->put('/password', [ 'current_password' => 'current-pass', 'password' => 'Recycled-Pass!1', 'password_confirmation' => 'Recycled-Pass!1', ]) ->assertSessionHasNoErrors(); }); // ── History recording ───────────────────────────────────────────────────────── test('successful password change records new entry in password history', function () { app(SystemConfigService::class)->update(['password_history_count' => 5]); $user = User::factory()->create(['password' => Hash::make('current-pass')]); $this->actingAs($user)->put('/password', [ 'current_password' => 'current-pass', 'password' => 'Brand-New-777!', 'password_confirmation' => 'Brand-New-777!', ]); expect(PasswordHistory::where('user_id', $user->id)->count())->toBe(1); }); // ── old password no longer works after update ───────────────────────────────── test('old password cannot be used to log in after successful update', function () { $user = User::factory()->create(['password' => Hash::make('old-pass-456')]); $this->actingAs($user)->put('/password', [ 'current_password' => 'old-pass-456', 'password' => 'Brand-New-888!', 'password_confirmation' => 'Brand-New-888!', ]); // Old password must not match the new stored hash expect(Hash::check('old-pass-456', $user->fresh()->password))->toBeFalse(); // New password must match expect(Hash::check('Brand-New-888!', $user->fresh()->password))->toBeTrue(); }); // ── Guest ───────────────────────────────────────────────────────────────────── test('guest cannot update password', function () { $this->put('/password', [ 'current_password' => 'x', 'password' => 'y', 'password_confirmation' => 'y', ])->assertRedirect('/login'); }); // ── JSON response ───────────────────────────────────────────────────────────── test('json request receives json success response', function () { $user = User::factory()->create(['password' => Hash::make('current-pass')]); $this->actingAs($user) ->putJson('/password', [ 'current_password' => 'current-pass', 'password' => 'Json-Pass-1!', 'password_confirmation' => 'Json-Pass-1!', ]) ->assertOk() ->assertJsonPath('success', true); });