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(); });