@luanpdd/kit-mcp 1.30.1 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +168 -168
- package/gates/agent-no-recursive-dispatch.md +84 -82
- package/kit/COMANDOS.md +138 -138
- package/kit/README.md +76 -76
- package/kit/agents/advisor-researcher.md +107 -106
- package/kit/agents/ai-mutation-tester.md +1 -0
- package/kit/agents/assumptions-analyzer.md +108 -107
- package/kit/agents/audit-log-implementer.md +314 -313
- package/kit/agents/auditor-consistencia-isolamento.md +414 -413
- package/kit/agents/b2b-saas-architect.md +157 -156
- package/kit/agents/burn-rate-forecaster.md +1 -0
- package/kit/agents/cascading-failures-auditor.md +299 -298
- package/kit/agents/codebase-mapper.md +769 -768
- package/kit/agents/crm-pipeline-implementer.md +257 -256
- package/kit/agents/debugger.md +814 -813
- package/kit/agents/detector-tenant-quente.md +338 -337
- package/kit/agents/evolution-go-integrator.md +201 -200
- package/kit/agents/example-reviewer.md +22 -21
- package/kit/agents/executor.md +565 -564
- package/kit/agents/golden-signals-instrumenter.md +1 -0
- package/kit/agents/incident-investigator.md +1 -0
- package/kit/agents/integration-checker.md +201 -200
- package/kit/agents/invite-flow-implementer.md +190 -189
- package/kit/agents/legacy-characterizer.md +369 -368
- package/kit/agents/lgpd-compliance-auditor.md +296 -295
- package/kit/agents/load-shedding-instrumenter.md +1 -0
- package/kit/agents/multi-tenant-isolation-auditor.md +254 -253
- package/kit/agents/multi-tenant-rls-writer.md +341 -340
- package/kit/agents/nyquist-auditor.md +179 -178
- package/kit/agents/observability-coverage-auditor.md +316 -315
- package/kit/agents/observability-instrumenter.md +1 -0
- package/kit/agents/omm-auditor.md +1 -0
- package/kit/agents/org-onboarding-implementer.md +224 -223
- package/kit/agents/payload-capture-instrumenter.md +274 -273
- package/kit/agents/phase-researcher.md +697 -696
- package/kit/agents/plan-checker.md +273 -272
- package/kit/agents/planner.md +923 -922
- package/kit/agents/postmortem-writer.md +1 -0
- package/kit/agents/project-researcher.md +653 -652
- package/kit/agents/prr-conductor.md +1 -0
- package/kit/agents/refactor-safety-auditor.md +405 -404
- package/kit/agents/release-pipeline-auditor.md +1 -0
- package/kit/agents/research-synthesizer.md +246 -245
- package/kit/agents/roadmapper.md +678 -677
- package/kit/agents/schema-checker.md +1 -0
- package/kit/agents/seam-finder.md +360 -359
- package/kit/agents/shotgun-surgery-detector.md +350 -349
- package/kit/agents/slo-engineer.md +1 -0
- package/kit/agents/storytelling-analyst.md +1 -0
- package/kit/agents/supabase-architect.md +1 -0
- package/kit/agents/supabase-auth-bootstrapper.md +1 -0
- package/kit/agents/supabase-branching-architect.md +563 -562
- package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -777
- package/kit/agents/supabase-column-privileges-writer.md +400 -399
- package/kit/agents/supabase-edge-fn-tester.md +2 -1
- package/kit/agents/supabase-edge-fn-writer.md +2 -1
- package/kit/agents/supabase-migration-writer.md +386 -385
- package/kit/agents/supabase-rbac-implementer.md +393 -392
- package/kit/agents/supabase-realtime-implementer.md +364 -363
- package/kit/agents/supabase-rls-hardener.md +522 -521
- package/kit/agents/supabase-rls-writer.md +324 -323
- package/kit/agents/supabase-roles-implementer.md +356 -355
- package/kit/agents/supabase-storage-implementer.md +1 -0
- package/kit/agents/super-admin-implementer.md +282 -281
- package/kit/agents/toil-auditor.md +1 -0
- package/kit/agents/ui-auditor.md +438 -437
- package/kit/agents/ui-checker.md +303 -302
- package/kit/agents/ui-researcher.md +356 -355
- package/kit/agents/user-profiler.md +176 -175
- package/kit/agents/validador-evolucao-schema.md +336 -335
- package/kit/agents/verifier.md +729 -728
- package/kit/commands/adicionar-backlog.md +75 -75
- package/kit/commands/adicionar-fase.md +42 -42
- package/kit/commands/adicionar-tarefa.md +45 -45
- package/kit/commands/adicionar-testes.md +41 -41
- package/kit/commands/ajuda.md +21 -21
- package/kit/commands/atualizar.md +37 -37
- package/kit/commands/auditar-cascading.md +111 -111
- package/kit/commands/auditar-marco.md +179 -179
- package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
- package/kit/commands/auditar-refactor.md +219 -219
- package/kit/commands/auditar-release.md +109 -109
- package/kit/commands/auditar-uat.md +23 -23
- package/kit/commands/autonomo.md +40 -40
- package/kit/commands/branch-pr.md +24 -24
- package/kit/commands/burn-rate-status.md +408 -408
- package/kit/commands/capturar-payloads.md +193 -193
- package/kit/commands/caracterizar.md +212 -212
- package/kit/commands/concluir-marco.md +247 -247
- package/kit/commands/configuracoes.md +36 -36
- package/kit/commands/dados-distribuidos.md +188 -188
- package/kit/commands/definir-perfil.md +10 -10
- package/kit/commands/depurar.md +190 -190
- package/kit/commands/detectar-duplicacao.md +197 -197
- package/kit/commands/discutir-fase.md +131 -131
- package/kit/commands/encontrar-seams.md +136 -136
- package/kit/commands/entrar-discord.md +17 -17
- package/kit/commands/estatisticas.md +18 -18
- package/kit/commands/example-greeting.md +33 -33
- package/kit/commands/executar-fase.md +58 -58
- package/kit/commands/expresso.md +56 -56
- package/kit/commands/fase-ui.md +34 -34
- package/kit/commands/fazer.md +57 -57
- package/kit/commands/fio.md +125 -125
- package/kit/commands/fluxos-trabalho.md +64 -64
- package/kit/commands/forense.md +176 -176
- package/kit/commands/gerenciador.md +38 -38
- package/kit/commands/inserir-fase.md +31 -31
- package/kit/commands/legacy.md +263 -263
- package/kit/commands/limpeza.md +17 -17
- package/kit/commands/listar-hipoteses-fase.md +45 -45
- package/kit/commands/listar-workspaces.md +18 -18
- package/kit/commands/load-shedding.md +117 -117
- package/kit/commands/mapear-codebase.md +70 -70
- package/kit/commands/multi-tenant.md +163 -163
- package/kit/commands/nota.md +33 -33
- package/kit/commands/novo-marco.md +43 -43
- package/kit/commands/novo-projeto.md +41 -41
- package/kit/commands/novo-workspace.md +43 -43
- package/kit/commands/pausar-trabalho.md +37 -37
- package/kit/commands/perfil-usuario.md +45 -45
- package/kit/commands/pesquisar-fase.md +195 -195
- package/kit/commands/planejar-fase.md +67 -67
- package/kit/commands/planejar-lacunas.md +33 -33
- package/kit/commands/plantar-ideia.md +25 -25
- package/kit/commands/progresso.md +24 -24
- package/kit/commands/proximo.md +30 -30
- package/kit/commands/publicar.md +490 -490
- package/kit/commands/rapido.md +35 -35
- package/kit/commands/reaplicar-patches.md +124 -124
- package/kit/commands/refactor-seguro.md +321 -321
- package/kit/commands/relatorio-sessao.md +19 -19
- package/kit/commands/remover-fase.md +31 -31
- package/kit/commands/remover-workspace.md +26 -26
- package/kit/commands/resumo-marco.md +50 -50
- package/kit/commands/retomar-trabalho.md +40 -40
- package/kit/commands/revisar-backlog.md +60 -60
- package/kit/commands/revisar-ui.md +32 -32
- package/kit/commands/revisar.md +37 -37
- package/kit/commands/saude.md +21 -21
- package/kit/commands/setup-notion.md +93 -93
- package/kit/commands/storytelling.md +179 -179
- package/kit/commands/sync-main.md +68 -68
- package/kit/commands/validar-fase.md +35 -35
- package/kit/commands/verificar-tarefas.md +44 -44
- package/kit/commands/verificar-trabalho.md +64 -64
- package/kit/file-manifest.json +82 -81
- package/kit/framework/bin/lib/commands.cjs +959 -959
- package/kit/framework/bin/lib/config.cjs +442 -442
- package/kit/framework/bin/lib/core.cjs +1230 -1230
- package/kit/framework/bin/lib/frontmatter.cjs +336 -336
- package/kit/framework/bin/lib/init.cjs +1442 -1442
- package/kit/framework/bin/lib/milestone.cjs +252 -252
- package/kit/framework/bin/lib/model-profiles.cjs +68 -68
- package/kit/framework/bin/lib/phase.cjs +888 -888
- package/kit/framework/bin/lib/profile-output.cjs +952 -952
- package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
- package/kit/framework/bin/lib/roadmap.cjs +329 -329
- package/kit/framework/bin/lib/security.cjs +382 -382
- package/kit/framework/bin/lib/state.cjs +1031 -1031
- package/kit/framework/bin/lib/template.cjs +222 -222
- package/kit/framework/bin/lib/uat.cjs +282 -282
- package/kit/framework/bin/lib/verify.cjs +888 -888
- package/kit/framework/bin/lib/workstream.cjs +491 -491
- package/kit/framework/bin/tools.cjs +918 -918
- package/kit/framework/commands/workstreams.md +63 -63
- package/kit/framework/references/checkpoints.md +778 -778
- package/kit/framework/references/continuation-format.md +249 -249
- package/kit/framework/references/decimal-phase-calculation.md +64 -64
- package/kit/framework/references/git-integration.md +295 -295
- package/kit/framework/references/git-planning-commit.md +38 -38
- package/kit/framework/references/model-profile-resolution.md +36 -36
- package/kit/framework/references/model-profiles.md +139 -139
- package/kit/framework/references/phase-argument-parsing.md +61 -61
- package/kit/framework/references/planning-config.md +202 -202
- package/kit/framework/references/questioning.md +162 -162
- package/kit/framework/references/tdd.md +263 -263
- package/kit/framework/references/ui-brand.md +160 -160
- package/kit/framework/references/user-profiling.md +657 -657
- package/kit/framework/references/verification-patterns.md +612 -612
- package/kit/framework/references/workstream-flag.md +58 -58
- package/kit/framework/templates/DEBUG.md +164 -164
- package/kit/framework/templates/UAT.md +265 -265
- package/kit/framework/templates/UI-SPEC.md +100 -100
- package/kit/framework/templates/VALIDATION.md +76 -76
- package/kit/framework/templates/claude-md.md +122 -122
- package/kit/framework/templates/codebase/architecture.md +185 -185
- package/kit/framework/templates/codebase/concerns.md +205 -205
- package/kit/framework/templates/codebase/conventions.md +204 -204
- package/kit/framework/templates/codebase/integrations.md +192 -192
- package/kit/framework/templates/codebase/stack.md +158 -158
- package/kit/framework/templates/codebase/structure.md +199 -199
- package/kit/framework/templates/codebase/testing.md +301 -301
- package/kit/framework/templates/config.json +44 -44
- package/kit/framework/templates/context.md +352 -352
- package/kit/framework/templates/continue-here.md +78 -78
- package/kit/framework/templates/copilot-instructions.md +7 -7
- package/kit/framework/templates/debug-subagent-prompt.md +91 -91
- package/kit/framework/templates/dev-preferences.md +20 -20
- package/kit/framework/templates/discovery.md +146 -146
- package/kit/framework/templates/discussion-log.md +63 -63
- package/kit/framework/templates/milestone-archive.md +123 -123
- package/kit/framework/templates/milestone.md +115 -115
- package/kit/framework/templates/phase-prompt.md +610 -610
- package/kit/framework/templates/planner-subagent-prompt.md +117 -117
- package/kit/framework/templates/project.md +186 -186
- package/kit/framework/templates/requirements.md +231 -231
- package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
- package/kit/framework/templates/research-project/FEATURES.md +147 -147
- package/kit/framework/templates/research-project/PITFALLS.md +200 -200
- package/kit/framework/templates/research-project/STACK.md +120 -120
- package/kit/framework/templates/research-project/SUMMARY.md +170 -170
- package/kit/framework/templates/research.md +419 -419
- package/kit/framework/templates/retrospective.md +54 -54
- package/kit/framework/templates/roadmap.md +202 -202
- package/kit/framework/templates/state.md +176 -176
- package/kit/framework/templates/summary-complex.md +59 -59
- package/kit/framework/templates/summary-minimal.md +41 -41
- package/kit/framework/templates/summary-standard.md +48 -48
- package/kit/framework/templates/summary.md +209 -209
- package/kit/framework/templates/user-profile.md +146 -146
- package/kit/framework/templates/user-setup.md +256 -256
- package/kit/framework/templates/verification-report.md +258 -258
- package/kit/framework/workflows/add-phase.md +112 -112
- package/kit/framework/workflows/add-tests.md +351 -351
- package/kit/framework/workflows/add-todo.md +158 -158
- package/kit/framework/workflows/audit-milestone.md +340 -340
- package/kit/framework/workflows/audit-uat.md +109 -109
- package/kit/framework/workflows/autonomous.md +891 -891
- package/kit/framework/workflows/check-todos.md +177 -177
- package/kit/framework/workflows/cleanup.md +152 -152
- package/kit/framework/workflows/complete-milestone.md +696 -696
- package/kit/framework/workflows/diagnose-issues.md +231 -231
- package/kit/framework/workflows/discovery-phase.md +289 -289
- package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
- package/kit/framework/workflows/discuss-phase.md +784 -784
- package/kit/framework/workflows/do.md +104 -104
- package/kit/framework/workflows/execute-phase.md +838 -838
- package/kit/framework/workflows/execute-plan.md +510 -510
- package/kit/framework/workflows/fast.md +102 -102
- package/kit/framework/workflows/forensics.md +265 -265
- package/kit/framework/workflows/health.md +181 -181
- package/kit/framework/workflows/help.md +619 -619
- package/kit/framework/workflows/insert-phase.md +130 -130
- package/kit/framework/workflows/list-phase-assumptions.md +178 -178
- package/kit/framework/workflows/list-workspaces.md +56 -56
- package/kit/framework/workflows/manager.md +362 -362
- package/kit/framework/workflows/map-codebase.md +377 -377
- package/kit/framework/workflows/milestone-summary.md +223 -223
- package/kit/framework/workflows/new-milestone.md +486 -486
- package/kit/framework/workflows/new-project.md +1159 -1159
- package/kit/framework/workflows/new-workspace.md +237 -237
- package/kit/framework/workflows/next.md +97 -97
- package/kit/framework/workflows/node-repair.md +92 -92
- package/kit/framework/workflows/note.md +156 -156
- package/kit/framework/workflows/pause-work.md +176 -176
- package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
- package/kit/framework/workflows/plan-phase.md +765 -765
- package/kit/framework/workflows/plant-seed.md +169 -169
- package/kit/framework/workflows/pr-branch.md +129 -129
- package/kit/framework/workflows/profile-user.md +450 -450
- package/kit/framework/workflows/progress.md +507 -507
- package/kit/framework/workflows/quick.md +757 -757
- package/kit/framework/workflows/remove-phase.md +155 -155
- package/kit/framework/workflows/remove-workspace.md +90 -90
- package/kit/framework/workflows/research-phase.md +82 -82
- package/kit/framework/workflows/resume-project.md +326 -326
- package/kit/framework/workflows/review.md +228 -228
- package/kit/framework/workflows/session-report.md +146 -146
- package/kit/framework/workflows/settings.md +283 -283
- package/kit/framework/workflows/ship.md +228 -228
- package/kit/framework/workflows/stats.md +60 -60
- package/kit/framework/workflows/transition.md +671 -671
- package/kit/framework/workflows/ui-phase.md +302 -302
- package/kit/framework/workflows/ui-review.md +165 -165
- package/kit/framework/workflows/update.md +323 -323
- package/kit/framework/workflows/validate-phase.md +174 -174
- package/kit/framework/workflows/verify-phase.md +252 -252
- package/kit/framework/workflows/verify-work.md +637 -637
- package/kit/hooks/check-update.js +118 -118
- package/kit/hooks/context-monitor.js +163 -163
- package/kit/hooks/kit-attribution-reminder.cjs +30 -36
- package/kit/hooks/kit-router.cjs +137 -0
- package/kit/hooks/prompt-guard.js +103 -103
- package/kit/hooks/statusline.js +125 -125
- package/kit/hooks/workflow-guard.js +101 -101
- package/kit/settings.json +45 -45
- package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
- package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
- package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
- package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
- package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
- package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
- package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
- package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
- package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
- package/kit/skills/example-skill/SKILL.md +42 -42
- package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
- package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
- package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
- package/kit/skills/legacy-extract-class/SKILL.md +203 -203
- package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
- package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
- package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
- package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
- package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
- package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
- package/kit/skills/member-invite-flow/SKILL.md +305 -305
- package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
- package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
- package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
- package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
- package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
- package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
- package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
- package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
- package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
- package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
- package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
- package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
- package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
- package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
- package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
- package/kit/skills/supabase-edge-functions/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-auth/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-limits/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +1 -1
- package/kit/skills/supabase-edge-functions-testing/SKILL.md +1 -1
- package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +1 -1
- package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
- package/kit/skills/supabase-migrations/SKILL.md +297 -297
- package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
- package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
- package/kit/skills/supabase-realtime/SKILL.md +460 -460
- package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
- package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
- package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
- package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
- package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
- package/package.json +1 -1
- package/src/core/kit.js +216 -216
- package/src/core/reflect.js +247 -247
- package/src/core/reverse-sync.js +372 -372
- package/src/core/sync.js +437 -418
- package/src/core/watch.js +121 -121
- package/src/mcp-server/index.js +794 -715
|
@@ -1,1053 +1,1053 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: supabase-pgtap-testing
|
|
3
|
-
description: Use ao escrever testes de database/Edge Function em Supabase — pgTAP extension (TAP…
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Supabase — pgTAP & Deno Testing
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
Esta skill cobre **testing-shift-left para Supabase** — testar schema, RLS, PG functions e Edge Functions ANTES de deploy via CI. Dois runners canônicos:
|
|
11
|
-
|
|
12
|
-
1. **pgTAP** (`supabase test db`) — database tests via TAP (Test Anything Protocol) Postgres extension
|
|
13
|
-
2. **Deno test** (`deno test --allow-all`) — Edge Functions tests via Deno runtime nativo
|
|
14
|
-
|
|
15
|
-
Trigger phrases:
|
|
16
|
-
|
|
17
|
-
- "testes Supabase", "test Supabase", "testing Supabase"
|
|
18
|
-
- "pgTAP", "supabase test db", "TAP Postgres extension"
|
|
19
|
-
- "plan ok is throws_ok results_eq pgTAP", "has_table has_column col_type_is"
|
|
20
|
-
- "Deno test Edge Function", "deno test --allow-all"
|
|
21
|
-
- "testar RLS Supabase", "characterization tests PG function"
|
|
22
|
-
- "supabase/tests/", "supabase/functions/_tests/"
|
|
23
|
-
- "testar trigger Postgres", "testar function Postgres"
|
|
24
|
-
- ".env.local Deno tests", "SUPABASE_URL SUPABASE_ANON_KEY tests"
|
|
25
|
-
- "characterization PG legado", "refatorar function Postgres com tests"
|
|
26
|
-
|
|
27
|
-
**Use APENAS para:**
|
|
28
|
-
|
|
29
|
-
- Validar schema, columns, tipos, FKs via pgTAP (`has_table`, `has_column`, `col_type_is`)
|
|
30
|
-
- Validar RLS policies retornando `permission denied` quando esperado (`throws_ok 42501`)
|
|
31
|
-
- Validar PG functions retornando outputs esperados (`results_eq`)
|
|
32
|
-
- Validar constraints, triggers, defaults (`throws_ok 23502 not_null_violation`, `throws_ok 23505 unique_violation`)
|
|
33
|
-
- Testar Edge Functions HTTP handlers (status, response body, side effects)
|
|
34
|
-
- Capturar comportamento atual de PG function legada como **characterization oracle** (Feathers cap 13)
|
|
35
|
-
- Integrar com workflow CI `database-tests.yml` (Phase 151 CI-05) e `functions-tests.yml` (CI-06)
|
|
36
|
-
|
|
37
|
-
**NÃO use para:**
|
|
38
|
-
|
|
39
|
-
- Substituir integration tests no app layer (frontend ↔ Supabase via client SDK) — pgTAP cobre só DB; Deno tests cobrem só Edge Function isolado
|
|
40
|
-
- Performance tests (latência, throughput) — pgTAP é assertion-based, não benchmark; use `pgbench` ou Vegeta para load testing
|
|
41
|
-
- Tests em produção sem `rollback` — sempre `begin; ... rollback;` (cross-ref Anti-pattern 1)
|
|
42
|
-
- Substituir auditoria via `auditor-consistencia-isolamento` (v1.22) — tests validam comportamento esperado; auditor detecta anti-patterns estáticos no SQL
|
|
43
|
-
- Substituir mutation testing (`ai-mutation-tester`) — pgTAP é characterization; mutation testing valida que tests realmente detectam regressão
|
|
44
|
-
|
|
45
|
-
## Princípio canônico
|
|
46
|
-
|
|
47
|
-
Três princípios canônicos:
|
|
48
|
-
|
|
49
|
-
1. **Testing shift-left.** Cada PR valida schema + RLS + PG functions + Edge Functions ANTES do merge. Gate canônico = required status check `database-tests / build` e `functions-tests / build` enforced via branch protection (cross-ref skill `supabase-ci-cd-github-actions` Phase 151 patterns 5 e 6).
|
|
50
|
-
|
|
51
|
-
2. **Tests são schema mutations transacionais.** Todo teste pgTAP roda dentro de `begin; ... rollback;` — assertions executam, fixtures são inseridas, RLS é exercitada, mas DB volta ao estado original. Nunca polui DB compartilhado; tests podem rodar em paralelo sem race.
|
|
52
|
-
|
|
53
|
-
3. **pgTAP é o oracle imutável para refactor de PG legado.** Quando PG function complexa (> 100 linhas, sem tests) precisa ser refatorada, pgTAP captura comportamento atual como characterization (Feathers cap 13). Tests viram oracle congelado — qualquer mudança que altere comportamento previamente capturado falha o build. Sem characterization, refactor de PG é "edit and pray" (cross-ref skill `legacy-characterization-tests` v1.16).
|
|
54
|
-
|
|
55
|
-
### Distinção canônica vs outros runners
|
|
56
|
-
|
|
57
|
-
| | pgTAP (`supabase test db`) | Deno test (`deno test`) | Vitest/Jest (app layer) |
|
|
58
|
-
|---|---|---|---|
|
|
59
|
-
| Escopo | Schema, RLS, PG functions, triggers | Edge Functions HTTP handlers | Frontend + cliente Supabase |
|
|
60
|
-
| Linguagem | SQL (TAP syntax) | TypeScript (Deno runtime) | TypeScript (Node runtime) |
|
|
61
|
-
| Isolamento | `begin; ... rollback;` (atomic) | Function call por test | Mocked client OR real (E2E) |
|
|
62
|
-
| Onde roda | Postgres local (via `supabase start`) | Deno runtime + Postgres local | Node + browser environment |
|
|
63
|
-
| CI workflow | `database-tests.yml` (Phase 151 CI-05) | `functions-tests.yml` (CI-06) | Custom `vitest.yml` ou `jest.yml` |
|
|
64
|
-
| Trigger canônico | PR (gate) | PR (gate) | PR + post-merge (canary) |
|
|
65
|
-
|
|
66
|
-
Os três runners são **complementares** — pgTAP valida DB, Deno valida Edge Function HTTP layer, Vitest/Jest valida cliente. Nenhum substitui os outros.
|
|
67
|
-
|
|
68
|
-
## Pattern 1: pgTAP extension setup + sintaxe canônica (TEST-01)
|
|
69
|
-
|
|
70
|
-
### Habilitar pgTAP
|
|
71
|
-
|
|
72
|
-
Em migration dedicada (uma única vez por projeto):
|
|
73
|
-
|
|
74
|
-
```sql
|
|
75
|
-
-- supabase/migrations/YYYYMMDDHHmmss_enable_pgtap.sql
|
|
76
|
-
create extension if not exists pgtap with schema extensions;
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
**Por que `with schema extensions`:** convenção Supabase canônica — todas extensions ficam em schema dedicado (`extensions`), separadas de `public`. Permite namespace isolation + GRANT EXECUTE granular.
|
|
80
|
-
|
|
81
|
-
### Diretório canônico `supabase/tests/`
|
|
82
|
-
|
|
83
|
-
Supabase CLI busca automaticamente arquivos `*.sql` em `supabase/tests/` quando executa `supabase test db`:
|
|
84
|
-
|
|
85
|
-
```text
|
|
86
|
-
supabase/
|
|
87
|
-
├── migrations/
|
|
88
|
-
│ ├── 20260101000000_initial_schema.sql
|
|
89
|
-
│ └── 20260102000000_enable_pgtap.sql
|
|
90
|
-
├── tests/
|
|
91
|
-
│ ├── employees_test.sql # ← descoberto automaticamente
|
|
92
|
-
│ ├── rls_organizations_test.sql # ← descoberto automaticamente
|
|
93
|
-
│ └── triggers_audit_test.sql # ← descoberto automaticamente
|
|
94
|
-
└── functions/
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Naming convention: `<entity>_test.sql` ou `<concern>_test.sql` — nome descritivo + sufixo `_test.sql`. CLI roda em ordem alfabética.
|
|
98
|
-
|
|
99
|
-
### Estrutura canônica de um teste pgTAP
|
|
100
|
-
|
|
101
|
-
```sql
|
|
102
|
-
-- supabase/tests/employees_test.sql
|
|
103
|
-
begin; -- inicia transação
|
|
104
|
-
select plan(4); -- declara 4 testes (CRÍTICO — sem plan, falha silencioso)
|
|
105
|
-
|
|
106
|
-
-- Test 1: tabela existe
|
|
107
|
-
select has_table('public', 'employees', 'employees table should exist');
|
|
108
|
-
|
|
109
|
-
-- Test 2: coluna existe com tipo correto
|
|
110
|
-
select has_column('public', 'employees', 'name', 'name column should exist');
|
|
111
|
-
select col_type_is('public', 'employees', 'name', 'text', 'name should be text');
|
|
112
|
-
|
|
113
|
-
-- Test 3: comportamento de seed
|
|
114
|
-
select results_eq(
|
|
115
|
-
'select count(*) from public.employees',
|
|
116
|
-
'values (3::bigint)',
|
|
117
|
-
'employees count should be 3 after seed'
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
-- Test 4: constraint NOT NULL
|
|
121
|
-
select throws_ok(
|
|
122
|
-
'insert into public.employees (name) values (null)',
|
|
123
|
-
'23502', -- SQLSTATE not_null_violation
|
|
124
|
-
'null in name should violate not-null constraint'
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
select * from finish(); -- finaliza plano (gera summary TAP)
|
|
128
|
-
rollback; -- desfaz qualquer side effect
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
**Crítico:** `plan(N)` declara o número exato de assertions executadas. Se você roda 5 mas declarou 4, pgTAP reporta `# Looks like you planned 4 tests but ran 5.` — teste falha. Se rodar menos do que planejou, idem.
|
|
132
|
-
|
|
133
|
-
### Funções pgTAP canônicas
|
|
134
|
-
|
|
135
|
-
#### Schema/structure assertions
|
|
136
|
-
|
|
137
|
-
```sql
|
|
138
|
-
-- tabela existe
|
|
139
|
-
select has_table('public', 'orders', 'orders table exists');
|
|
140
|
-
|
|
141
|
-
-- tabela NÃO existe
|
|
142
|
-
select hasnt_table('public', 'old_table', 'old_table was dropped');
|
|
143
|
-
|
|
144
|
-
-- coluna existe
|
|
145
|
-
select has_column('public', 'orders', 'total', 'total column exists');
|
|
146
|
-
|
|
147
|
-
-- tipo da coluna
|
|
148
|
-
select col_type_is('public', 'orders', 'total', 'numeric(10,2)', 'total is numeric(10,2)');
|
|
149
|
-
|
|
150
|
-
-- NOT NULL
|
|
151
|
-
select col_not_null('public', 'orders', 'customer_id', 'customer_id is NOT NULL');
|
|
152
|
-
|
|
153
|
-
-- DEFAULT
|
|
154
|
-
select col_has_default('public', 'orders', 'created_at', 'created_at has default');
|
|
155
|
-
|
|
156
|
-
-- PRIMARY KEY
|
|
157
|
-
select has_pk('public', 'orders', 'orders has primary key');
|
|
158
|
-
|
|
159
|
-
-- FOREIGN KEY
|
|
160
|
-
select has_fk('public', 'orders', 'orders.customer_id references customers');
|
|
161
|
-
|
|
162
|
-
-- INDEX
|
|
163
|
-
select has_index('public', 'orders', 'orders_customer_id_idx', 'index exists');
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
#### Function/trigger assertions
|
|
167
|
-
|
|
168
|
-
```sql
|
|
169
|
-
-- function existe
|
|
170
|
-
select has_function('public', 'calculate_total', array['uuid'], 'function exists');
|
|
171
|
-
|
|
172
|
-
-- return type
|
|
173
|
-
select function_returns('public', 'calculate_total', 'numeric', 'returns numeric');
|
|
174
|
-
|
|
175
|
-
-- trigger existe
|
|
176
|
-
select has_trigger('public', 'orders', 'set_updated_at', 'trigger exists');
|
|
177
|
-
|
|
178
|
-
-- enum value
|
|
179
|
-
select has_enum('public', 'order_status', array['pending', 'paid', 'shipped'], 'enum values');
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
#### Behavioral assertions
|
|
183
|
-
|
|
184
|
-
```sql
|
|
185
|
-
-- equality (simple)
|
|
186
|
-
select is(1 + 1, 2, '1+1 equals 2');
|
|
187
|
-
|
|
188
|
-
-- inequality
|
|
189
|
-
select isnt(now(), '2020-01-01'::timestamp, 'now is not 2020');
|
|
190
|
-
|
|
191
|
-
-- boolean check
|
|
192
|
-
select ok(exists(select 1 from public.users where id = '...'), 'user exists');
|
|
193
|
-
|
|
194
|
-
-- equality (query result)
|
|
195
|
-
select results_eq(
|
|
196
|
-
'select id, name from public.users where active = true order by id limit 2',
|
|
197
|
-
$$values ('uuid-1'::uuid, 'Alice'), ('uuid-2'::uuid, 'Bob')$$,
|
|
198
|
-
'active users are Alice and Bob'
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
-- inequality (query result)
|
|
202
|
-
select results_ne(
|
|
203
|
-
'select count(*) from public.deleted_users',
|
|
204
|
-
'values (0::bigint)',
|
|
205
|
-
'deleted_users is not empty'
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
-- subset (query result is subset)
|
|
209
|
-
select set_eq(
|
|
210
|
-
'select email from public.users',
|
|
211
|
-
$$values ('a@x.com'), ('b@x.com'), ('c@x.com')$$,
|
|
212
|
-
'all users have correct emails'
|
|
213
|
-
);
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
#### Error/exception assertions
|
|
217
|
-
|
|
218
|
-
```sql
|
|
219
|
-
-- erro específico
|
|
220
|
-
select throws_ok(
|
|
221
|
-
'insert into public.employees (name) values (null)',
|
|
222
|
-
'23502', -- SQLSTATE not_null_violation
|
|
223
|
-
'null name should fail not-null constraint'
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
-- error matching pattern
|
|
227
|
-
select throws_like(
|
|
228
|
-
'select * from public.private_table',
|
|
229
|
-
'%permission denied%',
|
|
230
|
-
'cross-org SELECT raises permission denied'
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
-- NÃO lança erro
|
|
234
|
-
select lives_ok(
|
|
235
|
-
$$insert into public.employees (name, email) values ('Alice', 'alice@x.com')$$,
|
|
236
|
-
'valid insert should succeed'
|
|
237
|
-
);
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
#### Custom test helpers — fixtures
|
|
241
|
-
|
|
242
|
-
```sql
|
|
243
|
-
-- helper para criar user JWT context
|
|
244
|
-
create or replace function tests.set_jwt_claim(claim_name text, claim_value text)
|
|
245
|
-
returns void
|
|
246
|
-
language sql
|
|
247
|
-
as $$
|
|
248
|
-
select set_config('request.jwt.claim.' || claim_name, claim_value, true);
|
|
249
|
-
$$;
|
|
250
|
-
|
|
251
|
-
-- uso em teste
|
|
252
|
-
select tests.set_jwt_claim('sub', 'uuid-of-user-1');
|
|
253
|
-
select tests.set_jwt_claim('user_role', 'admin');
|
|
254
|
-
|
|
255
|
-
-- agora policies que consultam auth.uid() retornam 'uuid-of-user-1'
|
|
256
|
-
select results_eq(
|
|
257
|
-
'select count(*) from public.posts',
|
|
258
|
-
'values (10::bigint)', -- esperando 10 posts visíveis para este user
|
|
259
|
-
'user-1 admin sees all posts'
|
|
260
|
-
);
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### SQLSTATE codes canônicos para `throws_ok`
|
|
264
|
-
|
|
265
|
-
| Code | Nome | Caso |
|
|
266
|
-
|------|------|------|
|
|
267
|
-
| `23502` | `not_null_violation` | INSERT/UPDATE com null em column NOT NULL |
|
|
268
|
-
| `23503` | `foreign_key_violation` | Referência inexistente em FK |
|
|
269
|
-
| `23505` | `unique_violation` | Duplicate em UNIQUE/PK |
|
|
270
|
-
| `23514` | `check_violation` | Falha em CHECK constraint |
|
|
271
|
-
| `42501` | `insufficient_privilege` | RLS bloqueando OR GRANT ausente |
|
|
272
|
-
| `22001` | `string_data_right_truncation` | String muito longa para column |
|
|
273
|
-
| `22008` | `datetime_field_overflow` | Date inválida |
|
|
274
|
-
| `P0001` | `raise_exception` | RAISE EXCEPTION explícito em PG function |
|
|
275
|
-
|
|
276
|
-
## Pattern 2: `supabase test db` runner + integração CI (TEST-02)
|
|
277
|
-
|
|
278
|
-
### Execução local
|
|
279
|
-
|
|
280
|
-
```bash
|
|
281
|
-
# Garantir Postgres local rodando (sobe via Docker)
|
|
282
|
-
supabase start
|
|
283
|
-
|
|
284
|
-
# Rodar todos os tests em supabase/tests/
|
|
285
|
-
supabase test db
|
|
286
|
-
|
|
287
|
-
# Output (TAP format):
|
|
288
|
-
# 1..4
|
|
289
|
-
# ok 1 - employees table should exist
|
|
290
|
-
# ok 2 - name column should exist
|
|
291
|
-
# ok 3 - name should be text
|
|
292
|
-
# ok 4 - employees count should be 3 after seed
|
|
293
|
-
# # Pass: 4
|
|
294
|
-
# # Fail: 0
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### Output TAP — anatomia
|
|
298
|
-
|
|
299
|
-
```text
|
|
300
|
-
1..4 ← plan (4 tests esperados)
|
|
301
|
-
ok 1 - test description ← test passou
|
|
302
|
-
not ok 2 - test description ← test falhou
|
|
303
|
-
# Failed test 2: 'test description' ← detalhe da falha
|
|
304
|
-
# got: 'actual_value'
|
|
305
|
-
# expected: 'expected_value'
|
|
306
|
-
ok 3 - test description
|
|
307
|
-
ok 4 - test description
|
|
308
|
-
# Looks like you failed 1 test of 4. ← summary
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
Exit code:
|
|
312
|
-
- **0** — todos tests passaram
|
|
313
|
-
- **1** — algum teste falhou (CI fails → required check fails → merge bloqueado)
|
|
314
|
-
- **3** — erro fatal (DB não disponível, syntax error em arquivo de test)
|
|
315
|
-
|
|
316
|
-
### Rodar test específico
|
|
317
|
-
|
|
318
|
-
```bash
|
|
319
|
-
# Apenas um arquivo
|
|
320
|
-
supabase test db supabase/tests/employees_test.sql
|
|
321
|
-
|
|
322
|
-
# Útil para debug rápido durante TDD
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### Integração CI (cross-ref Phase 151 CI-05 `database-tests.yml`)
|
|
326
|
-
|
|
327
|
-
Workflow canônico em `.github/workflows/database-tests.yml`:
|
|
328
|
-
|
|
329
|
-
```yaml
|
|
330
|
-
name: database-tests
|
|
331
|
-
on:
|
|
332
|
-
pull_request:
|
|
333
|
-
jobs:
|
|
334
|
-
build:
|
|
335
|
-
runs-on: ubuntu-latest
|
|
336
|
-
steps:
|
|
337
|
-
- uses: actions/checkout@v4
|
|
338
|
-
- uses: supabase/setup-cli@v1
|
|
339
|
-
with:
|
|
340
|
-
version: latest
|
|
341
|
-
- run: supabase db start
|
|
342
|
-
- run: supabase test db
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
**Crítico:**
|
|
346
|
-
- `supabase db start` (não `supabase start`) — sobe apenas Postgres, mais rápido que stack completa
|
|
347
|
-
- `supabase test db` consome `supabase/tests/*.sql` em ordem alfabética
|
|
348
|
-
- Falha em qualquer teste = exit code 1 = workflow falha = required check falha = merge bloqueado
|
|
349
|
-
|
|
350
|
-
Required check `database-tests / build` deve ser obrigatório em branch protection rule para `main` (cross-ref Phase 151 lista de required checks).
|
|
351
|
-
|
|
352
|
-
### Caveat — fixtures via `supabase/seed.sql`
|
|
353
|
-
|
|
354
|
-
`supabase db start` aplica todas migrations + executa `supabase/seed.sql` (se existir). Use seed para popular dados de fixture compartilhados:
|
|
355
|
-
|
|
356
|
-
```sql
|
|
357
|
-
-- supabase/seed.sql (rodado uma vez em db start)
|
|
358
|
-
insert into public.employees (id, name, email) values
|
|
359
|
-
('00000000-0000-0000-0000-000000000001', 'Alice', 'alice@x.com'),
|
|
360
|
-
('00000000-0000-0000-0000-000000000002', 'Bob', 'bob@x.com'),
|
|
361
|
-
('00000000-0000-0000-0000-000000000003', 'Carol', 'carol@x.com');
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
Testes referenciam UUIDs determinísticos:
|
|
365
|
-
|
|
366
|
-
```sql
|
|
367
|
-
select results_eq(
|
|
368
|
-
'select name from public.employees where id = ''00000000-0000-0000-0000-000000000001'' ',
|
|
369
|
-
$$values ('Alice')$$,
|
|
370
|
-
'Alice exists'
|
|
371
|
-
);
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
**Caveat:** seed roda APENAS em `db start` (não a cada test). Tests que precisam de fixture específica devem inserir dentro do `begin; ... rollback;` para isolar.
|
|
375
|
-
|
|
376
|
-
### Caveat — paralelismo
|
|
377
|
-
|
|
378
|
-
`supabase test db` roda arquivos **sequencialmente** (não em paralelo). Cada arquivo tem `begin; ... rollback;` próprio — isolamento OK. Mas tempo total cresce linearmente com N arquivos.
|
|
379
|
-
|
|
380
|
-
Mitigação para suítes grandes (> 50 arquivos):
|
|
381
|
-
- Particionar em jobs paralelos no GitHub Actions (matrix strategy)
|
|
382
|
-
- Cada job roda subset de `supabase/tests/<group>/`
|
|
383
|
-
|
|
384
|
-
## Pattern 3: Deno Edge Function tests (TEST-03)
|
|
385
|
-
|
|
386
|
-
### Estrutura canônica
|
|
387
|
-
|
|
388
|
-
```text
|
|
389
|
-
supabase/
|
|
390
|
-
└── functions/
|
|
391
|
-
├── hello/
|
|
392
|
-
│ └── index.ts # Edge Function code
|
|
393
|
-
├── create-invite/
|
|
394
|
-
│ └── index.ts
|
|
395
|
-
└── _tests/ # ← convenção (_ prefix evita conflito com fn name)
|
|
396
|
-
├── hello_test.ts
|
|
397
|
-
└── create-invite_test.ts
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
Naming convention: `<fn-name>_test.ts` em `supabase/functions/_tests/`.
|
|
401
|
-
|
|
402
|
-
### Anatomy de um teste Deno
|
|
403
|
-
|
|
404
|
-
```typescript
|
|
405
|
-
// supabase/functions/_tests/hello_test.ts
|
|
406
|
-
import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts";
|
|
407
|
-
|
|
408
|
-
Deno.test("hello function returns 200", async () => {
|
|
409
|
-
const response = await fetch("http://127.0.0.1:54321/functions/v1/hello", {
|
|
410
|
-
method: "POST",
|
|
411
|
-
headers: {
|
|
412
|
-
"Authorization": `Bearer ${Deno.env.get("SUPABASE_ANON_KEY")}`,
|
|
413
|
-
"Content-Type": "application/json",
|
|
414
|
-
},
|
|
415
|
-
body: JSON.stringify({ name: "World" }),
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
assertEquals(response.status, 200);
|
|
419
|
-
const data = await response.json();
|
|
420
|
-
assertEquals(data.message, "Hello World!");
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
Deno.test("hello function rejects empty body", async () => {
|
|
424
|
-
const response = await fetch("http://127.0.0.1:54321/functions/v1/hello", {
|
|
425
|
-
method: "POST",
|
|
426
|
-
headers: {
|
|
427
|
-
"Authorization": `Bearer ${Deno.env.get("SUPABASE_ANON_KEY")}`,
|
|
428
|
-
"Content-Type": "application/json",
|
|
429
|
-
},
|
|
430
|
-
body: JSON.stringify({}),
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
assertEquals(response.status, 400);
|
|
434
|
-
});
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### Execução local
|
|
438
|
-
|
|
439
|
-
```bash
|
|
440
|
-
# Sobe stack completa (Postgres + Auth + Edge Functions runtime + Storage)
|
|
441
|
-
supabase start
|
|
442
|
-
|
|
443
|
-
# Rodar tests
|
|
444
|
-
deno test --allow-all supabase/functions/_tests/ --env-file .env.local
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
`--allow-all` permite acesso file system + network + env vars (Deno é sandboxed por default).
|
|
448
|
-
|
|
449
|
-
`--env-file .env.local` carrega env vars de arquivo local. Sem essa flag, `Deno.env.get(...)` retorna `undefined`.
|
|
450
|
-
|
|
451
|
-
### Estrutura `.env.local` (gitignored)
|
|
452
|
-
|
|
453
|
-
```bash
|
|
454
|
-
# supabase/functions/.env.local (gitignored — gerado dinamicamente)
|
|
455
|
-
SUPABASE_URL=http://127.0.0.1:54321
|
|
456
|
-
SUPABASE_ANON_KEY=eyJ...local-anon-key...
|
|
457
|
-
SUPABASE_SERVICE_ROLE_KEY=eyJ...local-service-role-key...
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
**Caveat:** `supabase start` imprime essas keys no output. Capture-as automaticamente:
|
|
461
|
-
|
|
462
|
-
```bash
|
|
463
|
-
# Linux/macOS
|
|
464
|
-
supabase status -o env > supabase/functions/.env.local
|
|
465
|
-
|
|
466
|
-
# Windows PowerShell
|
|
467
|
-
supabase status -o env | Out-File -Encoding utf8 supabase/functions/.env.local
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
Atualizar `.env.local` a cada `supabase start` — keys mudam se containers forem recriados.
|
|
471
|
-
|
|
472
|
-
### Importar handler diretamente (test unitário)
|
|
473
|
-
|
|
474
|
-
Alternativa ao test via HTTP — importar handler direto:
|
|
475
|
-
|
|
476
|
-
```typescript
|
|
477
|
-
// supabase/functions/_tests/hello_unit_test.ts
|
|
478
|
-
import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts";
|
|
479
|
-
|
|
480
|
-
// Assumindo hello/index.ts exporta handler
|
|
481
|
-
import { handler } from "../hello/index.ts";
|
|
482
|
-
|
|
483
|
-
Deno.test("handler returns 200 for valid input", async () => {
|
|
484
|
-
const req = new Request("http://localhost/hello", {
|
|
485
|
-
method: "POST",
|
|
486
|
-
headers: { "Content-Type": "application/json" },
|
|
487
|
-
body: JSON.stringify({ name: "World" }),
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
const res = await handler(req);
|
|
491
|
-
assertEquals(res.status, 200);
|
|
492
|
-
|
|
493
|
-
const data = await res.json();
|
|
494
|
-
assertEquals(data.message, "Hello World!");
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
Deno.test("handler returns 400 for invalid input", async () => {
|
|
498
|
-
const req = new Request("http://localhost/hello", {
|
|
499
|
-
method: "POST",
|
|
500
|
-
headers: { "Content-Type": "application/json" },
|
|
501
|
-
body: JSON.stringify({}),
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
const res = await handler(req);
|
|
505
|
-
assertEquals(res.status, 400);
|
|
506
|
-
});
|
|
507
|
-
```
|
|
508
|
-
|
|
509
|
-
**Vantagens vs HTTP test:**
|
|
510
|
-
- Mais rápido (sem network roundtrip)
|
|
511
|
-
- Testa lógica isolada do handler
|
|
512
|
-
- Não requer `supabase start` (apenas Deno runtime)
|
|
513
|
-
|
|
514
|
-
**Desvantagens:**
|
|
515
|
-
- Não exercita auth middleware (JWT verification)
|
|
516
|
-
- Não exercita rate limiting / CORS / Deno.serve setup
|
|
517
|
-
- Mais frágil a mudanças no boilerplate da Edge Function
|
|
518
|
-
|
|
519
|
-
**Canônico:** combinar AMBOS — unit tests do handler (rápidos) + smoke test via HTTP (validação E2E mínima).
|
|
520
|
-
|
|
521
|
-
### Integração CI (cross-ref Phase 151 CI-06 `functions-tests.yml`)
|
|
522
|
-
|
|
523
|
-
Workflow canônico em `.github/workflows/functions-tests.yml`:
|
|
524
|
-
|
|
525
|
-
```yaml
|
|
526
|
-
name: functions-tests
|
|
527
|
-
on:
|
|
528
|
-
pull_request:
|
|
529
|
-
jobs:
|
|
530
|
-
build:
|
|
531
|
-
runs-on: ubuntu-latest
|
|
532
|
-
steps:
|
|
533
|
-
- uses: actions/checkout@v4
|
|
534
|
-
- uses: supabase/setup-cli@v1
|
|
535
|
-
with:
|
|
536
|
-
version: latest
|
|
537
|
-
- uses: denoland/setup-deno@v2
|
|
538
|
-
with:
|
|
539
|
-
deno-version: latest
|
|
540
|
-
- run: supabase start
|
|
541
|
-
- name: Generate .env.local
|
|
542
|
-
run: supabase status -o env > supabase/functions/.env.local
|
|
543
|
-
- run: deno test --allow-all supabase/functions/_tests/ --env-file supabase/functions/.env.local
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
Required check `functions-tests / build` deve ser obrigatório em branch protection rule para `main`.
|
|
547
|
-
|
|
548
|
-
### Caveat — fixtures DB para Edge Function tests
|
|
549
|
-
|
|
550
|
-
Edge Function pode escrever em DB. Tests precisam:
|
|
551
|
-
|
|
552
|
-
1. Inserir fixtures via SQL antes do test
|
|
553
|
-
2. Limpar após o test (ou cada test recria estado)
|
|
554
|
-
|
|
555
|
-
```typescript
|
|
556
|
-
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
|
557
|
-
|
|
558
|
-
const supabase = createClient(
|
|
559
|
-
Deno.env.get("SUPABASE_URL")!,
|
|
560
|
-
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!, // bypass RLS para setup
|
|
561
|
-
);
|
|
562
|
-
|
|
563
|
-
Deno.test("create-invite writes to org_invites table", async () => {
|
|
564
|
-
// Setup
|
|
565
|
-
await supabase.from("organizations").insert({ id: "org-1", name: "Test" });
|
|
566
|
-
|
|
567
|
-
// Act
|
|
568
|
-
const { data } = await supabase.functions.invoke("create-invite", {
|
|
569
|
-
body: { org_id: "org-1", email: "new@x.com", role: "member" },
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
// Assert
|
|
573
|
-
assertEquals(data.success, true);
|
|
574
|
-
|
|
575
|
-
const { data: invites } = await supabase
|
|
576
|
-
.from("org_invites")
|
|
577
|
-
.select("*")
|
|
578
|
-
.eq("org_id", "org-1");
|
|
579
|
-
assertEquals(invites?.length, 1);
|
|
580
|
-
|
|
581
|
-
// Teardown
|
|
582
|
-
await supabase.from("org_invites").delete().eq("org_id", "org-1");
|
|
583
|
-
await supabase.from("organizations").delete().eq("id", "org-1");
|
|
584
|
-
});
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
**Caveat:** ao contrário de pgTAP (transactional rollback automático), Deno tests precisam de cleanup MANUAL. Considere padrão `beforeEach/afterEach`:
|
|
588
|
-
|
|
589
|
-
```typescript
|
|
590
|
-
async function cleanup() {
|
|
591
|
-
await supabase.from("org_invites").delete().neq("id", "00000000-0000-0000-0000-000000000000");
|
|
592
|
-
await supabase.from("organizations").delete().neq("id", "00000000-0000-0000-0000-000000000000");
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
Deno.test({
|
|
596
|
-
name: "create-invite test",
|
|
597
|
-
async fn() {
|
|
598
|
-
await cleanup(); // estado limpo antes
|
|
599
|
-
// ... test body ...
|
|
600
|
-
await cleanup(); // teardown
|
|
601
|
-
},
|
|
602
|
-
});
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
Ou usar `Deno.test.beforeEach` (Deno 1.40+):
|
|
606
|
-
|
|
607
|
-
```typescript
|
|
608
|
-
Deno.test.beforeEach(cleanup);
|
|
609
|
-
Deno.test.afterEach(cleanup);
|
|
610
|
-
|
|
611
|
-
Deno.test("create-invite writes to org_invites", async () => {
|
|
612
|
-
// ...
|
|
613
|
-
});
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
## Pattern 4: Cross-ref legacy-characterizer — pgTAP como mecanismo de characterization (TEST-04)
|
|
617
|
-
|
|
618
|
-
### Princípio canônico (Feathers cap 13)
|
|
619
|
-
|
|
620
|
-
**Legacy code = código sem testes** (definição Feathers, não estética). PG function/trigger/policy com > 100 linhas e sem tests = legacy, mesmo se escrita ontem.
|
|
621
|
-
|
|
622
|
-
Antes de refatorar PG function legada, **characterize first** — capture comportamento atual como pgTAP tests. Tests viram oracle imutável. Refactor preserva oracle. Bug fix vem em PR separado depois.
|
|
623
|
-
|
|
624
|
-
### Workflow canônico de characterization de PG function
|
|
625
|
-
|
|
626
|
-
```text
|
|
627
|
-
1. Identificar PG function alvo do refactor
|
|
628
|
-
Exemplo: public.calculate_invoice_total(uuid) — 200 linhas, sem tests
|
|
629
|
-
|
|
630
|
-
2. Inventariar inputs/outputs
|
|
631
|
-
Inputs:
|
|
632
|
-
- parâmetro: invoice_id uuid
|
|
633
|
-
- reads: invoices, line_items, discounts, taxes (4 tabelas)
|
|
634
|
-
- reads globais: current_setting('app.tax_rate')
|
|
635
|
-
Outputs:
|
|
636
|
-
- return: numeric(10,2)
|
|
637
|
-
- side effects: insert em audit_log + update em invoices.calculated_at
|
|
638
|
-
|
|
639
|
-
3. Para cada grupo de equivalência (5+ inputs):
|
|
640
|
-
a. Construir input (fixture)
|
|
641
|
-
b. Executar function REAL — sem mocks ainda
|
|
642
|
-
c. Capturar output completo + side effects
|
|
643
|
-
d. REVISAR linha por linha — marcar bugs conhecidos como comments
|
|
644
|
-
e. Salvar como pgTAP assertion
|
|
645
|
-
|
|
646
|
-
4. Escrever pgTAP test file
|
|
647
|
-
- plan(N) declara N grupos × M assertions cada
|
|
648
|
-
- results_eq para return value
|
|
649
|
-
- results_eq para side effects (linhas em audit_log)
|
|
650
|
-
- throws_ok para edge cases que esperam erro
|
|
651
|
-
|
|
652
|
-
5. supabase test db — TODOS verdes = baseline estabelecido
|
|
653
|
-
|
|
654
|
-
6. Refactor pode começar
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
### Exemplo canônico
|
|
658
|
-
|
|
659
|
-
```sql
|
|
660
|
-
-- supabase/tests/calculate_invoice_total_characterization_test.sql
|
|
661
|
-
begin;
|
|
662
|
-
select plan(10);
|
|
663
|
-
|
|
664
|
-
-- ===== GRUPO 1: invoice típica com 1 line item =====
|
|
665
|
-
-- Setup
|
|
666
|
-
insert into public.invoices (id, customer_id, status) values
|
|
667
|
-
('inv-001', 'cust-001', 'pending');
|
|
668
|
-
insert into public.line_items (invoice_id, sku, qty, unit_price) values
|
|
669
|
-
('inv-001', 'SKU-1', 2, 50.00);
|
|
670
|
-
|
|
671
|
-
-- Test 1: return value
|
|
672
|
-
select results_eq(
|
|
673
|
-
$$select public.calculate_invoice_total('inv-001'::uuid)$$,
|
|
674
|
-
$$values (100.00::numeric)$$,
|
|
675
|
-
'GROUP 1: typical invoice 1 line item — total 100.00'
|
|
676
|
-
);
|
|
677
|
-
|
|
678
|
-
-- Test 2: side effect — audit_log row created
|
|
679
|
-
select results_eq(
|
|
680
|
-
$$select event_type, target_id from public.audit_log where target_id = 'inv-001'::uuid$$,
|
|
681
|
-
$$values ('invoice_total_calculated'::text, 'inv-001'::uuid)$$,
|
|
682
|
-
'GROUP 1: audit_log row created'
|
|
683
|
-
);
|
|
684
|
-
|
|
685
|
-
-- ===== GRUPO 2: invoice com discount =====
|
|
686
|
-
insert into public.invoices (id, customer_id, status) values
|
|
687
|
-
('inv-002', 'cust-001', 'pending');
|
|
688
|
-
insert into public.line_items (invoice_id, sku, qty, unit_price) values
|
|
689
|
-
('inv-002', 'SKU-2', 1, 200.00);
|
|
690
|
-
insert into public.discounts (invoice_id, percentage) values
|
|
691
|
-
('inv-002', 10.0); -- 10% discount
|
|
692
|
-
|
|
693
|
-
select results_eq(
|
|
694
|
-
$$select public.calculate_invoice_total('inv-002'::uuid)$$,
|
|
695
|
-
$$values (180.00::numeric)$$,
|
|
696
|
-
'GROUP 2: invoice with 10% discount — total 180.00'
|
|
697
|
-
);
|
|
698
|
-
|
|
699
|
-
-- ===== GRUPO 3: invoice vazia (BUG #1 conhecido — retorna 0, deveria raise) =====
|
|
700
|
-
insert into public.invoices (id, customer_id, status) values
|
|
701
|
-
('inv-003', 'cust-001', 'pending');
|
|
702
|
-
-- NO line_items inseridos
|
|
703
|
-
|
|
704
|
-
-- BUG #1: function retorna 0 para invoice vazia, deveria raise 'invalid_invoice'
|
|
705
|
-
-- Preservar comportamento bugado durante refactor; fix em PR separado.
|
|
706
|
-
select results_eq(
|
|
707
|
-
$$select public.calculate_invoice_total('inv-003'::uuid)$$,
|
|
708
|
-
$$values (0.00::numeric)$$,
|
|
709
|
-
'GROUP 3 [BUG #1]: empty invoice returns 0 (should raise — preserved as oracle)'
|
|
710
|
-
);
|
|
711
|
-
|
|
712
|
-
-- ===== GRUPO 4: invoice inexistente — espera erro =====
|
|
713
|
-
select throws_ok(
|
|
714
|
-
$$select public.calculate_invoice_total('00000000-0000-0000-0000-000000000000'::uuid)$$,
|
|
715
|
-
'P0001',
|
|
716
|
-
'GROUP 4: non-existent invoice raises P0001'
|
|
717
|
-
);
|
|
718
|
-
|
|
719
|
-
-- ===== GRUPO 5: invoice com NEG line items (edge case histórico) =====
|
|
720
|
-
insert into public.invoices (id, customer_id, status) values
|
|
721
|
-
('inv-005', 'cust-001', 'pending');
|
|
722
|
-
insert into public.line_items (invoice_id, sku, qty, unit_price) values
|
|
723
|
-
('inv-005', 'SKU-3', -1, 100.00); -- qty NEGATIVO (devolução)
|
|
724
|
-
|
|
725
|
-
select results_eq(
|
|
726
|
-
$$select public.calculate_invoice_total('inv-005'::uuid)$$,
|
|
727
|
-
$$values (-100.00::numeric)$$,
|
|
728
|
-
'GROUP 5: negative qty produces negative total (refund flow)'
|
|
729
|
-
);
|
|
730
|
-
|
|
731
|
-
-- ... outros grupos (boundary valid, side-effect heavy, etc.) ...
|
|
732
|
-
|
|
733
|
-
select * from finish();
|
|
734
|
-
rollback;
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
### Bugs preservados como comments
|
|
738
|
-
|
|
739
|
-
**Crítico:** characterization captura o que código FAZ, não o que DEVERIA fazer. Bugs conhecidos viram comments inline (`-- BUG #X: deveria Y, é Z`). Refactor preserva o oracle (incluindo bugs). Bug fix vem em PR separado **depois** do refactor, com seu próprio teste.
|
|
740
|
-
|
|
741
|
-
Exemplo do test acima: GROUP 3 marca `BUG #1` — função retorna 0 para invoice vazia em vez de raise exception. Refactor manterá esse comportamento. PR de bug fix subsequente alterará a assertion para `throws_ok` e fixará a função.
|
|
742
|
-
|
|
743
|
-
### Behavioral coverage check (mutation testing — recomendado)
|
|
744
|
-
|
|
745
|
-
pgTAP cobre o **que** o código faz; mutation testing valida que tests **detectam regressão**. Para PG functions críticas:
|
|
746
|
-
|
|
747
|
-
1. Rodar `supabase test db` — baseline verde
|
|
748
|
-
2. Aplicar mutation (manualmente — não há tool padrão para PG mutation; use search-replace deliberado):
|
|
749
|
-
- Trocar `+` por `-` na cálculo
|
|
750
|
-
- Trocar `>` por `>=` em CHECK
|
|
751
|
-
- Comentar `RAISE EXCEPTION` em edge case
|
|
752
|
-
3. Rodar `supabase test db` — esperado vermelho. Se ficar verde, characterization tem ponto cego.
|
|
753
|
-
4. Adicionar test que cobre o ponto cego
|
|
754
|
-
5. Reverter mutation; suite volta a verde
|
|
755
|
-
|
|
756
|
-
Cross-ref skill `ai-mutation-tester` (v1.20) — pattern análogo para JavaScript/TypeScript.
|
|
757
|
-
|
|
758
|
-
### Quando pgTAP characterization é mandatório
|
|
759
|
-
|
|
760
|
-
Aplicar pgTAP characterization (cross-ref skill `pre-refactor-characterization` v1.18) ANTES de refactor de:
|
|
761
|
-
|
|
762
|
-
- PG function > 100 linhas sem tests existentes
|
|
763
|
-
- PG function consumida por > 3 callers (alto blast radius se regredir)
|
|
764
|
-
- RLS policy complexa (> 5 OR conditions, ou referencia auth.uid() + claim + subquery)
|
|
765
|
-
- Trigger BEFORE/AFTER que muta dados em outras tabelas
|
|
766
|
-
- Function exposta via Edge Function ou PostgREST RPC (contrato externo)
|
|
767
|
-
|
|
768
|
-
## Anti-patterns
|
|
769
|
-
|
|
770
|
-
### Anti-pattern 1: Tests sem `rollback`
|
|
771
|
-
|
|
772
|
-
**Errado:**
|
|
773
|
-
|
|
774
|
-
```sql
|
|
775
|
-
-- supabase/tests/employees_test.sql
|
|
776
|
-
select plan(2);
|
|
777
|
-
|
|
778
|
-
select has_table('public', 'employees', 'employees exists');
|
|
779
|
-
|
|
780
|
-
insert into public.employees (name) values ('Test User'); -- SEM begin/rollback!
|
|
781
|
-
|
|
782
|
-
select results_eq(
|
|
783
|
-
'select count(*) from public.employees where name = ''Test User''',
|
|
784
|
-
'values (1::bigint)',
|
|
785
|
-
'inserted user found'
|
|
786
|
-
);
|
|
787
|
-
|
|
788
|
-
select * from finish();
|
|
789
|
-
```
|
|
790
|
-
|
|
791
|
-
**Por quê:** `INSERT` sem `begin; ... rollback;` é **persistido** no DB local. Após rodar `supabase test db`:
|
|
792
|
-
|
|
793
|
-
- Teste pode "passar" na primeira run mas falhar na segunda (`count` já não é 1, é 2)
|
|
794
|
-
- DB local poluído com dados sintéticos — afeta outros tests
|
|
795
|
-
- Se rodar em CI, novo container DB cada run = OK; mas localmente é problema
|
|
796
|
-
|
|
797
|
-
**Certo:**
|
|
798
|
-
|
|
799
|
-
```sql
|
|
800
|
-
-- supabase/tests/employees_test.sql
|
|
801
|
-
begin; -- inicia transação
|
|
802
|
-
select plan(2);
|
|
803
|
-
|
|
804
|
-
select has_table('public', 'employees', 'employees exists');
|
|
805
|
-
|
|
806
|
-
insert into public.employees (name) values ('Test User');
|
|
807
|
-
|
|
808
|
-
select results_eq(
|
|
809
|
-
'select count(*) from public.employees where name = ''Test User''',
|
|
810
|
-
'values (1::bigint)',
|
|
811
|
-
'inserted user found'
|
|
812
|
-
);
|
|
813
|
-
|
|
814
|
-
select * from finish();
|
|
815
|
-
rollback; -- desfaz INSERT
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
`rollback` é **incondicional** — mesmo se `finish()` falhar, transação aborta no rollback. Estado DB inalterado.
|
|
819
|
-
|
|
820
|
-
### Anti-pattern 2: Esquecer `plan(N)` — testes silenciosos
|
|
821
|
-
|
|
822
|
-
**Errado:**
|
|
823
|
-
|
|
824
|
-
```sql
|
|
825
|
-
begin;
|
|
826
|
-
-- SEM plan() declarado
|
|
827
|
-
|
|
828
|
-
select has_table('public', 'employees', 'test 1');
|
|
829
|
-
select has_column('public', 'employees', 'name', 'test 2');
|
|
830
|
-
select col_type_is('public', 'employees', 'name', 'text', 'test 3');
|
|
831
|
-
|
|
832
|
-
select * from finish();
|
|
833
|
-
rollback;
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
**Por quê:** sem `plan(N)`, pgTAP **não sabe** quantos tests esperar. Output ainda mostra `ok 1`, `ok 2`, `ok 3` mas:
|
|
837
|
-
|
|
838
|
-
- TAP harness pode interpretar como "0 tests planned, 3 executed" → falha silenciosa em CI
|
|
839
|
-
- `finish()` reporta `1..0` (planejados zero) — exit code pode ser 0 (sucesso) mesmo se tests **falharem** silenciosamente
|
|
840
|
-
- Adicionar test novo não é notado (deveria mudar `plan(N)` para `plan(N+1)`, mas como não tem plan, esquecer é invisível)
|
|
841
|
-
|
|
842
|
-
**Certo:**
|
|
843
|
-
|
|
844
|
-
```sql
|
|
845
|
-
begin;
|
|
846
|
-
select plan(3); -- DECLARA EXPLICITAMENTE 3 tests
|
|
847
|
-
|
|
848
|
-
select has_table('public', 'employees', 'test 1');
|
|
849
|
-
select has_column('public', 'employees', 'name', 'test 2');
|
|
850
|
-
select col_type_is('public', 'employees', 'name', 'text', 'test 3');
|
|
851
|
-
|
|
852
|
-
select * from finish();
|
|
853
|
-
rollback;
|
|
854
|
-
```
|
|
855
|
-
|
|
856
|
-
`plan(N)` é **obrigatório**. Se rodar 4 mas declarou 3, pgTAP reporta `# Looks like you planned 3 tests but ran 4.` — falha.
|
|
857
|
-
|
|
858
|
-
Manter `plan(N)` atualizado é parte do contrato — se adiciona test, atualiza plan.
|
|
859
|
-
|
|
860
|
-
### Anti-pattern 3: Tests Deno sem `.env.local`
|
|
861
|
-
|
|
862
|
-
**Errado:**
|
|
863
|
-
|
|
864
|
-
```bash
|
|
865
|
-
# rodar tests sem env file
|
|
866
|
-
deno test --allow-all supabase/functions/_tests/
|
|
867
|
-
```
|
|
868
|
-
|
|
869
|
-
```typescript
|
|
870
|
-
// dentro do test
|
|
871
|
-
const response = await fetch(`${Deno.env.get("SUPABASE_URL")}/functions/v1/hello`);
|
|
872
|
-
// Deno.env.get("SUPABASE_URL") retorna undefined
|
|
873
|
-
// fetch("undefined/functions/v1/hello") = URL inválida
|
|
874
|
-
// Error: Invalid URL: 'undefined/functions/v1/hello'
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
**Por quê:** sem `--env-file`, `Deno.env.get(...)` retorna `undefined`. `fetch` com URL inválida lança erro genérico. Mensagem confusa — dev pensa que tem bug no handler, na verdade é env config.
|
|
878
|
-
|
|
879
|
-
**Certo:**
|
|
880
|
-
|
|
881
|
-
```bash
|
|
882
|
-
# Gerar .env.local
|
|
883
|
-
supabase status -o env > supabase/functions/.env.local
|
|
884
|
-
|
|
885
|
-
# Rodar com env file
|
|
886
|
-
deno test --allow-all supabase/functions/_tests/ --env-file supabase/functions/.env.local
|
|
887
|
-
```
|
|
888
|
-
|
|
889
|
-
```typescript
|
|
890
|
-
// Defensive: assert env vars existem (fail-fast com mensagem clara)
|
|
891
|
-
const SUPABASE_URL = Deno.env.get("SUPABASE_URL");
|
|
892
|
-
const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY");
|
|
893
|
-
|
|
894
|
-
if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
|
|
895
|
-
throw new Error(
|
|
896
|
-
"Missing SUPABASE_URL or SUPABASE_ANON_KEY. " +
|
|
897
|
-
"Run: supabase status -o env > supabase/functions/.env.local"
|
|
898
|
-
);
|
|
899
|
-
}
|
|
900
|
-
```
|
|
901
|
-
|
|
902
|
-
Em CI, geração de `.env.local` deve ser **step explícito** do workflow (cross-ref Phase 151 CI-06):
|
|
903
|
-
|
|
904
|
-
```yaml
|
|
905
|
-
- name: Generate .env.local
|
|
906
|
-
run: supabase status -o env > supabase/functions/.env.local
|
|
907
|
-
- run: deno test --allow-all supabase/functions/_tests/ --env-file supabase/functions/.env.local
|
|
908
|
-
```
|
|
909
|
-
|
|
910
|
-
### Anti-pattern 4: Tratar pgTAP como "testes completos"
|
|
911
|
-
|
|
912
|
-
**Errado:** time considera que pgTAP + Deno tests cobrem TODO o sistema → para CI: "se database-tests e functions-tests passam, deploy é seguro".
|
|
913
|
-
|
|
914
|
-
**Por quê:** pgTAP cobre **schema e PG logic**; Deno tests cobrem **Edge Function HTTP handlers**. NENHUM dos dois cobre:
|
|
915
|
-
|
|
916
|
-
- Cliente frontend interagindo com Supabase via `@supabase/supabase-js` (race conditions, retry, optimistic UI)
|
|
917
|
-
- Comportamento Realtime (subscriptions, broadcast, presence)
|
|
918
|
-
- Auth flows end-to-end (signup → email confirm → first login → setup wizard)
|
|
919
|
-
- RLS visto do ponto de vista do cliente autenticado (JWT real, não simulado via `set_config`)
|
|
920
|
-
- Performance / latência sob carga
|
|
921
|
-
- Edge cases de browser (CORS, cookies, localStorage)
|
|
922
|
-
|
|
923
|
-
**Certo:** treat pgTAP + Deno como **two layers of defense-in-depth**, não substituto de integration tests:
|
|
924
|
-
|
|
925
|
-
```text
|
|
926
|
-
Pirâmide de testes Supabase (canônica):
|
|
927
|
-
|
|
928
|
-
/\
|
|
929
|
-
/E2E\ ← Playwright/Cypress: 5-10 critical user journeys
|
|
930
|
-
/------\
|
|
931
|
-
/ INT. \ ← Vitest/Jest com Supabase real: auth flow, multi-tenant
|
|
932
|
-
/----------\
|
|
933
|
-
/ DENO TESTS \ ← pattern 3: Edge Functions HTTP handlers
|
|
934
|
-
/--------------\
|
|
935
|
-
/ pgTAP \ ← pattern 1: schema + RLS + PG functions
|
|
936
|
-
/------------------\
|
|
937
|
-
| Unit tests app | ← Vitest/Jest cliente: componentes React, helpers
|
|
938
|
-
```
|
|
939
|
-
|
|
940
|
-
Cada camada cobre **incidentes diferentes**:
|
|
941
|
-
- pgTAP: regressão em schema/policy/function
|
|
942
|
-
- Deno: regressão em Edge Function logic
|
|
943
|
-
- Integration: regressão em cliente ↔ Supabase wire
|
|
944
|
-
- E2E: regressão em user journey
|
|
945
|
-
|
|
946
|
-
Sem todas as camadas, gaps de cobertura existem. CI deve ter **separate required checks** por camada.
|
|
947
|
-
|
|
948
|
-
### Anti-pattern 5: Snapshot pgTAP sem revisão (characterization shortcut)
|
|
949
|
-
|
|
950
|
-
**Errado:** copy-paste output bruto de query como `results_eq` expected sem inspecionar — "se runs verde, está OK".
|
|
951
|
-
|
|
952
|
-
```sql
|
|
953
|
-
-- characterization sem revisão
|
|
954
|
-
select results_eq(
|
|
955
|
-
$$select public.complex_function('input-1')$$,
|
|
956
|
-
$$values ('a', '2026-05-11T10:23:45.123456Z'::timestamptz, 'token_abc123xyz', 42, '00000000-1234-5678-9abc-def012345678'::uuid)$$,
|
|
957
|
-
'preserved as-is'
|
|
958
|
-
);
|
|
959
|
-
```
|
|
960
|
-
|
|
961
|
-
**Por quê:** output bruto pode incluir:
|
|
962
|
-
|
|
963
|
-
- **PII** (emails, names) — vazam para git history
|
|
964
|
-
- **Tokens/secrets** — vazam para git history (irreversível)
|
|
965
|
-
- **Timestamps voláteis** — test será flaky em run subsequente
|
|
966
|
-
- **UUIDs locais** — gerados por `gen_random_uuid()`, mudam a cada run
|
|
967
|
-
|
|
968
|
-
CI fica "verde" porque snapshot bate consigo mesmo da última run, mas:
|
|
969
|
-
- Adicionar fixture nova quebra (timestamp diferente)
|
|
970
|
-
- PR review humano não detecta bugs (não inspecionou output)
|
|
971
|
-
- Secrets podem ser commitados sem aviso
|
|
972
|
-
|
|
973
|
-
**Certo:** revisão linha-por-linha + sanitização antes de salvar (cross-ref skill `legacy-characterization-tests` Pattern 6):
|
|
974
|
-
|
|
975
|
-
```sql
|
|
976
|
-
-- characterization com sanitização determinística
|
|
977
|
-
begin;
|
|
978
|
-
-- 1. Fixar clock para determinismo
|
|
979
|
-
select set_config('app.fake_now', '2026-05-11T10:00:00Z', true);
|
|
980
|
-
|
|
981
|
-
-- 2. Fixar UUIDs determinísticos via fixture
|
|
982
|
-
insert into public.things (id, name, created_at) values
|
|
983
|
-
('00000000-0000-0000-0000-000000000001', 'Test', '2026-05-11T10:00:00Z'::timestamptz);
|
|
984
|
-
|
|
985
|
-
-- 3. Snapshot review:
|
|
986
|
-
-- - Email 'alice@x.com' está no fixture (não real PII)
|
|
987
|
-
-- - Timestamp '2026-05-11T10:00:00Z' está congelado (fake clock)
|
|
988
|
-
-- - UUID '00000000...001' está determinístico (fixture)
|
|
989
|
-
-- - Token 'token_test_abc' está sanitized (não real)
|
|
990
|
-
select results_eq(
|
|
991
|
-
$$select id, name, created_at from public.things where id = '00000000-0000-0000-0000-000000000001'::uuid$$,
|
|
992
|
-
$$values ('00000000-0000-0000-0000-000000000001'::uuid, 'Test'::text, '2026-05-11T10:00:00Z'::timestamptz)$$,
|
|
993
|
-
'thing fixture matches characterization oracle (reviewed 2026-05-11)'
|
|
994
|
-
);
|
|
995
|
-
|
|
996
|
-
select * from finish();
|
|
997
|
-
rollback;
|
|
998
|
-
```
|
|
999
|
-
|
|
1000
|
-
**Crítico:** comentar quando snapshot foi revisado + por quem. Commit message: "characterize complex_function — reviewed 2026-05-11, bugs noted in comments".
|
|
1001
|
-
|
|
1002
|
-
## Cross-suite integration (v1.27)
|
|
1003
|
-
|
|
1004
|
-
Esta skill é **complemento essencial** da skill Phase 151 `supabase-ci-cd-github-actions`:
|
|
1005
|
-
|
|
1006
|
-
- Phase 151 estabelece workflows CI (`database-tests.yml` + `functions-tests.yml`) que executam `supabase test db` + `deno test --allow-all`
|
|
1007
|
-
- Phase 152 (ESTA) detalha a **sintaxe canônica** dos tests que esses workflows consomem
|
|
1008
|
-
- Forward-ref de Phase 151 é fechado por esta skill (Pattern 5 da Phase 151 referenciava "skill futura `supabase-pgtap-testing` Phase 152")
|
|
1009
|
-
|
|
1010
|
-
Cross-refs com skills existentes v1.x:
|
|
1011
|
-
|
|
1012
|
-
- **`legacy-characterization-tests` (v1.16)** — pgTAP é o mecanismo canônico para implementar characterization (cap 13 Feathers) em PG legado; Pattern 4 desta skill é especialização para Postgres
|
|
1013
|
-
- **`pre-refactor-characterization` (v1.18)** — gate auto-trigger que bloqueia refactor sem characterization; pgTAP satisfaz a pré-condição para PG functions
|
|
1014
|
-
- **`ai-mutation-tester` (v1.20)** — mutation testing complementar; valida que pgTAP detecta regressão
|
|
1015
|
-
- **`supabase-database-functions`** — PG functions criadas seguindo essa skill são prime target de pgTAP (SECURITY INVOKER + SET search_path = '' são testáveis)
|
|
1016
|
-
- **`supabase-rls-policies` (v1.23)** — RLS policies validadas via `throws_like '%permission denied%'` + `results_eq` com diferentes JWT claims
|
|
1017
|
-
- **`supabase-edge-functions`** — Edge Functions cobertas por Deno tests (Pattern 3)
|
|
1018
|
-
- **`supabase-postgres-roles` (v1.26)** — testes de roles validáveis via `has_role`, `set role test_role; ...; reset role`
|
|
1019
|
-
- **`supabase-custom-claims-rbac` (v1.25)** — testes de auth hook validáveis via fixture JWT + `set_config('request.jwt.claim.user_role', 'admin', true)`
|
|
1020
|
-
|
|
1021
|
-
Base para agent futuro v1.27:
|
|
1022
|
-
|
|
1023
|
-
- **`supabase-cicd-pipeline-implementer` (Phase 154, futura)** — agent que materializa workflows + tests; consome esta skill para gerar test files iniciais junto com migrations
|
|
1024
|
-
|
|
1025
|
-
Pattern de handoff cooperativo herdado v1.23-v1.26: **architect** projeta strategy → **test-writer** materializa pgTAP/Deno tests → **release-pipeline-auditor** (v1.10) audita coverage do pipeline. Nenhum agente descarta upstream — handoff cooperativo (princípio canônico v1.23).
|
|
1026
|
-
|
|
1027
|
-
### Casos de uso por agent
|
|
1028
|
-
|
|
1029
|
-
| Agent | Como consome esta skill |
|
|
1030
|
-
|-------|-------------------------|
|
|
1031
|
-
| `supabase-migration-writer` | Gera migration + pgTAP test inicial (has_table, has_column, has_pk) |
|
|
1032
|
-
| `supabase-rls-writer` (v1.23) | Gera RLS policies + pgTAP test (throws_like '%permission denied%' para cross-tenant) |
|
|
1033
|
-
| `supabase-edge-fn-writer` | Gera Edge Function + Deno test (handler retorna 200/400 esperado) |
|
|
1034
|
-
| `legacy-characterizer` | Gera pgTAP characterization tests para PG function legada (cap 13 Feathers) |
|
|
1035
|
-
| `refactor-safety-auditor` | Verifica existência de pgTAP tests antes de permitir refactor PG |
|
|
1036
|
-
| `supabase-cicd-pipeline-implementer` (Phase 154 futura) | Gera workflows `database-tests.yml` + `functions-tests.yml` + tests iniciais |
|
|
1037
|
-
|
|
1038
|
-
## Ver também
|
|
1039
|
-
|
|
1040
|
-
- [supabase-ci-cd-github-actions](../supabase-ci-cd-github-actions/SKILL.md) (v1.27, Phase 151) — workflows CI que executam `supabase test db` + `deno test`
|
|
1041
|
-
- [legacy-characterization-tests](../legacy-characterization-tests/SKILL.md) (v1.16) — cap 13 Feathers; pgTAP é mecanismo canônico para Postgres
|
|
1042
|
-
- [pre-refactor-characterization](../pre-refactor-characterization/SKILL.md) (v1.18) — gate que pgTAP satisfaz para PG functions
|
|
1043
|
-
- [supabase-database-functions](../supabase-database-functions/SKILL.md) — PG functions testáveis via pgTAP
|
|
1044
|
-
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) (v1.23) — RLS testável via `throws_like` + `set_config jwt claim`
|
|
1045
|
-
- [supabase-rls-defense-in-depth](../supabase-rls-defense-in-depth/SKILL.md) (v1.23) — Camadas testáveis em pgTAP
|
|
1046
|
-
- [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions cobertas por Deno tests
|
|
1047
|
-
- [supabase-postgres-roles](../supabase-postgres-roles/SKILL.md) (v1.26) — `has_role`, `set role` testáveis
|
|
1048
|
-
- [supabase-custom-claims-rbac](../supabase-custom-claims-rbac/SKILL.md) (v1.25) — fixture JWT via `set_config('request.jwt.claim.*')`
|
|
1049
|
-
- [supabase-migrations](../supabase-migrations/SKILL.md) (v1.23) — migration cria tabela; pgTAP test valida
|
|
1050
|
-
- [ai-mutation-tester](../../agents/ai-mutation-tester.md) (v1.20) — mutation testing complementar
|
|
1051
|
-
- [glossário compartilhado](../_shared-supabase/glossary.md) — termos pgTAP, TAP, plan(N), throws_ok, results_eq, supabase test db, deno test --allow-all
|
|
1052
|
-
- Doc oficial pgTAP: [pgTAP Documentation](https://pgtap.org/documentation.html)
|
|
1053
|
-
- Doc oficial Supabase: [Testing with pgTAP](https://supabase.com/docs/guides/database/extensions/pgtap), [Testing Edge Functions](https://supabase.com/docs/guides/functions/unit-test)
|
|
1
|
+
---
|
|
2
|
+
name: supabase-pgtap-testing
|
|
3
|
+
description: Use ao escrever testes de database/Edge Function em Supabase — pgTAP extension (TAP…
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supabase — pgTAP & Deno Testing
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
Esta skill cobre **testing-shift-left para Supabase** — testar schema, RLS, PG functions e Edge Functions ANTES de deploy via CI. Dois runners canônicos:
|
|
11
|
+
|
|
12
|
+
1. **pgTAP** (`supabase test db`) — database tests via TAP (Test Anything Protocol) Postgres extension
|
|
13
|
+
2. **Deno test** (`deno test --allow-all`) — Edge Functions tests via Deno runtime nativo
|
|
14
|
+
|
|
15
|
+
Trigger phrases:
|
|
16
|
+
|
|
17
|
+
- "testes Supabase", "test Supabase", "testing Supabase"
|
|
18
|
+
- "pgTAP", "supabase test db", "TAP Postgres extension"
|
|
19
|
+
- "plan ok is throws_ok results_eq pgTAP", "has_table has_column col_type_is"
|
|
20
|
+
- "Deno test Edge Function", "deno test --allow-all"
|
|
21
|
+
- "testar RLS Supabase", "characterization tests PG function"
|
|
22
|
+
- "supabase/tests/", "supabase/functions/_tests/"
|
|
23
|
+
- "testar trigger Postgres", "testar function Postgres"
|
|
24
|
+
- ".env.local Deno tests", "SUPABASE_URL SUPABASE_ANON_KEY tests"
|
|
25
|
+
- "characterization PG legado", "refatorar function Postgres com tests"
|
|
26
|
+
|
|
27
|
+
**Use APENAS para:**
|
|
28
|
+
|
|
29
|
+
- Validar schema, columns, tipos, FKs via pgTAP (`has_table`, `has_column`, `col_type_is`)
|
|
30
|
+
- Validar RLS policies retornando `permission denied` quando esperado (`throws_ok 42501`)
|
|
31
|
+
- Validar PG functions retornando outputs esperados (`results_eq`)
|
|
32
|
+
- Validar constraints, triggers, defaults (`throws_ok 23502 not_null_violation`, `throws_ok 23505 unique_violation`)
|
|
33
|
+
- Testar Edge Functions HTTP handlers (status, response body, side effects)
|
|
34
|
+
- Capturar comportamento atual de PG function legada como **characterization oracle** (Feathers cap 13)
|
|
35
|
+
- Integrar com workflow CI `database-tests.yml` (Phase 151 CI-05) e `functions-tests.yml` (CI-06)
|
|
36
|
+
|
|
37
|
+
**NÃO use para:**
|
|
38
|
+
|
|
39
|
+
- Substituir integration tests no app layer (frontend ↔ Supabase via client SDK) — pgTAP cobre só DB; Deno tests cobrem só Edge Function isolado
|
|
40
|
+
- Performance tests (latência, throughput) — pgTAP é assertion-based, não benchmark; use `pgbench` ou Vegeta para load testing
|
|
41
|
+
- Tests em produção sem `rollback` — sempre `begin; ... rollback;` (cross-ref Anti-pattern 1)
|
|
42
|
+
- Substituir auditoria via `auditor-consistencia-isolamento` (v1.22) — tests validam comportamento esperado; auditor detecta anti-patterns estáticos no SQL
|
|
43
|
+
- Substituir mutation testing (`ai-mutation-tester`) — pgTAP é characterization; mutation testing valida que tests realmente detectam regressão
|
|
44
|
+
|
|
45
|
+
## Princípio canônico
|
|
46
|
+
|
|
47
|
+
Três princípios canônicos:
|
|
48
|
+
|
|
49
|
+
1. **Testing shift-left.** Cada PR valida schema + RLS + PG functions + Edge Functions ANTES do merge. Gate canônico = required status check `database-tests / build` e `functions-tests / build` enforced via branch protection (cross-ref skill `supabase-ci-cd-github-actions` Phase 151 patterns 5 e 6).
|
|
50
|
+
|
|
51
|
+
2. **Tests são schema mutations transacionais.** Todo teste pgTAP roda dentro de `begin; ... rollback;` — assertions executam, fixtures são inseridas, RLS é exercitada, mas DB volta ao estado original. Nunca polui DB compartilhado; tests podem rodar em paralelo sem race.
|
|
52
|
+
|
|
53
|
+
3. **pgTAP é o oracle imutável para refactor de PG legado.** Quando PG function complexa (> 100 linhas, sem tests) precisa ser refatorada, pgTAP captura comportamento atual como characterization (Feathers cap 13). Tests viram oracle congelado — qualquer mudança que altere comportamento previamente capturado falha o build. Sem characterization, refactor de PG é "edit and pray" (cross-ref skill `legacy-characterization-tests` v1.16).
|
|
54
|
+
|
|
55
|
+
### Distinção canônica vs outros runners
|
|
56
|
+
|
|
57
|
+
| | pgTAP (`supabase test db`) | Deno test (`deno test`) | Vitest/Jest (app layer) |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| Escopo | Schema, RLS, PG functions, triggers | Edge Functions HTTP handlers | Frontend + cliente Supabase |
|
|
60
|
+
| Linguagem | SQL (TAP syntax) | TypeScript (Deno runtime) | TypeScript (Node runtime) |
|
|
61
|
+
| Isolamento | `begin; ... rollback;` (atomic) | Function call por test | Mocked client OR real (E2E) |
|
|
62
|
+
| Onde roda | Postgres local (via `supabase start`) | Deno runtime + Postgres local | Node + browser environment |
|
|
63
|
+
| CI workflow | `database-tests.yml` (Phase 151 CI-05) | `functions-tests.yml` (CI-06) | Custom `vitest.yml` ou `jest.yml` |
|
|
64
|
+
| Trigger canônico | PR (gate) | PR (gate) | PR + post-merge (canary) |
|
|
65
|
+
|
|
66
|
+
Os três runners são **complementares** — pgTAP valida DB, Deno valida Edge Function HTTP layer, Vitest/Jest valida cliente. Nenhum substitui os outros.
|
|
67
|
+
|
|
68
|
+
## Pattern 1: pgTAP extension setup + sintaxe canônica (TEST-01)
|
|
69
|
+
|
|
70
|
+
### Habilitar pgTAP
|
|
71
|
+
|
|
72
|
+
Em migration dedicada (uma única vez por projeto):
|
|
73
|
+
|
|
74
|
+
```sql
|
|
75
|
+
-- supabase/migrations/YYYYMMDDHHmmss_enable_pgtap.sql
|
|
76
|
+
create extension if not exists pgtap with schema extensions;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Por que `with schema extensions`:** convenção Supabase canônica — todas extensions ficam em schema dedicado (`extensions`), separadas de `public`. Permite namespace isolation + GRANT EXECUTE granular.
|
|
80
|
+
|
|
81
|
+
### Diretório canônico `supabase/tests/`
|
|
82
|
+
|
|
83
|
+
Supabase CLI busca automaticamente arquivos `*.sql` em `supabase/tests/` quando executa `supabase test db`:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
supabase/
|
|
87
|
+
├── migrations/
|
|
88
|
+
│ ├── 20260101000000_initial_schema.sql
|
|
89
|
+
│ └── 20260102000000_enable_pgtap.sql
|
|
90
|
+
├── tests/
|
|
91
|
+
│ ├── employees_test.sql # ← descoberto automaticamente
|
|
92
|
+
│ ├── rls_organizations_test.sql # ← descoberto automaticamente
|
|
93
|
+
│ └── triggers_audit_test.sql # ← descoberto automaticamente
|
|
94
|
+
└── functions/
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Naming convention: `<entity>_test.sql` ou `<concern>_test.sql` — nome descritivo + sufixo `_test.sql`. CLI roda em ordem alfabética.
|
|
98
|
+
|
|
99
|
+
### Estrutura canônica de um teste pgTAP
|
|
100
|
+
|
|
101
|
+
```sql
|
|
102
|
+
-- supabase/tests/employees_test.sql
|
|
103
|
+
begin; -- inicia transação
|
|
104
|
+
select plan(4); -- declara 4 testes (CRÍTICO — sem plan, falha silencioso)
|
|
105
|
+
|
|
106
|
+
-- Test 1: tabela existe
|
|
107
|
+
select has_table('public', 'employees', 'employees table should exist');
|
|
108
|
+
|
|
109
|
+
-- Test 2: coluna existe com tipo correto
|
|
110
|
+
select has_column('public', 'employees', 'name', 'name column should exist');
|
|
111
|
+
select col_type_is('public', 'employees', 'name', 'text', 'name should be text');
|
|
112
|
+
|
|
113
|
+
-- Test 3: comportamento de seed
|
|
114
|
+
select results_eq(
|
|
115
|
+
'select count(*) from public.employees',
|
|
116
|
+
'values (3::bigint)',
|
|
117
|
+
'employees count should be 3 after seed'
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
-- Test 4: constraint NOT NULL
|
|
121
|
+
select throws_ok(
|
|
122
|
+
'insert into public.employees (name) values (null)',
|
|
123
|
+
'23502', -- SQLSTATE not_null_violation
|
|
124
|
+
'null in name should violate not-null constraint'
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
select * from finish(); -- finaliza plano (gera summary TAP)
|
|
128
|
+
rollback; -- desfaz qualquer side effect
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Crítico:** `plan(N)` declara o número exato de assertions executadas. Se você roda 5 mas declarou 4, pgTAP reporta `# Looks like you planned 4 tests but ran 5.` — teste falha. Se rodar menos do que planejou, idem.
|
|
132
|
+
|
|
133
|
+
### Funções pgTAP canônicas
|
|
134
|
+
|
|
135
|
+
#### Schema/structure assertions
|
|
136
|
+
|
|
137
|
+
```sql
|
|
138
|
+
-- tabela existe
|
|
139
|
+
select has_table('public', 'orders', 'orders table exists');
|
|
140
|
+
|
|
141
|
+
-- tabela NÃO existe
|
|
142
|
+
select hasnt_table('public', 'old_table', 'old_table was dropped');
|
|
143
|
+
|
|
144
|
+
-- coluna existe
|
|
145
|
+
select has_column('public', 'orders', 'total', 'total column exists');
|
|
146
|
+
|
|
147
|
+
-- tipo da coluna
|
|
148
|
+
select col_type_is('public', 'orders', 'total', 'numeric(10,2)', 'total is numeric(10,2)');
|
|
149
|
+
|
|
150
|
+
-- NOT NULL
|
|
151
|
+
select col_not_null('public', 'orders', 'customer_id', 'customer_id is NOT NULL');
|
|
152
|
+
|
|
153
|
+
-- DEFAULT
|
|
154
|
+
select col_has_default('public', 'orders', 'created_at', 'created_at has default');
|
|
155
|
+
|
|
156
|
+
-- PRIMARY KEY
|
|
157
|
+
select has_pk('public', 'orders', 'orders has primary key');
|
|
158
|
+
|
|
159
|
+
-- FOREIGN KEY
|
|
160
|
+
select has_fk('public', 'orders', 'orders.customer_id references customers');
|
|
161
|
+
|
|
162
|
+
-- INDEX
|
|
163
|
+
select has_index('public', 'orders', 'orders_customer_id_idx', 'index exists');
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Function/trigger assertions
|
|
167
|
+
|
|
168
|
+
```sql
|
|
169
|
+
-- function existe
|
|
170
|
+
select has_function('public', 'calculate_total', array['uuid'], 'function exists');
|
|
171
|
+
|
|
172
|
+
-- return type
|
|
173
|
+
select function_returns('public', 'calculate_total', 'numeric', 'returns numeric');
|
|
174
|
+
|
|
175
|
+
-- trigger existe
|
|
176
|
+
select has_trigger('public', 'orders', 'set_updated_at', 'trigger exists');
|
|
177
|
+
|
|
178
|
+
-- enum value
|
|
179
|
+
select has_enum('public', 'order_status', array['pending', 'paid', 'shipped'], 'enum values');
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Behavioral assertions
|
|
183
|
+
|
|
184
|
+
```sql
|
|
185
|
+
-- equality (simple)
|
|
186
|
+
select is(1 + 1, 2, '1+1 equals 2');
|
|
187
|
+
|
|
188
|
+
-- inequality
|
|
189
|
+
select isnt(now(), '2020-01-01'::timestamp, 'now is not 2020');
|
|
190
|
+
|
|
191
|
+
-- boolean check
|
|
192
|
+
select ok(exists(select 1 from public.users where id = '...'), 'user exists');
|
|
193
|
+
|
|
194
|
+
-- equality (query result)
|
|
195
|
+
select results_eq(
|
|
196
|
+
'select id, name from public.users where active = true order by id limit 2',
|
|
197
|
+
$$values ('uuid-1'::uuid, 'Alice'), ('uuid-2'::uuid, 'Bob')$$,
|
|
198
|
+
'active users are Alice and Bob'
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
-- inequality (query result)
|
|
202
|
+
select results_ne(
|
|
203
|
+
'select count(*) from public.deleted_users',
|
|
204
|
+
'values (0::bigint)',
|
|
205
|
+
'deleted_users is not empty'
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
-- subset (query result is subset)
|
|
209
|
+
select set_eq(
|
|
210
|
+
'select email from public.users',
|
|
211
|
+
$$values ('a@x.com'), ('b@x.com'), ('c@x.com')$$,
|
|
212
|
+
'all users have correct emails'
|
|
213
|
+
);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### Error/exception assertions
|
|
217
|
+
|
|
218
|
+
```sql
|
|
219
|
+
-- erro específico
|
|
220
|
+
select throws_ok(
|
|
221
|
+
'insert into public.employees (name) values (null)',
|
|
222
|
+
'23502', -- SQLSTATE not_null_violation
|
|
223
|
+
'null name should fail not-null constraint'
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
-- error matching pattern
|
|
227
|
+
select throws_like(
|
|
228
|
+
'select * from public.private_table',
|
|
229
|
+
'%permission denied%',
|
|
230
|
+
'cross-org SELECT raises permission denied'
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
-- NÃO lança erro
|
|
234
|
+
select lives_ok(
|
|
235
|
+
$$insert into public.employees (name, email) values ('Alice', 'alice@x.com')$$,
|
|
236
|
+
'valid insert should succeed'
|
|
237
|
+
);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### Custom test helpers — fixtures
|
|
241
|
+
|
|
242
|
+
```sql
|
|
243
|
+
-- helper para criar user JWT context
|
|
244
|
+
create or replace function tests.set_jwt_claim(claim_name text, claim_value text)
|
|
245
|
+
returns void
|
|
246
|
+
language sql
|
|
247
|
+
as $$
|
|
248
|
+
select set_config('request.jwt.claim.' || claim_name, claim_value, true);
|
|
249
|
+
$$;
|
|
250
|
+
|
|
251
|
+
-- uso em teste
|
|
252
|
+
select tests.set_jwt_claim('sub', 'uuid-of-user-1');
|
|
253
|
+
select tests.set_jwt_claim('user_role', 'admin');
|
|
254
|
+
|
|
255
|
+
-- agora policies que consultam auth.uid() retornam 'uuid-of-user-1'
|
|
256
|
+
select results_eq(
|
|
257
|
+
'select count(*) from public.posts',
|
|
258
|
+
'values (10::bigint)', -- esperando 10 posts visíveis para este user
|
|
259
|
+
'user-1 admin sees all posts'
|
|
260
|
+
);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### SQLSTATE codes canônicos para `throws_ok`
|
|
264
|
+
|
|
265
|
+
| Code | Nome | Caso |
|
|
266
|
+
|------|------|------|
|
|
267
|
+
| `23502` | `not_null_violation` | INSERT/UPDATE com null em column NOT NULL |
|
|
268
|
+
| `23503` | `foreign_key_violation` | Referência inexistente em FK |
|
|
269
|
+
| `23505` | `unique_violation` | Duplicate em UNIQUE/PK |
|
|
270
|
+
| `23514` | `check_violation` | Falha em CHECK constraint |
|
|
271
|
+
| `42501` | `insufficient_privilege` | RLS bloqueando OR GRANT ausente |
|
|
272
|
+
| `22001` | `string_data_right_truncation` | String muito longa para column |
|
|
273
|
+
| `22008` | `datetime_field_overflow` | Date inválida |
|
|
274
|
+
| `P0001` | `raise_exception` | RAISE EXCEPTION explícito em PG function |
|
|
275
|
+
|
|
276
|
+
## Pattern 2: `supabase test db` runner + integração CI (TEST-02)
|
|
277
|
+
|
|
278
|
+
### Execução local
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Garantir Postgres local rodando (sobe via Docker)
|
|
282
|
+
supabase start
|
|
283
|
+
|
|
284
|
+
# Rodar todos os tests em supabase/tests/
|
|
285
|
+
supabase test db
|
|
286
|
+
|
|
287
|
+
# Output (TAP format):
|
|
288
|
+
# 1..4
|
|
289
|
+
# ok 1 - employees table should exist
|
|
290
|
+
# ok 2 - name column should exist
|
|
291
|
+
# ok 3 - name should be text
|
|
292
|
+
# ok 4 - employees count should be 3 after seed
|
|
293
|
+
# # Pass: 4
|
|
294
|
+
# # Fail: 0
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Output TAP — anatomia
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
1..4 ← plan (4 tests esperados)
|
|
301
|
+
ok 1 - test description ← test passou
|
|
302
|
+
not ok 2 - test description ← test falhou
|
|
303
|
+
# Failed test 2: 'test description' ← detalhe da falha
|
|
304
|
+
# got: 'actual_value'
|
|
305
|
+
# expected: 'expected_value'
|
|
306
|
+
ok 3 - test description
|
|
307
|
+
ok 4 - test description
|
|
308
|
+
# Looks like you failed 1 test of 4. ← summary
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Exit code:
|
|
312
|
+
- **0** — todos tests passaram
|
|
313
|
+
- **1** — algum teste falhou (CI fails → required check fails → merge bloqueado)
|
|
314
|
+
- **3** — erro fatal (DB não disponível, syntax error em arquivo de test)
|
|
315
|
+
|
|
316
|
+
### Rodar test específico
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# Apenas um arquivo
|
|
320
|
+
supabase test db supabase/tests/employees_test.sql
|
|
321
|
+
|
|
322
|
+
# Útil para debug rápido durante TDD
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Integração CI (cross-ref Phase 151 CI-05 `database-tests.yml`)
|
|
326
|
+
|
|
327
|
+
Workflow canônico em `.github/workflows/database-tests.yml`:
|
|
328
|
+
|
|
329
|
+
```yaml
|
|
330
|
+
name: database-tests
|
|
331
|
+
on:
|
|
332
|
+
pull_request:
|
|
333
|
+
jobs:
|
|
334
|
+
build:
|
|
335
|
+
runs-on: ubuntu-latest
|
|
336
|
+
steps:
|
|
337
|
+
- uses: actions/checkout@v4
|
|
338
|
+
- uses: supabase/setup-cli@v1
|
|
339
|
+
with:
|
|
340
|
+
version: latest
|
|
341
|
+
- run: supabase db start
|
|
342
|
+
- run: supabase test db
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Crítico:**
|
|
346
|
+
- `supabase db start` (não `supabase start`) — sobe apenas Postgres, mais rápido que stack completa
|
|
347
|
+
- `supabase test db` consome `supabase/tests/*.sql` em ordem alfabética
|
|
348
|
+
- Falha em qualquer teste = exit code 1 = workflow falha = required check falha = merge bloqueado
|
|
349
|
+
|
|
350
|
+
Required check `database-tests / build` deve ser obrigatório em branch protection rule para `main` (cross-ref Phase 151 lista de required checks).
|
|
351
|
+
|
|
352
|
+
### Caveat — fixtures via `supabase/seed.sql`
|
|
353
|
+
|
|
354
|
+
`supabase db start` aplica todas migrations + executa `supabase/seed.sql` (se existir). Use seed para popular dados de fixture compartilhados:
|
|
355
|
+
|
|
356
|
+
```sql
|
|
357
|
+
-- supabase/seed.sql (rodado uma vez em db start)
|
|
358
|
+
insert into public.employees (id, name, email) values
|
|
359
|
+
('00000000-0000-0000-0000-000000000001', 'Alice', 'alice@x.com'),
|
|
360
|
+
('00000000-0000-0000-0000-000000000002', 'Bob', 'bob@x.com'),
|
|
361
|
+
('00000000-0000-0000-0000-000000000003', 'Carol', 'carol@x.com');
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Testes referenciam UUIDs determinísticos:
|
|
365
|
+
|
|
366
|
+
```sql
|
|
367
|
+
select results_eq(
|
|
368
|
+
'select name from public.employees where id = ''00000000-0000-0000-0000-000000000001'' ',
|
|
369
|
+
$$values ('Alice')$$,
|
|
370
|
+
'Alice exists'
|
|
371
|
+
);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Caveat:** seed roda APENAS em `db start` (não a cada test). Tests que precisam de fixture específica devem inserir dentro do `begin; ... rollback;` para isolar.
|
|
375
|
+
|
|
376
|
+
### Caveat — paralelismo
|
|
377
|
+
|
|
378
|
+
`supabase test db` roda arquivos **sequencialmente** (não em paralelo). Cada arquivo tem `begin; ... rollback;` próprio — isolamento OK. Mas tempo total cresce linearmente com N arquivos.
|
|
379
|
+
|
|
380
|
+
Mitigação para suítes grandes (> 50 arquivos):
|
|
381
|
+
- Particionar em jobs paralelos no GitHub Actions (matrix strategy)
|
|
382
|
+
- Cada job roda subset de `supabase/tests/<group>/`
|
|
383
|
+
|
|
384
|
+
## Pattern 3: Deno Edge Function tests (TEST-03)
|
|
385
|
+
|
|
386
|
+
### Estrutura canônica
|
|
387
|
+
|
|
388
|
+
```text
|
|
389
|
+
supabase/
|
|
390
|
+
└── functions/
|
|
391
|
+
├── hello/
|
|
392
|
+
│ └── index.ts # Edge Function code
|
|
393
|
+
├── create-invite/
|
|
394
|
+
│ └── index.ts
|
|
395
|
+
└── _tests/ # ← convenção (_ prefix evita conflito com fn name)
|
|
396
|
+
├── hello_test.ts
|
|
397
|
+
└── create-invite_test.ts
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Naming convention: `<fn-name>_test.ts` em `supabase/functions/_tests/`.
|
|
401
|
+
|
|
402
|
+
### Anatomy de um teste Deno
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
// supabase/functions/_tests/hello_test.ts
|
|
406
|
+
import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts";
|
|
407
|
+
|
|
408
|
+
Deno.test("hello function returns 200", async () => {
|
|
409
|
+
const response = await fetch("http://127.0.0.1:54321/functions/v1/hello", {
|
|
410
|
+
method: "POST",
|
|
411
|
+
headers: {
|
|
412
|
+
"Authorization": `Bearer ${Deno.env.get("SUPABASE_ANON_KEY")}`,
|
|
413
|
+
"Content-Type": "application/json",
|
|
414
|
+
},
|
|
415
|
+
body: JSON.stringify({ name: "World" }),
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
assertEquals(response.status, 200);
|
|
419
|
+
const data = await response.json();
|
|
420
|
+
assertEquals(data.message, "Hello World!");
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
Deno.test("hello function rejects empty body", async () => {
|
|
424
|
+
const response = await fetch("http://127.0.0.1:54321/functions/v1/hello", {
|
|
425
|
+
method: "POST",
|
|
426
|
+
headers: {
|
|
427
|
+
"Authorization": `Bearer ${Deno.env.get("SUPABASE_ANON_KEY")}`,
|
|
428
|
+
"Content-Type": "application/json",
|
|
429
|
+
},
|
|
430
|
+
body: JSON.stringify({}),
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
assertEquals(response.status, 400);
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Execução local
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
# Sobe stack completa (Postgres + Auth + Edge Functions runtime + Storage)
|
|
441
|
+
supabase start
|
|
442
|
+
|
|
443
|
+
# Rodar tests
|
|
444
|
+
deno test --allow-all supabase/functions/_tests/ --env-file .env.local
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
`--allow-all` permite acesso file system + network + env vars (Deno é sandboxed por default).
|
|
448
|
+
|
|
449
|
+
`--env-file .env.local` carrega env vars de arquivo local. Sem essa flag, `Deno.env.get(...)` retorna `undefined`.
|
|
450
|
+
|
|
451
|
+
### Estrutura `.env.local` (gitignored)
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
# supabase/functions/.env.local (gitignored — gerado dinamicamente)
|
|
455
|
+
SUPABASE_URL=http://127.0.0.1:54321
|
|
456
|
+
SUPABASE_ANON_KEY=eyJ...local-anon-key...
|
|
457
|
+
SUPABASE_SERVICE_ROLE_KEY=eyJ...local-service-role-key...
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
**Caveat:** `supabase start` imprime essas keys no output. Capture-as automaticamente:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
# Linux/macOS
|
|
464
|
+
supabase status -o env > supabase/functions/.env.local
|
|
465
|
+
|
|
466
|
+
# Windows PowerShell
|
|
467
|
+
supabase status -o env | Out-File -Encoding utf8 supabase/functions/.env.local
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Atualizar `.env.local` a cada `supabase start` — keys mudam se containers forem recriados.
|
|
471
|
+
|
|
472
|
+
### Importar handler diretamente (test unitário)
|
|
473
|
+
|
|
474
|
+
Alternativa ao test via HTTP — importar handler direto:
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
// supabase/functions/_tests/hello_unit_test.ts
|
|
478
|
+
import { assertEquals } from "https://deno.land/std@0.221.0/assert/mod.ts";
|
|
479
|
+
|
|
480
|
+
// Assumindo hello/index.ts exporta handler
|
|
481
|
+
import { handler } from "../hello/index.ts";
|
|
482
|
+
|
|
483
|
+
Deno.test("handler returns 200 for valid input", async () => {
|
|
484
|
+
const req = new Request("http://localhost/hello", {
|
|
485
|
+
method: "POST",
|
|
486
|
+
headers: { "Content-Type": "application/json" },
|
|
487
|
+
body: JSON.stringify({ name: "World" }),
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const res = await handler(req);
|
|
491
|
+
assertEquals(res.status, 200);
|
|
492
|
+
|
|
493
|
+
const data = await res.json();
|
|
494
|
+
assertEquals(data.message, "Hello World!");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
Deno.test("handler returns 400 for invalid input", async () => {
|
|
498
|
+
const req = new Request("http://localhost/hello", {
|
|
499
|
+
method: "POST",
|
|
500
|
+
headers: { "Content-Type": "application/json" },
|
|
501
|
+
body: JSON.stringify({}),
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const res = await handler(req);
|
|
505
|
+
assertEquals(res.status, 400);
|
|
506
|
+
});
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
**Vantagens vs HTTP test:**
|
|
510
|
+
- Mais rápido (sem network roundtrip)
|
|
511
|
+
- Testa lógica isolada do handler
|
|
512
|
+
- Não requer `supabase start` (apenas Deno runtime)
|
|
513
|
+
|
|
514
|
+
**Desvantagens:**
|
|
515
|
+
- Não exercita auth middleware (JWT verification)
|
|
516
|
+
- Não exercita rate limiting / CORS / Deno.serve setup
|
|
517
|
+
- Mais frágil a mudanças no boilerplate da Edge Function
|
|
518
|
+
|
|
519
|
+
**Canônico:** combinar AMBOS — unit tests do handler (rápidos) + smoke test via HTTP (validação E2E mínima).
|
|
520
|
+
|
|
521
|
+
### Integração CI (cross-ref Phase 151 CI-06 `functions-tests.yml`)
|
|
522
|
+
|
|
523
|
+
Workflow canônico em `.github/workflows/functions-tests.yml`:
|
|
524
|
+
|
|
525
|
+
```yaml
|
|
526
|
+
name: functions-tests
|
|
527
|
+
on:
|
|
528
|
+
pull_request:
|
|
529
|
+
jobs:
|
|
530
|
+
build:
|
|
531
|
+
runs-on: ubuntu-latest
|
|
532
|
+
steps:
|
|
533
|
+
- uses: actions/checkout@v4
|
|
534
|
+
- uses: supabase/setup-cli@v1
|
|
535
|
+
with:
|
|
536
|
+
version: latest
|
|
537
|
+
- uses: denoland/setup-deno@v2
|
|
538
|
+
with:
|
|
539
|
+
deno-version: latest
|
|
540
|
+
- run: supabase start
|
|
541
|
+
- name: Generate .env.local
|
|
542
|
+
run: supabase status -o env > supabase/functions/.env.local
|
|
543
|
+
- run: deno test --allow-all supabase/functions/_tests/ --env-file supabase/functions/.env.local
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Required check `functions-tests / build` deve ser obrigatório em branch protection rule para `main`.
|
|
547
|
+
|
|
548
|
+
### Caveat — fixtures DB para Edge Function tests
|
|
549
|
+
|
|
550
|
+
Edge Function pode escrever em DB. Tests precisam:
|
|
551
|
+
|
|
552
|
+
1. Inserir fixtures via SQL antes do test
|
|
553
|
+
2. Limpar após o test (ou cada test recria estado)
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
|
557
|
+
|
|
558
|
+
const supabase = createClient(
|
|
559
|
+
Deno.env.get("SUPABASE_URL")!,
|
|
560
|
+
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!, // bypass RLS para setup
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
Deno.test("create-invite writes to org_invites table", async () => {
|
|
564
|
+
// Setup
|
|
565
|
+
await supabase.from("organizations").insert({ id: "org-1", name: "Test" });
|
|
566
|
+
|
|
567
|
+
// Act
|
|
568
|
+
const { data } = await supabase.functions.invoke("create-invite", {
|
|
569
|
+
body: { org_id: "org-1", email: "new@x.com", role: "member" },
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// Assert
|
|
573
|
+
assertEquals(data.success, true);
|
|
574
|
+
|
|
575
|
+
const { data: invites } = await supabase
|
|
576
|
+
.from("org_invites")
|
|
577
|
+
.select("*")
|
|
578
|
+
.eq("org_id", "org-1");
|
|
579
|
+
assertEquals(invites?.length, 1);
|
|
580
|
+
|
|
581
|
+
// Teardown
|
|
582
|
+
await supabase.from("org_invites").delete().eq("org_id", "org-1");
|
|
583
|
+
await supabase.from("organizations").delete().eq("id", "org-1");
|
|
584
|
+
});
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**Caveat:** ao contrário de pgTAP (transactional rollback automático), Deno tests precisam de cleanup MANUAL. Considere padrão `beforeEach/afterEach`:
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
async function cleanup() {
|
|
591
|
+
await supabase.from("org_invites").delete().neq("id", "00000000-0000-0000-0000-000000000000");
|
|
592
|
+
await supabase.from("organizations").delete().neq("id", "00000000-0000-0000-0000-000000000000");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
Deno.test({
|
|
596
|
+
name: "create-invite test",
|
|
597
|
+
async fn() {
|
|
598
|
+
await cleanup(); // estado limpo antes
|
|
599
|
+
// ... test body ...
|
|
600
|
+
await cleanup(); // teardown
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Ou usar `Deno.test.beforeEach` (Deno 1.40+):
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
Deno.test.beforeEach(cleanup);
|
|
609
|
+
Deno.test.afterEach(cleanup);
|
|
610
|
+
|
|
611
|
+
Deno.test("create-invite writes to org_invites", async () => {
|
|
612
|
+
// ...
|
|
613
|
+
});
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
## Pattern 4: Cross-ref legacy-characterizer — pgTAP como mecanismo de characterization (TEST-04)
|
|
617
|
+
|
|
618
|
+
### Princípio canônico (Feathers cap 13)
|
|
619
|
+
|
|
620
|
+
**Legacy code = código sem testes** (definição Feathers, não estética). PG function/trigger/policy com > 100 linhas e sem tests = legacy, mesmo se escrita ontem.
|
|
621
|
+
|
|
622
|
+
Antes de refatorar PG function legada, **characterize first** — capture comportamento atual como pgTAP tests. Tests viram oracle imutável. Refactor preserva oracle. Bug fix vem em PR separado depois.
|
|
623
|
+
|
|
624
|
+
### Workflow canônico de characterization de PG function
|
|
625
|
+
|
|
626
|
+
```text
|
|
627
|
+
1. Identificar PG function alvo do refactor
|
|
628
|
+
Exemplo: public.calculate_invoice_total(uuid) — 200 linhas, sem tests
|
|
629
|
+
|
|
630
|
+
2. Inventariar inputs/outputs
|
|
631
|
+
Inputs:
|
|
632
|
+
- parâmetro: invoice_id uuid
|
|
633
|
+
- reads: invoices, line_items, discounts, taxes (4 tabelas)
|
|
634
|
+
- reads globais: current_setting('app.tax_rate')
|
|
635
|
+
Outputs:
|
|
636
|
+
- return: numeric(10,2)
|
|
637
|
+
- side effects: insert em audit_log + update em invoices.calculated_at
|
|
638
|
+
|
|
639
|
+
3. Para cada grupo de equivalência (5+ inputs):
|
|
640
|
+
a. Construir input (fixture)
|
|
641
|
+
b. Executar function REAL — sem mocks ainda
|
|
642
|
+
c. Capturar output completo + side effects
|
|
643
|
+
d. REVISAR linha por linha — marcar bugs conhecidos como comments
|
|
644
|
+
e. Salvar como pgTAP assertion
|
|
645
|
+
|
|
646
|
+
4. Escrever pgTAP test file
|
|
647
|
+
- plan(N) declara N grupos × M assertions cada
|
|
648
|
+
- results_eq para return value
|
|
649
|
+
- results_eq para side effects (linhas em audit_log)
|
|
650
|
+
- throws_ok para edge cases que esperam erro
|
|
651
|
+
|
|
652
|
+
5. supabase test db — TODOS verdes = baseline estabelecido
|
|
653
|
+
|
|
654
|
+
6. Refactor pode começar
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### Exemplo canônico
|
|
658
|
+
|
|
659
|
+
```sql
|
|
660
|
+
-- supabase/tests/calculate_invoice_total_characterization_test.sql
|
|
661
|
+
begin;
|
|
662
|
+
select plan(10);
|
|
663
|
+
|
|
664
|
+
-- ===== GRUPO 1: invoice típica com 1 line item =====
|
|
665
|
+
-- Setup
|
|
666
|
+
insert into public.invoices (id, customer_id, status) values
|
|
667
|
+
('inv-001', 'cust-001', 'pending');
|
|
668
|
+
insert into public.line_items (invoice_id, sku, qty, unit_price) values
|
|
669
|
+
('inv-001', 'SKU-1', 2, 50.00);
|
|
670
|
+
|
|
671
|
+
-- Test 1: return value
|
|
672
|
+
select results_eq(
|
|
673
|
+
$$select public.calculate_invoice_total('inv-001'::uuid)$$,
|
|
674
|
+
$$values (100.00::numeric)$$,
|
|
675
|
+
'GROUP 1: typical invoice 1 line item — total 100.00'
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
-- Test 2: side effect — audit_log row created
|
|
679
|
+
select results_eq(
|
|
680
|
+
$$select event_type, target_id from public.audit_log where target_id = 'inv-001'::uuid$$,
|
|
681
|
+
$$values ('invoice_total_calculated'::text, 'inv-001'::uuid)$$,
|
|
682
|
+
'GROUP 1: audit_log row created'
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
-- ===== GRUPO 2: invoice com discount =====
|
|
686
|
+
insert into public.invoices (id, customer_id, status) values
|
|
687
|
+
('inv-002', 'cust-001', 'pending');
|
|
688
|
+
insert into public.line_items (invoice_id, sku, qty, unit_price) values
|
|
689
|
+
('inv-002', 'SKU-2', 1, 200.00);
|
|
690
|
+
insert into public.discounts (invoice_id, percentage) values
|
|
691
|
+
('inv-002', 10.0); -- 10% discount
|
|
692
|
+
|
|
693
|
+
select results_eq(
|
|
694
|
+
$$select public.calculate_invoice_total('inv-002'::uuid)$$,
|
|
695
|
+
$$values (180.00::numeric)$$,
|
|
696
|
+
'GROUP 2: invoice with 10% discount — total 180.00'
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
-- ===== GRUPO 3: invoice vazia (BUG #1 conhecido — retorna 0, deveria raise) =====
|
|
700
|
+
insert into public.invoices (id, customer_id, status) values
|
|
701
|
+
('inv-003', 'cust-001', 'pending');
|
|
702
|
+
-- NO line_items inseridos
|
|
703
|
+
|
|
704
|
+
-- BUG #1: function retorna 0 para invoice vazia, deveria raise 'invalid_invoice'
|
|
705
|
+
-- Preservar comportamento bugado durante refactor; fix em PR separado.
|
|
706
|
+
select results_eq(
|
|
707
|
+
$$select public.calculate_invoice_total('inv-003'::uuid)$$,
|
|
708
|
+
$$values (0.00::numeric)$$,
|
|
709
|
+
'GROUP 3 [BUG #1]: empty invoice returns 0 (should raise — preserved as oracle)'
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
-- ===== GRUPO 4: invoice inexistente — espera erro =====
|
|
713
|
+
select throws_ok(
|
|
714
|
+
$$select public.calculate_invoice_total('00000000-0000-0000-0000-000000000000'::uuid)$$,
|
|
715
|
+
'P0001',
|
|
716
|
+
'GROUP 4: non-existent invoice raises P0001'
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
-- ===== GRUPO 5: invoice com NEG line items (edge case histórico) =====
|
|
720
|
+
insert into public.invoices (id, customer_id, status) values
|
|
721
|
+
('inv-005', 'cust-001', 'pending');
|
|
722
|
+
insert into public.line_items (invoice_id, sku, qty, unit_price) values
|
|
723
|
+
('inv-005', 'SKU-3', -1, 100.00); -- qty NEGATIVO (devolução)
|
|
724
|
+
|
|
725
|
+
select results_eq(
|
|
726
|
+
$$select public.calculate_invoice_total('inv-005'::uuid)$$,
|
|
727
|
+
$$values (-100.00::numeric)$$,
|
|
728
|
+
'GROUP 5: negative qty produces negative total (refund flow)'
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
-- ... outros grupos (boundary valid, side-effect heavy, etc.) ...
|
|
732
|
+
|
|
733
|
+
select * from finish();
|
|
734
|
+
rollback;
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### Bugs preservados como comments
|
|
738
|
+
|
|
739
|
+
**Crítico:** characterization captura o que código FAZ, não o que DEVERIA fazer. Bugs conhecidos viram comments inline (`-- BUG #X: deveria Y, é Z`). Refactor preserva o oracle (incluindo bugs). Bug fix vem em PR separado **depois** do refactor, com seu próprio teste.
|
|
740
|
+
|
|
741
|
+
Exemplo do test acima: GROUP 3 marca `BUG #1` — função retorna 0 para invoice vazia em vez de raise exception. Refactor manterá esse comportamento. PR de bug fix subsequente alterará a assertion para `throws_ok` e fixará a função.
|
|
742
|
+
|
|
743
|
+
### Behavioral coverage check (mutation testing — recomendado)
|
|
744
|
+
|
|
745
|
+
pgTAP cobre o **que** o código faz; mutation testing valida que tests **detectam regressão**. Para PG functions críticas:
|
|
746
|
+
|
|
747
|
+
1. Rodar `supabase test db` — baseline verde
|
|
748
|
+
2. Aplicar mutation (manualmente — não há tool padrão para PG mutation; use search-replace deliberado):
|
|
749
|
+
- Trocar `+` por `-` na cálculo
|
|
750
|
+
- Trocar `>` por `>=` em CHECK
|
|
751
|
+
- Comentar `RAISE EXCEPTION` em edge case
|
|
752
|
+
3. Rodar `supabase test db` — esperado vermelho. Se ficar verde, characterization tem ponto cego.
|
|
753
|
+
4. Adicionar test que cobre o ponto cego
|
|
754
|
+
5. Reverter mutation; suite volta a verde
|
|
755
|
+
|
|
756
|
+
Cross-ref skill `ai-mutation-tester` (v1.20) — pattern análogo para JavaScript/TypeScript.
|
|
757
|
+
|
|
758
|
+
### Quando pgTAP characterization é mandatório
|
|
759
|
+
|
|
760
|
+
Aplicar pgTAP characterization (cross-ref skill `pre-refactor-characterization` v1.18) ANTES de refactor de:
|
|
761
|
+
|
|
762
|
+
- PG function > 100 linhas sem tests existentes
|
|
763
|
+
- PG function consumida por > 3 callers (alto blast radius se regredir)
|
|
764
|
+
- RLS policy complexa (> 5 OR conditions, ou referencia auth.uid() + claim + subquery)
|
|
765
|
+
- Trigger BEFORE/AFTER que muta dados em outras tabelas
|
|
766
|
+
- Function exposta via Edge Function ou PostgREST RPC (contrato externo)
|
|
767
|
+
|
|
768
|
+
## Anti-patterns
|
|
769
|
+
|
|
770
|
+
### Anti-pattern 1: Tests sem `rollback`
|
|
771
|
+
|
|
772
|
+
**Errado:**
|
|
773
|
+
|
|
774
|
+
```sql
|
|
775
|
+
-- supabase/tests/employees_test.sql
|
|
776
|
+
select plan(2);
|
|
777
|
+
|
|
778
|
+
select has_table('public', 'employees', 'employees exists');
|
|
779
|
+
|
|
780
|
+
insert into public.employees (name) values ('Test User'); -- SEM begin/rollback!
|
|
781
|
+
|
|
782
|
+
select results_eq(
|
|
783
|
+
'select count(*) from public.employees where name = ''Test User''',
|
|
784
|
+
'values (1::bigint)',
|
|
785
|
+
'inserted user found'
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
select * from finish();
|
|
789
|
+
```
|
|
790
|
+
|
|
791
|
+
**Por quê:** `INSERT` sem `begin; ... rollback;` é **persistido** no DB local. Após rodar `supabase test db`:
|
|
792
|
+
|
|
793
|
+
- Teste pode "passar" na primeira run mas falhar na segunda (`count` já não é 1, é 2)
|
|
794
|
+
- DB local poluído com dados sintéticos — afeta outros tests
|
|
795
|
+
- Se rodar em CI, novo container DB cada run = OK; mas localmente é problema
|
|
796
|
+
|
|
797
|
+
**Certo:**
|
|
798
|
+
|
|
799
|
+
```sql
|
|
800
|
+
-- supabase/tests/employees_test.sql
|
|
801
|
+
begin; -- inicia transação
|
|
802
|
+
select plan(2);
|
|
803
|
+
|
|
804
|
+
select has_table('public', 'employees', 'employees exists');
|
|
805
|
+
|
|
806
|
+
insert into public.employees (name) values ('Test User');
|
|
807
|
+
|
|
808
|
+
select results_eq(
|
|
809
|
+
'select count(*) from public.employees where name = ''Test User''',
|
|
810
|
+
'values (1::bigint)',
|
|
811
|
+
'inserted user found'
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
select * from finish();
|
|
815
|
+
rollback; -- desfaz INSERT
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
`rollback` é **incondicional** — mesmo se `finish()` falhar, transação aborta no rollback. Estado DB inalterado.
|
|
819
|
+
|
|
820
|
+
### Anti-pattern 2: Esquecer `plan(N)` — testes silenciosos
|
|
821
|
+
|
|
822
|
+
**Errado:**
|
|
823
|
+
|
|
824
|
+
```sql
|
|
825
|
+
begin;
|
|
826
|
+
-- SEM plan() declarado
|
|
827
|
+
|
|
828
|
+
select has_table('public', 'employees', 'test 1');
|
|
829
|
+
select has_column('public', 'employees', 'name', 'test 2');
|
|
830
|
+
select col_type_is('public', 'employees', 'name', 'text', 'test 3');
|
|
831
|
+
|
|
832
|
+
select * from finish();
|
|
833
|
+
rollback;
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
**Por quê:** sem `plan(N)`, pgTAP **não sabe** quantos tests esperar. Output ainda mostra `ok 1`, `ok 2`, `ok 3` mas:
|
|
837
|
+
|
|
838
|
+
- TAP harness pode interpretar como "0 tests planned, 3 executed" → falha silenciosa em CI
|
|
839
|
+
- `finish()` reporta `1..0` (planejados zero) — exit code pode ser 0 (sucesso) mesmo se tests **falharem** silenciosamente
|
|
840
|
+
- Adicionar test novo não é notado (deveria mudar `plan(N)` para `plan(N+1)`, mas como não tem plan, esquecer é invisível)
|
|
841
|
+
|
|
842
|
+
**Certo:**
|
|
843
|
+
|
|
844
|
+
```sql
|
|
845
|
+
begin;
|
|
846
|
+
select plan(3); -- DECLARA EXPLICITAMENTE 3 tests
|
|
847
|
+
|
|
848
|
+
select has_table('public', 'employees', 'test 1');
|
|
849
|
+
select has_column('public', 'employees', 'name', 'test 2');
|
|
850
|
+
select col_type_is('public', 'employees', 'name', 'text', 'test 3');
|
|
851
|
+
|
|
852
|
+
select * from finish();
|
|
853
|
+
rollback;
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
`plan(N)` é **obrigatório**. Se rodar 4 mas declarou 3, pgTAP reporta `# Looks like you planned 3 tests but ran 4.` — falha.
|
|
857
|
+
|
|
858
|
+
Manter `plan(N)` atualizado é parte do contrato — se adiciona test, atualiza plan.
|
|
859
|
+
|
|
860
|
+
### Anti-pattern 3: Tests Deno sem `.env.local`
|
|
861
|
+
|
|
862
|
+
**Errado:**
|
|
863
|
+
|
|
864
|
+
```bash
|
|
865
|
+
# rodar tests sem env file
|
|
866
|
+
deno test --allow-all supabase/functions/_tests/
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
```typescript
|
|
870
|
+
// dentro do test
|
|
871
|
+
const response = await fetch(`${Deno.env.get("SUPABASE_URL")}/functions/v1/hello`);
|
|
872
|
+
// Deno.env.get("SUPABASE_URL") retorna undefined
|
|
873
|
+
// fetch("undefined/functions/v1/hello") = URL inválida
|
|
874
|
+
// Error: Invalid URL: 'undefined/functions/v1/hello'
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
**Por quê:** sem `--env-file`, `Deno.env.get(...)` retorna `undefined`. `fetch` com URL inválida lança erro genérico. Mensagem confusa — dev pensa que tem bug no handler, na verdade é env config.
|
|
878
|
+
|
|
879
|
+
**Certo:**
|
|
880
|
+
|
|
881
|
+
```bash
|
|
882
|
+
# Gerar .env.local
|
|
883
|
+
supabase status -o env > supabase/functions/.env.local
|
|
884
|
+
|
|
885
|
+
# Rodar com env file
|
|
886
|
+
deno test --allow-all supabase/functions/_tests/ --env-file supabase/functions/.env.local
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
```typescript
|
|
890
|
+
// Defensive: assert env vars existem (fail-fast com mensagem clara)
|
|
891
|
+
const SUPABASE_URL = Deno.env.get("SUPABASE_URL");
|
|
892
|
+
const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_ANON_KEY");
|
|
893
|
+
|
|
894
|
+
if (!SUPABASE_URL || !SUPABASE_ANON_KEY) {
|
|
895
|
+
throw new Error(
|
|
896
|
+
"Missing SUPABASE_URL or SUPABASE_ANON_KEY. " +
|
|
897
|
+
"Run: supabase status -o env > supabase/functions/.env.local"
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
Em CI, geração de `.env.local` deve ser **step explícito** do workflow (cross-ref Phase 151 CI-06):
|
|
903
|
+
|
|
904
|
+
```yaml
|
|
905
|
+
- name: Generate .env.local
|
|
906
|
+
run: supabase status -o env > supabase/functions/.env.local
|
|
907
|
+
- run: deno test --allow-all supabase/functions/_tests/ --env-file supabase/functions/.env.local
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
### Anti-pattern 4: Tratar pgTAP como "testes completos"
|
|
911
|
+
|
|
912
|
+
**Errado:** time considera que pgTAP + Deno tests cobrem TODO o sistema → para CI: "se database-tests e functions-tests passam, deploy é seguro".
|
|
913
|
+
|
|
914
|
+
**Por quê:** pgTAP cobre **schema e PG logic**; Deno tests cobrem **Edge Function HTTP handlers**. NENHUM dos dois cobre:
|
|
915
|
+
|
|
916
|
+
- Cliente frontend interagindo com Supabase via `@supabase/supabase-js` (race conditions, retry, optimistic UI)
|
|
917
|
+
- Comportamento Realtime (subscriptions, broadcast, presence)
|
|
918
|
+
- Auth flows end-to-end (signup → email confirm → first login → setup wizard)
|
|
919
|
+
- RLS visto do ponto de vista do cliente autenticado (JWT real, não simulado via `set_config`)
|
|
920
|
+
- Performance / latência sob carga
|
|
921
|
+
- Edge cases de browser (CORS, cookies, localStorage)
|
|
922
|
+
|
|
923
|
+
**Certo:** treat pgTAP + Deno como **two layers of defense-in-depth**, não substituto de integration tests:
|
|
924
|
+
|
|
925
|
+
```text
|
|
926
|
+
Pirâmide de testes Supabase (canônica):
|
|
927
|
+
|
|
928
|
+
/\
|
|
929
|
+
/E2E\ ← Playwright/Cypress: 5-10 critical user journeys
|
|
930
|
+
/------\
|
|
931
|
+
/ INT. \ ← Vitest/Jest com Supabase real: auth flow, multi-tenant
|
|
932
|
+
/----------\
|
|
933
|
+
/ DENO TESTS \ ← pattern 3: Edge Functions HTTP handlers
|
|
934
|
+
/--------------\
|
|
935
|
+
/ pgTAP \ ← pattern 1: schema + RLS + PG functions
|
|
936
|
+
/------------------\
|
|
937
|
+
| Unit tests app | ← Vitest/Jest cliente: componentes React, helpers
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
Cada camada cobre **incidentes diferentes**:
|
|
941
|
+
- pgTAP: regressão em schema/policy/function
|
|
942
|
+
- Deno: regressão em Edge Function logic
|
|
943
|
+
- Integration: regressão em cliente ↔ Supabase wire
|
|
944
|
+
- E2E: regressão em user journey
|
|
945
|
+
|
|
946
|
+
Sem todas as camadas, gaps de cobertura existem. CI deve ter **separate required checks** por camada.
|
|
947
|
+
|
|
948
|
+
### Anti-pattern 5: Snapshot pgTAP sem revisão (characterization shortcut)
|
|
949
|
+
|
|
950
|
+
**Errado:** copy-paste output bruto de query como `results_eq` expected sem inspecionar — "se runs verde, está OK".
|
|
951
|
+
|
|
952
|
+
```sql
|
|
953
|
+
-- characterization sem revisão
|
|
954
|
+
select results_eq(
|
|
955
|
+
$$select public.complex_function('input-1')$$,
|
|
956
|
+
$$values ('a', '2026-05-11T10:23:45.123456Z'::timestamptz, 'token_abc123xyz', 42, '00000000-1234-5678-9abc-def012345678'::uuid)$$,
|
|
957
|
+
'preserved as-is'
|
|
958
|
+
);
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
**Por quê:** output bruto pode incluir:
|
|
962
|
+
|
|
963
|
+
- **PII** (emails, names) — vazam para git history
|
|
964
|
+
- **Tokens/secrets** — vazam para git history (irreversível)
|
|
965
|
+
- **Timestamps voláteis** — test será flaky em run subsequente
|
|
966
|
+
- **UUIDs locais** — gerados por `gen_random_uuid()`, mudam a cada run
|
|
967
|
+
|
|
968
|
+
CI fica "verde" porque snapshot bate consigo mesmo da última run, mas:
|
|
969
|
+
- Adicionar fixture nova quebra (timestamp diferente)
|
|
970
|
+
- PR review humano não detecta bugs (não inspecionou output)
|
|
971
|
+
- Secrets podem ser commitados sem aviso
|
|
972
|
+
|
|
973
|
+
**Certo:** revisão linha-por-linha + sanitização antes de salvar (cross-ref skill `legacy-characterization-tests` Pattern 6):
|
|
974
|
+
|
|
975
|
+
```sql
|
|
976
|
+
-- characterization com sanitização determinística
|
|
977
|
+
begin;
|
|
978
|
+
-- 1. Fixar clock para determinismo
|
|
979
|
+
select set_config('app.fake_now', '2026-05-11T10:00:00Z', true);
|
|
980
|
+
|
|
981
|
+
-- 2. Fixar UUIDs determinísticos via fixture
|
|
982
|
+
insert into public.things (id, name, created_at) values
|
|
983
|
+
('00000000-0000-0000-0000-000000000001', 'Test', '2026-05-11T10:00:00Z'::timestamptz);
|
|
984
|
+
|
|
985
|
+
-- 3. Snapshot review:
|
|
986
|
+
-- - Email 'alice@x.com' está no fixture (não real PII)
|
|
987
|
+
-- - Timestamp '2026-05-11T10:00:00Z' está congelado (fake clock)
|
|
988
|
+
-- - UUID '00000000...001' está determinístico (fixture)
|
|
989
|
+
-- - Token 'token_test_abc' está sanitized (não real)
|
|
990
|
+
select results_eq(
|
|
991
|
+
$$select id, name, created_at from public.things where id = '00000000-0000-0000-0000-000000000001'::uuid$$,
|
|
992
|
+
$$values ('00000000-0000-0000-0000-000000000001'::uuid, 'Test'::text, '2026-05-11T10:00:00Z'::timestamptz)$$,
|
|
993
|
+
'thing fixture matches characterization oracle (reviewed 2026-05-11)'
|
|
994
|
+
);
|
|
995
|
+
|
|
996
|
+
select * from finish();
|
|
997
|
+
rollback;
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
**Crítico:** comentar quando snapshot foi revisado + por quem. Commit message: "characterize complex_function — reviewed 2026-05-11, bugs noted in comments".
|
|
1001
|
+
|
|
1002
|
+
## Cross-suite integration (v1.27)
|
|
1003
|
+
|
|
1004
|
+
Esta skill é **complemento essencial** da skill Phase 151 `supabase-ci-cd-github-actions`:
|
|
1005
|
+
|
|
1006
|
+
- Phase 151 estabelece workflows CI (`database-tests.yml` + `functions-tests.yml`) que executam `supabase test db` + `deno test --allow-all`
|
|
1007
|
+
- Phase 152 (ESTA) detalha a **sintaxe canônica** dos tests que esses workflows consomem
|
|
1008
|
+
- Forward-ref de Phase 151 é fechado por esta skill (Pattern 5 da Phase 151 referenciava "skill futura `supabase-pgtap-testing` Phase 152")
|
|
1009
|
+
|
|
1010
|
+
Cross-refs com skills existentes v1.x:
|
|
1011
|
+
|
|
1012
|
+
- **`legacy-characterization-tests` (v1.16)** — pgTAP é o mecanismo canônico para implementar characterization (cap 13 Feathers) em PG legado; Pattern 4 desta skill é especialização para Postgres
|
|
1013
|
+
- **`pre-refactor-characterization` (v1.18)** — gate auto-trigger que bloqueia refactor sem characterization; pgTAP satisfaz a pré-condição para PG functions
|
|
1014
|
+
- **`ai-mutation-tester` (v1.20)** — mutation testing complementar; valida que pgTAP detecta regressão
|
|
1015
|
+
- **`supabase-database-functions`** — PG functions criadas seguindo essa skill são prime target de pgTAP (SECURITY INVOKER + SET search_path = '' são testáveis)
|
|
1016
|
+
- **`supabase-rls-policies` (v1.23)** — RLS policies validadas via `throws_like '%permission denied%'` + `results_eq` com diferentes JWT claims
|
|
1017
|
+
- **`supabase-edge-functions`** — Edge Functions cobertas por Deno tests (Pattern 3)
|
|
1018
|
+
- **`supabase-postgres-roles` (v1.26)** — testes de roles validáveis via `has_role`, `set role test_role; ...; reset role`
|
|
1019
|
+
- **`supabase-custom-claims-rbac` (v1.25)** — testes de auth hook validáveis via fixture JWT + `set_config('request.jwt.claim.user_role', 'admin', true)`
|
|
1020
|
+
|
|
1021
|
+
Base para agent futuro v1.27:
|
|
1022
|
+
|
|
1023
|
+
- **`supabase-cicd-pipeline-implementer` (Phase 154, futura)** — agent que materializa workflows + tests; consome esta skill para gerar test files iniciais junto com migrations
|
|
1024
|
+
|
|
1025
|
+
Pattern de handoff cooperativo herdado v1.23-v1.26: **architect** projeta strategy → **test-writer** materializa pgTAP/Deno tests → **release-pipeline-auditor** (v1.10) audita coverage do pipeline. Nenhum agente descarta upstream — handoff cooperativo (princípio canônico v1.23).
|
|
1026
|
+
|
|
1027
|
+
### Casos de uso por agent
|
|
1028
|
+
|
|
1029
|
+
| Agent | Como consome esta skill |
|
|
1030
|
+
|-------|-------------------------|
|
|
1031
|
+
| `supabase-migration-writer` | Gera migration + pgTAP test inicial (has_table, has_column, has_pk) |
|
|
1032
|
+
| `supabase-rls-writer` (v1.23) | Gera RLS policies + pgTAP test (throws_like '%permission denied%' para cross-tenant) |
|
|
1033
|
+
| `supabase-edge-fn-writer` | Gera Edge Function + Deno test (handler retorna 200/400 esperado) |
|
|
1034
|
+
| `legacy-characterizer` | Gera pgTAP characterization tests para PG function legada (cap 13 Feathers) |
|
|
1035
|
+
| `refactor-safety-auditor` | Verifica existência de pgTAP tests antes de permitir refactor PG |
|
|
1036
|
+
| `supabase-cicd-pipeline-implementer` (Phase 154 futura) | Gera workflows `database-tests.yml` + `functions-tests.yml` + tests iniciais |
|
|
1037
|
+
|
|
1038
|
+
## Ver também
|
|
1039
|
+
|
|
1040
|
+
- [supabase-ci-cd-github-actions](../supabase-ci-cd-github-actions/SKILL.md) (v1.27, Phase 151) — workflows CI que executam `supabase test db` + `deno test`
|
|
1041
|
+
- [legacy-characterization-tests](../legacy-characterization-tests/SKILL.md) (v1.16) — cap 13 Feathers; pgTAP é mecanismo canônico para Postgres
|
|
1042
|
+
- [pre-refactor-characterization](../pre-refactor-characterization/SKILL.md) (v1.18) — gate que pgTAP satisfaz para PG functions
|
|
1043
|
+
- [supabase-database-functions](../supabase-database-functions/SKILL.md) — PG functions testáveis via pgTAP
|
|
1044
|
+
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) (v1.23) — RLS testável via `throws_like` + `set_config jwt claim`
|
|
1045
|
+
- [supabase-rls-defense-in-depth](../supabase-rls-defense-in-depth/SKILL.md) (v1.23) — Camadas testáveis em pgTAP
|
|
1046
|
+
- [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions cobertas por Deno tests
|
|
1047
|
+
- [supabase-postgres-roles](../supabase-postgres-roles/SKILL.md) (v1.26) — `has_role`, `set role` testáveis
|
|
1048
|
+
- [supabase-custom-claims-rbac](../supabase-custom-claims-rbac/SKILL.md) (v1.25) — fixture JWT via `set_config('request.jwt.claim.*')`
|
|
1049
|
+
- [supabase-migrations](../supabase-migrations/SKILL.md) (v1.23) — migration cria tabela; pgTAP test valida
|
|
1050
|
+
- [ai-mutation-tester](../../agents/ai-mutation-tester.md) (v1.20) — mutation testing complementar
|
|
1051
|
+
- [glossário compartilhado](../_shared-supabase/glossary.md) — termos pgTAP, TAP, plan(N), throws_ok, results_eq, supabase test db, deno test --allow-all
|
|
1052
|
+
- Doc oficial pgTAP: [pgTAP Documentation](https://pgtap.org/documentation.html)
|
|
1053
|
+
- Doc oficial Supabase: [Testing with pgTAP](https://supabase.com/docs/guides/database/extensions/pgtap), [Testing Edge Functions](https://supabase.com/docs/guides/functions/unit-test)
|