feat: add routes, lang, tests, stubs, docs, and docker configurations
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
use App\Models\PasswordHistory;
|
||||
use App\Models\User;
|
||||
use App\Services\Auth\PasswordPolicyService;
|
||||
use App\Services\SystemConfig\SystemConfigService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
beforeEach(function () {
|
||||
$ref = new ReflectionClass(SystemConfigService::class);
|
||||
$prop = $ref->getProperty('resolvedSettings');
|
||||
$prop->setAccessible(true);
|
||||
$prop->setValue(null, null);
|
||||
Cache::flush();
|
||||
});
|
||||
|
||||
function setSetting(string $key, mixed $value): void
|
||||
{
|
||||
app(SystemConfigService::class)->update([$key => $value]);
|
||||
}
|
||||
|
||||
test('isPasswordExpired returns false when expiry is disabled', function () {
|
||||
setSetting('password_expiry_days', 0);
|
||||
$user = User::factory()->create([
|
||||
'password_changed_at' => now()->subYears(5),
|
||||
]);
|
||||
|
||||
expect(PasswordPolicyService::isPasswordExpired($user))->toBeFalse();
|
||||
});
|
||||
|
||||
test('isPasswordExpired uses password_changed_at when present', function () {
|
||||
setSetting('password_expiry_days', 30);
|
||||
$expired = User::factory()->create(['password_changed_at' => now()->subDays(31)]);
|
||||
$fresh = User::factory()->create(['password_changed_at' => now()->subDays(5)]);
|
||||
|
||||
expect(PasswordPolicyService::isPasswordExpired($expired))->toBeTrue();
|
||||
expect(PasswordPolicyService::isPasswordExpired($fresh))->toBeFalse();
|
||||
});
|
||||
|
||||
test('isPasswordExpired falls back to created_at when password_changed_at is null', function () {
|
||||
setSetting('password_expiry_days', 30);
|
||||
$user = User::factory()->create();
|
||||
DB::table('users')->where('id', $user->id)->update([
|
||||
'password_changed_at' => null,
|
||||
'created_at' => now()->subDays(40),
|
||||
]);
|
||||
|
||||
expect(PasswordPolicyService::isPasswordExpired($user->fresh()))->toBeTrue();
|
||||
});
|
||||
|
||||
test('checkHistory is a no-op when history count is zero', function () {
|
||||
setSetting('password_history_count', 0);
|
||||
$user = User::factory()->create();
|
||||
PasswordHistory::create(['user_id' => $user->id, 'password' => Hash::make('old-pass')]);
|
||||
|
||||
PasswordPolicyService::checkHistory($user, 'old-pass');
|
||||
})->throwsNoExceptions();
|
||||
|
||||
test('checkHistory throws when reusing a recent password', function () {
|
||||
setSetting('password_history_count', 3);
|
||||
$user = User::factory()->create();
|
||||
PasswordHistory::create(['user_id' => $user->id, 'password' => Hash::make('reused-pass')]);
|
||||
|
||||
PasswordPolicyService::checkHistory($user, 'reused-pass');
|
||||
})->throws(ValidationException::class);
|
||||
|
||||
test('checkHistory passes when new password is different from history', function () {
|
||||
setSetting('password_history_count', 3);
|
||||
$user = User::factory()->create();
|
||||
PasswordHistory::create(['user_id' => $user->id, 'password' => Hash::make('old-1')]);
|
||||
PasswordHistory::create(['user_id' => $user->id, 'password' => Hash::make('old-2')]);
|
||||
|
||||
PasswordPolicyService::checkHistory($user, 'totally-different');
|
||||
})->throwsNoExceptions();
|
||||
|
||||
test('checkHistory only inspects the most recent N entries', function () {
|
||||
setSetting('password_history_count', 2);
|
||||
$user = User::factory()->create();
|
||||
|
||||
DB::table('password_histories')->insert([
|
||||
['user_id' => $user->id, 'password' => Hash::make('ancient'), 'created_at' => now()->subDays(10), 'updated_at' => now()->subDays(10)],
|
||||
['user_id' => $user->id, 'password' => Hash::make('recent-1'), 'created_at' => now()->subDays(2), 'updated_at' => now()->subDays(2)],
|
||||
['user_id' => $user->id, 'password' => Hash::make('recent-2'), 'created_at' => now()->subDay(), 'updated_at' => now()->subDay()],
|
||||
]);
|
||||
|
||||
PasswordPolicyService::checkHistory($user, 'ancient');
|
||||
})->throwsNoExceptions();
|
||||
|
||||
test('recordPasswordChange creates history row and stamps password_changed_at', function () {
|
||||
setSetting('password_history_count', 5);
|
||||
$user = User::factory()->create(['password_changed_at' => null]);
|
||||
|
||||
PasswordPolicyService::recordPasswordChange($user, Hash::make('new-pass'));
|
||||
|
||||
expect($user->fresh()->password_changed_at)->not->toBeNull();
|
||||
expect(PasswordHistory::where('user_id', $user->id)->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('getRules respects min/max length from settings', function () {
|
||||
setSetting('password_min_length', 10);
|
||||
setSetting('password_max_length', 50);
|
||||
|
||||
$rules = PasswordPolicyService::getRules();
|
||||
|
||||
expect($rules)->toBeInstanceOf(Password::class);
|
||||
});
|
||||
Reference in New Issue
Block a user