@luanpdd/kit-mcp 1.30.2 → 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.
Files changed (347) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -168
  3. package/gates/agent-no-recursive-dispatch.md +84 -82
  4. package/kit/COMANDOS.md +138 -138
  5. package/kit/README.md +76 -76
  6. package/kit/agents/advisor-researcher.md +107 -106
  7. package/kit/agents/ai-mutation-tester.md +1 -0
  8. package/kit/agents/assumptions-analyzer.md +108 -107
  9. package/kit/agents/audit-log-implementer.md +314 -313
  10. package/kit/agents/auditor-consistencia-isolamento.md +414 -413
  11. package/kit/agents/b2b-saas-architect.md +157 -156
  12. package/kit/agents/burn-rate-forecaster.md +1 -0
  13. package/kit/agents/cascading-failures-auditor.md +299 -298
  14. package/kit/agents/codebase-mapper.md +769 -768
  15. package/kit/agents/crm-pipeline-implementer.md +257 -256
  16. package/kit/agents/debugger.md +814 -813
  17. package/kit/agents/detector-tenant-quente.md +338 -337
  18. package/kit/agents/evolution-go-integrator.md +201 -200
  19. package/kit/agents/example-reviewer.md +22 -21
  20. package/kit/agents/executor.md +565 -564
  21. package/kit/agents/golden-signals-instrumenter.md +1 -0
  22. package/kit/agents/incident-investigator.md +1 -0
  23. package/kit/agents/integration-checker.md +201 -200
  24. package/kit/agents/invite-flow-implementer.md +190 -189
  25. package/kit/agents/legacy-characterizer.md +369 -368
  26. package/kit/agents/lgpd-compliance-auditor.md +296 -295
  27. package/kit/agents/load-shedding-instrumenter.md +1 -0
  28. package/kit/agents/multi-tenant-isolation-auditor.md +254 -253
  29. package/kit/agents/multi-tenant-rls-writer.md +341 -340
  30. package/kit/agents/nyquist-auditor.md +179 -178
  31. package/kit/agents/observability-coverage-auditor.md +316 -315
  32. package/kit/agents/observability-instrumenter.md +1 -0
  33. package/kit/agents/omm-auditor.md +1 -0
  34. package/kit/agents/org-onboarding-implementer.md +224 -223
  35. package/kit/agents/payload-capture-instrumenter.md +274 -273
  36. package/kit/agents/phase-researcher.md +697 -696
  37. package/kit/agents/plan-checker.md +273 -272
  38. package/kit/agents/planner.md +923 -922
  39. package/kit/agents/postmortem-writer.md +1 -0
  40. package/kit/agents/project-researcher.md +653 -652
  41. package/kit/agents/prr-conductor.md +1 -0
  42. package/kit/agents/refactor-safety-auditor.md +405 -404
  43. package/kit/agents/release-pipeline-auditor.md +1 -0
  44. package/kit/agents/research-synthesizer.md +246 -245
  45. package/kit/agents/roadmapper.md +678 -677
  46. package/kit/agents/schema-checker.md +1 -0
  47. package/kit/agents/seam-finder.md +360 -359
  48. package/kit/agents/shotgun-surgery-detector.md +350 -349
  49. package/kit/agents/slo-engineer.md +1 -0
  50. package/kit/agents/storytelling-analyst.md +1 -0
  51. package/kit/agents/supabase-architect.md +1 -0
  52. package/kit/agents/supabase-auth-bootstrapper.md +1 -0
  53. package/kit/agents/supabase-branching-architect.md +563 -562
  54. package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -777
  55. package/kit/agents/supabase-column-privileges-writer.md +400 -399
  56. package/kit/agents/supabase-edge-fn-tester.md +2 -1
  57. package/kit/agents/supabase-edge-fn-writer.md +2 -1
  58. package/kit/agents/supabase-migration-writer.md +386 -385
  59. package/kit/agents/supabase-rbac-implementer.md +393 -392
  60. package/kit/agents/supabase-realtime-implementer.md +364 -363
  61. package/kit/agents/supabase-rls-hardener.md +522 -521
  62. package/kit/agents/supabase-rls-writer.md +324 -323
  63. package/kit/agents/supabase-roles-implementer.md +356 -355
  64. package/kit/agents/supabase-storage-implementer.md +1 -0
  65. package/kit/agents/super-admin-implementer.md +282 -281
  66. package/kit/agents/toil-auditor.md +1 -0
  67. package/kit/agents/ui-auditor.md +438 -437
  68. package/kit/agents/ui-checker.md +303 -302
  69. package/kit/agents/ui-researcher.md +356 -355
  70. package/kit/agents/user-profiler.md +176 -175
  71. package/kit/agents/validador-evolucao-schema.md +336 -335
  72. package/kit/agents/verifier.md +729 -728
  73. package/kit/commands/adicionar-backlog.md +75 -75
  74. package/kit/commands/adicionar-fase.md +42 -42
  75. package/kit/commands/adicionar-tarefa.md +45 -45
  76. package/kit/commands/adicionar-testes.md +41 -41
  77. package/kit/commands/ajuda.md +21 -21
  78. package/kit/commands/atualizar.md +37 -37
  79. package/kit/commands/auditar-cascading.md +111 -111
  80. package/kit/commands/auditar-marco.md +179 -179
  81. package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
  82. package/kit/commands/auditar-refactor.md +219 -219
  83. package/kit/commands/auditar-release.md +109 -109
  84. package/kit/commands/auditar-uat.md +23 -23
  85. package/kit/commands/autonomo.md +40 -40
  86. package/kit/commands/branch-pr.md +24 -24
  87. package/kit/commands/burn-rate-status.md +408 -408
  88. package/kit/commands/capturar-payloads.md +193 -193
  89. package/kit/commands/caracterizar.md +212 -212
  90. package/kit/commands/concluir-marco.md +247 -247
  91. package/kit/commands/configuracoes.md +36 -36
  92. package/kit/commands/dados-distribuidos.md +188 -188
  93. package/kit/commands/definir-perfil.md +10 -10
  94. package/kit/commands/depurar.md +190 -190
  95. package/kit/commands/detectar-duplicacao.md +197 -197
  96. package/kit/commands/discutir-fase.md +131 -131
  97. package/kit/commands/encontrar-seams.md +136 -136
  98. package/kit/commands/entrar-discord.md +17 -17
  99. package/kit/commands/estatisticas.md +18 -18
  100. package/kit/commands/example-greeting.md +33 -33
  101. package/kit/commands/executar-fase.md +58 -58
  102. package/kit/commands/expresso.md +56 -56
  103. package/kit/commands/fase-ui.md +34 -34
  104. package/kit/commands/fazer.md +57 -57
  105. package/kit/commands/fio.md +125 -125
  106. package/kit/commands/fluxos-trabalho.md +64 -64
  107. package/kit/commands/forense.md +176 -176
  108. package/kit/commands/gerenciador.md +38 -38
  109. package/kit/commands/inserir-fase.md +31 -31
  110. package/kit/commands/legacy.md +263 -263
  111. package/kit/commands/limpeza.md +17 -17
  112. package/kit/commands/listar-hipoteses-fase.md +45 -45
  113. package/kit/commands/listar-workspaces.md +18 -18
  114. package/kit/commands/load-shedding.md +117 -117
  115. package/kit/commands/mapear-codebase.md +70 -70
  116. package/kit/commands/multi-tenant.md +163 -163
  117. package/kit/commands/nota.md +33 -33
  118. package/kit/commands/novo-marco.md +43 -43
  119. package/kit/commands/novo-projeto.md +41 -41
  120. package/kit/commands/novo-workspace.md +43 -43
  121. package/kit/commands/pausar-trabalho.md +37 -37
  122. package/kit/commands/perfil-usuario.md +45 -45
  123. package/kit/commands/pesquisar-fase.md +195 -195
  124. package/kit/commands/planejar-fase.md +67 -67
  125. package/kit/commands/planejar-lacunas.md +33 -33
  126. package/kit/commands/plantar-ideia.md +25 -25
  127. package/kit/commands/progresso.md +24 -24
  128. package/kit/commands/proximo.md +30 -30
  129. package/kit/commands/publicar.md +490 -490
  130. package/kit/commands/rapido.md +35 -35
  131. package/kit/commands/reaplicar-patches.md +124 -124
  132. package/kit/commands/refactor-seguro.md +321 -321
  133. package/kit/commands/relatorio-sessao.md +19 -19
  134. package/kit/commands/remover-fase.md +31 -31
  135. package/kit/commands/remover-workspace.md +26 -26
  136. package/kit/commands/resumo-marco.md +50 -50
  137. package/kit/commands/retomar-trabalho.md +40 -40
  138. package/kit/commands/revisar-backlog.md +60 -60
  139. package/kit/commands/revisar-ui.md +32 -32
  140. package/kit/commands/revisar.md +37 -37
  141. package/kit/commands/saude.md +21 -21
  142. package/kit/commands/setup-notion.md +93 -93
  143. package/kit/commands/storytelling.md +179 -179
  144. package/kit/commands/sync-main.md +68 -68
  145. package/kit/commands/validar-fase.md +35 -35
  146. package/kit/commands/verificar-tarefas.md +44 -44
  147. package/kit/commands/verificar-trabalho.md +64 -64
  148. package/kit/file-manifest.json +82 -81
  149. package/kit/framework/bin/lib/commands.cjs +959 -959
  150. package/kit/framework/bin/lib/config.cjs +442 -442
  151. package/kit/framework/bin/lib/core.cjs +1230 -1230
  152. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  153. package/kit/framework/bin/lib/init.cjs +1442 -1442
  154. package/kit/framework/bin/lib/milestone.cjs +252 -252
  155. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  156. package/kit/framework/bin/lib/phase.cjs +888 -888
  157. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  158. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  159. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  160. package/kit/framework/bin/lib/security.cjs +382 -382
  161. package/kit/framework/bin/lib/state.cjs +1031 -1031
  162. package/kit/framework/bin/lib/template.cjs +222 -222
  163. package/kit/framework/bin/lib/uat.cjs +282 -282
  164. package/kit/framework/bin/lib/verify.cjs +888 -888
  165. package/kit/framework/bin/lib/workstream.cjs +491 -491
  166. package/kit/framework/bin/tools.cjs +918 -918
  167. package/kit/framework/commands/workstreams.md +63 -63
  168. package/kit/framework/references/checkpoints.md +778 -778
  169. package/kit/framework/references/continuation-format.md +249 -249
  170. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  171. package/kit/framework/references/git-integration.md +295 -295
  172. package/kit/framework/references/git-planning-commit.md +38 -38
  173. package/kit/framework/references/model-profile-resolution.md +36 -36
  174. package/kit/framework/references/model-profiles.md +139 -139
  175. package/kit/framework/references/phase-argument-parsing.md +61 -61
  176. package/kit/framework/references/planning-config.md +202 -202
  177. package/kit/framework/references/questioning.md +162 -162
  178. package/kit/framework/references/tdd.md +263 -263
  179. package/kit/framework/references/ui-brand.md +160 -160
  180. package/kit/framework/references/user-profiling.md +657 -657
  181. package/kit/framework/references/verification-patterns.md +612 -612
  182. package/kit/framework/references/workstream-flag.md +58 -58
  183. package/kit/framework/templates/DEBUG.md +164 -164
  184. package/kit/framework/templates/UAT.md +265 -265
  185. package/kit/framework/templates/UI-SPEC.md +100 -100
  186. package/kit/framework/templates/VALIDATION.md +76 -76
  187. package/kit/framework/templates/claude-md.md +122 -122
  188. package/kit/framework/templates/codebase/architecture.md +185 -185
  189. package/kit/framework/templates/codebase/concerns.md +205 -205
  190. package/kit/framework/templates/codebase/conventions.md +204 -204
  191. package/kit/framework/templates/codebase/integrations.md +192 -192
  192. package/kit/framework/templates/codebase/stack.md +158 -158
  193. package/kit/framework/templates/codebase/structure.md +199 -199
  194. package/kit/framework/templates/codebase/testing.md +301 -301
  195. package/kit/framework/templates/config.json +44 -44
  196. package/kit/framework/templates/context.md +352 -352
  197. package/kit/framework/templates/continue-here.md +78 -78
  198. package/kit/framework/templates/copilot-instructions.md +7 -7
  199. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  200. package/kit/framework/templates/dev-preferences.md +20 -20
  201. package/kit/framework/templates/discovery.md +146 -146
  202. package/kit/framework/templates/discussion-log.md +63 -63
  203. package/kit/framework/templates/milestone-archive.md +123 -123
  204. package/kit/framework/templates/milestone.md +115 -115
  205. package/kit/framework/templates/phase-prompt.md +610 -610
  206. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  207. package/kit/framework/templates/project.md +186 -186
  208. package/kit/framework/templates/requirements.md +231 -231
  209. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  210. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  211. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  212. package/kit/framework/templates/research-project/STACK.md +120 -120
  213. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  214. package/kit/framework/templates/research.md +419 -419
  215. package/kit/framework/templates/retrospective.md +54 -54
  216. package/kit/framework/templates/roadmap.md +202 -202
  217. package/kit/framework/templates/state.md +176 -176
  218. package/kit/framework/templates/summary-complex.md +59 -59
  219. package/kit/framework/templates/summary-minimal.md +41 -41
  220. package/kit/framework/templates/summary-standard.md +48 -48
  221. package/kit/framework/templates/summary.md +209 -209
  222. package/kit/framework/templates/user-profile.md +146 -146
  223. package/kit/framework/templates/user-setup.md +256 -256
  224. package/kit/framework/templates/verification-report.md +258 -258
  225. package/kit/framework/workflows/add-phase.md +112 -112
  226. package/kit/framework/workflows/add-tests.md +351 -351
  227. package/kit/framework/workflows/add-todo.md +158 -158
  228. package/kit/framework/workflows/audit-milestone.md +340 -340
  229. package/kit/framework/workflows/audit-uat.md +109 -109
  230. package/kit/framework/workflows/autonomous.md +891 -891
  231. package/kit/framework/workflows/check-todos.md +177 -177
  232. package/kit/framework/workflows/cleanup.md +152 -152
  233. package/kit/framework/workflows/complete-milestone.md +696 -696
  234. package/kit/framework/workflows/diagnose-issues.md +231 -231
  235. package/kit/framework/workflows/discovery-phase.md +289 -289
  236. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  237. package/kit/framework/workflows/discuss-phase.md +784 -784
  238. package/kit/framework/workflows/do.md +104 -104
  239. package/kit/framework/workflows/execute-phase.md +838 -838
  240. package/kit/framework/workflows/execute-plan.md +510 -510
  241. package/kit/framework/workflows/fast.md +102 -102
  242. package/kit/framework/workflows/forensics.md +265 -265
  243. package/kit/framework/workflows/health.md +181 -181
  244. package/kit/framework/workflows/help.md +619 -619
  245. package/kit/framework/workflows/insert-phase.md +130 -130
  246. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  247. package/kit/framework/workflows/list-workspaces.md +56 -56
  248. package/kit/framework/workflows/manager.md +362 -362
  249. package/kit/framework/workflows/map-codebase.md +377 -377
  250. package/kit/framework/workflows/milestone-summary.md +223 -223
  251. package/kit/framework/workflows/new-milestone.md +486 -486
  252. package/kit/framework/workflows/new-project.md +1159 -1159
  253. package/kit/framework/workflows/new-workspace.md +237 -237
  254. package/kit/framework/workflows/next.md +97 -97
  255. package/kit/framework/workflows/node-repair.md +92 -92
  256. package/kit/framework/workflows/note.md +156 -156
  257. package/kit/framework/workflows/pause-work.md +176 -176
  258. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  259. package/kit/framework/workflows/plan-phase.md +765 -765
  260. package/kit/framework/workflows/plant-seed.md +169 -169
  261. package/kit/framework/workflows/pr-branch.md +129 -129
  262. package/kit/framework/workflows/profile-user.md +450 -450
  263. package/kit/framework/workflows/progress.md +507 -507
  264. package/kit/framework/workflows/quick.md +757 -757
  265. package/kit/framework/workflows/remove-phase.md +155 -155
  266. package/kit/framework/workflows/remove-workspace.md +90 -90
  267. package/kit/framework/workflows/research-phase.md +82 -82
  268. package/kit/framework/workflows/resume-project.md +326 -326
  269. package/kit/framework/workflows/review.md +228 -228
  270. package/kit/framework/workflows/session-report.md +146 -146
  271. package/kit/framework/workflows/settings.md +283 -283
  272. package/kit/framework/workflows/ship.md +228 -228
  273. package/kit/framework/workflows/stats.md +60 -60
  274. package/kit/framework/workflows/transition.md +671 -671
  275. package/kit/framework/workflows/ui-phase.md +302 -302
  276. package/kit/framework/workflows/ui-review.md +165 -165
  277. package/kit/framework/workflows/update.md +323 -323
  278. package/kit/framework/workflows/validate-phase.md +174 -174
  279. package/kit/framework/workflows/verify-phase.md +252 -252
  280. package/kit/framework/workflows/verify-work.md +637 -637
  281. package/kit/hooks/check-update.js +118 -118
  282. package/kit/hooks/context-monitor.js +163 -163
  283. package/kit/hooks/kit-attribution-reminder.cjs +29 -50
  284. package/kit/hooks/kit-router.cjs +137 -0
  285. package/kit/hooks/prompt-guard.js +103 -103
  286. package/kit/hooks/statusline.js +125 -125
  287. package/kit/hooks/workflow-guard.js +101 -101
  288. package/kit/settings.json +45 -45
  289. package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
  290. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
  291. package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
  292. package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
  293. package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
  294. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
  295. package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
  296. package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
  297. package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
  298. package/kit/skills/example-skill/SKILL.md +42 -42
  299. package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
  300. package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
  301. package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
  302. package/kit/skills/legacy-extract-class/SKILL.md +203 -203
  303. package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
  304. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
  305. package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
  306. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
  307. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
  308. package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
  309. package/kit/skills/member-invite-flow/SKILL.md +305 -305
  310. package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
  311. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
  312. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
  313. package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
  314. package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
  315. package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
  316. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
  317. package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
  318. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
  319. package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
  320. package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
  321. package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
  322. package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
  323. package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
  324. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
  325. package/kit/skills/supabase-edge-functions/SKILL.md +1 -1
  326. package/kit/skills/supabase-edge-functions-auth/SKILL.md +1 -1
  327. package/kit/skills/supabase-edge-functions-limits/SKILL.md +1 -1
  328. package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +1 -1
  329. package/kit/skills/supabase-edge-functions-testing/SKILL.md +1 -1
  330. package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +1 -1
  331. package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
  332. package/kit/skills/supabase-migrations/SKILL.md +297 -297
  333. package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
  334. package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
  335. package/kit/skills/supabase-realtime/SKILL.md +460 -460
  336. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
  337. package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
  338. package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
  339. package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
  340. package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
  341. package/package.json +1 -1
  342. package/src/core/kit.js +216 -216
  343. package/src/core/reflect.js +247 -247
  344. package/src/core/reverse-sync.js +372 -372
  345. package/src/core/sync.js +437 -418
  346. package/src/core/watch.js +121 -121
  347. package/src/mcp-server/index.js +794 -746
@@ -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)