@optimizely-opal/opal-tool-ocp-sdk 0.0.0-beta.1 → 0.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.
- package/README.md +106 -51
- package/dist/auth/TokenVerifier.d.ts +31 -0
- package/dist/auth/TokenVerifier.d.ts.map +1 -0
- package/dist/auth/TokenVerifier.js +127 -0
- package/dist/auth/TokenVerifier.js.map +1 -0
- package/dist/auth/TokenVerifier.test.d.ts +2 -0
- package/dist/auth/TokenVerifier.test.d.ts.map +1 -0
- package/dist/auth/TokenVerifier.test.js +114 -0
- package/dist/auth/TokenVerifier.test.js.map +1 -0
- package/dist/decorator/Decorator.d.ts +4 -2
- package/dist/decorator/Decorator.d.ts.map +1 -1
- package/dist/decorator/Decorator.js +26 -4
- package/dist/decorator/Decorator.js.map +1 -1
- package/dist/decorator/Decorator.test.js +110 -0
- package/dist/decorator/Decorator.test.js.map +1 -1
- package/dist/function/ToolFunction.d.ts +14 -1
- package/dist/function/ToolFunction.d.ts.map +1 -1
- package/dist/function/ToolFunction.js +59 -3
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.test.js +229 -104
- package/dist/function/ToolFunction.test.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/service/Service.d.ts +14 -13
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js +25 -19
- package/dist/service/Service.js.map +1 -1
- package/dist/service/Service.test.js +122 -36
- package/dist/service/Service.test.js.map +1 -1
- package/dist/types/Models.d.ts +5 -5
- package/dist/types/Models.d.ts.map +1 -1
- package/dist/types/Models.js +9 -9
- package/dist/types/Models.js.map +1 -1
- package/package.json +10 -3
- package/src/auth/TokenVerifier.test.ts +152 -0
- package/src/auth/TokenVerifier.ts +145 -0
- package/src/decorator/Decorator.test.ts +126 -0
- package/src/decorator/Decorator.ts +32 -4
- package/src/function/ToolFunction.test.ts +259 -109
- package/src/function/ToolFunction.ts +66 -5
- package/src/index.ts +1 -0
- package/src/service/Service.test.ts +139 -28
- package/src/service/Service.ts +31 -24
- package/src/types/Models.ts +4 -4
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { toolsService, Tool, Interaction } from './Service';
|
|
2
2
|
import { Parameter, ParameterType, AuthRequirement, OptiIdAuthDataCredentials, OptiIdAuthData } from '../types/Models';
|
|
3
|
+
import { ToolFunction } from '../function/ToolFunction';
|
|
3
4
|
import { logger } from '@zaiusinc/app-sdk';
|
|
4
5
|
|
|
5
|
-
// Mock the logger
|
|
6
|
+
// Mock the logger and other app-sdk exports
|
|
6
7
|
jest.mock('@zaiusinc/app-sdk', () => ({
|
|
7
8
|
logger: {
|
|
8
9
|
error: jest.fn()
|
|
9
10
|
},
|
|
11
|
+
Function: class {
|
|
12
|
+
public constructor(public request: any) {}
|
|
13
|
+
},
|
|
10
14
|
Response: jest.fn().mockImplementation((status, data) => ({
|
|
11
15
|
status,
|
|
12
16
|
data,
|
|
@@ -18,6 +22,7 @@ jest.mock('@zaiusinc/app-sdk', () => ({
|
|
|
18
22
|
describe('ToolsService', () => {
|
|
19
23
|
let mockTool: Tool<unknown>;
|
|
20
24
|
let mockInteraction: Interaction<unknown>;
|
|
25
|
+
let mockToolFunction: ToolFunction;
|
|
21
26
|
|
|
22
27
|
beforeEach(() => {
|
|
23
28
|
// Clear registered functions and interactions before each test
|
|
@@ -27,6 +32,14 @@ describe('ToolsService', () => {
|
|
|
27
32
|
// Reset all mocks
|
|
28
33
|
jest.clearAllMocks();
|
|
29
34
|
|
|
35
|
+
// Create mock ToolFunction
|
|
36
|
+
mockToolFunction = {
|
|
37
|
+
ready: jest.fn().mockResolvedValue(true),
|
|
38
|
+
perform: jest.fn(),
|
|
39
|
+
validateBearerToken: jest.fn().mockReturnValue(true),
|
|
40
|
+
request: {} as any
|
|
41
|
+
} as any;
|
|
42
|
+
|
|
30
43
|
// Create mock tool handler
|
|
31
44
|
const mockToolHandler = jest.fn().mockResolvedValue({ result: 'success' });
|
|
32
45
|
|
|
@@ -90,7 +103,7 @@ describe('ToolsService', () => {
|
|
|
90
103
|
);
|
|
91
104
|
|
|
92
105
|
const discoveryRequest = createMockRequest({ path: '/discovery' });
|
|
93
|
-
const response = await toolsService.processRequest(discoveryRequest);
|
|
106
|
+
const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
|
|
94
107
|
|
|
95
108
|
expect(response.status).toBe(200);
|
|
96
109
|
expect(response).toHaveProperty('bodyJSON');
|
|
@@ -109,13 +122,14 @@ describe('ToolsService', () => {
|
|
|
109
122
|
description: mockTool.description,
|
|
110
123
|
parameters: mockTool.parameters.map((p: Parameter) => p.toJSON()),
|
|
111
124
|
endpoint: mockTool.endpoint,
|
|
112
|
-
http_method: 'POST'
|
|
125
|
+
http_method: 'POST',
|
|
126
|
+
auth_requirements: [{ provider: 'OptiID', scope_bundle: 'default', required: true }]
|
|
113
127
|
});
|
|
114
128
|
});
|
|
115
129
|
|
|
116
130
|
it('should return empty functions array when no tools are registered', async () => {
|
|
117
131
|
const discoveryRequest = createMockRequest({ path: '/discovery' });
|
|
118
|
-
const response = await toolsService.processRequest(discoveryRequest);
|
|
132
|
+
const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
|
|
119
133
|
|
|
120
134
|
expect(response.status).toBe(200);
|
|
121
135
|
expect(response.bodyAsU8Array).toBeDefined();
|
|
@@ -149,7 +163,7 @@ describe('ToolsService', () => {
|
|
|
149
163
|
);
|
|
150
164
|
|
|
151
165
|
const discoveryRequest = createMockRequest({ path: '/discovery' });
|
|
152
|
-
const response = await toolsService.processRequest(discoveryRequest);
|
|
166
|
+
const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
|
|
153
167
|
|
|
154
168
|
expect(response.status).toBe(200);
|
|
155
169
|
|
|
@@ -168,7 +182,8 @@ describe('ToolsService', () => {
|
|
|
168
182
|
description: mockTool.description,
|
|
169
183
|
parameters: mockTool.parameters.map((p: Parameter) => p.toJSON()),
|
|
170
184
|
endpoint: mockTool.endpoint,
|
|
171
|
-
http_method: 'POST'
|
|
185
|
+
http_method: 'POST',
|
|
186
|
+
auth_requirements: [{ provider: 'OptiID', scope_bundle: 'default', required: true }]
|
|
172
187
|
});
|
|
173
188
|
|
|
174
189
|
expect(secondFunction).toEqual({
|
|
@@ -177,7 +192,10 @@ describe('ToolsService', () => {
|
|
|
177
192
|
parameters: [],
|
|
178
193
|
endpoint: '/second-tool',
|
|
179
194
|
http_method: 'POST',
|
|
180
|
-
auth_requirements:
|
|
195
|
+
auth_requirements: [
|
|
196
|
+
{ provider: 'oauth2', scope_bundle: 'calendar', required: true },
|
|
197
|
+
{ provider: 'OptiID', scope_bundle: 'default', required: true }
|
|
198
|
+
]
|
|
181
199
|
});
|
|
182
200
|
});
|
|
183
201
|
});
|
|
@@ -195,15 +213,101 @@ describe('ToolsService', () => {
|
|
|
195
213
|
|
|
196
214
|
it('should execute tool successfully with parameters', async () => {
|
|
197
215
|
const mockRequest = createMockRequest();
|
|
198
|
-
const response = await toolsService.processRequest(mockRequest);
|
|
216
|
+
const response = await toolsService.processRequest(mockRequest, mockToolFunction);
|
|
217
|
+
|
|
218
|
+
expect(response.status).toBe(200);
|
|
219
|
+
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
220
|
+
mockToolFunction, // functionContext
|
|
221
|
+
{ param1: 'test-value' },
|
|
222
|
+
undefined
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should execute tool with existing ToolFunction instance context', async () => {
|
|
227
|
+
// Create a mock ToolFunction instance
|
|
228
|
+
const mockToolFunctionInstance = {
|
|
229
|
+
someProperty: 'test-value',
|
|
230
|
+
someMethod: jest.fn(),
|
|
231
|
+
ready: jest.fn().mockResolvedValue(true),
|
|
232
|
+
perform: jest.fn(),
|
|
233
|
+
validateBearerToken: jest.fn().mockReturnValue(true),
|
|
234
|
+
request: {} as any
|
|
235
|
+
} as any;
|
|
236
|
+
|
|
237
|
+
const mockRequest = createMockRequest();
|
|
238
|
+
const response = await toolsService.processRequest(mockRequest, mockToolFunctionInstance);
|
|
199
239
|
|
|
200
240
|
expect(response.status).toBe(200);
|
|
201
241
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
242
|
+
mockToolFunctionInstance, // functionContext - existing instance
|
|
202
243
|
{ param1: 'test-value' },
|
|
203
244
|
undefined
|
|
204
245
|
);
|
|
205
246
|
});
|
|
206
247
|
|
|
248
|
+
it('should allow handler in ToolFunction subclass to access request object', async () => {
|
|
249
|
+
// Create a mock class that extends ToolFunction
|
|
250
|
+
class MockToolFunction extends ToolFunction {
|
|
251
|
+
public testMethod() {
|
|
252
|
+
return `path: ${this.request.path}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
public getRequestPath() {
|
|
256
|
+
return this.request.path;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Create an instance with a mock request
|
|
261
|
+
const mockRequest = createMockRequest({ path: '/test-path' });
|
|
262
|
+
const mockToolFunctionInstance = new MockToolFunction(mockRequest);
|
|
263
|
+
|
|
264
|
+
// Create a handler that will use the ToolFunction instance's methods and properties
|
|
265
|
+
const handlerThatAccessesRequest = jest.fn().mockImplementation((
|
|
266
|
+
functionContext: any,
|
|
267
|
+
params: any,
|
|
268
|
+
_authData: any
|
|
269
|
+
) => {
|
|
270
|
+
// This simulates what would happen in a decorated method of a ToolFunction subclass
|
|
271
|
+
if (functionContext && functionContext instanceof MockToolFunction) {
|
|
272
|
+
return Promise.resolve({
|
|
273
|
+
success: true,
|
|
274
|
+
requestPath: functionContext.getRequestPath(), // Use public method to access request
|
|
275
|
+
testMethodResult: functionContext.testMethod(),
|
|
276
|
+
receivedParams: params
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return Promise.resolve({ success: false, error: 'No valid function context' });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Register a tool with our custom handler
|
|
283
|
+
toolsService.registerTool(
|
|
284
|
+
'test-toolfunction-access',
|
|
285
|
+
'Test handler access to ToolFunction instance',
|
|
286
|
+
handlerThatAccessesRequest,
|
|
287
|
+
[],
|
|
288
|
+
'/test-toolfunction-access'
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const testRequest = createMockRequest({
|
|
292
|
+
path: '/test-toolfunction-access',
|
|
293
|
+
bodyJSON: { action: 'test' }
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const response = await toolsService.processRequest(testRequest, mockToolFunctionInstance);
|
|
297
|
+
|
|
298
|
+
expect(response.status).toBe(200);
|
|
299
|
+
expect((response as any).data).toBeDefined();
|
|
300
|
+
expect((response as any).data.success).toBe(true);
|
|
301
|
+
expect((response as any).data.requestPath).toBe('/test-path');
|
|
302
|
+
expect((response as any).data.testMethodResult).toBe('path: /test-path');
|
|
303
|
+
expect((response as any).data.receivedParams).toEqual({ action: 'test' });
|
|
304
|
+
expect(handlerThatAccessesRequest).toHaveBeenCalledWith(
|
|
305
|
+
mockToolFunctionInstance, // functionContext is the ToolFunction instance
|
|
306
|
+
{ action: 'test' },
|
|
307
|
+
undefined
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
|
|
207
311
|
it('should execute tool with OptiID auth data when provided', async () => {
|
|
208
312
|
const authData = new OptiIdAuthData(
|
|
209
313
|
'optiId',
|
|
@@ -221,10 +325,11 @@ describe('ToolsService', () => {
|
|
|
221
325
|
})
|
|
222
326
|
});
|
|
223
327
|
|
|
224
|
-
const response = await toolsService.processRequest(requestWithAuth);
|
|
328
|
+
const response = await toolsService.processRequest(requestWithAuth, mockToolFunction);
|
|
225
329
|
|
|
226
330
|
expect(response.status).toBe(200);
|
|
227
331
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
332
|
+
mockToolFunction, // functionContext
|
|
228
333
|
{ param1: 'test-value' },
|
|
229
334
|
authData
|
|
230
335
|
);
|
|
@@ -236,10 +341,11 @@ describe('ToolsService', () => {
|
|
|
236
341
|
body: JSON.stringify({ param1: 'test-value' })
|
|
237
342
|
});
|
|
238
343
|
|
|
239
|
-
const response = await toolsService.processRequest(requestWithoutWrapper);
|
|
344
|
+
const response = await toolsService.processRequest(requestWithoutWrapper, mockToolFunction);
|
|
240
345
|
|
|
241
346
|
expect(response.status).toBe(200);
|
|
242
347
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
348
|
+
mockToolFunction, // functionContext
|
|
243
349
|
{ param1: 'test-value' },
|
|
244
350
|
undefined
|
|
245
351
|
);
|
|
@@ -250,7 +356,7 @@ describe('ToolsService', () => {
|
|
|
250
356
|
jest.mocked(mockTool.handler).mockRejectedValueOnce(new Error(errorMessage));
|
|
251
357
|
|
|
252
358
|
const mockRequest = createMockRequest();
|
|
253
|
-
const response = await toolsService.processRequest(mockRequest);
|
|
359
|
+
const response = await toolsService.processRequest(mockRequest, mockToolFunction);
|
|
254
360
|
|
|
255
361
|
expect(response.status).toBe(500);
|
|
256
362
|
expect(logger.error).toHaveBeenCalledWith(
|
|
@@ -263,7 +369,7 @@ describe('ToolsService', () => {
|
|
|
263
369
|
jest.mocked(mockTool.handler).mockRejectedValueOnce({});
|
|
264
370
|
|
|
265
371
|
const mockRequest = createMockRequest();
|
|
266
|
-
const response = await toolsService.processRequest(mockRequest);
|
|
372
|
+
const response = await toolsService.processRequest(mockRequest, mockToolFunction);
|
|
267
373
|
|
|
268
374
|
expect(response.status).toBe(500);
|
|
269
375
|
});
|
|
@@ -285,10 +391,10 @@ describe('ToolsService', () => {
|
|
|
285
391
|
body: JSON.stringify({ data: { param1: 'test-value' } })
|
|
286
392
|
});
|
|
287
393
|
|
|
288
|
-
const response = await toolsService.processRequest(interactionRequest);
|
|
394
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
289
395
|
|
|
290
396
|
expect(response.status).toBe(200);
|
|
291
|
-
expect(mockInteraction.handler).toHaveBeenCalledWith({ param1: 'test-value' }, undefined);
|
|
397
|
+
expect(mockInteraction.handler).toHaveBeenCalledWith(mockToolFunction, { param1: 'test-value' }, undefined);
|
|
292
398
|
});
|
|
293
399
|
|
|
294
400
|
it('should handle interaction request body without data wrapper', async () => {
|
|
@@ -298,10 +404,10 @@ describe('ToolsService', () => {
|
|
|
298
404
|
body: JSON.stringify({ param1: 'test-value' })
|
|
299
405
|
});
|
|
300
406
|
|
|
301
|
-
const response = await toolsService.processRequest(interactionRequest);
|
|
407
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
302
408
|
|
|
303
409
|
expect(response.status).toBe(200);
|
|
304
|
-
expect(mockInteraction.handler).toHaveBeenCalledWith({ param1: 'test-value' }, undefined);
|
|
410
|
+
expect(mockInteraction.handler).toHaveBeenCalledWith(mockToolFunction, { param1: 'test-value' }, undefined);
|
|
305
411
|
});
|
|
306
412
|
|
|
307
413
|
it('should execute interaction with OptiID auth data when provided', async () => {
|
|
@@ -322,10 +428,11 @@ describe('ToolsService', () => {
|
|
|
322
428
|
})
|
|
323
429
|
});
|
|
324
430
|
|
|
325
|
-
const response = await toolsService.processRequest(interactionRequest);
|
|
431
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
326
432
|
|
|
327
433
|
expect(response.status).toBe(200);
|
|
328
434
|
expect(mockInteraction.handler).toHaveBeenCalledWith(
|
|
435
|
+
mockToolFunction, // functionContext
|
|
329
436
|
{ param1: 'test-value' },
|
|
330
437
|
authData
|
|
331
438
|
);
|
|
@@ -349,10 +456,11 @@ describe('ToolsService', () => {
|
|
|
349
456
|
})
|
|
350
457
|
});
|
|
351
458
|
|
|
352
|
-
const response = await toolsService.processRequest(interactionRequest);
|
|
459
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
353
460
|
|
|
354
461
|
expect(response.status).toBe(200);
|
|
355
462
|
expect(mockInteraction.handler).toHaveBeenCalledWith(
|
|
463
|
+
mockToolFunction, // functionContext
|
|
356
464
|
{
|
|
357
465
|
param1: 'test-value',
|
|
358
466
|
auth: authData
|
|
@@ -370,7 +478,7 @@ describe('ToolsService', () => {
|
|
|
370
478
|
bodyJSON: { data: { param1: 'test-value' } }
|
|
371
479
|
});
|
|
372
480
|
|
|
373
|
-
const response = await toolsService.processRequest(interactionRequest);
|
|
481
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
374
482
|
|
|
375
483
|
expect(response.status).toBe(500);
|
|
376
484
|
expect(logger.error).toHaveBeenCalledWith(
|
|
@@ -383,7 +491,7 @@ describe('ToolsService', () => {
|
|
|
383
491
|
describe('error cases', () => {
|
|
384
492
|
it('should return 404 when no matching tool or interaction is found', async () => {
|
|
385
493
|
const unknownRequest = createMockRequest({ path: '/unknown-endpoint' });
|
|
386
|
-
const response = await toolsService.processRequest(unknownRequest);
|
|
494
|
+
const response = await toolsService.processRequest(unknownRequest, mockToolFunction);
|
|
387
495
|
|
|
388
496
|
expect(response.status).toBe(404);
|
|
389
497
|
});
|
|
@@ -406,7 +514,7 @@ describe('ToolsService', () => {
|
|
|
406
514
|
path: '/optid-auth-tool'
|
|
407
515
|
});
|
|
408
516
|
|
|
409
|
-
const response = await toolsService.processRequest(authRequest);
|
|
517
|
+
const response = await toolsService.processRequest(authRequest, mockToolFunction);
|
|
410
518
|
|
|
411
519
|
expect(response.status).toBe(200);
|
|
412
520
|
});
|
|
@@ -427,10 +535,10 @@ describe('ToolsService', () => {
|
|
|
427
535
|
body: null
|
|
428
536
|
});
|
|
429
537
|
|
|
430
|
-
const response = await toolsService.processRequest(requestWithNullBody);
|
|
538
|
+
const response = await toolsService.processRequest(requestWithNullBody, mockToolFunction);
|
|
431
539
|
|
|
432
540
|
expect(response.status).toBe(200);
|
|
433
|
-
expect(mockTool.handler).toHaveBeenCalledWith(null, undefined);
|
|
541
|
+
expect(mockTool.handler).toHaveBeenCalledWith(mockToolFunction, null, undefined);
|
|
434
542
|
});
|
|
435
543
|
|
|
436
544
|
it('should handle request with undefined bodyJSON', async () => {
|
|
@@ -447,10 +555,10 @@ describe('ToolsService', () => {
|
|
|
447
555
|
body: undefined
|
|
448
556
|
});
|
|
449
557
|
|
|
450
|
-
const response = await toolsService.processRequest(requestWithUndefinedBody);
|
|
558
|
+
const response = await toolsService.processRequest(requestWithUndefinedBody, mockToolFunction);
|
|
451
559
|
|
|
452
560
|
expect(response.status).toBe(200);
|
|
453
|
-
expect(mockTool.handler).toHaveBeenCalledWith(undefined, undefined);
|
|
561
|
+
expect(mockTool.handler).toHaveBeenCalledWith(mockToolFunction, undefined, undefined);
|
|
454
562
|
});
|
|
455
563
|
|
|
456
564
|
it('should extract auth data from bodyJSON when body exists', async () => {
|
|
@@ -478,10 +586,11 @@ describe('ToolsService', () => {
|
|
|
478
586
|
})
|
|
479
587
|
});
|
|
480
588
|
|
|
481
|
-
const response = await toolsService.processRequest(requestWithAuth);
|
|
589
|
+
const response = await toolsService.processRequest(requestWithAuth, mockToolFunction);
|
|
482
590
|
|
|
483
591
|
expect(response.status).toBe(200);
|
|
484
592
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
593
|
+
mockToolFunction, // functionContext
|
|
485
594
|
{ param1: 'test-value' },
|
|
486
595
|
authData
|
|
487
596
|
);
|
|
@@ -506,10 +615,11 @@ describe('ToolsService', () => {
|
|
|
506
615
|
})
|
|
507
616
|
});
|
|
508
617
|
|
|
509
|
-
const response = await toolsService.processRequest(requestWithoutAuth);
|
|
618
|
+
const response = await toolsService.processRequest(requestWithoutAuth, mockToolFunction);
|
|
510
619
|
|
|
511
620
|
expect(response.status).toBe(200);
|
|
512
621
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
622
|
+
mockToolFunction, // functionContext
|
|
513
623
|
{ param1: 'test-value' },
|
|
514
624
|
undefined
|
|
515
625
|
);
|
|
@@ -537,10 +647,11 @@ describe('ToolsService', () => {
|
|
|
537
647
|
body: ''
|
|
538
648
|
});
|
|
539
649
|
|
|
540
|
-
const response = await toolsService.processRequest(requestWithAuthButNoBody);
|
|
650
|
+
const response = await toolsService.processRequest(requestWithAuthButNoBody, mockToolFunction);
|
|
541
651
|
|
|
542
652
|
expect(response.status).toBe(200);
|
|
543
653
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
654
|
+
mockToolFunction, // functionContext
|
|
544
655
|
{ param1: 'test-value' },
|
|
545
656
|
authData
|
|
546
657
|
);
|
package/src/service/Service.ts
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
import { AuthRequirement, Parameter } from '../types/Models';
|
|
3
3
|
import * as App from '@zaiusinc/app-sdk';
|
|
4
4
|
import { logger } from '@zaiusinc/app-sdk';
|
|
5
|
+
import { ToolFunction } from '../function/ToolFunction';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default OptiID authentication requirement that will be enforced for all tools
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_OPTIID_AUTH = new AuthRequirement('OptiID', 'default', true);
|
|
5
11
|
|
|
6
12
|
|
|
7
13
|
|
|
@@ -22,7 +28,7 @@ export class Interaction<TAuthData> {
|
|
|
22
28
|
public constructor(
|
|
23
29
|
public name: string,
|
|
24
30
|
public endpoint: string,
|
|
25
|
-
public handler: (data: unknown, authData?: TAuthData) => Promise<InteractionResult>
|
|
31
|
+
public handler: (functionContext: ToolFunction, data: unknown, authData?: TAuthData) => Promise<InteractionResult>
|
|
26
32
|
) {}
|
|
27
33
|
}
|
|
28
34
|
|
|
@@ -42,15 +48,15 @@ export class Tool<TAuthData> {
|
|
|
42
48
|
* @param parameters Function parameters
|
|
43
49
|
* @param endpoint API endpoint
|
|
44
50
|
* @param handler Function implementing the tool
|
|
45
|
-
* @param authRequirements Authentication requirements (
|
|
51
|
+
* @param authRequirements Authentication requirements (mandatory - OptiID enforced)
|
|
46
52
|
*/
|
|
47
53
|
public constructor(
|
|
48
54
|
public name: string,
|
|
49
55
|
public description: string,
|
|
50
56
|
public parameters: Parameter[],
|
|
51
57
|
public endpoint: string,
|
|
52
|
-
public handler: (params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
53
|
-
public authRequirements
|
|
58
|
+
public handler: (functionContext: ToolFunction, params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
59
|
+
public authRequirements: AuthRequirement[] = [DEFAULT_OPTIID_AUTH]
|
|
54
60
|
) {}
|
|
55
61
|
|
|
56
62
|
/**
|
|
@@ -62,13 +68,10 @@ export class Tool<TAuthData> {
|
|
|
62
68
|
description: this.description,
|
|
63
69
|
parameters: this.parameters.map((p) => p.toJSON()),
|
|
64
70
|
endpoint: this.endpoint,
|
|
65
|
-
http_method: this.httpMethod
|
|
71
|
+
http_method: this.httpMethod,
|
|
72
|
+
auth_requirements: this.authRequirements.map((auth) => auth.toJSON())
|
|
66
73
|
};
|
|
67
74
|
|
|
68
|
-
if (this.authRequirements && this.authRequirements.length > 0) {
|
|
69
|
-
result.auth_requirements = this.authRequirements.map((auth) => auth.toJSON());
|
|
70
|
-
}
|
|
71
|
-
|
|
72
75
|
return result;
|
|
73
76
|
}
|
|
74
77
|
}
|
|
@@ -78,18 +81,19 @@ export class ToolsService {
|
|
|
78
81
|
private interactions: Map<string, Interaction<any>> = new Map();
|
|
79
82
|
|
|
80
83
|
/**
|
|
81
|
-
*
|
|
82
|
-
* @param
|
|
83
|
-
* @returns
|
|
84
|
+
* Enforce OptiID authentication for tools by ensuring OptiID auth requirement is present
|
|
85
|
+
* @param authRequirements Original authentication requirements
|
|
86
|
+
* @returns Enforced authentication requirements with OptiID
|
|
84
87
|
*/
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
private enforceOptiIdAuth(authRequirements?: AuthRequirement[]): AuthRequirement[] {
|
|
89
|
+
const hasOptiIdProvider = authRequirements
|
|
90
|
+
&& authRequirements.some((auth) => auth.provider.toLowerCase() === 'optiid');
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
bearerToken = authHeader.substring(7).trim();
|
|
92
|
+
if (hasOptiIdProvider) {
|
|
93
|
+
return authRequirements;
|
|
91
94
|
}
|
|
92
|
-
|
|
95
|
+
|
|
96
|
+
return [...(authRequirements || []), DEFAULT_OPTIID_AUTH];
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
/**
|
|
@@ -104,12 +108,14 @@ export class ToolsService {
|
|
|
104
108
|
public registerTool<TAuthData>(
|
|
105
109
|
name: string,
|
|
106
110
|
description: string,
|
|
107
|
-
handler: (params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
111
|
+
handler: (functionContext: ToolFunction, params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
108
112
|
parameters: Parameter[],
|
|
109
113
|
endpoint: string,
|
|
110
114
|
authRequirements?: AuthRequirement[]
|
|
111
115
|
): void {
|
|
112
|
-
|
|
116
|
+
// Enforce OptiID authentication for all tools
|
|
117
|
+
const enforcedAuthRequirements = this.enforceOptiIdAuth(authRequirements);
|
|
118
|
+
const func = new Tool<TAuthData>(name, description, parameters, endpoint, handler, enforcedAuthRequirements);
|
|
113
119
|
this.functions.set(endpoint, func);
|
|
114
120
|
}
|
|
115
121
|
|
|
@@ -121,14 +127,15 @@ export class ToolsService {
|
|
|
121
127
|
*/
|
|
122
128
|
public registerInteraction<TAuthData>(
|
|
123
129
|
name: string,
|
|
124
|
-
handler: (data: unknown, authData?: TAuthData) => Promise<InteractionResult>,
|
|
130
|
+
handler: (functionContext: ToolFunction, data: unknown, authData?: TAuthData) => Promise<InteractionResult>,
|
|
125
131
|
endpoint: string
|
|
126
132
|
): void {
|
|
127
133
|
const func = new Interaction<TAuthData>(name, endpoint, handler);
|
|
128
134
|
this.interactions.set(endpoint, func);
|
|
129
135
|
}
|
|
130
136
|
|
|
131
|
-
public async processRequest(req: App.Request
|
|
137
|
+
public async processRequest(req: App.Request,
|
|
138
|
+
functionContext: ToolFunction): Promise<App.Response> {
|
|
132
139
|
if (req.path === '/discovery') {
|
|
133
140
|
return new App.Response(200, { functions: Array.from(this.functions.values()).map((f) => f.toJSON()) });
|
|
134
141
|
} else {
|
|
@@ -145,7 +152,7 @@ export class ToolsService {
|
|
|
145
152
|
// Extract auth data from body JSON
|
|
146
153
|
const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
|
|
147
154
|
|
|
148
|
-
const result = await func.handler(params, authData);
|
|
155
|
+
const result = await func.handler(functionContext, params, authData);
|
|
149
156
|
return new App.Response(200, result);
|
|
150
157
|
} catch (error: any) {
|
|
151
158
|
logger.error(`Error in function ${func.name}:`, error);
|
|
@@ -166,7 +173,7 @@ export class ToolsService {
|
|
|
166
173
|
// Extract auth data from body JSON
|
|
167
174
|
const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
|
|
168
175
|
|
|
169
|
-
const result = await interaction.handler(params, authData);
|
|
176
|
+
const result = await interaction.handler(functionContext, params, authData);
|
|
170
177
|
return new App.Response(200, result);
|
|
171
178
|
} catch (error: any) {
|
|
172
179
|
logger.error(`Error in function ${interaction.name}:`, error);
|
package/src/types/Models.ts
CHANGED
|
@@ -48,10 +48,10 @@ export class Parameter {
|
|
|
48
48
|
export class OptiIdAuthDataCredentials {
|
|
49
49
|
|
|
50
50
|
public constructor(
|
|
51
|
-
public
|
|
52
|
-
public
|
|
53
|
-
public
|
|
54
|
-
public
|
|
51
|
+
public customer_id: string,
|
|
52
|
+
public instance_id: string,
|
|
53
|
+
public access_token: string,
|
|
54
|
+
public product_sku: string
|
|
55
55
|
) {}
|
|
56
56
|
}
|
|
57
57
|
|