-- ============================================================================= -- Migration: 20260418000004_dev_tests -- Nova aba "Testes" em /saas/desenvolvimento — catálogo de suítes de teste. -- ----------------------------------------------------------------------------- -- Espelha a estrutura de dev_verificacoes_items. Uma linha = uma suíte de -- teste (arquivo .spec.js ou grupo de testes). Serve para responder "quais -- áreas estão cobertas por teste?" sem rodar npm test. -- ============================================================================= CREATE TABLE IF NOT EXISTS public.dev_test_items ( id BIGSERIAL PRIMARY KEY, area VARCHAR(80) NOT NULL, categoria VARCHAR(120), -- unit, integration, e2e, manual titulo TEXT NOT NULL, arquivo TEXT, descricao TEXT, total_tests INTEGER DEFAULT 0, passing INTEGER DEFAULT 0, failing INTEGER DEFAULT 0, skipped INTEGER DEFAULT 0, cobertura_pct NUMERIC(5,2), -- cobertura estimada daquela área status VARCHAR(20) NOT NULL DEFAULT 'ok' CHECK (status IN ('ok','falhando','pendente','obsoleto','a_escrever')), last_run_at TIMESTAMPTZ, sessao_criacao VARCHAR(160), notas TEXT, tags TEXT[] DEFAULT '{}', ordem INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_dev_test_items_area ON public.dev_test_items(area); CREATE INDEX IF NOT EXISTS idx_dev_test_items_status ON public.dev_test_items(status); CREATE INDEX IF NOT EXISTS idx_dev_test_items_ordem ON public.dev_test_items(area, ordem); DROP TRIGGER IF EXISTS trg_dev_test_items_updated_at ON public.dev_test_items; CREATE TRIGGER trg_dev_test_items_updated_at BEFORE UPDATE ON public.dev_test_items FOR EACH ROW EXECUTE FUNCTION public.dev_set_updated_at(); ALTER TABLE public.dev_test_items ENABLE ROW LEVEL SECURITY; DROP POLICY IF EXISTS dev_test_items_saas_admin_all ON public.dev_test_items; CREATE POLICY dev_test_items_saas_admin_all ON public.dev_test_items FOR ALL TO authenticated USING (public.is_saas_admin()) WITH CHECK (public.is_saas_admin()); COMMENT ON TABLE public.dev_test_items IS 'Catálogo de suítes de teste por área. Responde "o que está testado?" sem precisar rodar npm test.'; -- ============================================================================= -- Seed inicial — testes existentes em 2026-04-18 -- ============================================================================= INSERT INTO public.dev_test_items (area, categoria, titulo, arquivo, descricao, total_tests, passing, failing, skipped, cobertura_pct, status, last_run_at, sessao_criacao, notas, tags, ordem) VALUES ('agenda', 'unit', 'useRecurrence — geração de ocorrências', 'src/features/agenda/composables/__tests__/useRecurrence.spec.js', $$Cobre: generateDates (weekly, biweekly, custom_weekdays, monthly, yearly), expandRules com exceções (cancel_session, patient_missed, reschedule_session, holiday_block), mergeWithStoredSessions, max_occurrences, range boundaries, remarcação inbound.$$, 23, 23, 0, 0, NULL, 'ok', '2026-04-18 08:47:00+00', 'Sessão 2 — agenda', 'Suite sólida. Cobre os branches críticos da expansão de recorrência. Testes sobreviveram à adição do cap de range (V#20) e ao filtro de tenant_id nas CRUDs (V#12).', ARRAY['unit','agenda','recurrence','critical'], 1), ('agenda', 'unit', 'agendaMappers — transformação pra FullCalendar', 'src/features/agenda/services/__tests__/agendaMappers.spec.js', $$Cobre: mapAgendaEventosToCalendarEvents (shape, campos extras), status → cor + ícone (agendado, realizado, faltou, cancelado, remarcado), aliases de FK (patients, determined_commitments), tipo fallback, ocorrência virtual (is_occurrence), resource events (clinic mosaic).$$, 40, 40, 0, 0, NULL, 'ok', '2026-04-18 08:47:00+00', 'Sessão 2 — agenda', 'Quatro testes estavam falhando antes do V#21 (status "remarcado" vs "remarcar" + cores faltou/cancelado invertidas). Agora 100%.', ARRAY['unit','agenda','mappers'], 2), ('auth', 'a_escrever', 'guards.js — branches do router beforeEach', 'src/router/__tests__/guards.spec.js (não existe)', $$Deveria cobrir: rotas públicas liberadas, redirect pra /auth/login sem session, área /account sem tenant, saas_admin só em /saas, tenant lockdown, trocaTenantScope, matchesRoles com aliases, cache de globalRole, cache de saasAdmin.$$, 0, 0, 0, 0, NULL, 'a_escrever', NULL, 'Sessão 1 — auth/router', 'guards.js tem ~650 linhas e só roda via navegação real. Sem teste unitário → mudanças no guard são de alto risco. Prioridade média para criar (mock do router + pinia).', ARRAY['unit','auth','router','guard','missing'], 3), ('auth', 'a_escrever', 'session.js — hydrate e race conditions', 'src/app/__tests__/session.spec.js (não existe)', $$Deveria cobrir: initSession com/sem session, refreshSession que não dispara se refreshing, SIGNED_IN redundante ignorado, SIGNED_OUT zera state, TOKEN_REFRESHED não derruba cache, hydrate preserva user em erro.$$, 0, 0, 0, 0, NULL, 'a_escrever', NULL, 'Sessão 1 — auth/router', 'Módulo tem histórico de race conditions (comentado no próprio arquivo). Teste unitário daria garantia contra regressão.', ARRAY['unit','auth','session','race','missing'], 4), ('stores', 'a_escrever', 'tenantStore — singleflight + persist', 'src/stores/__tests__/tenantStore.spec.js (não existe)', $$Deveria cobrir: loadSessionAndTenant com Promise compartilhada (V#3), ensureLoaded sem setInterval, tenant salvo só se pertence ao user, normalizeTenantRole, reset, persistência em localStorage.$$, 0, 0, 0, 0, NULL, 'a_escrever', NULL, 'Sessão 1 — auth/router', 'V#3 trocou polling por Promise singleflight — a correção não tem teste que proteja contra regressão.', ARRAY['unit','store','tenant','missing'], 5), ('utils', 'a_escrever', 'roleNormalizer — saídas esperadas', 'src/utils/__tests__/roleNormalizer.spec.js (não existe)', $$Fácil de testar — função pura, sem IO. Cobre: tenant_admin+therapist→therapist, tenant_admin+clinic→clinic_admin, tenant_admin+supervisor→supervisor, tenant_admin sem kind→clinic_admin, clinic_admin→clinic_admin, pass-through.$$, 0, 0, 0, 0, NULL, 'a_escrever', NULL, 'Sessão 1 — auth/router', 'Criado em V#4. É função pura — fácil de cobrir em 10min. Baixa prioridade técnica mas alto valor simbólico (garantir que os 2 consumidores — guards.js e tenantStore.js — concordam).', ARRAY['unit','utils','trivial'], 6), ('pacientes', 'a_escrever', 'Cadastros externos — fluxo do paciente', 'src/features/patients/__tests__/external-intake.spec.js (não existe)', $$Deveria cobrir: validação client-side (token regex, email, consent), truncation em todos os campos, payload final, não envio de notas_internas, comportamento com token inválido.$$, 0, 0, 0, 0, NULL, 'a_escrever', NULL, 'Sessão 4 — Security Hardening', 'Página pública é ponto crítico de segurança. Teste de regressão importante após A#17/A#18/A#21 — garantir que nenhum dos valores "perigosos" voltem a ser enviados.', ARRAY['unit','pacientes','external','security-regression'], 7), ('database', 'manual', 'RPCs de intake — validação de inputs maliciosos', 'database-novo/tests/test_patient_intake_security.sql (sugerido)', $$Deveria cobrir: token inválido raise, token desativado raise (A#16), token expirado raise, max_uses raise, uses incrementa após sucesso, consent=false raise, payload com notas_internas é ignorado (A#17), tenant_id é preenchido (A#19), nome > 200 chars raise, email inválido raise, genero fora whitelist vira NULL, data_nascimento futura vira NULL.$$, 0, 0, 0, 0, NULL, 'a_escrever', NULL, 'Sessão 4 — Security Hardening', 'Testes SQL diretos via psql. Importantes porque as validações estão dentro do RPC SECURITY DEFINER. Executar antes de cada deploy.', ARRAY['manual','sql','security','rpc'], 8), ('agenda', 'a_escrever', 'useAgendaEvents — wrapper do repository', 'src/features/agenda/composables/__tests__/useAgendaEvents.spec.js (não existe)', $$Deveria cobrir: loadMyRange chama listMyAgendaEvents, estado loading/error transições, sem ownerId retorna cedo, rollback em erro.$$, 0, 0, 0, 0, NULL, 'a_escrever', NULL, 'Sessão 2 — agenda', 'Após refactor V#14 o composable virou fino. Teste garante que continue fino.', ARRAY['unit','agenda','composable','missing'], 9), ('e2e', 'a_escrever', 'Fluxo completo: terapeuta cria link → paciente preenche → terapeuta vê', '(não existe)', $$Deveria cobrir o happy path integrado: login terapeuta, gera link via issue_patient_invite, abre /cadastro/paciente em aba anônima, preenche, submit, terapeuta vê em /therapist/patients/recebidos.$$, 0, 0, 0, 0, NULL, 'a_escrever', NULL, 'Sessão 4 — Security Hardening', 'Não há E2E hoje. Playwright ou Cypress valem? Decidir provider. Alta prioridade pra confiança em deploy.', ARRAY['e2e','critical','missing','decisão-pendente'], 10); SELECT id, area, categoria, status, total_tests, passing FROM public.dev_test_items ORDER BY ordem;