Files
portal/app/tests/Feature/Auth/ResetPasswordTest.php
T
Дмитрий 9c488122a1 phase2(reset-password): POST /api/auth/reset-password + ResetPasswordView + DB timezone fix
- AuthController::resetPassword через Password::reset() (callback пишет password_hash)
- ResetPasswordRequest: token + email + password (min 10 по ТЗ §22.4.1) + confirmed
- Rate-limit auth:reset:{sha256(token)[0..16]}|{ip} (5/15мин)
- ResetPasswordView для deep-link /reset/:token?email=...; pre-fill email из query; success → redirect /login через 3 сек
- Vue Router /reset/:token (guestOnly); web.php /reset SPA-path
- DB FIX: config/database.php pgsql.timezone=UTC — без него PG TIMESTAMPTZ +03 терялся при Carbon::parse и tokenExpired ошибочно срабатывал
- Pest +6 ResetPasswordTest (85/85 за 11.50с, 291 assertions)
- Vitest +7 (160/160 за 11.02с)
- Регресс: lint+type+format OK; build 784ms; story:build 21/28 за 30.74с; Pint+Stan passed
- CLAUDE.md v1.37→v1.38, реестр v1.46→v1.47

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 03:36:27 +03:00

114 lines
4.0 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
uses(DatabaseTransactions::class);
beforeEach(function () {
$this->tenant = Tenant::factory()->create();
$this->user = User::factory()->create([
'tenant_id' => $this->tenant->id,
'email' => 'reset@example.ru',
'password_hash' => Hash::make('old-password-1234'),
]);
});
test('POST /api/auth/reset-password успешно меняет password_hash и удаляет token', function () {
$token = Password::createToken($this->user);
$r = $this->postJson('/api/auth/reset-password', [
'token' => $token,
'email' => 'reset@example.ru',
'password' => 'new-strong-password-1234',
'password_confirmation' => 'new-strong-password-1234',
]);
$r->assertOk();
expect($r->json('message'))->toContain('успешно');
// Password::reset обновляет hash через callback.
$this->user->refresh();
expect(Hash::check('new-strong-password-1234', $this->user->password_hash))->toBeTrue();
expect(Hash::check('old-password-1234', $this->user->password_hash))->toBeFalse();
});
test('POST /api/auth/reset-password 422 при невалидном token', function () {
$r = $this->postJson('/api/auth/reset-password', [
'token' => 'fake-bad-token-zzz',
'email' => 'reset@example.ru',
'password' => 'new-strong-password-1234',
'password_confirmation' => 'new-strong-password-1234',
]);
$r->assertStatus(422);
expect($r->json('message'))->toContain('недействительна');
});
test('POST /api/auth/reset-password 422 при mismatch password_confirmation', function () {
$token = Password::createToken($this->user);
$r = $this->postJson('/api/auth/reset-password', [
'token' => $token,
'email' => 'reset@example.ru',
'password' => 'new-strong-password-1234',
'password_confirmation' => 'different-typo-zzz-9876',
]);
$r->assertStatus(422);
expect($r->json('errors.password'))->not->toBeEmpty();
});
test('POST /api/auth/reset-password 422 при коротком пароле (<10 символов по ТЗ §22.4.1)', function () {
$token = Password::createToken($this->user);
$r = $this->postJson('/api/auth/reset-password', [
'token' => $token,
'email' => 'reset@example.ru',
'password' => 'short9',
'password_confirmation' => 'short9',
]);
$r->assertStatus(422);
expect($r->json('errors.password'))->not->toBeEmpty();
});
test('POST /api/auth/reset-password 422 для несуществующего email', function () {
$r = $this->postJson('/api/auth/reset-password', [
'token' => 'any-token',
'email' => 'nobody@example.ru',
'password' => 'new-strong-password-1234',
'password_confirmation' => 'new-strong-password-1234',
]);
$r->assertStatus(422);
});
test('POST /api/auth/reset-password rate-limit: 5 неудачных → 6-я = 429', function () {
// Все 5 попыток с заведомо невалидным token → 422.
for ($i = 1; $i <= 5; $i++) {
$this->postJson('/api/auth/reset-password', [
'token' => 'bad-token-fixed-12345',
'email' => 'reset@example.ru',
'password' => 'new-strong-password-1234',
'password_confirmation' => 'new-strong-password-1234',
])->assertStatus(422);
}
// 6-я → 429 (token throttle key — sha256(token)+ip, тот же token = тот же key).
$r = $this->postJson('/api/auth/reset-password', [
'token' => 'bad-token-fixed-12345',
'email' => 'reset@example.ru',
'password' => 'new-strong-password-1234',
'password_confirmation' => 'new-strong-password-1234',
]);
$r->assertStatus(429);
expect($r->headers->get('Retry-After'))->not->toBeNull();
});