@optimizely-opal/opal-tool-ocp-sdk 0.0.0-beta.2 → 0.0.0-beta.3
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 +78 -3
- 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.map +1 -1
- package/dist/function/ToolFunction.js +2 -1
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.test.js +27 -5
- package/dist/function/ToolFunction.test.js.map +1 -1
- package/dist/service/Service.d.ts +7 -7
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js +3 -3
- package/dist/service/Service.js.map +1 -1
- package/dist/service/Service.test.js +82 -13
- package/dist/service/Service.test.js.map +1 -1
- package/package.json +1 -1
- package/src/decorator/Decorator.test.ts +126 -0
- package/src/decorator/Decorator.ts +32 -4
- package/src/function/ToolFunction.test.ts +32 -5
- package/src/function/ToolFunction.ts +2 -1
- package/src/service/Service.test.ts +98 -5
- package/src/service/Service.ts +7 -7
|
@@ -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,
|
|
@@ -199,11 +203,93 @@ describe('ToolsService', () => {
|
|
|
199
203
|
|
|
200
204
|
expect(response.status).toBe(200);
|
|
201
205
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
206
|
+
undefined, // functionContext
|
|
202
207
|
{ param1: 'test-value' },
|
|
203
208
|
undefined
|
|
204
209
|
);
|
|
205
210
|
});
|
|
206
211
|
|
|
212
|
+
it('should execute tool with existing ToolFunction instance context', async () => {
|
|
213
|
+
// Create a mock ToolFunction instance
|
|
214
|
+
const mockToolFunctionInstance = {
|
|
215
|
+
someProperty: 'test-value',
|
|
216
|
+
someMethod: jest.fn()
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const mockRequest = createMockRequest();
|
|
220
|
+
const response = await toolsService.processRequest(mockRequest, mockToolFunctionInstance);
|
|
221
|
+
|
|
222
|
+
expect(response.status).toBe(200);
|
|
223
|
+
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
224
|
+
mockToolFunctionInstance, // functionContext - existing instance
|
|
225
|
+
{ param1: 'test-value' },
|
|
226
|
+
undefined
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should allow handler in ToolFunction subclass to access request object', async () => {
|
|
231
|
+
// Create a mock class that extends ToolFunction
|
|
232
|
+
class MockToolFunction extends ToolFunction {
|
|
233
|
+
public testMethod() {
|
|
234
|
+
return `path: ${this.request.path}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public getRequestPath() {
|
|
238
|
+
return this.request.path;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Create an instance with a mock request
|
|
243
|
+
const mockRequest = createMockRequest({ path: '/test-path' });
|
|
244
|
+
const mockToolFunctionInstance = new MockToolFunction(mockRequest);
|
|
245
|
+
|
|
246
|
+
// Create a handler that will use the ToolFunction instance's methods and properties
|
|
247
|
+
const handlerThatAccessesRequest = jest.fn().mockImplementation((
|
|
248
|
+
functionContext: any,
|
|
249
|
+
params: any,
|
|
250
|
+
_authData: any
|
|
251
|
+
) => {
|
|
252
|
+
// This simulates what would happen in a decorated method of a ToolFunction subclass
|
|
253
|
+
if (functionContext && functionContext instanceof MockToolFunction) {
|
|
254
|
+
return Promise.resolve({
|
|
255
|
+
success: true,
|
|
256
|
+
requestPath: functionContext.getRequestPath(), // Use public method to access request
|
|
257
|
+
testMethodResult: functionContext.testMethod(),
|
|
258
|
+
receivedParams: params
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return Promise.resolve({ success: false, error: 'No valid function context' });
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Register a tool with our custom handler
|
|
265
|
+
toolsService.registerTool(
|
|
266
|
+
'test-toolfunction-access',
|
|
267
|
+
'Test handler access to ToolFunction instance',
|
|
268
|
+
handlerThatAccessesRequest,
|
|
269
|
+
[],
|
|
270
|
+
'/test-toolfunction-access'
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const testRequest = createMockRequest({
|
|
274
|
+
path: '/test-toolfunction-access',
|
|
275
|
+
bodyJSON: { action: 'test' }
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const response = await toolsService.processRequest(testRequest, mockToolFunctionInstance);
|
|
279
|
+
|
|
280
|
+
expect(response.status).toBe(200);
|
|
281
|
+
expect((response as any).data).toBeDefined();
|
|
282
|
+
expect((response as any).data.success).toBe(true);
|
|
283
|
+
expect((response as any).data.requestPath).toBe('/test-path');
|
|
284
|
+
expect((response as any).data.testMethodResult).toBe('path: /test-path');
|
|
285
|
+
expect((response as any).data.receivedParams).toEqual({ action: 'test' });
|
|
286
|
+
expect(handlerThatAccessesRequest).toHaveBeenCalledWith(
|
|
287
|
+
mockToolFunctionInstance, // functionContext is the ToolFunction instance
|
|
288
|
+
{ action: 'test' },
|
|
289
|
+
undefined
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
|
|
207
293
|
it('should execute tool with OptiID auth data when provided', async () => {
|
|
208
294
|
const authData = new OptiIdAuthData(
|
|
209
295
|
'optiId',
|
|
@@ -225,6 +311,7 @@ describe('ToolsService', () => {
|
|
|
225
311
|
|
|
226
312
|
expect(response.status).toBe(200);
|
|
227
313
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
314
|
+
undefined, // functionContext
|
|
228
315
|
{ param1: 'test-value' },
|
|
229
316
|
authData
|
|
230
317
|
);
|
|
@@ -240,6 +327,7 @@ describe('ToolsService', () => {
|
|
|
240
327
|
|
|
241
328
|
expect(response.status).toBe(200);
|
|
242
329
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
330
|
+
undefined, // functionContext
|
|
243
331
|
{ param1: 'test-value' },
|
|
244
332
|
undefined
|
|
245
333
|
);
|
|
@@ -288,7 +376,7 @@ describe('ToolsService', () => {
|
|
|
288
376
|
const response = await toolsService.processRequest(interactionRequest);
|
|
289
377
|
|
|
290
378
|
expect(response.status).toBe(200);
|
|
291
|
-
expect(mockInteraction.handler).toHaveBeenCalledWith({ param1: 'test-value' }, undefined);
|
|
379
|
+
expect(mockInteraction.handler).toHaveBeenCalledWith(undefined, { param1: 'test-value' }, undefined);
|
|
292
380
|
});
|
|
293
381
|
|
|
294
382
|
it('should handle interaction request body without data wrapper', async () => {
|
|
@@ -301,7 +389,7 @@ describe('ToolsService', () => {
|
|
|
301
389
|
const response = await toolsService.processRequest(interactionRequest);
|
|
302
390
|
|
|
303
391
|
expect(response.status).toBe(200);
|
|
304
|
-
expect(mockInteraction.handler).toHaveBeenCalledWith({ param1: 'test-value' }, undefined);
|
|
392
|
+
expect(mockInteraction.handler).toHaveBeenCalledWith(undefined, { param1: 'test-value' }, undefined);
|
|
305
393
|
});
|
|
306
394
|
|
|
307
395
|
it('should execute interaction with OptiID auth data when provided', async () => {
|
|
@@ -326,6 +414,7 @@ describe('ToolsService', () => {
|
|
|
326
414
|
|
|
327
415
|
expect(response.status).toBe(200);
|
|
328
416
|
expect(mockInteraction.handler).toHaveBeenCalledWith(
|
|
417
|
+
undefined, // functionContext
|
|
329
418
|
{ param1: 'test-value' },
|
|
330
419
|
authData
|
|
331
420
|
);
|
|
@@ -353,6 +442,7 @@ describe('ToolsService', () => {
|
|
|
353
442
|
|
|
354
443
|
expect(response.status).toBe(200);
|
|
355
444
|
expect(mockInteraction.handler).toHaveBeenCalledWith(
|
|
445
|
+
undefined, // functionContext
|
|
356
446
|
{
|
|
357
447
|
param1: 'test-value',
|
|
358
448
|
auth: authData
|
|
@@ -430,7 +520,7 @@ describe('ToolsService', () => {
|
|
|
430
520
|
const response = await toolsService.processRequest(requestWithNullBody);
|
|
431
521
|
|
|
432
522
|
expect(response.status).toBe(200);
|
|
433
|
-
expect(mockTool.handler).toHaveBeenCalledWith(null, undefined);
|
|
523
|
+
expect(mockTool.handler).toHaveBeenCalledWith(undefined, null, undefined);
|
|
434
524
|
});
|
|
435
525
|
|
|
436
526
|
it('should handle request with undefined bodyJSON', async () => {
|
|
@@ -450,7 +540,7 @@ describe('ToolsService', () => {
|
|
|
450
540
|
const response = await toolsService.processRequest(requestWithUndefinedBody);
|
|
451
541
|
|
|
452
542
|
expect(response.status).toBe(200);
|
|
453
|
-
expect(mockTool.handler).toHaveBeenCalledWith(undefined, undefined);
|
|
543
|
+
expect(mockTool.handler).toHaveBeenCalledWith(undefined, undefined, undefined);
|
|
454
544
|
});
|
|
455
545
|
|
|
456
546
|
it('should extract auth data from bodyJSON when body exists', async () => {
|
|
@@ -482,6 +572,7 @@ describe('ToolsService', () => {
|
|
|
482
572
|
|
|
483
573
|
expect(response.status).toBe(200);
|
|
484
574
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
575
|
+
undefined, // functionContext
|
|
485
576
|
{ param1: 'test-value' },
|
|
486
577
|
authData
|
|
487
578
|
);
|
|
@@ -510,6 +601,7 @@ describe('ToolsService', () => {
|
|
|
510
601
|
|
|
511
602
|
expect(response.status).toBe(200);
|
|
512
603
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
604
|
+
undefined, // functionContext
|
|
513
605
|
{ param1: 'test-value' },
|
|
514
606
|
undefined
|
|
515
607
|
);
|
|
@@ -541,6 +633,7 @@ describe('ToolsService', () => {
|
|
|
541
633
|
|
|
542
634
|
expect(response.status).toBe(200);
|
|
543
635
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
636
|
+
undefined, // functionContext
|
|
544
637
|
{ param1: 'test-value' },
|
|
545
638
|
authData
|
|
546
639
|
);
|
package/src/service/Service.ts
CHANGED
|
@@ -22,7 +22,7 @@ export class Interaction<TAuthData> {
|
|
|
22
22
|
public constructor(
|
|
23
23
|
public name: string,
|
|
24
24
|
public endpoint: string,
|
|
25
|
-
public handler: (data: unknown, authData?: TAuthData) => Promise<InteractionResult>
|
|
25
|
+
public handler: (functionContext: any, data: unknown, authData?: TAuthData) => Promise<InteractionResult>
|
|
26
26
|
) {}
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -49,7 +49,7 @@ export class Tool<TAuthData> {
|
|
|
49
49
|
public description: string,
|
|
50
50
|
public parameters: Parameter[],
|
|
51
51
|
public endpoint: string,
|
|
52
|
-
public handler: (params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
52
|
+
public handler: (functionContext: any, params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
53
53
|
public authRequirements?: AuthRequirement[]
|
|
54
54
|
) {}
|
|
55
55
|
|
|
@@ -104,7 +104,7 @@ export class ToolsService {
|
|
|
104
104
|
public registerTool<TAuthData>(
|
|
105
105
|
name: string,
|
|
106
106
|
description: string,
|
|
107
|
-
handler: (params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
107
|
+
handler: (functionContext: any, params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
108
108
|
parameters: Parameter[],
|
|
109
109
|
endpoint: string,
|
|
110
110
|
authRequirements?: AuthRequirement[]
|
|
@@ -121,14 +121,14 @@ export class ToolsService {
|
|
|
121
121
|
*/
|
|
122
122
|
public registerInteraction<TAuthData>(
|
|
123
123
|
name: string,
|
|
124
|
-
handler: (data: unknown, authData?: TAuthData) => Promise<InteractionResult>,
|
|
124
|
+
handler: (functionContext: any, data: unknown, authData?: TAuthData) => Promise<InteractionResult>,
|
|
125
125
|
endpoint: string
|
|
126
126
|
): void {
|
|
127
127
|
const func = new Interaction<TAuthData>(name, endpoint, handler);
|
|
128
128
|
this.interactions.set(endpoint, func);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
public async processRequest(req: App.Request): Promise<App.Response> {
|
|
131
|
+
public async processRequest(req: App.Request, functionContext?: any): Promise<App.Response> {
|
|
132
132
|
if (req.path === '/discovery') {
|
|
133
133
|
return new App.Response(200, { functions: Array.from(this.functions.values()).map((f) => f.toJSON()) });
|
|
134
134
|
} else {
|
|
@@ -145,7 +145,7 @@ export class ToolsService {
|
|
|
145
145
|
// Extract auth data from body JSON
|
|
146
146
|
const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
|
|
147
147
|
|
|
148
|
-
const result = await func.handler(params, authData);
|
|
148
|
+
const result = await func.handler(functionContext, params, authData);
|
|
149
149
|
return new App.Response(200, result);
|
|
150
150
|
} catch (error: any) {
|
|
151
151
|
logger.error(`Error in function ${func.name}:`, error);
|
|
@@ -166,7 +166,7 @@ export class ToolsService {
|
|
|
166
166
|
// Extract auth data from body JSON
|
|
167
167
|
const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
|
|
168
168
|
|
|
169
|
-
const result = await interaction.handler(params, authData);
|
|
169
|
+
const result = await interaction.handler(functionContext, params, authData);
|
|
170
170
|
return new App.Response(200, result);
|
|
171
171
|
} catch (error: any) {
|
|
172
172
|
logger.error(`Error in function ${interaction.name}:`, error);
|