@optimizely-opal/opal-tool-ocp-sdk 1.1.0-beta.1 → 1.1.0-beta.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 +13 -11
- package/dist/auth/AuthUtils.d.ts +2 -5
- package/dist/auth/AuthUtils.d.ts.map +1 -1
- package/dist/auth/AuthUtils.js +5 -5
- package/dist/auth/AuthUtils.js.map +1 -1
- package/dist/decorator/Decorator.test.js +4 -4
- package/dist/decorator/Decorator.test.js.map +1 -1
- package/dist/function/GlobalToolFunction.js +2 -2
- package/dist/function/GlobalToolFunction.js.map +1 -1
- package/dist/service/Service.d.ts +10 -10
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js +8 -1
- package/dist/service/Service.js.map +1 -1
- package/dist/service/Service.test.js +169 -38
- package/dist/service/Service.test.js.map +1 -1
- package/dist/types/Models.d.ts +18 -7
- package/dist/types/Models.d.ts.map +1 -1
- package/dist/types/Models.js +1 -29
- package/dist/types/Models.js.map +1 -1
- package/package.json +1 -1
- package/src/auth/AuthUtils.ts +10 -10
- package/src/decorator/Decorator.test.ts +4 -4
- package/src/function/GlobalToolFunction.ts +2 -2
- package/src/service/Service.test.ts +177 -61
- package/src/service/Service.ts +26 -16
- package/src/types/Models.ts +24 -15
package/dist/types/Models.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.IslandResponse = exports.IslandConfig = exports.IslandAction = exports.IslandField = exports.AuthRequirement = exports.
|
|
3
|
+
exports.IslandResponse = exports.IslandConfig = exports.IslandAction = exports.IslandField = exports.AuthRequirement = exports.Parameter = exports.ParameterType = void 0;
|
|
4
4
|
/* eslint-disable max-classes-per-file */
|
|
5
5
|
/**
|
|
6
6
|
* Types of parameters supported by Opal tools
|
|
@@ -48,34 +48,6 @@ class Parameter {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
exports.Parameter = Parameter;
|
|
51
|
-
/**
|
|
52
|
-
* Credentials structure
|
|
53
|
-
*/
|
|
54
|
-
class OptiIdAuthDataCredentials {
|
|
55
|
-
customer_id;
|
|
56
|
-
instance_id;
|
|
57
|
-
access_token;
|
|
58
|
-
product_sku;
|
|
59
|
-
constructor(customer_id, instance_id, access_token, product_sku) {
|
|
60
|
-
this.customer_id = customer_id;
|
|
61
|
-
this.instance_id = instance_id;
|
|
62
|
-
this.access_token = access_token;
|
|
63
|
-
this.product_sku = product_sku;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
exports.OptiIdAuthDataCredentials = OptiIdAuthDataCredentials;
|
|
67
|
-
/**
|
|
68
|
-
* Auth data structure
|
|
69
|
-
*/
|
|
70
|
-
class OptiIdAuthData {
|
|
71
|
-
provider;
|
|
72
|
-
credentials;
|
|
73
|
-
constructor(provider, credentials) {
|
|
74
|
-
this.provider = provider;
|
|
75
|
-
this.credentials = credentials;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
exports.OptiIdAuthData = OptiIdAuthData;
|
|
79
51
|
/**
|
|
80
52
|
* Authentication requirements for an Opal tool
|
|
81
53
|
*/
|
package/dist/types/Models.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Models.js","sourceRoot":"","sources":["../../src/types/Models.ts"],"names":[],"mappings":";;;AAAA,yCAAyC;AACzC;;GAEG;AACH,IAAY,aAOX;AAPD,WAAY,aAAa;IACvB,kCAAiB,CAAA;IACjB,oCAAmB,CAAA;IACnB,kCAAiB,CAAA;IACjB,oCAAmB,CAAA;IACnB,8BAAa,CAAA;IACb,sCAAqB,CAAA;AACvB,CAAC,EAPW,aAAa,6BAAb,aAAa,QAOxB;AAED;;GAEG;AACH,MAAa,SAAS;IASX;IACA;IACA;IACA;IAXT;;;;;;OAMG;IACH,YACS,IAAY,EACZ,IAAmB,EACnB,WAAmB,EACnB,QAAiB;QAHjB,SAAI,GAAJ,IAAI,CAAQ;QACZ,SAAI,GAAJ,IAAI,CAAe;QACnB,gBAAW,GAAX,WAAW,CAAQ;QACnB,aAAQ,GAAR,QAAQ,CAAS;IACvB,CAAC;IAEJ;;OAEG;IACI,MAAM;QACX,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;IACJ,CAAC;CACF;AA1BD,8BA0BC;
|
|
1
|
+
{"version":3,"file":"Models.js","sourceRoot":"","sources":["../../src/types/Models.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/auth/AuthUtils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getAppContext, logger, Request } from '@zaiusinc/app-sdk';
|
|
2
2
|
import { getTokenVerifier } from './TokenVerifier';
|
|
3
|
-
import {
|
|
3
|
+
import { AuthData, OptiIdAuthDataCredentials } from '../types/Models';
|
|
4
4
|
import { ToolError } from '../types/ToolError';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -34,8 +34,8 @@ async function validateAccessToken(accessToken: string | undefined): Promise<voi
|
|
|
34
34
|
* @param request - The incoming request
|
|
35
35
|
* @returns object with authData and accessToken, or null if auth is missing
|
|
36
36
|
*/
|
|
37
|
-
export function extractAuthData(request: any):
|
|
38
|
-
const authData = request?.bodyJSON?.auth as
|
|
37
|
+
export function extractAuthData(request: any): OptiIdAuthDataCredentials | null {
|
|
38
|
+
const authData = request?.bodyJSON?.auth as AuthData;
|
|
39
39
|
|
|
40
40
|
if (!authData) {
|
|
41
41
|
return null;
|
|
@@ -50,7 +50,7 @@ export function extractAuthData(request: any): { authData: OptiIdAuthData; acces
|
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
return
|
|
53
|
+
return authData?.credentials as OptiIdAuthDataCredentials;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -60,8 +60,8 @@ export function extractAuthData(request: any): { authData: OptiIdAuthData; acces
|
|
|
60
60
|
* @returns object with authData and accessToken
|
|
61
61
|
* @throws {ToolError} If auth data is invalid or missing
|
|
62
62
|
*/
|
|
63
|
-
function extractAndValidateAuthData(request: any):
|
|
64
|
-
const authData = request?.bodyJSON?.auth as
|
|
63
|
+
function extractAndValidateAuthData(request: any): OptiIdAuthDataCredentials {
|
|
64
|
+
const authData = request?.bodyJSON?.auth as AuthData;
|
|
65
65
|
|
|
66
66
|
if (!authData) {
|
|
67
67
|
throw new ToolError('Forbidden', 403, 'Authentication data is required');
|
|
@@ -76,7 +76,7 @@ function extractAndValidateAuthData(request: any): { authData: OptiIdAuthData; a
|
|
|
76
76
|
throw new ToolError('Forbidden', 403, 'OptiID access token is required');
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
return
|
|
79
|
+
return authData?.credentials as OptiIdAuthDataCredentials;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -120,14 +120,14 @@ async function authenticateRequest(request: any, validateOrg: boolean): Promise<
|
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
const
|
|
123
|
+
const optiIdCredentials = extractAndValidateAuthData(request);
|
|
124
124
|
|
|
125
125
|
// Validate organization ID if required
|
|
126
126
|
if (validateOrg) {
|
|
127
|
-
validateOrganizationId(
|
|
127
|
+
validateOrganizationId(optiIdCredentials.customer_id);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
await validateAccessToken(
|
|
130
|
+
await validateAccessToken(optiIdCredentials.access_token);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
@@ -544,7 +544,7 @@ describe('Decorators', () => {
|
|
|
544
544
|
const registeredHandler = registerToolCall![2];
|
|
545
545
|
|
|
546
546
|
// Call the handler
|
|
547
|
-
const result = await registeredHandler(undefined as any, {},
|
|
547
|
+
const result = await registeredHandler(undefined as any, {}, undefined as any);
|
|
548
548
|
|
|
549
549
|
// Should be able to call class methods through 'this'
|
|
550
550
|
expect((result as any).result).toBe('class-method-called');
|
|
@@ -574,7 +574,7 @@ describe('Decorators', () => {
|
|
|
574
574
|
const registeredHandler = registerInteractionCall![1];
|
|
575
575
|
|
|
576
576
|
// Call the handler
|
|
577
|
-
const result = await registeredHandler(undefined as any, {});
|
|
577
|
+
const result = await registeredHandler(undefined as any, {}, undefined as any);
|
|
578
578
|
|
|
579
579
|
// Should be able to call class methods through 'this'
|
|
580
580
|
expect((result as any).message).toBe('interaction-processed');
|
|
@@ -606,7 +606,7 @@ describe('Decorators', () => {
|
|
|
606
606
|
// Call the handler with an invalid functionContext (truthy but wrong type)
|
|
607
607
|
// This should trigger the instanceof check and create a new instance instead
|
|
608
608
|
const invalidContext = { notAnInstance: true, someOtherProperty: 'invalid' };
|
|
609
|
-
const result = await registeredHandler(invalidContext as any, {},
|
|
609
|
+
const result = await registeredHandler(invalidContext as any, {}, undefined as any);
|
|
610
610
|
|
|
611
611
|
// Should still work by creating a new instance
|
|
612
612
|
expect((result as any).result).toBe('class-method-called');
|
|
@@ -638,7 +638,7 @@ describe('Decorators', () => {
|
|
|
638
638
|
// Call the handler with an invalid functionContext (truthy but wrong type)
|
|
639
639
|
// This should trigger the instanceof check and create a new instance instead
|
|
640
640
|
const invalidContext = { notAnInstance: true, someOtherProperty: 'invalid' };
|
|
641
|
-
const result = await registeredHandler(invalidContext as any, {});
|
|
641
|
+
const result = await registeredHandler(invalidContext as any, {}, undefined as any);
|
|
642
642
|
|
|
643
643
|
// Should still work by creating a new instance
|
|
644
644
|
expect((result as any).message).toBe('interaction-processed');
|
|
@@ -30,8 +30,8 @@ export abstract class GlobalToolFunction extends GlobalFunction {
|
|
|
30
30
|
const startTime = Date.now();
|
|
31
31
|
|
|
32
32
|
// Extract customer_id from auth data for global context attribution
|
|
33
|
-
const
|
|
34
|
-
const customerId =
|
|
33
|
+
const optiIdCredentials = extractAuthData(this.request);
|
|
34
|
+
const customerId = optiIdCredentials?.customer_id;
|
|
35
35
|
|
|
36
36
|
amendLogContext({
|
|
37
37
|
opalThreadId: this.request.headers.get('x-opal-thread-id') || '',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
2
2
|
import { toolsService, Tool, Interaction } from './Service';
|
|
3
|
-
import { Parameter, ParameterType, AuthRequirement,
|
|
3
|
+
import { Parameter, ParameterType, AuthRequirement, AuthData } from '../types/Models';
|
|
4
4
|
import { ToolError } from '../types/ToolError';
|
|
5
5
|
import { ToolFunction } from '../function/ToolFunction';
|
|
6
6
|
import { logger } from '@zaiusinc/app-sdk';
|
|
@@ -82,8 +82,8 @@ const mockKvStore = storage.kvStore;
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
describe('ToolsService', () => {
|
|
85
|
-
let mockTool: Tool
|
|
86
|
-
let mockInteraction: Interaction
|
|
85
|
+
let mockTool: Tool;
|
|
86
|
+
let mockInteraction: Interaction;
|
|
87
87
|
let mockToolFunction: ToolFunction;
|
|
88
88
|
|
|
89
89
|
beforeEach(() => {
|
|
@@ -135,11 +135,21 @@ describe('ToolsService', () => {
|
|
|
135
135
|
return map;
|
|
136
136
|
};
|
|
137
137
|
|
|
138
|
+
const defaultAuth = {
|
|
139
|
+
provider: 'OptiID',
|
|
140
|
+
credentials: {
|
|
141
|
+
customer_id: 'test-customer',
|
|
142
|
+
instance_id: 'test-instance',
|
|
143
|
+
access_token: 'test-token',
|
|
144
|
+
product_sku: 'test-sku'
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
138
148
|
const baseRequest = {
|
|
139
149
|
path: '/test-tool',
|
|
140
150
|
method: 'POST',
|
|
141
|
-
bodyJSON: { parameters: { param1: 'test-value' } },
|
|
142
|
-
body: JSON.stringify({ parameters: { param1: 'test-value' } }),
|
|
151
|
+
bodyJSON: { parameters: { param1: 'test-value' }, auth: defaultAuth },
|
|
152
|
+
body: JSON.stringify({ parameters: { param1: 'test-value' }, auth: defaultAuth }),
|
|
143
153
|
bodyData: Buffer.from(''),
|
|
144
154
|
headers: createHeadersMap(),
|
|
145
155
|
params: {},
|
|
@@ -280,7 +290,7 @@ describe('ToolsService', () => {
|
|
|
280
290
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
281
291
|
mockToolFunction, // functionContext
|
|
282
292
|
{ param1: 'test-value' },
|
|
283
|
-
|
|
293
|
+
expect.objectContaining({ provider: 'OptiID' })
|
|
284
294
|
);
|
|
285
295
|
});
|
|
286
296
|
|
|
@@ -302,7 +312,7 @@ describe('ToolsService', () => {
|
|
|
302
312
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
303
313
|
mockToolFunctionInstance, // functionContext - existing instance
|
|
304
314
|
{ param1: 'test-value' },
|
|
305
|
-
|
|
315
|
+
expect.objectContaining({ provider: 'OptiID' })
|
|
306
316
|
);
|
|
307
317
|
});
|
|
308
318
|
|
|
@@ -349,9 +359,19 @@ describe('ToolsService', () => {
|
|
|
349
359
|
'/test-toolfunction-access'
|
|
350
360
|
);
|
|
351
361
|
|
|
362
|
+
const authData = {
|
|
363
|
+
provider: 'OptiID',
|
|
364
|
+
credentials: {
|
|
365
|
+
customer_id: 'test-customer',
|
|
366
|
+
instance_id: 'test-instance',
|
|
367
|
+
access_token: 'test-token',
|
|
368
|
+
product_sku: 'test-sku'
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
352
372
|
const testRequest = createMockRequest({
|
|
353
373
|
path: '/test-toolfunction-access',
|
|
354
|
-
bodyJSON: { action: 'test' }
|
|
374
|
+
bodyJSON: { action: 'test', auth: authData }
|
|
355
375
|
});
|
|
356
376
|
|
|
357
377
|
const response = await toolsService.processRequest(testRequest, mockToolFunctionInstance);
|
|
@@ -361,19 +381,24 @@ describe('ToolsService', () => {
|
|
|
361
381
|
expect((response as any).data.success).toBe(true);
|
|
362
382
|
expect((response as any).data.requestPath).toBe('/test-path');
|
|
363
383
|
expect((response as any).data.testMethodResult).toBe('path: /test-path');
|
|
364
|
-
expect((response as any).data.receivedParams).toEqual({ action: 'test' });
|
|
384
|
+
expect((response as any).data.receivedParams).toEqual({ action: 'test', auth: authData });
|
|
365
385
|
expect(handlerThatAccessesRequest).toHaveBeenCalledWith(
|
|
366
386
|
mockToolFunctionInstance, // functionContext is the ToolFunction instance
|
|
367
|
-
{ action: 'test' },
|
|
368
|
-
|
|
387
|
+
{ action: 'test', auth: authData },
|
|
388
|
+
authData
|
|
369
389
|
);
|
|
370
390
|
});
|
|
371
391
|
|
|
372
392
|
it('should execute tool with OptiID auth data when provided', async () => {
|
|
373
|
-
const authData =
|
|
374
|
-
'
|
|
375
|
-
|
|
376
|
-
|
|
393
|
+
const authData: AuthData = {
|
|
394
|
+
provider: 'OptiID',
|
|
395
|
+
credentials: {
|
|
396
|
+
customer_id: 'customer123',
|
|
397
|
+
instance_id: 'instance123',
|
|
398
|
+
access_token: 'token123',
|
|
399
|
+
product_sku: 'sku123'
|
|
400
|
+
}
|
|
401
|
+
};
|
|
377
402
|
|
|
378
403
|
const requestWithAuth = createMockRequest({
|
|
379
404
|
bodyJSON: {
|
|
@@ -397,9 +422,19 @@ describe('ToolsService', () => {
|
|
|
397
422
|
});
|
|
398
423
|
|
|
399
424
|
it('should handle request body without parameters wrapper', async () => {
|
|
425
|
+
const authData = {
|
|
426
|
+
provider: 'OptiID',
|
|
427
|
+
credentials: {
|
|
428
|
+
customer_id: 'test-customer',
|
|
429
|
+
instance_id: 'test-instance',
|
|
430
|
+
access_token: 'test-token',
|
|
431
|
+
product_sku: 'test-sku'
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
400
435
|
const requestWithoutWrapper = createMockRequest({
|
|
401
|
-
bodyJSON: { param1: 'test-value' },
|
|
402
|
-
body: JSON.stringify({ param1: 'test-value' })
|
|
436
|
+
bodyJSON: { param1: 'test-value', auth: authData },
|
|
437
|
+
body: JSON.stringify({ param1: 'test-value', auth: authData })
|
|
403
438
|
});
|
|
404
439
|
|
|
405
440
|
const response = await toolsService.processRequest(requestWithoutWrapper, mockToolFunction);
|
|
@@ -407,8 +442,8 @@ describe('ToolsService', () => {
|
|
|
407
442
|
expect(response.status).toBe(200);
|
|
408
443
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
409
444
|
mockToolFunction, // functionContext
|
|
410
|
-
{ param1: 'test-value' },
|
|
411
|
-
|
|
445
|
+
{ param1: 'test-value', auth: authData },
|
|
446
|
+
authData
|
|
412
447
|
);
|
|
413
448
|
});
|
|
414
449
|
|
|
@@ -474,36 +509,62 @@ describe('ToolsService', () => {
|
|
|
474
509
|
});
|
|
475
510
|
|
|
476
511
|
it('should execute interaction successfully with data', async () => {
|
|
512
|
+
const authData = {
|
|
513
|
+
provider: 'OptiID',
|
|
514
|
+
credentials: {
|
|
515
|
+
customer_id: 'test-customer',
|
|
516
|
+
instance_id: 'test-instance',
|
|
517
|
+
access_token: 'test-token',
|
|
518
|
+
product_sku: 'test-sku'
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
477
522
|
const interactionRequest = createMockRequest({
|
|
478
523
|
path: '/test-interaction',
|
|
479
|
-
bodyJSON: { data: { param1: 'test-value' } },
|
|
480
|
-
body: JSON.stringify({ data: { param1: 'test-value' } })
|
|
524
|
+
bodyJSON: { data: { param1: 'test-value' }, auth: authData },
|
|
525
|
+
body: JSON.stringify({ data: { param1: 'test-value' }, auth: authData })
|
|
481
526
|
});
|
|
482
527
|
|
|
483
528
|
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
484
529
|
|
|
485
530
|
expect(response.status).toBe(200);
|
|
486
|
-
expect(mockInteraction.handler).toHaveBeenCalledWith(mockToolFunction, { param1: 'test-value' },
|
|
531
|
+
expect(mockInteraction.handler).toHaveBeenCalledWith(mockToolFunction, { param1: 'test-value' }, authData);
|
|
487
532
|
});
|
|
488
533
|
|
|
489
534
|
it('should handle interaction request body without data wrapper', async () => {
|
|
535
|
+
const authData = {
|
|
536
|
+
provider: 'OptiID',
|
|
537
|
+
credentials: {
|
|
538
|
+
customer_id: 'test-customer',
|
|
539
|
+
instance_id: 'test-instance',
|
|
540
|
+
access_token: 'test-token',
|
|
541
|
+
product_sku: 'test-sku'
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
490
545
|
const interactionRequest = createMockRequest({
|
|
491
546
|
path: '/test-interaction',
|
|
492
|
-
bodyJSON: { param1: 'test-value' },
|
|
493
|
-
body: JSON.stringify({ param1: 'test-value' })
|
|
547
|
+
bodyJSON: { param1: 'test-value', auth: authData },
|
|
548
|
+
body: JSON.stringify({ param1: 'test-value', auth: authData })
|
|
494
549
|
});
|
|
495
550
|
|
|
496
551
|
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
497
552
|
|
|
498
553
|
expect(response.status).toBe(200);
|
|
499
|
-
expect(mockInteraction.handler)
|
|
554
|
+
expect(mockInteraction.handler)
|
|
555
|
+
.toHaveBeenCalledWith(mockToolFunction, { param1: 'test-value', auth: authData }, authData);
|
|
500
556
|
});
|
|
501
557
|
|
|
502
558
|
it('should execute interaction with OptiID auth data when provided', async () => {
|
|
503
|
-
const authData =
|
|
504
|
-
'
|
|
505
|
-
|
|
506
|
-
|
|
559
|
+
const authData = {
|
|
560
|
+
provider: 'OptiID',
|
|
561
|
+
credentials: {
|
|
562
|
+
customer_id: 'customer123',
|
|
563
|
+
instance_id: 'instance123',
|
|
564
|
+
access_token: 'token123',
|
|
565
|
+
product_sku: 'sku123'
|
|
566
|
+
}
|
|
567
|
+
};
|
|
507
568
|
|
|
508
569
|
const interactionRequest = createMockRequest({
|
|
509
570
|
path: '/test-interaction',
|
|
@@ -528,10 +589,15 @@ describe('ToolsService', () => {
|
|
|
528
589
|
});
|
|
529
590
|
|
|
530
591
|
it('should handle interaction request without data wrapper but with auth data', async () => {
|
|
531
|
-
const authData =
|
|
532
|
-
'
|
|
533
|
-
|
|
534
|
-
|
|
592
|
+
const authData = {
|
|
593
|
+
provider: 'OptiID',
|
|
594
|
+
credentials: {
|
|
595
|
+
customer_id: 'customer123',
|
|
596
|
+
instance_id: 'instance123',
|
|
597
|
+
access_token: 'token123',
|
|
598
|
+
product_sku: 'sku123'
|
|
599
|
+
}
|
|
600
|
+
};
|
|
535
601
|
|
|
536
602
|
const interactionRequest = createMockRequest({
|
|
537
603
|
path: '/test-interaction',
|
|
@@ -559,12 +625,21 @@ describe('ToolsService', () => {
|
|
|
559
625
|
});
|
|
560
626
|
|
|
561
627
|
it('should throw error when interaction handler throws a regular error', async () => {
|
|
628
|
+
const authData = {
|
|
629
|
+
provider: 'OptiID',
|
|
630
|
+
credentials: {
|
|
631
|
+
customer_id: 'test-customer',
|
|
632
|
+
instance_id: 'test-instance',
|
|
633
|
+
access_token: 'test-token',
|
|
634
|
+
product_sku: 'test-sku'
|
|
635
|
+
}
|
|
636
|
+
};
|
|
562
637
|
const errorMessage = 'Interaction execution failed';
|
|
563
638
|
jest.mocked(mockInteraction.handler).mockRejectedValueOnce(new Error(errorMessage));
|
|
564
639
|
|
|
565
640
|
const interactionRequest = createMockRequest({
|
|
566
641
|
path: '/test-interaction',
|
|
567
|
-
bodyJSON: { data: { param1: 'test-value' } }
|
|
642
|
+
bodyJSON: { data: { param1: 'test-value' }, auth: authData }
|
|
568
643
|
});
|
|
569
644
|
|
|
570
645
|
await expect(toolsService.processRequest(interactionRequest, mockToolFunction))
|
|
@@ -572,12 +647,21 @@ describe('ToolsService', () => {
|
|
|
572
647
|
});
|
|
573
648
|
|
|
574
649
|
it('should throw ToolError when interaction handler throws ToolError', async () => {
|
|
650
|
+
const authData = {
|
|
651
|
+
provider: 'OptiID',
|
|
652
|
+
credentials: {
|
|
653
|
+
customer_id: 'test-customer',
|
|
654
|
+
instance_id: 'test-instance',
|
|
655
|
+
access_token: 'test-token',
|
|
656
|
+
product_sku: 'test-sku'
|
|
657
|
+
}
|
|
658
|
+
};
|
|
575
659
|
const toolError = new ToolError('Webhook validation failed', 400, 'Invalid signature');
|
|
576
660
|
jest.mocked(mockInteraction.handler).mockRejectedValueOnce(toolError);
|
|
577
661
|
|
|
578
662
|
const interactionRequest = createMockRequest({
|
|
579
663
|
path: '/test-interaction',
|
|
580
|
-
bodyJSON: { data: { param1: 'test-value' } }
|
|
664
|
+
bodyJSON: { data: { param1: 'test-value' }, auth: authData }
|
|
581
665
|
});
|
|
582
666
|
|
|
583
667
|
await expect(toolsService.processRequest(interactionRequest, mockToolFunction))
|
|
@@ -627,7 +711,7 @@ describe('ToolsService', () => {
|
|
|
627
711
|
});
|
|
628
712
|
|
|
629
713
|
describe('edge cases', () => {
|
|
630
|
-
it('should
|
|
714
|
+
it('should throw 403 when request has null bodyJSON (no auth data)', async () => {
|
|
631
715
|
// Create a tool without required parameters
|
|
632
716
|
const toolWithoutRequiredParams = {
|
|
633
717
|
name: 'no_required_params_tool',
|
|
@@ -651,13 +735,18 @@ describe('ToolsService', () => {
|
|
|
651
735
|
body: null
|
|
652
736
|
});
|
|
653
737
|
|
|
654
|
-
|
|
738
|
+
await expect(toolsService.processRequest(requestWithNullBody, mockToolFunction))
|
|
739
|
+
.rejects.toThrow(ToolError);
|
|
655
740
|
|
|
656
|
-
|
|
657
|
-
|
|
741
|
+
try {
|
|
742
|
+
await toolsService.processRequest(requestWithNullBody, mockToolFunction);
|
|
743
|
+
} catch (error) {
|
|
744
|
+
expect((error as ToolError).status).toBe(403);
|
|
745
|
+
expect((error as ToolError).message).toContain('Authentication data is required');
|
|
746
|
+
}
|
|
658
747
|
});
|
|
659
748
|
|
|
660
|
-
it('should
|
|
749
|
+
it('should throw 403 when request has undefined bodyJSON (no auth data)', async () => {
|
|
661
750
|
// Create a tool without required parameters
|
|
662
751
|
const toolWithoutRequiredParams = {
|
|
663
752
|
name: 'no_required_params_tool_2',
|
|
@@ -681,10 +770,15 @@ describe('ToolsService', () => {
|
|
|
681
770
|
body: undefined
|
|
682
771
|
});
|
|
683
772
|
|
|
684
|
-
|
|
773
|
+
await expect(toolsService.processRequest(requestWithUndefinedBody, mockToolFunction))
|
|
774
|
+
.rejects.toThrow(ToolError);
|
|
685
775
|
|
|
686
|
-
|
|
687
|
-
|
|
776
|
+
try {
|
|
777
|
+
await toolsService.processRequest(requestWithUndefinedBody, mockToolFunction);
|
|
778
|
+
} catch (error) {
|
|
779
|
+
expect((error as ToolError).status).toBe(403);
|
|
780
|
+
expect((error as ToolError).message).toContain('Authentication data is required');
|
|
781
|
+
}
|
|
688
782
|
});
|
|
689
783
|
|
|
690
784
|
it('should extract auth data from bodyJSON when body exists', async () => {
|
|
@@ -696,10 +790,15 @@ describe('ToolsService', () => {
|
|
|
696
790
|
mockTool.endpoint
|
|
697
791
|
);
|
|
698
792
|
|
|
699
|
-
const authData =
|
|
700
|
-
'
|
|
701
|
-
|
|
702
|
-
|
|
793
|
+
const authData = {
|
|
794
|
+
provider: 'OptiID',
|
|
795
|
+
credentials: {
|
|
796
|
+
customer_id: 'customer123',
|
|
797
|
+
instance_id: 'instance123',
|
|
798
|
+
access_token: 'token123',
|
|
799
|
+
product_sku: 'sku123'
|
|
800
|
+
}
|
|
801
|
+
};
|
|
703
802
|
|
|
704
803
|
const requestWithAuth = createMockRequest({
|
|
705
804
|
bodyJSON: {
|
|
@@ -722,7 +821,7 @@ describe('ToolsService', () => {
|
|
|
722
821
|
);
|
|
723
822
|
});
|
|
724
823
|
|
|
725
|
-
it('should
|
|
824
|
+
it('should throw 403 when auth data is missing for tool with auth requirements', async () => {
|
|
726
825
|
toolsService.registerTool(
|
|
727
826
|
mockTool.name,
|
|
728
827
|
mockTool.description,
|
|
@@ -741,14 +840,15 @@ describe('ToolsService', () => {
|
|
|
741
840
|
})
|
|
742
841
|
});
|
|
743
842
|
|
|
744
|
-
|
|
843
|
+
await expect(toolsService.processRequest(requestWithoutAuth, mockToolFunction))
|
|
844
|
+
.rejects.toThrow(ToolError);
|
|
745
845
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
846
|
+
try {
|
|
847
|
+
await toolsService.processRequest(requestWithoutAuth, mockToolFunction);
|
|
848
|
+
} catch (error) {
|
|
849
|
+
expect((error as ToolError).status).toBe(403);
|
|
850
|
+
expect((error as ToolError).message).toContain('Authentication data is required');
|
|
851
|
+
}
|
|
752
852
|
});
|
|
753
853
|
|
|
754
854
|
it('should handle auth extraction when body is falsy but bodyJSON has auth', async () => {
|
|
@@ -760,10 +860,15 @@ describe('ToolsService', () => {
|
|
|
760
860
|
mockTool.endpoint
|
|
761
861
|
);
|
|
762
862
|
|
|
763
|
-
const authData =
|
|
764
|
-
'
|
|
765
|
-
|
|
766
|
-
|
|
863
|
+
const authData = {
|
|
864
|
+
provider: 'OptiID',
|
|
865
|
+
credentials: {
|
|
866
|
+
customer_id: 'customer123',
|
|
867
|
+
instance_id: 'instance123',
|
|
868
|
+
access_token: 'token123',
|
|
869
|
+
product_sku: 'sku123'
|
|
870
|
+
}
|
|
871
|
+
};
|
|
767
872
|
|
|
768
873
|
const requestWithAuthButNoBody = createMockRequest({
|
|
769
874
|
bodyJSON: {
|
|
@@ -864,13 +969,24 @@ describe('ToolsService', () => {
|
|
|
864
969
|
toolWithoutParams.endpoint
|
|
865
970
|
);
|
|
866
971
|
|
|
972
|
+
const authData = {
|
|
973
|
+
provider: 'OptiID',
|
|
974
|
+
credentials: {
|
|
975
|
+
customer_id: 'test-customer',
|
|
976
|
+
instance_id: 'test-instance',
|
|
977
|
+
access_token: 'test-token',
|
|
978
|
+
product_sku: 'test-sku'
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
|
|
867
982
|
// Send request with any data (should be ignored)
|
|
868
983
|
const request = createMockRequest({
|
|
869
984
|
path: '/no-params-tool',
|
|
870
985
|
bodyJSON: {
|
|
871
986
|
parameters: {
|
|
872
987
|
unexpected: 'value'
|
|
873
|
-
}
|
|
988
|
+
},
|
|
989
|
+
auth: authData
|
|
874
990
|
}
|
|
875
991
|
});
|
|
876
992
|
|
|
@@ -880,7 +996,7 @@ describe('ToolsService', () => {
|
|
|
880
996
|
expect(toolWithoutParams.handler).toHaveBeenCalledWith(
|
|
881
997
|
mockToolFunction,
|
|
882
998
|
{ unexpected: 'value' },
|
|
883
|
-
|
|
999
|
+
authData
|
|
884
1000
|
);
|
|
885
1001
|
});
|
|
886
1002
|
});
|