7c20b518d4
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>
150 lines
9.2 KiB
SQL
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 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;
|