Files
biiproject-kit-v1/tests/Feature/Auth/PasswordControllerTest.php
T

177 lines
7.1 KiB
PHP

<?php
use App\Models\PasswordHistory;
use App\Models\User;
use App\Services\Auth\PasswordPolicyService;
use App\Services\SystemConfig\SystemConfigService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Hash;
beforeEach(function () {
$ref = new ReflectionClass(SystemConfigService::class);
$prop = $ref->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);
});