mcp4openapi 0.2.8 → 0.3.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 (310) hide show
  1. package/README.md +143 -63
  2. package/dist/scripts/validate-profile.js +3 -3
  3. package/dist/scripts/validate-profile.js.map +1 -1
  4. package/dist/src/{oauth-provider.d.ts → auth/oauth-provider.d.ts} +7 -2
  5. package/dist/src/auth/oauth-provider.d.ts.map +1 -0
  6. package/dist/src/{oauth-provider.js → auth/oauth-provider.js} +30 -2
  7. package/dist/src/auth/oauth-provider.js.map +1 -0
  8. package/dist/src/core/cli-config.d.ts +9 -0
  9. package/dist/src/core/cli-config.d.ts.map +1 -0
  10. package/dist/src/core/cli-config.js +124 -0
  11. package/dist/src/core/cli-config.js.map +1 -0
  12. package/dist/src/{constants.d.ts → core/constants.d.ts} +1 -0
  13. package/dist/src/core/constants.d.ts.map +1 -0
  14. package/dist/src/{constants.js → core/constants.js} +1 -0
  15. package/dist/src/core/constants.js.map +1 -0
  16. package/dist/src/{errors.d.ts → core/errors.d.ts} +6 -0
  17. package/dist/src/core/errors.d.ts.map +1 -0
  18. package/dist/src/{errors.js → core/errors.js} +15 -6
  19. package/dist/src/core/errors.js.map +1 -0
  20. package/dist/src/core/filtering.d.ts +19 -0
  21. package/dist/src/core/filtering.d.ts.map +1 -0
  22. package/dist/src/core/filtering.js +292 -0
  23. package/dist/src/core/filtering.js.map +1 -0
  24. package/dist/src/core/index.d.ts +26 -0
  25. package/dist/src/core/index.d.ts.map +1 -0
  26. package/dist/src/core/index.js +275 -0
  27. package/dist/src/core/index.js.map +1 -0
  28. package/dist/src/core/lib.d.ts +8 -0
  29. package/dist/src/core/lib.d.ts.map +1 -0
  30. package/dist/src/core/lib.js +7 -0
  31. package/dist/src/core/lib.js.map +1 -0
  32. package/dist/src/{logger.d.ts → core/logger.d.ts} +6 -1
  33. package/dist/src/core/logger.d.ts.map +1 -0
  34. package/dist/src/{logger.js → core/logger.js} +30 -2
  35. package/dist/src/core/logger.js.map +1 -0
  36. package/dist/src/{metrics.d.ts → core/metrics.d.ts} +11 -0
  37. package/dist/src/core/metrics.d.ts.map +1 -0
  38. package/dist/src/{metrics.js → core/metrics.js} +61 -0
  39. package/dist/src/core/metrics.js.map +1 -0
  40. package/dist/src/core/naming-warnings.d.ts.map +1 -0
  41. package/dist/src/core/naming-warnings.js.map +1 -0
  42. package/dist/src/core/naming.d.ts.map +1 -0
  43. package/dist/src/core/naming.js.map +1 -0
  44. package/dist/src/generated-schemas.d.ts +245 -79
  45. package/dist/src/generated-schemas.d.ts.map +1 -1
  46. package/dist/src/generated-schemas.js +14 -2
  47. package/dist/src/generated-schemas.js.map +1 -1
  48. package/dist/src/index.d.ts +1 -6
  49. package/dist/src/index.d.ts.map +1 -1
  50. package/dist/src/index.js +1 -170
  51. package/dist/src/index.js.map +1 -1
  52. package/dist/src/lib.d.ts +1 -7
  53. package/dist/src/lib.d.ts.map +1 -1
  54. package/dist/src/lib.js +1 -6
  55. package/dist/src/lib.js.map +1 -1
  56. package/dist/src/mcp/mcp-server-manager.d.ts +20 -0
  57. package/dist/src/mcp/mcp-server-manager.d.ts.map +1 -0
  58. package/dist/src/mcp/mcp-server-manager.js +38 -0
  59. package/dist/src/mcp/mcp-server-manager.js.map +1 -0
  60. package/dist/src/{mcp-server.d.ts → mcp/mcp-server.d.ts} +31 -1
  61. package/dist/src/mcp/mcp-server.d.ts.map +1 -0
  62. package/dist/src/{mcp-server.js → mcp/mcp-server.js} +547 -146
  63. package/dist/src/mcp/mcp-server.js.map +1 -0
  64. package/dist/src/{openapi-parser.d.ts → openapi/openapi-parser.d.ts} +1 -1
  65. package/dist/src/openapi/openapi-parser.d.ts.map +1 -0
  66. package/dist/src/{openapi-parser.js → openapi/openapi-parser.js} +2 -2
  67. package/dist/src/openapi/openapi-parser.js.map +1 -0
  68. package/dist/src/{profile-loader.d.ts → profile/profile-loader.d.ts} +3 -2
  69. package/dist/src/profile/profile-loader.d.ts.map +1 -0
  70. package/dist/src/{profile-loader.js → profile/profile-loader.js} +17 -6
  71. package/dist/src/profile/profile-loader.js.map +1 -0
  72. package/dist/src/profile/profile-registry.d.ts +18 -0
  73. package/dist/src/profile/profile-registry.d.ts.map +1 -0
  74. package/dist/src/profile/profile-registry.js +26 -0
  75. package/dist/src/profile/profile-registry.js.map +1 -0
  76. package/dist/src/profile/profile-resolver.d.ts +25 -0
  77. package/dist/src/profile/profile-resolver.d.ts.map +1 -0
  78. package/dist/src/profile/profile-resolver.js +204 -0
  79. package/dist/src/profile/profile-resolver.js.map +1 -0
  80. package/dist/src/profile/startup-profile.d.ts +17 -0
  81. package/dist/src/profile/startup-profile.d.ts.map +1 -0
  82. package/dist/src/profile/startup-profile.js +30 -0
  83. package/dist/src/profile/startup-profile.js.map +1 -0
  84. package/dist/src/profile/startup-validation.d.ts +11 -0
  85. package/dist/src/profile/startup-validation.d.ts.map +1 -0
  86. package/dist/src/profile/startup-validation.js +21 -0
  87. package/dist/src/profile/startup-validation.js.map +1 -0
  88. package/dist/src/testing/dynamic-mock-server.d.ts +24 -0
  89. package/dist/src/testing/dynamic-mock-server.d.ts.map +1 -0
  90. package/dist/src/testing/dynamic-mock-server.js +138 -0
  91. package/dist/src/testing/dynamic-mock-server.js.map +1 -0
  92. package/dist/src/testing/request-assertions.d.ts +5 -0
  93. package/dist/src/testing/request-assertions.d.ts.map +1 -0
  94. package/dist/src/testing/request-assertions.js +165 -0
  95. package/dist/src/testing/request-assertions.js.map +1 -0
  96. package/dist/src/testing/template-utils.d.ts +10 -0
  97. package/dist/src/testing/template-utils.d.ts.map +1 -0
  98. package/dist/src/testing/template-utils.js +72 -0
  99. package/dist/src/testing/template-utils.js.map +1 -0
  100. package/dist/src/testing/test-http-utils.d.ts +1 -1
  101. package/dist/src/testing/test-http-utils.d.ts.map +1 -1
  102. package/dist/src/testing/test-http-utils.js +1 -1
  103. package/dist/src/testing/test-http-utils.js.map +1 -1
  104. package/dist/src/testing/test-loader.d.ts +6 -0
  105. package/dist/src/testing/test-loader.d.ts.map +1 -0
  106. package/dist/src/testing/test-loader.js +212 -0
  107. package/dist/src/testing/test-loader.js.map +1 -0
  108. package/dist/src/testing/test-schema.d.ts +1270 -0
  109. package/dist/src/testing/test-schema.d.ts.map +1 -0
  110. package/dist/src/testing/test-schema.js +76 -0
  111. package/dist/src/testing/test-schema.js.map +1 -0
  112. package/dist/src/tool-filter/compat.d.ts +49 -0
  113. package/dist/src/tool-filter/compat.d.ts.map +1 -0
  114. package/dist/src/tool-filter/compat.js +72 -0
  115. package/dist/src/tool-filter/compat.js.map +1 -0
  116. package/dist/src/tool-filter/config/env-config-parser.d.ts +38 -0
  117. package/dist/src/tool-filter/config/env-config-parser.d.ts.map +1 -0
  118. package/dist/src/tool-filter/config/env-config-parser.js +103 -0
  119. package/dist/src/tool-filter/config/env-config-parser.js.map +1 -0
  120. package/dist/src/tool-filter/config/header-config-parser.d.ts +37 -0
  121. package/dist/src/tool-filter/config/header-config-parser.d.ts.map +1 -0
  122. package/dist/src/tool-filter/config/header-config-parser.js +118 -0
  123. package/dist/src/tool-filter/config/header-config-parser.js.map +1 -0
  124. package/dist/src/tool-filter/errors.d.ts +18 -0
  125. package/dist/src/tool-filter/errors.d.ts.map +1 -0
  126. package/dist/src/tool-filter/errors.js +21 -0
  127. package/dist/src/tool-filter/errors.js.map +1 -0
  128. package/dist/src/tool-filter/filter/filter-engine.d.ts +45 -0
  129. package/dist/src/tool-filter/filter/filter-engine.d.ts.map +1 -0
  130. package/dist/src/tool-filter/filter/filter-engine.js +94 -0
  131. package/dist/src/tool-filter/filter/filter-engine.js.map +1 -0
  132. package/dist/src/tool-filter/filter/filter-rules.d.ts +44 -0
  133. package/dist/src/tool-filter/filter/filter-rules.d.ts.map +1 -0
  134. package/dist/src/tool-filter/filter/filter-rules.js +72 -0
  135. package/dist/src/tool-filter/filter/filter-rules.js.map +1 -0
  136. package/dist/src/tool-filter/filter/global-tool-filter.d.ts +40 -0
  137. package/dist/src/tool-filter/filter/global-tool-filter.d.ts.map +1 -0
  138. package/dist/src/tool-filter/filter/global-tool-filter.js +92 -0
  139. package/dist/src/tool-filter/filter/global-tool-filter.js.map +1 -0
  140. package/dist/src/tool-filter/filter/session-tool-filter.d.ts +29 -0
  141. package/dist/src/tool-filter/filter/session-tool-filter.d.ts.map +1 -0
  142. package/dist/src/tool-filter/filter/session-tool-filter.js +69 -0
  143. package/dist/src/tool-filter/filter/session-tool-filter.js.map +1 -0
  144. package/dist/src/tool-filter/index.d.ts +25 -0
  145. package/dist/src/tool-filter/index.d.ts.map +1 -0
  146. package/dist/src/tool-filter/index.js +30 -0
  147. package/dist/src/tool-filter/index.js.map +1 -0
  148. package/dist/src/tool-filter/integration/tool-filter-service.d.ts +44 -0
  149. package/dist/src/tool-filter/integration/tool-filter-service.d.ts.map +1 -0
  150. package/dist/src/tool-filter/integration/tool-filter-service.js +68 -0
  151. package/dist/src/tool-filter/integration/tool-filter-service.js.map +1 -0
  152. package/dist/src/tool-filter/operation/operation-classifier.d.ts +20 -0
  153. package/dist/src/tool-filter/operation/operation-classifier.d.ts.map +1 -0
  154. package/dist/src/tool-filter/operation/operation-classifier.js +26 -0
  155. package/dist/src/tool-filter/operation/operation-classifier.js.map +1 -0
  156. package/dist/src/tool-filter/operation/operation-detector.d.ts +30 -0
  157. package/dist/src/tool-filter/operation/operation-detector.d.ts.map +1 -0
  158. package/dist/src/tool-filter/operation/operation-detector.js +96 -0
  159. package/dist/src/tool-filter/operation/operation-detector.js.map +1 -0
  160. package/dist/src/tool-filter/operation/operation-resolver.d.ts +22 -0
  161. package/dist/src/tool-filter/operation/operation-resolver.d.ts.map +1 -0
  162. package/dist/src/tool-filter/operation/operation-resolver.js +32 -0
  163. package/dist/src/tool-filter/operation/operation-resolver.js.map +1 -0
  164. package/dist/src/tool-filter/regex/regex-compiler.d.ts +22 -0
  165. package/dist/src/tool-filter/regex/regex-compiler.d.ts.map +1 -0
  166. package/dist/src/tool-filter/regex/regex-compiler.js +56 -0
  167. package/dist/src/tool-filter/regex/regex-compiler.js.map +1 -0
  168. package/dist/src/tool-filter/regex/regex-validator.d.ts +24 -0
  169. package/dist/src/tool-filter/regex/regex-validator.d.ts.map +1 -0
  170. package/dist/src/tool-filter/regex/regex-validator.js +58 -0
  171. package/dist/src/tool-filter/regex/regex-validator.js.map +1 -0
  172. package/dist/src/tool-filter/types.d.ts +92 -0
  173. package/dist/src/tool-filter/types.d.ts.map +1 -0
  174. package/dist/src/tool-filter/types.js +5 -0
  175. package/dist/src/tool-filter/types.js.map +1 -0
  176. package/dist/src/tool-filter/utils.d.ts +11 -0
  177. package/dist/src/tool-filter/utils.d.ts.map +1 -0
  178. package/dist/src/tool-filter/utils.js +13 -0
  179. package/dist/src/tool-filter/utils.js.map +1 -0
  180. package/dist/src/{composite-executor.d.ts → tooling/composite-executor.d.ts} +3 -3
  181. package/dist/src/tooling/composite-executor.d.ts.map +1 -0
  182. package/dist/src/{composite-executor.js → tooling/composite-executor.js} +1 -1
  183. package/dist/src/tooling/composite-executor.js.map +1 -0
  184. package/dist/src/{dag-executor.d.ts → tooling/dag-executor.d.ts} +1 -1
  185. package/dist/src/tooling/dag-executor.d.ts.map +1 -0
  186. package/dist/src/tooling/dag-executor.js.map +1 -0
  187. package/dist/src/{proxy-executor.d.ts → tooling/proxy-executor.d.ts} +2 -2
  188. package/dist/src/tooling/proxy-executor.d.ts.map +1 -0
  189. package/dist/src/{proxy-executor.js → tooling/proxy-executor.js} +8 -1
  190. package/dist/src/tooling/proxy-executor.js.map +1 -0
  191. package/dist/src/{tool-generator.d.ts → tooling/tool-generator.d.ts} +4 -3
  192. package/dist/src/tooling/tool-generator.d.ts.map +1 -0
  193. package/dist/src/{tool-generator.js → tooling/tool-generator.js} +23 -7
  194. package/dist/src/tooling/tool-generator.js.map +1 -0
  195. package/dist/src/{http-client-factory.d.ts → transport/http-client-factory.d.ts} +4 -1
  196. package/dist/src/transport/http-client-factory.d.ts.map +1 -0
  197. package/dist/src/{http-client-factory.js → transport/http-client-factory.js} +13 -3
  198. package/dist/src/transport/http-client-factory.js.map +1 -0
  199. package/dist/src/transport/http-transport-config.d.ts +6 -0
  200. package/dist/src/transport/http-transport-config.d.ts.map +1 -0
  201. package/dist/src/transport/http-transport-config.js +62 -0
  202. package/dist/src/transport/http-transport-config.js.map +1 -0
  203. package/dist/src/{http-transport.d.ts → transport/http-transport.d.ts} +72 -14
  204. package/dist/src/transport/http-transport.d.ts.map +1 -0
  205. package/dist/src/{http-transport.js → transport/http-transport.js} +1166 -493
  206. package/dist/src/transport/http-transport.js.map +1 -0
  207. package/dist/src/{interceptors.d.ts → transport/interceptors.d.ts} +6 -2
  208. package/dist/src/transport/interceptors.d.ts.map +1 -0
  209. package/dist/src/{interceptors.js → transport/interceptors.js} +72 -41
  210. package/dist/src/transport/interceptors.js.map +1 -0
  211. package/dist/src/types/http-transport.d.ts +25 -0
  212. package/dist/src/types/http-transport.d.ts.map +1 -1
  213. package/dist/src/types/profile.d.ts +13 -1
  214. package/dist/src/types/profile.d.ts.map +1 -1
  215. package/dist/src/validation/argument-normalizer.d.ts +6 -0
  216. package/dist/src/validation/argument-normalizer.d.ts.map +1 -0
  217. package/dist/src/validation/argument-normalizer.js +70 -0
  218. package/dist/src/validation/argument-normalizer.js.map +1 -0
  219. package/dist/src/validation/jsonrpc-validator.d.ts.map +1 -0
  220. package/dist/src/validation/jsonrpc-validator.js.map +1 -0
  221. package/dist/src/{schema-validator.d.ts → validation/schema-validator.d.ts} +2 -2
  222. package/dist/src/validation/schema-validator.d.ts.map +1 -0
  223. package/dist/src/validation/schema-validator.js.map +1 -0
  224. package/dist/src/validation/validation-utils.d.ts.map +1 -0
  225. package/dist/src/validation/validation-utils.js.map +1 -0
  226. package/package.json +9 -3
  227. package/profile-schema.json +63 -3
  228. package/profiles/gitlab/developer-profile-oauth.json +1520 -0
  229. package/profiles/gitlab/developer-profile-oauth.test.json +3432 -0
  230. package/profiles/gitlab/openapi.yaml +6891 -0
  231. package/profiles/n8n/openapi.yaml +2441 -0
  232. package/profiles/n8n/profile-optimized.json +965 -0
  233. package/profiles/n8n/profile-optimized.test.json +1078 -0
  234. package/profiles/n8n/profile.json +1033 -0
  235. package/profiles/n8n/profile.test.json +983 -0
  236. package/profiles/n8n-nodes/openapi.yaml +24 -0
  237. package/profiles/n8n-nodes/profile-nodes.json +44 -0
  238. package/profiles/n8n-nodes/profile-nodes.test.json +91 -0
  239. package/profiles/semgrep/openapi.yaml +4706 -0
  240. package/profiles/semgrep/profile.json +692 -0
  241. package/profiles/semgrep/profile.test.json +471 -0
  242. package/profiles/youtrack/openapi.json +16976 -0
  243. package/profiles/youtrack/profile.json +608 -0
  244. package/profiles/youtrack/profile.test.json +1926 -0
  245. package/dist/src/composite-executor.d.ts.map +0 -1
  246. package/dist/src/composite-executor.js.map +0 -1
  247. package/dist/src/constants.d.ts.map +0 -1
  248. package/dist/src/constants.js.map +0 -1
  249. package/dist/src/dag-executor.d.ts.map +0 -1
  250. package/dist/src/dag-executor.js.map +0 -1
  251. package/dist/src/errors.d.ts.map +0 -1
  252. package/dist/src/errors.js.map +0 -1
  253. package/dist/src/http-client-factory.d.ts.map +0 -1
  254. package/dist/src/http-client-factory.js.map +0 -1
  255. package/dist/src/http-transport.d.ts.map +0 -1
  256. package/dist/src/http-transport.js.map +0 -1
  257. package/dist/src/interceptors.d.ts.map +0 -1
  258. package/dist/src/interceptors.js.map +0 -1
  259. package/dist/src/jsonrpc-validator.d.ts.map +0 -1
  260. package/dist/src/jsonrpc-validator.js.map +0 -1
  261. package/dist/src/logger.d.ts.map +0 -1
  262. package/dist/src/logger.js.map +0 -1
  263. package/dist/src/mcp-server.d.ts.map +0 -1
  264. package/dist/src/mcp-server.js.map +0 -1
  265. package/dist/src/metrics.d.ts.map +0 -1
  266. package/dist/src/metrics.js.map +0 -1
  267. package/dist/src/naming-warnings.d.ts.map +0 -1
  268. package/dist/src/naming-warnings.js.map +0 -1
  269. package/dist/src/naming.d.ts.map +0 -1
  270. package/dist/src/naming.js.map +0 -1
  271. package/dist/src/oauth-provider.d.ts.map +0 -1
  272. package/dist/src/oauth-provider.js.map +0 -1
  273. package/dist/src/openapi-parser.d.ts.map +0 -1
  274. package/dist/src/openapi-parser.js.map +0 -1
  275. package/dist/src/profile-loader.d.ts.map +0 -1
  276. package/dist/src/profile-loader.js.map +0 -1
  277. package/dist/src/proxy-executor.d.ts.map +0 -1
  278. package/dist/src/proxy-executor.js.map +0 -1
  279. package/dist/src/schema-validator.d.ts.map +0 -1
  280. package/dist/src/schema-validator.js.map +0 -1
  281. package/dist/src/testing/fixtures.d.ts +0 -684
  282. package/dist/src/testing/fixtures.d.ts.map +0 -1
  283. package/dist/src/testing/fixtures.js +0 -528
  284. package/dist/src/testing/fixtures.js.map +0 -1
  285. package/dist/src/testing/mock-gitlab-server.d.ts +0 -43
  286. package/dist/src/testing/mock-gitlab-server.d.ts.map +0 -1
  287. package/dist/src/testing/mock-gitlab-server.js +0 -1026
  288. package/dist/src/testing/mock-gitlab-server.js.map +0 -1
  289. package/dist/src/testing/mock-semgrep-server.d.ts +0 -32
  290. package/dist/src/testing/mock-semgrep-server.d.ts.map +0 -1
  291. package/dist/src/testing/mock-semgrep-server.js +0 -213
  292. package/dist/src/testing/mock-semgrep-server.js.map +0 -1
  293. package/dist/src/testing/mock-youtrack-server.d.ts +0 -11
  294. package/dist/src/testing/mock-youtrack-server.d.ts.map +0 -1
  295. package/dist/src/testing/mock-youtrack-server.js +0 -152
  296. package/dist/src/testing/mock-youtrack-server.js.map +0 -1
  297. package/dist/src/tool-generator.d.ts.map +0 -1
  298. package/dist/src/tool-generator.js.map +0 -1
  299. package/dist/src/validation-utils.d.ts.map +0 -1
  300. package/dist/src/validation-utils.js.map +0 -1
  301. /package/dist/src/{naming-warnings.d.ts → core/naming-warnings.d.ts} +0 -0
  302. /package/dist/src/{naming-warnings.js → core/naming-warnings.js} +0 -0
  303. /package/dist/src/{naming.d.ts → core/naming.d.ts} +0 -0
  304. /package/dist/src/{naming.js → core/naming.js} +0 -0
  305. /package/dist/src/{dag-executor.js → tooling/dag-executor.js} +0 -0
  306. /package/dist/src/{jsonrpc-validator.d.ts → validation/jsonrpc-validator.d.ts} +0 -0
  307. /package/dist/src/{jsonrpc-validator.js → validation/jsonrpc-validator.js} +0 -0
  308. /package/dist/src/{schema-validator.js → validation/schema-validator.js} +0 -0
  309. /package/dist/src/{validation-utils.d.ts → validation/validation-utils.d.ts} +0 -0
  310. /package/dist/src/{validation-utils.js → validation/validation-utils.js} +0 -0
@@ -1,1026 +0,0 @@
1
- /**
2
- * Mock GitLab API server for integration testing
3
- *
4
- * Why: Enables end-to-end testing without real GitLab instance.
5
- * Tests actual HTTP flow, parameter handling, error scenarios.
6
- */
7
- import { http, HttpResponse } from 'msw';
8
- import { setupServer } from 'msw/node';
9
- import * as fixtures from './fixtures.js';
10
- import { parsePaginationParams, parseSearchParam, parseBranchParams, parseScopeParam, } from './mock-utils.js';
11
- /** Default BASE_URL for GitLab API (used by MSW interceptor) */
12
- export const DEFAULT_BASE_URL = 'https://gitlab.com/api/v4';
13
- /** Default OAuth config */
14
- const DEFAULT_OAUTH_CONFIG = {
15
- oauthBaseUrl: 'http://localhost:4000',
16
- accessToken: 'mock-access-token-12345',
17
- refreshToken: 'mock-refresh-token-67890',
18
- expiresIn: 3600,
19
- };
20
- /**
21
- * Helper: Extract and validate IID from URL
22
- *
23
- * Why: Prevents path traversal and invalid integer attacks
24
- * Returns null if invalid (caller should return 400 Bad Request)
25
- */
26
- function extractIidFromUrl(url) {
27
- const parts = url.split('/');
28
- const iidStr = parts[parts.length - 1];
29
- if (!iidStr || !/^\d+$/.test(iidStr)) {
30
- return null;
31
- }
32
- const iid = parseInt(iidStr, 10);
33
- if (isNaN(iid) || iid < 1 || iid > 2147483647) {
34
- return null;
35
- }
36
- return iid;
37
- }
38
- /**
39
- * Extract merge request IID from notes URL
40
- * URL format: /projects/{project}/merge_requests/{iid}/notes
41
- */
42
- function extractMrIidFromNotesUrl(url) {
43
- const urlWithoutQuery = url.split('?')[0];
44
- const match = urlWithoutQuery.match(/\/merge_requests\/(\d+)\/notes/);
45
- if (!match) {
46
- return null;
47
- }
48
- const iid = parseInt(match[1], 10);
49
- if (isNaN(iid) || iid < 1 || iid > 2147483647) {
50
- return null;
51
- }
52
- return iid;
53
- }
54
- function extractBranchNameFromUrl(url) {
55
- const pathWithoutQuery = url.split('?')[0];
56
- const match = pathWithoutQuery.match(/\/repository\/branches\/([^\/]+)/);
57
- if (!match) {
58
- return null;
59
- }
60
- return decodeURIComponent(match[1]);
61
- }
62
- /**
63
- * Create OAuth handlers for mock OAuth server
64
- */
65
- export function createOAuthHandlers(config = DEFAULT_OAUTH_CONFIG) {
66
- const { oauthBaseUrl, accessToken, refreshToken, expiresIn } = { ...DEFAULT_OAUTH_CONFIG, ...config };
67
- return [
68
- // OAuth Discovery endpoint
69
- http.get(`${oauthBaseUrl}/.well-known/oauth-authorization-server`, () => {
70
- return HttpResponse.json({
71
- issuer: oauthBaseUrl,
72
- authorization_endpoint: `${oauthBaseUrl}/oauth/authorize`,
73
- token_endpoint: `${oauthBaseUrl}/oauth/token`,
74
- response_types_supported: ['code'],
75
- grant_types_supported: ['authorization_code', 'refresh_token'],
76
- code_challenge_methods_supported: ['S256'],
77
- });
78
- }),
79
- // OAuth Authorization endpoint - redirects with code
80
- http.get(`${oauthBaseUrl}/oauth/authorize`, ({ request }) => {
81
- const url = new URL(request.url);
82
- const redirectUri = url.searchParams.get('redirect_uri');
83
- const state = url.searchParams.get('state');
84
- if (!redirectUri) {
85
- return HttpResponse.json({ error: 'missing_redirect_uri' }, { status: 400 });
86
- }
87
- const code = 'mock-code-' + Math.random().toString(36).substring(7);
88
- const redirectUrl = new URL(redirectUri);
89
- redirectUrl.searchParams.set('code', code);
90
- if (state) {
91
- redirectUrl.searchParams.set('state', state);
92
- }
93
- return new HttpResponse(null, {
94
- status: 302,
95
- headers: { Location: redirectUrl.toString() },
96
- });
97
- }),
98
- // OAuth Token endpoint
99
- http.post(`${oauthBaseUrl}/oauth/token`, async ({ request }) => {
100
- const contentType = request.headers.get('content-type') || '';
101
- let params;
102
- if (contentType.includes('application/json')) {
103
- params = await request.json();
104
- }
105
- else {
106
- const body = await request.text();
107
- params = Object.fromEntries(new URLSearchParams(body));
108
- }
109
- const grantType = params.grant_type;
110
- if (grantType === 'authorization_code') {
111
- if (!params.code) {
112
- return HttpResponse.json({ error: 'invalid_grant' }, { status: 400 });
113
- }
114
- return HttpResponse.json({
115
- access_token: accessToken,
116
- refresh_token: refreshToken,
117
- token_type: 'Bearer',
118
- expires_in: expiresIn,
119
- scope: 'api',
120
- });
121
- }
122
- if (grantType === 'refresh_token') {
123
- if (!params.refresh_token) {
124
- return HttpResponse.json({ error: 'invalid_grant' }, { status: 400 });
125
- }
126
- return HttpResponse.json({
127
- access_token: `${accessToken}-refreshed`,
128
- refresh_token: `${refreshToken}-new`,
129
- token_type: 'Bearer',
130
- expires_in: expiresIn,
131
- scope: 'api',
132
- });
133
- }
134
- return HttpResponse.json({ error: 'unsupported_grant_type' }, { status: 400 });
135
- }),
136
- ];
137
- }
138
- /**
139
- * Create GitLab API handlers with configurable base URL
140
- */
141
- export function createGitLabHandlers(baseUrl = DEFAULT_BASE_URL) {
142
- const branchStates = new Map(fixtures.mockBranchesList.map((branch) => [
143
- branch.name,
144
- structuredClone(branch),
145
- ]));
146
- const escapedBaseUrl = baseUrl.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
147
- const findBranch = (name) => {
148
- if (!name)
149
- return undefined;
150
- return branchStates.get(name);
151
- };
152
- const listBranches = () => Array.from(branchStates.values());
153
- return [
154
- // Groups
155
- http.get(`${baseUrl}/groups`, ({ request }) => {
156
- const { page } = parsePaginationParams(request);
157
- const search = parseSearchParam(request);
158
- let groups = fixtures.mockGroupsList;
159
- if (search) {
160
- groups = groups.filter(g => g.name.toLowerCase().includes(search.toLowerCase()) ||
161
- g.path.toLowerCase().includes(search.toLowerCase()));
162
- }
163
- if (page > 1) {
164
- return HttpResponse.json([]);
165
- }
166
- return HttpResponse.json(groups);
167
- }),
168
- http.get(`${baseUrl}/groups/:id`, ({ params }) => {
169
- const groupId = params.id;
170
- if (groupId === '36173' || groupId === 'davidruzicka') {
171
- return HttpResponse.json(fixtures.mockGroup);
172
- }
173
- return HttpResponse.json({ message: 'Group Not Found' }, { status: 404 });
174
- }),
175
- http.get(`${baseUrl}/groups/:id/projects`, ({ request, params }) => {
176
- const groupId = params.id;
177
- const { page } = parsePaginationParams(request);
178
- if (groupId === '36173' || groupId === 'davidruzicka') {
179
- if (page > 1) {
180
- return HttpResponse.json([]);
181
- }
182
- return HttpResponse.json(fixtures.mockProjectsList);
183
- }
184
- return HttpResponse.json({ message: 'Group Not Found' }, { status: 404 });
185
- }),
186
- http.get(`${baseUrl}/groups/:id/subgroups`, ({ request, params }) => {
187
- const groupId = params.id;
188
- const { page } = parsePaginationParams(request);
189
- if (groupId === '36173' || groupId === 'davidruzicka') {
190
- if (page > 1) {
191
- return HttpResponse.json([]);
192
- }
193
- return HttpResponse.json(fixtures.mockSubgroupsList);
194
- }
195
- return HttpResponse.json({ message: 'Group Not Found' }, { status: 404 });
196
- }),
197
- // Projects
198
- http.get(`${baseUrl}/projects`, ({ request }) => {
199
- const { page } = parsePaginationParams(request);
200
- const search = parseSearchParam(request);
201
- let projects = fixtures.mockProjectsList;
202
- if (search) {
203
- projects = projects.filter(p => p.name.toLowerCase().includes(search.toLowerCase()) ||
204
- p.description?.toLowerCase().includes(search.toLowerCase()));
205
- }
206
- if (page > 1) {
207
- return HttpResponse.json([]);
208
- }
209
- return HttpResponse.json(projects);
210
- }),
211
- http.get(`${baseUrl}/projects/:id`, ({ params }) => {
212
- const projectId = params.id;
213
- if (projectId === '12345' || projectId === 'davidruzicka%2Fmcp4openapi') {
214
- return HttpResponse.json(fixtures.mockProject);
215
- }
216
- return HttpResponse.json({ message: 'Project Not Found' }, { status: 404 });
217
- }),
218
- // Project Badges
219
- http.get(`${baseUrl}/projects/*/badges`, ({ request }) => {
220
- const { page } = parsePaginationParams(request);
221
- if (page === 1) {
222
- return HttpResponse.json(fixtures.mockBadgesList);
223
- }
224
- return HttpResponse.json([]);
225
- }),
226
- http.get(`${baseUrl}/projects/*/badges/*`, ({ request }) => {
227
- const badgeId = extractIidFromUrl(request.url);
228
- if (badgeId === null) {
229
- return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
230
- }
231
- if (badgeId === 1) {
232
- return HttpResponse.json(fixtures.mockBadge);
233
- }
234
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
235
- }),
236
- http.post(`${baseUrl}/projects/*/badges`, async ({ request }) => {
237
- const body = await request.json();
238
- if (!body.link_url || !body.image_url) {
239
- return HttpResponse.json({ error: 'link_url and image_url are required' }, { status: 400 });
240
- }
241
- return HttpResponse.json({
242
- ...fixtures.mockBadge,
243
- id: 3,
244
- name: body.name || 'New Badge',
245
- link_url: body.link_url,
246
- image_url: body.image_url,
247
- }, { status: 201 });
248
- }),
249
- http.put(`${baseUrl}/projects/*/badges/*`, async ({ request }) => {
250
- const badgeId = extractIidFromUrl(request.url);
251
- if (badgeId === null) {
252
- return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
253
- }
254
- const body = await request.json();
255
- if (badgeId === 1) {
256
- return HttpResponse.json({
257
- ...fixtures.mockBadge,
258
- name: body.name || fixtures.mockBadge.name,
259
- link_url: body.link_url || fixtures.mockBadge.link_url,
260
- image_url: body.image_url || fixtures.mockBadge.image_url,
261
- });
262
- }
263
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
264
- }),
265
- http.delete(`${baseUrl}/projects/*/badges/*`, ({ request }) => {
266
- const badgeId = extractIidFromUrl(request.url);
267
- if (badgeId === null) {
268
- return HttpResponse.json({ error: 'Invalid badge ID' }, { status: 400 });
269
- }
270
- if (badgeId === 1) {
271
- return new HttpResponse(null, { status: 204 });
272
- }
273
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
274
- }),
275
- // Group Badges
276
- http.get(`${baseUrl}/groups/*/badges`, () => {
277
- return HttpResponse.json(fixtures.mockBadgesList);
278
- }),
279
- http.post(`${baseUrl}/groups/*/badges`, async ({ request }) => {
280
- const body = await request.json();
281
- return HttpResponse.json({
282
- ...fixtures.mockBadge,
283
- id: 4,
284
- kind: 'group',
285
- name: body.name || 'Group Badge',
286
- }, { status: 201 });
287
- }),
288
- // Branches
289
- http.get(`${baseUrl}/projects/*/repository/branches`, ({ request }) => {
290
- const search = parseSearchParam(request);
291
- let branches = listBranches();
292
- if (search) {
293
- branches = branches.filter((b) => b.name.includes(search));
294
- }
295
- return HttpResponse.json(branches);
296
- }),
297
- http.head(`${baseUrl}/projects/*/repository/branches/*`, ({ request }) => {
298
- const branchName = extractBranchNameFromUrl(request.url);
299
- const branch = findBranch(branchName);
300
- if (!branch) {
301
- return HttpResponse.json({ message: 'Branch Not Found' }, { status: 404 });
302
- }
303
- return HttpResponse.json({ exists: true });
304
- }),
305
- http.get(`${baseUrl}/projects/*/repository/branches/*`, ({ request }) => {
306
- const branchName = extractBranchNameFromUrl(request.url);
307
- const branch = findBranch(branchName);
308
- if (branch) {
309
- return HttpResponse.json(branch);
310
- }
311
- return HttpResponse.json({ message: 'Branch Not Found' }, { status: 404 });
312
- }),
313
- http.post(`${baseUrl}/projects/*/repository/branches`, async ({ request }) => {
314
- const { branch, ref } = parseBranchParams(request);
315
- if (!branch || !ref) {
316
- return HttpResponse.json({ error: 'branch and ref parameters are required' }, { status: 400 });
317
- }
318
- const baseCommit = structuredClone(fixtures.mockBranch.commit);
319
- const newBranch = {
320
- name: branch,
321
- commit: {
322
- ...baseCommit,
323
- id: `${branch}-${Date.now()}`,
324
- short_id: `${branch.substring(0, 7)}-new`
325
- },
326
- merged: false,
327
- protected: false,
328
- default: false,
329
- can_push: true,
330
- web_url: `https://gitlab.com/my-org/my-project/-/tree/${encodeURIComponent(branch)}`,
331
- };
332
- branchStates.set(branch, newBranch);
333
- return HttpResponse.json(newBranch, { status: 201 });
334
- }),
335
- http.delete(`${baseUrl}/projects/*/repository/branches/*`, ({ request }) => {
336
- const branchName = extractBranchNameFromUrl(request.url);
337
- const branch = findBranch(branchName);
338
- if (!branch) {
339
- return HttpResponse.json({ message: 'Branch Not Found' }, { status: 404 });
340
- }
341
- if (branch.default) {
342
- return HttpResponse.json({ message: 'Cannot delete default branch' }, { status: 403 });
343
- }
344
- branchStates.delete(branchName);
345
- return new HttpResponse(null, { status: 204 });
346
- }),
347
- http.put(`${baseUrl}/projects/*/repository/branches/*/protect`, async ({ request }) => {
348
- const branchName = extractBranchNameFromUrl(request.url);
349
- const branch = findBranch(branchName);
350
- if (!branch) {
351
- return HttpResponse.json({ message: 'Branch Not Found' }, { status: 404 });
352
- }
353
- const body = await request.json();
354
- branch.protected = true;
355
- if (typeof body.developers_can_push === 'boolean') {
356
- branch.developers_can_push = body.developers_can_push;
357
- }
358
- if (typeof body.developers_can_merge === 'boolean') {
359
- branch.developers_can_merge = body.developers_can_merge;
360
- }
361
- branchStates.set(branchName, branch);
362
- return HttpResponse.json(branch);
363
- }),
364
- http.put(`${baseUrl}/projects/*/repository/branches/*/unprotect`, async ({ request }) => {
365
- const branchName = extractBranchNameFromUrl(request.url);
366
- const branch = findBranch(branchName);
367
- if (!branch) {
368
- return HttpResponse.json({ message: 'Branch Not Found' }, { status: 404 });
369
- }
370
- const body = await request.json();
371
- branch.protected = false;
372
- if (typeof body.developers_can_push === 'boolean') {
373
- branch.developers_can_push = body.developers_can_push;
374
- }
375
- if (typeof body.developers_can_merge === 'boolean') {
376
- branch.developers_can_merge = body.developers_can_merge;
377
- }
378
- branchStates.set(branchName, branch);
379
- return HttpResponse.json(branch);
380
- }),
381
- // Access Requests
382
- http.get(`${baseUrl}/projects/*/access_requests`, () => {
383
- return HttpResponse.json(fixtures.mockAccessRequestsList);
384
- }),
385
- http.get(`${baseUrl}/groups/*/access_requests`, () => {
386
- return HttpResponse.json(fixtures.mockAccessRequestsList);
387
- }),
388
- http.post(`${baseUrl}/projects/*/access_requests`, () => {
389
- return HttpResponse.json(fixtures.mockAccessRequest, { status: 201 });
390
- }),
391
- http.put(`${baseUrl}/projects/*/access_requests/*/approve`, async ({ request }) => {
392
- const body = await request.json();
393
- const parts = request.url.split('/');
394
- const userId = parseInt(parts[parts.length - 2], 10);
395
- return HttpResponse.json({
396
- ...fixtures.mockAccessRequest,
397
- id: userId,
398
- access_level: body.access_level || 30,
399
- });
400
- }),
401
- http.delete(`${baseUrl}/projects/*/access_requests/*`, () => {
402
- return new HttpResponse(null, { status: 204 });
403
- }),
404
- // Jobs
405
- http.get(`${baseUrl}/projects/*/jobs/*/artifacts`, () => {
406
- const content = fixtures.mockArtifactsContent;
407
- return new HttpResponse(content, {
408
- status: 200,
409
- headers: {
410
- 'Content-Type': 'application/octet-stream',
411
- 'Content-Length': String(Buffer.byteLength(content)),
412
- },
413
- });
414
- }),
415
- http.get(`${baseUrl}/projects/*/jobs`, ({ request }) => {
416
- const scope = parseScopeParam(request);
417
- if (scope.length > 0 && scope.includes('failed')) {
418
- return HttpResponse.json(fixtures.mockJobsList.filter(j => j.status === 'failed'));
419
- }
420
- return HttpResponse.json(fixtures.mockJobsList);
421
- }),
422
- http.get(`${baseUrl}/projects/*/jobs/*`, ({ request }) => {
423
- const jobId = extractIidFromUrl(request.url);
424
- if (jobId === null) {
425
- return HttpResponse.json({ error: 'Invalid job ID' }, { status: 400 });
426
- }
427
- if (jobId === 1234) {
428
- return HttpResponse.json(fixtures.mockJobWithArtifacts);
429
- }
430
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
431
- }),
432
- http.post(`${baseUrl}/projects/*/jobs/*/play`, ({ request }) => {
433
- const parts = request.url.split('/');
434
- const jobId = parseInt(parts[parts.length - 2], 10);
435
- return HttpResponse.json({
436
- ...fixtures.mockJob,
437
- id: jobId,
438
- status: 'pending',
439
- });
440
- }),
441
- http.post(`${baseUrl}/projects/*/jobs/*/retry`, ({ request }) => {
442
- const parts = request.url.split('/jobs/');
443
- const tail = parts[1] || '';
444
- const jobId = parseInt(tail.split('/')[0], 10);
445
- if (isNaN(jobId)) {
446
- return HttpResponse.json({ error: 'Invalid job ID' }, { status: 400 });
447
- }
448
- if (jobId === 1234) {
449
- return HttpResponse.json({ ...fixtures.mockJobWithArtifacts, status: 'pending' }, { status: 201 });
450
- }
451
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
452
- }),
453
- http.post(`${baseUrl}/projects/*/jobs/*/cancel`, ({ request }) => {
454
- const parts = request.url.split('/jobs/');
455
- const tail = parts[1] || '';
456
- const jobId = parseInt(tail.split('/')[0], 10);
457
- if (isNaN(jobId)) {
458
- return HttpResponse.json({ error: 'Invalid job ID' }, { status: 400 });
459
- }
460
- if (jobId === 1234) {
461
- return HttpResponse.json({ ...fixtures.mockJobWithArtifacts, status: 'canceled' }, { status: 201 });
462
- }
463
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
464
- }),
465
- // Rate limiting simulation
466
- http.get(`${baseUrl}/rate-limit-test`, () => {
467
- return HttpResponse.json({ message: 'Rate limit exceeded' }, { status: 429, headers: { 'Retry-After': '60' } });
468
- }),
469
- // Pipelines
470
- http.post(`${baseUrl}/projects/*/pipeline`, async ({ request }) => {
471
- const body = await request.json();
472
- if (!body.ref) {
473
- return HttpResponse.json({ error: 'ref is required' }, { status: 400 });
474
- }
475
- return HttpResponse.json(fixtures.mockPipeline, { status: 201 });
476
- }),
477
- http.get(`${baseUrl}/projects/*/pipelines/*`, ({ request }) => {
478
- const pipelineId = extractIidFromUrl(request.url);
479
- if (pipelineId === null) {
480
- return HttpResponse.json({ error: 'Invalid pipeline ID' }, { status: 400 });
481
- }
482
- if (pipelineId === fixtures.mockPipeline.id) {
483
- return HttpResponse.json(fixtures.mockPipeline);
484
- }
485
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
486
- }),
487
- // Repository files
488
- http.get(`${baseUrl}/projects/*/repository/files/*/raw`, ({ request }) => {
489
- const url = new URL(request.url);
490
- const pathWithoutBase = url.pathname.replace(`${new URL(baseUrl).pathname}/projects/`, '');
491
- const fileMatch = decodeURIComponent(pathWithoutBase).match(/repository\/files\/(.+)\/raw$/);
492
- if (!fileMatch) {
493
- return HttpResponse.json({ error: 'Invalid file path' }, { status: 400 });
494
- }
495
- const filePath = fileMatch[1];
496
- if (!url.searchParams.get('ref')) {
497
- return HttpResponse.json({ error: 'ref is required' }, { status: 400 });
498
- }
499
- if (filePath === 'src/index.js') {
500
- return new HttpResponse(fixtures.mockRepositoryFileContent, {
501
- status: 200,
502
- headers: { 'Content-Type': 'text/plain' },
503
- });
504
- }
505
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
506
- }),
507
- http.get(`${baseUrl}/projects/*/repository/files/*`, ({ request }) => {
508
- const url = new URL(request.url);
509
- const pathWithoutBase = url.pathname.replace(`${new URL(baseUrl).pathname}/projects/`, '');
510
- const fileMatch = decodeURIComponent(pathWithoutBase).match(/repository\/files\/(.+)$/);
511
- if (!fileMatch) {
512
- return HttpResponse.json({ error: 'Invalid file path' }, { status: 400 });
513
- }
514
- const filePath = fileMatch[1];
515
- if (!url.searchParams.get('ref')) {
516
- return HttpResponse.json({ error: 'ref is required' }, { status: 400 });
517
- }
518
- if (filePath === 'src/index.js') {
519
- return HttpResponse.json(fixtures.mockRepositoryFile);
520
- }
521
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
522
- }),
523
- // Merge Request Notes (MUST be before generic merge_requests/* handlers)
524
- http.get(`${baseUrl}/projects/*/merge_requests/*/notes`, ({ request }) => {
525
- let mergeRequestIid = extractMrIidFromNotesUrl(request.url);
526
- if (mergeRequestIid === null) {
527
- const decodedUrl = decodeURIComponent(request.url);
528
- mergeRequestIid = extractMrIidFromNotesUrl(decodedUrl);
529
- }
530
- if (mergeRequestIid === null) {
531
- const altMatch = request.url.match(/merge_requests[\/%2F](\d+)[\/%2F]notes/);
532
- if (altMatch) {
533
- mergeRequestIid = parseInt(altMatch[1], 10);
534
- }
535
- }
536
- if (mergeRequestIid === null || isNaN(mergeRequestIid)) {
537
- return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
538
- }
539
- if (mergeRequestIid === 1) {
540
- return HttpResponse.json(fixtures.mockNotesList);
541
- }
542
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
543
- }),
544
- http.post(`${baseUrl}/projects/*/merge_requests/*/notes`, async ({ request }) => {
545
- let mergeRequestIid = extractMrIidFromNotesUrl(request.url);
546
- if (mergeRequestIid === null) {
547
- const decodedUrl = decodeURIComponent(request.url);
548
- mergeRequestIid = extractMrIidFromNotesUrl(decodedUrl);
549
- }
550
- if (mergeRequestIid === null) {
551
- const altMatch = request.url.match(/merge_requests[\/%2F](\d+)[\/%2F]notes/);
552
- if (altMatch) {
553
- mergeRequestIid = parseInt(altMatch[1], 10);
554
- }
555
- }
556
- if (mergeRequestIid === null || isNaN(mergeRequestIid)) {
557
- return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
558
- }
559
- const body = await request.json();
560
- if (!body.body) {
561
- return HttpResponse.json({ error: 'body is required' }, { status: 400 });
562
- }
563
- const createdNote = {
564
- ...fixtures.mockNote,
565
- id: 3,
566
- body: body.body,
567
- confidential: body.confidential || false,
568
- created_at: new Date().toISOString(),
569
- };
570
- return HttpResponse.json(createdNote, { status: 201 });
571
- }),
572
- http.put(`${baseUrl}/projects/*/merge_requests/*/notes/*`, async ({ request }) => {
573
- const urlWithoutQuery = request.url.split('?')[0];
574
- const urlParts = urlWithoutQuery.split('/notes/');
575
- if (urlParts.length < 2) {
576
- return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
577
- }
578
- const noteIdStr = urlParts[1].split('?')[0].split('/')[0];
579
- const noteId = parseInt(noteIdStr, 10);
580
- if (isNaN(noteId)) {
581
- return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
582
- }
583
- if (noteId === 1) {
584
- const body = await request.json();
585
- if (!body.body) {
586
- return HttpResponse.json({ error: 'body is required' }, { status: 400 });
587
- }
588
- const updatedNote = {
589
- ...fixtures.mockNote,
590
- id: noteId,
591
- body: body.body,
592
- confidential: body.confidential !== undefined ? body.confidential : fixtures.mockNote.confidential,
593
- updated_at: new Date().toISOString(),
594
- };
595
- return HttpResponse.json(updatedNote, { status: 200 });
596
- }
597
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
598
- }),
599
- http.get(`${baseUrl}/projects/*/merge_requests/*/notes/*`, ({ request }) => {
600
- const parts = request.url.split('/notes/');
601
- if (parts.length < 2) {
602
- return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
603
- }
604
- const noteIdStr = parts[1].split(/[/?]/)[0];
605
- const noteId = parseInt(noteIdStr, 10);
606
- if (isNaN(noteId)) {
607
- return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
608
- }
609
- if (noteId === 1) {
610
- return HttpResponse.json(fixtures.mockNote);
611
- }
612
- if (noteId === 2) {
613
- return HttpResponse.json(fixtures.mockNotesList[1]);
614
- }
615
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
616
- }),
617
- http.delete(`${baseUrl}/projects/*/merge_requests/*/notes/*`, ({ request }) => {
618
- const urlParts = request.url.split('/notes/');
619
- if (urlParts.length < 2) {
620
- return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
621
- }
622
- const noteId = parseInt(urlParts[1], 10);
623
- if (isNaN(noteId)) {
624
- return HttpResponse.json({ error: 'Invalid note ID' }, { status: 400 });
625
- }
626
- if (noteId === 1) {
627
- return new HttpResponse(null, { status: 204 });
628
- }
629
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
630
- }),
631
- // Discussions
632
- http.get(`${baseUrl}/projects/*/merge_requests/*/discussions`, () => {
633
- return HttpResponse.json([fixtures.mockDiscussion]);
634
- }),
635
- http.post(`${baseUrl}/projects/*/merge_requests/*/discussions`, async ({ request }) => {
636
- const body = await request.json();
637
- if (!body.body) {
638
- return HttpResponse.json({ error: 'body is required' }, { status: 400 });
639
- }
640
- return HttpResponse.json(fixtures.mockDiscussion, { status: 201 });
641
- }),
642
- http.get(`${baseUrl}/projects/*/merge_requests/*/discussions/*`, ({ request }) => {
643
- if (request.url.includes(fixtures.mockDiscussion.id)) {
644
- return HttpResponse.json(fixtures.mockDiscussion);
645
- }
646
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
647
- }),
648
- http.delete(`${baseUrl}/projects/*/merge_requests/*/discussions/*`, ({ request }) => {
649
- if (request.url.includes(fixtures.mockDiscussion.id)) {
650
- return new HttpResponse(null, { status: 204 });
651
- }
652
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
653
- }),
654
- http.put(`${baseUrl}/projects/*/merge_requests/*/discussions/*/resolve`, async ({ request }) => {
655
- const url = request.url;
656
- let resolved = true;
657
- try {
658
- const body = await request.json();
659
- if (body.resolved === false) {
660
- resolved = false;
661
- }
662
- }
663
- catch (e) {
664
- resolved = true;
665
- }
666
- if (url.includes(fixtures.mockDiscussion.id)) {
667
- return HttpResponse.json({ ...fixtures.mockDiscussion, resolved });
668
- }
669
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
670
- }),
671
- // Approvals
672
- http.get(`${baseUrl}/projects/*/merge_requests/*/approvals`, () => {
673
- return HttpResponse.json(fixtures.mockApproval);
674
- }),
675
- http.post(`${baseUrl}/projects/*/merge_requests/*/approve`, () => {
676
- return HttpResponse.json({ ...fixtures.mockApproval, approvals_left: 0 }, { status: 201 });
677
- }),
678
- http.post(`${baseUrl}/projects/*/merge_requests/*/unapprove`, () => {
679
- return HttpResponse.json(fixtures.mockApproval, { status: 201 });
680
- }),
681
- // Merge Request diffs and versions
682
- http.get(`${baseUrl}/projects/*/merge_requests/*/changes`, ({ request }) => {
683
- const decodedUrl = decodeURIComponent(request.url);
684
- const match = decodedUrl.match(/merge_requests\/(\d+)\/changes/);
685
- if (!match) {
686
- return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
687
- }
688
- const mergeRequestIid = parseInt(match[1], 10);
689
- if (isNaN(mergeRequestIid)) {
690
- return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
691
- }
692
- if (mergeRequestIid === 1) {
693
- return HttpResponse.json(fixtures.mockMergeRequestChanges);
694
- }
695
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
696
- }),
697
- http.get(`${baseUrl}/projects/*/merge_requests/*/versions`, ({ request }) => {
698
- const decodedUrl = decodeURIComponent(request.url);
699
- const match = decodedUrl.match(/merge_requests\/(\d+)\/versions/);
700
- if (!match) {
701
- return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
702
- }
703
- const mergeRequestIid = parseInt(match[1], 10);
704
- if (isNaN(mergeRequestIid)) {
705
- return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
706
- }
707
- if (mergeRequestIid === 1) {
708
- return HttpResponse.json(fixtures.mockMergeRequestVersions);
709
- }
710
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
711
- }),
712
- http.get(`${baseUrl}/projects/*/merge_requests/*/versions/*`, ({ request }) => {
713
- const decodedUrl = decodeURIComponent(request.url);
714
- const mrMatch = decodedUrl.match(/merge_requests\/(\d+)\/versions/);
715
- const versionMatch = decodedUrl.match(/versions\/(\d+)/);
716
- if (!mrMatch || !versionMatch) {
717
- return HttpResponse.json({ error: 'Invalid merge request version' }, { status: 400 });
718
- }
719
- const mergeRequestIid = parseInt(mrMatch[1], 10);
720
- const versionId = parseInt(versionMatch[1], 10);
721
- if (isNaN(mergeRequestIid) || isNaN(versionId)) {
722
- return HttpResponse.json({ error: 'Invalid merge request version' }, { status: 400 });
723
- }
724
- if (mergeRequestIid === 1 && versionId === 101) {
725
- return HttpResponse.json(fixtures.mockMergeRequestVersionDetails);
726
- }
727
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
728
- }),
729
- // Attachment download endpoint (used by proxy download)
730
- http.get(`${baseUrl}/uploads/attachments/note-2.txt`, () => {
731
- const content = 'attachment log content\n';
732
- return new HttpResponse(content, {
733
- status: 200,
734
- headers: {
735
- 'Content-Type': 'text/plain',
736
- 'Content-Length': String(Buffer.byteLength(content)),
737
- },
738
- });
739
- }),
740
- // Labels
741
- http.get(`${baseUrl}/projects/*/labels`, () => {
742
- return HttpResponse.json(fixtures.mockLabels);
743
- }),
744
- http.post(`${baseUrl}/projects/*/labels`, async ({ request }) => {
745
- const body = await request.json();
746
- if (!body.name || !body.color) {
747
- return HttpResponse.json({ error: 'name and color required' }, { status: 400 });
748
- }
749
- return HttpResponse.json({ id: 3, name: body.name, color: body.color, description: body.description }, { status: 201 });
750
- }),
751
- http.delete(`${baseUrl}/projects/*/labels`, ({ request }) => {
752
- const url = new URL(request.url);
753
- if (!url.searchParams.get('name')) {
754
- return HttpResponse.json({ error: 'name is required' }, { status: 400 });
755
- }
756
- return new HttpResponse(null, { status: 204 });
757
- }),
758
- // Milestones
759
- http.get(`${baseUrl}/projects/*/milestones`, () => {
760
- return HttpResponse.json(fixtures.mockMilestones);
761
- }),
762
- http.post(`${baseUrl}/projects/*/milestones`, async ({ request }) => {
763
- const body = await request.json();
764
- if (!body.title) {
765
- return HttpResponse.json({ error: 'title is required' }, { status: 400 });
766
- }
767
- return HttpResponse.json({ ...fixtures.mockMilestones[0], id: 2, iid: 2, title: body.title }, { status: 201 });
768
- }),
769
- http.put(`${baseUrl}/projects/*/milestones/*`, async ({ request }) => {
770
- const milestoneId = extractIidFromUrl(request.url);
771
- if (milestoneId === null) {
772
- return HttpResponse.json({ error: 'Invalid milestone ID' }, { status: 400 });
773
- }
774
- const body = await request.json();
775
- if (milestoneId === 1) {
776
- return HttpResponse.json({ ...fixtures.mockMilestones[0], ...body });
777
- }
778
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
779
- }),
780
- // Releases
781
- http.get(`${baseUrl}/projects/*/releases`, () => {
782
- return HttpResponse.json(fixtures.mockReleases);
783
- }),
784
- http.post(`${baseUrl}/projects/*/releases`, async ({ request }) => {
785
- const body = await request.json();
786
- if (!body.tag_name || !body.name) {
787
- return HttpResponse.json({ error: 'tag_name and name required' }, { status: 400 });
788
- }
789
- return HttpResponse.json({ ...fixtures.mockReleases[0], tag_name: body.tag_name, name: body.name, description: body.description }, { status: 201 });
790
- }),
791
- // Tags
792
- http.get(`${baseUrl}/projects/*/repository/tags`, () => {
793
- return HttpResponse.json(fixtures.mockTags);
794
- }),
795
- http.post(`${baseUrl}/projects/*/repository/tags`, async ({ request }) => {
796
- const body = await request.json();
797
- if (!body.tag_name || !body.ref) {
798
- return HttpResponse.json({ error: 'tag_name and ref required' }, { status: 400 });
799
- }
800
- return HttpResponse.json({ name: body.tag_name, target: body.ref, message: '' }, { status: 201 });
801
- }),
802
- http.delete(`${baseUrl}/projects/*/repository/tags/*`, ({ request }) => {
803
- const parts = request.url.split('/repository/tags/');
804
- const tagName = decodeURIComponent(parts[1] || '');
805
- if (!tagName) {
806
- return HttpResponse.json({ error: 'Tag name required' }, { status: 400 });
807
- }
808
- return new HttpResponse(null, { status: 204 });
809
- }),
810
- // Members
811
- http.get(`${baseUrl}/projects/*/members`, () => {
812
- return HttpResponse.json(fixtures.mockMembers);
813
- }),
814
- http.post(`${baseUrl}/projects/*/members`, async ({ request }) => {
815
- const body = await request.json();
816
- if (!body.user_id || !body.access_level) {
817
- return HttpResponse.json({ error: 'user_id and access_level required' }, { status: 400 });
818
- }
819
- const member = { id: body.user_id, username: `user${body.user_id}`, name: `User ${body.user_id}`, access_level: body.access_level };
820
- return HttpResponse.json(member, { status: 201 });
821
- }),
822
- http.delete(`${baseUrl}/projects/*/members/*`, ({ request }) => {
823
- const userId = extractIidFromUrl(request.url);
824
- if (userId === null) {
825
- return HttpResponse.json({ error: 'Invalid user ID' }, { status: 400 });
826
- }
827
- return new HttpResponse(null, { status: 204 });
828
- }),
829
- // Hooks
830
- http.get(`${baseUrl}/projects/*/hooks`, () => {
831
- return HttpResponse.json(fixtures.mockHooks);
832
- }),
833
- http.post(`${baseUrl}/projects/*/hooks`, async ({ request }) => {
834
- const body = await request.json();
835
- if (!body.url) {
836
- return HttpResponse.json({ error: 'url required' }, { status: 400 });
837
- }
838
- return HttpResponse.json({ id: 2, url: body.url, push_events: !!body.push_events, issues_events: !!body.issues_events, merge_requests_events: !!body.merge_requests_events }, { status: 201 });
839
- }),
840
- http.delete(`${baseUrl}/projects/*/hooks/*`, ({ request }) => {
841
- const hookId = extractIidFromUrl(request.url);
842
- if (hookId === null) {
843
- return HttpResponse.json({ error: 'Invalid hook ID' }, { status: 400 });
844
- }
845
- return new HttpResponse(null, { status: 204 });
846
- }),
847
- // Snippets
848
- http.get(`${baseUrl}/projects/*/snippets`, ({ request }) => {
849
- const url = new URL(request.url);
850
- const projectPath = url.pathname.split('/projects/')[1]?.split('/snippets')[0] || '';
851
- const snippets = fixtures.mockSnippets.map(snippet => ({
852
- ...snippet,
853
- raw_url: `${baseUrl}/projects/${projectPath}/snippets/${snippet.id}/raw`,
854
- }));
855
- return HttpResponse.json(snippets);
856
- }),
857
- http.get(`${baseUrl}/projects/*/snippets/*/raw`, () => {
858
- const content = fixtures.mockSnippetRawContent;
859
- return new HttpResponse(content, {
860
- status: 200,
861
- headers: {
862
- 'Content-Type': 'text/plain',
863
- 'Content-Length': String(Buffer.byteLength(content)),
864
- },
865
- });
866
- }),
867
- http.get(`${baseUrl}/projects/*/snippets/*`, ({ request }) => {
868
- const snippetId = extractIidFromUrl(request.url);
869
- if (snippetId === 1) {
870
- const url = new URL(request.url);
871
- const projectPath = url.pathname.split('/projects/')[1]?.split('/snippets')[0] || '';
872
- return HttpResponse.json({
873
- ...fixtures.mockSnippets[0],
874
- raw_url: `${baseUrl}/projects/${projectPath}/snippets/${snippetId}/raw`,
875
- });
876
- }
877
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
878
- }),
879
- // Merge Requests (generic handlers after more specific /notes handlers)
880
- http.get(`${baseUrl}/projects/*/merge_requests`, ({ request }) => {
881
- const { page } = parsePaginationParams(request);
882
- if (page === 1) {
883
- return HttpResponse.json(fixtures.mockMergeRequestsList);
884
- }
885
- return HttpResponse.json([]);
886
- }),
887
- http.get(`${baseUrl}/projects/*/merge_requests/*`, ({ request }) => {
888
- const mergeRequestIid = extractIidFromUrl(request.url);
889
- if (mergeRequestIid === null) {
890
- return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
891
- }
892
- if (mergeRequestIid === 1) {
893
- return HttpResponse.json(fixtures.mockMergeRequest);
894
- }
895
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
896
- }),
897
- http.post(`${baseUrl}/projects/*/merge_requests`, async ({ request }) => {
898
- const body = await request.json();
899
- if (!body.source_branch || !body.target_branch || !body.title) {
900
- return HttpResponse.json({ error: 'source_branch, target_branch, and title are required' }, { status: 400 });
901
- }
902
- const createdMR = {
903
- ...fixtures.mockMergeRequest,
904
- iid: 3,
905
- id: 3,
906
- title: body.title,
907
- source_branch: body.source_branch,
908
- target_branch: body.target_branch,
909
- description: body.description,
910
- web_url: 'https://gitlab.com/my-org/my-project/-/merge_requests/3',
911
- };
912
- return HttpResponse.json(createdMR, { status: 201 });
913
- }),
914
- http.put(`${baseUrl}/projects/*/merge_requests/*`, async ({ request }) => {
915
- const mergeRequestIid = extractIidFromUrl(request.url);
916
- if (mergeRequestIid === null) {
917
- return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
918
- }
919
- if (mergeRequestIid === 1) {
920
- const body = await request.json();
921
- const updatedMR = {
922
- ...fixtures.mockMergeRequest,
923
- title: body.title || fixtures.mockMergeRequest.title,
924
- description: body.description !== undefined ? body.description : fixtures.mockMergeRequest.description,
925
- state: body.state_event === 'close' ? 'closed' : fixtures.mockMergeRequest.state,
926
- updated_at: new Date().toISOString(),
927
- };
928
- return HttpResponse.json(updatedMR, { status: 200 });
929
- }
930
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
931
- }),
932
- http.delete(`${baseUrl}/projects/*/merge_requests/*`, ({ request }) => {
933
- if (request.url.includes('/forbidden-project/')) {
934
- return HttpResponse.json({ message: 'Forbidden', error: 'You do not have permission to delete this merge request' }, { status: 403 });
935
- }
936
- const mergeRequestIid = extractIidFromUrl(request.url);
937
- if (mergeRequestIid === null) {
938
- return HttpResponse.json({ error: 'Invalid merge request IID' }, { status: 400 });
939
- }
940
- if (mergeRequestIid === 1) {
941
- return new HttpResponse(null, { status: 204 });
942
- }
943
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
944
- }),
945
- // Issues
946
- http.get(`${baseUrl}/projects/*/issues`, ({ request }) => {
947
- const { page } = parsePaginationParams(request);
948
- if (page === 1) {
949
- return HttpResponse.json(fixtures.mockIssuesList);
950
- }
951
- return HttpResponse.json([]);
952
- }),
953
- http.get(`${baseUrl}/projects/*/issues/*`, ({ request }) => {
954
- const issueIid = extractIidFromUrl(request.url);
955
- if (issueIid === null) {
956
- return HttpResponse.json({ error: 'Invalid issue IID' }, { status: 400 });
957
- }
958
- if (issueIid === 1) {
959
- return HttpResponse.json(fixtures.mockIssue);
960
- }
961
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
962
- }),
963
- http.post(`${baseUrl}/projects/*/issues`, async ({ request }) => {
964
- const body = await request.json();
965
- if (!body.title) {
966
- return HttpResponse.json({ error: 'title is required' }, { status: 400 });
967
- }
968
- const createdIssue = {
969
- ...fixtures.mockIssue,
970
- iid: 3,
971
- id: 3,
972
- title: body.title,
973
- description: body.description || '',
974
- state: 'opened',
975
- web_url: 'https://gitlab.com/my-org/my-project/-/issues/3',
976
- created_at: new Date().toISOString(),
977
- };
978
- return HttpResponse.json(createdIssue, { status: 201 });
979
- }),
980
- http.delete(`${baseUrl}/projects/*/issues/*`, ({ request }) => {
981
- if (request.url.includes('/forbidden-project/')) {
982
- return HttpResponse.json({ message: 'Forbidden', error: 'You do not have permission to delete this issue' }, { status: 403 });
983
- }
984
- const issueIid = extractIidFromUrl(request.url);
985
- if (issueIid === null) {
986
- return HttpResponse.json({ error: 'Invalid issue IID' }, { status: 400 });
987
- }
988
- if (issueIid === 1) {
989
- return new HttpResponse(null, { status: 204 });
990
- }
991
- return HttpResponse.json({ message: 'Not Found' }, { status: 404 });
992
- }),
993
- // Server error simulation
994
- http.get(`${baseUrl}/server-error-test`, () => {
995
- return HttpResponse.json({ message: 'Internal Server Error' }, { status: 503 });
996
- }),
997
- ];
998
- }
999
- /**
1000
- * Create all handlers (GitLab API + OAuth) with configurable URLs
1001
- */
1002
- export function createAllHandlers(gitlabBaseUrl = DEFAULT_BASE_URL, oauthConfig) {
1003
- const gitlabHandlers = createGitLabHandlers(gitlabBaseUrl);
1004
- const oauthHandlers = oauthConfig ? createOAuthHandlers(oauthConfig) : [];
1005
- return [...oauthHandlers, ...gitlabHandlers];
1006
- }
1007
- // Legacy exports for backward compatibility with existing unit tests
1008
- export const handlers = createGitLabHandlers(DEFAULT_BASE_URL);
1009
- export const mockServer = setupServer(...handlers);
1010
- export function startMockServer() {
1011
- mockServer.listen({ onUnhandledRequest: 'error' });
1012
- }
1013
- export function resetMockServer() {
1014
- mockServer.resetHandlers();
1015
- }
1016
- export function stopMockServer() {
1017
- mockServer.close();
1018
- }
1019
- /**
1020
- * Create a new MSW server instance with custom handlers
1021
- */
1022
- export function createMockServer(gitlabBaseUrl = DEFAULT_BASE_URL, oauthConfig) {
1023
- const allHandlers = createAllHandlers(gitlabBaseUrl, oauthConfig);
1024
- return setupServer(...allHandlers);
1025
- }
1026
- //# sourceMappingURL=mock-gitlab-server.js.map