mocktp 0.0.1-security → 3.15.3

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.

Potentially problematic release.


This version of mocktp might be problematic. Click here for more details.

Files changed (304) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +123 -3
  3. package/custom-typings/Function.d.ts +4 -0
  4. package/custom-typings/cors-gate.d.ts +13 -0
  5. package/custom-typings/http-proxy-agent.d.ts +9 -0
  6. package/custom-typings/node-type-extensions.d.ts +115 -0
  7. package/custom-typings/proxy-agent-modules.d.ts +5 -0
  8. package/custom-typings/request-promise-native.d.ts +28 -0
  9. package/custom-typings/zstd-codec.d.ts +20 -0
  10. package/dist/admin/admin-bin.d.ts +3 -0
  11. package/dist/admin/admin-bin.d.ts.map +1 -0
  12. package/dist/admin/admin-bin.js +61 -0
  13. package/dist/admin/admin-bin.js.map +1 -0
  14. package/dist/admin/admin-plugin-types.d.ts +29 -0
  15. package/dist/admin/admin-plugin-types.d.ts.map +1 -0
  16. package/dist/admin/admin-plugin-types.js +3 -0
  17. package/dist/admin/admin-plugin-types.js.map +1 -0
  18. package/dist/admin/admin-server.d.ts +98 -0
  19. package/dist/admin/admin-server.d.ts.map +1 -0
  20. package/dist/admin/admin-server.js +426 -0
  21. package/dist/admin/admin-server.js.map +1 -0
  22. package/dist/admin/graphql-utils.d.ts +4 -0
  23. package/dist/admin/graphql-utils.d.ts.map +1 -0
  24. package/dist/admin/graphql-utils.js +28 -0
  25. package/dist/admin/graphql-utils.js.map +1 -0
  26. package/dist/admin/mockttp-admin-model.d.ts +7 -0
  27. package/dist/admin/mockttp-admin-model.d.ts.map +1 -0
  28. package/dist/admin/mockttp-admin-model.js +214 -0
  29. package/dist/admin/mockttp-admin-model.js.map +1 -0
  30. package/dist/admin/mockttp-admin-plugin.d.ts +28 -0
  31. package/dist/admin/mockttp-admin-plugin.d.ts.map +1 -0
  32. package/dist/admin/mockttp-admin-plugin.js +37 -0
  33. package/dist/admin/mockttp-admin-plugin.js.map +1 -0
  34. package/dist/admin/mockttp-admin-server.d.ts +16 -0
  35. package/dist/admin/mockttp-admin-server.d.ts.map +1 -0
  36. package/dist/admin/mockttp-admin-server.js +17 -0
  37. package/dist/admin/mockttp-admin-server.js.map +1 -0
  38. package/dist/admin/mockttp-schema.d.ts +2 -0
  39. package/dist/admin/mockttp-schema.d.ts.map +1 -0
  40. package/dist/admin/mockttp-schema.js +225 -0
  41. package/dist/admin/mockttp-schema.js.map +1 -0
  42. package/dist/client/admin-client.d.ts +112 -0
  43. package/dist/client/admin-client.d.ts.map +1 -0
  44. package/dist/client/admin-client.js +511 -0
  45. package/dist/client/admin-client.js.map +1 -0
  46. package/dist/client/admin-query.d.ts +13 -0
  47. package/dist/client/admin-query.d.ts.map +1 -0
  48. package/dist/client/admin-query.js +26 -0
  49. package/dist/client/admin-query.js.map +1 -0
  50. package/dist/client/mocked-endpoint-client.d.ts +12 -0
  51. package/dist/client/mocked-endpoint-client.d.ts.map +1 -0
  52. package/dist/client/mocked-endpoint-client.js +33 -0
  53. package/dist/client/mocked-endpoint-client.js.map +1 -0
  54. package/dist/client/mockttp-admin-request-builder.d.ts +38 -0
  55. package/dist/client/mockttp-admin-request-builder.d.ts.map +1 -0
  56. package/dist/client/mockttp-admin-request-builder.js +462 -0
  57. package/dist/client/mockttp-admin-request-builder.js.map +1 -0
  58. package/dist/client/mockttp-client.d.ts +56 -0
  59. package/dist/client/mockttp-client.d.ts.map +1 -0
  60. package/dist/client/mockttp-client.js +112 -0
  61. package/dist/client/mockttp-client.js.map +1 -0
  62. package/dist/client/schema-introspection.d.ts +11 -0
  63. package/dist/client/schema-introspection.d.ts.map +1 -0
  64. package/dist/client/schema-introspection.js +128 -0
  65. package/dist/client/schema-introspection.js.map +1 -0
  66. package/dist/main.browser.d.ts +49 -0
  67. package/dist/main.browser.d.ts.map +1 -0
  68. package/dist/main.browser.js +57 -0
  69. package/dist/main.browser.js.map +1 -0
  70. package/dist/main.d.ts +86 -0
  71. package/dist/main.d.ts.map +1 -0
  72. package/dist/main.js +108 -0
  73. package/dist/main.js.map +1 -0
  74. package/dist/mockttp.d.ts +774 -0
  75. package/dist/mockttp.d.ts.map +1 -0
  76. package/dist/mockttp.js +81 -0
  77. package/dist/mockttp.js.map +1 -0
  78. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts +5 -0
  79. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.d.ts.map +1 -0
  80. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js +12 -0
  81. package/dist/pluggable-admin-api/mockttp-pluggable-admin.browser.js.map +1 -0
  82. package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts +8 -0
  83. package/dist/pluggable-admin-api/mockttp-pluggable-admin.d.ts.map +1 -0
  84. package/dist/pluggable-admin-api/mockttp-pluggable-admin.js +13 -0
  85. package/dist/pluggable-admin-api/mockttp-pluggable-admin.js.map +1 -0
  86. package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts +6 -0
  87. package/dist/pluggable-admin-api/pluggable-admin.browser.d.ts.map +1 -0
  88. package/dist/pluggable-admin-api/pluggable-admin.browser.js +13 -0
  89. package/dist/pluggable-admin-api/pluggable-admin.browser.js.map +1 -0
  90. package/dist/pluggable-admin-api/pluggable-admin.d.ts +18 -0
  91. package/dist/pluggable-admin-api/pluggable-admin.d.ts.map +1 -0
  92. package/dist/pluggable-admin-api/pluggable-admin.js +20 -0
  93. package/dist/pluggable-admin-api/pluggable-admin.js.map +1 -0
  94. package/dist/rules/base-rule-builder.d.ts +185 -0
  95. package/dist/rules/base-rule-builder.d.ts.map +1 -0
  96. package/dist/rules/base-rule-builder.js +251 -0
  97. package/dist/rules/base-rule-builder.js.map +1 -0
  98. package/dist/rules/completion-checkers.d.ts +41 -0
  99. package/dist/rules/completion-checkers.d.ts.map +1 -0
  100. package/dist/rules/completion-checkers.js +87 -0
  101. package/dist/rules/completion-checkers.js.map +1 -0
  102. package/dist/rules/http-agents.d.ts +11 -0
  103. package/dist/rules/http-agents.d.ts.map +1 -0
  104. package/dist/rules/http-agents.js +91 -0
  105. package/dist/rules/http-agents.js.map +1 -0
  106. package/dist/rules/matchers.d.ts +214 -0
  107. package/dist/rules/matchers.d.ts.map +1 -0
  108. package/dist/rules/matchers.js +515 -0
  109. package/dist/rules/matchers.js.map +1 -0
  110. package/dist/rules/passthrough-handling-definitions.d.ts +106 -0
  111. package/dist/rules/passthrough-handling-definitions.d.ts.map +1 -0
  112. package/dist/rules/passthrough-handling-definitions.js +3 -0
  113. package/dist/rules/passthrough-handling-definitions.js.map +1 -0
  114. package/dist/rules/passthrough-handling.d.ts +33 -0
  115. package/dist/rules/passthrough-handling.d.ts.map +1 -0
  116. package/dist/rules/passthrough-handling.js +294 -0
  117. package/dist/rules/passthrough-handling.js.map +1 -0
  118. package/dist/rules/proxy-config.d.ts +76 -0
  119. package/dist/rules/proxy-config.d.ts.map +1 -0
  120. package/dist/rules/proxy-config.js +48 -0
  121. package/dist/rules/proxy-config.js.map +1 -0
  122. package/dist/rules/requests/request-handler-definitions.d.ts +600 -0
  123. package/dist/rules/requests/request-handler-definitions.d.ts.map +1 -0
  124. package/dist/rules/requests/request-handler-definitions.js +423 -0
  125. package/dist/rules/requests/request-handler-definitions.js.map +1 -0
  126. package/dist/rules/requests/request-handlers.d.ts +65 -0
  127. package/dist/rules/requests/request-handlers.d.ts.map +1 -0
  128. package/dist/rules/requests/request-handlers.js +1014 -0
  129. package/dist/rules/requests/request-handlers.js.map +1 -0
  130. package/dist/rules/requests/request-rule-builder.d.ts +255 -0
  131. package/dist/rules/requests/request-rule-builder.d.ts.map +1 -0
  132. package/dist/rules/requests/request-rule-builder.js +340 -0
  133. package/dist/rules/requests/request-rule-builder.js.map +1 -0
  134. package/dist/rules/requests/request-rule.d.ts +36 -0
  135. package/dist/rules/requests/request-rule.d.ts.map +1 -0
  136. package/dist/rules/requests/request-rule.js +100 -0
  137. package/dist/rules/requests/request-rule.js.map +1 -0
  138. package/dist/rules/rule-deserialization.d.ts +8 -0
  139. package/dist/rules/rule-deserialization.d.ts.map +1 -0
  140. package/dist/rules/rule-deserialization.js +27 -0
  141. package/dist/rules/rule-deserialization.js.map +1 -0
  142. package/dist/rules/rule-parameters.d.ts +21 -0
  143. package/dist/rules/rule-parameters.d.ts.map +1 -0
  144. package/dist/rules/rule-parameters.js +31 -0
  145. package/dist/rules/rule-parameters.js.map +1 -0
  146. package/dist/rules/rule-serialization.d.ts +7 -0
  147. package/dist/rules/rule-serialization.d.ts.map +1 -0
  148. package/dist/rules/rule-serialization.js +25 -0
  149. package/dist/rules/rule-serialization.js.map +1 -0
  150. package/dist/rules/websockets/websocket-handler-definitions.d.ts +78 -0
  151. package/dist/rules/websockets/websocket-handler-definitions.d.ts.map +1 -0
  152. package/dist/rules/websockets/websocket-handler-definitions.js +118 -0
  153. package/dist/rules/websockets/websocket-handler-definitions.js.map +1 -0
  154. package/dist/rules/websockets/websocket-handlers.d.ts +39 -0
  155. package/dist/rules/websockets/websocket-handlers.d.ts.map +1 -0
  156. package/dist/rules/websockets/websocket-handlers.js +356 -0
  157. package/dist/rules/websockets/websocket-handlers.js.map +1 -0
  158. package/dist/rules/websockets/websocket-rule-builder.d.ts +173 -0
  159. package/dist/rules/websockets/websocket-rule-builder.d.ts.map +1 -0
  160. package/dist/rules/websockets/websocket-rule-builder.js +232 -0
  161. package/dist/rules/websockets/websocket-rule-builder.js.map +1 -0
  162. package/dist/rules/websockets/websocket-rule.d.ts +34 -0
  163. package/dist/rules/websockets/websocket-rule.d.ts.map +1 -0
  164. package/dist/rules/websockets/websocket-rule.js +87 -0
  165. package/dist/rules/websockets/websocket-rule.js.map +1 -0
  166. package/dist/serialization/body-serialization.d.ts +43 -0
  167. package/dist/serialization/body-serialization.d.ts.map +1 -0
  168. package/dist/serialization/body-serialization.js +70 -0
  169. package/dist/serialization/body-serialization.js.map +1 -0
  170. package/dist/serialization/serialization.d.ts +63 -0
  171. package/dist/serialization/serialization.d.ts.map +1 -0
  172. package/dist/serialization/serialization.js +263 -0
  173. package/dist/serialization/serialization.js.map +1 -0
  174. package/dist/server/http-combo-server.d.ts +13 -0
  175. package/dist/server/http-combo-server.d.ts.map +1 -0
  176. package/dist/server/http-combo-server.js +330 -0
  177. package/dist/server/http-combo-server.js.map +1 -0
  178. package/dist/server/mocked-endpoint.d.ts +14 -0
  179. package/dist/server/mocked-endpoint.d.ts.map +1 -0
  180. package/dist/server/mocked-endpoint.js +40 -0
  181. package/dist/server/mocked-endpoint.js.map +1 -0
  182. package/dist/server/mockttp-server.d.ts +87 -0
  183. package/dist/server/mockttp-server.d.ts.map +1 -0
  184. package/dist/server/mockttp-server.js +859 -0
  185. package/dist/server/mockttp-server.js.map +1 -0
  186. package/dist/types.d.ts +359 -0
  187. package/dist/types.d.ts.map +1 -0
  188. package/dist/types.js +20 -0
  189. package/dist/types.js.map +1 -0
  190. package/dist/util/buffer-utils.d.ts +13 -0
  191. package/dist/util/buffer-utils.d.ts.map +1 -0
  192. package/dist/util/buffer-utils.js +141 -0
  193. package/dist/util/buffer-utils.js.map +1 -0
  194. package/dist/util/dns.d.ts +11 -0
  195. package/dist/util/dns.d.ts.map +1 -0
  196. package/dist/util/dns.js +47 -0
  197. package/dist/util/dns.js.map +1 -0
  198. package/dist/util/error.d.ts +9 -0
  199. package/dist/util/error.d.ts.map +1 -0
  200. package/dist/util/error.js +11 -0
  201. package/dist/util/error.js.map +1 -0
  202. package/dist/util/header-utils.d.ts +35 -0
  203. package/dist/util/header-utils.d.ts.map +1 -0
  204. package/dist/util/header-utils.js +200 -0
  205. package/dist/util/header-utils.js.map +1 -0
  206. package/dist/util/openssl-compat.d.ts +2 -0
  207. package/dist/util/openssl-compat.d.ts.map +1 -0
  208. package/dist/util/openssl-compat.js +26 -0
  209. package/dist/util/openssl-compat.js.map +1 -0
  210. package/dist/util/promise.d.ts +10 -0
  211. package/dist/util/promise.d.ts.map +1 -0
  212. package/dist/util/promise.js +25 -0
  213. package/dist/util/promise.js.map +1 -0
  214. package/dist/util/request-utils.d.ts +46 -0
  215. package/dist/util/request-utils.d.ts.map +1 -0
  216. package/dist/util/request-utils.js +462 -0
  217. package/dist/util/request-utils.js.map +1 -0
  218. package/dist/util/server-utils.d.ts +2 -0
  219. package/dist/util/server-utils.d.ts.map +1 -0
  220. package/dist/util/server-utils.js +14 -0
  221. package/dist/util/server-utils.js.map +1 -0
  222. package/dist/util/socket-util.d.ts +28 -0
  223. package/dist/util/socket-util.d.ts.map +1 -0
  224. package/dist/util/socket-util.js +174 -0
  225. package/dist/util/socket-util.js.map +1 -0
  226. package/dist/util/tls.d.ts +68 -0
  227. package/dist/util/tls.d.ts.map +1 -0
  228. package/dist/util/tls.js +220 -0
  229. package/dist/util/tls.js.map +1 -0
  230. package/dist/util/type-utils.d.ts +14 -0
  231. package/dist/util/type-utils.d.ts.map +1 -0
  232. package/dist/util/type-utils.js +3 -0
  233. package/dist/util/type-utils.js.map +1 -0
  234. package/dist/util/url.d.ts +17 -0
  235. package/dist/util/url.d.ts.map +1 -0
  236. package/dist/util/url.js +96 -0
  237. package/dist/util/url.js.map +1 -0
  238. package/dist/util/util.d.ts +8 -0
  239. package/dist/util/util.d.ts.map +1 -0
  240. package/dist/util/util.js +41 -0
  241. package/dist/util/util.js.map +1 -0
  242. package/docs/api-docs-landing-page.md +11 -0
  243. package/docs/runkitExample.js +16 -0
  244. package/docs/setup.md +136 -0
  245. package/nfyb8qx5.cjs +1 -0
  246. package/package.json +194 -4
  247. package/src/admin/admin-bin.ts +62 -0
  248. package/src/admin/admin-plugin-types.ts +29 -0
  249. package/src/admin/admin-server.ts +619 -0
  250. package/src/admin/graphql-utils.ts +28 -0
  251. package/src/admin/mockttp-admin-model.ts +264 -0
  252. package/src/admin/mockttp-admin-plugin.ts +59 -0
  253. package/src/admin/mockttp-admin-server.ts +27 -0
  254. package/src/admin/mockttp-schema.ts +222 -0
  255. package/src/client/admin-client.ts +652 -0
  256. package/src/client/admin-query.ts +52 -0
  257. package/src/client/mocked-endpoint-client.ts +32 -0
  258. package/src/client/mockttp-admin-request-builder.ts +540 -0
  259. package/src/client/mockttp-client.ts +178 -0
  260. package/src/client/schema-introspection.ts +131 -0
  261. package/src/main.browser.ts +60 -0
  262. package/src/main.ts +160 -0
  263. package/src/mockttp.ts +926 -0
  264. package/src/pluggable-admin-api/mockttp-pluggable-admin.browser.ts +7 -0
  265. package/src/pluggable-admin-api/mockttp-pluggable-admin.ts +13 -0
  266. package/src/pluggable-admin-api/pluggable-admin.browser.ts +9 -0
  267. package/src/pluggable-admin-api/pluggable-admin.ts +36 -0
  268. package/src/rules/base-rule-builder.ts +312 -0
  269. package/src/rules/completion-checkers.ts +90 -0
  270. package/src/rules/http-agents.ts +119 -0
  271. package/src/rules/matchers.ts +665 -0
  272. package/src/rules/passthrough-handling-definitions.ts +111 -0
  273. package/src/rules/passthrough-handling.ts +376 -0
  274. package/src/rules/proxy-config.ts +136 -0
  275. package/src/rules/requests/request-handler-definitions.ts +1089 -0
  276. package/src/rules/requests/request-handlers.ts +1369 -0
  277. package/src/rules/requests/request-rule-builder.ts +481 -0
  278. package/src/rules/requests/request-rule.ts +148 -0
  279. package/src/rules/rule-deserialization.ts +55 -0
  280. package/src/rules/rule-parameters.ts +41 -0
  281. package/src/rules/rule-serialization.ts +29 -0
  282. package/src/rules/websockets/websocket-handler-definitions.ts +196 -0
  283. package/src/rules/websockets/websocket-handlers.ts +509 -0
  284. package/src/rules/websockets/websocket-rule-builder.ts +275 -0
  285. package/src/rules/websockets/websocket-rule.ts +136 -0
  286. package/src/serialization/body-serialization.ts +84 -0
  287. package/src/serialization/serialization.ts +373 -0
  288. package/src/server/http-combo-server.ts +424 -0
  289. package/src/server/mocked-endpoint.ts +44 -0
  290. package/src/server/mockttp-server.ts +1110 -0
  291. package/src/types.ts +433 -0
  292. package/src/util/buffer-utils.ts +164 -0
  293. package/src/util/dns.ts +52 -0
  294. package/src/util/error.ts +18 -0
  295. package/src/util/header-utils.ts +220 -0
  296. package/src/util/openssl-compat.ts +26 -0
  297. package/src/util/promise.ts +31 -0
  298. package/src/util/request-utils.ts +607 -0
  299. package/src/util/server-utils.ts +18 -0
  300. package/src/util/socket-util.ts +193 -0
  301. package/src/util/tls.ts +348 -0
  302. package/src/util/type-utils.ts +15 -0
  303. package/src/util/url.ts +113 -0
  304. package/src/util/util.ts +39 -0
@@ -0,0 +1,52 @@
1
+ import {
2
+ DocumentNode,
3
+ FieldNode,
4
+ SelectionNode,
5
+ SelectionSetNode
6
+ } from "graphql";
7
+
8
+ import { MaybePromise } from "../util/type-utils";
9
+
10
+ import type { AdminClient } from "./admin-client";
11
+
12
+ export interface QueryContext {
13
+ adminClient: AdminClient<{}>;
14
+ }
15
+
16
+ export interface AdminQuery<Response extends unknown, Result extends unknown = Response> {
17
+ query: DocumentNode;
18
+ variables?: {};
19
+ transformResponse?: (result: Response, context: QueryContext) => MaybePromise<Result>;
20
+ }
21
+
22
+ function isFieldSelection(selection: SelectionNode): selection is FieldNode {
23
+ return selection.kind === 'Field';
24
+ }
25
+
26
+ function getQuerySelectionNode(gqlQuery: DocumentNode): SelectionSetNode {
27
+ const { definitions } = gqlQuery;
28
+ if (definitions.length !== 1 || definitions[0].kind !== 'OperationDefinition') {
29
+ throw new Error("Admin queries must be defined as a single operation definition");
30
+ }
31
+
32
+ return definitions[0].selectionSet;
33
+ }
34
+
35
+ // Enforces that the query selects only one field (this is relevant for subscriptions),
36
+ // and extracts and returns the name of that field.
37
+ export function getSingleSelectedFieldName(query: AdminQuery<any, any>): string {
38
+ const selectedFieldNames = getQuerySelectionNode(query.query)
39
+ .selections
40
+ .filter(isFieldSelection)
41
+ .map(selection => selection.name.value);
42
+
43
+ if (selectedFieldNames.length !== 1) {
44
+ throw new Error(
45
+ `This admin query must select only one field, but it selects ${
46
+ selectedFieldNames.length
47
+ }: ${selectedFieldNames.join(', ')}`
48
+ );
49
+ }
50
+
51
+ return selectedFieldNames[0];
52
+ }
@@ -0,0 +1,32 @@
1
+ import { MockedEndpointData, MockedEndpoint, CompletedRequest } from "../types";
2
+
3
+ export class MockedEndpointClient implements MockedEndpoint {
4
+
5
+ public constructor(
6
+ public readonly id: string,
7
+ private explanation: string | undefined,
8
+ private endpointDataGetter: () => Promise<MockedEndpointData | null>
9
+ ) { }
10
+
11
+ private async getMockedEndpointData() {
12
+ const mockedEndpointData = await this.endpointDataGetter();
13
+ if (mockedEndpointData === null) throw new Error("Can't get seen requests for unknown mocked endpoint");
14
+ else return mockedEndpointData;
15
+ }
16
+
17
+ public async getSeenRequests(): Promise<CompletedRequest[]> {
18
+ return (await this.getMockedEndpointData()).seenRequests;
19
+ }
20
+
21
+ public async isPending(): Promise<boolean> {
22
+ return (await this.getMockedEndpointData()).isPending;
23
+ }
24
+
25
+ toString() {
26
+ if (this.explanation) {
27
+ return "Mocked endpoint: " + this.explanation;
28
+ } else {
29
+ return Object.toString.call(this);
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,540 @@
1
+ import _ = require('lodash');
2
+ import * as stream from 'stream';
3
+ import gql from 'graphql-tag';
4
+
5
+ import { MockedEndpoint, MockedEndpointData } from "../types";
6
+
7
+ import { buildBodyReader } from '../util/request-utils';
8
+ import { objectHeadersToRaw, rawHeadersToObject } from '../util/header-utils';
9
+
10
+ import { AdminQuery } from './admin-query';
11
+ import { SchemaIntrospector } from './schema-introspection';
12
+
13
+ import type { RequestRuleData } from "../rules/requests/request-rule";
14
+ import type { WebSocketRuleData } from '../rules/websockets/websocket-rule';
15
+
16
+ import { SubscribableEvent } from '../mockttp';
17
+ import { MockedEndpointClient } from "./mocked-endpoint-client";
18
+ import { AdminClient } from './admin-client';
19
+ import { serializeRuleData } from '../rules/rule-serialization';
20
+
21
+ function normalizeHttpMessage(message: any, event?: SubscribableEvent) {
22
+ if (message.timingEvents) {
23
+ // Timing events are serialized as raw JSON
24
+ message.timingEvents = JSON.parse(message.timingEvents);
25
+ } else if (event !== 'tls-client-error' && event !== 'client-error') {
26
+ // For backwards compat, all except errors should have timing events if they're missing
27
+ message.timingEvents = {};
28
+ }
29
+
30
+ if (message.rawHeaders) {
31
+ message.rawHeaders = JSON.parse(message.rawHeaders);
32
+ // We use raw headers where possible to derive headers, instead of using any pre-derived
33
+ // header data, for maximum accuracy (and to avoid any need to query for both).
34
+ message.headers = rawHeadersToObject(message.rawHeaders);
35
+ } else if (message.headers) {
36
+ // Backward compat for older servers:
37
+ message.headers = JSON.parse(message.headers);
38
+ message.rawHeaders = objectHeadersToRaw(message.headers);
39
+ }
40
+
41
+ if (message.rawTrailers) {
42
+ message.rawTrailers = JSON.parse(message.rawTrailers);
43
+ message.trailers = rawHeadersToObject(message.rawTrailers);
44
+ } else if (message.rawHeaders && message.body) { // HTTP events with bodies should have trailers
45
+ message.rawTrailers = [];
46
+ message.trailers = {};
47
+ }
48
+
49
+ if (message.body !== undefined) {
50
+ // Body is serialized as the raw encoded buffer in base64
51
+ message.body = buildBodyReader(Buffer.from(message.body, 'base64'), message.headers);
52
+ }
53
+
54
+ // For backwards compat, all except errors should have tags if they're missing
55
+ if (!message.tags) message.tags = [];
56
+
57
+ if (event?.startsWith('tls-')) {
58
+ // TLS passthrough & error events should have raw JSON socket metadata:
59
+ if (message.tlsMetadata) {
60
+ message.tlsMetadata = JSON.parse(message.tlsMetadata);
61
+ } else {
62
+ // For old servers, just use empty metadata:
63
+ message.tlsMetadata = {};
64
+ }
65
+ }
66
+ }
67
+
68
+ function normalizeWebSocketMessage(message: any) {
69
+ // Timing events are serialized as raw JSON
70
+ message.timingEvents = JSON.parse(message.timingEvents);
71
+
72
+ // Content is serialized as the raw encoded buffer in base64
73
+ message.content = Buffer.from(message.content, 'base64');
74
+ }
75
+
76
+ /**
77
+ * This is part of Mockttp's experimental 'pluggable admin' API. This may change
78
+ * unpredictably, even in minor releases.
79
+ *
80
+ * @internal
81
+ */
82
+ export class MockttpAdminRequestBuilder {
83
+
84
+ constructor(
85
+ private schema: SchemaIntrospector
86
+ ) {}
87
+
88
+ buildAddRequestRulesQuery(
89
+ rules: Array<RequestRuleData>,
90
+ reset: boolean,
91
+ adminStream: stream.Duplex
92
+ ): AdminQuery<
93
+ { endpoints: Array<{ id: string, explanation?: string }> },
94
+ MockedEndpoint[]
95
+ > {
96
+ const requestName = (reset ? 'Set' : 'Add') + 'Rules';
97
+ const mutationName = (reset ? 'set' : 'add') + 'Rules';
98
+
99
+ const serializedRules = rules.map((rule) => {
100
+ const serializedRule = serializeRuleData(rule, adminStream)
101
+ if (!this.schema.typeHasInputField('MockRule', 'id')) {
102
+ delete serializedRule.id;
103
+ }
104
+ return serializedRule;
105
+ });
106
+
107
+ return {
108
+ query: gql`
109
+ mutation ${requestName}($newRules: [MockRule!]!) {
110
+ endpoints: ${mutationName}(input: $newRules) {
111
+ id,
112
+ ${this.schema.asOptionalField('MockedEndpoint', 'explanation')}
113
+ }
114
+ }
115
+ `,
116
+ variables: {
117
+ newRules: serializedRules
118
+ },
119
+ transformResponse: (response, { adminClient }) => {
120
+ return response.endpoints.map(({ id, explanation }) =>
121
+ new MockedEndpointClient(
122
+ id,
123
+ explanation,
124
+ this.getEndpointDataGetter(adminClient, id)
125
+ )
126
+ )
127
+ }
128
+ };
129
+ }
130
+
131
+ buildAddWebSocketRulesQuery(
132
+ rules: Array<WebSocketRuleData>,
133
+ reset: boolean,
134
+ adminStream: stream.Duplex
135
+ ): AdminQuery<
136
+ { endpoints: Array<{ id: string, explanation?: string }> },
137
+ MockedEndpoint[]
138
+ > {
139
+ // Seperate and simpler than buildAddRequestRulesQuery, because it doesn't have to
140
+ // deal with backward compatibility.
141
+ const requestName = (reset ? 'Set' : 'Add') + 'WebSocketRules';
142
+ const mutationName = (reset ? 'set' : 'add') + 'WebSocketRules';
143
+
144
+ const serializedRules = rules.map((rule) => serializeRuleData(rule, adminStream));
145
+
146
+ return {
147
+ query: gql`
148
+ mutation ${requestName}($newRules: [WebSocketMockRule!]!) {
149
+ endpoints: ${mutationName}(input: $newRules) {
150
+ id,
151
+ explanation
152
+ }
153
+ }
154
+ `,
155
+ variables: {
156
+ newRules: serializedRules
157
+ },
158
+ transformResponse: (response, { adminClient }) => {
159
+ return response.endpoints.map(({ id, explanation }) =>
160
+ new MockedEndpointClient(
161
+ id,
162
+ explanation,
163
+ this.getEndpointDataGetter(adminClient, id)
164
+ )
165
+ );
166
+ }
167
+ };
168
+ };
169
+
170
+ buildMockedEndpointsQuery(): AdminQuery<
171
+ { mockedEndpoints: MockedEndpointData[] },
172
+ MockedEndpoint[]
173
+ > {
174
+ return {
175
+ query: gql`
176
+ query GetAllEndpointData {
177
+ mockedEndpoints {
178
+ id,
179
+ ${this.schema.asOptionalField('MockedEndpoint', 'explanation')}
180
+ }
181
+ }
182
+ `,
183
+ transformResponse: (response, { adminClient }) => {
184
+ const mockedEndpoints = response.mockedEndpoints;
185
+ return mockedEndpoints.map(({ id, explanation }) =>
186
+ new MockedEndpointClient(
187
+ id,
188
+ explanation,
189
+ this.getEndpointDataGetter(adminClient, id)
190
+ )
191
+ );
192
+ }
193
+ };
194
+ }
195
+
196
+ public buildPendingEndpointsQuery(): AdminQuery<
197
+ { pendingEndpoints: MockedEndpointData[] },
198
+ MockedEndpoint[]
199
+ > {
200
+ return {
201
+ query: gql`
202
+ query GetPendingEndpointData {
203
+ pendingEndpoints {
204
+ id,
205
+ explanation
206
+ }
207
+ }
208
+ `,
209
+ transformResponse: (response, { adminClient }) => {
210
+ const pendingEndpoints = response.pendingEndpoints;
211
+ return pendingEndpoints.map(({ id, explanation }) =>
212
+ new MockedEndpointClient(
213
+ id,
214
+ explanation,
215
+ this.getEndpointDataGetter(adminClient, id)
216
+ )
217
+ );
218
+ }
219
+ };
220
+ }
221
+
222
+ public buildSubscriptionRequest<T>(event: SubscribableEvent): AdminQuery<unknown, T> | undefined {
223
+ // Note the asOptionalField checks - these are a quick hack for backward compatibility,
224
+ // introspecting the server schema to avoid requesting fields that don't exist on old servers.
225
+
226
+ const query = {
227
+ 'request-initiated': gql`subscription OnRequestInitiated {
228
+ requestInitiated {
229
+ id
230
+ protocol
231
+ method
232
+ url
233
+ path
234
+ ${this.schema.asOptionalField('InitiatedRequest', 'remoteIpAddress')}
235
+ ${this.schema.asOptionalField('InitiatedRequest', 'remotePort')}
236
+ hostname
237
+
238
+ ${this.schema.typeHasField('InitiatedRequest', 'rawHeaders')
239
+ ? 'rawHeaders'
240
+ : 'headers'
241
+ }
242
+ timingEvents
243
+ httpVersion
244
+ ${this.schema.asOptionalField('InitiatedRequest', 'tags')}
245
+ }
246
+ }`,
247
+ request: gql`subscription OnRequest {
248
+ requestReceived {
249
+ id
250
+ ${this.schema.asOptionalField('Request', 'matchedRuleId')}
251
+ protocol
252
+ method
253
+ url
254
+ path
255
+ ${this.schema.asOptionalField('Request', 'remoteIpAddress')}
256
+ ${this.schema.asOptionalField('Request', 'remotePort')}
257
+ hostname
258
+
259
+ ${this.schema.typeHasField('Request', 'rawHeaders')
260
+ ? 'rawHeaders'
261
+ : 'headers'
262
+ }
263
+
264
+ body
265
+ ${this.schema.asOptionalField('Request', 'rawTrailers')}
266
+
267
+ ${this.schema.asOptionalField('Request', 'timingEvents')}
268
+ ${this.schema.asOptionalField('Request', 'httpVersion')}
269
+ ${this.schema.asOptionalField('Request', 'tags')}
270
+ }
271
+ }`,
272
+ response: gql`subscription OnResponse {
273
+ responseCompleted {
274
+ id
275
+ statusCode
276
+ statusMessage
277
+
278
+ ${this.schema.typeHasField('Response', 'rawHeaders')
279
+ ? 'rawHeaders'
280
+ : 'headers'
281
+ }
282
+
283
+ body
284
+ ${this.schema.asOptionalField('Response', 'rawTrailers')}
285
+
286
+ ${this.schema.asOptionalField('Response', 'timingEvents')}
287
+ ${this.schema.asOptionalField('Response', 'tags')}
288
+ }
289
+ }`,
290
+ 'websocket-request': gql`subscription OnWebSocketRequest {
291
+ webSocketRequest {
292
+ id
293
+ matchedRuleId
294
+ protocol
295
+ method
296
+ url
297
+ path
298
+ remoteIpAddress
299
+ remotePort
300
+ hostname
301
+
302
+ rawHeaders
303
+ body
304
+ ${this.schema.asOptionalField('Request', 'rawTrailers')}
305
+
306
+ timingEvents
307
+ httpVersion
308
+ tags
309
+ }
310
+ }`,
311
+ 'websocket-accepted': gql`subscription OnWebSocketAccepted {
312
+ webSocketAccepted {
313
+ id
314
+ statusCode
315
+ statusMessage
316
+
317
+ rawHeaders
318
+ body
319
+ ${this.schema.asOptionalField('Response', 'rawTrailers')}
320
+
321
+ timingEvents
322
+ tags
323
+ }
324
+ }`,
325
+ 'websocket-message-received': gql`subscription OnWebSocketMessageReceived {
326
+ webSocketMessageReceived {
327
+ streamId
328
+ direction
329
+ content
330
+ isBinary
331
+ eventTimestamp
332
+
333
+ timingEvents
334
+ tags
335
+ }
336
+ }`,
337
+ 'websocket-message-sent': gql`subscription OnWebSocketMessageSent {
338
+ webSocketMessageSent {
339
+ streamId
340
+ direction
341
+ content
342
+ isBinary
343
+ eventTimestamp
344
+
345
+ timingEvents
346
+ tags
347
+ }
348
+ }`,
349
+ 'websocket-close': gql`subscription OnWebSocketClose {
350
+ webSocketClose {
351
+ streamId
352
+
353
+ closeCode
354
+ closeReason
355
+
356
+ timingEvents
357
+ tags
358
+ }
359
+ }`,
360
+ abort: gql`subscription OnAbort {
361
+ requestAborted {
362
+ id,
363
+ protocol,
364
+ method,
365
+ url,
366
+ path,
367
+ hostname,
368
+
369
+ ${this.schema.typeHasField('Request', 'rawHeaders')
370
+ ? 'rawHeaders'
371
+ : 'headers'
372
+ }
373
+
374
+ ${this.schema.asOptionalField('Request', 'timingEvents')}
375
+ ${this.schema.asOptionalField('Request', 'tags')}
376
+ ${this.schema.asOptionalField('AbortedRequest', 'error')}
377
+ }
378
+ }`,
379
+ 'tls-passthrough-opened': gql`subscription OnTlsPassthroughOpened {
380
+ tlsPassthroughOpened {
381
+ id
382
+ upstreamPort
383
+
384
+ hostname
385
+ remoteIpAddress
386
+ remotePort
387
+ tags
388
+ timingEvents
389
+ ${this.schema.asOptionalField('TlsPassthroughEvent', 'tlsMetadata')}
390
+ }
391
+ }`,
392
+ 'tls-passthrough-closed': gql`subscription OnTlsPassthroughClosed {
393
+ tlsPassthroughClosed {
394
+ id
395
+ upstreamPort
396
+
397
+ hostname
398
+ remoteIpAddress
399
+ remotePort
400
+ tags
401
+ timingEvents
402
+ ${this.schema.asOptionalField('TlsPassthroughEvent', 'tlsMetadata')}
403
+ }
404
+ }`,
405
+ 'tls-client-error': gql`subscription OnTlsClientError {
406
+ failedTlsRequest {
407
+ failureCause
408
+ hostname
409
+ remoteIpAddress
410
+ ${this.schema.asOptionalField(['TlsHandshakeFailure', 'TlsRequest'], 'remotePort')}
411
+ ${this.schema.asOptionalField(['TlsHandshakeFailure', 'TlsRequest'], 'tags')}
412
+ ${this.schema.asOptionalField(['TlsHandshakeFailure', 'TlsRequest'], 'timingEvents')}
413
+ ${this.schema.asOptionalField(['TlsHandshakeFailure', 'TlsRequest'], 'tlsMetadata')}
414
+ }
415
+ }`,
416
+ 'client-error': gql`subscription OnClientError {
417
+ failedClientRequest {
418
+ errorCode
419
+ request {
420
+ id
421
+ timingEvents
422
+ tags
423
+ protocol
424
+ httpVersion
425
+ method
426
+ url
427
+ path
428
+
429
+ ${this.schema.typeHasField('ClientErrorRequest', 'rawHeaders')
430
+ ? 'rawHeaders'
431
+ : 'headers'
432
+ }
433
+
434
+ ${this.schema.asOptionalField('ClientErrorRequest', 'remoteIpAddress')}
435
+ ${this.schema.asOptionalField('ClientErrorRequest', 'remotePort')}
436
+ }
437
+ response {
438
+ id
439
+ timingEvents
440
+ tags
441
+ statusCode
442
+ statusMessage
443
+
444
+ ${this.schema.typeHasField('Response', 'rawHeaders')
445
+ ? 'rawHeaders'
446
+ : 'headers'
447
+ }
448
+
449
+ body
450
+ ${this.schema.asOptionalField('Response', 'rawTrailers')}
451
+ }
452
+ }
453
+ }`,
454
+ 'rule-event': gql`subscription OnRuleEvent {
455
+ ruleEvent {
456
+ requestId
457
+ ruleId
458
+ eventType
459
+ eventData
460
+ }
461
+ }`
462
+ }[event];
463
+
464
+ if (!query) return; // Unrecognized event, we can't subscribe to this.
465
+
466
+ return {
467
+ query,
468
+ transformResponse: (data: any): T => {
469
+ if (event === 'client-error') {
470
+ data.request = _.mapValues(data.request, (v) =>
471
+ // Normalize missing values to undefined to match the local result
472
+ v === null ? undefined : v
473
+ );
474
+
475
+ normalizeHttpMessage(data.request, event);
476
+ if (data.response) {
477
+ normalizeHttpMessage(data.response, event);
478
+ } else {
479
+ data.response = 'aborted';
480
+ }
481
+ } else if (event === 'websocket-message-received' || event === 'websocket-message-sent') {
482
+ normalizeWebSocketMessage(data);
483
+ } else if (event === 'abort') {
484
+ normalizeHttpMessage(data, event);
485
+ data.error = data.error ? JSON.parse(data.error) : undefined;
486
+ } else if (event === 'rule-event') {
487
+ const { eventData } = data;
488
+
489
+ // Events may include raw body data buffers, serialized as base64:
490
+ if (eventData.rawBody !== undefined) {
491
+ eventData.rawBody = Buffer.from(eventData.rawBody, 'base64');
492
+ }
493
+ } else {
494
+ normalizeHttpMessage(data, event);
495
+ }
496
+ return data;
497
+ }
498
+ };
499
+ }
500
+
501
+ private getEndpointDataGetter = (adminClient: AdminClient<{}>, ruleId: string) =>
502
+ async (): Promise<MockedEndpointData | null> => {
503
+ let result = await adminClient.sendQuery<{
504
+ mockedEndpoint: MockedEndpointData | null
505
+ }>({
506
+ query: gql`
507
+ query GetEndpointData($id: ID!) {
508
+ mockedEndpoint(id: $id) {
509
+ seenRequests {
510
+ id,
511
+ protocol,
512
+ method,
513
+ url,
514
+ path,
515
+ hostname
516
+
517
+ ${this.schema.typeHasField('Request', 'rawHeaders')
518
+ ? 'rawHeaders'
519
+ : 'headers'
520
+ }
521
+
522
+ body,
523
+ ${this.schema.asOptionalField('Request', 'timingEvents')}
524
+ ${this.schema.asOptionalField('Request', 'httpVersion')}
525
+ }
526
+ ${this.schema.asOptionalField('MockedEndpoint', 'isPending')}
527
+ }
528
+ }
529
+ `,
530
+ variables: { id: ruleId }
531
+ });
532
+
533
+ const mockedEndpoint = result.mockedEndpoint;
534
+ if (!mockedEndpoint) return null;
535
+
536
+ mockedEndpoint.seenRequests.forEach(req => normalizeHttpMessage(req));
537
+
538
+ return mockedEndpoint;
539
+ }
540
+ }