@frontmcp/testing 0.5.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 (112) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1358 -0
  3. package/jest-preset.js +61 -0
  4. package/package.json +94 -0
  5. package/src/assertions/index.d.ts +5 -0
  6. package/src/assertions/index.js +18 -0
  7. package/src/assertions/index.js.map +1 -0
  8. package/src/assertions/mcp-assertions.d.ts +81 -0
  9. package/src/assertions/mcp-assertions.js +220 -0
  10. package/src/assertions/mcp-assertions.js.map +1 -0
  11. package/src/auth/auth-headers.d.ts +29 -0
  12. package/src/auth/auth-headers.js +62 -0
  13. package/src/auth/auth-headers.js.map +1 -0
  14. package/src/auth/index.d.ts +9 -0
  15. package/src/auth/index.js +15 -0
  16. package/src/auth/index.js.map +1 -0
  17. package/src/auth/token-factory.d.ts +94 -0
  18. package/src/auth/token-factory.js +181 -0
  19. package/src/auth/token-factory.js.map +1 -0
  20. package/src/auth/user-fixtures.d.ts +26 -0
  21. package/src/auth/user-fixtures.js +92 -0
  22. package/src/auth/user-fixtures.js.map +1 -0
  23. package/src/client/index.d.ts +7 -0
  24. package/src/client/index.js +12 -0
  25. package/src/client/index.js.map +1 -0
  26. package/src/client/mcp-test-client.builder.d.ts +72 -0
  27. package/src/client/mcp-test-client.builder.js +111 -0
  28. package/src/client/mcp-test-client.builder.js.map +1 -0
  29. package/src/client/mcp-test-client.d.ts +360 -0
  30. package/src/client/mcp-test-client.js +929 -0
  31. package/src/client/mcp-test-client.js.map +1 -0
  32. package/src/client/mcp-test-client.types.d.ts +216 -0
  33. package/src/client/mcp-test-client.types.js +7 -0
  34. package/src/client/mcp-test-client.types.js.map +1 -0
  35. package/src/errors/index.d.ts +45 -0
  36. package/src/errors/index.js +85 -0
  37. package/src/errors/index.js.map +1 -0
  38. package/src/expect.d.ts +67 -0
  39. package/src/expect.js +31 -0
  40. package/src/expect.js.map +1 -0
  41. package/src/fixtures/fixture-types.d.ts +166 -0
  42. package/src/fixtures/fixture-types.js +7 -0
  43. package/src/fixtures/fixture-types.js.map +1 -0
  44. package/src/fixtures/index.d.ts +7 -0
  45. package/src/fixtures/index.js +16 -0
  46. package/src/fixtures/index.js.map +1 -0
  47. package/src/fixtures/test-fixture.d.ts +41 -0
  48. package/src/fixtures/test-fixture.js +280 -0
  49. package/src/fixtures/test-fixture.js.map +1 -0
  50. package/src/http-mock/http-mock.d.ts +84 -0
  51. package/src/http-mock/http-mock.js +544 -0
  52. package/src/http-mock/http-mock.js.map +1 -0
  53. package/src/http-mock/http-mock.types.d.ts +124 -0
  54. package/src/http-mock/http-mock.types.js +10 -0
  55. package/src/http-mock/http-mock.types.js.map +1 -0
  56. package/src/http-mock/index.d.ts +6 -0
  57. package/src/http-mock/index.js +11 -0
  58. package/src/http-mock/index.js.map +1 -0
  59. package/src/index.d.ts +65 -0
  60. package/src/index.js +128 -0
  61. package/src/index.js.map +1 -0
  62. package/src/interceptor/index.d.ts +7 -0
  63. package/src/interceptor/index.js +15 -0
  64. package/src/interceptor/index.js.map +1 -0
  65. package/src/interceptor/interceptor-chain.d.ts +77 -0
  66. package/src/interceptor/interceptor-chain.js +207 -0
  67. package/src/interceptor/interceptor-chain.js.map +1 -0
  68. package/src/interceptor/interceptor.types.d.ts +131 -0
  69. package/src/interceptor/interceptor.types.js +7 -0
  70. package/src/interceptor/interceptor.types.js.map +1 -0
  71. package/src/interceptor/mock-registry.d.ts +82 -0
  72. package/src/interceptor/mock-registry.js +189 -0
  73. package/src/interceptor/mock-registry.js.map +1 -0
  74. package/src/matchers/index.d.ts +7 -0
  75. package/src/matchers/index.js +12 -0
  76. package/src/matchers/index.js.map +1 -0
  77. package/src/matchers/matcher-types.d.ts +266 -0
  78. package/src/matchers/matcher-types.js +10 -0
  79. package/src/matchers/matcher-types.js.map +1 -0
  80. package/src/matchers/mcp-matchers.d.ts +47 -0
  81. package/src/matchers/mcp-matchers.js +391 -0
  82. package/src/matchers/mcp-matchers.js.map +1 -0
  83. package/src/playwright/index.d.ts +37 -0
  84. package/src/playwright/index.js +49 -0
  85. package/src/playwright/index.js.map +1 -0
  86. package/src/server/index.d.ts +6 -0
  87. package/src/server/index.js +10 -0
  88. package/src/server/index.js.map +1 -0
  89. package/src/server/test-server.d.ts +99 -0
  90. package/src/server/test-server.js +286 -0
  91. package/src/server/test-server.js.map +1 -0
  92. package/src/setup.d.ts +22 -0
  93. package/src/setup.js +30 -0
  94. package/src/setup.js.map +1 -0
  95. package/src/transport/index.d.ts +6 -0
  96. package/src/transport/index.js +10 -0
  97. package/src/transport/index.js.map +1 -0
  98. package/src/transport/streamable-http.transport.d.ts +65 -0
  99. package/src/transport/streamable-http.transport.js +432 -0
  100. package/src/transport/streamable-http.transport.js.map +1 -0
  101. package/src/transport/transport.interface.d.ts +124 -0
  102. package/src/transport/transport.interface.js +7 -0
  103. package/src/transport/transport.interface.js.map +1 -0
  104. package/src/ui/index.d.ts +17 -0
  105. package/src/ui/index.js +23 -0
  106. package/src/ui/index.js.map +1 -0
  107. package/src/ui/ui-assertions.d.ts +94 -0
  108. package/src/ui/ui-assertions.js +215 -0
  109. package/src/ui/ui-assertions.js.map +1 -0
  110. package/src/ui/ui-matchers.d.ts +39 -0
  111. package/src/ui/ui-matchers.js +275 -0
  112. package/src/ui/ui-matchers.js.map +1 -0
@@ -0,0 +1,544 @@
1
+ "use strict";
2
+ /**
3
+ * @file http-mock.ts
4
+ * @description HTTP request mocking implementation for offline MCP server testing
5
+ *
6
+ * This module intercepts fetch() and XMLHttpRequest calls, allowing tools to be
7
+ * tested without making real HTTP requests.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { httpMock } from '@frontmcp/testing';
12
+ *
13
+ * // Enable HTTP mocking
14
+ * const interceptor = httpMock.interceptor();
15
+ *
16
+ * // Mock a GET request
17
+ * interceptor.get('https://api.example.com/users', {
18
+ * body: [{ id: 1, name: 'John' }],
19
+ * });
20
+ *
21
+ * // Mock a POST request with pattern matching
22
+ * interceptor.post(/api\.example\.com\/users/, {
23
+ * status: 201,
24
+ * body: { id: 2, name: 'Jane' },
25
+ * });
26
+ *
27
+ * // Now run your MCP server test - HTTP calls will be intercepted
28
+ * const result = await mcp.tools.call('fetch-users', {});
29
+ *
30
+ * // Verify all mocks were used
31
+ * interceptor.assertDone();
32
+ *
33
+ * // Restore original fetch
34
+ * interceptor.restore();
35
+ * ```
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.httpResponse = exports.httpMock = void 0;
39
+ // ═══════════════════════════════════════════════════════════════════
40
+ // GLOBAL STATE
41
+ // ═══════════════════════════════════════════════════════════════════
42
+ /** Original fetch function */
43
+ let originalFetch = null;
44
+ /** Whether mocking is enabled */
45
+ let mockingEnabled = false;
46
+ /** All active interceptors */
47
+ const activeInterceptors = [];
48
+ class HttpInterceptorImpl {
49
+ mocks = [];
50
+ _allowPassthrough = false;
51
+ _isActive = true;
52
+ mock(definition) {
53
+ const entry = {
54
+ definition,
55
+ callCount: 0,
56
+ calls: [],
57
+ remainingUses: definition.times ?? Infinity,
58
+ };
59
+ this.mocks.push(entry);
60
+ return {
61
+ remove: () => {
62
+ const index = this.mocks.indexOf(entry);
63
+ if (index !== -1) {
64
+ this.mocks.splice(index, 1);
65
+ }
66
+ },
67
+ callCount: () => entry.callCount,
68
+ calls: () => [...entry.calls],
69
+ waitForCalls: async (count, timeoutMs = 5000) => {
70
+ const deadline = Date.now() + timeoutMs;
71
+ while (Date.now() < deadline) {
72
+ if (entry.callCount >= count) {
73
+ return entry.calls.slice(0, count);
74
+ }
75
+ await sleep(50);
76
+ }
77
+ throw new Error(`Timeout waiting for ${count} calls, got ${entry.callCount}`);
78
+ },
79
+ };
80
+ }
81
+ get(url, response) {
82
+ return this.mock({
83
+ match: { url, method: 'GET' },
84
+ response: normalizeResponse(response),
85
+ });
86
+ }
87
+ post(url, response) {
88
+ return this.mock({
89
+ match: { url, method: 'POST' },
90
+ response: normalizeResponse(response),
91
+ });
92
+ }
93
+ put(url, response) {
94
+ return this.mock({
95
+ match: { url, method: 'PUT' },
96
+ response: normalizeResponse(response),
97
+ });
98
+ }
99
+ delete(url, response) {
100
+ return this.mock({
101
+ match: { url, method: 'DELETE' },
102
+ response: normalizeResponse(response),
103
+ });
104
+ }
105
+ any(url, response) {
106
+ return this.mock({
107
+ match: { url },
108
+ response: normalizeResponse(response),
109
+ });
110
+ }
111
+ clear() {
112
+ this.mocks = [];
113
+ }
114
+ pending() {
115
+ return this.mocks.filter((m) => m.remainingUses > 0).map((m) => m.definition);
116
+ }
117
+ isDone() {
118
+ return this.mocks.every((m) => m.remainingUses <= 0 || m.definition.times === undefined);
119
+ }
120
+ assertDone() {
121
+ const pending = this.pending().filter((m) => m.times !== undefined);
122
+ if (pending.length > 0) {
123
+ const descriptions = pending.map((m) => {
124
+ const url = m.match.url instanceof RegExp ? m.match.url.toString() : m.match.url;
125
+ return ` - ${m.match.method ?? 'ANY'} ${url}`;
126
+ });
127
+ throw new Error(`Unused HTTP mocks:\n${descriptions.join('\n')}`);
128
+ }
129
+ }
130
+ allowPassthrough(allow) {
131
+ this._allowPassthrough = allow;
132
+ }
133
+ restore() {
134
+ this._isActive = false;
135
+ const index = activeInterceptors.indexOf(this);
136
+ if (index !== -1) {
137
+ activeInterceptors.splice(index, 1);
138
+ }
139
+ }
140
+ // Internal methods
141
+ isActive() {
142
+ return this._isActive;
143
+ }
144
+ canPassthrough() {
145
+ return this._allowPassthrough;
146
+ }
147
+ /**
148
+ * Try to match a request against mocks in this scope
149
+ */
150
+ async matchRequest(info) {
151
+ if (!this._isActive)
152
+ return null;
153
+ for (const entry of this.mocks) {
154
+ if (entry.remainingUses <= 0)
155
+ continue;
156
+ if (this.requestMatches(info, entry.definition.match)) {
157
+ // Found a match
158
+ entry.callCount++;
159
+ entry.calls.push(info);
160
+ entry.remainingUses--;
161
+ // Get response
162
+ let mockResponse;
163
+ if (typeof entry.definition.response === 'function') {
164
+ mockResponse = await entry.definition.response(info);
165
+ }
166
+ else {
167
+ mockResponse = entry.definition.response;
168
+ }
169
+ // Apply delay
170
+ if (mockResponse.delay && mockResponse.delay > 0) {
171
+ await sleep(mockResponse.delay);
172
+ }
173
+ return createMockResponse(mockResponse);
174
+ }
175
+ }
176
+ return null;
177
+ }
178
+ requestMatches(info, matcher) {
179
+ // Check URL
180
+ if (!this.urlMatches(info.url, matcher.url)) {
181
+ return false;
182
+ }
183
+ // Check method
184
+ if (matcher.method) {
185
+ const methods = Array.isArray(matcher.method) ? matcher.method : [matcher.method];
186
+ if (!methods.includes(info.method)) {
187
+ return false;
188
+ }
189
+ }
190
+ // Check headers
191
+ if (matcher.headers) {
192
+ for (const [key, expected] of Object.entries(matcher.headers)) {
193
+ const actual = info.headers[key.toLowerCase()];
194
+ if (!actual)
195
+ return false;
196
+ if (expected instanceof RegExp) {
197
+ if (!expected.test(actual))
198
+ return false;
199
+ }
200
+ else if (actual !== expected) {
201
+ return false;
202
+ }
203
+ }
204
+ }
205
+ // Check body
206
+ if (matcher.body !== undefined) {
207
+ if (!this.bodyMatches(info.body, matcher.body)) {
208
+ return false;
209
+ }
210
+ }
211
+ return true;
212
+ }
213
+ urlMatches(url, matcher) {
214
+ if (typeof matcher === 'string') {
215
+ // Exact match or contains
216
+ return url === matcher || url.includes(matcher);
217
+ }
218
+ else if (matcher instanceof RegExp) {
219
+ return matcher.test(url);
220
+ }
221
+ else {
222
+ return matcher(url);
223
+ }
224
+ }
225
+ bodyMatches(actual, expected) {
226
+ if (typeof expected === 'function') {
227
+ return expected(actual);
228
+ }
229
+ if (typeof expected === 'string') {
230
+ const actualStr = typeof actual === 'string' ? actual : JSON.stringify(actual);
231
+ return actualStr === expected || actualStr.includes(expected);
232
+ }
233
+ if (expected instanceof RegExp) {
234
+ const actualStr = typeof actual === 'string' ? actual : JSON.stringify(actual);
235
+ return expected.test(actualStr);
236
+ }
237
+ // Object comparison - check that all expected keys match
238
+ if (typeof actual !== 'object' || actual === null) {
239
+ return false;
240
+ }
241
+ for (const [key, value] of Object.entries(expected)) {
242
+ const actualValue = actual[key];
243
+ if (typeof value === 'object' && value !== null) {
244
+ if (!this.bodyMatches(actualValue, value)) {
245
+ return false;
246
+ }
247
+ }
248
+ else if (actualValue !== value) {
249
+ return false;
250
+ }
251
+ }
252
+ return true;
253
+ }
254
+ }
255
+ // ═══════════════════════════════════════════════════════════════════
256
+ // FETCH INTERCEPTOR
257
+ // ═══════════════════════════════════════════════════════════════════
258
+ async function interceptedFetch(input, init) {
259
+ // Build request info
260
+ const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;
261
+ const method = (init?.method ?? 'GET').toUpperCase();
262
+ // Parse headers
263
+ const headers = {};
264
+ if (init?.headers) {
265
+ if (init.headers instanceof Headers) {
266
+ init.headers.forEach((value, key) => {
267
+ headers[key.toLowerCase()] = value;
268
+ });
269
+ }
270
+ else if (Array.isArray(init.headers)) {
271
+ for (const [key, value] of init.headers) {
272
+ headers[key.toLowerCase()] = value;
273
+ }
274
+ }
275
+ else {
276
+ for (const [key, value] of Object.entries(init.headers)) {
277
+ headers[key.toLowerCase()] = value;
278
+ }
279
+ }
280
+ }
281
+ // Parse body
282
+ let body;
283
+ let rawBody;
284
+ if (init?.body) {
285
+ if (typeof init.body === 'string') {
286
+ rawBody = init.body;
287
+ try {
288
+ body = JSON.parse(init.body);
289
+ }
290
+ catch {
291
+ body = init.body;
292
+ }
293
+ }
294
+ else if (init.body instanceof ArrayBuffer || init.body instanceof Uint8Array) {
295
+ rawBody = new TextDecoder().decode(init.body);
296
+ try {
297
+ body = JSON.parse(rawBody);
298
+ }
299
+ catch {
300
+ body = rawBody;
301
+ }
302
+ }
303
+ else {
304
+ body = init.body;
305
+ }
306
+ }
307
+ const requestInfo = {
308
+ url,
309
+ method,
310
+ headers,
311
+ body,
312
+ rawBody,
313
+ };
314
+ // Try to match against active interceptors (in reverse order - last added first)
315
+ for (let i = activeInterceptors.length - 1; i >= 0; i--) {
316
+ const interceptor = activeInterceptors[i];
317
+ if (!interceptor.isActive())
318
+ continue;
319
+ const mockResponse = await interceptor.matchRequest(requestInfo);
320
+ if (mockResponse) {
321
+ return mockResponse;
322
+ }
323
+ }
324
+ // No match found - check if passthrough is allowed
325
+ for (const interceptor of activeInterceptors) {
326
+ if (interceptor.isActive() && interceptor.canPassthrough()) {
327
+ // Passthrough to real fetch
328
+ if (originalFetch) {
329
+ return originalFetch(input, init);
330
+ }
331
+ }
332
+ }
333
+ // No match and no passthrough - throw error
334
+ throw new Error(`No HTTP mock found for ${method} ${url}\n` +
335
+ `Add a mock using httpMock.interceptor().${method.toLowerCase()}('${url}', { body: ... })`);
336
+ }
337
+ // ═══════════════════════════════════════════════════════════════════
338
+ // HELPERS
339
+ // ═══════════════════════════════════════════════════════════════════
340
+ function normalizeResponse(response) {
341
+ // If it looks like a plain object (response body), wrap it
342
+ if (!('status' in response) && !('body' in response) && !('headers' in response)) {
343
+ return { body: response };
344
+ }
345
+ return response;
346
+ }
347
+ function createMockResponse(mock) {
348
+ // Check for network error - throw instead of returning Response
349
+ // This simulates real network failures where fetch rejects
350
+ const mockAny = mock;
351
+ if (mockAny._throwError) {
352
+ const body = mock.body;
353
+ const message = body && typeof body === 'object' && 'message' in body ? String(body['message']) : 'Network error';
354
+ throw new TypeError(`fetch failed: ${message}`);
355
+ }
356
+ const status = mock.status ?? 200;
357
+ const statusText = mock.statusText ?? 'OK';
358
+ // Build headers
359
+ const headers = new Headers(mock.headers ?? {});
360
+ // Build body
361
+ let body = null;
362
+ if (mock.body !== undefined) {
363
+ if (typeof mock.body === 'string') {
364
+ body = mock.body;
365
+ if (!headers.has('content-type')) {
366
+ headers.set('content-type', 'text/plain');
367
+ }
368
+ }
369
+ else if (mock.body instanceof ArrayBuffer) {
370
+ body = mock.body;
371
+ }
372
+ else if (Buffer.isBuffer(mock.body)) {
373
+ // Convert Buffer to ArrayBuffer for Response compatibility
374
+ body = new Uint8Array(mock.body).buffer;
375
+ }
376
+ else {
377
+ body = JSON.stringify(mock.body);
378
+ if (!headers.has('content-type')) {
379
+ headers.set('content-type', 'application/json');
380
+ }
381
+ }
382
+ }
383
+ return new Response(body, { status, statusText, headers });
384
+ }
385
+ function sleep(ms) {
386
+ return new Promise((resolve) => setTimeout(resolve, ms));
387
+ }
388
+ // ═══════════════════════════════════════════════════════════════════
389
+ // MANAGER IMPLEMENTATION
390
+ // ═══════════════════════════════════════════════════════════════════
391
+ class HttpMockManagerImpl {
392
+ interceptor() {
393
+ // Auto-enable mocking when creating an interceptor
394
+ if (!mockingEnabled) {
395
+ this.enable();
396
+ }
397
+ const interceptor = new HttpInterceptorImpl();
398
+ activeInterceptors.push(interceptor);
399
+ return interceptor;
400
+ }
401
+ enable() {
402
+ if (mockingEnabled)
403
+ return;
404
+ // Store original fetch
405
+ if (typeof globalThis.fetch === 'function') {
406
+ originalFetch = globalThis.fetch;
407
+ globalThis.fetch = interceptedFetch;
408
+ }
409
+ mockingEnabled = true;
410
+ }
411
+ disable() {
412
+ if (!mockingEnabled)
413
+ return;
414
+ // Restore original fetch
415
+ if (originalFetch) {
416
+ globalThis.fetch = originalFetch;
417
+ originalFetch = null;
418
+ }
419
+ // Clear all interceptors
420
+ activeInterceptors.length = 0;
421
+ mockingEnabled = false;
422
+ }
423
+ isEnabled() {
424
+ return mockingEnabled;
425
+ }
426
+ clearAll() {
427
+ for (const interceptor of activeInterceptors) {
428
+ interceptor.clear();
429
+ }
430
+ activeInterceptors.length = 0;
431
+ }
432
+ }
433
+ // ═══════════════════════════════════════════════════════════════════
434
+ // EXPORTS
435
+ // ═══════════════════════════════════════════════════════════════════
436
+ /**
437
+ * Global HTTP mock manager
438
+ *
439
+ * @example
440
+ * ```typescript
441
+ * import { httpMock } from '@frontmcp/testing';
442
+ *
443
+ * // Create an HTTP interceptor
444
+ * const interceptor = httpMock.interceptor();
445
+ *
446
+ * // Mock requests
447
+ * interceptor.get('https://api.example.com/data', { id: 1, name: 'Test' });
448
+ *
449
+ * // Run tests...
450
+ *
451
+ * // Clean up
452
+ * interceptor.restore();
453
+ * ```
454
+ */
455
+ exports.httpMock = new HttpMockManagerImpl();
456
+ /**
457
+ * Helper to create mock responses
458
+ */
459
+ exports.httpResponse = {
460
+ /** Create a JSON response */
461
+ json(data, status = 200) {
462
+ return {
463
+ status,
464
+ headers: { 'content-type': 'application/json' },
465
+ body: data,
466
+ };
467
+ },
468
+ /** Create a text response */
469
+ text(data, status = 200) {
470
+ return {
471
+ status,
472
+ headers: { 'content-type': 'text/plain' },
473
+ body: data,
474
+ };
475
+ },
476
+ /** Create an HTML response */
477
+ html(data, status = 200) {
478
+ return {
479
+ status,
480
+ headers: { 'content-type': 'text/html' },
481
+ body: data,
482
+ };
483
+ },
484
+ /** Create an error response */
485
+ error(status, message) {
486
+ return {
487
+ status,
488
+ statusText: message ?? getStatusText(status),
489
+ body: message ? { error: message } : undefined,
490
+ };
491
+ },
492
+ /** Create a 404 Not Found response */
493
+ notFound(message = 'Not Found') {
494
+ return exports.httpResponse.error(404, message);
495
+ },
496
+ /** Create a 500 Internal Server Error response */
497
+ serverError(message = 'Internal Server Error') {
498
+ return exports.httpResponse.error(500, message);
499
+ },
500
+ /** Create a 401 Unauthorized response */
501
+ unauthorized(message = 'Unauthorized') {
502
+ return exports.httpResponse.error(401, message);
503
+ },
504
+ /** Create a 403 Forbidden response */
505
+ forbidden(message = 'Forbidden') {
506
+ return exports.httpResponse.error(403, message);
507
+ },
508
+ /**
509
+ * Create a network error that causes fetch to reject
510
+ * This simulates real network failures where fetch throws instead of returning a Response
511
+ */
512
+ networkError(message = 'Network error') {
513
+ return {
514
+ status: 0,
515
+ body: { _networkError: true, message },
516
+ // Mark this as a network error for the interceptor to throw
517
+ _throwError: true,
518
+ };
519
+ },
520
+ /** Create a delayed response */
521
+ delayed(data, delayMs, status = 200) {
522
+ return {
523
+ status,
524
+ body: data,
525
+ delay: delayMs,
526
+ };
527
+ },
528
+ };
529
+ function getStatusText(status) {
530
+ const texts = {
531
+ 200: 'OK',
532
+ 201: 'Created',
533
+ 204: 'No Content',
534
+ 400: 'Bad Request',
535
+ 401: 'Unauthorized',
536
+ 403: 'Forbidden',
537
+ 404: 'Not Found',
538
+ 500: 'Internal Server Error',
539
+ 502: 'Bad Gateway',
540
+ 503: 'Service Unavailable',
541
+ };
542
+ return texts[status] ?? 'Unknown';
543
+ }
544
+ //# sourceMappingURL=http-mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-mock.js","sourceRoot":"","sources":["../../../src/http-mock/http-mock.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;;;AAaH,sEAAsE;AACtE,eAAe;AACf,sEAAsE;AAEtE,8BAA8B;AAC9B,IAAI,aAAa,GAAmC,IAAI,CAAC;AAEzD,iCAAiC;AACjC,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B,8BAA8B;AAC9B,MAAM,kBAAkB,GAA0B,EAAE,CAAC;AAarD,MAAM,mBAAmB;IACf,KAAK,GAAgB,EAAE,CAAC;IACxB,iBAAiB,GAAG,KAAK,CAAC;IAC1B,SAAS,GAAG,IAAI,CAAC;IAEzB,IAAI,CAAC,UAA8B;QACjC,MAAM,KAAK,GAAc;YACvB,UAAU;YACV,SAAS,EAAE,CAAC;YACZ,KAAK,EAAE,EAAE;YACT,aAAa,EAAE,UAAU,CAAC,KAAK,IAAI,QAAQ;SAC5C,CAAC;QAEF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAEvB,OAAO;YACL,MAAM,EAAE,GAAG,EAAE;gBACX,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACxC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;oBACjB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBAC9B,CAAC;YACH,CAAC;YACD,SAAS,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS;YAChC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC;YAC7B,YAAY,EAAE,KAAK,EAAE,KAAa,EAAE,SAAS,GAAG,IAAI,EAAE,EAAE;gBACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;gBACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;oBAC7B,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,EAAE,CAAC;wBAC7B,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;oBACrC,CAAC;oBACD,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM,IAAI,KAAK,CAAC,uBAAuB,KAAK,eAAe,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;YAChF,CAAC;SACF,CAAC;IACJ,CAAC;IAED,GAAG,CAAC,GAAoB,EAAE,QAAoD;QAC5E,OAAO,IAAI,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE;YAC7B,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC;SACtC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,GAAoB,EAAE,QAAoD;QAC7E,OAAO,IAAI,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE;YAC9B,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC;SACtC,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,GAAoB,EAAE,QAAoD;QAC5E,OAAO,IAAI,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE;YAC7B,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC;SACtC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,GAAoB,EAAE,QAAoD;QAC/E,OAAO,IAAI,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE;YAChC,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC;SACtC,CAAC,CAAC;IACL,CAAC;IAED,GAAG,CAAC,GAAoB,EAAE,QAAoD;QAC5E,OAAO,IAAI,CAAC,IAAI,CAAC;YACf,KAAK,EAAE,EAAE,GAAG,EAAE;YACd,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC;SACtC,CAAC,CAAC;IACL,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAChF,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IAC3F,CAAC;IAED,UAAU;QACR,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;QACpE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACrC,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,YAAY,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;gBACjF,OAAO,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;YACjD,CAAC,CAAC,CAAC;YACH,MAAM,IAAI,KAAK,CAAC,uBAAuB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,gBAAgB,CAAC,KAAc;QAC7B,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC;IACjC,CAAC;IAED,OAAO;QACL,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;YACjB,kBAAkB,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACtC,CAAC;IACH,CAAC;IAED,mBAAmB;IAEnB,QAAQ;QACN,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,YAAY,CAAC,IAAqB;QACtC,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QAEjC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/B,IAAI,KAAK,CAAC,aAAa,IAAI,CAAC;gBAAE,SAAS;YAEvC,IAAI,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtD,gBAAgB;gBAChB,KAAK,CAAC,SAAS,EAAE,CAAC;gBAClB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvB,KAAK,CAAC,aAAa,EAAE,CAAC;gBAEtB,eAAe;gBACf,IAAI,YAA8B,CAAC;gBACnC,IAAI,OAAO,KAAK,CAAC,UAAU,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;oBACpD,YAAY,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACvD,CAAC;qBAAM,CAAC;oBACN,YAAY,GAAG,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAC3C,CAAC;gBAED,cAAc;gBACd,IAAI,YAAY,CAAC,KAAK,IAAI,YAAY,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;oBACjD,MAAM,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;gBAClC,CAAC;gBAED,OAAO,kBAAkB,CAAC,YAAY,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,cAAc,CAAC,IAAqB,EAAE,OAA2B;QACvE,YAAY;QACZ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,eAAe;QACf,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAClF,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnC,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,gBAAgB;QAChB,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,KAAK,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;gBAC/C,IAAI,CAAC,MAAM;oBAAE,OAAO,KAAK,CAAC;gBAC1B,IAAI,QAAQ,YAAY,MAAM,EAAE,CAAC;oBAC/B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;wBAAE,OAAO,KAAK,CAAC;gBAC3C,CAAC;qBAAM,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;oBAC/B,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;QACH,CAAC;QAED,aAAa;QACb,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/C,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,UAAU,CAAC,GAAW,EAAE,OAAqD;QACnF,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,0BAA0B;YAC1B,OAAO,GAAG,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;aAAM,IAAI,OAAO,YAAY,MAAM,EAAE,CAAC;YACrC,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAEO,WAAW,CACjB,MAAe,EACf,QAAkF;QAElF,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;YACnC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;QAED,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAC/E,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAChE,CAAC;QAED,IAAI,QAAQ,YAAY,MAAM,EAAE,CAAC;YAC/B,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAC/E,OAAO,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClC,CAAC;QAED,yDAAyD;QACzD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAClD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpD,MAAM,WAAW,GAAI,MAAkC,CAAC,GAAG,CAAC,CAAC;YAC7D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBAChD,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,KAAgC,CAAC,EAAE,CAAC;oBACrE,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;iBAAM,IAAI,WAAW,KAAK,KAAK,EAAE,CAAC;gBACjC,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,sEAAsE;AACtE,oBAAoB;AACpB,sEAAsE;AAEtE,KAAK,UAAU,gBAAgB,CAAC,KAAwB,EAAE,IAAkB;IAC1E,qBAAqB;IACrB,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;IACpG,MAAM,MAAM,GAAI,CAAC,IAAI,EAAE,MAAM,IAAI,KAAK,CAAgB,CAAC,WAAW,EAAgB,CAAC;IAEnF,gBAAgB;IAChB,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,IAAI,EAAE,OAAO,EAAE,CAAC;QAClB,IAAI,IAAI,CAAC,OAAO,YAAY,OAAO,EAAE,CAAC;YACpC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBAClC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;YACrC,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACxC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;YACrC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxD,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,GAAG,KAAK,CAAC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAED,aAAa;IACb,IAAI,IAAa,CAAC;IAClB,IAAI,OAA2B,CAAC;IAChC,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC;QACf,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC;YACpB,IAAI,CAAC;gBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACnB,CAAC;QACH,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,YAAY,WAAW,IAAI,IAAI,CAAC,IAAI,YAAY,UAAU,EAAE,CAAC;YAC/E,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9C,IAAI,CAAC;gBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7B,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,GAAG,OAAO,CAAC;YACjB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAoB;QACnC,GAAG;QACH,MAAM;QACN,OAAO;QACP,IAAI;QACJ,OAAO;KACR,CAAC;IAEF,iFAAiF;IACjF,KAAK,IAAI,CAAC,GAAG,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACxD,MAAM,WAAW,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC1C,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE;YAAE,SAAS;QAEtC,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QACjE,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO,YAAY,CAAC;QACtB,CAAC;IACH,CAAC;IAED,mDAAmD;IACnD,KAAK,MAAM,WAAW,IAAI,kBAAkB,EAAE,CAAC;QAC7C,IAAI,WAAW,CAAC,QAAQ,EAAE,IAAI,WAAW,CAAC,cAAc,EAAE,EAAE,CAAC;YAC3D,4BAA4B;YAC5B,IAAI,aAAa,EAAE,CAAC;gBAClB,OAAO,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;IACH,CAAC;IAED,4CAA4C;IAC5C,MAAM,IAAI,KAAK,CACb,0BAA0B,MAAM,IAAI,GAAG,IAAI;QACzC,2CAA2C,MAAM,CAAC,WAAW,EAAE,KAAK,GAAG,mBAAmB,CAC7F,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,UAAU;AACV,sEAAsE;AAEtE,SAAS,iBAAiB,CAAC,QAAoD;IAC7E,2DAA2D;IAC3D,IAAI,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,QAAQ,CAAC,EAAE,CAAC;QACjF,OAAO,EAAE,IAAI,EAAE,QAAmC,EAAE,CAAC;IACvD,CAAC;IACD,OAAO,QAA4B,CAAC;AACtC,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAsB;IAChD,gEAAgE;IAChE,2DAA2D;IAC3D,MAAM,OAAO,GAAG,IAAoD,CAAC;IACrE,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,IAA2C,CAAC;QAC9D,MAAM,OAAO,GAAG,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC;QAClH,MAAM,IAAI,SAAS,CAAC,iBAAiB,OAAO,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;IAE3C,gBAAgB;IAChB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAEhD,aAAa;IACb,IAAI,IAAI,GAAoB,IAAI,CAAC;IACjC,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;gBACjC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,YAAY,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,YAAY,WAAW,EAAE,CAAC;YAC5C,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,2DAA2D;YAC3D,IAAI,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QAC1C,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;gBACjC,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,sEAAsE;AACtE,yBAAyB;AACzB,sEAAsE;AAEtE,MAAM,mBAAmB;IACvB,WAAW;QACT,mDAAmD;QACnD,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,mBAAmB,EAAE,CAAC;QAC9C,kBAAkB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACrC,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM;QACJ,IAAI,cAAc;YAAE,OAAO;QAE3B,uBAAuB;QACvB,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC3C,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC;YACjC,UAAU,CAAC,KAAK,GAAG,gBAAgB,CAAC;QACtC,CAAC;QAED,cAAc,GAAG,IAAI,CAAC;IACxB,CAAC;IAED,OAAO;QACL,IAAI,CAAC,cAAc;YAAE,OAAO;QAE5B,yBAAyB;QACzB,IAAI,aAAa,EAAE,CAAC;YAClB,UAAU,CAAC,KAAK,GAAG,aAAa,CAAC;YACjC,aAAa,GAAG,IAAI,CAAC;QACvB,CAAC;QAED,yBAAyB;QACzB,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC;QAC9B,cAAc,GAAG,KAAK,CAAC;IACzB,CAAC;IAED,SAAS;QACP,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,QAAQ;QACN,KAAK,MAAM,WAAW,IAAI,kBAAkB,EAAE,CAAC;YAC7C,WAAW,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;QACD,kBAAkB,CAAC,MAAM,GAAG,CAAC,CAAC;IAChC,CAAC;CACF;AAED,sEAAsE;AACtE,UAAU;AACV,sEAAsE;AAEtE;;;;;;;;;;;;;;;;;;GAkBG;AACU,QAAA,QAAQ,GAAoB,IAAI,mBAAmB,EAAE,CAAC;AAEnE;;GAEG;AACU,QAAA,YAAY,GAAG;IAC1B,6BAA6B;IAC7B,IAAI,CAAI,IAAO,EAAE,MAAM,GAAG,GAAG;QAC3B,OAAO;YACL,MAAM;YACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAA+B;SACtC,CAAC;IACJ,CAAC;IAED,6BAA6B;IAC7B,IAAI,CAAC,IAAY,EAAE,MAAM,GAAG,GAAG;QAC7B,OAAO;YACL,MAAM;YACN,OAAO,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE;YACzC,IAAI,EAAE,IAAI;SACX,CAAC;IACJ,CAAC;IAED,8BAA8B;IAC9B,IAAI,CAAC,IAAY,EAAE,MAAM,GAAG,GAAG;QAC7B,OAAO;YACL,MAAM;YACN,OAAO,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE;YACxC,IAAI,EAAE,IAAI;SACX,CAAC;IACJ,CAAC;IAED,+BAA+B;IAC/B,KAAK,CAAC,MAAc,EAAE,OAAgB;QACpC,OAAO;YACL,MAAM;YACN,UAAU,EAAE,OAAO,IAAI,aAAa,CAAC,MAAM,CAAC;YAC5C,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS;SAC/C,CAAC;IACJ,CAAC;IAED,sCAAsC;IACtC,QAAQ,CAAC,OAAO,GAAG,WAAW;QAC5B,OAAO,oBAAY,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,kDAAkD;IAClD,WAAW,CAAC,OAAO,GAAG,uBAAuB;QAC3C,OAAO,oBAAY,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,yCAAyC;IACzC,YAAY,CAAC,OAAO,GAAG,cAAc;QACnC,OAAO,oBAAY,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,sCAAsC;IACtC,SAAS,CAAC,OAAO,GAAG,WAAW;QAC7B,OAAO,oBAAY,CAAC,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED;;;OAGG;IACH,YAAY,CAAC,OAAO,GAAG,eAAe;QACpC,OAAO;YACL,MAAM,EAAE,CAAC;YACT,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,OAAO,EAAE;YACtC,4DAA4D;YAC5D,WAAW,EAAE,IAAI;SACE,CAAC;IACxB,CAAC;IAED,gCAAgC;IAChC,OAAO,CAAI,IAAO,EAAE,OAAe,EAAE,MAAM,GAAG,GAAG;QAC/C,OAAO;YACL,MAAM;YACN,IAAI,EAAE,IAA+B;YACrC,KAAK,EAAE,OAAO;SACf,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,SAAS,aAAa,CAAC,MAAc;IACnC,MAAM,KAAK,GAA2B;QACpC,GAAG,EAAE,IAAI;QACT,GAAG,EAAE,SAAS;QACd,GAAG,EAAE,YAAY;QACjB,GAAG,EAAE,aAAa;QAClB,GAAG,EAAE,cAAc;QACnB,GAAG,EAAE,WAAW;QAChB,GAAG,EAAE,WAAW;QAChB,GAAG,EAAE,uBAAuB;QAC5B,GAAG,EAAE,aAAa;QAClB,GAAG,EAAE,qBAAqB;KAC3B,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC;AACpC,CAAC","sourcesContent":["/**\n * @file http-mock.ts\n * @description HTTP request mocking implementation for offline MCP server testing\n *\n * This module intercepts fetch() and XMLHttpRequest calls, allowing tools to be\n * tested without making real HTTP requests.\n *\n * @example\n * ```typescript\n * import { httpMock } from '@frontmcp/testing';\n *\n * // Enable HTTP mocking\n * const interceptor = httpMock.interceptor();\n *\n * // Mock a GET request\n * interceptor.get('https://api.example.com/users', {\n * body: [{ id: 1, name: 'John' }],\n * });\n *\n * // Mock a POST request with pattern matching\n * interceptor.post(/api\\.example\\.com\\/users/, {\n * status: 201,\n * body: { id: 2, name: 'Jane' },\n * });\n *\n * // Now run your MCP server test - HTTP calls will be intercepted\n * const result = await mcp.tools.call('fetch-users', {});\n *\n * // Verify all mocks were used\n * interceptor.assertDone();\n *\n * // Restore original fetch\n * interceptor.restore();\n * ```\n */\n\nimport type {\n HttpMethod,\n HttpRequestMatcher,\n HttpMockResponse,\n HttpMockDefinition,\n HttpRequestInfo,\n HttpMockHandle,\n HttpInterceptor,\n HttpMockManager,\n} from './http-mock.types';\n\n// ═══════════════════════════════════════════════════════════════════\n// GLOBAL STATE\n// ═══════════════════════════════════════════════════════════════════\n\n/** Original fetch function */\nlet originalFetch: typeof globalThis.fetch | null = null;\n\n/** Whether mocking is enabled */\nlet mockingEnabled = false;\n\n/** All active interceptors */\nconst activeInterceptors: HttpInterceptorImpl[] = [];\n\n// ═══════════════════════════════════════════════════════════════════\n// HTTP INTERCEPTOR IMPLEMENTATION\n// ═══════════════════════════════════════════════════════════════════\n\ninterface MockEntry {\n definition: HttpMockDefinition;\n callCount: number;\n calls: HttpRequestInfo[];\n remainingUses: number;\n}\n\nclass HttpInterceptorImpl implements HttpInterceptor {\n private mocks: MockEntry[] = [];\n private _allowPassthrough = false;\n private _isActive = true;\n\n mock(definition: HttpMockDefinition): HttpMockHandle {\n const entry: MockEntry = {\n definition,\n callCount: 0,\n calls: [],\n remainingUses: definition.times ?? Infinity,\n };\n\n this.mocks.push(entry);\n\n return {\n remove: () => {\n const index = this.mocks.indexOf(entry);\n if (index !== -1) {\n this.mocks.splice(index, 1);\n }\n },\n callCount: () => entry.callCount,\n calls: () => [...entry.calls],\n waitForCalls: async (count: number, timeoutMs = 5000) => {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n if (entry.callCount >= count) {\n return entry.calls.slice(0, count);\n }\n await sleep(50);\n }\n throw new Error(`Timeout waiting for ${count} calls, got ${entry.callCount}`);\n },\n };\n }\n\n get(url: string | RegExp, response: HttpMockResponse | Record<string, unknown>): HttpMockHandle {\n return this.mock({\n match: { url, method: 'GET' },\n response: normalizeResponse(response),\n });\n }\n\n post(url: string | RegExp, response: HttpMockResponse | Record<string, unknown>): HttpMockHandle {\n return this.mock({\n match: { url, method: 'POST' },\n response: normalizeResponse(response),\n });\n }\n\n put(url: string | RegExp, response: HttpMockResponse | Record<string, unknown>): HttpMockHandle {\n return this.mock({\n match: { url, method: 'PUT' },\n response: normalizeResponse(response),\n });\n }\n\n delete(url: string | RegExp, response: HttpMockResponse | Record<string, unknown>): HttpMockHandle {\n return this.mock({\n match: { url, method: 'DELETE' },\n response: normalizeResponse(response),\n });\n }\n\n any(url: string | RegExp, response: HttpMockResponse | Record<string, unknown>): HttpMockHandle {\n return this.mock({\n match: { url },\n response: normalizeResponse(response),\n });\n }\n\n clear(): void {\n this.mocks = [];\n }\n\n pending(): HttpMockDefinition[] {\n return this.mocks.filter((m) => m.remainingUses > 0).map((m) => m.definition);\n }\n\n isDone(): boolean {\n return this.mocks.every((m) => m.remainingUses <= 0 || m.definition.times === undefined);\n }\n\n assertDone(): void {\n const pending = this.pending().filter((m) => m.times !== undefined);\n if (pending.length > 0) {\n const descriptions = pending.map((m) => {\n const url = m.match.url instanceof RegExp ? m.match.url.toString() : m.match.url;\n return ` - ${m.match.method ?? 'ANY'} ${url}`;\n });\n throw new Error(`Unused HTTP mocks:\\n${descriptions.join('\\n')}`);\n }\n }\n\n allowPassthrough(allow: boolean): void {\n this._allowPassthrough = allow;\n }\n\n restore(): void {\n this._isActive = false;\n const index = activeInterceptors.indexOf(this);\n if (index !== -1) {\n activeInterceptors.splice(index, 1);\n }\n }\n\n // Internal methods\n\n isActive(): boolean {\n return this._isActive;\n }\n\n canPassthrough(): boolean {\n return this._allowPassthrough;\n }\n\n /**\n * Try to match a request against mocks in this scope\n */\n async matchRequest(info: HttpRequestInfo): Promise<Response | null> {\n if (!this._isActive) return null;\n\n for (const entry of this.mocks) {\n if (entry.remainingUses <= 0) continue;\n\n if (this.requestMatches(info, entry.definition.match)) {\n // Found a match\n entry.callCount++;\n entry.calls.push(info);\n entry.remainingUses--;\n\n // Get response\n let mockResponse: HttpMockResponse;\n if (typeof entry.definition.response === 'function') {\n mockResponse = await entry.definition.response(info);\n } else {\n mockResponse = entry.definition.response;\n }\n\n // Apply delay\n if (mockResponse.delay && mockResponse.delay > 0) {\n await sleep(mockResponse.delay);\n }\n\n return createMockResponse(mockResponse);\n }\n }\n\n return null;\n }\n\n private requestMatches(info: HttpRequestInfo, matcher: HttpRequestMatcher): boolean {\n // Check URL\n if (!this.urlMatches(info.url, matcher.url)) {\n return false;\n }\n\n // Check method\n if (matcher.method) {\n const methods = Array.isArray(matcher.method) ? matcher.method : [matcher.method];\n if (!methods.includes(info.method)) {\n return false;\n }\n }\n\n // Check headers\n if (matcher.headers) {\n for (const [key, expected] of Object.entries(matcher.headers)) {\n const actual = info.headers[key.toLowerCase()];\n if (!actual) return false;\n if (expected instanceof RegExp) {\n if (!expected.test(actual)) return false;\n } else if (actual !== expected) {\n return false;\n }\n }\n }\n\n // Check body\n if (matcher.body !== undefined) {\n if (!this.bodyMatches(info.body, matcher.body)) {\n return false;\n }\n }\n\n return true;\n }\n\n private urlMatches(url: string, matcher: string | RegExp | ((url: string) => boolean)): boolean {\n if (typeof matcher === 'string') {\n // Exact match or contains\n return url === matcher || url.includes(matcher);\n } else if (matcher instanceof RegExp) {\n return matcher.test(url);\n } else {\n return matcher(url);\n }\n }\n\n private bodyMatches(\n actual: unknown,\n expected: string | RegExp | Record<string, unknown> | ((body: unknown) => boolean),\n ): boolean {\n if (typeof expected === 'function') {\n return expected(actual);\n }\n\n if (typeof expected === 'string') {\n const actualStr = typeof actual === 'string' ? actual : JSON.stringify(actual);\n return actualStr === expected || actualStr.includes(expected);\n }\n\n if (expected instanceof RegExp) {\n const actualStr = typeof actual === 'string' ? actual : JSON.stringify(actual);\n return expected.test(actualStr);\n }\n\n // Object comparison - check that all expected keys match\n if (typeof actual !== 'object' || actual === null) {\n return false;\n }\n\n for (const [key, value] of Object.entries(expected)) {\n const actualValue = (actual as Record<string, unknown>)[key];\n if (typeof value === 'object' && value !== null) {\n if (!this.bodyMatches(actualValue, value as Record<string, unknown>)) {\n return false;\n }\n } else if (actualValue !== value) {\n return false;\n }\n }\n\n return true;\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// FETCH INTERCEPTOR\n// ═══════════════════════════════════════════════════════════════════\n\nasync function interceptedFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n // Build request info\n const url = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n const method = ((init?.method ?? 'GET') as HttpMethod).toUpperCase() as HttpMethod;\n\n // Parse headers\n const headers: Record<string, string> = {};\n if (init?.headers) {\n if (init.headers instanceof Headers) {\n init.headers.forEach((value, key) => {\n headers[key.toLowerCase()] = value;\n });\n } else if (Array.isArray(init.headers)) {\n for (const [key, value] of init.headers) {\n headers[key.toLowerCase()] = value;\n }\n } else {\n for (const [key, value] of Object.entries(init.headers)) {\n headers[key.toLowerCase()] = value;\n }\n }\n }\n\n // Parse body\n let body: unknown;\n let rawBody: string | undefined;\n if (init?.body) {\n if (typeof init.body === 'string') {\n rawBody = init.body;\n try {\n body = JSON.parse(init.body);\n } catch {\n body = init.body;\n }\n } else if (init.body instanceof ArrayBuffer || init.body instanceof Uint8Array) {\n rawBody = new TextDecoder().decode(init.body);\n try {\n body = JSON.parse(rawBody);\n } catch {\n body = rawBody;\n }\n } else {\n body = init.body;\n }\n }\n\n const requestInfo: HttpRequestInfo = {\n url,\n method,\n headers,\n body,\n rawBody,\n };\n\n // Try to match against active interceptors (in reverse order - last added first)\n for (let i = activeInterceptors.length - 1; i >= 0; i--) {\n const interceptor = activeInterceptors[i];\n if (!interceptor.isActive()) continue;\n\n const mockResponse = await interceptor.matchRequest(requestInfo);\n if (mockResponse) {\n return mockResponse;\n }\n }\n\n // No match found - check if passthrough is allowed\n for (const interceptor of activeInterceptors) {\n if (interceptor.isActive() && interceptor.canPassthrough()) {\n // Passthrough to real fetch\n if (originalFetch) {\n return originalFetch(input, init);\n }\n }\n }\n\n // No match and no passthrough - throw error\n throw new Error(\n `No HTTP mock found for ${method} ${url}\\n` +\n `Add a mock using httpMock.interceptor().${method.toLowerCase()}('${url}', { body: ... })`,\n );\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// HELPERS\n// ═══════════════════════════════════════════════════════════════════\n\nfunction normalizeResponse(response: HttpMockResponse | Record<string, unknown>): HttpMockResponse {\n // If it looks like a plain object (response body), wrap it\n if (!('status' in response) && !('body' in response) && !('headers' in response)) {\n return { body: response as Record<string, unknown> };\n }\n return response as HttpMockResponse;\n}\n\nfunction createMockResponse(mock: HttpMockResponse): Response {\n // Check for network error - throw instead of returning Response\n // This simulates real network failures where fetch rejects\n const mockAny = mock as HttpMockResponse & { _throwError?: boolean };\n if (mockAny._throwError) {\n const body = mock.body as Record<string, unknown> | undefined;\n const message = body && typeof body === 'object' && 'message' in body ? String(body['message']) : 'Network error';\n throw new TypeError(`fetch failed: ${message}`);\n }\n\n const status = mock.status ?? 200;\n const statusText = mock.statusText ?? 'OK';\n\n // Build headers\n const headers = new Headers(mock.headers ?? {});\n\n // Build body\n let body: BodyInit | null = null;\n if (mock.body !== undefined) {\n if (typeof mock.body === 'string') {\n body = mock.body;\n if (!headers.has('content-type')) {\n headers.set('content-type', 'text/plain');\n }\n } else if (mock.body instanceof ArrayBuffer) {\n body = mock.body;\n } else if (Buffer.isBuffer(mock.body)) {\n // Convert Buffer to ArrayBuffer for Response compatibility\n body = new Uint8Array(mock.body).buffer;\n } else {\n body = JSON.stringify(mock.body);\n if (!headers.has('content-type')) {\n headers.set('content-type', 'application/json');\n }\n }\n }\n\n return new Response(body, { status, statusText, headers });\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// MANAGER IMPLEMENTATION\n// ═══════════════════════════════════════════════════════════════════\n\nclass HttpMockManagerImpl implements HttpMockManager {\n interceptor(): HttpInterceptor {\n // Auto-enable mocking when creating an interceptor\n if (!mockingEnabled) {\n this.enable();\n }\n\n const interceptor = new HttpInterceptorImpl();\n activeInterceptors.push(interceptor);\n return interceptor;\n }\n\n enable(): void {\n if (mockingEnabled) return;\n\n // Store original fetch\n if (typeof globalThis.fetch === 'function') {\n originalFetch = globalThis.fetch;\n globalThis.fetch = interceptedFetch;\n }\n\n mockingEnabled = true;\n }\n\n disable(): void {\n if (!mockingEnabled) return;\n\n // Restore original fetch\n if (originalFetch) {\n globalThis.fetch = originalFetch;\n originalFetch = null;\n }\n\n // Clear all interceptors\n activeInterceptors.length = 0;\n mockingEnabled = false;\n }\n\n isEnabled(): boolean {\n return mockingEnabled;\n }\n\n clearAll(): void {\n for (const interceptor of activeInterceptors) {\n interceptor.clear();\n }\n activeInterceptors.length = 0;\n }\n}\n\n// ═══════════════════════════════════════════════════════════════════\n// EXPORTS\n// ═══════════════════════════════════════════════════════════════════\n\n/**\n * Global HTTP mock manager\n *\n * @example\n * ```typescript\n * import { httpMock } from '@frontmcp/testing';\n *\n * // Create an HTTP interceptor\n * const interceptor = httpMock.interceptor();\n *\n * // Mock requests\n * interceptor.get('https://api.example.com/data', { id: 1, name: 'Test' });\n *\n * // Run tests...\n *\n * // Clean up\n * interceptor.restore();\n * ```\n */\nexport const httpMock: HttpMockManager = new HttpMockManagerImpl();\n\n/**\n * Helper to create mock responses\n */\nexport const httpResponse = {\n /** Create a JSON response */\n json<T>(data: T, status = 200): HttpMockResponse {\n return {\n status,\n headers: { 'content-type': 'application/json' },\n body: data as Record<string, unknown>,\n };\n },\n\n /** Create a text response */\n text(data: string, status = 200): HttpMockResponse {\n return {\n status,\n headers: { 'content-type': 'text/plain' },\n body: data,\n };\n },\n\n /** Create an HTML response */\n html(data: string, status = 200): HttpMockResponse {\n return {\n status,\n headers: { 'content-type': 'text/html' },\n body: data,\n };\n },\n\n /** Create an error response */\n error(status: number, message?: string): HttpMockResponse {\n return {\n status,\n statusText: message ?? getStatusText(status),\n body: message ? { error: message } : undefined,\n };\n },\n\n /** Create a 404 Not Found response */\n notFound(message = 'Not Found'): HttpMockResponse {\n return httpResponse.error(404, message);\n },\n\n /** Create a 500 Internal Server Error response */\n serverError(message = 'Internal Server Error'): HttpMockResponse {\n return httpResponse.error(500, message);\n },\n\n /** Create a 401 Unauthorized response */\n unauthorized(message = 'Unauthorized'): HttpMockResponse {\n return httpResponse.error(401, message);\n },\n\n /** Create a 403 Forbidden response */\n forbidden(message = 'Forbidden'): HttpMockResponse {\n return httpResponse.error(403, message);\n },\n\n /**\n * Create a network error that causes fetch to reject\n * This simulates real network failures where fetch throws instead of returning a Response\n */\n networkError(message = 'Network error'): HttpMockResponse {\n return {\n status: 0,\n body: { _networkError: true, message },\n // Mark this as a network error for the interceptor to throw\n _throwError: true,\n } as HttpMockResponse;\n },\n\n /** Create a delayed response */\n delayed<T>(data: T, delayMs: number, status = 200): HttpMockResponse {\n return {\n status,\n body: data as Record<string, unknown>,\n delay: delayMs,\n };\n },\n};\n\nfunction getStatusText(status: number): string {\n const texts: Record<number, string> = {\n 200: 'OK',\n 201: 'Created',\n 204: 'No Content',\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not Found',\n 500: 'Internal Server Error',\n 502: 'Bad Gateway',\n 503: 'Service Unavailable',\n };\n return texts[status] ?? 'Unknown';\n}\n"]}