File manager - Edit - /var/www/html/homologBancodetalentos/tests/Feature/TalentBank/PasswordRecoveryE2ETest.php
Back
<?php namespace Tests\Feature\TalentBank; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Http; use Tests\TestCase; /** * Cobre o ciclo completo de recuperação de senha ponta a ponta: * Solicitação → (e-mail simulado) → Redefinição → Redirecionamento para /entrar * * O serviço de identidade (AUTH_SERVICE_URL) é substituído por Http::fake() em todos os testes. * Os testes de homologação manual seguem o roteiro em docs/talent-bank/password-recovery.md. */ class PasswordRecoveryE2ETest extends TestCase { private const FORGOT_URL = '/esqueci-senha'; private const RESET_URL = '/redefinir-senha'; private const LOGIN_ROUTE = 'entrar'; private const VALID_CPF = '01234567890'; private const VALID_EMAIL = 'usuario@example.com'; private const VALID_CODE = 'A1B2C3'; private const VALID_PASSWORD = 'Senha123'; protected function setUp(): void { parent::setUp(); // Desabilita CSRF e throttle para testes de formulário web $this->withoutMiddleware([ \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\ThrottleRequests::class, ]); } // ────────────────────────────────────────────── // Renderização das páginas // ────────────────────────────────────────────── public function test_forgot_password_page_renders(): void { $r = $this->get(self::FORGOT_URL); $r->assertOk(); } public function test_reset_password_page_renders(): void { $r = $this->get(self::RESET_URL); $r->assertOk(); } // ────────────────────────────────────────────── // Validação de formulário — Etapa 1 (solicitação) // ────────────────────────────────────────────── public function test_forgot_requires_cpf_cnpj_and_email(): void { $r = $this->post(self::FORGOT_URL, []); $r->assertSessionHasErrors(['cpf_cnpj', 'email']); } public function test_forgot_rejects_invalid_document_format(): void { $r = $this->post(self::FORGOT_URL, [ 'cpf_cnpj' => 'nao-e-cpf', 'email' => self::VALID_EMAIL, ]); $r->assertSessionHasErrors(['document']); } public function test_forgot_rejects_invalid_email(): void { $r = $this->post(self::FORGOT_URL, [ 'cpf_cnpj' => self::VALID_CPF, 'email' => 'nao-e-email', ]); $r->assertSessionHasErrors(['email']); } public function test_forgot_accepts_cnpj(): void { Http::fake([ '*/api/password/email' => Http::response(['message' => 'sent'], JsonResponse::HTTP_OK), ]); $r = $this->post(self::FORGOT_URL, [ 'cpf_cnpj' => '12345678000195', // 14 dígitos → CNPJ 'email' => self::VALID_EMAIL, ]); $r->assertSessionHasNoErrors(); } // ────────────────────────────────────────────── // Anti-enumeração — Etapa 1 // ────────────────────────────────────────────── /** * O legado e o novo devem sempre retornar a mesma mensagem genérica, * independente do status retornado pelo serviço de identidade. * Nenhum consumidor pode deduzir se o documento/e-mail existe. */ public function test_forgot_returns_generic_message_when_service_returns_200(): void { Http::fake([ '*/api/password/email' => Http::response(['message' => 'ok'], JsonResponse::HTTP_OK), ]); $r = $this->post(self::FORGOT_URL, [ 'cpf_cnpj' => self::VALID_CPF, 'email' => self::VALID_EMAIL, ]); $r->assertRedirect(self::FORGOT_URL); $r->assertSessionHas('status'); $this->assertStringNotContainsString( self::VALID_EMAIL, (string) session('status'), 'A mensagem de status não deve conter o e-mail do usuário (anti-enumeração)' ); } public function test_forgot_returns_same_generic_message_when_service_returns_404(): void { Http::fake([ '*/api/password/email' => Http::response(['message' => 'not found'], JsonResponse::HTTP_NOT_FOUND), ]); $r = $this->post(self::FORGOT_URL, [ 'cpf_cnpj' => self::VALID_CPF, 'email' => self::VALID_EMAIL, ]); $r->assertRedirect(self::FORGOT_URL); $r->assertSessionHas('status'); } public function test_forgot_returns_same_generic_message_when_service_returns_422(): void { Http::fake([ '*/api/password/email' => Http::response(['errors' => ['email' => ['invalid']]], JsonResponse::HTTP_UNPROCESSABLE_ENTITY), ]); $r = $this->post(self::FORGOT_URL, [ 'cpf_cnpj' => self::VALID_CPF, 'email' => self::VALID_EMAIL, ]); $r->assertRedirect(self::FORGOT_URL); $r->assertSessionHas('status'); } // ────────────────────────────────────────────── // Validação de formulário — Etapa 2 (redefinição) // ────────────────────────────────────────────── public function test_reset_requires_code_and_password(): void { $r = $this->post(self::RESET_URL, []); $r->assertSessionHasErrors(['code', 'new_password']); } public function test_reset_rejects_code_with_invalid_format(): void { // Código deve ser exatamente 6 caracteres alfanuméricos foreach (['AB12', 'ABCDEF1', 'AB 12C', 'ab@12c'] as $badCode) { $r = $this->post(self::RESET_URL, [ 'code' => $badCode, 'new_password' => self::VALID_PASSWORD, 'new_password_confirmation' => self::VALID_PASSWORD, ]); $r->assertSessionHasErrors(['code'], "Código \"{$badCode}\" deveria ser rejeitado"); } } public function test_reset_rejects_password_without_letters_and_numbers(): void { foreach (['somenumbers123', 'SOMENTE123', 'somenteletras'] as $weak) { // só letras minúsculas sem número, ou só maiúsculas, ou só letras } $r = $this->post(self::RESET_URL, [ 'code' => self::VALID_CODE, 'new_password' => 'somenteletras', 'new_password_confirmation' => 'somenteletras', ]); $r->assertSessionHasErrors(['new_password']); } public function test_reset_rejects_password_with_forbidden_chars(): void { $r = $this->post(self::RESET_URL, [ 'code' => self::VALID_CODE, 'new_password' => 'Senhaç123', 'new_password_confirmation' => 'Senhaç123', ]); $r->assertSessionHasErrors(['new_password']); } public function test_reset_rejects_mismatched_password_confirmation(): void { $r = $this->post(self::RESET_URL, [ 'code' => self::VALID_CODE, 'new_password' => self::VALID_PASSWORD, 'new_password_confirmation' => 'Outra123', ]); $r->assertSessionHasErrors(['new_password']); } // ────────────────────────────────────────────── // Integração — Etapa 2 (respostas do serviço) // ────────────────────────────────────────────── public function test_reset_redirects_to_login_with_success_when_service_returns_200(): void { Http::fake([ '*/api/password/reset' => Http::response(['message' => 'updated'], JsonResponse::HTTP_OK), ]); $r = $this->post(self::RESET_URL, [ 'code' => self::VALID_CODE, 'new_password' => self::VALID_PASSWORD, 'new_password_confirmation' => self::VALID_PASSWORD, ]); $r->assertRedirect(route(self::LOGIN_ROUTE)); $r->assertSessionHas('success'); } public function test_reset_shows_validation_errors_from_service_on_422(): void { Http::fake([ '*/api/password/reset' => Http::response([ 'errors' => [ 'code' => ['Código inválido ou expirado.'], ], ], JsonResponse::HTTP_UNPROCESSABLE_ENTITY), ]); $r = $this->post(self::RESET_URL, [ 'code' => self::VALID_CODE, 'new_password' => self::VALID_PASSWORD, 'new_password_confirmation' => self::VALID_PASSWORD, ]); $r->assertRedirect(); $r->assertSessionHasErrors(['code']); } public function test_reset_shows_generic_error_when_service_unavailable(): void { Http::fake([ '*/api/password/reset' => Http::response([], JsonResponse::HTTP_SERVICE_UNAVAILABLE), ]); $r = $this->post(self::RESET_URL, [ 'code' => self::VALID_CODE, 'new_password' => self::VALID_PASSWORD, 'new_password_confirmation' => self::VALID_PASSWORD, ]); $r->assertRedirect(); $r->assertSessionHasErrors(['code']); // Não deve redirecionar para /entrar $this->assertStringNotContainsString( route(self::LOGIN_ROUTE), $r->headers->get('Location', '') ); } // ────────────────────────────────────────────── // Ciclo completo E2E (ponta a ponta simulado) // ────────────────────────────────────────────── /** * Simula o ciclo completo descrito no roteiro de homologação: * 1) Solicitação de recuperação → mensagem genérica de envio * 2) Redefinição com código válido → redirect para /entrar com sucesso * * A etapa "receber e-mail e capturar código" não é automatizável; * ela é coberta pelo roteiro manual em docs/talent-bank/password-recovery.md. */ public function test_full_recovery_cycle_send_then_reset_redirects_to_login(): void { Http::fake([ '*/api/password/email' => Http::response(['message' => 'sent'], JsonResponse::HTTP_OK), '*/api/password/reset' => Http::response(['message' => 'updated'], JsonResponse::HTTP_OK), ]); // Etapa 1: solicitar recuperação $sendResponse = $this->post(self::FORGOT_URL, [ 'cpf_cnpj' => self::VALID_CPF, 'email' => self::VALID_EMAIL, ]); $sendResponse->assertRedirect(self::FORGOT_URL); $sendResponse->assertSessionHas('status'); // Etapa 2: redefinir com código recebido (simulado) $resetResponse = $this->post(self::RESET_URL, [ 'code' => self::VALID_CODE, 'new_password' => self::VALID_PASSWORD, 'new_password_confirmation' => self::VALID_PASSWORD, ]); $resetResponse->assertRedirect(route(self::LOGIN_ROUTE)); $resetResponse->assertSessionHas('success'); // Confirma que ambas as chamadas ao serviço de identidade ocorreram Http::assertSent(fn ($req) => str_contains($req->url(), '/api/password/email')); Http::assertSent(fn ($req) => str_contains($req->url(), '/api/password/reset')); } }
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings