@optimizely-opal/opal-tool-ocp-sdk 1.0.0-OCP-1449.1 → 1.0.0-beta.10

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 (47) hide show
  1. package/README.md +13 -3
  2. package/dist/function/GlobalToolFunction.d.ts +3 -2
  3. package/dist/function/GlobalToolFunction.d.ts.map +1 -1
  4. package/dist/function/GlobalToolFunction.js +7 -4
  5. package/dist/function/GlobalToolFunction.js.map +1 -1
  6. package/dist/function/GlobalToolFunction.test.js +16 -4
  7. package/dist/function/GlobalToolFunction.test.js.map +1 -1
  8. package/dist/function/ToolFunction.d.ts +3 -2
  9. package/dist/function/ToolFunction.d.ts.map +1 -1
  10. package/dist/function/ToolFunction.js +7 -4
  11. package/dist/function/ToolFunction.js.map +1 -1
  12. package/dist/function/ToolFunction.test.js +15 -3
  13. package/dist/function/ToolFunction.test.js.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/logging/ToolLogger.d.ts +11 -3
  19. package/dist/logging/ToolLogger.d.ts.map +1 -1
  20. package/dist/logging/ToolLogger.js +114 -13
  21. package/dist/logging/ToolLogger.js.map +1 -1
  22. package/dist/logging/ToolLogger.test.js +177 -71
  23. package/dist/logging/ToolLogger.test.js.map +1 -1
  24. package/dist/types/Models.d.ts +7 -1
  25. package/dist/types/Models.d.ts.map +1 -1
  26. package/dist/types/Models.js +5 -1
  27. package/dist/types/Models.js.map +1 -1
  28. package/dist/types/ToolError.d.ts +13 -0
  29. package/dist/types/ToolError.d.ts.map +1 -1
  30. package/dist/types/ToolError.js +30 -2
  31. package/dist/types/ToolError.js.map +1 -1
  32. package/dist/types/ToolError.test.js +27 -3
  33. package/dist/types/ToolError.test.js.map +1 -1
  34. package/dist/validation/ParameterValidator.test.js +2 -1
  35. package/dist/validation/ParameterValidator.test.js.map +1 -1
  36. package/package.json +3 -3
  37. package/src/function/GlobalToolFunction.test.ts +21 -6
  38. package/src/function/GlobalToolFunction.ts +9 -5
  39. package/src/function/ToolFunction.test.ts +21 -5
  40. package/src/function/ToolFunction.ts +9 -5
  41. package/src/index.ts +1 -1
  42. package/src/logging/ToolLogger.test.ts +225 -74
  43. package/src/logging/ToolLogger.ts +129 -15
  44. package/src/types/Models.ts +8 -1
  45. package/src/types/ToolError.test.ts +33 -3
  46. package/src/types/ToolError.ts +32 -2
  47. package/src/validation/ParameterValidator.test.ts +4 -1
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ToolLogger = void 0;
4
4
  const app_sdk_1 = require("@zaiusinc/app-sdk");
5
+ const MAX_PARAM_LOG_LENGTH = 128;
6
+ const MAX_BODY_LOG_LENGTH = 256;
7
+ const MAX_ARRAY_ITEMS = 2;
5
8
  /**
6
9
  * Utility class for logging Opal tool requests and responses with security considerations
7
10
  */
@@ -45,12 +48,13 @@ class ToolLogger {
45
48
  'jwt',
46
49
  'bearer_token'
47
50
  ];
48
- static MAX_PARAM_LENGTH = 100;
49
- static MAX_ARRAY_ITEMS = 10;
50
51
  /**
51
52
  * Redacts sensitive data from an object
52
53
  */
53
- static redactSensitiveData(data, maxDepth = 5) {
54
+ static redactSensitiveDataAndTruncate(data, maxDepth = 5, accumulatedLength = 0) {
55
+ if (accumulatedLength > MAX_BODY_LOG_LENGTH) {
56
+ return '';
57
+ }
54
58
  if (maxDepth <= 0) {
55
59
  return '[MAX_DEPTH_EXCEEDED]';
56
60
  }
@@ -58,31 +62,42 @@ class ToolLogger {
58
62
  return data;
59
63
  }
60
64
  if (typeof data === 'string') {
61
- return data.length > this.MAX_PARAM_LENGTH
62
- ? `${data.substring(0, this.MAX_PARAM_LENGTH)}... (truncated, ${data.length} chars total)`
63
- : data;
65
+ if (data.length > MAX_PARAM_LOG_LENGTH) {
66
+ const lead = data.substring(0, MAX_PARAM_LOG_LENGTH - 10);
67
+ const tail = data.substring(data.length - 10);
68
+ return `${lead}...[${data.length - MAX_PARAM_LOG_LENGTH} truncated]...${tail}`;
69
+ }
70
+ else {
71
+ return data;
72
+ }
64
73
  }
65
74
  if (typeof data === 'number' || typeof data === 'boolean') {
66
75
  return data;
67
76
  }
68
77
  if (Array.isArray(data)) {
69
- const truncated = data.slice(0, this.MAX_ARRAY_ITEMS);
70
- const result = truncated.map((item) => this.redactSensitiveData(item, maxDepth));
71
- if (data.length > this.MAX_ARRAY_ITEMS) {
72
- result.push(`... (${data.length - this.MAX_ARRAY_ITEMS} more items truncated)`);
78
+ const truncated = data.slice(0, MAX_ARRAY_ITEMS);
79
+ const result = truncated.map((item) => this.redactSensitiveDataAndTruncate(item, maxDepth, accumulatedLength));
80
+ if (data.length > MAX_ARRAY_ITEMS) {
81
+ result.push(`... (${data.length - MAX_ARRAY_ITEMS} more items truncated)`);
73
82
  }
74
83
  return result;
75
84
  }
76
85
  if (typeof data === 'object') {
77
86
  const result = {};
78
87
  for (const [key, value] of Object.entries(data)) {
88
+ if (accumulatedLength > MAX_BODY_LOG_LENGTH) {
89
+ break;
90
+ }
79
91
  // Check if this field contains sensitive data
80
92
  const isSensitive = this.isSensitiveField(key);
81
93
  if (isSensitive) {
82
94
  result[key] = '[REDACTED]';
83
95
  }
84
96
  else {
85
- result[key] = this.redactSensitiveData(value, maxDepth - 1);
97
+ result[key] = this.redactSensitiveDataAndTruncate(value, maxDepth - 1, accumulatedLength);
98
+ }
99
+ if (result[key]) {
100
+ accumulatedLength += JSON.stringify(result[key]).length;
86
101
  }
87
102
  }
88
103
  return result;
@@ -103,7 +118,7 @@ class ToolLogger {
103
118
  if (!params) {
104
119
  return null;
105
120
  }
106
- return this.redactSensitiveData(params);
121
+ return this.redactSensitiveDataAndTruncate(params);
107
122
  }
108
123
  /**
109
124
  * Calculates content length of response data
@@ -119,6 +134,87 @@ class ToolLogger {
119
134
  return 'unknown';
120
135
  }
121
136
  }
137
+ /**
138
+ * Extracts the response body as a string or parsed JSON object
139
+ */
140
+ static getResponseBody(response) {
141
+ if (!response) {
142
+ return null;
143
+ }
144
+ try {
145
+ const contentType = response.headers?.get('content-type') || '';
146
+ const isJson = contentType.includes('application/json') || contentType.includes('application/problem+json');
147
+ const isText = contentType.startsWith('text/');
148
+ if (!isJson && !isText) {
149
+ return null;
150
+ }
151
+ // Try to access bodyAsU8Array - this may throw
152
+ const bodyData = response.bodyAsU8Array;
153
+ if (!bodyData) {
154
+ return null;
155
+ }
156
+ // Convert Uint8Array to string
157
+ const bodyString = Buffer.from(bodyData).toString();
158
+ if (!bodyString) {
159
+ return null;
160
+ }
161
+ // Try to parse as JSON if content-type indicates JSON
162
+ if (isJson) {
163
+ try {
164
+ return JSON.parse(bodyString);
165
+ }
166
+ catch {
167
+ // If JSON parsing fails, return as string
168
+ return bodyString;
169
+ }
170
+ }
171
+ // Return as plain text for non-JSON content types
172
+ return bodyString;
173
+ }
174
+ catch {
175
+ return null;
176
+ }
177
+ }
178
+ /**
179
+ * Creates a summary of response body with security redaction and truncation
180
+ * For failed responses (4xx, 5xx): returns full body with redacted sensitive data
181
+ * For successful responses (2xx): returns first 100 chars with redacted sensitive data
182
+ */
183
+ static createResponseBodySummary(response, success) {
184
+ const body = this.getResponseBody(response);
185
+ if (body === null || body === undefined) {
186
+ return null;
187
+ }
188
+ // For objects (parsed JSON), apply redaction
189
+ if (typeof body === 'object') {
190
+ // For failed responses, don't truncate strings within the object
191
+ const redactedBody = this.redactSensitiveDataAndTruncate(body, 5);
192
+ // For successful responses, truncate to first MAX_BODY_LOG_LENGTH chars
193
+ if (success) {
194
+ const bodyString = JSON.stringify(redactedBody);
195
+ if (bodyString.length > MAX_BODY_LOG_LENGTH) {
196
+ const truncated = bodyString.substring(0, MAX_BODY_LOG_LENGTH);
197
+ return `${truncated}... (truncated)`;
198
+ }
199
+ return redactedBody;
200
+ }
201
+ // For failed responses, return full redacted body
202
+ return redactedBody;
203
+ }
204
+ // For strings (plain text or unparseable JSON)
205
+ if (typeof body === 'string') {
206
+ // For successful responses, truncate to first 100 chars
207
+ if (success) {
208
+ if (body.length > MAX_BODY_LOG_LENGTH) {
209
+ return `${body.substring(0, MAX_BODY_LOG_LENGTH)}... (truncated)`;
210
+ }
211
+ return body;
212
+ }
213
+ // For failed responses, return full body
214
+ return body;
215
+ }
216
+ return body;
217
+ }
122
218
  /**
123
219
  * Logs an incoming request
124
220
  */
@@ -137,6 +233,7 @@ class ToolLogger {
137
233
  * Logs a successful response
138
234
  */
139
235
  static logResponse(req, response, processingTimeMs) {
236
+ const success = response.status >= 200 && response.status < 300;
140
237
  const responseLog = {
141
238
  event: 'opal_tool_response',
142
239
  path: req.path,
@@ -144,8 +241,12 @@ class ToolLogger {
144
241
  status: response.status,
145
242
  contentType: response.headers?.get('content-type') || 'unknown',
146
243
  contentLength: this.calculateContentLength(response),
147
- success: response.status >= 200 && response.status < 300
244
+ success
148
245
  };
246
+ const responseBodySummary = this.createResponseBodySummary(response, success);
247
+ if (responseBodySummary) {
248
+ responseLog.responseBody = responseBodySummary;
249
+ }
149
250
  // Log with Zaius audience so developers only see requests for accounts they have access to
150
251
  app_sdk_1.logger.info(app_sdk_1.LogVisibility.Zaius, JSON.stringify(responseLog));
151
252
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ToolLogger.js","sourceRoot":"","sources":["../../src/logging/ToolLogger.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ToolLogger.js","sourceRoot":"","sources":["../../src/logging/ToolLogger.ts"],"names":[],"mappings":"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"}
@@ -75,6 +75,22 @@ describe('ToolLogger', () => {
75
75
  });
76
76
  return response;
77
77
  };
78
+ const createMockResponseWithBody = (status, bodyData, contentType) => {
79
+ const mockHeaders = {
80
+ get: jest.fn().mockReturnValue(contentType)
81
+ };
82
+ const response = {
83
+ status,
84
+ headers: mockHeaders
85
+ };
86
+ Object.defineProperty(response, 'bodyAsU8Array', {
87
+ get() {
88
+ return bodyData;
89
+ },
90
+ enumerable: true
91
+ });
92
+ return response;
93
+ };
78
94
  describe('logRequest', () => {
79
95
  it('should log request with parameters', () => {
80
96
  const req = createMockRequest();
@@ -206,7 +222,7 @@ describe('ToolLogger', () => {
206
222
  path: '/test-tool',
207
223
  method: 'POST',
208
224
  parameters: {
209
- description: `${'a'.repeat(100)}... (truncated, 150 chars total)`,
225
+ description: `${'a'.repeat(118)}...[22 truncated]...${'a'.repeat(10)}`,
210
226
  short_field: 'normal'
211
227
  }
212
228
  });
@@ -228,8 +244,8 @@ describe('ToolLogger', () => {
228
244
  method: 'POST',
229
245
  parameters: {
230
246
  items: [
231
- ...largeArray.slice(0, 10),
232
- '... (5 more items truncated)'
247
+ ...largeArray.slice(0, 2),
248
+ '... (13 more items truncated)'
233
249
  ],
234
250
  small_array: ['a', 'b']
235
251
  }
@@ -367,6 +383,19 @@ describe('ToolLogger', () => {
367
383
  // Should not throw error or cause infinite recursion
368
384
  expect(() => ToolLogger_1.ToolLogger.logRequest(req)).not.toThrow();
369
385
  expect(mockLogger.info).toHaveBeenCalled();
386
+ // Verify that deeply nested parts are replaced with placeholder
387
+ const logCall = mockLogger.info.mock.calls[0];
388
+ const loggedData = JSON.parse(logCall[1]);
389
+ // Navigate to a deeply nested level that should be truncated
390
+ // At maxDepth=5, objects beyond depth 5 should be replaced
391
+ const nested1 = loggedData.parameters.deep.nested;
392
+ expect(nested1).toBeDefined(); // depth 2
393
+ const nested2 = nested1.nested;
394
+ expect(nested2).toBeDefined(); // depth 3
395
+ const nested3 = nested2.nested;
396
+ expect(nested3).toBeDefined(); // depth 4
397
+ const nested4 = nested3.nested;
398
+ expect(nested4).toBe('[MAX_DEPTH_EXCEEDED]'); // depth 5, should be truncated
370
399
  });
371
400
  it('should replace deeply nested objects with MAX_DEPTH_EXCEEDED placeholder', () => {
372
401
  // Create an object with exactly 6 levels (exceeds maxDepth of 5)
@@ -406,6 +435,7 @@ describe('ToolLogger', () => {
406
435
  level1: {
407
436
  items: [
408
437
  {
438
+ shallow: 'data',
409
439
  level2: {
410
440
  level3: {
411
441
  level4: {
@@ -441,8 +471,11 @@ describe('ToolLogger', () => {
441
471
  const logCall = mockLogger.info.mock.calls[0];
442
472
  const loggedData = JSON.parse(logCall[1]);
443
473
  const items = loggedData.parameters.level1.items;
474
+ expect(items.length).toBe(3);
444
475
  // First item: deeply nested object with inner parts replaced by placeholder
476
+ // shallow object should be processed normally
445
477
  expect(items[0]).toEqual({
478
+ shallow: 'data',
446
479
  level2: {
447
480
  level3: {
448
481
  level4: '[MAX_DEPTH_EXCEEDED]'
@@ -451,16 +484,6 @@ describe('ToolLogger', () => {
451
484
  });
452
485
  // Second item: simple string should remain unchanged
453
486
  expect(items[1]).toBe('simple-item');
454
- // Third item: shallow object should be processed normally
455
- expect(items[2]).toEqual({ shallow: 'data' });
456
- // Fourth item: another deeply nested object with inner parts replaced by placeholder
457
- expect(items[3]).toEqual({
458
- level2: {
459
- level3: {
460
- level4: '[MAX_DEPTH_EXCEEDED]'
461
- }
462
- }
463
- });
464
487
  });
465
488
  });
466
489
  describe('logResponse', () => {
@@ -475,7 +498,8 @@ describe('ToolLogger', () => {
475
498
  status: 200,
476
499
  contentType: 'application/json',
477
500
  contentLength: 34, // JSON.stringify({ result: 'success', data: 'test' }).length
478
- success: true
501
+ success: true,
502
+ responseBody: { result: 'success', data: 'test' }
479
503
  };
480
504
  expect(mockLogger.info).toHaveBeenCalledWith(app_sdk_1.LogVisibility.Zaius, JSON.stringify(expectedLog));
481
505
  });
@@ -490,13 +514,13 @@ describe('ToolLogger', () => {
490
514
  status: 400,
491
515
  contentType: 'application/json',
492
516
  contentLength: 23,
493
- success: false
517
+ success: false,
518
+ responseBody: { error: 'Bad request' }
494
519
  });
495
520
  });
496
- it('should handle response without bodyJSON', () => {
521
+ it('should handle response without body data', () => {
497
522
  const req = createMockRequest();
498
- const response = createMockResponse(204);
499
- response.bodyJSON = undefined;
523
+ const response = createMockResponseWithBody(204, undefined, 'application/json');
500
524
  ToolLogger_1.ToolLogger.logResponse(req, response);
501
525
  expectJsonLog({
502
526
  event: 'opal_tool_response',
@@ -517,10 +541,11 @@ describe('ToolLogger', () => {
517
541
  status: 200,
518
542
  contentType: 'application/json',
519
543
  contentLength: 15,
520
- success: true
544
+ success: true,
545
+ responseBody: { data: 'test' }
521
546
  });
522
547
  });
523
- it('should handle unknown content type', () => {
548
+ it('should handle unknown content type - response body not logged', () => {
524
549
  const req = createMockRequest();
525
550
  const response = createMockResponse(200, { data: 'test' });
526
551
  response.headers.get = jest.fn().mockReturnValue(null);
@@ -536,18 +561,29 @@ describe('ToolLogger', () => {
536
561
  });
537
562
  it('should handle content length calculation error', () => {
538
563
  const req = createMockRequest();
539
- const circularObj = { name: 'test' };
540
- circularObj.self = circularObj; // Create circular reference
541
- const response = createMockResponse(200, circularObj);
542
- ToolLogger_1.ToolLogger.logResponse(req, response);
543
- expectJsonLog({
544
- event: 'opal_tool_response',
545
- path: '/test-tool',
564
+ // Simulate a response that will cause errors when trying to calculate content length
565
+ // by providing a Uint8Array but the underlying data causes issues
566
+ const mockHeaders = {
567
+ get: jest.fn().mockReturnValue('application/json')
568
+ };
569
+ const response = {
546
570
  status: 200,
547
- contentType: 'application/json',
548
- contentLength: 'unknown',
549
- success: true
571
+ headers: mockHeaders
572
+ };
573
+ // Create a getter that throws when accessed (simulating serialization error)
574
+ Object.defineProperty(response, 'bodyAsU8Array', {
575
+ get() {
576
+ throw new Error('Circular structure');
577
+ },
578
+ enumerable: true
550
579
  });
580
+ ToolLogger_1.ToolLogger.logResponse(req, response);
581
+ // The error causes both contentLength and responseBody to fail gracefully
582
+ const logCall = mockLogger.info.mock.calls[0];
583
+ const loggedData = JSON.parse(logCall[1]);
584
+ expect(loggedData.event).toBe('opal_tool_response');
585
+ expect(loggedData.contentLength).toBe('unknown');
586
+ expect(loggedData.responseBody).toBeUndefined();
551
587
  });
552
588
  it('should correctly identify success status codes', () => {
553
589
  const req = createMockRequest();
@@ -565,38 +601,125 @@ describe('ToolLogger', () => {
565
601
  mockLogger.info.mockClear();
566
602
  const response = createMockResponse(status);
567
603
  ToolLogger_1.ToolLogger.logResponse(req, response);
568
- expectJsonLog({
569
- event: 'opal_tool_response',
570
- path: '/test-tool',
571
- status,
572
- contentType: 'application/json',
573
- contentLength: 2,
574
- success: expected
575
- });
604
+ const logCall = mockLogger.info.mock.calls[0];
605
+ const loggedData = JSON.parse(logCall[1]);
606
+ expect(loggedData.event).toBe('opal_tool_response');
607
+ expect(loggedData.path).toBe('/test-tool');
608
+ expect(loggedData.status).toBe(status);
609
+ expect(loggedData.contentType).toBe('application/json');
610
+ expect(loggedData.contentLength).toBe(2);
611
+ expect(loggedData.success).toBe(expected);
612
+ expect(loggedData.responseBody).toEqual({});
576
613
  });
577
614
  });
578
615
  it('should handle different content types', () => {
579
616
  const req = createMockRequest();
580
617
  const testCases = [
581
- 'application/json',
582
- 'text/plain',
583
- 'application/xml',
584
- 'text/html'
618
+ { contentType: 'application/json', expectedBody: { data: 'test' } },
619
+ { contentType: 'text/plain', expectedBody: '{"data":"test"}' },
620
+ { contentType: 'text/html', expectedBody: '{"data":"test"}' }
585
621
  ];
586
- testCases.forEach((contentType) => {
622
+ testCases.forEach(({ contentType, expectedBody }) => {
587
623
  mockLogger.info.mockClear();
588
624
  const response = createMockResponse(200, { data: 'test' });
589
625
  response.headers.get = jest.fn().mockReturnValue(contentType);
590
626
  ToolLogger_1.ToolLogger.logResponse(req, response);
591
- expectJsonLog({
592
- event: 'opal_tool_response',
593
- path: '/test-tool',
594
- status: 200,
595
- contentType,
596
- contentLength: 15,
597
- success: true
598
- });
627
+ const logCall = mockLogger.info.mock.calls[0];
628
+ const loggedData = JSON.parse(logCall[1]);
629
+ expect(loggedData.event).toBe('opal_tool_response');
630
+ expect(loggedData.path).toBe('/test-tool');
631
+ expect(loggedData.status).toBe(200);
632
+ expect(loggedData.contentType).toBe(contentType);
633
+ expect(loggedData.contentLength).toBe(15);
634
+ expect(loggedData.success).toBe(true);
635
+ expect(loggedData.responseBody).toEqual(expectedBody);
636
+ });
637
+ });
638
+ it('should log short successful response body', () => {
639
+ const req = createMockRequest();
640
+ const response = createMockResponse(200, { result: 'success', data: 'test' });
641
+ ToolLogger_1.ToolLogger.logResponse(req, response);
642
+ const logCall = mockLogger.info.mock.calls[0];
643
+ const loggedData = JSON.parse(logCall[1]);
644
+ expect(loggedData.responseBody).toEqual({ result: 'success', data: 'test' });
645
+ expect(loggedData.success).toBe(true);
646
+ });
647
+ it('should truncate long successful response body to 256 chars', () => {
648
+ const req = createMockRequest();
649
+ const longData = 'a'.repeat(300);
650
+ const response = createMockResponse(200, { message: longData });
651
+ ToolLogger_1.ToolLogger.logResponse(req, response);
652
+ const logCall = mockLogger.info.mock.calls[0];
653
+ const loggedData = JSON.parse(logCall[1]);
654
+ // The response body should be truncated when stringified
655
+ expect(loggedData.responseBody.message).toContain(' truncated]...');
656
+ });
657
+ it('truncates long properties of failed responses', () => {
658
+ const req = createMockRequest();
659
+ const longData = 'a'.repeat(150);
660
+ const response = createMockResponse(400, { error: 'Bad request', details: longData });
661
+ ToolLogger_1.ToolLogger.logResponse(req, response);
662
+ const logCall = mockLogger.info.mock.calls[0];
663
+ const loggedData = JSON.parse(logCall[1]);
664
+ // Failed responses should include full body, not truncated
665
+ expect(loggedData.responseBody.error).toEqual('Bad request');
666
+ expect(loggedData.responseBody.details).toContain(' truncated]...');
667
+ expect(loggedData.success).toBe(false);
668
+ });
669
+ it('should redact sensitive data in response body', () => {
670
+ const req = createMockRequest();
671
+ const response = createMockResponse(200, {
672
+ user: 'john',
673
+ password: 'secret123',
674
+ api_key: 'key456',
675
+ data: 'public'
599
676
  });
677
+ ToolLogger_1.ToolLogger.logResponse(req, response);
678
+ const logCall = mockLogger.info.mock.calls[0];
679
+ const loggedData = JSON.parse(logCall[1]);
680
+ expect(loggedData.responseBody).toEqual({
681
+ user: 'john',
682
+ password: '[REDACTED]',
683
+ api_key: '[REDACTED]',
684
+ data: 'public'
685
+ });
686
+ });
687
+ it('should handle response with no body', () => {
688
+ const req = createMockRequest();
689
+ const response = createMockResponseWithBody(204, undefined, 'application/json');
690
+ ToolLogger_1.ToolLogger.logResponse(req, response);
691
+ const logCall = mockLogger.info.mock.calls[0];
692
+ const loggedData = JSON.parse(logCall[1]);
693
+ expect(loggedData.responseBody).toBeUndefined();
694
+ expect(loggedData.success).toBe(true);
695
+ });
696
+ it('should handle plain text response body', () => {
697
+ const req = createMockRequest();
698
+ const plainText = 'This is a plain text response';
699
+ const response = createMockResponseWithBody(200, new Uint8Array(Buffer.from(plainText)), 'text/plain');
700
+ ToolLogger_1.ToolLogger.logResponse(req, response);
701
+ const logCall = mockLogger.info.mock.calls[0];
702
+ const loggedData = JSON.parse(logCall[1]);
703
+ expect(loggedData.responseBody).toBe(plainText);
704
+ });
705
+ it('should truncate long plain text successful responses', () => {
706
+ const req = createMockRequest();
707
+ const longText = 'a'.repeat(300);
708
+ const response = createMockResponseWithBody(200, new Uint8Array(Buffer.from(longText)), 'text/plain');
709
+ ToolLogger_1.ToolLogger.logResponse(req, response);
710
+ const logCall = mockLogger.info.mock.calls[0];
711
+ const loggedData = JSON.parse(logCall[1]);
712
+ expect(loggedData.responseBody).toBe(`${'a'.repeat(256)}... (truncated)`);
713
+ });
714
+ it('should not truncate long plain text failed responses', () => {
715
+ const req = createMockRequest();
716
+ const longText = 'a'.repeat(150);
717
+ const response = createMockResponseWithBody(500, new Uint8Array(Buffer.from(longText)), 'text/plain');
718
+ ToolLogger_1.ToolLogger.logResponse(req, response);
719
+ const logCall = mockLogger.info.mock.calls[0];
720
+ const loggedData = JSON.parse(logCall[1]);
721
+ expect(loggedData.responseBody).toBe(longText);
722
+ expect(loggedData.success).toBe(false);
600
723
  });
601
724
  });
602
725
  describe('edge cases', () => {
@@ -637,7 +760,7 @@ describe('ToolLogger', () => {
637
760
  string: 'text',
638
761
  number: 42,
639
762
  boolean: true,
640
- array: [1, 2, 3],
763
+ array: [1, 2],
641
764
  object: { nested: 'value' },
642
765
  nullValue: null,
643
766
  password: 'secret'
@@ -653,7 +776,7 @@ describe('ToolLogger', () => {
653
776
  string: 'text',
654
777
  number: 42,
655
778
  boolean: true,
656
- array: [1, 2, 3],
779
+ array: [1, 2],
657
780
  object: { nested: 'value' },
658
781
  nullValue: null,
659
782
  password: '[REDACTED]'
@@ -729,24 +852,7 @@ describe('ToolLogger', () => {
729
852
  description: 'OVERRIDDEN: Enhanced minimum detectable effect calculation',
730
853
  required: true
731
854
  },
732
- {
733
- name: 'sigLevel',
734
- type: 'number',
735
- description: 'OVERRIDDEN: Enhanced statistical significance level',
736
- required: true
737
- },
738
- {
739
- name: 'numVariations',
740
- type: 'number',
741
- description: 'OVERRIDDEN: Enhanced number of variations handling',
742
- required: true
743
- },
744
- {
745
- name: 'dailyVisitors',
746
- type: 'number',
747
- description: 'OVERRIDDEN: Enhanced daily visitor count with forecasting',
748
- required: true
749
- }
855
+ '... (3 more items truncated)'
750
856
  ]
751
857
  }
752
858
  ]