@zigrivers/scaffold 3.16.0 → 3.18.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 (385) hide show
  1. package/README.md +28 -0
  2. package/content/knowledge/backend/backend-fintech-broker-integration.md +244 -0
  3. package/content/knowledge/backend/backend-fintech-compliance.md +181 -0
  4. package/content/knowledge/backend/backend-fintech-data-modeling.md +210 -0
  5. package/content/knowledge/backend/backend-fintech-ledger.md +226 -0
  6. package/content/knowledge/backend/backend-fintech-observability.md +151 -0
  7. package/content/knowledge/backend/backend-fintech-order-lifecycle.md +213 -0
  8. package/content/knowledge/backend/backend-fintech-risk-management.md +150 -0
  9. package/content/knowledge/backend/backend-fintech-testing.md +197 -0
  10. package/content/knowledge/core/automated-review-tooling.md +10 -0
  11. package/content/knowledge/core/multi-service-api-contracts.md +634 -0
  12. package/content/knowledge/core/multi-service-architecture.md +492 -0
  13. package/content/knowledge/core/multi-service-auth.md +706 -0
  14. package/content/knowledge/core/multi-service-data-ownership.md +539 -0
  15. package/content/knowledge/core/multi-service-observability.md +545 -0
  16. package/content/knowledge/core/multi-service-resilience.md +710 -0
  17. package/content/knowledge/core/multi-service-task-decomposition.md +615 -0
  18. package/content/knowledge/core/multi-service-testing.md +728 -0
  19. package/content/methodology/backend-fintech.yml +46 -0
  20. package/content/methodology/custom-defaults.yml +6 -0
  21. package/content/methodology/deep.yml +6 -0
  22. package/content/methodology/multi-service-overlay.yml +103 -0
  23. package/content/methodology/mvp.yml +6 -0
  24. package/content/pipeline/architecture/service-ownership-map.md +83 -0
  25. package/content/pipeline/quality/cross-service-auth.md +96 -0
  26. package/content/pipeline/quality/cross-service-observability.md +104 -0
  27. package/content/pipeline/quality/integration-test-plan.md +106 -0
  28. package/content/pipeline/specification/inter-service-contracts.md +95 -0
  29. package/dist/cli/commands/adopt.cli-flags.test.js +20 -0
  30. package/dist/cli/commands/adopt.cli-flags.test.js.map +1 -1
  31. package/dist/cli/commands/adopt.d.ts.map +1 -1
  32. package/dist/cli/commands/adopt.js +11 -3
  33. package/dist/cli/commands/adopt.js.map +1 -1
  34. package/dist/cli/commands/complete.d.ts +1 -0
  35. package/dist/cli/commands/complete.d.ts.map +1 -1
  36. package/dist/cli/commands/complete.js +26 -8
  37. package/dist/cli/commands/complete.js.map +1 -1
  38. package/dist/cli/commands/dashboard.d.ts +1 -0
  39. package/dist/cli/commands/dashboard.d.ts.map +1 -1
  40. package/dist/cli/commands/dashboard.js +19 -6
  41. package/dist/cli/commands/dashboard.js.map +1 -1
  42. package/dist/cli/commands/decisions.d.ts +1 -0
  43. package/dist/cli/commands/decisions.d.ts.map +1 -1
  44. package/dist/cli/commands/decisions.js +18 -4
  45. package/dist/cli/commands/decisions.js.map +1 -1
  46. package/dist/cli/commands/info.d.ts +1 -0
  47. package/dist/cli/commands/info.d.ts.map +1 -1
  48. package/dist/cli/commands/info.js +25 -3
  49. package/dist/cli/commands/info.js.map +1 -1
  50. package/dist/cli/commands/init-from.test.d.ts +2 -0
  51. package/dist/cli/commands/init-from.test.d.ts.map +1 -0
  52. package/dist/cli/commands/init-from.test.js +315 -0
  53. package/dist/cli/commands/init-from.test.js.map +1 -0
  54. package/dist/cli/commands/init.d.ts +3 -0
  55. package/dist/cli/commands/init.d.ts.map +1 -1
  56. package/dist/cli/commands/init.js +239 -129
  57. package/dist/cli/commands/init.js.map +1 -1
  58. package/dist/cli/commands/init.test.js +20 -0
  59. package/dist/cli/commands/init.test.js.map +1 -1
  60. package/dist/cli/commands/next.d.ts +1 -0
  61. package/dist/cli/commands/next.d.ts.map +1 -1
  62. package/dist/cli/commands/next.js +40 -4
  63. package/dist/cli/commands/next.js.map +1 -1
  64. package/dist/cli/commands/next.test.js +153 -0
  65. package/dist/cli/commands/next.test.js.map +1 -1
  66. package/dist/cli/commands/reset.d.ts +1 -0
  67. package/dist/cli/commands/reset.d.ts.map +1 -1
  68. package/dist/cli/commands/reset.js +77 -29
  69. package/dist/cli/commands/reset.js.map +1 -1
  70. package/dist/cli/commands/rework.d.ts +1 -0
  71. package/dist/cli/commands/rework.d.ts.map +1 -1
  72. package/dist/cli/commands/rework.js +16 -2
  73. package/dist/cli/commands/rework.js.map +1 -1
  74. package/dist/cli/commands/run.d.ts +1 -0
  75. package/dist/cli/commands/run.d.ts.map +1 -1
  76. package/dist/cli/commands/run.js +65 -13
  77. package/dist/cli/commands/run.js.map +1 -1
  78. package/dist/cli/commands/run.test.js +255 -3
  79. package/dist/cli/commands/run.test.js.map +1 -1
  80. package/dist/cli/commands/skip.d.ts +1 -0
  81. package/dist/cli/commands/skip.d.ts.map +1 -1
  82. package/dist/cli/commands/skip.js +24 -7
  83. package/dist/cli/commands/skip.js.map +1 -1
  84. package/dist/cli/commands/status.d.ts +1 -0
  85. package/dist/cli/commands/status.d.ts.map +1 -1
  86. package/dist/cli/commands/status.js +51 -4
  87. package/dist/cli/commands/status.js.map +1 -1
  88. package/dist/cli/commands/status.test.js +130 -0
  89. package/dist/cli/commands/status.test.js.map +1 -1
  90. package/dist/cli/guards-coverage.test.d.ts +2 -0
  91. package/dist/cli/guards-coverage.test.d.ts.map +1 -0
  92. package/dist/cli/guards-coverage.test.js +26 -0
  93. package/dist/cli/guards-coverage.test.js.map +1 -0
  94. package/dist/cli/guards-integration.test.d.ts +2 -0
  95. package/dist/cli/guards-integration.test.d.ts.map +1 -0
  96. package/dist/cli/guards-integration.test.js +178 -0
  97. package/dist/cli/guards-integration.test.js.map +1 -0
  98. package/dist/cli/guards.d.ts +13 -0
  99. package/dist/cli/guards.d.ts.map +1 -0
  100. package/dist/cli/guards.js +70 -0
  101. package/dist/cli/guards.js.map +1 -0
  102. package/dist/cli/guards.test.d.ts +2 -0
  103. package/dist/cli/guards.test.d.ts.map +1 -0
  104. package/dist/cli/guards.test.js +136 -0
  105. package/dist/cli/guards.test.js.map +1 -0
  106. package/dist/cli/init-flag-families.d.ts +1 -1
  107. package/dist/cli/init-flag-families.d.ts.map +1 -1
  108. package/dist/cli/init-flag-families.js +4 -1
  109. package/dist/cli/init-flag-families.js.map +1 -1
  110. package/dist/cli/init-flag-families.test.js +10 -0
  111. package/dist/cli/init-flag-families.test.js.map +1 -1
  112. package/dist/cli/shutdown.d.ts +2 -3
  113. package/dist/cli/shutdown.d.ts.map +1 -1
  114. package/dist/cli/shutdown.js +14 -11
  115. package/dist/cli/shutdown.js.map +1 -1
  116. package/dist/cli/shutdown.test.js +2 -4
  117. package/dist/cli/shutdown.test.js.map +1 -1
  118. package/dist/config/schema.d.ts +12122 -288
  119. package/dist/config/schema.d.ts.map +1 -1
  120. package/dist/config/schema.js +74 -79
  121. package/dist/config/schema.js.map +1 -1
  122. package/dist/config/schema.test.js +230 -1
  123. package/dist/config/schema.test.js.map +1 -1
  124. package/dist/config/validators/backend.d.ts +4 -0
  125. package/dist/config/validators/backend.d.ts.map +1 -0
  126. package/dist/config/validators/backend.js +14 -0
  127. package/dist/config/validators/backend.js.map +1 -0
  128. package/dist/config/validators/browser-extension.d.ts +4 -0
  129. package/dist/config/validators/browser-extension.d.ts.map +1 -0
  130. package/dist/config/validators/browser-extension.js +24 -0
  131. package/dist/config/validators/browser-extension.js.map +1 -0
  132. package/dist/config/validators/cli.d.ts +4 -0
  133. package/dist/config/validators/cli.d.ts.map +1 -0
  134. package/dist/config/validators/cli.js +14 -0
  135. package/dist/config/validators/cli.js.map +1 -0
  136. package/dist/config/validators/data-pipeline.d.ts +4 -0
  137. package/dist/config/validators/data-pipeline.d.ts.map +1 -0
  138. package/dist/config/validators/data-pipeline.js +14 -0
  139. package/dist/config/validators/data-pipeline.js.map +1 -0
  140. package/dist/config/validators/game.d.ts +4 -0
  141. package/dist/config/validators/game.d.ts.map +1 -0
  142. package/dist/config/validators/game.js +14 -0
  143. package/dist/config/validators/game.js.map +1 -0
  144. package/dist/config/validators/index.d.ts +7 -0
  145. package/dist/config/validators/index.d.ts.map +1 -0
  146. package/dist/config/validators/index.js +27 -0
  147. package/dist/config/validators/index.js.map +1 -0
  148. package/dist/config/validators/library.d.ts +4 -0
  149. package/dist/config/validators/library.d.ts.map +1 -0
  150. package/dist/config/validators/library.js +25 -0
  151. package/dist/config/validators/library.js.map +1 -0
  152. package/dist/config/validators/ml.d.ts +4 -0
  153. package/dist/config/validators/ml.d.ts.map +1 -0
  154. package/dist/config/validators/ml.js +31 -0
  155. package/dist/config/validators/ml.js.map +1 -0
  156. package/dist/config/validators/mobile-app.d.ts +4 -0
  157. package/dist/config/validators/mobile-app.d.ts.map +1 -0
  158. package/dist/config/validators/mobile-app.js +14 -0
  159. package/dist/config/validators/mobile-app.js.map +1 -0
  160. package/dist/config/validators/registry.test.d.ts +2 -0
  161. package/dist/config/validators/registry.test.d.ts.map +1 -0
  162. package/dist/config/validators/registry.test.js +26 -0
  163. package/dist/config/validators/registry.test.js.map +1 -0
  164. package/dist/config/validators/research.d.ts +4 -0
  165. package/dist/config/validators/research.d.ts.map +1 -0
  166. package/dist/config/validators/research.js +24 -0
  167. package/dist/config/validators/research.js.map +1 -0
  168. package/dist/config/validators/research.test.d.ts +2 -0
  169. package/dist/config/validators/research.test.d.ts.map +1 -0
  170. package/dist/config/validators/research.test.js +44 -0
  171. package/dist/config/validators/research.test.js.map +1 -0
  172. package/dist/config/validators/types.d.ts +19 -0
  173. package/dist/config/validators/types.d.ts.map +1 -0
  174. package/dist/config/validators/types.js +2 -0
  175. package/dist/config/validators/types.js.map +1 -0
  176. package/dist/config/validators/validators.test.d.ts +2 -0
  177. package/dist/config/validators/validators.test.d.ts.map +1 -0
  178. package/dist/config/validators/validators.test.js +25 -0
  179. package/dist/config/validators/validators.test.js.map +1 -0
  180. package/dist/config/validators/web-app.d.ts +4 -0
  181. package/dist/config/validators/web-app.d.ts.map +1 -0
  182. package/dist/config/validators/web-app.js +31 -0
  183. package/dist/config/validators/web-app.js.map +1 -0
  184. package/dist/core/assembly/context-gatherer.d.ts.map +1 -1
  185. package/dist/core/assembly/context-gatherer.js +4 -2
  186. package/dist/core/assembly/context-gatherer.js.map +1 -1
  187. package/dist/core/assembly/cross-reads.d.ts +61 -0
  188. package/dist/core/assembly/cross-reads.d.ts.map +1 -0
  189. package/dist/core/assembly/cross-reads.js +190 -0
  190. package/dist/core/assembly/cross-reads.js.map +1 -0
  191. package/dist/core/assembly/cross-reads.test.d.ts +2 -0
  192. package/dist/core/assembly/cross-reads.test.d.ts.map +1 -0
  193. package/dist/core/assembly/cross-reads.test.js +497 -0
  194. package/dist/core/assembly/cross-reads.test.js.map +1 -0
  195. package/dist/core/assembly/overlay-loader-structural.test.d.ts +2 -0
  196. package/dist/core/assembly/overlay-loader-structural.test.d.ts.map +1 -0
  197. package/dist/core/assembly/overlay-loader-structural.test.js +173 -0
  198. package/dist/core/assembly/overlay-loader-structural.test.js.map +1 -0
  199. package/dist/core/assembly/overlay-loader.d.ts +19 -3
  200. package/dist/core/assembly/overlay-loader.d.ts.map +1 -1
  201. package/dist/core/assembly/overlay-loader.js +135 -4
  202. package/dist/core/assembly/overlay-loader.js.map +1 -1
  203. package/dist/core/assembly/overlay-loader.test.js +204 -1
  204. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  205. package/dist/core/assembly/overlay-resolver.d.ts +9 -2
  206. package/dist/core/assembly/overlay-resolver.d.ts.map +1 -1
  207. package/dist/core/assembly/overlay-resolver.js +32 -1
  208. package/dist/core/assembly/overlay-resolver.js.map +1 -1
  209. package/dist/core/assembly/overlay-resolver.test.js +135 -17
  210. package/dist/core/assembly/overlay-resolver.test.js.map +1 -1
  211. package/dist/core/assembly/overlay-state-resolver.d.ts +9 -0
  212. package/dist/core/assembly/overlay-state-resolver.d.ts.map +1 -1
  213. package/dist/core/assembly/overlay-state-resolver.js +43 -2
  214. package/dist/core/assembly/overlay-state-resolver.js.map +1 -1
  215. package/dist/core/assembly/overlay-state-resolver.test.js +321 -0
  216. package/dist/core/assembly/overlay-state-resolver.test.js.map +1 -1
  217. package/dist/core/assembly/update-mode.d.ts +1 -0
  218. package/dist/core/assembly/update-mode.d.ts.map +1 -1
  219. package/dist/core/assembly/update-mode.js +17 -9
  220. package/dist/core/assembly/update-mode.js.map +1 -1
  221. package/dist/core/dependency/eligibility.d.ts +10 -1
  222. package/dist/core/dependency/eligibility.d.ts.map +1 -1
  223. package/dist/core/dependency/eligibility.js +19 -1
  224. package/dist/core/dependency/eligibility.js.map +1 -1
  225. package/dist/core/dependency/eligibility.test.js +82 -0
  226. package/dist/core/dependency/eligibility.test.js.map +1 -1
  227. package/dist/core/dependency/graph.d.ts +4 -1
  228. package/dist/core/dependency/graph.d.ts.map +1 -1
  229. package/dist/core/dependency/graph.js +7 -1
  230. package/dist/core/dependency/graph.js.map +1 -1
  231. package/dist/core/dependency/graph.test.js +48 -0
  232. package/dist/core/dependency/graph.test.js.map +1 -1
  233. package/dist/core/pipeline/global-steps.d.ts +7 -0
  234. package/dist/core/pipeline/global-steps.d.ts.map +1 -0
  235. package/dist/core/pipeline/global-steps.js +18 -0
  236. package/dist/core/pipeline/global-steps.js.map +1 -0
  237. package/dist/core/pipeline/resolver.d.ts +1 -0
  238. package/dist/core/pipeline/resolver.d.ts.map +1 -1
  239. package/dist/core/pipeline/resolver.js +54 -7
  240. package/dist/core/pipeline/resolver.js.map +1 -1
  241. package/dist/core/pipeline/resolver.test.js +51 -1
  242. package/dist/core/pipeline/resolver.test.js.map +1 -1
  243. package/dist/core/pipeline/types.d.ts +5 -1
  244. package/dist/core/pipeline/types.d.ts.map +1 -1
  245. package/dist/e2e/cross-service-references.test.d.ts +22 -0
  246. package/dist/e2e/cross-service-references.test.d.ts.map +1 -0
  247. package/dist/e2e/cross-service-references.test.js +230 -0
  248. package/dist/e2e/cross-service-references.test.js.map +1 -0
  249. package/dist/e2e/multi-service-pipeline.test.d.ts +10 -0
  250. package/dist/e2e/multi-service-pipeline.test.d.ts.map +1 -0
  251. package/dist/e2e/multi-service-pipeline.test.js +185 -0
  252. package/dist/e2e/multi-service-pipeline.test.js.map +1 -0
  253. package/dist/e2e/project-type-overlays.test.js +68 -0
  254. package/dist/e2e/project-type-overlays.test.js.map +1 -1
  255. package/dist/e2e/service-execution.test.d.ts +15 -0
  256. package/dist/e2e/service-execution.test.d.ts.map +1 -0
  257. package/dist/e2e/service-execution.test.js +219 -0
  258. package/dist/e2e/service-execution.test.js.map +1 -0
  259. package/dist/e2e/service-manifest.test.d.ts +19 -0
  260. package/dist/e2e/service-manifest.test.d.ts.map +1 -0
  261. package/dist/e2e/service-manifest.test.js +166 -0
  262. package/dist/e2e/service-manifest.test.js.map +1 -0
  263. package/dist/project/__frozen-schemas__/schema-v3.9.2.d.ts +224 -224
  264. package/dist/project/frontmatter.d.ts.map +1 -1
  265. package/dist/project/frontmatter.js +11 -0
  266. package/dist/project/frontmatter.js.map +1 -1
  267. package/dist/project/frontmatter.test.js +71 -0
  268. package/dist/project/frontmatter.test.js.map +1 -1
  269. package/dist/state/completion.d.ts +1 -1
  270. package/dist/state/completion.d.ts.map +1 -1
  271. package/dist/state/completion.js +10 -8
  272. package/dist/state/completion.js.map +1 -1
  273. package/dist/state/decision-logger.d.ts +3 -2
  274. package/dist/state/decision-logger.d.ts.map +1 -1
  275. package/dist/state/decision-logger.js +12 -11
  276. package/dist/state/decision-logger.js.map +1 -1
  277. package/dist/state/ensure-v3-migration.d.ts +9 -0
  278. package/dist/state/ensure-v3-migration.d.ts.map +1 -0
  279. package/dist/state/ensure-v3-migration.js +35 -0
  280. package/dist/state/ensure-v3-migration.js.map +1 -0
  281. package/dist/state/lock-manager.d.ts +5 -4
  282. package/dist/state/lock-manager.d.ts.map +1 -1
  283. package/dist/state/lock-manager.js +11 -11
  284. package/dist/state/lock-manager.js.map +1 -1
  285. package/dist/state/rework-manager.d.ts +1 -2
  286. package/dist/state/rework-manager.d.ts.map +1 -1
  287. package/dist/state/rework-manager.js +4 -5
  288. package/dist/state/rework-manager.js.map +1 -1
  289. package/dist/state/state-manager.d.ts +25 -1
  290. package/dist/state/state-manager.d.ts.map +1 -1
  291. package/dist/state/state-manager.js +86 -12
  292. package/dist/state/state-manager.js.map +1 -1
  293. package/dist/state/state-manager.test.js +278 -0
  294. package/dist/state/state-manager.test.js.map +1 -1
  295. package/dist/state/state-migration-v3.d.ts +22 -0
  296. package/dist/state/state-migration-v3.d.ts.map +1 -0
  297. package/dist/state/state-migration-v3.js +82 -0
  298. package/dist/state/state-migration-v3.js.map +1 -0
  299. package/dist/state/state-migration-v3.test.d.ts +2 -0
  300. package/dist/state/state-migration-v3.test.d.ts.map +1 -0
  301. package/dist/state/state-migration-v3.test.js +196 -0
  302. package/dist/state/state-migration-v3.test.js.map +1 -0
  303. package/dist/state/state-migration.d.ts.map +1 -1
  304. package/dist/state/state-migration.js +11 -6
  305. package/dist/state/state-migration.js.map +1 -1
  306. package/dist/state/state-migration.test.js +47 -2
  307. package/dist/state/state-migration.test.js.map +1 -1
  308. package/dist/state/state-path-resolver.d.ts +23 -0
  309. package/dist/state/state-path-resolver.d.ts.map +1 -0
  310. package/dist/state/state-path-resolver.js +36 -0
  311. package/dist/state/state-path-resolver.js.map +1 -0
  312. package/dist/state/state-path-resolver.test.d.ts +2 -0
  313. package/dist/state/state-path-resolver.test.d.ts.map +1 -0
  314. package/dist/state/state-path-resolver.test.js +78 -0
  315. package/dist/state/state-path-resolver.test.js.map +1 -0
  316. package/dist/state/state-version-dispatch.d.ts +17 -0
  317. package/dist/state/state-version-dispatch.d.ts.map +1 -0
  318. package/dist/state/state-version-dispatch.js +27 -0
  319. package/dist/state/state-version-dispatch.js.map +1 -0
  320. package/dist/state/state-version-dispatch.test.d.ts +2 -0
  321. package/dist/state/state-version-dispatch.test.d.ts.map +1 -0
  322. package/dist/state/state-version-dispatch.test.js +40 -0
  323. package/dist/state/state-version-dispatch.test.js.map +1 -0
  324. package/dist/types/config.d.ts +33 -3
  325. package/dist/types/config.d.ts.map +1 -1
  326. package/dist/types/config.test.js +62 -1
  327. package/dist/types/config.test.js.map +1 -1
  328. package/dist/types/dependency.d.ts +9 -0
  329. package/dist/types/dependency.d.ts.map +1 -1
  330. package/dist/types/frontmatter.d.ts +5 -0
  331. package/dist/types/frontmatter.d.ts.map +1 -1
  332. package/dist/types/lock.d.ts +1 -1
  333. package/dist/types/lock.d.ts.map +1 -1
  334. package/dist/types/state.d.ts +1 -1
  335. package/dist/types/state.d.ts.map +1 -1
  336. package/dist/utils/artifact-path.d.ts +19 -0
  337. package/dist/utils/artifact-path.d.ts.map +1 -0
  338. package/dist/utils/artifact-path.js +95 -0
  339. package/dist/utils/artifact-path.js.map +1 -0
  340. package/dist/utils/artifact-path.test.d.ts +2 -0
  341. package/dist/utils/artifact-path.test.d.ts.map +1 -0
  342. package/dist/utils/artifact-path.test.js +138 -0
  343. package/dist/utils/artifact-path.test.js.map +1 -0
  344. package/dist/utils/errors.d.ts +3 -1
  345. package/dist/utils/errors.d.ts.map +1 -1
  346. package/dist/utils/errors.js +21 -2
  347. package/dist/utils/errors.js.map +1 -1
  348. package/dist/utils/errors.test.js +27 -1
  349. package/dist/utils/errors.test.js.map +1 -1
  350. package/dist/utils/user-errors.d.ts +46 -0
  351. package/dist/utils/user-errors.d.ts.map +1 -0
  352. package/dist/utils/user-errors.js +76 -0
  353. package/dist/utils/user-errors.js.map +1 -0
  354. package/dist/utils/user-errors.test.d.ts +2 -0
  355. package/dist/utils/user-errors.test.d.ts.map +1 -0
  356. package/dist/utils/user-errors.test.js +74 -0
  357. package/dist/utils/user-errors.test.js.map +1 -0
  358. package/dist/validation/index.d.ts.map +1 -1
  359. package/dist/validation/index.js +16 -0
  360. package/dist/validation/index.js.map +1 -1
  361. package/dist/validation/index.test.js +48 -0
  362. package/dist/validation/index.test.js.map +1 -1
  363. package/dist/validation/state-validator.d.ts +5 -2
  364. package/dist/validation/state-validator.d.ts.map +1 -1
  365. package/dist/validation/state-validator.js +18 -20
  366. package/dist/validation/state-validator.js.map +1 -1
  367. package/dist/validation/state-validator.test.js +31 -2
  368. package/dist/validation/state-validator.test.js.map +1 -1
  369. package/dist/wizard/copy/backend.d.ts.map +1 -1
  370. package/dist/wizard/copy/backend.js +12 -0
  371. package/dist/wizard/copy/backend.js.map +1 -1
  372. package/dist/wizard/flags.d.ts +1 -0
  373. package/dist/wizard/flags.d.ts.map +1 -1
  374. package/dist/wizard/questions.d.ts.map +1 -1
  375. package/dist/wizard/questions.js +5 -1
  376. package/dist/wizard/questions.js.map +1 -1
  377. package/dist/wizard/questions.test.js +45 -2
  378. package/dist/wizard/questions.test.js.map +1 -1
  379. package/dist/wizard/wizard.d.ts +23 -0
  380. package/dist/wizard/wizard.d.ts.map +1 -1
  381. package/dist/wizard/wizard.js +85 -47
  382. package/dist/wizard/wizard.js.map +1 -1
  383. package/dist/wizard/wizard.test.js +186 -1
  384. package/dist/wizard/wizard.test.js.map +1 -1
  385. package/package.json +1 -1
@@ -0,0 +1,728 @@
1
+ ---
2
+ name: multi-service-testing
3
+ description: Consumer-driven contract testing, cross-service E2E strategies, and service test doubles
4
+ topics: [contract-tests, pact, schema-registry, cross-service-e2e, test-doubles]
5
+ ---
6
+
7
+ ## Summary
8
+
9
+ Testing a multi-service system requires a different strategy than testing a monolith. The test pyramid still applies, but each layer has multi-service-specific concerns.
10
+
11
+ **The multi-service test pyramid** (bottom to top):
12
+ - **Unit tests:** Business logic within a single service. No I/O, no other services.
13
+ - **Integration tests:** A service with its own database, cache, and internal queue. Real infrastructure, no other services.
14
+ - **Contract tests:** Verify inter-service API agreements. Consumers define expectations; providers verify them in CI. Fast, per-service, no full-system setup required.
15
+ - **Cross-service E2E:** Full user journeys against a real multi-service environment. Highest confidence, highest maintenance cost. Use sparingly (5-15 tests for critical journeys).
16
+
17
+ **Consumer-driven contract testing with Pact:** Consumers write tests that define expected interactions (request + response shape). These generate `.json` pact files published to a Pact Broker. Providers verify all consumer contracts in their CI pipeline. A breaking provider change fails the provider's CI before deployment. The `can-i-deploy` gate prevents incompatible versions from reaching production.
18
+
19
+ **Async contracts via schema registry:** For Kafka events, Avro schemas registered in a schema registry enforce compatibility at registration time (`BACKWARD`, `FORWARD`, or `FULL` compatibility modes). Breaking schema changes are rejected before any message is produced.
20
+
21
+ **Service test doubles:**
22
+ - **WireMock:** Configurable HTTP stub servers for integration tests.
23
+ - **In-memory fakes:** Stateful service simulators with rich assertion APIs.
24
+ - **Full service in Docker:** Real service with isolated test database, used in E2E tests.
25
+
26
+ ## Deep Guidance
27
+
28
+ ## The Multi-Service Test Pyramid
29
+
30
+ The standard test pyramid (unit, integration, E2E) extends naturally to multi-service systems with contract tests inserted between integration and E2E:
31
+
32
+ ```
33
+ / Cross-Service E2E \ Few (5-15), slow, validates real user journeys
34
+ / Contract Tests \ Moderate, fast, validates service API agreements
35
+ / Integration Tests \ Per-service, medium speed, validates service internals
36
+ / Unit Tests \ Many, fast, tests pure business logic in isolation
37
+ ________________________________
38
+ ```
39
+
40
+ **Layer responsibilities:**
41
+ - **Unit tests:** Business logic within a single service. No I/O, no other services, milliseconds.
42
+ - **Integration tests:** A service interacting with its own database, cache, and internal message queue. Real infrastructure, no other services.
43
+ - **Contract tests:** Verify that a service's API matches what its consumers expect, and that consumers correctly call the provider's API. Run per-service, not as a full-system test.
44
+ - **Cross-service E2E:** Full user journeys against a real or realistic multi-service environment. Highest confidence, highest maintenance cost.
45
+
46
+ ## Consumer-Driven Contract Testing with Pact
47
+
48
+ ### What Contract Testing Solves
49
+
50
+ Without contract tests, breaking changes in a provider's API are discovered when consumers deploy — in staging or, worse, production. Contract tests move that discovery to CI, in the provider's build, before the breaking change is merged.
51
+
52
+ Consumer-driven contract testing inverts the usual testing relationship: consumers define their expectations of the provider; the provider verifies it satisfies all consumer contracts. This means:
53
+
54
+ - Consumers own the contract specification.
55
+ - Providers run consumer contracts as part of their CI pipeline.
56
+ - A breaking change in the provider fails the provider's CI before deployment.
57
+
58
+ ### Pact: Consumer Side
59
+
60
+ The consumer writes a Pact test that defines the expected interaction with the provider. Pact records the interaction and generates a `.json` pact file.
61
+
62
+ ```typescript
63
+ // tests/contracts/order-service.pact.test.ts (consumer: api-gateway)
64
+ import { PactV3, MatchersV3 } from '@pact-foundation/pact'
65
+ import path from 'path'
66
+ import { OrderServiceClient } from '../../src/clients/order-service.js'
67
+
68
+ const { like, string, integer, eachLike } = MatchersV3
69
+
70
+ const provider = new PactV3({
71
+ consumer: 'api-gateway',
72
+ provider: 'order-service',
73
+ dir: path.join(__dirname, '../../pacts'),
74
+ logLevel: 'warn',
75
+ })
76
+
77
+ describe('api-gateway → order-service contract', () => {
78
+ describe('GET /orders/:id', () => {
79
+ it('returns order details for a valid order ID', async () => {
80
+ await provider
81
+ .given('order 550e8400 exists and is confirmed')
82
+ .uponReceiving('a request for order 550e8400')
83
+ .withRequest({
84
+ method: 'GET',
85
+ path: '/orders/550e8400-e29b-41d4-a716-446655440000',
86
+ headers: { Authorization: like('Bearer token') },
87
+ })
88
+ .willRespondWith({
89
+ status: 200,
90
+ headers: { 'Content-Type': 'application/json' },
91
+ body: {
92
+ orderId: string('550e8400-e29b-41d4-a716-446655440000'),
93
+ status: string('confirmed'),
94
+ items: eachLike({
95
+ productId: string('prod-123'),
96
+ quantity: integer(2),
97
+ unitPriceCents: integer(1999),
98
+ }),
99
+ totalCents: integer(3998),
100
+ },
101
+ })
102
+ .executeTest(async (mockServer) => {
103
+ const client = new OrderServiceClient({ baseUrl: mockServer.url })
104
+ const order = await client.getOrder('550e8400-e29b-41d4-a716-446655440000')
105
+ expect(order.status).toBe('confirmed')
106
+ expect(order.items).toHaveLength(1)
107
+ })
108
+ })
109
+ })
110
+
111
+ describe('POST /orders', () => {
112
+ it('places a new order and returns order ID', async () => {
113
+ await provider
114
+ .given('user cust-001 exists and inventory is available')
115
+ .uponReceiving('a request to place an order')
116
+ .withRequest({
117
+ method: 'POST',
118
+ path: '/orders',
119
+ headers: {
120
+ 'Content-Type': 'application/json',
121
+ Authorization: like('Bearer token'),
122
+ },
123
+ body: {
124
+ customerId: string('cust-001'),
125
+ items: eachLike({ productId: string('prod-123'), quantity: integer(1) }),
126
+ },
127
+ })
128
+ .willRespondWith({
129
+ status: 201,
130
+ body: {
131
+ orderId: string('new-order-uuid'),
132
+ status: string('pending'),
133
+ },
134
+ })
135
+ .executeTest(async (mockServer) => {
136
+ const client = new OrderServiceClient({ baseUrl: mockServer.url })
137
+ const result = await client.placeOrder({
138
+ customerId: 'cust-001',
139
+ items: [{ productId: 'prod-123', quantity: 1 }],
140
+ })
141
+ expect(result.status).toBe('pending')
142
+ expect(result.orderId).toBeTruthy()
143
+ })
144
+ })
145
+ })
146
+ })
147
+ ```
148
+
149
+ **Running the consumer test generates `pacts/api-gateway-order-service.json`** — this file is published to the Pact Broker for the provider to verify.
150
+
151
+ ### Pact: Provider Side
152
+
153
+ The provider loads consumer pacts from the Pact Broker and verifies them against the running service. Provider states map to database setup functions.
154
+
155
+ ```typescript
156
+ // tests/contracts/verify-pacts.test.ts (provider: order-service)
157
+ import { Verifier } from '@pact-foundation/pact'
158
+ import { app } from '../../src/app.js'
159
+ import { db } from '../../src/db/index.js'
160
+ import type { Server } from 'http'
161
+
162
+ let server: Server
163
+
164
+ beforeAll(async () => {
165
+ await db.migrate.latest()
166
+ server = app.listen(0) // random port
167
+ })
168
+
169
+ afterAll(async () => {
170
+ await new Promise((resolve) => server.close(resolve))
171
+ await db.destroy()
172
+ })
173
+
174
+ describe('Pact provider verification: order-service', () => {
175
+ it('satisfies all consumer contracts', async () => {
176
+ const opts = {
177
+ provider: 'order-service',
178
+ providerBaseUrl: `http://localhost:${(server.address() as { port: number }).port}`,
179
+
180
+ // Fetch pacts from broker
181
+ pactBrokerUrl: process.env.PACT_BROKER_URL ?? 'http://pact-broker:9292',
182
+ pactBrokerToken: process.env.PACT_BROKER_TOKEN,
183
+ publishVerificationResult: process.env.CI === 'true',
184
+ providerVersion: process.env.GIT_SHA ?? 'local',
185
+
186
+ // Provider state handlers — set up database state for each interaction
187
+ stateHandlers: {
188
+ 'order 550e8400 exists and is confirmed': async () => {
189
+ await db('orders').insert({
190
+ id: '550e8400-e29b-41d4-a716-446655440000',
191
+ customer_id: 'cust-001',
192
+ status: 'confirmed',
193
+ })
194
+ await db('order_items').insert({
195
+ order_id: '550e8400-e29b-41d4-a716-446655440000',
196
+ product_id: 'prod-123',
197
+ quantity: 2,
198
+ unit_price_cents: 1999,
199
+ })
200
+ },
201
+ 'user cust-001 exists and inventory is available': async () => {
202
+ await db('customers').insert({ id: 'cust-001', email: 'test@example.com' })
203
+ await db('inventory').insert({ product_id: 'prod-123', available_units: 100 })
204
+ },
205
+ },
206
+
207
+ // Teardown between states
208
+ beforeEach: async () => {
209
+ await db.raw('TRUNCATE orders, order_items, customers, inventory RESTART IDENTITY CASCADE')
210
+ },
211
+
212
+ logLevel: 'warn',
213
+ }
214
+
215
+ await new Verifier(opts).verifyProvider()
216
+ })
217
+ })
218
+ ```
219
+
220
+ **Trade-offs (Pact):**
221
+ - (+) Breaking changes in the provider are detected in the provider's CI before the change is deployed.
222
+ - (+) Consumers define exactly what they use — providers can safely change anything not referenced in contracts.
223
+ - (+) The Pact Broker provides a dependency graph: which consumers use which provider endpoints.
224
+ - (-) Pact tests require maintaining provider state handlers — a setup burden that grows with the number of interactions.
225
+ - (-) Pact tests are not a substitute for integration tests. They verify the contract format, not business logic.
226
+ - (-) Requires a Pact Broker for CI integration. Self-hosting adds operational overhead (PactFlow offers hosted option).
227
+
228
+ ### Pact Broker Integration in CI
229
+
230
+ ```yaml
231
+ # .github/workflows/consumer-contract-test.yml
232
+ name: Consumer Contract Tests
233
+ on: [push, pull_request]
234
+
235
+ jobs:
236
+ contract-tests:
237
+ runs-on: ubuntu-latest
238
+ steps:
239
+ - uses: actions/checkout@v4
240
+
241
+ - name: Run consumer contract tests
242
+ run: npm run test:contract:consumer
243
+ env:
244
+ PACT_BROKER_URL: ${{ secrets.PACT_BROKER_URL }}
245
+
246
+ - name: Publish pacts to broker
247
+ run: |
248
+ npx pact-broker publish \
249
+ --pact-files-or-dirs pacts/ \
250
+ --consumer-app-version ${{ github.sha }} \
251
+ --branch ${{ github.ref_name }} \
252
+ --broker-base-url ${{ secrets.PACT_BROKER_URL }} \
253
+ --broker-token ${{ secrets.PACT_BROKER_TOKEN }}
254
+
255
+ ---
256
+ # .github/workflows/provider-contract-test.yml
257
+ name: Provider Contract Verification
258
+ on: [push, pull_request]
259
+
260
+ jobs:
261
+ verify-contracts:
262
+ runs-on: ubuntu-latest
263
+ services:
264
+ postgres:
265
+ image: postgres:16
266
+ env:
267
+ POSTGRES_DB: order_service_test
268
+ POSTGRES_USER: test
269
+ POSTGRES_PASSWORD: test
270
+ ports: ['5432:5432']
271
+ steps:
272
+ - uses: actions/checkout@v4
273
+
274
+ - name: Verify consumer contracts
275
+ run: npm run test:contract:provider
276
+ env:
277
+ DATABASE_URL: postgres://test:test@localhost:5432/order_service_test
278
+ PACT_BROKER_URL: ${{ secrets.PACT_BROKER_URL }}
279
+ PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
280
+ GIT_SHA: ${{ github.sha }}
281
+ CI: true
282
+
283
+ - name: Can-I-Deploy check
284
+ run: |
285
+ npx pact-broker can-i-deploy \
286
+ --pacticipant order-service \
287
+ --version ${{ github.sha }} \
288
+ --to-environment production \
289
+ --broker-base-url ${{ secrets.PACT_BROKER_URL }} \
290
+ --broker-token ${{ secrets.PACT_BROKER_TOKEN }}
291
+ ```
292
+
293
+ ## Schema Registry Approach
294
+
295
+ For event-driven systems using Kafka, Avro schemas registered in a schema registry replace Pact for async contract testing.
296
+
297
+ **Schema registration (producer/provider side):**
298
+
299
+ ```typescript
300
+ // src/events/order-placed.schema.ts
301
+ import { SchemaRegistry } from '@kafkajs/confluent-schema-registry'
302
+
303
+ const registry = new SchemaRegistry({
304
+ host: process.env.SCHEMA_REGISTRY_URL ?? 'http://schema-registry:8081',
305
+ })
306
+
307
+ export const ORDER_PLACED_SUBJECT = 'order.placed-value'
308
+
309
+ export const orderPlacedSchema = {
310
+ type: 'record' as const,
311
+ name: 'OrderPlaced',
312
+ namespace: 'com.example.orders',
313
+ fields: [
314
+ { name: 'orderId', type: 'string' },
315
+ { name: 'customerId', type: 'string' },
316
+ { name: 'totalCents', type: 'int' },
317
+ { name: 'placedAt', type: 'string' }, // ISO 8601
318
+ {
319
+ name: 'items',
320
+ type: {
321
+ type: 'array',
322
+ items: {
323
+ type: 'record',
324
+ name: 'OrderItem',
325
+ fields: [
326
+ { name: 'productId', type: 'string' },
327
+ { name: 'quantity', type: 'int' },
328
+ { name: 'unitPriceCents', type: 'int' },
329
+ ],
330
+ },
331
+ },
332
+ },
333
+ ],
334
+ }
335
+
336
+ export async function registerOrderPlacedSchema(): Promise<number> {
337
+ const { id } = await registry.register(
338
+ { type: 'AVRO', schema: JSON.stringify(orderPlacedSchema) },
339
+ { subject: ORDER_PLACED_SUBJECT }
340
+ )
341
+ return id
342
+ }
343
+ ```
344
+
345
+ **Schema compatibility modes:**
346
+ - `BACKWARD`: new schema can read data written with the old schema. Add fields with defaults, remove optional fields. Safe for consumers to upgrade first.
347
+ - `FORWARD`: old schema can read data written with the new schema. Safe for producers to upgrade first.
348
+ - `FULL`: both backward and forward compatible. Strictest, safest for large consumer bases.
349
+
350
+ **Trade-offs (schema registry vs. Pact for async):**
351
+ - (+) Schema compatibility checks run at schema registration time — breaking changes are rejected before any message is produced.
352
+ - (+) Every consumer automatically validates incoming messages against the registered schema. No additional test setup.
353
+ - (-) Schema registry only validates structure, not behavior. Business logic changes (field semantics, value ranges) are not caught.
354
+ - (-) Schema registry is a shared dependency. If it is unavailable, schema validation fails. Cache schemas locally for resilience.
355
+
356
+ ## Service Test Doubles
357
+
358
+ ### Test Double Taxonomy for Multi-Service Systems
359
+
360
+ In a multi-service system, test doubles replace entire downstream services, not just individual functions. The appropriate double depends on the test level.
361
+
362
+ | Double Type | Used At | Behavior | State |
363
+ |-------------|---------|----------|-------|
364
+ | Mock server (WireMock, msw) | Integration tests | Configurable stubbed HTTP responses | Stateless |
365
+ | In-memory fake service | Integration tests | Simplified but functionally correct | Stateful |
366
+ | Contract mock (Pact mock server) | Contract tests | Records interactions for contract files | Stateless |
367
+ | Full service in Docker | E2E / acceptance tests | Real service, isolated test database | Stateful |
368
+
369
+ ### WireMock for HTTP Service Doubles
370
+
371
+ ```typescript
372
+ // tests/integration/order-service.test.ts
373
+ // The order service calls the inventory service and payment service.
374
+ // In integration tests, replace both with WireMock servers.
375
+ import { WireMock } from 'wiremock-captain'
376
+
377
+ describe('OrderService integration', () => {
378
+ let inventoryMock: WireMock
379
+ let paymentMock: WireMock
380
+
381
+ beforeAll(async () => {
382
+ inventoryMock = new WireMock('http://inventory-mock:8080')
383
+ paymentMock = new WireMock('http://payment-mock:8080')
384
+ })
385
+
386
+ afterEach(async () => {
387
+ await inventoryMock.clearAll()
388
+ await paymentMock.clearAll()
389
+ })
390
+
391
+ it('creates an order when inventory is available and payment succeeds', async () => {
392
+ // Stub inventory service
393
+ await inventoryMock.register(
394
+ { method: 'POST', endpoint: '/reserve' },
395
+ {
396
+ status: 200,
397
+ body: { reservationId: 'res-001', reserved: true },
398
+ }
399
+ )
400
+
401
+ // Stub payment service
402
+ await paymentMock.register(
403
+ { method: 'POST', endpoint: '/charge' },
404
+ {
405
+ status: 200,
406
+ body: { chargeId: 'chg-001', status: 'succeeded' },
407
+ }
408
+ )
409
+
410
+ const result = await orderService.placeOrder({
411
+ customerId: 'cust-001',
412
+ items: [{ productId: 'prod-123', quantity: 1 }],
413
+ paymentMethodId: 'pm-visa',
414
+ })
415
+
416
+ expect(result.status).toBe('confirmed')
417
+
418
+ // Verify inventory was reserved exactly once
419
+ const inventoryRequests = await inventoryMock.getRequestsForAPI(
420
+ { method: 'POST', endpoint: '/reserve' }
421
+ )
422
+ expect(inventoryRequests).toHaveLength(1)
423
+ })
424
+
425
+ it('rolls back inventory reservation when payment fails', async () => {
426
+ await inventoryMock.register(
427
+ { method: 'POST', endpoint: '/reserve' },
428
+ { status: 200, body: { reservationId: 'res-002', reserved: true } }
429
+ )
430
+
431
+ await inventoryMock.register(
432
+ { method: 'DELETE', endpoint: '/reserve/res-002' },
433
+ { status: 204 }
434
+ )
435
+
436
+ await paymentMock.register(
437
+ { method: 'POST', endpoint: '/charge' },
438
+ { status: 402, body: { error: 'INSUFFICIENT_FUNDS' } }
439
+ )
440
+
441
+ await expect(
442
+ orderService.placeOrder({
443
+ customerId: 'cust-001',
444
+ items: [{ productId: 'prod-123', quantity: 1 }],
445
+ paymentMethodId: 'pm-declined',
446
+ })
447
+ ).rejects.toThrow('Payment failed: INSUFFICIENT_FUNDS')
448
+
449
+ // Verify the reservation was cancelled (rollback executed)
450
+ const cancelRequests = await inventoryMock.getRequestsForAPI(
451
+ { method: 'DELETE', endpoint: '/reserve/res-002' }
452
+ )
453
+ expect(cancelRequests).toHaveLength(1)
454
+ })
455
+ })
456
+ ```
457
+
458
+ ### In-Memory Fake Services
459
+
460
+ For services where you need stateful behavior in tests (e.g., a notification service that should accumulate sent notifications for later assertion), an in-memory fake is more ergonomic than WireMock.
461
+
462
+ ```typescript
463
+ // tests/fakes/fake-notification-service.ts
464
+ import express from 'express'
465
+ import type { Application } from 'express'
466
+
467
+ interface SentNotification {
468
+ to: string
469
+ template: string
470
+ data: Record<string, unknown>
471
+ sentAt: Date
472
+ }
473
+
474
+ export class FakeNotificationService {
475
+ private readonly app: Application
476
+ private readonly notifications: SentNotification[] = []
477
+ private server?: ReturnType<Application['listen']>
478
+
479
+ constructor() {
480
+ this.app = express()
481
+ this.app.use(express.json())
482
+
483
+ this.app.post('/notifications', (req, res) => {
484
+ this.notifications.push({
485
+ ...req.body,
486
+ sentAt: new Date(),
487
+ })
488
+ res.json({ notificationId: `notif-${Date.now()}`, status: 'queued' })
489
+ })
490
+ }
491
+
492
+ async start(port = 0): Promise<number> {
493
+ return new Promise((resolve) => {
494
+ this.server = this.app.listen(port, () => {
495
+ resolve((this.server!.address() as { port: number }).port)
496
+ })
497
+ })
498
+ }
499
+
500
+ async stop(): Promise<void> {
501
+ return new Promise((resolve) => this.server?.close(() => resolve()))
502
+ }
503
+
504
+ reset(): void {
505
+ this.notifications.length = 0
506
+ }
507
+
508
+ getSentNotifications(): SentNotification[] {
509
+ return [...this.notifications]
510
+ }
511
+
512
+ getNotificationsTo(email: string): SentNotification[] {
513
+ return this.notifications.filter((n) => n.to === email)
514
+ }
515
+ }
516
+ ```
517
+
518
+ **Trade-offs (in-memory fakes vs. mock servers):**
519
+ - (+) Fakes can maintain state across multiple requests — essential for testing workflows.
520
+ - (+) Assertion API is richer (`.getSentNotifications()`) than polling a mock server's request log.
521
+ - (-) Fakes require maintenance — when the real service's API changes, the fake must be updated.
522
+ - (-) Fakes can diverge from the real service behavior, making integration tests misleading. Mitigate by running contract tests against fakes as well as real services.
523
+
524
+ ## Cross-Service E2E Test Design
525
+
526
+ ### When to Use Cross-Service E2E Tests
527
+
528
+ Cross-service E2E tests are expensive: they require a running multi-service environment, real or realistic databases, and produce flaky failures unrelated to the code under test (network timeouts, service startup order, database state). Use them sparingly for the scenarios that nothing else can validate.
529
+
530
+ **Use cross-service E2E for:**
531
+ - Critical user journeys that exercise the full service graph (place order, payment, fulfillment, notification)
532
+ - Smoke tests after deployment to verify the environment is healthy
533
+ - Integration scenarios that contract tests cannot cover (e.g., business logic that depends on real data state across services)
534
+
535
+ **Do NOT use cross-service E2E for:**
536
+ - Validation error paths (unit tests)
537
+ - Per-service business logic (integration tests)
538
+ - API contract format verification (contract tests)
539
+
540
+ ### E2E Test Environment Strategies
541
+
542
+ | Strategy | Setup Cost | Isolation | CI Suitability |
543
+ |----------|------------|-----------|----------------|
544
+ | Shared staging environment | Low | None | Poor — state bleeds between runs |
545
+ | Per-PR ephemeral environment | High | Full | Good — but slow to provision |
546
+ | Docker Compose local multi-service | Medium | Full | Good — fast for local dev |
547
+ | Kubernetes namespace per-branch | High | Full | Good — realistic but expensive |
548
+
549
+ **Docker Compose for cross-service E2E (recommended for most teams):**
550
+
551
+ ```yaml
552
+ # docker-compose.e2e.yml
553
+ version: '3.9'
554
+
555
+ services:
556
+ api-gateway:
557
+ build:
558
+ context: ../../api-gateway
559
+ dockerfile: Dockerfile
560
+ ports: ['3000:3000']
561
+ environment:
562
+ ORDER_SERVICE_URL: http://order-service:8080
563
+ AUTH_SERVICE_URL: http://auth-service:8080
564
+ depends_on:
565
+ order-service:
566
+ condition: service_healthy
567
+ auth-service:
568
+ condition: service_healthy
569
+
570
+ order-service:
571
+ build:
572
+ context: ../../order-service
573
+ dockerfile: Dockerfile
574
+ environment:
575
+ DATABASE_URL: postgres://test:test@order-db:5432/order_test
576
+ INVENTORY_SERVICE_URL: http://inventory-service:8080
577
+ PAYMENT_SERVICE_URL: http://payment-service:8080
578
+ depends_on:
579
+ order-db:
580
+ condition: service_healthy
581
+ healthcheck:
582
+ test: ['CMD', 'curl', '-f', 'http://localhost:8080/health']
583
+ interval: 5s
584
+ timeout: 3s
585
+ retries: 10
586
+ start_period: 10s
587
+
588
+ order-db:
589
+ image: postgres:16-alpine
590
+ environment:
591
+ POSTGRES_DB: order_test
592
+ POSTGRES_USER: test
593
+ POSTGRES_PASSWORD: test
594
+ healthcheck:
595
+ test: ['CMD-SHELL', 'pg_isready -U test -d order_test']
596
+ interval: 2s
597
+ timeout: 2s
598
+ retries: 10
599
+
600
+ auth-service:
601
+ build:
602
+ context: ../../auth-service
603
+ dockerfile: Dockerfile
604
+ environment:
605
+ DATABASE_URL: postgres://test:test@auth-db:5432/auth_test
606
+ JWT_SECRET: test-secret-not-for-production
607
+ depends_on:
608
+ auth-db:
609
+ condition: service_healthy
610
+ healthcheck:
611
+ test: ['CMD', 'curl', '-f', 'http://localhost:8080/health']
612
+ interval: 5s
613
+ retries: 10
614
+
615
+ auth-db:
616
+ image: postgres:16-alpine
617
+ environment:
618
+ POSTGRES_DB: auth_test
619
+ POSTGRES_USER: test
620
+ POSTGRES_PASSWORD: test
621
+ healthcheck:
622
+ test: ['CMD-SHELL', 'pg_isready -U test -d auth_test']
623
+ interval: 2s
624
+ retries: 10
625
+ ```
626
+
627
+ **Cross-service E2E test (using the Docker Compose stack):**
628
+
629
+ ```typescript
630
+ // tests/e2e/order-placement.e2e.test.ts
631
+ import axios from 'axios'
632
+
633
+ const GATEWAY = 'http://localhost:3000'
634
+
635
+ describe('Order placement journey', () => {
636
+ let authToken: string
637
+ let userId: string
638
+
639
+ beforeAll(async () => {
640
+ // Register and authenticate a test user
641
+ const reg = await axios.post(`${GATEWAY}/api/v1/auth/register`, {
642
+ email: `e2e-${Date.now()}@example.com`,
643
+ password: 'TestPassword123!',
644
+ })
645
+ userId = reg.data.userId
646
+
647
+ const login = await axios.post(`${GATEWAY}/api/v1/auth/login`, {
648
+ email: reg.data.email,
649
+ password: 'TestPassword123!',
650
+ })
651
+ authToken = login.data.token
652
+ })
653
+
654
+ it('completes the full order placement flow', async () => {
655
+ // Place an order
656
+ const orderRes = await axios.post(
657
+ `${GATEWAY}/api/v1/orders`,
658
+ {
659
+ items: [{ productId: 'test-product-001', quantity: 2 }],
660
+ paymentMethodId: 'test-card-visa',
661
+ },
662
+ { headers: { Authorization: `Bearer ${authToken}` } }
663
+ )
664
+
665
+ expect(orderRes.status).toBe(201)
666
+ const { orderId } = orderRes.data
667
+ expect(orderId).toBeTruthy()
668
+
669
+ // Poll for order confirmation (async processing)
670
+ let order: { status: string } | null = null
671
+ for (let i = 0; i < 10; i++) {
672
+ const statusRes = await axios.get(`${GATEWAY}/api/v1/orders/${orderId}`, {
673
+ headers: { Authorization: `Bearer ${authToken}` },
674
+ })
675
+ order = statusRes.data
676
+ if (order?.status === 'confirmed') break
677
+ await new Promise((r) => setTimeout(r, 500))
678
+ }
679
+
680
+ expect(order?.status).toBe('confirmed')
681
+ }, 30_000)
682
+ })
683
+ ```
684
+
685
+ **Trade-offs (Docker Compose E2E):**
686
+ - (+) Full service isolation — no shared state with other test runs.
687
+ - (+) Reproducible locally — developers can run the same E2E suite on their machines.
688
+ - (-) Startup time. A 5-service Docker Compose stack with healthchecks takes 30-60 seconds to become ready.
689
+ - (-) Service build dependencies — CI must build all service images or pull from a registry.
690
+ - (-) Test data management is harder in a multi-service setup. Clearing state requires hitting each service's internal API or truncating databases directly.
691
+
692
+ ## CI Integration Checklist for Contract Tests
693
+
694
+ A complete CI pipeline for a multi-service system runs contract tests in the correct order:
695
+
696
+ ```
697
+ Consumer builds:
698
+ 1. Run unit + integration tests
699
+ 2. Run consumer contract tests → publish pact files to Pact Broker
700
+
701
+ Provider builds:
702
+ 1. Run unit + integration tests
703
+ 2. Fetch consumer pacts from Pact Broker
704
+ 3. Run provider verification → publish results to Pact Broker
705
+ 4. Run can-i-deploy check before deployment
706
+
707
+ Deployment gate:
708
+ 5. can-i-deploy must pass for all consumers before provider deploys
709
+ 6. can-i-deploy must pass for all providers before consumer deploys
710
+ ```
711
+
712
+ **Key principle:** Never deploy a service that fails `can-i-deploy`. The Pact Broker tracks which versions of provider and consumer are compatible and will fail the check if the deployment would break a consumer.
713
+
714
+ ## Common Pitfalls
715
+
716
+ **Testing the wrong level.** A team writes cross-service E2E tests for every business rule because "it's more realistic." These tests are 100x slower, flaky, and provide no more confidence than per-service integration tests for logic that lives entirely within one service. Fix: follow the test pyramid. Only cross-service E2E tests need multiple running services.
717
+
718
+ **Stale pacts.** Consumer contract files are committed to version control and drift out of sync with the actual service code. Fix: generate pacts from code, not handwritten YAML. Publish pacts to the Pact Broker on every consumer CI run. The Broker is the source of truth.
719
+
720
+ **Provider state drift.** Provider state handlers in Pact verification set up database rows that diverge from production schemas over time. Fix: use the same migration system and seed factories for provider state setup as for other integration tests. Run provider verification against a recently migrated test database.
721
+
722
+ **Fake services that lie.** An in-memory fake returns hardcoded 200s for all requests, masking integration bugs. Fix: fakes must fail on unexpected inputs (unregistered routes return 404 or 400), not silently succeed. Validate fake inputs against the same schema as the real service.
723
+
724
+ **E2E test pollution.** Tests create data but do not clean up, causing later tests to see unexpected state. Fix: each E2E test must own its test data. Use unique identifiers (timestamps, UUIDs) per test run. Provide a teardown or seed reset mechanism between test scenarios.
725
+
726
+ **Missing contract tests for async events.** Teams use Pact for HTTP APIs but leave Kafka events untested. A producer changes an event schema without updating consumers. Fix: use schema registry with compatibility enforcement for all Kafka events. Treat schema compatibility checks as contract tests for async events.
727
+
728
+ **Can-i-deploy bypassed.** A developer bypasses the can-i-deploy gate because it is slow or flapping. Fix: can-i-deploy is a hard gate. If it is slow, optimize the Pact Broker setup. If it is failing, investigate the failing contract — do not bypass.