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