@outputai/http 0.1.1 → 0.1.2

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 (43) hide show
  1. package/package.json +2 -2
  2. package/dist/config.d.ts +0 -10
  3. package/dist/config.js +0 -10
  4. package/dist/hooks/assign_request_id.d.ts +0 -9
  5. package/dist/hooks/assign_request_id.js +0 -15
  6. package/dist/hooks/index.d.ts +0 -4
  7. package/dist/hooks/index.js +0 -4
  8. package/dist/hooks/trace_error.d.ts +0 -14
  9. package/dist/hooks/trace_error.js +0 -61
  10. package/dist/hooks/trace_error.spec.d.ts +0 -1
  11. package/dist/hooks/trace_error.spec.js +0 -35
  12. package/dist/hooks/trace_request.d.ts +0 -6
  13. package/dist/hooks/trace_request.js +0 -25
  14. package/dist/hooks/trace_request.spec.d.ts +0 -1
  15. package/dist/hooks/trace_request.spec.js +0 -60
  16. package/dist/hooks/trace_response.d.ts +0 -6
  17. package/dist/hooks/trace_response.js +0 -26
  18. package/dist/hooks/trace_response.spec.d.ts +0 -1
  19. package/dist/hooks/trace_response.spec.js +0 -68
  20. package/dist/index.d.ts +0 -26
  21. package/dist/index.integration.test.d.ts +0 -1
  22. package/dist/index.integration.test.js +0 -389
  23. package/dist/index.js +0 -51
  24. package/dist/index.spec.d.ts +0 -1
  25. package/dist/index.spec.js +0 -391
  26. package/dist/utils/create_trace_id.d.ts +0 -13
  27. package/dist/utils/create_trace_id.js +0 -20
  28. package/dist/utils/create_trace_id.spec.d.ts +0 -1
  29. package/dist/utils/create_trace_id.spec.js +0 -20
  30. package/dist/utils/index.d.ts +0 -4
  31. package/dist/utils/index.js +0 -4
  32. package/dist/utils/parse_request_body.d.ts +0 -7
  33. package/dist/utils/parse_request_body.js +0 -19
  34. package/dist/utils/parse_request_body.spec.d.ts +0 -1
  35. package/dist/utils/parse_request_body.spec.js +0 -19
  36. package/dist/utils/parse_response_body.d.ts +0 -10
  37. package/dist/utils/parse_response_body.js +0 -14
  38. package/dist/utils/parse_response_body.spec.d.ts +0 -1
  39. package/dist/utils/parse_response_body.spec.js +0 -19
  40. package/dist/utils/redact_headers.d.ts +0 -6
  41. package/dist/utils/redact_headers.js +0 -27
  42. package/dist/utils/redact_headers.spec.d.ts +0 -1
  43. package/dist/utils/redact_headers.spec.js +0 -245
@@ -1,391 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { httpClient, HTTPError, TimeoutError } from './index.js';
3
- import { Tracing } from '@outputai/core/sdk_activity_integration';
4
- import { config } from './config.js';
5
- vi.mock('@outputai/core/sdk_activity_integration', () => ({
6
- Tracing: {
7
- addEventStart: vi.fn(),
8
- addEventEnd: vi.fn(),
9
- addEventError: vi.fn()
10
- }
11
- }));
12
- vi.mock('./config.js', () => ({
13
- config: {
14
- logVerbose: false
15
- }
16
- }));
17
- // Mock ky at the module level to intercept at the source
18
- vi.mock('ky', () => {
19
- const createMockResponse = () => new Response(JSON.stringify({ success: true }), {
20
- status: 200,
21
- headers: { 'content-type': 'application/json' }
22
- });
23
- // Mock error types that match ky's actual error classes
24
- // IMPORTANT: These must be the same instances exported by the mock
25
- // so that instanceof checks work correctly
26
- class MockHTTPError extends Error {
27
- response;
28
- request;
29
- options;
30
- constructor(response, request, options) {
31
- super(`${response.status} ${response.statusText}`);
32
- this.name = 'HTTPError';
33
- this.response = response;
34
- this.request = request;
35
- this.options = options;
36
- }
37
- }
38
- class MockTimeoutError extends Error {
39
- request;
40
- constructor(request) {
41
- super('Request timed out');
42
- this.name = 'TimeoutError';
43
- this.request = request;
44
- }
45
- }
46
- // Helper to extract URL string from various input types
47
- const getUrlString = (input) => {
48
- if (typeof input === 'string') {
49
- return input;
50
- }
51
- if (input instanceof Request) {
52
- return input.url;
53
- }
54
- return input.toString();
55
- };
56
- // Default mock fetch implementation
57
- const defaultMockFetch = (input, init) => {
58
- const urlStr = getUrlString(input);
59
- const request = input instanceof Request ? input : new Request(urlStr, init);
60
- // Simulate timeout error (bypass hooks, thrown at fetch level)
61
- if (urlStr.includes('/timeout')) {
62
- throw new MockTimeoutError(request);
63
- }
64
- // Simulate network error (bypass hooks, thrown at fetch level)
65
- if (urlStr.includes('/network-error')) {
66
- throw new TypeError('Failed to fetch');
67
- }
68
- // Simulate HTTP 500 error (goes through hooks)
69
- if (urlStr.includes('/500')) {
70
- return Promise.resolve(new Response('Internal Server Error', {
71
- status: 500,
72
- statusText: 'Internal Server Error'
73
- }));
74
- }
75
- // Simulate HTTP 404 error (goes through hooks)
76
- if (urlStr.includes('/404')) {
77
- return Promise.resolve(new Response('Not Found', {
78
- status: 404,
79
- statusText: 'Not Found'
80
- }));
81
- }
82
- return Promise.resolve(createMockResponse());
83
- };
84
- class MockKy {
85
- hooks = {};
86
- options = {};
87
- customFetch;
88
- constructor(options = {}) {
89
- this.hooks = options.hooks || {};
90
- // Use provided fetch or default mock fetch
91
- this.customFetch = options.fetch || defaultMockFetch;
92
- // Store options with fetch function included
93
- this.options = {
94
- ...options,
95
- fetch: this.customFetch
96
- };
97
- }
98
- async runHooks(hookType, ...args) {
99
- const hooks = this.hooks[hookType] || [];
100
- for (const hook of hooks) {
101
- await hook(...args);
102
- }
103
- }
104
- async get(url, options = {}) {
105
- return this.makeRequest('GET', url, options);
106
- }
107
- async post(url, options = {}) {
108
- return this.makeRequest('POST', url, options);
109
- }
110
- async put(url, options = {}) {
111
- return this.makeRequest('PUT', url, options);
112
- }
113
- async patch(url, options = {}) {
114
- return this.makeRequest('PATCH', url, options);
115
- }
116
- async delete(url, options = {}) {
117
- return this.makeRequest('DELETE', url, options);
118
- }
119
- async makeRequest(method, url, options = {}) {
120
- // Construct full URL like ky would
121
- const fullUrl = this.options.prefixUrl ? `${this.options.prefixUrl}/${url}` : `https://example.com/${url}`;
122
- const request = new Request(fullUrl, { method });
123
- // Run beforeRequest hooks
124
- await this.runHooks('beforeRequest', request);
125
- // Use the custom fetch (which may be wrapped by applyFetchErrorTracing)
126
- // Fetch-level errors (timeout, network) bypass hooks entirely and will throw
127
- const response = await this.customFetch(request, { method });
128
- // Check for HTTP errors (non-2xx status codes)
129
- if (!response.ok) {
130
- const httpError = new MockHTTPError(response, request, options);
131
- // Run beforeError hooks for HTTP errors
132
- try {
133
- await this.runHooks('beforeError', httpError);
134
- }
135
- catch (hookErr) {
136
- // Hooks can transform the error
137
- throw hookErr;
138
- }
139
- throw httpError;
140
- }
141
- // Run afterResponse hooks for successful responses
142
- await this.runHooks('afterResponse', request, options, response);
143
- return response;
144
- }
145
- extend(options = {}) {
146
- // Handle function-based options (like applyDefaultOptions returns)
147
- const resolvedOptions = typeof options === 'function' ? options(this.options) : options;
148
- const mergedOptions = { ...this.options, ...resolvedOptions };
149
- const mergedHooks = { ...this.hooks };
150
- if (resolvedOptions.hooks) {
151
- Object.entries(resolvedOptions.hooks).forEach(([hookType, hookArray]) => {
152
- mergedHooks[hookType] = [
153
- ...(this.hooks[hookType] || []),
154
- ...(Array.isArray(hookArray) ? hookArray : [])
155
- ];
156
- });
157
- }
158
- mergedOptions.hooks = mergedHooks;
159
- return new MockKy(mergedOptions);
160
- }
161
- create(options = {}) {
162
- return new MockKy(options);
163
- }
164
- }
165
- return {
166
- default: new MockKy(),
167
- create: (options) => new MockKy(options),
168
- HTTPError: MockHTTPError,
169
- TimeoutError: MockTimeoutError
170
- };
171
- });
172
- const mockedTracing = vi.mocked(Tracing, true);
173
- const mockedConfig = vi.mocked(config);
174
- describe('HTTP Client', () => {
175
- beforeEach(() => {
176
- mockedTracing.addEventStart.mockClear();
177
- mockedTracing.addEventEnd.mockClear();
178
- mockedTracing.addEventError.mockClear();
179
- });
180
- describe('httpClient function', () => {
181
- it('should create an HTTP client with default options', () => {
182
- const client = httpClient();
183
- expect(client).toBeDefined();
184
- expect(typeof client.get).toBe('function');
185
- expect(typeof client.post).toBe('function');
186
- expect(typeof client.put).toBe('function');
187
- expect(typeof client.patch).toBe('function');
188
- expect(typeof client.delete).toBe('function');
189
- });
190
- it('should create an HTTP client with custom options', () => {
191
- const client = httpClient({
192
- prefixUrl: 'https://api.example.com',
193
- timeout: 5000
194
- });
195
- expect(client).toBeDefined();
196
- });
197
- it('should allow method chaining with extend', () => {
198
- const client = httpClient();
199
- const extendedClient = client.extend({
200
- headers: { 'X-Custom': 'test' }
201
- });
202
- expect(extendedClient).toBeDefined();
203
- });
204
- });
205
- describe('HTTP Client Interface', () => {
206
- it('should have all HTTP methods', () => {
207
- const client = httpClient();
208
- expect(typeof client.get).toBe('function');
209
- expect(typeof client.post).toBe('function');
210
- expect(typeof client.put).toBe('function');
211
- expect(typeof client.patch).toBe('function');
212
- expect(typeof client.delete).toBe('function');
213
- });
214
- });
215
- describe('Error Exports', () => {
216
- it('should export HTTPError', () => {
217
- expect(HTTPError).toBeDefined();
218
- expect(typeof HTTPError).toBe('function');
219
- });
220
- it('should export TimeoutError', () => {
221
- expect(TimeoutError).toBeDefined();
222
- expect(typeof TimeoutError).toBe('function');
223
- });
224
- });
225
- describe('Tracing Configuration', () => {
226
- it('should not trace headers or bodies by default', async () => {
227
- mockedConfig.logVerbose = false;
228
- const client = httpClient({
229
- prefixUrl: 'https://api.example.com'
230
- });
231
- await client.get('users/1');
232
- expect(mockedTracing.addEventStart).toHaveBeenCalled();
233
- });
234
- it('should trace headers and bodies when verbose logging is enabled', async () => {
235
- mockedConfig.logVerbose = true;
236
- const client = httpClient({
237
- prefixUrl: 'https://api.example.com'
238
- });
239
- await client.post('users', { json: { name: 'test', email: 'test@example.com' } });
240
- expect(mockedTracing.addEventStart).toHaveBeenCalled();
241
- });
242
- });
243
- describe('Hook Preservation', () => {
244
- it('should preserve original hooks when extending client with custom hooks', async () => {
245
- const customBeforeRequestCalled = vi.fn();
246
- const customAfterResponseCalled = vi.fn();
247
- const customBeforeErrorCalled = vi.fn();
248
- const client = httpClient({
249
- prefixUrl: 'https://api.example.com'
250
- });
251
- const extendedClient = client.extend({
252
- hooks: {
253
- beforeRequest: [
254
- async (request) => {
255
- customBeforeRequestCalled();
256
- return request;
257
- }
258
- ],
259
- afterResponse: [
260
- async (_request, _options, response) => {
261
- customAfterResponseCalled();
262
- return response;
263
- }
264
- ],
265
- beforeError: [
266
- async (error) => {
267
- customBeforeErrorCalled();
268
- return error;
269
- }
270
- ]
271
- }
272
- });
273
- await extendedClient.get('users/1');
274
- expect(customBeforeRequestCalled).toHaveBeenCalled();
275
- expect(customAfterResponseCalled).toHaveBeenCalled();
276
- expect(mockedTracing.addEventStart).toHaveBeenCalled();
277
- expect(mockedTracing.addEventEnd).toHaveBeenCalled();
278
- });
279
- });
280
- describe('Mocking Verification', () => {
281
- it('should use mocked responses and not make real HTTP requests', async () => {
282
- const client = httpClient({
283
- prefixUrl: 'https://api.example.com'
284
- });
285
- // Test GET request
286
- const getResponse = await client.get('users/1');
287
- const getData = await getResponse.json();
288
- expect(getData).toEqual({ success: true });
289
- // Test POST request
290
- const postResponse = await client.post('users', { json: { name: 'test' } });
291
- const postData = await postResponse.json();
292
- expect(postData).toEqual({ success: true });
293
- // Verify no actual network delay (should be very fast)
294
- const startTime = Date.now();
295
- await client.get('test');
296
- const duration = Date.now() - startTime;
297
- expect(duration).toBeLessThan(50); // Mocked calls should be nearly instantaneous
298
- // Verify that responses come from mocks, not real HTTP
299
- expect(getData.success).toBe(true);
300
- expect(postData.success).toBe(true);
301
- });
302
- });
303
- describe('Error Tracing', () => {
304
- describe('Fetch-Level Errors (should be traced by wrapped fetch)', () => {
305
- it('should trace timeout errors that bypass ky hooks', async () => {
306
- const client = httpClient({
307
- prefixUrl: 'https://api.example.com'
308
- });
309
- await expect(client.get('users/timeout')).rejects.toThrow(TimeoutError);
310
- // Timeout errors should be traced by the wrapped fetch
311
- expect(mockedTracing.addEventError).toHaveBeenCalledTimes(1);
312
- const errorCall = mockedTracing.addEventError.mock.calls[0][0];
313
- expect(errorCall).toHaveProperty('id');
314
- expect(errorCall).toHaveProperty('details');
315
- expect(errorCall.details).toHaveProperty('message');
316
- expect(errorCall.details).toHaveProperty('error');
317
- });
318
- it('should trace network errors that bypass ky hooks', async () => {
319
- const client = httpClient({
320
- prefixUrl: 'https://api.example.com'
321
- });
322
- await expect(client.get('users/network-error')).rejects.toThrow(TypeError);
323
- // Network errors should be traced by the wrapped fetch
324
- expect(mockedTracing.addEventError).toHaveBeenCalledTimes(1);
325
- const errorCall = mockedTracing.addEventError.mock.calls[0][0];
326
- expect(errorCall).toHaveProperty('id');
327
- expect(errorCall).toHaveProperty('details');
328
- expect(errorCall.details).toHaveProperty('message');
329
- expect(errorCall.details.message).toBe('Unknown error occurred');
330
- expect(errorCall.details).toHaveProperty('error');
331
- });
332
- });
333
- describe('HTTP Errors (should be traced by beforeError hook only)', () => {
334
- it('should trace HTTP 500 errors via beforeError hook, not fetch wrapper', async () => {
335
- const client = httpClient({
336
- prefixUrl: 'https://api.example.com'
337
- });
338
- await expect(client.get('users/500')).rejects.toThrow(HTTPError);
339
- // HTTP errors should be traced by the beforeError hook (traceError)
340
- // The wrapped fetch should NOT trace it (to prevent double-tracing)
341
- expect(mockedTracing.addEventError).toHaveBeenCalledTimes(1);
342
- const errorCall = mockedTracing.addEventError.mock.calls[0][0];
343
- expect(errorCall).toHaveProperty('id');
344
- expect(errorCall).toHaveProperty('details');
345
- expect(errorCall.details).toHaveProperty('status', 500);
346
- expect(errorCall.details).toHaveProperty('statusText', 'Internal Server Error');
347
- });
348
- it('should trace HTTP 404 errors via beforeError hook, not fetch wrapper', async () => {
349
- const client = httpClient({
350
- prefixUrl: 'https://api.example.com'
351
- });
352
- await expect(client.get('users/404')).rejects.toThrow(HTTPError);
353
- // HTTP errors should be traced by the beforeError hook (traceError)
354
- expect(mockedTracing.addEventError).toHaveBeenCalledTimes(1);
355
- const errorCall = mockedTracing.addEventError.mock.calls[0][0];
356
- expect(errorCall).toHaveProperty('id');
357
- expect(errorCall).toHaveProperty('details');
358
- expect(errorCall.details).toHaveProperty('status', 404);
359
- expect(errorCall.details).toHaveProperty('statusText', 'Not Found');
360
- });
361
- it('should not double-trace HTTP errors', async () => {
362
- const client = httpClient({
363
- prefixUrl: 'https://api.example.com'
364
- });
365
- await expect(client.get('users/500')).rejects.toThrow(HTTPError);
366
- // Should only be traced once (by beforeError hook)
367
- expect(mockedTracing.addEventError).toHaveBeenCalledTimes(1);
368
- });
369
- });
370
- describe('Error Type Differentiation', () => {
371
- it('should handle timeout and HTTP errors differently in the same client', async () => {
372
- const client = httpClient({
373
- prefixUrl: 'https://api.example.com'
374
- });
375
- // Test timeout error
376
- mockedTracing.addEventError.mockClear();
377
- await expect(client.get('users/timeout')).rejects.toThrow(TimeoutError);
378
- expect(mockedTracing.addEventError).toHaveBeenCalledTimes(1);
379
- const timeoutCall = mockedTracing.addEventError.mock.calls[0][0];
380
- expect(timeoutCall.details).toHaveProperty('message');
381
- expect(timeoutCall.details).toHaveProperty('error');
382
- // Test HTTP error
383
- mockedTracing.addEventError.mockClear();
384
- await expect(client.get('users/500')).rejects.toThrow(HTTPError);
385
- expect(mockedTracing.addEventError).toHaveBeenCalledTimes(1);
386
- const httpCall = mockedTracing.addEventError.mock.calls[0][0];
387
- expect(httpCall.details).toHaveProperty('status', 500);
388
- });
389
- });
390
- });
391
- });
@@ -1,13 +0,0 @@
1
- /**
2
- * Create a trace ID from the X-Request-ID header
3
- *
4
- * Returns the X-Request-ID header value if present, otherwise returns null.
5
- * When null is returned, tracing should be skipped entirely.
6
- *
7
- * The X-Request-ID header is assigned by the assignRequestId hook,
8
- * ensuring each request invocation has a unique identifier for tracing.
9
- *
10
- * @param {Request} request - The fetch API request object
11
- * @returns {string | null} The X-Request-ID value or null if not present
12
- */
13
- export default function createTraceId(request: Request): string | null;
@@ -1,20 +0,0 @@
1
- /**
2
- * Create a trace ID from the X-Request-ID header
3
- *
4
- * Returns the X-Request-ID header value if present, otherwise returns null.
5
- * When null is returned, tracing should be skipped entirely.
6
- *
7
- * The X-Request-ID header is assigned by the assignRequestId hook,
8
- * ensuring each request invocation has a unique identifier for tracing.
9
- *
10
- * @param {Request} request - The fetch API request object
11
- * @returns {string | null} The X-Request-ID value or null if not present
12
- */
13
- export default function createTraceId(request) {
14
- const requestId = request.headers?.get('X-Request-ID');
15
- if (!requestId) {
16
- console.warn('createTraceId: X-Request-ID header not found. Tracing will be skipped for this request.');
17
- return null;
18
- }
19
- return requestId;
20
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,20 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import createTraceId from './create_trace_id.js';
3
- describe('utils/create_trace_id', () => {
4
- it('returns the X-Request-ID header when present', () => {
5
- const req = new Request('https://ex.com/users/1', {
6
- method: 'GET',
7
- headers: { 'X-Request-ID': 'test-uuid-123' }
8
- });
9
- const id = createTraceId(req);
10
- expect(id).toBe('test-uuid-123');
11
- });
12
- it('returns null and logs warning when X-Request-ID header is missing', () => {
13
- const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
14
- const req = new Request('https://ex.com/users/1', { method: 'GET' });
15
- const id = createTraceId(req);
16
- expect(id).toBeNull();
17
- expect(warnSpy).toHaveBeenCalledWith('createTraceId: X-Request-ID header not found. Tracing will be skipped for this request.');
18
- warnSpy.mockRestore();
19
- });
20
- });
@@ -1,4 +0,0 @@
1
- export { default as parseResponseBody } from './parse_response_body.js';
2
- export { default as parseRequestBody } from './parse_request_body.js';
3
- export { default as redactHeaders } from './redact_headers.js';
4
- export { default as createTraceId } from './create_trace_id.js';
@@ -1,4 +0,0 @@
1
- export { default as parseResponseBody } from './parse_response_body.js';
2
- export { default as parseRequestBody } from './parse_request_body.js';
3
- export { default as redactHeaders } from './redact_headers.js';
4
- export { default as createTraceId } from './create_trace_id.js';
@@ -1,7 +0,0 @@
1
- /**
2
- * Safely parses request body as JSON, falling back to string if parsing fails
3
- *
4
- * @param {Request} request - The fetch API request object
5
- * @returns {object|string|null} The parsed response
6
- */
7
- export default function parseRequestBody(request: Request): Promise<object | string | null>;
@@ -1,19 +0,0 @@
1
- /**
2
- * Safely parses request body as JSON, falling back to string if parsing fails
3
- *
4
- * @param {Request} request - The fetch API request object
5
- * @returns {object|string|null} The parsed response
6
- */
7
- export default async function parseRequestBody(request) {
8
- if (!request.body) {
9
- return null;
10
- }
11
- const cloned = request.clone();
12
- const body = await cloned.text();
13
- try {
14
- return JSON.parse(body);
15
- }
16
- catch {
17
- return body;
18
- }
19
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,19 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import parseRequestBody from './parse_request_body.js';
3
- describe('utils/parse_request_body', () => {
4
- it('returns null when no body is present', async () => {
5
- const req = new Request('https://ex.com', { method: 'GET' });
6
- const result = await parseRequestBody(req);
7
- expect(result).toBeNull();
8
- });
9
- it('parses JSON body when present', async () => {
10
- const req = new Request('https://ex.com', { method: 'POST', body: JSON.stringify({ a: 1 }) });
11
- const result = await parseRequestBody(req);
12
- expect(result).toEqual({ a: 1 });
13
- });
14
- it('returns raw text when not valid JSON', async () => {
15
- const req = new Request('https://ex.com', { method: 'POST', body: 'not-json' });
16
- const result = await parseRequestBody(req);
17
- expect(result).toBe('not-json');
18
- });
19
- });
@@ -1,10 +0,0 @@
1
- import type { KyResponse } from 'ky';
2
- /**
3
- * Parses response body based on content type:
4
- * - application/json = object
5
- * - text/plain = string
6
- *
7
- * @param {KyResponse} response
8
- * @returns {object|string|null} The parsed response
9
- */
10
- export default function parseResponseBody(response: KyResponse): Promise<object | string | null>;
@@ -1,14 +0,0 @@
1
- /**
2
- * Parses response body based on content type:
3
- * - application/json = object
4
- * - text/plain = string
5
- *
6
- * @param {KyResponse} response
7
- * @returns {object|string|null} The parsed response
8
- */
9
- export default async function parseResponseBody(response) {
10
- const cloned = response.clone();
11
- const contentType = response.headers.get('content-type') || '';
12
- const body = await cloned[contentType.includes('application/json') ? 'json' : 'text']();
13
- return body || null;
14
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,19 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import parseResponseBody from './parse_response_body.js';
3
- describe('utils/parse_response_body', () => {
4
- it('parses JSON when content-type is application/json', async () => {
5
- const res = new Response(JSON.stringify({ ok: true }), { headers: { 'content-type': 'application/json' } });
6
- const result = await parseResponseBody(res);
7
- expect(result).toEqual({ ok: true });
8
- });
9
- it('returns text when content-type is not JSON', async () => {
10
- const res = new Response('hello', { headers: { 'content-type': 'text/plain' } });
11
- const result = await parseResponseBody(res);
12
- expect(result).toBe('hello');
13
- });
14
- it('returns null for empty body', async () => {
15
- const res = new Response('', { headers: { 'content-type': 'text/plain' } });
16
- const result = await parseResponseBody(res);
17
- expect(result).toBeNull();
18
- });
19
- });
@@ -1,6 +0,0 @@
1
- /**
2
- * Redacts sensitive headers for safe logging
3
- * @param headers - Headers object to redact
4
- * @returns Object with sensitive headers redacted
5
- */
6
- export default function redactHeaders(headers: Record<string, string> | Headers): Record<string, string>;
@@ -1,27 +0,0 @@
1
- /**
2
- * Sensitive header patterns for redaction (case-insensitive)
3
- */
4
- const SENSITIVE_HEADER_PATTERNS = [
5
- /authorization/i,
6
- /token/i,
7
- /api-?key/i,
8
- /secret/i,
9
- /password/i,
10
- /pwd/i,
11
- /key/i,
12
- /cookie/i
13
- ];
14
- /**
15
- * Redacts sensitive headers for safe logging
16
- * @param headers - Headers object to redact
17
- * @returns Object with sensitive headers redacted
18
- */
19
- export default function redactHeaders(headers) {
20
- const result = {};
21
- const entries = headers instanceof Headers ? headers.entries() : Object.entries(headers);
22
- for (const [key, value] of entries) {
23
- const isSensitive = SENSITIVE_HEADER_PATTERNS.some(pattern => pattern.test(key));
24
- result[key] = isSensitive ? '[REDACTED]' : value;
25
- }
26
- return result;
27
- }
@@ -1 +0,0 @@
1
- export {};