@optimizely-opal/opal-tool-ocp-sdk 0.0.0-dev.3 → 0.0.0-dev.5

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.
@@ -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
  );
@@ -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);