Files
agenciapsilmno/database-novo/migrations/20260418000004_dev_tests.sql
T
Leonardo 7c20b518d4 Sessoes 1-6 acumuladas: hardening B2, defesa em camadas, +192 testes
Repositorio estava ha ~5 sessoes sem commit. Consolida tudo desde d088a89.

Ver commit.md na raiz para descricao completa por sessao.

# Numeros
- A# auditoria abertos: 0/30
- V# verificacoes abertos: 5/52 (todos adiados com plano)
- T# testes escritos: 10/10
- Vitest: 192/192
- SQL integration: 33/33
- E2E (Playwright, novo): 5/5
- Migrations: 17 (10 novas Sessao 6)
- Areas auditadas: 7 (+documentos com 10 V#)

# Highlights Sessao 6 (hoje)
- V#34/V#41 Opcao B2: tenant_features com plano + override (RPC SECURITY DEFINER, tela /saas/tenant-features)
- A#20 rev2 self-hosted: defesa em 5 camadas (honeypot + rate limit + math captcha condicional + paranoid mode + dashboard /saas/security)
- Documentos hardening (V#43-V#49): tenant scoping em storage policies (vazamento entre clinicas eliminado), RPC validate_share_token, signatures policy granular
- SaaS Twilio Config (/saas/twilio-config): UI editavel para SID/webhook/cotacao; AUTH_TOKEN permanece em env var
- T#9 + T#10: useAgendaEvents.spec.js + Playwright E2E (descobriu bug no front que foi corrigido)

# Sessoes anteriores (1-5) consolidadas
- Sessao 1: auth/router/session, normalizeRole extraido
- Sessao 2: agenda - composables/services consolidados
- Sessao 3: pacientes - tenant_id em todas queries
- Sessao 4: security review pagina publica - 14/15 vulnerabilidades corrigidas
- Sessao 5: SaaS - P0 (A#30: 7 tabelas com RLS off corrigidas)

# .gitignore ajustado
- supabase/* + !supabase/functions/ (mantem 10 edge functions, ignora .temp/migrations gerados pelo CLI)
- database-novo/backups/ (regeneravel via db.cjs backup)
- test-results/ + playwright-report/
- .claude/settings.local.json (config local com senha de dev removida do tracking)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:42:46 -03:00

150 lines
9.2 KiB
SQL

-- =============================================================================
-- 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 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 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+therapisttherapist, tenant_admin+clinicclinic_admin, tenant_admin+supervisorsupervisor, tenant_admin sem kindclinic_admin, clinic_adminclinic_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 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;