@optimizely-opal/opal-tool-ocp-sdk 0.0.0-OCP-1487.1

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 (72) hide show
  1. package/README.md +631 -0
  2. package/dist/auth/AuthUtils.d.ts +31 -0
  3. package/dist/auth/AuthUtils.d.ts.map +1 -0
  4. package/dist/auth/AuthUtils.js +64 -0
  5. package/dist/auth/AuthUtils.js.map +1 -0
  6. package/dist/auth/AuthUtils.test.d.ts +2 -0
  7. package/dist/auth/AuthUtils.test.d.ts.map +1 -0
  8. package/dist/auth/AuthUtils.test.js +469 -0
  9. package/dist/auth/AuthUtils.test.js.map +1 -0
  10. package/dist/auth/TokenVerifier.d.ts +31 -0
  11. package/dist/auth/TokenVerifier.d.ts.map +1 -0
  12. package/dist/auth/TokenVerifier.js +127 -0
  13. package/dist/auth/TokenVerifier.js.map +1 -0
  14. package/dist/auth/TokenVerifier.test.d.ts +2 -0
  15. package/dist/auth/TokenVerifier.test.d.ts.map +1 -0
  16. package/dist/auth/TokenVerifier.test.js +125 -0
  17. package/dist/auth/TokenVerifier.test.js.map +1 -0
  18. package/dist/decorator/Decorator.d.ts +48 -0
  19. package/dist/decorator/Decorator.d.ts.map +1 -0
  20. package/dist/decorator/Decorator.js +53 -0
  21. package/dist/decorator/Decorator.js.map +1 -0
  22. package/dist/decorator/Decorator.test.d.ts +2 -0
  23. package/dist/decorator/Decorator.test.d.ts.map +1 -0
  24. package/dist/decorator/Decorator.test.js +528 -0
  25. package/dist/decorator/Decorator.test.js.map +1 -0
  26. package/dist/function/GlobalToolFunction.d.ts +28 -0
  27. package/dist/function/GlobalToolFunction.d.ts.map +1 -0
  28. package/dist/function/GlobalToolFunction.js +56 -0
  29. package/dist/function/GlobalToolFunction.js.map +1 -0
  30. package/dist/function/GlobalToolFunction.test.d.ts +2 -0
  31. package/dist/function/GlobalToolFunction.test.d.ts.map +1 -0
  32. package/dist/function/GlobalToolFunction.test.js +425 -0
  33. package/dist/function/GlobalToolFunction.test.js.map +1 -0
  34. package/dist/function/ToolFunction.d.ts +28 -0
  35. package/dist/function/ToolFunction.d.ts.map +1 -0
  36. package/dist/function/ToolFunction.js +60 -0
  37. package/dist/function/ToolFunction.js.map +1 -0
  38. package/dist/function/ToolFunction.test.d.ts +2 -0
  39. package/dist/function/ToolFunction.test.d.ts.map +1 -0
  40. package/dist/function/ToolFunction.test.js +314 -0
  41. package/dist/function/ToolFunction.test.js.map +1 -0
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +26 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/service/Service.d.ts +80 -0
  47. package/dist/service/Service.d.ts.map +1 -0
  48. package/dist/service/Service.js +210 -0
  49. package/dist/service/Service.js.map +1 -0
  50. package/dist/service/Service.test.d.ts +2 -0
  51. package/dist/service/Service.test.d.ts.map +1 -0
  52. package/dist/service/Service.test.js +427 -0
  53. package/dist/service/Service.test.js.map +1 -0
  54. package/dist/types/Models.d.ts +126 -0
  55. package/dist/types/Models.d.ts.map +1 -0
  56. package/dist/types/Models.js +181 -0
  57. package/dist/types/Models.js.map +1 -0
  58. package/package.json +64 -0
  59. package/src/auth/AuthUtils.test.ts +586 -0
  60. package/src/auth/AuthUtils.ts +66 -0
  61. package/src/auth/TokenVerifier.test.ts +165 -0
  62. package/src/auth/TokenVerifier.ts +145 -0
  63. package/src/decorator/Decorator.test.ts +649 -0
  64. package/src/decorator/Decorator.ts +111 -0
  65. package/src/function/GlobalToolFunction.test.ts +505 -0
  66. package/src/function/GlobalToolFunction.ts +61 -0
  67. package/src/function/ToolFunction.test.ts +374 -0
  68. package/src/function/ToolFunction.ts +64 -0
  69. package/src/index.ts +5 -0
  70. package/src/service/Service.test.ts +661 -0
  71. package/src/service/Service.ts +213 -0
  72. package/src/types/Models.ts +163 -0
@@ -0,0 +1,649 @@
1
+ import { tool, interaction, ToolConfig, InteractionConfig, ParameterConfig, AuthRequirementConfig } from './Decorator';
2
+ import { toolsService } from '../service/Service';
3
+ import { ParameterType } from '../types/Models';
4
+
5
+ // Mock the toolsService
6
+ jest.mock('../service/Service', () => ({
7
+ toolsService: {
8
+ registerTool: jest.fn(),
9
+ registerInteraction: jest.fn()
10
+ }
11
+ }));
12
+ describe('Decorators', () => {
13
+ beforeEach(() => {
14
+ jest.clearAllMocks();
15
+ });
16
+
17
+ describe('@tool decorator', () => {
18
+ it('should register a tool with minimal configuration', () => {
19
+ const config: ToolConfig = {
20
+ name: 'testTool',
21
+ description: 'A test tool',
22
+ parameters: [],
23
+ endpoint: '/test-tool'
24
+ };
25
+
26
+ class TestClass {
27
+ @tool(config)
28
+ public async testMethod(_params: any) {
29
+ return { result: 'success' };
30
+ }
31
+ }
32
+
33
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
34
+ 'testTool',
35
+ 'A test tool',
36
+ expect.any(Function),
37
+ [],
38
+ '/test-tool',
39
+ []
40
+ );
41
+
42
+ // Ensure TestClass is considered "used" by TypeScript
43
+ expect(TestClass).toBeDefined();
44
+ });
45
+
46
+ it('should register a tool with parameters', () => {
47
+ const parameterConfig: ParameterConfig = {
48
+ name: 'inputParam',
49
+ type: ParameterType.String,
50
+ description: 'An input parameter',
51
+ required: true
52
+ };
53
+
54
+ const config: ToolConfig = {
55
+ name: 'toolWithParams',
56
+ description: 'A tool with parameters',
57
+ parameters: [parameterConfig],
58
+ endpoint: '/tool-with-params'
59
+ };
60
+
61
+ class TestClass {
62
+ @tool(config)
63
+ public async toolWithParams(_params: any) {
64
+ return { result: _params.inputParam };
65
+ }
66
+ }
67
+
68
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
69
+ 'toolWithParams',
70
+ 'A tool with parameters',
71
+ expect.any(Function),
72
+ expect.arrayContaining([
73
+ expect.objectContaining({
74
+ name: 'inputParam',
75
+ type: ParameterType.String,
76
+ description: 'An input parameter',
77
+ required: true
78
+ })
79
+ ]),
80
+ '/tool-with-params',
81
+ []
82
+ );
83
+
84
+ // Ensure TestClass is considered "used" by TypeScript
85
+ expect(TestClass).toBeDefined();
86
+ });
87
+
88
+ it('should register a tool with auth requirements', () => {
89
+ const authRequirementConfig: AuthRequirementConfig = {
90
+ provider: 'optiId',
91
+ scopeBundle: 'calendar',
92
+ required: true
93
+ };
94
+
95
+ const config: ToolConfig = {
96
+ name: 'toolWithAuth',
97
+ description: 'A tool with auth requirements',
98
+ parameters: [],
99
+ authRequirements: [authRequirementConfig],
100
+ endpoint: '/tool-with-auth'
101
+ };
102
+
103
+ class TestClass {
104
+ @tool(config)
105
+ public async toolWithAuth(_params: any, _authData: any) {
106
+ return { result: 'authenticated' };
107
+ }
108
+ }
109
+
110
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
111
+ 'toolWithAuth',
112
+ 'A tool with auth requirements',
113
+ expect.any(Function),
114
+ [],
115
+ '/tool-with-auth',
116
+ expect.arrayContaining([
117
+ expect.objectContaining({
118
+ provider: 'optiId',
119
+ scopeBundle: 'calendar',
120
+ required: true
121
+ })
122
+ ])
123
+ );
124
+
125
+ // Ensure TestClass is considered "used" by TypeScript
126
+ expect(TestClass).toBeDefined();
127
+ });
128
+
129
+ it('should register a tool with multiple parameters and auth requirements', () => {
130
+ const parameterConfigs: ParameterConfig[] = [
131
+ {
132
+ name: 'param1',
133
+ type: ParameterType.String,
134
+ description: 'First parameter',
135
+ required: true
136
+ },
137
+ {
138
+ name: 'param2',
139
+ type: ParameterType.Integer,
140
+ description: 'Second parameter',
141
+ required: false
142
+ }
143
+ ];
144
+
145
+ const authRequirementConfigs: AuthRequirementConfig[] = [
146
+ {
147
+ provider: 'optiId',
148
+ scopeBundle: 'calendar',
149
+ required: true
150
+ },
151
+ {
152
+ provider: 'Bearer',
153
+ scopeBundle: 'drive',
154
+ required: false
155
+ }
156
+ ];
157
+
158
+ const config: ToolConfig = {
159
+ name: 'complexTool',
160
+ description: 'A complex tool with multiple configs',
161
+ parameters: parameterConfigs,
162
+ authRequirements: authRequirementConfigs,
163
+ endpoint: '/complex-tool'
164
+ };
165
+
166
+ class TestClass {
167
+ @tool(config)
168
+ public async complexTool(_params: any, _authData: any) {
169
+ return { result: 'complex operation completed' };
170
+ }
171
+ }
172
+
173
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
174
+ 'complexTool',
175
+ 'A complex tool with multiple configs',
176
+ expect.any(Function),
177
+ expect.arrayContaining([
178
+ expect.objectContaining({
179
+ name: 'param1',
180
+ type: ParameterType.String,
181
+ description: 'First parameter',
182
+ required: true
183
+ }),
184
+ expect.objectContaining({
185
+ name: 'param2',
186
+ type: ParameterType.Integer,
187
+ description: 'Second parameter',
188
+ required: false
189
+ })
190
+ ]),
191
+ '/complex-tool',
192
+ expect.arrayContaining([
193
+ expect.objectContaining({
194
+ provider: 'optiId',
195
+ scopeBundle: 'calendar',
196
+ required: true
197
+ }),
198
+ expect.objectContaining({
199
+ provider: 'Bearer',
200
+ scopeBundle: 'drive',
201
+ required: false
202
+ })
203
+ ])
204
+ );
205
+
206
+ // Ensure TestClass is considered "used" by TypeScript
207
+ expect(TestClass).toBeDefined();
208
+ });
209
+
210
+ it('should handle empty parameters array', () => {
211
+ const config: ToolConfig = {
212
+ name: 'emptyParamsTool',
213
+ description: 'Tool with empty parameters',
214
+ parameters: [],
215
+ endpoint: '/empty-params'
216
+ };
217
+
218
+ class TestClass {
219
+ @tool(config)
220
+ public async emptyParamsTool() {
221
+ return { result: 'no params needed' };
222
+ }
223
+ }
224
+
225
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
226
+ 'emptyParamsTool',
227
+ 'Tool with empty parameters',
228
+ expect.any(Function),
229
+ [],
230
+ '/empty-params',
231
+ []
232
+ );
233
+
234
+ expect(TestClass).toBeDefined();
235
+ });
236
+
237
+ it('should handle empty auth requirements array', () => {
238
+ const config: ToolConfig = {
239
+ name: 'noAuthTool',
240
+ description: 'Tool with no auth requirements',
241
+ parameters: [],
242
+ authRequirements: [],
243
+ endpoint: '/no-auth'
244
+ };
245
+
246
+ class TestClass {
247
+ @tool(config)
248
+ public async noAuthTool(_params: any) {
249
+ return { result: 'no auth needed' };
250
+ }
251
+ }
252
+
253
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
254
+ 'noAuthTool',
255
+ 'Tool with no auth requirements',
256
+ expect.any(Function),
257
+ [],
258
+ '/no-auth',
259
+ []
260
+ );
261
+
262
+ expect(TestClass).toBeDefined();
263
+ });
264
+
265
+ it('should register the actual method as the handler', () => {
266
+ const config: ToolConfig = {
267
+ name: 'handlerTest',
268
+ description: 'Test handler registration',
269
+ parameters: [],
270
+ endpoint: '/handler-test'
271
+ };
272
+
273
+ class TestClass {
274
+ @tool(config)
275
+ public async handlerTest(_params: any) {
276
+ return { result: 'handler called' };
277
+ }
278
+ }
279
+
280
+ const mockedRegisterTool = jest.mocked(toolsService.registerTool);
281
+ const registerToolCall = mockedRegisterTool.mock.calls[0];
282
+ const registeredHandler = registerToolCall[2]; // Third argument is the handler
283
+
284
+ expect(typeof registeredHandler).toBe('function');
285
+ expect(TestClass).toBeDefined();
286
+ });
287
+
288
+ it('should handle different parameter types', () => {
289
+ const parameterConfigs: ParameterConfig[] = [
290
+ { name: 'stringParam', type: ParameterType.String, description: 'String param', required: true },
291
+ { name: 'intParam', type: ParameterType.Integer, description: 'Integer param', required: true },
292
+ { name: 'numberParam', type: ParameterType.Number, description: 'Number param', required: false },
293
+ { name: 'boolParam', type: ParameterType.Boolean, description: 'Boolean param', required: false },
294
+ { name: 'listParam', type: ParameterType.List, description: 'List param', required: false },
295
+ { name: 'dictParam', type: ParameterType.Dictionary, description: 'Dictionary param', required: false }
296
+ ];
297
+
298
+ const config: ToolConfig = {
299
+ name: 'multiTypeTool',
300
+ description: 'Tool with multiple parameter types',
301
+ parameters: parameterConfigs,
302
+ endpoint: '/multi-type'
303
+ };
304
+
305
+ class TestClass {
306
+ @tool(config)
307
+ public async multiTypeTool(_params: any) {
308
+ return { result: 'multi-type processing' };
309
+ }
310
+ }
311
+ expect(TestClass).toBeDefined();
312
+
313
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
314
+ 'multiTypeTool',
315
+ 'Tool with multiple parameter types',
316
+ expect.any(Function),
317
+ expect.arrayContaining([
318
+ expect.objectContaining({ name: 'stringParam', type: ParameterType.String }),
319
+ expect.objectContaining({ name: 'intParam', type: ParameterType.Integer }),
320
+ expect.objectContaining({ name: 'numberParam', type: ParameterType.Number }),
321
+ expect.objectContaining({ name: 'boolParam', type: ParameterType.Boolean }),
322
+ expect.objectContaining({ name: 'listParam', type: ParameterType.List }),
323
+ expect.objectContaining({ name: 'dictParam', type: ParameterType.Dictionary })
324
+ ]),
325
+ '/multi-type',
326
+ []
327
+ );
328
+ });
329
+ });
330
+
331
+ describe('@interaction decorator', () => {
332
+ it('should register an interaction with minimal configuration', () => {
333
+ const config: InteractionConfig = {
334
+ name: 'testInteraction',
335
+ endpoint: '/test-interaction'
336
+ };
337
+
338
+ class TestClass {
339
+ @interaction(config)
340
+ public async testInteraction(_data: any) {
341
+ return { message: 'interaction completed' };
342
+ }
343
+ }
344
+
345
+ expect(TestClass).toBeDefined();
346
+
347
+ expect(toolsService.registerInteraction).toHaveBeenCalledWith(
348
+ 'testInteraction',
349
+ expect.any(Function),
350
+ '/test-interaction'
351
+ );
352
+ });
353
+
354
+ it('should register the actual method as the handler', () => {
355
+ const config: InteractionConfig = {
356
+ name: 'handlerInteraction',
357
+ endpoint: '/handler-interaction'
358
+ };
359
+
360
+ class TestClass {
361
+ @interaction(config)
362
+ public async handlerInteraction(_data: any) {
363
+ return { message: 'handler interaction' };
364
+ }
365
+ }
366
+
367
+ const mockedRegisterInteraction = jest.mocked(toolsService.registerInteraction);
368
+ const registerInteractionCall = mockedRegisterInteraction.mock.calls[0];
369
+ const registeredHandler = registerInteractionCall[1]; // Second argument is the handler
370
+ expect(TestClass).toBeDefined();
371
+ expect(typeof registeredHandler).toBe('function');
372
+ });
373
+
374
+ it('should handle multiple interactions in the same class', () => {
375
+ class TestClass {
376
+ @interaction({ name: 'interaction1', endpoint: '/interaction1' })
377
+ public async interaction1(_data: any) {
378
+ return { message: 'interaction 1' };
379
+ }
380
+
381
+ @interaction({ name: 'interaction2', endpoint: '/interaction2' })
382
+ public async interaction2(_data: any) {
383
+ return { message: 'interaction 2' };
384
+ }
385
+ }
386
+ expect(TestClass).toBeDefined();
387
+ expect(toolsService.registerInteraction).toHaveBeenCalledTimes(2);
388
+ expect(toolsService.registerInteraction).toHaveBeenNthCalledWith(
389
+ 1,
390
+ 'interaction1',
391
+ expect.any(Function),
392
+ '/interaction1'
393
+ );
394
+ expect(toolsService.registerInteraction).toHaveBeenNthCalledWith(
395
+ 2,
396
+ 'interaction2',
397
+ expect.any(Function),
398
+ '/interaction2'
399
+ );
400
+ });
401
+ });
402
+
403
+ describe('mixed decorators', () => {
404
+ it('should handle both tool and interaction decorators in the same class', () => {
405
+ const toolConfig: ToolConfig = {
406
+ name: 'mixedTool',
407
+ description: 'A tool in mixed class',
408
+ parameters: [
409
+ { name: 'param1', type: ParameterType.String, description: 'String param', required: true }
410
+ ],
411
+ endpoint: '/mixed-tool'
412
+ };
413
+
414
+ const interactionConfig: InteractionConfig = {
415
+ name: 'mixedInteraction',
416
+ endpoint: '/mixed-interaction'
417
+ };
418
+
419
+ class MixedClass {
420
+ @tool(toolConfig)
421
+ public async mixedTool(_params: any) {
422
+ return { result: 'tool result' };
423
+ }
424
+
425
+ @interaction(interactionConfig)
426
+ public async mixedInteraction(_data: any) {
427
+ return { message: 'interaction result' };
428
+ }
429
+ }
430
+
431
+ expect(MixedClass).toBeDefined();
432
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
433
+ 'mixedTool',
434
+ 'A tool in mixed class',
435
+ expect.any(Function),
436
+ expect.arrayContaining([
437
+ expect.objectContaining({
438
+ name: 'param1',
439
+ type: ParameterType.String,
440
+ description: 'String param',
441
+ required: true
442
+ })
443
+ ]),
444
+ '/mixed-tool',
445
+ []
446
+ );
447
+
448
+ expect(toolsService.registerInteraction).toHaveBeenCalledWith(
449
+ 'mixedInteraction',
450
+ expect.any(Function),
451
+ '/mixed-interaction'
452
+ );
453
+ });
454
+ });
455
+
456
+ describe('edge cases', () => {
457
+ it('should handle auth requirements with default required value', () => {
458
+ const authRequirementConfig: AuthRequirementConfig = {
459
+ provider: 'optiId',
460
+ scopeBundle: 'calendar'
461
+ // required is not specified, should default to true in AuthRequirement constructor
462
+ };
463
+
464
+ const config: ToolConfig = {
465
+ name: 'defaultAuthTool',
466
+ description: 'Tool with default auth requirement',
467
+ parameters: [],
468
+ authRequirements: [authRequirementConfig],
469
+ endpoint: '/default-auth'
470
+ };
471
+
472
+ class TestClass {
473
+ @tool(config)
474
+ public async defaultAuthTool(_params: any, _authData: any) {
475
+ return { result: 'default auth' };
476
+ }
477
+ }
478
+
479
+ expect(TestClass).toBeDefined();
480
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
481
+ 'defaultAuthTool',
482
+ 'Tool with default auth requirement',
483
+ expect.any(Function),
484
+ [],
485
+ '/default-auth',
486
+ expect.arrayContaining([
487
+ expect.objectContaining({
488
+ provider: 'optiId',
489
+ scopeBundle: 'calendar',
490
+ required: true // Should default to true
491
+ })
492
+ ])
493
+ );
494
+ });
495
+
496
+ it('should handle undefined parameters and authRequirements arrays', () => {
497
+ const config = {
498
+ name: 'undefinedArraysTool',
499
+ description: 'Tool with undefined arrays',
500
+ endpoint: '/undefined-arrays'
501
+ // parameters and authRequirements are not defined
502
+ } as ToolConfig;
503
+
504
+ class TestClass {
505
+ @tool(config)
506
+ public async undefinedArraysTool(_params: any) {
507
+ return { result: 'undefined arrays handled' };
508
+ }
509
+ }
510
+
511
+ expect(TestClass).toBeDefined();
512
+ expect(toolsService.registerTool).toHaveBeenCalledWith(
513
+ 'undefinedArraysTool',
514
+ 'Tool with undefined arrays',
515
+ expect.any(Function),
516
+ [], // Should be empty array when parameters is undefined
517
+ '/undefined-arrays',
518
+ [] // Should be empty array when authRequirements is undefined
519
+ );
520
+ });
521
+ });
522
+
523
+ describe('handler instance context', () => {
524
+ it('should allow tool handler to call class methods', async () => {
525
+ class TestClass {
526
+ private getValue() {
527
+ return 'class-method-called';
528
+ }
529
+
530
+ @tool({
531
+ name: 'instanceMethodTest',
532
+ description: 'Test calling class methods',
533
+ parameters: [],
534
+ endpoint: '/instance-method-test'
535
+ })
536
+ public async instanceMethodTest(_params: any) {
537
+ return { result: this.getValue() };
538
+ }
539
+ }
540
+
541
+ // Get the registered handler
542
+ const mockedRegisterTool = jest.mocked(toolsService.registerTool);
543
+ const registerToolCall = mockedRegisterTool.mock.calls.find((call) => call[0] === 'instanceMethodTest');
544
+ const registeredHandler = registerToolCall![2];
545
+
546
+ // Call the handler
547
+ const result = await registeredHandler(undefined as any, {}, {});
548
+
549
+ // Should be able to call class methods through 'this'
550
+ expect((result as any).result).toBe('class-method-called');
551
+ expect(TestClass).toBeDefined();
552
+ });
553
+
554
+ it('should allow interaction handler to call class methods', async () => {
555
+ class TestClass {
556
+ private processData() {
557
+ return 'interaction-processed';
558
+ }
559
+
560
+ @interaction({
561
+ name: 'instanceInteractionTest',
562
+ endpoint: '/instance-interaction-test'
563
+ })
564
+ public async instanceInteractionTest(_data: any) {
565
+ return { message: this.processData() };
566
+ }
567
+ }
568
+
569
+ // Get the registered handler
570
+ const mockedRegisterInteraction = jest.mocked(toolsService.registerInteraction);
571
+ const registerInteractionCall = mockedRegisterInteraction.mock.calls.find(
572
+ (call) => call[0] === 'instanceInteractionTest'
573
+ );
574
+ const registeredHandler = registerInteractionCall![1];
575
+
576
+ // Call the handler
577
+ const result = await registeredHandler(undefined as any, {});
578
+
579
+ // Should be able to call class methods through 'this'
580
+ expect((result as any).message).toBe('interaction-processed');
581
+ expect(TestClass).toBeDefined();
582
+ });
583
+
584
+ it('should handle invalid functionContext gracefully for tool', async () => {
585
+ class TestClass {
586
+ private getValue() {
587
+ return 'class-method-called';
588
+ }
589
+
590
+ @tool({
591
+ name: 'invalidContextTest',
592
+ description: 'Test handling invalid context',
593
+ parameters: [],
594
+ endpoint: '/invalid-context-test'
595
+ })
596
+ public async invalidContextTest(_params: any) {
597
+ return { result: this.getValue() };
598
+ }
599
+ }
600
+
601
+ // Get the registered handler
602
+ const mockedRegisterTool = jest.mocked(toolsService.registerTool);
603
+ const registerToolCall = mockedRegisterTool.mock.calls.find((call) => call[0] === 'invalidContextTest');
604
+ const registeredHandler = registerToolCall![2];
605
+
606
+ // Call the handler with an invalid functionContext (truthy but wrong type)
607
+ // This should trigger the instanceof check and create a new instance instead
608
+ const invalidContext = { notAnInstance: true, someOtherProperty: 'invalid' };
609
+ const result = await registeredHandler(invalidContext as any, {}, {});
610
+
611
+ // Should still work by creating a new instance
612
+ expect((result as any).result).toBe('class-method-called');
613
+ expect(TestClass).toBeDefined();
614
+ });
615
+
616
+ it('should handle invalid functionContext gracefully for interaction', async () => {
617
+ class TestClass {
618
+ private processData() {
619
+ return 'interaction-processed';
620
+ }
621
+
622
+ @interaction({
623
+ name: 'invalidContextInteractionTest',
624
+ endpoint: '/invalid-context-interaction-test'
625
+ })
626
+ public async invalidContextInteractionTest(_data: any) {
627
+ return { message: this.processData() };
628
+ }
629
+ }
630
+
631
+ // Get the registered handler
632
+ const mockedRegisterInteraction = jest.mocked(toolsService.registerInteraction);
633
+ const registerInteractionCall = mockedRegisterInteraction.mock.calls.find(
634
+ (call) => call[0] === 'invalidContextInteractionTest'
635
+ );
636
+ const registeredHandler = registerInteractionCall![1];
637
+
638
+ // Call the handler with an invalid functionContext (truthy but wrong type)
639
+ // This should trigger the instanceof check and create a new instance instead
640
+ const invalidContext = { notAnInstance: true, someOtherProperty: 'invalid' };
641
+ const result = await registeredHandler(invalidContext as any, {});
642
+
643
+ // Should still work by creating a new instance
644
+ expect((result as any).message).toBe('interaction-processed');
645
+ expect(TestClass).toBeDefined();
646
+ });
647
+ });
648
+ });
649
+