@shortlink-org/portolan 0.1.0 → 0.2.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 (381) hide show
  1. package/README.md +69 -205
  2. package/cli/init.mjs +285 -0
  3. package/cli/init.test.mjs +158 -0
  4. package/cli/portolan.mjs +37 -73
  5. package/index.html +19 -0
  6. package/package.json +5 -10
  7. package/plugins/README.md +66 -27
  8. package/plugins/portolan-go.wasm +0 -0
  9. package/portolan.json +52 -73
  10. package/public/landing/cats/bug-hunter.webp +0 -0
  11. package/public/landing/cats/compass-nap.webp +0 -0
  12. package/public/landing/cats/diagrammer.webp +0 -0
  13. package/public/landing/cats/docs-reader.webp +0 -0
  14. package/public/landing/cats/laptop.webp +0 -0
  15. package/public/landing/cats/map-inspector.webp +0 -0
  16. package/public/landing/cats/server-break.webp +0 -0
  17. package/public/landing/cats/star-mapper.webp +0 -0
  18. package/public/landing/cats/system-builder.webp +0 -0
  19. package/public/landing/cats/thread-tangle.webp +0 -0
  20. package/public/og.png +0 -0
  21. package/public/readme-header.webp +0 -0
  22. package/schema/portolan.schema.json +180 -0
  23. package/scripts/builtin-plugins.mjs +14 -0
  24. package/scripts/delivery-presets.mjs +353 -0
  25. package/scripts/gen.mjs +37 -4
  26. package/scripts/history.mjs +126 -0
  27. package/scripts/history.test.mjs +110 -0
  28. package/scripts/host-plugins/fetch-bsr.mjs +351 -0
  29. package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
  30. package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
  31. package/scripts/host-plugins/fetch-csr.mjs +386 -0
  32. package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
  33. package/scripts/host-plugins/fetch-git.mjs +379 -0
  34. package/scripts/host-plugins/fetch-git.test.mjs +210 -0
  35. package/scripts/local-api.mjs +22 -4
  36. package/scripts/local-api.test.mjs +84 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +6 -2
  39. package/scripts/plugin-host.mjs +77 -18
  40. package/scripts/plugin-host.test.mjs +50 -1
  41. package/scripts/plugin-wasm-worker.mjs +6 -3
  42. package/scripts/run-builtin.mjs +6 -26
  43. package/scripts/schema.mjs +6 -1
  44. package/src/app/App.tsx +24 -429
  45. package/src/app/Breadcrumbs.test.ts +7 -0
  46. package/src/app/Breadcrumbs.tsx +15 -2
  47. package/src/app/CatalogApp.tsx +436 -0
  48. package/src/app/Sidebar.tsx +0 -1
  49. package/src/chat/ChatPanel.tsx +98 -41
  50. package/src/chat/Composer.tsx +4 -2
  51. package/src/chat/Conversation.tsx +79 -15
  52. package/src/chat/Header.tsx +43 -9
  53. package/src/chat/Starter.tsx +64 -3
  54. package/src/chat/Steps.tsx +1 -1
  55. package/src/chat/flags.test.ts +51 -14
  56. package/src/chat/flags.ts +8 -2
  57. package/src/chat/page-context.test.ts +51 -0
  58. package/src/chat/page-context.ts +128 -0
  59. package/src/chat/prompt.test.ts +49 -1
  60. package/src/chat/prompt.ts +42 -1
  61. package/src/chat/tools.ts +7 -2
  62. package/src/chat/transport.ts +16 -4
  63. package/src/components/ProblemRow.tsx +182 -0
  64. package/src/data.ts +20 -5
  65. package/src/er/ErCanvas.tsx +1 -1
  66. package/src/graph/DependencyGraph.tsx +10 -1
  67. package/src/graph/FocusedEventGraph.tsx +1 -1
  68. package/src/graph/GraphToolbar.tsx +2 -4
  69. package/src/index.css +220 -2
  70. package/src/landing/DraggableReveal.tsx +168 -0
  71. package/src/landing/EstateGraph.tsx +28 -0
  72. package/src/landing/FlowPlayback.tsx +282 -0
  73. package/src/landing/HeroMap.tsx +128 -0
  74. package/src/landing/LandingPage.tsx +757 -0
  75. package/src/landing/ProductFrame.tsx +68 -0
  76. package/src/landing/ProductTour.tsx +371 -0
  77. package/src/landing/catalog.ts +25 -0
  78. package/src/landing/motion.tsx +52 -0
  79. package/src/lib/all-problems.ts +28 -0
  80. package/src/lib/local-api.ts +35 -1
  81. package/src/lib/setup-info.test.ts +8 -0
  82. package/src/lib/setup-info.ts +3 -2
  83. package/src/lib/source-code.ts +2 -1
  84. package/src/likec4/C4View.tsx +5 -0
  85. package/src/likec4/InteractiveView.tsx +5 -1
  86. package/src/map/ContextMapGraph.tsx +47 -20
  87. package/src/pages/ContextMap.tsx +5 -1
  88. package/src/pages/FlowDetail.tsx +24 -7
  89. package/src/pages/Overview.tsx +68 -12
  90. package/src/pages/Problems.tsx +6 -193
  91. package/src/pages/Settings.tsx +133 -108
  92. package/src/pages/settings/DeliverySettings.tsx +229 -0
  93. package/src/pages/settings/PreferencesSettings.tsx +101 -0
  94. package/src/routes.test.ts +20 -0
  95. package/src/routes.ts +13 -1
  96. package/vite.config.ts +12 -8
  97. package/catalog/enum_test.go +0 -46
  98. package/catalog/model.go +0 -932
  99. package/catalog/roundtrip_test.go +0 -185
  100. package/catalog/via_test.go +0 -38
  101. package/go.mod +0 -14
  102. package/go.sum +0 -14
  103. package/internal/commands/cargo.go +0 -154
  104. package/internal/commands/commands.go +0 -143
  105. package/internal/commands/commands_test.go +0 -323
  106. package/internal/commands/gradle.go +0 -77
  107. package/internal/commands/justfile.go +0 -82
  108. package/internal/commands/makefile.go +0 -106
  109. package/internal/commands/maven.go +0 -93
  110. package/internal/commands/packagejson.go +0 -159
  111. package/internal/commands/pyproject.go +0 -169
  112. package/internal/commands/taskfile.go +0 -101
  113. package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
  114. package/internal/commands/testdata/estate/Makefile +0 -33
  115. package/internal/commands/testdata/estate/Taskfile.yml +0 -21
  116. package/internal/commands/testdata/estate/build.gradle.kts +0 -21
  117. package/internal/commands/testdata/estate/justfile +0 -20
  118. package/internal/commands/testdata/estate/package.json +0 -13
  119. package/internal/commands/testdata/estate/pom.xml +0 -29
  120. package/internal/commands/testdata/estate/pyproject.toml +0 -27
  121. package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
  122. package/internal/commands/testdata/golden/commands.json +0 -305
  123. package/internal/gohttp/analyze.go +0 -2500
  124. package/internal/gohttp/endpoints.go +0 -1067
  125. package/internal/gohttp/roots.go +0 -320
  126. package/internal/gohttp/typed.go +0 -143
  127. package/internal/goscan/constants.go +0 -85
  128. package/internal/goscan/goscan_test.go +0 -227
  129. package/internal/goscan/names.go +0 -52
  130. package/internal/goscan/parse_test.go +0 -11
  131. package/internal/goscan/source.go +0 -36
  132. package/internal/goscan/tree.go +0 -155
  133. package/internal/goscan/types.go +0 -99
  134. package/internal/wsdl/ids.go +0 -127
  135. package/internal/wsdl/ids_test.go +0 -21
  136. package/internal/wsdl/model.go +0 -62
  137. package/internal/wsdl/parse.go +0 -920
  138. package/internal/wsdl/parse_test.go +0 -133
  139. package/plugin/describe.go +0 -107
  140. package/plugin/describe_test.go +0 -114
  141. package/plugin/protocol.go +0 -117
  142. package/plugin/schematest/schematest.go +0 -126
  143. package/plugins/extract-adr/describe.go +0 -19
  144. package/plugins/extract-adr/describe_test.go +0 -11
  145. package/plugins/extract-adr/extract.go +0 -153
  146. package/plugins/extract-adr/extract_test.go +0 -350
  147. package/plugins/extract-adr/history.go +0 -99
  148. package/plugins/extract-adr/main.go +0 -65
  149. package/plugins/extract-adr/parse.go +0 -642
  150. package/plugins/extract-adr/parse_test.go +0 -405
  151. package/plugins/extract-asyncapi/describe.go +0 -19
  152. package/plugins/extract-asyncapi/describe_test.go +0 -11
  153. package/plugins/extract-asyncapi/extract.go +0 -315
  154. package/plugins/extract-asyncapi/extract_test.go +0 -197
  155. package/plugins/extract-asyncapi/main.go +0 -48
  156. package/plugins/extract-asyncapi/spec.go +0 -151
  157. package/plugins/extract-commands/describe.go +0 -19
  158. package/plugins/extract-commands/describe_test.go +0 -11
  159. package/plugins/extract-commands/extract.go +0 -79
  160. package/plugins/extract-commands/extract_test.go +0 -92
  161. package/plugins/extract-commands/main.go +0 -55
  162. package/plugins/extract-csr/avro.go +0 -258
  163. package/plugins/extract-csr/describe.go +0 -19
  164. package/plugins/extract-csr/describe_test.go +0 -11
  165. package/plugins/extract-csr/extract.go +0 -338
  166. package/plugins/extract-csr/extract_test.go +0 -374
  167. package/plugins/extract-csr/jsonschema.go +0 -343
  168. package/plugins/extract-csr/lock.go +0 -25
  169. package/plugins/extract-csr/main.go +0 -84
  170. package/plugins/extract-csr/subject.go +0 -102
  171. package/plugins/extract-flows/describe.go +0 -19
  172. package/plugins/extract-flows/describe_test.go +0 -11
  173. package/plugins/extract-flows/extract.go +0 -91
  174. package/plugins/extract-flows/main.go +0 -45
  175. package/plugins/extract-flows/parse.go +0 -593
  176. package/plugins/extract-flows/parse_test.go +0 -204
  177. package/plugins/extract-glossary/describe.go +0 -19
  178. package/plugins/extract-glossary/describe_test.go +0 -11
  179. package/plugins/extract-glossary/extract.go +0 -115
  180. package/plugins/extract-glossary/extract_test.go +0 -220
  181. package/plugins/extract-glossary/main.go +0 -59
  182. package/plugins/extract-glossary/parse.go +0 -214
  183. package/plugins/extract-glossary/parse_test.go +0 -203
  184. package/plugins/extract-go/aggregate.go +0 -214
  185. package/plugins/extract-go/client.go +0 -409
  186. package/plugins/extract-go/client_test.go +0 -266
  187. package/plugins/extract-go/describe.go +0 -19
  188. package/plugins/extract-go/describe_test.go +0 -11
  189. package/plugins/extract-go/enum.go +0 -195
  190. package/plugins/extract-go/enum_test.go +0 -82
  191. package/plugins/extract-go/event.go +0 -99
  192. package/plugins/extract-go/extract.go +0 -191
  193. package/plugins/extract-go/extract_test.go +0 -261
  194. package/plugins/extract-go/flow.go +0 -1441
  195. package/plugins/extract-go/flow_test.go +0 -609
  196. package/plugins/extract-go/httpclient.go +0 -174
  197. package/plugins/extract-go/httpclient_test.go +0 -296
  198. package/plugins/extract-go/ids.go +0 -92
  199. package/plugins/extract-go/layout.go +0 -225
  200. package/plugins/extract-go/layout_test.go +0 -125
  201. package/plugins/extract-go/lifecycle.go +0 -324
  202. package/plugins/extract-go/lifecycle_test.go +0 -108
  203. package/plugins/extract-go/main.go +0 -79
  204. package/plugins/extract-go/operation.go +0 -157
  205. package/plugins/extract-go/source.go +0 -316
  206. package/plugins/extract-go/transport.go +0 -321
  207. package/plugins/extract-go/transport_test.go +0 -145
  208. package/plugins/extract-go/wiring.go +0 -434
  209. package/plugins/extract-go-nats/describe.go +0 -19
  210. package/plugins/extract-go-nats/describe_test.go +0 -11
  211. package/plugins/extract-go-nats/extract.go +0 -177
  212. package/plugins/extract-go-nats/extract_test.go +0 -389
  213. package/plugins/extract-go-nats/index.go +0 -379
  214. package/plugins/extract-go-nats/main.go +0 -42
  215. package/plugins/extract-go-nats/resolve.go +0 -161
  216. package/plugins/extract-go-nats/sites.go +0 -224
  217. package/plugins/extract-graphql/describe.go +0 -19
  218. package/plugins/extract-graphql/describe_test.go +0 -11
  219. package/plugins/extract-graphql/extract.go +0 -433
  220. package/plugins/extract-graphql/extract_test.go +0 -256
  221. package/plugins/extract-graphql/ids.go +0 -49
  222. package/plugins/extract-graphql/lex.go +0 -237
  223. package/plugins/extract-graphql/main.go +0 -51
  224. package/plugins/extract-graphql/parse.go +0 -621
  225. package/plugins/extract-graphql/parse_test.go +0 -122
  226. package/plugins/extract-http-clients/describe.go +0 -19
  227. package/plugins/extract-http-clients/describe_test.go +0 -11
  228. package/plugins/extract-http-clients/extract.go +0 -705
  229. package/plugins/extract-http-clients/extract_test.go +0 -1263
  230. package/plugins/extract-http-clients/main.go +0 -42
  231. package/plugins/extract-openapi/describe.go +0 -19
  232. package/plugins/extract-openapi/describe_test.go +0 -11
  233. package/plugins/extract-openapi/discover.go +0 -243
  234. package/plugins/extract-openapi/extract.go +0 -526
  235. package/plugins/extract-openapi/extract_test.go +0 -545
  236. package/plugins/extract-openapi/main.go +0 -75
  237. package/plugins/extract-openapi/spec.go +0 -350
  238. package/plugins/extract-project/describe.go +0 -19
  239. package/plugins/extract-project/describe_test.go +0 -11
  240. package/plugins/extract-project/extract.go +0 -221
  241. package/plugins/extract-project/extract_test.go +0 -109
  242. package/plugins/extract-project/main.go +0 -41
  243. package/plugins/extract-proto/ast.go +0 -125
  244. package/plugins/extract-proto/consumes.go +0 -77
  245. package/plugins/extract-proto/describe.go +0 -19
  246. package/plugins/extract-proto/extract.go +0 -293
  247. package/plugins/extract-proto/extract_test.go +0 -459
  248. package/plugins/extract-proto/ids.go +0 -89
  249. package/plugins/extract-proto/ids_test.go +0 -57
  250. package/plugins/extract-proto/lex.go +0 -285
  251. package/plugins/extract-proto/main.go +0 -100
  252. package/plugins/extract-proto/module.go +0 -120
  253. package/plugins/extract-proto/parse.go +0 -720
  254. package/plugins/extract-proto/parse_test.go +0 -307
  255. package/plugins/extract-proto/provides.go +0 -236
  256. package/plugins/extract-proto/resolve.go +0 -222
  257. package/plugins/extract-proto/resolve_test.go +0 -119
  258. package/plugins/extract-redis/describe.go +0 -19
  259. package/plugins/extract-redis/describe_test.go +0 -11
  260. package/plugins/extract-redis/extract.go +0 -183
  261. package/plugins/extract-redis/extract_test.go +0 -168
  262. package/plugins/extract-redis/keyspaces.go +0 -469
  263. package/plugins/extract-redis/main.go +0 -44
  264. package/plugins/extract-river/describe.go +0 -19
  265. package/plugins/extract-river/describe_test.go +0 -11
  266. package/plugins/extract-river/extract.go +0 -507
  267. package/plugins/extract-river/extract_test.go +0 -132
  268. package/plugins/extract-river/main.go +0 -42
  269. package/plugins/extract-sql/ddl.go +0 -893
  270. package/plugins/extract-sql/ddl_test.go +0 -401
  271. package/plugins/extract-sql/describe.go +0 -19
  272. package/plugins/extract-sql/describe_test.go +0 -11
  273. package/plugins/extract-sql/layout.go +0 -221
  274. package/plugins/extract-sql/layout_test.go +0 -55
  275. package/plugins/extract-sql/lineage.go +0 -107
  276. package/plugins/extract-sql/main.go +0 -141
  277. package/plugins/extract-sql/maps.go +0 -564
  278. package/plugins/extract-sql/maps_java.go +0 -117
  279. package/plugins/extract-sql/maps_rust.go +0 -333
  280. package/plugins/extract-sql/maps_rust_test.go +0 -70
  281. package/plugins/extract-sql/maps_test.go +0 -204
  282. package/plugins/extract-sql/maps_ts.go +0 -398
  283. package/plugins/extract-sql/maps_ts_test.go +0 -136
  284. package/plugins/extract-sql/projection.go +0 -70
  285. package/plugins/extract-sql/projection_test.go +0 -50
  286. package/plugins/extract-sql/store.go +0 -420
  287. package/plugins/extract-sql/store_test.go +0 -233
  288. package/plugins/extract-sql/view.go +0 -295
  289. package/plugins/extract-watermill/describe.go +0 -19
  290. package/plugins/extract-watermill/describe_test.go +0 -11
  291. package/plugins/extract-watermill/extract.go +0 -1228
  292. package/plugins/extract-watermill/extract_test.go +0 -234
  293. package/plugins/extract-watermill/main.go +0 -41
  294. package/plugins/extract-wsdl/describe.go +0 -19
  295. package/plugins/extract-wsdl/describe_test.go +0 -11
  296. package/plugins/extract-wsdl/extract.go +0 -148
  297. package/plugins/extract-wsdl/extract_test.go +0 -45
  298. package/plugins/extract-wsdl/main.go +0 -53
  299. package/plugins/fetch-bsr/auth.go +0 -114
  300. package/plugins/fetch-bsr/bsr.go +0 -240
  301. package/plugins/fetch-bsr/cache.go +0 -66
  302. package/plugins/fetch-bsr/describe.go +0 -19
  303. package/plugins/fetch-bsr/fetch.go +0 -192
  304. package/plugins/fetch-bsr/fetch_test.go +0 -465
  305. package/plugins/fetch-bsr/lock.go +0 -71
  306. package/plugins/fetch-bsr/main.go +0 -117
  307. package/plugins/fetch-csr/auth.go +0 -95
  308. package/plugins/fetch-csr/cache.go +0 -69
  309. package/plugins/fetch-csr/describe.go +0 -19
  310. package/plugins/fetch-csr/describe_test.go +0 -11
  311. package/plugins/fetch-csr/fetch.go +0 -254
  312. package/plugins/fetch-csr/fetch_test.go +0 -494
  313. package/plugins/fetch-csr/lock.go +0 -95
  314. package/plugins/fetch-csr/main.go +0 -122
  315. package/plugins/fetch-csr/registry.go +0 -209
  316. package/plugins/fetch-git/cache.go +0 -65
  317. package/plugins/fetch-git/describe.go +0 -19
  318. package/plugins/fetch-git/describe_test.go +0 -11
  319. package/plugins/fetch-git/fetch.go +0 -181
  320. package/plugins/fetch-git/fetch_test.go +0 -332
  321. package/plugins/fetch-git/git.go +0 -169
  322. package/plugins/fetch-git/lock.go +0 -72
  323. package/plugins/fetch-git/main.go +0 -127
  324. package/plugins/fetch-git/offline.go +0 -39
  325. package/plugins/fetch-git/pin.go +0 -90
  326. package/plugins/fetch-git/pin_test.go +0 -74
  327. package/plugins/gen-backstage/describe.go +0 -17
  328. package/plugins/gen-backstage/main.go +0 -22
  329. package/plugins/gen-backstage/plugin.go +0 -473
  330. package/plugins/gen-backstage/plugin_test.go +0 -145
  331. package/plugins/gen-backstage.wasm +0 -0
  332. package/plugins/gen-markdown/adr.go +0 -162
  333. package/plugins/gen-markdown/aggregate.go +0 -362
  334. package/plugins/gen-markdown/canonical.go +0 -93
  335. package/plugins/gen-markdown/context.go +0 -90
  336. package/plugins/gen-markdown/coverage_test.go +0 -89
  337. package/plugins/gen-markdown/describe.go +0 -19
  338. package/plugins/gen-markdown/describe_test.go +0 -11
  339. package/plugins/gen-markdown/external.go +0 -71
  340. package/plugins/gen-markdown/flow.go +0 -277
  341. package/plugins/gen-markdown/glossary.go +0 -76
  342. package/plugins/gen-markdown/glossary_test.go +0 -148
  343. package/plugins/gen-markdown/llms.go +0 -302
  344. package/plugins/gen-markdown/main.go +0 -22
  345. package/plugins/gen-markdown/markdown.go +0 -254
  346. package/plugins/gen-markdown/markdown_test.go +0 -100
  347. package/plugins/gen-markdown/module.go +0 -107
  348. package/plugins/gen-markdown/plugin.go +0 -39
  349. package/plugins/gen-markdown/quality_test.go +0 -176
  350. package/plugins/gen-markdown/redis_test.go +0 -38
  351. package/plugins/gen-markdown/render.go +0 -344
  352. package/plugins/gen-markdown/render_test.go +0 -186
  353. package/plugins/gen-markdown/service.go +0 -456
  354. package/plugins/gen-markdown/source.go +0 -177
  355. package/plugins/gen-markdown/store.go +0 -201
  356. package/plugins/gen-markdown.wasm +0 -0
  357. package/plugins/gen-mermaid/describe.go +0 -17
  358. package/plugins/gen-mermaid/main.go +0 -22
  359. package/plugins/gen-mermaid/plugin.go +0 -104
  360. package/plugins/gen-mermaid/plugin_test.go +0 -33
  361. package/plugins/gen-mermaid.wasm +0 -0
  362. package/plugins/openapi/ids.go +0 -261
  363. package/plugins/openapi/ids_test.go +0 -98
  364. package/plugins/verify-codeowners/describe.go +0 -19
  365. package/plugins/verify-codeowners/describe_test.go +0 -11
  366. package/plugins/verify-codeowners/main.go +0 -75
  367. package/plugins/verify-codeowners/match.go +0 -85
  368. package/plugins/verify-codeowners/match_test.go +0 -47
  369. package/plugins/verify-codeowners/owners.go +0 -164
  370. package/plugins/verify-codeowners/owners_test.go +0 -225
  371. package/plugins/verify-codeowners/parse.go +0 -90
  372. package/plugins/verify-codeowners/parse_test.go +0 -62
  373. package/plugins/verify-otel/describe.go +0 -19
  374. package/plugins/verify-otel/describe_test.go +0 -11
  375. package/plugins/verify-otel/main.go +0 -53
  376. package/plugins/verify-otel/match.go +0 -336
  377. package/plugins/verify-otel/otlp.go +0 -200
  378. package/plugins/verify-otel/verify.go +0 -734
  379. package/plugins/verify-otel/verify_test.go +0 -460
  380. /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
  381. /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
@@ -1,1263 +0,0 @@
1
- package main
2
-
3
- import (
4
- "encoding/json"
5
- "os"
6
- "path/filepath"
7
- "strings"
8
- "testing"
9
-
10
- "github.com/shortlink-org/portolan/catalog"
11
- "github.com/shortlink-org/portolan/internal/gohttp"
12
- "github.com/shortlink-org/portolan/plugin"
13
- )
14
-
15
- func writeHTTPFixture(t *testing.T, root, name, contents string) {
16
- t.Helper()
17
- path := filepath.Join(root, name)
18
- if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
19
- t.Fatal(err)
20
- }
21
- if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
22
- t.Fatal(err)
23
- }
24
- }
25
-
26
- func TestExtractsRawGeneratedAndSOAPClients(t *testing.T) {
27
- root := t.TempDir()
28
- writeHTTPFixture(t, root, "go.mod", "module example.com/gateway\n")
29
- writeHTTPFixture(t, root, "partner/openapi.yaml", `openapi: 3.0.3
30
- info:
31
- title: Partner API
32
- version: 1.2.0
33
- paths:
34
- /v1/trips/{tripId}:
35
- get:
36
- operationId: getTrip
37
- tags: [trips]
38
- `)
39
- writeHTTPFixture(t, root, "partner/client.gen.go", `// Code generated by oapi-codegen; DO NOT EDIT.
40
- package partner
41
- import (
42
- "context"
43
- "fmt"
44
- "net/http"
45
- )
46
- type Client struct{}
47
- type ClientInterface interface { GetTrip(context.Context, string) (*http.Response, error) }
48
- func NewGetTripRequest(server, tripID string) (*http.Request, error) {
49
- operationPath := fmt.Sprintf("/v1/trips/%s", tripID)
50
- return http.NewRequest(http.MethodGet, server+operationPath, nil)
51
- }
52
-
53
- `)
54
- writeHTTPFixture(t, root, "partner/adapter.go", `package partner
55
- import "context"
56
- func Load(ctx context.Context, c *Client, id string) error {
57
- _, err := c.GetTripWithResponse(ctx, id)
58
- return err
59
- }
60
- `)
61
- writeHTTPFixture(t, root, "core/update.go", `package core
62
- import (
63
- "bytes"
64
- "context"
65
- "net/http"
66
- )
67
- const updatePath = "/v1/update"
68
- func Update(ctx context.Context, baseURL string, body []byte) error {
69
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+updatePath, bytes.NewReader(body))
70
- if err != nil { return err }
71
- _ = req
72
- return nil
73
- }
74
- `)
75
- writeHTTPFixture(t, root, "transport/handler.go", `package transport
76
- import (
77
- "context"
78
- "example.com/gateway/core"
79
- )
80
- func Handle(ctx context.Context, baseURL string, body []byte) error {
81
- return core.Update(ctx, baseURL, body)
82
- }
83
- `)
84
- writeHTTPFixture(t, root, "soap/cancel.go", `package soapclient
85
- import (
86
- "context"
87
- soap "github.com/hooklift/gowsdl/soap"
88
- )
89
- const CancelAction = "urn:CancelBooking"
90
- func Cancel(ctx context.Context, client soap.HTTPClient, request, response any) error {
91
- if request == nil {
92
- return nil
93
- }
94
- return client.CallContext(ctx, CancelAction, request, response)
95
- }
96
- `)
97
- writeHTTPFixture(t, root, "soap/booking.wsdl", `<?xml version="1.0"?>
98
- <definitions name="BookingService" targetNamespace="urn:booking"
99
- xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
100
- xmlns:tns="urn:booking"
101
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
102
- xmlns="http://schemas.xmlsoap.org/wsdl/">
103
- <message name="CancelRequest"><part name="body" type="xsd:string"/></message>
104
- <message name="CancelResponse"><part name="body" type="xsd:string"/></message>
105
- <portType name="BookingPortType">
106
- <operation name="CancelBooking"><input message="tns:CancelRequest"/><output message="tns:CancelResponse"/></operation>
107
- </portType>
108
- <binding name="BookingBinding" type="tns:BookingPortType">
109
- <soap:binding style="document"/>
110
- <operation name="CancelBooking">
111
- <soap:operation soapAction="urn:CancelBooking"/>
112
- </operation>
113
- </binding>
114
- <service name="BookingService">
115
- <port name="BookingPort" binding="tns:BookingBinding">
116
- <soap:address location="https://booking.example/soap"/>
117
- </port>
118
- </service>
119
- </definitions>
120
- `)
121
-
122
- resp, err := extract(plugin.Input{Root: root, Commit: "abc", GeneratedAt: "2026-01-01T00:00:00Z"}, Options{Context: "edge", Service: "gateway"})
123
- if err != nil {
124
- t.Fatal(err)
125
- }
126
- var got catalog.Catalog
127
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
128
- t.Fatal(err)
129
- }
130
- service := got.Contexts[0].Services[0]
131
- if len(service.Consumes) != 3 {
132
- t.Fatalf("consumes = %+v", service.Consumes)
133
- }
134
- var generated, raw, soapCall *catalog.RpcCall
135
- for i := range service.Consumes {
136
- call := &service.Consumes[i]
137
- switch {
138
- case strings.Contains(call.ID, "getTrip"):
139
- generated = call
140
- case strings.Contains(call.ID, "POST /v1/update"):
141
- raw = call
142
- case strings.Contains(call.ID, "CancelBooking"):
143
- soapCall = call
144
- }
145
- }
146
- if generated == nil || generated.Status != catalog.StatusDeclared || generated.Peer != "partner" {
147
- t.Fatalf("generated = %+v", generated)
148
- }
149
- if raw == nil || raw.Status != catalog.StatusUnresolved || raw.Source != "core/update.go:9" {
150
- t.Fatalf("raw = %+v", raw)
151
- }
152
- if soapCall == nil || soapCall.Status != catalog.StatusDeclared || soapCall.Peer != "booking" {
153
- t.Fatalf("soap = %+v", soapCall)
154
- }
155
- if len(got.Externals) != 2 {
156
- t.Fatalf("externals = %+v", got.Externals)
157
- }
158
- if len(got.Flows) != 4 {
159
- t.Fatalf("flows = %+v", got.Flows)
160
- }
161
- var sawCondition, sawChain bool
162
- for _, flow := range got.Flows {
163
- for _, node := range flow.Steps {
164
- if step, ok := node.(*catalog.Step); ok && strings.Contains(step.Note, "request == nil") {
165
- sawCondition = true
166
- }
167
- if step, ok := node.(*catalog.Step); ok && strings.Contains(step.Note, "Handle → Update") {
168
- sawChain = true
169
- }
170
- }
171
- }
172
- if !sawCondition {
173
- t.Fatal("SOAP source condition was not preserved")
174
- }
175
- if !sawChain {
176
- t.Fatal("generic entry point was not followed to its outbound call")
177
- }
178
- }
179
-
180
- func TestFlowTracesConfigThroughConstructorFieldAndWrapper(t *testing.T) {
181
- root := t.TempDir()
182
- writeHTTPFixture(t, root, "go.mod", "module example.com/payments\n")
183
- writeHTTPFixture(t, root, "client/client.go", `package client
184
- import (
185
- "context"
186
- "net/http"
187
- "net/url"
188
- )
189
- type Config struct { BaseURL string }
190
- type Client struct { baseURL url.URL }
191
- func Build(config map[string]string) (*Client, error) {
192
- return New(Config{BaseURL: config["url"]})
193
- }
194
- func New(cfg Config) (*Client, error) {
195
- parsed, err := url.Parse(cfg.BaseURL)
196
- if err != nil { return nil, err }
197
- return &Client{baseURL: *parsed}, nil
198
- }
199
- func (c *Client) request(ctx context.Context, method, path string) error {
200
- req, err := http.NewRequestWithContext(ctx, method, c.baseURL.String()+path, nil)
201
- if err != nil { return err }
202
- _ = req
203
- return nil
204
- }
205
- func (c *Client) Search(ctx context.Context) error {
206
- return c.request(ctx, http.MethodPost, "/v1/search")
207
- }
208
- `)
209
-
210
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "pay", Service: "gateway"})
211
- if err != nil {
212
- t.Fatal(err)
213
- }
214
- var got catalog.Catalog
215
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
216
- t.Fatal(err)
217
- }
218
- if len(got.Flows) != 1 {
219
- t.Fatalf("flows = %+v", got.Flows)
220
- }
221
- flow := got.Flows[0]
222
- if flow.Name != "Client Search → outbound APIs" {
223
- t.Fatalf("flow name = %q", flow.Name)
224
- }
225
- if flow.EntryPoint != "client:Client.Search" {
226
- t.Fatalf("flow entrypoint = %q", flow.EntryPoint)
227
- }
228
- step, ok := flow.Steps[0].(*catalog.Step)
229
- if !ok {
230
- t.Fatalf("step = %#v", flow.Steps[0])
231
- }
232
- if step.Label != "POST /v1/search" {
233
- t.Fatalf("step label = %q", step.Label)
234
- }
235
- if consumes := got.Contexts[0].Services[0].Consumes; len(consumes) != 1 || consumes[0].ID != "http-client/POST /v1/search" {
236
- t.Fatalf("consumes = %+v", consumes)
237
- }
238
- if !strings.Contains(step.Note, `config["url"] → Build → New → Client.baseURL → request URL`) {
239
- t.Fatalf("step note = %q", step.Note)
240
- }
241
- if !strings.Contains(step.Note, "Client.Search → Client.request") {
242
- t.Fatalf("step chain = %q", step.Note)
243
- }
244
- }
245
-
246
- func TestFlowKeepsDistinctCallsThroughClosureArguments(t *testing.T) {
247
- root := t.TempDir()
248
- writeHTTPFixture(t, root, "go.mod", "module example.com/closure\n")
249
- writeHTTPFixture(t, root, "client.go", `package closure
250
- import (
251
- "context"
252
- "net/http"
253
- )
254
- func request(ctx context.Context, path string) error {
255
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.test"+path, nil)
256
- if err != nil { return err }
257
- _ = req
258
- return nil
259
- }
260
- func Refresh(ctx context.Context) {
261
- go func(path string) { _ = request(ctx, path) }("/v1/primary")
262
- go func(path string) { _ = request(ctx, path) }("/v1/secondary")
263
- }
264
- `)
265
-
266
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "ops", Service: "refresher"})
267
- if err != nil {
268
- t.Fatal(err)
269
- }
270
- var got catalog.Catalog
271
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
272
- t.Fatal(err)
273
- }
274
- if len(got.Flows) != 1 {
275
- t.Fatalf("flows = %+v", got.Flows)
276
- }
277
- var labels []string
278
- for _, node := range got.Flows[0].Steps {
279
- if step, ok := node.(*catalog.Step); ok {
280
- labels = append(labels, step.Label)
281
- }
282
- }
283
- if strings.Join(labels, ",") != "GET /v1/primary,GET /v1/secondary" {
284
- t.Fatalf("labels = %+v", labels)
285
- }
286
- }
287
-
288
- func TestFlowDoesNotLinkMethodsByNameAlone(t *testing.T) {
289
- root := t.TempDir()
290
- writeHTTPFixture(t, root, "go.mod", "module example.com/close\n")
291
- writeHTTPFixture(t, root, "client.go", `package close
292
- import "net/http"
293
- type LocalCloser struct{}
294
- func (LocalCloser) Close() error {
295
- _, err := http.Get("https://audit.example/v1/closed")
296
- return err
297
- }
298
- func Fetch() error {
299
- response, err := http.Get("https://data.example/v1/items")
300
- if err != nil { return err }
301
- defer response.Body.Close()
302
- return nil
303
- }
304
- `)
305
-
306
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "edge", Service: "reader"})
307
- if err != nil {
308
- t.Fatal(err)
309
- }
310
- var got catalog.Catalog
311
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
312
- t.Fatal(err)
313
- }
314
- for _, flow := range got.Flows {
315
- if flow.Name != "Fetch → outbound APIs" {
316
- continue
317
- }
318
- if len(flow.Steps) != 1 {
319
- t.Fatalf("Fetch steps = %+v", flow.Steps)
320
- }
321
- step := flow.Steps[0].(*catalog.Step)
322
- if step.Label != "GET /v1/items" {
323
- t.Fatalf("Fetch step = %+v", step)
324
- }
325
- return
326
- }
327
- t.Fatalf("Fetch flow missing: %+v", got.Flows)
328
- }
329
-
330
- func TestBuildsEndpointRootedProviderFlowFromSource(t *testing.T) {
331
- root := t.TempDir()
332
- writeHTTPFixture(t, root, "go.mod", "module example.com/travel\n")
333
- writeHTTPFixture(t, root, "app/main.go", `package app
334
- import (
335
- "example.com/travel/actions/search"
336
- "example.com/travel/connector"
337
- )
338
- type Router struct{}
339
- func (*Router) POST(string, func()) {}
340
- type Requester interface { ConnExec(connector.API) }
341
- func Start(r *Router) {
342
- r.POST("/search", func() { SearchAction() })
343
- app := App{}
344
- r.POST("/direct-search", app.DirectSearch)
345
- }
346
- type App struct{}
347
- func (App) DirectSearch() {
348
- conn := connector.BuildConnector("runtime-name")
349
- conn.Search()
350
- }
351
- func SearchAction() {
352
- request := &search.Request{}
353
- ActionFlow(request)
354
- }
355
- func ActionFlow(request Requester) {
356
- conn := connector.BuildConnector("runtime-name")
357
- request.ConnExec(conn)
358
- }
359
- `)
360
- writeHTTPFixture(t, root, "actions/search/request.go", `package search
361
- import "example.com/travel/connector"
362
- type Request struct{}
363
- func (*Request) ConnExec(conn connector.API) { conn.Search() }
364
- `)
365
- writeHTTPFixture(t, root, "connector/factory.go", `package connector
366
- import (
367
- "example.com/travel/provider/alpha"
368
- "example.com/travel/provider/beta"
369
- "example.com/travel/provider/opaque"
370
- )
371
- type API interface { Search() }
372
- func BuildConnector(name string) API {
373
- switch name {
374
- case "alpha":
375
- return alpha.New()
376
- case "beta":
377
- client := beta.New()
378
- return client
379
- case "opaque":
380
- return opaque.New()
381
- default:
382
- return nil
383
- }
384
- }
385
- `)
386
- writeHTTPFixture(t, root, "provider/opaque/client.go", `package opaque
387
- type Connector struct{}
388
- func New() *Connector { return &Connector{} }
389
- func (*Connector) Search() {}
390
- `)
391
- writeHTTPFixture(t, root, "provider/alpha/client.go", `package alpha
392
- import (
393
- alphaclient "example.com/travel/provider/alpha/client"
394
- alphaport "example.com/travel/provider/alpha/port"
395
- )
396
- type Connector struct{ Client alphaport.API }
397
- func New() *Connector {
398
- client := alphaclient.New()
399
- return &Connector{Client: client}
400
- }
401
- func (c *Connector) Search() {
402
- c.Client.Search()
403
- c.Client.Retrieve()
404
- }
405
- `)
406
- writeHTTPFixture(t, root, "provider/alpha/port/api.go", `package port
407
- type API interface {
408
- Search()
409
- Retrieve()
410
- }
411
- `)
412
- writeHTTPFixture(t, root, "provider/alpha/client/client.go", `package client
413
- import "net/http"
414
- type Client struct{}
415
- func New() *Client { return &Client{} }
416
- func (c *Client) Search() { c.SearchRequest() }
417
- func (*Client) SearchRequest() { _, _ = http.Get("https://alpha.example/v1/search") }
418
- func (c *Client) Retrieve() { c.RetrieveRequest() }
419
- func (*Client) RetrieveRequest() { _, _ = http.Get("https://alpha.example/v1/retrieve") }
420
- `)
421
- writeHTTPFixture(t, root, "provider/beta/client.go", `package beta
422
- import betaclient "example.com/travel/provider/beta/client"
423
- type Connector struct{ Client *betaclient.Client }
424
- func New() *Connector { return &Connector{Client: &betaclient.Client{}} }
425
- func (c *Connector) Search() { c.Client.Search() }
426
- `)
427
- writeHTTPFixture(t, root, "provider/beta/client/client.go", `package client
428
- import "net/http"
429
- type Client struct{}
430
- func (*Client) Search() { _, _ = http.Get("https://beta.example/v2/offers") }
431
- `)
432
- writeHTTPFixture(t, root, "jobs/refresh.go", `package jobs
433
- import "net/http"
434
- func Refresh() { _, _ = http.Get("https://cache.example/refresh") }
435
- `)
436
-
437
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "travel", Service: "search"})
438
- if err != nil {
439
- t.Fatal(err)
440
- }
441
- var got catalog.Catalog
442
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
443
- t.Fatal(err)
444
- }
445
- if len(got.Flows) != 3 {
446
- t.Fatalf("flows = %+v", got.Flows)
447
- }
448
- var endpoint, direct, background *catalog.Flow
449
- for index := range got.Flows {
450
- flow := &got.Flows[index]
451
- if flow.Name == "POST /search → provider APIs" {
452
- endpoint = flow
453
- }
454
- if flow.Name == "POST /direct-search → provider APIs" {
455
- direct = flow
456
- }
457
- if flow.Name == "Refresh → outbound APIs" {
458
- background = flow
459
- }
460
- if strings.Contains(flow.Name, "Connector Search") {
461
- t.Fatalf("covered provider fragment was retained: %+v", flow)
462
- }
463
- if flow.Name == "Alpha Search Request → outbound APIs" {
464
- t.Fatalf("covered nested provider fragment was retained: %+v", flow)
465
- }
466
- }
467
- if endpoint == nil || direct == nil || background == nil {
468
- t.Fatalf("endpoint = %+v, direct = %+v, background = %+v", endpoint, direct, background)
469
- }
470
- if !strings.Contains(background.Summary, "No source caller was found") {
471
- t.Fatalf("standalone reason = %q", background.Summary)
472
- }
473
- if len(endpoint.Steps) != 2 {
474
- t.Fatalf("steps = %+v", endpoint.Steps)
475
- }
476
- inbound, ok := endpoint.Steps[0].(*catalog.Step)
477
- if !ok || inbound.Label != "POST /search" || inbound.Line != "app/main.go:10" {
478
- t.Fatalf("inbound = %+v", endpoint.Steps[0])
479
- }
480
- providers, ok := endpoint.Steps[1].(*catalog.Alt)
481
- if !ok || len(providers.Branches) != 3 {
482
- t.Fatalf("providers = %+v", endpoint.Steps[1])
483
- }
484
- if providers.Branches[0].Title != `connector = "alpha"` || providers.Branches[1].Title != `connector = "beta"` || providers.Branches[2].Title != `connector = "opaque"` {
485
- t.Fatalf("branches = %+v", providers.Branches)
486
- }
487
- alphaStep := providers.Branches[0].Steps[0].(*catalog.Step)
488
- betaStep := providers.Branches[1].Steps[0].(*catalog.Step)
489
- if alphaStep.Label != "GET /v1/search" || betaStep.Label != "GET /v2/offers" {
490
- t.Fatalf("provider steps = %+v / %+v", alphaStep, betaStep)
491
- }
492
- if len(providers.Branches[0].Steps) != 2 || providers.Branches[0].Steps[1].(*catalog.Step).Label != "GET /v1/retrieve" {
493
- t.Fatalf("constructor-wired call order = %+v", providers.Branches[0].Steps)
494
- }
495
- if !strings.Contains(alphaStep.Note, "Connector.Search → Client.Search → Client.SearchRequest") {
496
- t.Fatalf("nested provider chain = %+v", alphaStep)
497
- }
498
- if !strings.Contains(betaStep.Note, "Connector.Search → Client.Search") {
499
- t.Fatalf("concrete imported client chain = %+v", betaStep)
500
- }
501
- opaqueStep := providers.Branches[2].Steps[0].(*catalog.Step)
502
- if opaqueStep.Kind != catalog.StepCall || opaqueStep.Label != "Opaque Search" || !strings.Contains(opaqueStep.Note, "outbound transport was not resolved") {
503
- t.Fatalf("opaque step = %+v", opaqueStep)
504
- }
505
- }
506
-
507
- func TestBuildsEndpointThroughMultiStageInterfaceDispatch(t *testing.T) {
508
- root := t.TempDir()
509
- writeHTTPFixture(t, root, "go.mod", "module example.com/typed\n\ngo 1.27.0\n")
510
- writeHTTPFixture(t, root, "app/main.go", `package app
511
- import (
512
- "example.com/typed/actions/rules"
513
- "example.com/typed/connector"
514
- )
515
- type Router struct{}
516
- func (*Router) POST(string, func()) {}
517
- type Requester interface { ConnExec(connector.API) }
518
- func Start(r *Router) { r.POST("/rules", RulesAction) }
519
- func RulesAction() {
520
- request := &rules.Request{}
521
- Dispatch(request)
522
- }
523
- func Dispatch(request Requester) { Invoke(request) }
524
- func Invoke(request Requester) {
525
- conn := connector.Build("runtime")
526
- request.ConnExec(conn)
527
- }
528
- `)
529
- writeHTTPFixture(t, root, "actions/rules/request.go", `package rules
530
- import "example.com/typed/connector"
531
- type Request struct{}
532
- func (*Request) ConnExec(conn connector.API) { conn.Rules() }
533
- `)
534
- writeHTTPFixture(t, root, "connector/factory.go", `package connector
535
- import "example.com/typed/provider/alpha"
536
- type API interface { Rules() }
537
- func Build(name string) API {
538
- switch name {
539
- case "alpha": return alpha.New()
540
- default: return nil
541
- }
542
- }
543
- `)
544
- writeHTTPFixture(t, root, "provider/alpha/connector.go", `package alpha
545
- import "example.com/typed/provider/alpha/client"
546
- type rulesClient interface { FetchRules() }
547
- type Connector struct { client rulesClient }
548
- func New() *Connector { return &Connector{client: &client.Client{}} }
549
- func (c *Connector) Rules() { c.client.FetchRules() }
550
- `)
551
- writeHTTPFixture(t, root, "provider/alpha/client/client.go", `package client
552
- import "net/http"
553
- type Client struct{}
554
- func (c *Client) FetchRules() { c.fetchRules() }
555
- func (c *Client) fetchRules() { c.finishRules() }
556
- func (c *Client) finishRules() {
557
- _, _ = http.Get("https://alpha.example/v1/rules")
558
- if false { c.fetchRules() }
559
- }
560
- func (*Client) CheckRules() { _, _ = http.Get("https://alpha.example/v1/check-rules") }
561
- `)
562
-
563
- analysis, err := gohttp.Analyze(root)
564
- if err != nil {
565
- t.Fatal(err)
566
- }
567
- if !analysis.TypedCallGraph || analysis.TypedCallGraphError != "" {
568
- t.Fatalf("typed analysis = %v / %q", analysis.TypedCallGraph, analysis.TypedCallGraphError)
569
- }
570
- if len(analysis.EndpointFlows) != 1 || len(analysis.EndpointFlows[0].Branches) != 1 {
571
- t.Fatalf("endpoint flows = %+v", analysis.EndpointFlows)
572
- }
573
- branch := analysis.EndpointFlows[0].Branches[0]
574
- if branch.Operation != "Rules" || len(branch.Calls) != 1 || branch.Calls[0].Path != "/v1/rules" {
575
- t.Fatalf("typed provider branch = %+v", branch)
576
- }
577
-
578
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "travel", Service: "rules"})
579
- if err != nil {
580
- t.Fatal(err)
581
- }
582
- var got catalog.Catalog
583
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
584
- t.Fatal(err)
585
- }
586
- var endpoint, unused *catalog.Flow
587
- for index := range got.Flows {
588
- flow := &got.Flows[index]
589
- if flow.Name == "POST /rules → provider APIs" {
590
- endpoint = flow
591
- }
592
- if strings.Contains(flow.Name, "CheckRules") {
593
- unused = flow
594
- }
595
- if strings.Contains(flow.Name, "Fetch Rules") {
596
- t.Fatalf("covered typed fragment retained: %+v", flow)
597
- }
598
- }
599
- if endpoint == nil || unused == nil {
600
- t.Fatalf("endpoint = %+v, unused = %+v, flows = %+v", endpoint, unused, got.Flows)
601
- }
602
- step := endpoint.Steps[1].(*catalog.Step)
603
- if step.Label != "GET /v1/rules" || !strings.Contains(step.Note, "Connector.Rules → Client.FetchRules") {
604
- t.Fatalf("typed step = %+v", step)
605
- }
606
- if unused.Trigger == nil || unused.Trigger.Kind != "unproven" {
607
- t.Fatalf("unused trigger = %+v", unused.Trigger)
608
- }
609
- }
610
-
611
- func TestFallsBackToSyntaxWhenTypedPackagesDoNotLoad(t *testing.T) {
612
- root := t.TempDir()
613
- writeHTTPFixture(t, root, "go.mod", "module example.com/fallback\n\ngo 1.27.0\n")
614
- writeHTTPFixture(t, root, "app/main.go", `package app
615
- import (
616
- "net/http"
617
- "example.com/fallback/connector"
618
- )
619
- type Router struct{}
620
- func (*Router) POST(string, func()) {}
621
- func Start(r *Router) { r.POST("/search", Search) }
622
- func Search() {
623
- conn := connector.Build("runtime")
624
- conn.Search()
625
- _, _ = http.Get("https://fallback.example/search")
626
- }
627
- `)
628
- writeHTTPFixture(t, root, "connector/factory.go", `package connector
629
- import "example.com/fallback/provider/alpha"
630
- type API interface { Search() }
631
- func Build(name string) API {
632
- switch name { case "only": return alpha.New(); default: return nil }
633
- }
634
- `)
635
- writeHTTPFixture(t, root, "provider/alpha/connector.go", `package alpha
636
- type Connector struct{}
637
- func New() *Connector { return &Connector{} }
638
- func (*Connector) Search() {}
639
- `)
640
- writeHTTPFixture(t, root, "broken/broken.go", `package broken
641
- import _ "example.com/missing/private"
642
- `)
643
-
644
- analysis, err := gohttp.Analyze(root)
645
- if err != nil {
646
- t.Fatal(err)
647
- }
648
- if analysis.TypedCallGraph || analysis.TypedCallGraphError == "" {
649
- t.Fatalf("typed fallback = %v / %q", analysis.TypedCallGraph, analysis.TypedCallGraphError)
650
- }
651
- if len(analysis.Calls) != 1 || analysis.Calls[0].Path != "/search" {
652
- t.Fatalf("syntax calls = %+v", analysis.Calls)
653
- }
654
- }
655
-
656
- func TestBuildsEndpointThroughFixedFactorySetterAndWrapper(t *testing.T) {
657
- root := t.TempDir()
658
- writeHTTPFixture(t, root, "go.mod", "module example.com/wired\n")
659
- writeHTTPFixture(t, root, "app/main.go", `package app
660
- import "example.com/wired/connector"
661
- type Router struct{}
662
- func (*Router) POST(string, func()) {}
663
- func Start(r *Router) { r.POST("/cancel", Cancel) }
664
- func Cancel() {
665
- conn := connector.Build()
666
- conn.Cancel()
667
- }
668
- `)
669
- writeHTTPFixture(t, root, "connector/factory.go", `package connector
670
- import (
671
- "example.com/wired/provider/alpha"
672
- "example.com/wired/provider/wrapper"
673
- )
674
- type API interface{ Cancel() }
675
- func Build() API {
676
- conn := wrapper.New()
677
- client := alpha.New()
678
- conn.SetClient(client)
679
- return conn
680
- }
681
- `)
682
- writeHTTPFixture(t, root, "provider/port/api.go", `package port
683
- type API interface{ Cancel() }
684
- `)
685
- writeHTTPFixture(t, root, "provider/wrapper/connector.go", `package wrapper
686
- import "example.com/wired/provider/port"
687
- type Connector struct{ client port.API }
688
- func New() *Connector { return &Connector{} }
689
- func (c *Connector) SetClient(client port.API) { c.client = client }
690
- func (c *Connector) Cancel() { c.client.Cancel() }
691
- `)
692
- writeHTTPFixture(t, root, "provider/alpha/client.go", `package alpha
693
- import "net/http"
694
- type Client struct{}
695
- func New() *Client { return &Client{} }
696
- func (*Client) Cancel() { _, _ = http.Get("https://alpha.example/v1/cancel") }
697
- `)
698
-
699
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "travel", Service: "cancel"})
700
- if err != nil {
701
- t.Fatal(err)
702
- }
703
- var got catalog.Catalog
704
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
705
- t.Fatal(err)
706
- }
707
- if len(got.Flows) != 1 {
708
- t.Fatalf("flows = %+v", got.Flows)
709
- }
710
- flow := got.Flows[0]
711
- if flow.Name != "POST /cancel → provider APIs" || len(flow.Steps) != 2 {
712
- t.Fatalf("endpoint = %+v", flow)
713
- }
714
- step, ok := flow.Steps[1].(*catalog.Step)
715
- if !ok || step.Label != "GET /v1/cancel" {
716
- t.Fatalf("provider step = %+v", flow.Steps[1])
717
- }
718
- if !strings.Contains(step.Note, "Connector.Cancel → Client.Cancel") {
719
- t.Fatalf("setter-wired wrapper chain = %q", step.Note)
720
- }
721
- }
722
-
723
- func TestBuildsEndpointThroughCapabilityAssertion(t *testing.T) {
724
- root := t.TempDir()
725
- writeHTTPFixture(t, root, "go.mod", "module example.com/capability\n")
726
- writeHTTPFixture(t, root, "app/main.go", `package app
727
- import (
728
- "example.com/capability/actions/documents"
729
- "example.com/capability/connector"
730
- )
731
- type Router struct{}
732
- func (*Router) POST(string, func()) {}
733
- type Requester interface{ ConnExec(connector.API) }
734
- func Start(r *Router) { r.POST("/documents", DocumentsAction) }
735
- func DocumentsAction() {
736
- request := documents.Request{}
737
- ActionFlow(&request)
738
- }
739
- func ActionFlow(request Requester) {
740
- conn := connector.Build("runtime")
741
- request.ConnExec(conn)
742
- }
743
- `)
744
- writeHTTPFixture(t, root, "actions/documents/request.go", `package documents
745
- import "example.com/capability/connector"
746
- type Request struct{}
747
- func (*Request) ConnExec(conn connector.API) {
748
- getter, ok := conn.(interface{ GetDocuments() })
749
- if ok { getter.GetDocuments() }
750
- }
751
- `)
752
- writeHTTPFixture(t, root, "connector/factory.go", `package connector
753
- import (
754
- "example.com/capability/provider/alpha"
755
- "example.com/capability/provider/beta"
756
- )
757
- type API interface{}
758
- func Build(name string) API {
759
- switch name {
760
- case "alpha": return alpha.New()
761
- case "beta": return beta.New()
762
- default: return nil
763
- }
764
- }
765
- `)
766
- writeHTTPFixture(t, root, "provider/alpha/client.go", `package alpha
767
- import "net/http"
768
- type Client struct{}
769
- func New() *Client { return &Client{} }
770
- func (*Client) GetDocuments() { _, _ = http.Get("https://alpha.example/v1/documents") }
771
- `)
772
- writeHTTPFixture(t, root, "provider/beta/client.go", `package beta
773
- type Client struct{}
774
- func New() *Client { return &Client{} }
775
- `)
776
-
777
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "travel", Service: "documents"})
778
- if err != nil {
779
- t.Fatal(err)
780
- }
781
- var got catalog.Catalog
782
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
783
- t.Fatal(err)
784
- }
785
- if len(got.Flows) != 1 {
786
- t.Fatalf("flows = %+v", got.Flows)
787
- }
788
- flow := got.Flows[0]
789
- if flow.Name != "POST /documents → provider APIs" || len(flow.Steps) != 2 {
790
- t.Fatalf("endpoint = %+v", flow)
791
- }
792
- step, ok := flow.Steps[1].(*catalog.Step)
793
- if !ok || step.Label != "GET /v1/documents" {
794
- t.Fatalf("capability step = %+v", flow.Steps[1])
795
- }
796
- }
797
-
798
- func TestBuildsProviderBranchesFromConstructorMap(t *testing.T) {
799
- root := t.TempDir()
800
- writeHTTPFixture(t, root, "go.mod", "module example.com/registry\n")
801
- writeHTTPFixture(t, root, "app/main.go", `package app
802
- import "example.com/registry/connector"
803
- type Router struct{}
804
- func (*Router) POST(string, func()) {}
805
- func Start(r *Router) { r.POST("/search", Search) }
806
- func Search() {
807
- conn := connector.Build("runtime")
808
- conn.Search()
809
- }
810
- `)
811
- writeHTTPFixture(t, root, "connector/factory.go", `package connector
812
- import (
813
- "example.com/registry/provider/alpha"
814
- "example.com/registry/provider/beta"
815
- )
816
- type API interface{ Search() }
817
- var providers = map[string]func() API{
818
- "alpha": func() API { return alpha.New() },
819
- "beta": beta.New,
820
- }
821
- func Build(name string) API { return providers[name]() }
822
- `)
823
- writeHTTPFixture(t, root, "provider/alpha/client.go", `package alpha
824
- import "net/http"
825
- type Client struct{}
826
- func New() *Client { return &Client{} }
827
- func (*Client) Search() { _, _ = http.Get("https://alpha.example/search") }
828
- `)
829
- writeHTTPFixture(t, root, "provider/beta/client.go", `package beta
830
- import "net/http"
831
- type Client struct{}
832
- func New() *Client { return &Client{} }
833
- func (*Client) Search() { _, _ = http.Get("https://beta.example/search") }
834
- `)
835
-
836
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "travel", Service: "search"})
837
- if err != nil {
838
- t.Fatal(err)
839
- }
840
- var got catalog.Catalog
841
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
842
- t.Fatal(err)
843
- }
844
- if len(got.Flows) != 1 {
845
- t.Fatalf("flows = %+v", got.Flows)
846
- }
847
- flow := got.Flows[0]
848
- providers, ok := flow.Steps[1].(*catalog.Alt)
849
- if !ok || len(providers.Branches) != 2 {
850
- t.Fatalf("provider map branches = %+v", flow.Steps)
851
- }
852
- if providers.Branches[0].Title != `connector = "alpha"` || providers.Branches[1].Title != `connector = "beta"` {
853
- t.Fatalf("provider map titles = %+v", providers.Branches)
854
- }
855
- for _, branch := range providers.Branches {
856
- step := branch.Steps[len(branch.Steps)-1].(*catalog.Step)
857
- if len(step.Reaches) != 1 || !strings.HasSuffix(step.Reaches[0], ":Client.Search") {
858
- t.Fatalf("provider reachability = %+v", step.Reaches)
859
- }
860
- }
861
- }
862
-
863
- func TestBuildsDirectHTTPRootThroughClosureAndLocalVariable(t *testing.T) {
864
- root := t.TempDir()
865
- writeHTTPFixture(t, root, "go.mod", "module example.com/refresh\n")
866
- writeHTTPFixture(t, root, "app/routes.go", `package app
867
- import "example.com/refresh/cache"
868
- type Router struct{}
869
- func (*Router) POST(string, func()) {}
870
- func Routes(r *Router) {
871
- state := cache.New()
872
- r.POST("/debug/refresh", func() { _ = state.Update() })
873
- }
874
- `)
875
- writeHTTPFixture(t, root, "cache/cache.go", `package cache
876
- import "net/http"
877
- type Cache struct{}
878
- func New() *Cache { return &Cache{} }
879
- func (*Cache) Update() error {
880
- _, err := http.Get("https://geo.example/v1/airports")
881
- return err
882
- }
883
- `)
884
-
885
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "ops", Service: "catalog"})
886
- if err != nil {
887
- t.Fatal(err)
888
- }
889
- var got catalog.Catalog
890
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
891
- t.Fatal(err)
892
- }
893
- if len(got.Flows) != 1 {
894
- t.Fatalf("flows = %+v", got.Flows)
895
- }
896
- flow := got.Flows[0]
897
- if flow.Name != "POST /debug/refresh → outbound APIs" || len(flow.Steps) != 2 {
898
- t.Fatalf("rooted flow = %+v", flow)
899
- }
900
- if flow.Trigger == nil || flow.Trigger.Kind != "http" || flow.Trigger.Confidence != "high" {
901
- t.Fatalf("trigger = %+v", flow.Trigger)
902
- }
903
- if step := flow.Steps[1].(*catalog.Step); step.Label != "GET /v1/airports" || len(step.Reaches) != 1 || step.Reaches[0] != "cache:Cache.Update" {
904
- t.Fatalf("outbound step = %+v", step)
905
- }
906
- }
907
-
908
- func TestBuildsAnnotatedCallbackRootAndCoversNestedHelper(t *testing.T) {
909
- root := t.TempDir()
910
- writeHTTPFixture(t, root, "go.mod", "module example.com/callback\n")
911
- writeHTTPFixture(t, root, "callbacks/callback.go", `package callbacks
912
- import "net/http"
913
- type Registry struct{}
914
- func (*Registry) Register(string, string, func()) {}
915
- func Mount(registry *Registry) {
916
- registry.Register("provider", "completed", Completed())
917
- }
918
- // Completed receives the provider webhook.
919
- // @Router /callbacks/provider/completed [post]
920
- func Completed() func() {
921
- return func() { notifyCore() }
922
- }
923
- func notifyCore() { _, _ = http.Post("https://core.example/protected/update", "application/json", nil) }
924
- `)
925
-
926
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "travel", Service: "callbacks"})
927
- if err != nil {
928
- t.Fatal(err)
929
- }
930
- var got catalog.Catalog
931
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
932
- t.Fatal(err)
933
- }
934
- if len(got.Flows) != 1 {
935
- t.Fatalf("flows = %+v", got.Flows)
936
- }
937
- flow := got.Flows[0]
938
- if flow.Name != "POST /callbacks/provider/completed → outbound APIs" || len(flow.Steps) != 2 {
939
- t.Fatalf("callback flow = %+v", flow)
940
- }
941
- if flow.Trigger == nil || flow.Trigger.Kind != "callback" || flow.Trigger.Confidence != "high" {
942
- t.Fatalf("trigger = %+v", flow.Trigger)
943
- }
944
- if flow.Source != "callbacks/callback.go:6" {
945
- t.Fatalf("registration source = %q", flow.Source)
946
- }
947
- step := flow.Steps[1].(*catalog.Step)
948
- if step.Label != "POST /protected/update" || !strings.Contains(step.Note, "Completed → notifyCore") {
949
- t.Fatalf("callback continuation = %+v", step)
950
- }
951
- }
952
-
953
- func TestBuildsStartupRootFromMainAssembly(t *testing.T) {
954
- root := t.TempDir()
955
- writeHTTPFixture(t, root, "go.mod", "module example.com/startup\n")
956
- writeHTTPFixture(t, root, "cmd/catalog/main.go", `package main
957
- import app "example.com/startup/internal/app/catalog"
958
- func main() { app.Run() }
959
- `)
960
- writeHTTPFixture(t, root, "internal/app/catalog/run.go", `package catalog
961
- import "example.com/startup/cache"
962
- func Run() {
963
- state := cache.New()
964
- if err := state.Warm(); err != nil { panic(err) }
965
- }
966
- `)
967
- writeHTTPFixture(t, root, "cache/cache.go", `package cache
968
- import "net/http"
969
- type Cache struct{}
970
- func New() *Cache { return &Cache{} }
971
- func (*Cache) Warm() error {
972
- _, err := http.Get("https://geo.example/v1/bootstrap")
973
- return err
974
- }
975
- `)
976
-
977
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "ops", Service: "catalog"})
978
- if err != nil {
979
- t.Fatal(err)
980
- }
981
- var got catalog.Catalog
982
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
983
- t.Fatal(err)
984
- }
985
- if len(got.Flows) != 1 {
986
- t.Fatalf("flows = %+v", got.Flows)
987
- }
988
- flow := got.Flows[0]
989
- if flow.Trigger == nil || flow.Trigger.Kind != "startup" || flow.Trigger.Confidence != "high" {
990
- t.Fatalf("trigger = %+v", flow.Trigger)
991
- }
992
- if flow.Name != "Startup → Cache.Warm → outbound APIs" || flow.Source != "internal/app/catalog/run.go:5" {
993
- t.Fatalf("startup flow = %+v", flow)
994
- }
995
- }
996
-
997
- func TestBuildsScheduledRootFromTimerRegistration(t *testing.T) {
998
- root := t.TempDir()
999
- writeHTTPFixture(t, root, "go.mod", "module example.com/scheduled\n")
1000
- writeHTTPFixture(t, root, "jobs/refresh.go", `package jobs
1001
- import (
1002
- "net/http"
1003
- "time"
1004
- )
1005
- func Register() {
1006
- time.AfterFunc(5*time.Minute, func() { Refresh() })
1007
- }
1008
- func Refresh() { _, _ = http.Get("https://catalog.example/v1/refresh") }
1009
- `)
1010
-
1011
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "ops", Service: "catalog"})
1012
- if err != nil {
1013
- t.Fatal(err)
1014
- }
1015
- var got catalog.Catalog
1016
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
1017
- t.Fatal(err)
1018
- }
1019
- if len(got.Flows) != 1 {
1020
- t.Fatalf("flows = %+v", got.Flows)
1021
- }
1022
- flow := got.Flows[0]
1023
- if flow.Trigger == nil || flow.Trigger.Kind != "scheduled" || flow.Trigger.Confidence != "high" {
1024
- t.Fatalf("trigger = %+v", flow.Trigger)
1025
- }
1026
- if !strings.Contains(flow.Name, "Schedule 5 * time.Minute → Refresh") || len(flow.Steps) != 2 {
1027
- t.Fatalf("scheduled flow = %+v", flow)
1028
- }
1029
- }
1030
-
1031
- func TestProviderInitIsBusinessOperationAndIncludesNestedHelper(t *testing.T) {
1032
- root := t.TempDir()
1033
- writeHTTPFixture(t, root, "go.mod", "module example.com/providerinit\n")
1034
- writeHTTPFixture(t, root, "app/routes.go", `package app
1035
- import "example.com/providerinit/connector"
1036
- type Router struct{}
1037
- func (*Router) POST(string, func()) {}
1038
- func Routes(r *Router) { r.POST("/initialize", Initialize) }
1039
- func Initialize() {
1040
- conn := connector.Build()
1041
- conn.Init()
1042
- }
1043
- `)
1044
- writeHTTPFixture(t, root, "connector/factory.go", `package connector
1045
- import "example.com/providerinit/provider"
1046
- type API interface{ Init() }
1047
- func Build() API { return provider.New() }
1048
- `)
1049
- writeHTTPFixture(t, root, "provider/provider.go", `package provider
1050
- import "net/http"
1051
- type Connector struct{}
1052
- func New() *Connector { return &Connector{} }
1053
- func (c *Connector) Init() { c.waitUntilReady() }
1054
- func (*Connector) waitUntilReady() { _, _ = http.Get("https://provider.example/v1/status") }
1055
- `)
1056
-
1057
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "travel", Service: "booking"})
1058
- if err != nil {
1059
- t.Fatal(err)
1060
- }
1061
- var got catalog.Catalog
1062
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
1063
- t.Fatal(err)
1064
- }
1065
- if len(got.Flows) != 1 {
1066
- t.Fatalf("flows = %+v", got.Flows)
1067
- }
1068
- flow := got.Flows[0]
1069
- if flow.Name != "POST /initialize → provider APIs" || len(flow.Steps) != 2 {
1070
- t.Fatalf("endpoint flow = %+v", flow)
1071
- }
1072
- step := flow.Steps[1].(*catalog.Step)
1073
- if step.Label != "GET /v1/status" || !strings.Contains(step.Note, "Connector.Init → Connector.waitUntilReady") {
1074
- t.Fatalf("nested Init flow = %+v", step)
1075
- }
1076
- }
1077
-
1078
- func TestDoesNotGuessBetweenMultipleConstructorWiredImplementations(t *testing.T) {
1079
- root := t.TempDir()
1080
- writeHTTPFixture(t, root, "go.mod", "module example.com/ambiguous\n")
1081
- writeHTTPFixture(t, root, "port/api.go", `package port
1082
- type API interface{ Cancel() }
1083
- `)
1084
- writeHTTPFixture(t, root, "connector/connector.go", `package connector
1085
- import (
1086
- "example.com/ambiguous/port"
1087
- "example.com/ambiguous/provider/alpha"
1088
- "example.com/ambiguous/provider/beta"
1089
- )
1090
- type Connector struct{ client port.API }
1091
- func NewAlpha() *Connector { return &Connector{client: &alpha.Client{}} }
1092
- func NewBeta() *Connector { return &Connector{client: &beta.Client{}} }
1093
- func (c *Connector) Cancel() { c.client.Cancel() }
1094
- `)
1095
- writeHTTPFixture(t, root, "provider/alpha/client.go", `package alpha
1096
- import "net/http"
1097
- type Client struct{}
1098
- func (*Client) Cancel() { _, _ = http.Get("https://alpha.example/cancel") }
1099
- `)
1100
- writeHTTPFixture(t, root, "provider/beta/client.go", `package beta
1101
- import "net/http"
1102
- type Client struct{}
1103
- func (*Client) Cancel() { _, _ = http.Get("https://beta.example/cancel") }
1104
- `)
1105
-
1106
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "travel", Service: "cancel"})
1107
- if err != nil {
1108
- t.Fatal(err)
1109
- }
1110
- var got catalog.Catalog
1111
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
1112
- t.Fatal(err)
1113
- }
1114
- if len(got.Flows) != 2 {
1115
- t.Fatalf("ambiguous wiring created a composed flow: %+v", got.Flows)
1116
- }
1117
- for _, flow := range got.Flows {
1118
- if flow.Name == "Connector Cancel → outbound APIs" {
1119
- t.Fatalf("ambiguous receiver was guessed: %+v", flow)
1120
- }
1121
- }
1122
- }
1123
-
1124
- func TestCoveredEndpointCallRemovesWrapperWithoutAChainName(t *testing.T) {
1125
- call := gohttp.Call{
1126
- Function: "provider:Client.Send",
1127
- ID: "http-client/POST /send",
1128
- Source: gohttp.Source{File: "provider/client.go", Line: 42},
1129
- }
1130
- groups := []gohttp.FlowGroup{{Function: "provider:Connector.Publish", Calls: []gohttp.Call{call}}}
1131
- endpoints := []gohttp.EndpointFlow{{Branches: []gohttp.EndpointBranch{{
1132
- Function: "provider:Client.Send",
1133
- Calls: []gohttp.Call{call},
1134
- }}}}
1135
- covered := map[string]bool{}
1136
-
1137
- coverEndpointDescendants(covered, endpoints, groups)
1138
-
1139
- if !covered["provider:Connector.Publish"] {
1140
- t.Fatalf("exact source-backed wrapper was not covered: %+v", covered)
1141
- }
1142
- }
1143
-
1144
- func TestOnlySourceBackedComplementsBecomeAnAlt(t *testing.T) {
1145
- context := "edge"
1146
- calls := []gohttp.Call{
1147
- {Function: "choose", ID: "http-client/GET /one", Method: "GET", Path: "/one", Conditions: []string{"ready"}},
1148
- {Function: "choose", ID: "http-client/GET /two", Method: "GET", Path: "/two", Conditions: []string{"not (ready)"}},
1149
- }
1150
- flows := flowsOf("edge.gateway", context, calls, Options{})
1151
- if len(flows) != 1 || len(flows[0].Steps) != 1 {
1152
- t.Fatalf("flows = %+v", flows)
1153
- }
1154
- alt, ok := flows[0].Steps[0].(*catalog.Alt)
1155
- if !ok || alt.Branches[0].Title != "ready" || alt.Branches[1].Title != "not (ready)" {
1156
- t.Fatalf("alt = %+v", flows[0].Steps[0])
1157
- }
1158
-
1159
- calls[1].Conditions = nil
1160
- flows = flowsOf("edge.gateway", context, calls, Options{})
1161
- if _, ok := flows[0].Steps[0].(*catalog.Alt); ok {
1162
- t.Fatal("unrelated calls became an alternative")
1163
- }
1164
- }
1165
-
1166
- func TestLearnsHandWrittenSOAPWrapperArgumentPositions(t *testing.T) {
1167
- root := t.TempDir()
1168
- writeHTTPFixture(t, root, "go.mod", "module example.com/checkout\n")
1169
- writeHTTPFixture(t, root, "soap/booking.wsdl", `
1170
- <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="urn:booking" targetNamespace="urn:booking" name="Booking">
1171
- <message name="CancelRequest"/><message name="CancelResponse"/>
1172
- <portType name="BookingPortType"><operation name="Cancel"><input message="tns:CancelRequest"/><output message="tns:CancelResponse"/></operation></portType>
1173
- <binding name="BookingBinding" type="tns:BookingPortType"><soap:binding style="document"/><operation name="Cancel"><soap:operation soapAction="urn:booking:cancel"/></operation></binding>
1174
- <service name="BookingService"><port name="BookingPort" binding="tns:BookingBinding"><soap:address location="https://booking.example/soap"/></port></service>
1175
- </definitions>`)
1176
- writeHTTPFixture(t, root, "soap/transport.go", `package soap
1177
- import "net/http"
1178
- type Transport struct{}
1179
- func (t *Transport) Call(actionBase, operation string, messageID string, query, reply any) error {
1180
- req, err := http.NewRequest(http.MethodPost, "https://booking.example/soap", nil)
1181
- if err != nil { return err }
1182
- req.Header.Add("SOAPAction", actionBase+operation)
1183
- return nil
1184
- }
1185
-
1186
- type Service struct{ transport *Transport }
1187
- func (s *Service) Call(actionBase, operation string, query, reply any, stage string, options ...func()) error {
1188
- return s.transport.Call(actionBase, operation, "message-id", query, reply)
1189
- }`)
1190
- writeHTTPFixture(t, root, "checkout/cancel.go", `package checkout
1191
- import soapclient "example.com/checkout/soap"
1192
- const actionBase = "urn:booking:"
1193
- func CancelBooking(client *soapclient.Service, request, response any) error {
1194
- return client.Call(actionBase, "cancel", request, response, "checkout")
1195
- }`)
1196
-
1197
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "checkout", Service: "gateway"})
1198
- if err != nil {
1199
- t.Fatal(err)
1200
- }
1201
- var got catalog.Catalog
1202
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
1203
- t.Fatal(err)
1204
- }
1205
- service := got.Contexts[0].Services[0]
1206
- if len(service.Consumes) != 1 {
1207
- t.Fatalf("consumes = %+v", service.Consumes)
1208
- }
1209
- call := service.Consumes[0]
1210
- if call.ID != "bookingservice.soap.BookingPort/Cancel" || call.Status != catalog.StatusDeclared || call.Source != "soap/booking.wsdl" {
1211
- t.Fatalf("call = %+v", call)
1212
- }
1213
- if !strings.Contains(call.Note, "request 'request'") || !strings.Contains(call.Note, "response 'response'") {
1214
- t.Fatalf("note = %q", call.Note)
1215
- }
1216
- if len(got.Flows) != 1 || got.Flows[0].Source != "checkout/cancel.go:4" {
1217
- t.Fatalf("flows = %+v", got.Flows)
1218
- }
1219
- }
1220
-
1221
- func TestReadsSOAP12ActionFromContentType(t *testing.T) {
1222
- root := t.TempDir()
1223
- writeHTTPFixture(t, root, "go.mod", "module example.com/soap12\n")
1224
- writeHTTPFixture(t, root, "contract.wsdl", `
1225
- <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:tns="urn:status" targetNamespace="urn:status" name="Status">
1226
- <portType name="StatusPortType"><operation name="Check"/></portType>
1227
- <binding name="Status11Binding" type="tns:StatusPortType"><soap:binding style="document"/><operation name="Check"><soap:operation soapAction="urn:status:check"/></operation></binding>
1228
- <binding name="Status12Binding" type="tns:StatusPortType"><soap12:binding style="document"/><operation name="Check"><soap12:operation soapAction="urn:status:check"/></operation></binding>
1229
- <service name="StatusService">
1230
- <port name="Status11Port" binding="tns:Status11Binding"><soap:address location="https://status.example/soap11"/></port>
1231
- <port name="Status12Port" binding="tns:Status12Binding"><soap12:address location="https://status.example/soap12"/></port>
1232
- </service>
1233
- </definitions>`)
1234
- writeHTTPFixture(t, root, "transport.go", `package soap12
1235
- import "net/http"
1236
- type Client struct{}
1237
- func (c *Client) Send(action string, request, response any) error {
1238
- req, err := http.NewRequest(http.MethodPost, "https://status.example/soap", nil)
1239
- if err != nil { return err }
1240
- req.Header.Set("Content-Type", "application/soap+xml; charset=utf-8; action=\""+action+"\"")
1241
- return nil
1242
- }
1243
- func Check(client *Client, request, response any) error {
1244
- return client.Send("urn:status:check", request, response)
1245
- }`)
1246
-
1247
- resp, err := extract(plugin.Input{Root: root}, Options{Context: "monitoring", Service: "probe"})
1248
- if err != nil {
1249
- t.Fatal(err)
1250
- }
1251
- var got catalog.Catalog
1252
- if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
1253
- t.Fatal(err)
1254
- }
1255
- calls := got.Contexts[0].Services[0].Consumes
1256
- if len(calls) != 1 || calls[0].ID != "statusservice.soap.Status12Port/Check" || calls[0].Status != catalog.StatusDeclared {
1257
- t.Fatalf("calls = %+v", calls)
1258
- }
1259
- method := got.Externals[0].Provides[0].Methods[0]
1260
- if method.SOAP == nil || method.SOAP.Version != "1.2" || method.SOAP.Action != "urn:status:check" {
1261
- t.Fatalf("method = %+v", method)
1262
- }
1263
- }