-- =============================================================================
-- 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;
