@optimizely-opal/opal-tool-ocp-sdk 1.0.0-OCP-1441.4 → 1.0.0-OCP-1442.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.
- package/README.md +72 -0
- package/dist/function/GlobalToolFunction.d.ts +1 -0
- package/dist/function/GlobalToolFunction.d.ts.map +1 -1
- package/dist/function/GlobalToolFunction.js +8 -0
- package/dist/function/GlobalToolFunction.js.map +1 -1
- package/dist/function/GlobalToolFunction.test.js +3 -0
- package/dist/function/GlobalToolFunction.test.js.map +1 -1
- package/dist/function/ToolFunction.d.ts +7 -1
- package/dist/function/ToolFunction.d.ts.map +1 -1
- package/dist/function/ToolFunction.js +16 -2
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.test.d.ts +2 -0
- package/dist/function/ToolFunction.test.d.ts.map +1 -0
- package/dist/function/ToolFunction.test.js +317 -0
- package/dist/function/ToolFunction.test.js.map +1 -0
- package/dist/logging/ToolLogger.d.ts +34 -0
- package/dist/logging/ToolLogger.d.ts.map +1 -0
- package/dist/logging/ToolLogger.js +151 -0
- package/dist/logging/ToolLogger.js.map +1 -0
- package/dist/logging/ToolLogger.test.d.ts +2 -0
- package/dist/logging/ToolLogger.test.d.ts.map +1 -0
- package/dist/logging/ToolLogger.test.js +533 -0
- package/dist/logging/ToolLogger.test.js.map +1 -0
- package/package.json +1 -1
- package/src/function/GlobalToolFunction.test.ts +3 -0
- package/src/function/GlobalToolFunction.ts +11 -0
- package/src/function/ToolFunction.test.ts +377 -0
- package/src/function/ToolFunction.ts +21 -2
- package/src/logging/ToolLogger.test.ts +623 -0
- package/src/logging/ToolLogger.ts +175 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import { ToolLogger } from './ToolLogger';
|
|
2
|
+
import { logger, LogVisibility } from '@zaiusinc/app-sdk';
|
|
3
|
+
import * as App from '@zaiusinc/app-sdk';
|
|
4
|
+
|
|
5
|
+
// Mock the logger
|
|
6
|
+
jest.mock('@zaiusinc/app-sdk', () => ({
|
|
7
|
+
logger: {
|
|
8
|
+
info: jest.fn()
|
|
9
|
+
},
|
|
10
|
+
LogVisibility: {
|
|
11
|
+
Zaius: 'zaius'
|
|
12
|
+
},
|
|
13
|
+
Headers: jest.fn(),
|
|
14
|
+
Response: jest.fn()
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe('ToolLogger', () => {
|
|
18
|
+
const mockLogger = logger as jest.Mocked<typeof logger>;
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Helper function to check JSON string logs
|
|
25
|
+
const expectJsonLog = (expectedData: any) => {
|
|
26
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
27
|
+
LogVisibility.Zaius,
|
|
28
|
+
JSON.stringify(expectedData)
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const createMockRequest = (overrides: any = {}): App.Request => {
|
|
33
|
+
const defaultRequest = {
|
|
34
|
+
path: '/test-tool',
|
|
35
|
+
bodyJSON: {
|
|
36
|
+
parameters: {
|
|
37
|
+
name: 'test',
|
|
38
|
+
value: 'data'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
headers: {
|
|
42
|
+
get: jest.fn().mockReturnValue('application/json')
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return { ...defaultRequest, ...overrides };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const createMockResponse = (status = 200, bodyJSON: any = {}, headers: any = {}): App.Response => {
|
|
50
|
+
const mockHeaders = {
|
|
51
|
+
get: jest.fn().mockImplementation((name: string) => {
|
|
52
|
+
if (name === 'content-type') return 'application/json';
|
|
53
|
+
return headers[name] || null;
|
|
54
|
+
})
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
status,
|
|
59
|
+
bodyJSON,
|
|
60
|
+
headers: mockHeaders
|
|
61
|
+
} as any;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
describe('logRequest', () => {
|
|
65
|
+
it('should log request with parameters', () => {
|
|
66
|
+
const req = createMockRequest();
|
|
67
|
+
|
|
68
|
+
ToolLogger.logRequest(req);
|
|
69
|
+
|
|
70
|
+
const expectedLog = {
|
|
71
|
+
event: 'opal_tool_request',
|
|
72
|
+
path: '/test-tool',
|
|
73
|
+
parameters: {
|
|
74
|
+
name: 'test',
|
|
75
|
+
value: 'data'
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
80
|
+
LogVisibility.Zaius,
|
|
81
|
+
JSON.stringify(expectedLog)
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should handle request without parameters', () => {
|
|
86
|
+
const req = createMockRequest({
|
|
87
|
+
bodyJSON: null
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
ToolLogger.logRequest(req);
|
|
91
|
+
|
|
92
|
+
expectJsonLog({
|
|
93
|
+
event: 'opal_tool_request',
|
|
94
|
+
path: '/test-tool',
|
|
95
|
+
parameters: null
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should use bodyJSON as parameters when no parameters field exists', () => {
|
|
100
|
+
const req = createMockRequest({
|
|
101
|
+
bodyJSON: {
|
|
102
|
+
name: 'direct',
|
|
103
|
+
action: 'test'
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
ToolLogger.logRequest(req);
|
|
108
|
+
|
|
109
|
+
expectJsonLog({
|
|
110
|
+
event: 'opal_tool_request',
|
|
111
|
+
path: '/test-tool',
|
|
112
|
+
parameters: {
|
|
113
|
+
name: 'direct',
|
|
114
|
+
action: 'test'
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should redact all sensitive field variations', () => {
|
|
120
|
+
const req = createMockRequest({
|
|
121
|
+
bodyJSON: {
|
|
122
|
+
parameters: {
|
|
123
|
+
username: 'john',
|
|
124
|
+
password: 'secret123',
|
|
125
|
+
api_key: 'key123',
|
|
126
|
+
secret: 'mysecret',
|
|
127
|
+
token: 'abc123',
|
|
128
|
+
auth: 'authdata',
|
|
129
|
+
credentials: 'creds',
|
|
130
|
+
access_token: 'access123',
|
|
131
|
+
refresh_token: 'refresh123',
|
|
132
|
+
private_key: 'privatekey',
|
|
133
|
+
client_secret: 'clientsecret',
|
|
134
|
+
normal_field: 'visible'
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
ToolLogger.logRequest(req);
|
|
140
|
+
|
|
141
|
+
expectJsonLog({
|
|
142
|
+
event: 'opal_tool_request',
|
|
143
|
+
path: '/test-tool',
|
|
144
|
+
parameters: {
|
|
145
|
+
username: 'john',
|
|
146
|
+
password: '[REDACTED]',
|
|
147
|
+
api_key: '[REDACTED]',
|
|
148
|
+
secret: '[REDACTED]',
|
|
149
|
+
token: '[REDACTED]',
|
|
150
|
+
auth: '[REDACTED]',
|
|
151
|
+
credentials: '[REDACTED]',
|
|
152
|
+
access_token: '[REDACTED]',
|
|
153
|
+
refresh_token: '[REDACTED]',
|
|
154
|
+
private_key: '[REDACTED]',
|
|
155
|
+
client_secret: '[REDACTED]',
|
|
156
|
+
normal_field: 'visible'
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should redact sensitive fields with case variations', () => {
|
|
162
|
+
const req = createMockRequest({
|
|
163
|
+
bodyJSON: {
|
|
164
|
+
parameters: {
|
|
165
|
+
PASSWORD: 'secret1',
|
|
166
|
+
API_KEY: 'secret2',
|
|
167
|
+
clientSecret: 'secret3',
|
|
168
|
+
user_password: 'secret4',
|
|
169
|
+
oauth_token: 'secret5',
|
|
170
|
+
ssh_key: 'secret6',
|
|
171
|
+
normal_field: 'visible'
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
ToolLogger.logRequest(req);
|
|
177
|
+
|
|
178
|
+
expectJsonLog({
|
|
179
|
+
event: 'opal_tool_request',
|
|
180
|
+
path: '/test-tool',
|
|
181
|
+
parameters: {
|
|
182
|
+
PASSWORD: '[REDACTED]',
|
|
183
|
+
API_KEY: '[REDACTED]',
|
|
184
|
+
clientSecret: '[REDACTED]',
|
|
185
|
+
user_password: '[REDACTED]',
|
|
186
|
+
oauth_token: '[REDACTED]',
|
|
187
|
+
ssh_key: '[REDACTED]',
|
|
188
|
+
normal_field: 'visible'
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should truncate long string values', () => {
|
|
194
|
+
const longString = 'a'.repeat(150);
|
|
195
|
+
const req = createMockRequest({
|
|
196
|
+
bodyJSON: {
|
|
197
|
+
parameters: {
|
|
198
|
+
description: longString,
|
|
199
|
+
short_field: 'normal'
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
ToolLogger.logRequest(req);
|
|
205
|
+
|
|
206
|
+
expectJsonLog({
|
|
207
|
+
event: 'opal_tool_request',
|
|
208
|
+
path: '/test-tool',
|
|
209
|
+
parameters: {
|
|
210
|
+
description: `${'a'.repeat(100)}... (truncated, 150 chars total)`,
|
|
211
|
+
short_field: 'normal'
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should truncate large arrays', () => {
|
|
217
|
+
const largeArray = Array.from({ length: 15 }, (_, i) => `item${i}`);
|
|
218
|
+
const req = createMockRequest({
|
|
219
|
+
bodyJSON: {
|
|
220
|
+
parameters: {
|
|
221
|
+
items: largeArray,
|
|
222
|
+
small_array: ['a', 'b']
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
ToolLogger.logRequest(req);
|
|
228
|
+
|
|
229
|
+
expectJsonLog({
|
|
230
|
+
event: 'opal_tool_request',
|
|
231
|
+
path: '/test-tool',
|
|
232
|
+
parameters: {
|
|
233
|
+
items: [
|
|
234
|
+
...largeArray.slice(0, 10),
|
|
235
|
+
'... (5 more items truncated)'
|
|
236
|
+
],
|
|
237
|
+
small_array: ['a', 'b']
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should handle nested objects with sensitive fields', () => {
|
|
243
|
+
const req = createMockRequest({
|
|
244
|
+
bodyJSON: {
|
|
245
|
+
parameters: {
|
|
246
|
+
user: {
|
|
247
|
+
name: 'John',
|
|
248
|
+
email: 'john@example.com',
|
|
249
|
+
password: 'secret123'
|
|
250
|
+
},
|
|
251
|
+
config: {
|
|
252
|
+
database: {
|
|
253
|
+
host: 'localhost',
|
|
254
|
+
port: 5432,
|
|
255
|
+
password: 'dbpass'
|
|
256
|
+
},
|
|
257
|
+
api_key: 'apikey123'
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
ToolLogger.logRequest(req);
|
|
264
|
+
|
|
265
|
+
expectJsonLog({
|
|
266
|
+
event: 'opal_tool_request',
|
|
267
|
+
path: '/test-tool',
|
|
268
|
+
parameters: {
|
|
269
|
+
user: {
|
|
270
|
+
name: 'John',
|
|
271
|
+
email: '[REDACTED]',
|
|
272
|
+
password: '[REDACTED]'
|
|
273
|
+
},
|
|
274
|
+
config: {
|
|
275
|
+
database: {
|
|
276
|
+
host: 'localhost',
|
|
277
|
+
port: 5432,
|
|
278
|
+
password: '[REDACTED]'
|
|
279
|
+
},
|
|
280
|
+
api_key: '[REDACTED]'
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should handle null and undefined values', () => {
|
|
287
|
+
const req = createMockRequest({
|
|
288
|
+
bodyJSON: {
|
|
289
|
+
parameters: {
|
|
290
|
+
nullValue: null,
|
|
291
|
+
undefinedValue: undefined,
|
|
292
|
+
emptyString: '',
|
|
293
|
+
zero: 0,
|
|
294
|
+
false: false,
|
|
295
|
+
password: null // sensitive field with null value
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
ToolLogger.logRequest(req);
|
|
301
|
+
|
|
302
|
+
expectJsonLog({
|
|
303
|
+
event: 'opal_tool_request',
|
|
304
|
+
path: '/test-tool',
|
|
305
|
+
parameters: {
|
|
306
|
+
nullValue: null,
|
|
307
|
+
emptyString: '',
|
|
308
|
+
zero: 0,
|
|
309
|
+
false: false,
|
|
310
|
+
password: '[REDACTED]'
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should handle arrays in sensitive fields', () => {
|
|
316
|
+
const req = createMockRequest({
|
|
317
|
+
bodyJSON: {
|
|
318
|
+
parameters: {
|
|
319
|
+
credentials: ['user', 'pass', 'token'],
|
|
320
|
+
public_list: ['item1', 'item2']
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
ToolLogger.logRequest(req);
|
|
326
|
+
|
|
327
|
+
expectJsonLog({
|
|
328
|
+
event: 'opal_tool_request',
|
|
329
|
+
path: '/test-tool',
|
|
330
|
+
parameters: {
|
|
331
|
+
credentials: '[REDACTED]',
|
|
332
|
+
public_list: ['item1', 'item2']
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should handle objects in sensitive fields', () => {
|
|
338
|
+
const req = createMockRequest({
|
|
339
|
+
bodyJSON: {
|
|
340
|
+
parameters: {
|
|
341
|
+
auth: {
|
|
342
|
+
username: 'john',
|
|
343
|
+
password: 'secret'
|
|
344
|
+
},
|
|
345
|
+
public_config: {
|
|
346
|
+
timeout: 30,
|
|
347
|
+
retries: 3
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
ToolLogger.logRequest(req);
|
|
354
|
+
|
|
355
|
+
expectJsonLog({
|
|
356
|
+
event: 'opal_tool_request',
|
|
357
|
+
path: '/test-tool',
|
|
358
|
+
parameters: {
|
|
359
|
+
auth: '[REDACTED]',
|
|
360
|
+
public_config: {
|
|
361
|
+
timeout: 30,
|
|
362
|
+
retries: 3
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should respect max depth to prevent infinite recursion', () => {
|
|
369
|
+
const deepObject: any = { level: 0, data: 'test' };
|
|
370
|
+
let current = deepObject;
|
|
371
|
+
|
|
372
|
+
// Create a very deep nested object (deeper than maxDepth)
|
|
373
|
+
for (let i = 1; i <= 10; i++) {
|
|
374
|
+
current.nested = { level: i, data: `level${i}` };
|
|
375
|
+
current = current.nested;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const req = createMockRequest({
|
|
379
|
+
bodyJSON: { parameters: { deep: deepObject } }
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Should not throw error or cause infinite recursion
|
|
383
|
+
expect(() => ToolLogger.logRequest(req)).not.toThrow();
|
|
384
|
+
expect(mockLogger.info).toHaveBeenCalled();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe('logResponse', () => {
|
|
389
|
+
it('should log successful response with all details', () => {
|
|
390
|
+
const req = createMockRequest();
|
|
391
|
+
const response = createMockResponse(200, { result: 'success', data: 'test' });
|
|
392
|
+
|
|
393
|
+
ToolLogger.logResponse(req, response, 150);
|
|
394
|
+
|
|
395
|
+
const expectedLog = {
|
|
396
|
+
event: 'opal_tool_response',
|
|
397
|
+
path: '/test-tool',
|
|
398
|
+
duration: '150ms',
|
|
399
|
+
status: 200,
|
|
400
|
+
contentType: 'application/json',
|
|
401
|
+
contentLength: 34, // JSON.stringify({ result: 'success', data: 'test' }).length
|
|
402
|
+
success: true
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
406
|
+
LogVisibility.Zaius,
|
|
407
|
+
JSON.stringify(expectedLog)
|
|
408
|
+
);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('should log error response', () => {
|
|
412
|
+
const req = createMockRequest();
|
|
413
|
+
const response = createMockResponse(400, { error: 'Bad request' });
|
|
414
|
+
|
|
415
|
+
ToolLogger.logResponse(req, response, 75);
|
|
416
|
+
|
|
417
|
+
expectJsonLog({
|
|
418
|
+
event: 'opal_tool_response',
|
|
419
|
+
path: '/test-tool',
|
|
420
|
+
duration: '75ms',
|
|
421
|
+
status: 400,
|
|
422
|
+
contentType: 'application/json',
|
|
423
|
+
contentLength: 23,
|
|
424
|
+
success: false
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('should handle response without bodyJSON', () => {
|
|
429
|
+
const req = createMockRequest();
|
|
430
|
+
const response = createMockResponse(204);
|
|
431
|
+
response.bodyJSON = undefined;
|
|
432
|
+
|
|
433
|
+
ToolLogger.logResponse(req, response);
|
|
434
|
+
|
|
435
|
+
expectJsonLog({
|
|
436
|
+
event: 'opal_tool_response',
|
|
437
|
+
path: '/test-tool',
|
|
438
|
+
status: 204,
|
|
439
|
+
contentType: 'application/json',
|
|
440
|
+
contentLength: 0,
|
|
441
|
+
success: true
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('should handle response without processing time', () => {
|
|
446
|
+
const req = createMockRequest();
|
|
447
|
+
const response = createMockResponse(200, { data: 'test' });
|
|
448
|
+
|
|
449
|
+
ToolLogger.logResponse(req, response);
|
|
450
|
+
|
|
451
|
+
expectJsonLog({
|
|
452
|
+
event: 'opal_tool_response',
|
|
453
|
+
path: '/test-tool',
|
|
454
|
+
status: 200,
|
|
455
|
+
contentType: 'application/json',
|
|
456
|
+
contentLength: 15,
|
|
457
|
+
success: true
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should handle unknown content type', () => {
|
|
462
|
+
const req = createMockRequest();
|
|
463
|
+
const response = createMockResponse(200, { data: 'test' });
|
|
464
|
+
response.headers.get = jest.fn().mockReturnValue(null);
|
|
465
|
+
|
|
466
|
+
ToolLogger.logResponse(req, response);
|
|
467
|
+
|
|
468
|
+
expectJsonLog({
|
|
469
|
+
event: 'opal_tool_response',
|
|
470
|
+
path: '/test-tool',
|
|
471
|
+
status: 200,
|
|
472
|
+
contentType: 'unknown',
|
|
473
|
+
contentLength: 15,
|
|
474
|
+
success: true
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should handle content length calculation error', () => {
|
|
479
|
+
const req = createMockRequest();
|
|
480
|
+
const circularObj: any = { name: 'test' };
|
|
481
|
+
circularObj.self = circularObj; // Create circular reference
|
|
482
|
+
|
|
483
|
+
const response = createMockResponse(200, circularObj);
|
|
484
|
+
|
|
485
|
+
ToolLogger.logResponse(req, response);
|
|
486
|
+
|
|
487
|
+
expectJsonLog({
|
|
488
|
+
event: 'opal_tool_response',
|
|
489
|
+
path: '/test-tool',
|
|
490
|
+
status: 200,
|
|
491
|
+
contentType: 'application/json',
|
|
492
|
+
contentLength: 'unknown',
|
|
493
|
+
success: true
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('should correctly identify success status codes', () => {
|
|
498
|
+
const req = createMockRequest();
|
|
499
|
+
|
|
500
|
+
const testCases = [
|
|
501
|
+
{ status: 200, expected: true },
|
|
502
|
+
{ status: 201, expected: true },
|
|
503
|
+
{ status: 204, expected: true },
|
|
504
|
+
{ status: 299, expected: true },
|
|
505
|
+
{ status: 300, expected: false },
|
|
506
|
+
{ status: 400, expected: false },
|
|
507
|
+
{ status: 404, expected: false },
|
|
508
|
+
{ status: 500, expected: false }
|
|
509
|
+
];
|
|
510
|
+
|
|
511
|
+
testCases.forEach(({ status, expected }) => {
|
|
512
|
+
mockLogger.info.mockClear();
|
|
513
|
+
const response = createMockResponse(status);
|
|
514
|
+
ToolLogger.logResponse(req, response);
|
|
515
|
+
|
|
516
|
+
expectJsonLog({
|
|
517
|
+
event: 'opal_tool_response',
|
|
518
|
+
path: '/test-tool',
|
|
519
|
+
status,
|
|
520
|
+
contentType: 'application/json',
|
|
521
|
+
contentLength: 2,
|
|
522
|
+
success: expected
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it('should handle different content types', () => {
|
|
528
|
+
const req = createMockRequest();
|
|
529
|
+
|
|
530
|
+
const testCases = [
|
|
531
|
+
'application/json',
|
|
532
|
+
'text/plain',
|
|
533
|
+
'application/xml',
|
|
534
|
+
'text/html'
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
testCases.forEach((contentType) => {
|
|
538
|
+
mockLogger.info.mockClear();
|
|
539
|
+
const response = createMockResponse(200, { data: 'test' });
|
|
540
|
+
response.headers.get = jest.fn().mockReturnValue(contentType);
|
|
541
|
+
|
|
542
|
+
ToolLogger.logResponse(req, response);
|
|
543
|
+
|
|
544
|
+
expectJsonLog({
|
|
545
|
+
event: 'opal_tool_response',
|
|
546
|
+
path: '/test-tool',
|
|
547
|
+
status: 200,
|
|
548
|
+
contentType,
|
|
549
|
+
contentLength: 15,
|
|
550
|
+
success: true
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
describe('edge cases', () => {
|
|
557
|
+
it('should handle empty request bodyJSON', () => {
|
|
558
|
+
const req = createMockRequest({
|
|
559
|
+
bodyJSON: {}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
ToolLogger.logRequest(req);
|
|
563
|
+
|
|
564
|
+
expectJsonLog({
|
|
565
|
+
event: 'opal_tool_request',
|
|
566
|
+
path: '/test-tool',
|
|
567
|
+
parameters: {}
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should handle request with only parameters field', () => {
|
|
572
|
+
const req = createMockRequest({
|
|
573
|
+
bodyJSON: {
|
|
574
|
+
parameters: {
|
|
575
|
+
field: 'value' // Changed from 'key' to 'field' to avoid sensitive field detection
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
ToolLogger.logRequest(req);
|
|
581
|
+
|
|
582
|
+
expectJsonLog({
|
|
583
|
+
event: 'opal_tool_request',
|
|
584
|
+
path: '/test-tool',
|
|
585
|
+
parameters: {
|
|
586
|
+
field: 'value'
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('should handle mixed data types in parameters', () => {
|
|
592
|
+
const req = createMockRequest({
|
|
593
|
+
bodyJSON: {
|
|
594
|
+
parameters: {
|
|
595
|
+
string: 'text',
|
|
596
|
+
number: 42,
|
|
597
|
+
boolean: true,
|
|
598
|
+
array: [1, 2, 3],
|
|
599
|
+
object: { nested: 'value' },
|
|
600
|
+
nullValue: null,
|
|
601
|
+
password: 'secret'
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
ToolLogger.logRequest(req);
|
|
607
|
+
|
|
608
|
+
expectJsonLog({
|
|
609
|
+
event: 'opal_tool_request',
|
|
610
|
+
path: '/test-tool',
|
|
611
|
+
parameters: {
|
|
612
|
+
string: 'text',
|
|
613
|
+
number: 42,
|
|
614
|
+
boolean: true,
|
|
615
|
+
array: [1, 2, 3],
|
|
616
|
+
object: { nested: 'value' },
|
|
617
|
+
nullValue: null,
|
|
618
|
+
password: '[REDACTED]'
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
});
|