// ============================================================================= // T#10 — Golden path: paciente abre link → preenche → submit → sucesso // ============================================================================= // Strategy: intercepta as chamadas pra Supabase Functions/REST e responde // com mocks. Sem precisar de banco real ou edge functions rodando. // // Cobre as camadas de defesa em camadas (A#20 rev2): // - honeypot (campo invisível, garantimos que não preenchemos) // - submit normal funciona // - 403 captcha-required → componente MathCaptchaChallenge aparece // - 429 rate-limited → toast amigável // - token UUID inválido bloqueia a página // ============================================================================= import { test, expect } from '@playwright/test'; const VALID_TOKEN = '550e8400-e29b-41d4-a716-446655440000'; // UUID v4 válido (TOKEN_RX bate) const PUBLIC_URL = `/cadastro/paciente?t=${VALID_TOKEN}`; const FUNCTIONS_RX = /\/functions\/v1\/submit-patient-intake/; // Helper: preenche os 3 campos obrigatórios + consent async function fillRequired(page, { nome, email, telefone }) { await page.locator('#f_nome').fill(nome); await page.locator('#f_email_principal').fill(email); await page.locator('#f_telefone').fill(telefone); // PrimeVue Checkbox é um div com role=checkbox; .check() não funciona, precisa click await page.locator('label[for="ext_consent"]').click(); } test.describe('Cadastro paciente externo — golden path', () => { test('paciente preenche e submete com sucesso (sem captcha)', async ({ page }) => { await page.route(FUNCTIONS_RX, async (route) => { const body = route.request().postDataJSON(); // honeypot NÃO foi preenchido (humano) expect(body?.website).toBeFalsy(); expect(body?.token).toBe(VALID_TOKEN); expect(body?.payload?.nome_completo).toBe('Maria da Silva'); expect(body?.payload?.consent).toBe(true); await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ ok: true, intake_id: 'fake-intake-uuid' }) }); }); await page.goto(PUBLIC_URL); await expect(page.getByText('Pré-cadastro do paciente')).toBeVisible(); await fillRequired(page, { nome: 'Maria da Silva', email: 'maria@test.com', telefone: '11987654321' }); await page.getByRole('button', { name: 'Enviar cadastro', exact: true }).click(); await expect(page.getByText(/Enviado com sucesso/i).first()).toBeVisible({ timeout: 10_000 }); }); test('rate limit (429) mostra mensagem amigável de tentativas', async ({ page }) => { await page.route(FUNCTIONS_RX, async (route) => { await route.fulfill({ status: 429, contentType: 'application/json', body: JSON.stringify({ error: 'rate-limited', retry_after_seconds: 600 }) }); }); await page.goto(PUBLIC_URL); await expect(page.getByText('Pré-cadastro do paciente')).toBeVisible(); await fillRequired(page, { nome: 'João Bot', email: 'joao@test.com', telefone: '11999999999' }); await page.getByRole('button', { name: 'Enviar cadastro', exact: true }).click(); await expect(page.getByText(/Muitas tentativas/i)).toBeVisible({ timeout: 6_000 }); }); test('captcha-required mostra MathCaptchaChallenge e bloqueia botão até resposta', async ({ page }) => { let firstSubmitCall = true; await page.route(FUNCTIONS_RX, async (route) => { const url = route.request().url(); if (url.includes('/captcha-challenge')) { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ challenge: { id: 'fake-challenge-id', question: 'Quanto é 2 + 3?' } }) }); return; } if (firstSubmitCall) { firstSubmitCall = false; await route.fulfill({ status: 403, contentType: 'application/json', body: JSON.stringify({ error: 'captcha-required' }) }); return; } // segunda call: ok await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ ok: true, intake_id: 'after-captcha' }) }); }); await page.goto(PUBLIC_URL); await expect(page.getByText('Pré-cadastro do paciente')).toBeVisible(); await fillRequired(page, { nome: 'Ana Pereira', email: 'ana@test.com', telefone: '11955554444' }); await page.getByRole('button', { name: 'Enviar cadastro', exact: true }).click(); // MathCaptchaChallenge aparece await expect(page.getByText('Quanto é 2 + 3?')).toBeVisible({ timeout: 8_000 }); // Botão "Enviar cadastro" do footer fica disabled enquanto sem resposta await expect(page.getByRole('button', { name: 'Enviar cadastro', exact: true })).toBeDisabled(); }); test('honeypot field existe no DOM mas está fora da viewport', async ({ page }) => { await page.goto(PUBLIC_URL); await expect(page.getByText('Pré-cadastro do paciente')).toBeVisible(); const honeypot = page.locator('input#ext_website'); await expect(honeypot).toHaveCount(1); await expect(honeypot).not.toBeInViewport(); }); test('token inválido bloqueia toda a página de cadastro', async ({ page }) => { await page.goto('/cadastro/paciente?t=token-invalido'); // Mensagem de erro aparece await expect(page.getByText(/Link inválido ou ausente/i)).toBeVisible(); // Form não renderiza (botão Enviar não existe) await expect(page.getByRole('button', { name: 'Enviar cadastro', exact: true })).toHaveCount(0); }); });