@optimizely-opal/opal-tool-ocp-sdk 0.0.0-beta.5 → 0.0.0-beta.6
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/dist/decorator/Decorator.test.js.map +1 -1
- package/dist/function/ToolFunction.d.ts.map +1 -1
- package/dist/function/ToolFunction.js +6 -1
- package/dist/function/ToolFunction.js.map +1 -1
- package/dist/function/ToolFunction.test.js +127 -5
- package/dist/function/ToolFunction.test.js.map +1 -1
- package/dist/service/Service.d.ts +8 -7
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js +1 -5
- package/dist/service/Service.js.map +1 -1
- package/dist/service/Service.test.js +47 -63
- package/dist/service/Service.test.js.map +1 -1
- package/package.json +3 -3
- package/src/decorator/Decorator.test.ts +4 -4
- package/src/function/ToolFunction.test.ts +148 -5
- package/src/function/ToolFunction.ts +7 -2
- package/src/service/Service.test.ts +48 -70
- package/src/service/Service.ts +6 -9
|
@@ -26,11 +26,13 @@ jest.mock('@zaiusinc/app-sdk', () => ({
|
|
|
26
26
|
bodyJSON: data,
|
|
27
27
|
bodyAsU8Array: new Uint8Array()
|
|
28
28
|
})),
|
|
29
|
+
amendLogContext: jest.fn(),
|
|
29
30
|
}));
|
|
30
31
|
|
|
31
32
|
// Create a concrete implementation for testing
|
|
32
33
|
class TestToolFunction extends ToolFunction {
|
|
33
34
|
private mockValidateBearerToken: jest.MockedFunction<(token: string) => boolean>;
|
|
35
|
+
private mockReady: jest.MockedFunction<() => Promise<boolean>>;
|
|
34
36
|
|
|
35
37
|
public constructor(request?: any) {
|
|
36
38
|
super(request || {}); // Pass the request parameter properly
|
|
@@ -38,6 +40,7 @@ class TestToolFunction extends ToolFunction {
|
|
|
38
40
|
(this as any).request = request;
|
|
39
41
|
|
|
40
42
|
this.mockValidateBearerToken = jest.fn().mockReturnValue(true);
|
|
43
|
+
this.mockReady = jest.fn().mockResolvedValue(true);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
// Override the concrete method with mock implementation for testing
|
|
@@ -45,6 +48,11 @@ class TestToolFunction extends ToolFunction {
|
|
|
45
48
|
return this.mockValidateBearerToken(bearerToken);
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
// Override the ready method with mock implementation for testing
|
|
52
|
+
protected ready(): Promise<boolean> {
|
|
53
|
+
return this.mockReady();
|
|
54
|
+
}
|
|
55
|
+
|
|
48
56
|
// Expose request and validation mock for testing
|
|
49
57
|
public getRequest() {
|
|
50
58
|
return (this as any).request;
|
|
@@ -53,6 +61,10 @@ class TestToolFunction extends ToolFunction {
|
|
|
53
61
|
public getMockValidateBearerToken() {
|
|
54
62
|
return this.mockValidateBearerToken;
|
|
55
63
|
}
|
|
64
|
+
|
|
65
|
+
public getMockReady() {
|
|
66
|
+
return this.mockReady;
|
|
67
|
+
}
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
describe('ToolFunction', () => {
|
|
@@ -81,6 +93,137 @@ describe('ToolFunction', () => {
|
|
|
81
93
|
toolFunction = new TestToolFunction(mockRequest);
|
|
82
94
|
});
|
|
83
95
|
|
|
96
|
+
describe('/ready endpoint', () => {
|
|
97
|
+
it('should return ready: true when ready method returns true', async () => {
|
|
98
|
+
// Arrange
|
|
99
|
+
const readyRequest = {
|
|
100
|
+
headers: new Map(),
|
|
101
|
+
method: 'GET',
|
|
102
|
+
path: '/ready'
|
|
103
|
+
};
|
|
104
|
+
toolFunction = new TestToolFunction(readyRequest);
|
|
105
|
+
toolFunction.getMockReady().mockResolvedValue(true);
|
|
106
|
+
|
|
107
|
+
// Act
|
|
108
|
+
const result = await toolFunction.perform();
|
|
109
|
+
|
|
110
|
+
// Assert
|
|
111
|
+
expect(toolFunction.getMockReady()).toHaveBeenCalledTimes(1);
|
|
112
|
+
expect(result).toEqual(new Response(200, { ready: true }));
|
|
113
|
+
expect(mockProcessRequest).not.toHaveBeenCalled(); // Should not call service
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should return ready: false when ready method returns false', async () => {
|
|
117
|
+
// Arrange
|
|
118
|
+
const readyRequest = {
|
|
119
|
+
headers: new Map(),
|
|
120
|
+
method: 'GET',
|
|
121
|
+
path: '/ready'
|
|
122
|
+
};
|
|
123
|
+
toolFunction = new TestToolFunction(readyRequest);
|
|
124
|
+
toolFunction.getMockReady().mockResolvedValue(false);
|
|
125
|
+
|
|
126
|
+
// Act
|
|
127
|
+
const result = await toolFunction.perform();
|
|
128
|
+
|
|
129
|
+
// Assert
|
|
130
|
+
expect(toolFunction.getMockReady()).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(result).toEqual(new Response(200, { ready: false }));
|
|
132
|
+
expect(mockProcessRequest).not.toHaveBeenCalled(); // Should not call service
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should handle ready method throwing an error', async () => {
|
|
136
|
+
// Arrange
|
|
137
|
+
const readyRequest = {
|
|
138
|
+
headers: new Map(),
|
|
139
|
+
method: 'GET',
|
|
140
|
+
path: '/ready'
|
|
141
|
+
};
|
|
142
|
+
toolFunction = new TestToolFunction(readyRequest);
|
|
143
|
+
toolFunction.getMockReady().mockRejectedValue(new Error('Ready check failed'));
|
|
144
|
+
|
|
145
|
+
// Act & Assert
|
|
146
|
+
await expect(toolFunction.perform()).rejects.toThrow('Ready check failed');
|
|
147
|
+
expect(toolFunction.getMockReady()).toHaveBeenCalledTimes(1);
|
|
148
|
+
expect(mockProcessRequest).not.toHaveBeenCalled(); // Should not call service
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should handle /ready endpoint after bearer token validation passes', async () => {
|
|
152
|
+
// Arrange
|
|
153
|
+
const readyRequest = {
|
|
154
|
+
headers: new Map([['authorization', 'Bearer valid-token']]),
|
|
155
|
+
method: 'GET',
|
|
156
|
+
path: '/ready'
|
|
157
|
+
};
|
|
158
|
+
toolFunction = new TestToolFunction(readyRequest);
|
|
159
|
+
toolFunction.getMockReady().mockResolvedValue(true);
|
|
160
|
+
mockExtractBearerToken.mockReturnValue('valid-token');
|
|
161
|
+
toolFunction.getMockValidateBearerToken().mockReturnValue(true); // Make sure auth passes so we reach /ready
|
|
162
|
+
|
|
163
|
+
// Act
|
|
164
|
+
const result = await toolFunction.perform();
|
|
165
|
+
|
|
166
|
+
// Assert
|
|
167
|
+
expect(toolFunction.getMockReady()).toHaveBeenCalledTimes(1);
|
|
168
|
+
expect(result).toEqual(new Response(200, { ready: true }));
|
|
169
|
+
expect(mockExtractBearerToken).toHaveBeenCalledWith(readyRequest.headers); // Auth is checked first
|
|
170
|
+
expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith('valid-token'); // Auth validation happens
|
|
171
|
+
expect(mockProcessRequest).not.toHaveBeenCalled(); // Should not call service
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return 403 when bearer token validation fails even for /ready endpoint', async () => {
|
|
175
|
+
// Arrange
|
|
176
|
+
const readyRequest = {
|
|
177
|
+
headers: new Map([['authorization', 'Bearer invalid-token']]),
|
|
178
|
+
method: 'GET',
|
|
179
|
+
path: '/ready'
|
|
180
|
+
};
|
|
181
|
+
toolFunction = new TestToolFunction(readyRequest);
|
|
182
|
+
toolFunction.getMockReady().mockResolvedValue(true);
|
|
183
|
+
mockExtractBearerToken.mockReturnValue('invalid-token');
|
|
184
|
+
toolFunction.getMockValidateBearerToken().mockReturnValue(false); // Auth fails
|
|
185
|
+
|
|
186
|
+
// Act
|
|
187
|
+
const result = await toolFunction.perform();
|
|
188
|
+
|
|
189
|
+
// Assert
|
|
190
|
+
expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
|
|
191
|
+
expect(mockExtractBearerToken).toHaveBeenCalledWith(readyRequest.headers);
|
|
192
|
+
expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith('invalid-token');
|
|
193
|
+
expect(toolFunction.getMockReady()).not.toHaveBeenCalled(); // Should not reach ready check
|
|
194
|
+
expect(mockProcessRequest).not.toHaveBeenCalled(); // Should not call service
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should use default ready implementation when not overridden', async () => {
|
|
198
|
+
// Create a class that doesn't override ready method
|
|
199
|
+
class DefaultReadyToolFunction extends ToolFunction {
|
|
200
|
+
public constructor(request?: any) {
|
|
201
|
+
super(request || {});
|
|
202
|
+
(this as any).request = request;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public getRequest() {
|
|
206
|
+
return (this as any).request;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Arrange
|
|
211
|
+
const readyRequest = {
|
|
212
|
+
headers: new Map(),
|
|
213
|
+
method: 'GET',
|
|
214
|
+
path: '/ready'
|
|
215
|
+
};
|
|
216
|
+
const defaultToolFunction = new DefaultReadyToolFunction(readyRequest);
|
|
217
|
+
|
|
218
|
+
// Act
|
|
219
|
+
const result = await defaultToolFunction.perform();
|
|
220
|
+
|
|
221
|
+
// Assert - Default implementation should return true
|
|
222
|
+
expect(result).toEqual(new Response(200, { ready: true }));
|
|
223
|
+
expect(mockProcessRequest).not.toHaveBeenCalled(); // Should not call service
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
84
227
|
describe('bearer token validation', () => {
|
|
85
228
|
it('should extract bearer token from headers and validate it', async () => {
|
|
86
229
|
// Arrange
|
|
@@ -95,7 +238,7 @@ describe('ToolFunction', () => {
|
|
|
95
238
|
// Assert
|
|
96
239
|
expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
|
|
97
240
|
expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(bearerToken);
|
|
98
|
-
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest,
|
|
241
|
+
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, toolFunction);
|
|
99
242
|
expect(result).toBe(mockResponse);
|
|
100
243
|
});
|
|
101
244
|
|
|
@@ -129,7 +272,7 @@ describe('ToolFunction', () => {
|
|
|
129
272
|
// Assert
|
|
130
273
|
expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
|
|
131
274
|
expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(complexToken);
|
|
132
|
-
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest,
|
|
275
|
+
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, toolFunction);
|
|
133
276
|
expect(result).toBe(mockResponse);
|
|
134
277
|
});
|
|
135
278
|
|
|
@@ -189,7 +332,7 @@ describe('ToolFunction', () => {
|
|
|
189
332
|
// Assert
|
|
190
333
|
expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
|
|
191
334
|
expect(toolFunction.getMockValidateBearerToken()).not.toHaveBeenCalled();
|
|
192
|
-
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest,
|
|
335
|
+
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, toolFunction);
|
|
193
336
|
expect(result).toBe(mockResponse);
|
|
194
337
|
});
|
|
195
338
|
|
|
@@ -204,7 +347,7 @@ describe('ToolFunction', () => {
|
|
|
204
347
|
// Assert
|
|
205
348
|
expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
|
|
206
349
|
expect(toolFunction.getMockValidateBearerToken()).not.toHaveBeenCalled();
|
|
207
|
-
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest,
|
|
350
|
+
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, toolFunction);
|
|
208
351
|
expect(result).toBe(mockResponse);
|
|
209
352
|
});
|
|
210
353
|
|
|
@@ -232,7 +375,7 @@ describe('ToolFunction', () => {
|
|
|
232
375
|
|
|
233
376
|
// Assert - Default implementation should return true and allow request to proceed
|
|
234
377
|
expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
|
|
235
|
-
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest,
|
|
378
|
+
expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest, defaultToolFunction);
|
|
236
379
|
expect(result).toBe(mockResponse);
|
|
237
380
|
});
|
|
238
381
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Function, Response } from '@zaiusinc/app-sdk';
|
|
1
|
+
import { Function, Response, amendLogContext } from '@zaiusinc/app-sdk';
|
|
2
2
|
import { toolsService } from '../service/Service';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -23,13 +23,18 @@ export abstract class ToolFunction extends Function {
|
|
|
23
23
|
* @returns Response as the HTTP response
|
|
24
24
|
*/
|
|
25
25
|
public async perform(): Promise<Response> {
|
|
26
|
+
amendLogContext({ opalThreadId: this.request.headers.get('x-opal-thread-id') || '' });
|
|
26
27
|
const bearerToken = toolsService.extractBearerToken(this.request.headers);
|
|
27
28
|
if (bearerToken && !this.validateBearerToken(bearerToken)) {
|
|
28
29
|
return new Response(403, { error: 'Forbidden' });
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
if (this.request.path === '/ready') {
|
|
33
|
+
const isReady = await this.ready();
|
|
34
|
+
return new Response(200, { ready: isReady });
|
|
35
|
+
}
|
|
31
36
|
// Pass 'this' as context so decorated methods can use the existing instance
|
|
32
|
-
return toolsService.processRequest(this.request, this
|
|
37
|
+
return toolsService.processRequest(this.request, this);
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
/**
|
|
@@ -22,7 +22,7 @@ jest.mock('@zaiusinc/app-sdk', () => ({
|
|
|
22
22
|
describe('ToolsService', () => {
|
|
23
23
|
let mockTool: Tool<unknown>;
|
|
24
24
|
let mockInteraction: Interaction<unknown>;
|
|
25
|
-
let
|
|
25
|
+
let mockToolFunction: ToolFunction;
|
|
26
26
|
|
|
27
27
|
beforeEach(() => {
|
|
28
28
|
// Clear registered functions and interactions before each test
|
|
@@ -32,8 +32,13 @@ describe('ToolsService', () => {
|
|
|
32
32
|
// Reset all mocks
|
|
33
33
|
jest.clearAllMocks();
|
|
34
34
|
|
|
35
|
-
// Create mock
|
|
36
|
-
|
|
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;
|
|
37
42
|
|
|
38
43
|
// Create mock tool handler
|
|
39
44
|
const mockToolHandler = jest.fn().mockResolvedValue({ result: 'success' });
|
|
@@ -98,7 +103,7 @@ describe('ToolsService', () => {
|
|
|
98
103
|
);
|
|
99
104
|
|
|
100
105
|
const discoveryRequest = createMockRequest({ path: '/discovery' });
|
|
101
|
-
const response = await toolsService.processRequest(discoveryRequest,
|
|
106
|
+
const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
|
|
102
107
|
|
|
103
108
|
expect(response.status).toBe(200);
|
|
104
109
|
expect(response).toHaveProperty('bodyJSON');
|
|
@@ -123,7 +128,7 @@ describe('ToolsService', () => {
|
|
|
123
128
|
|
|
124
129
|
it('should return empty functions array when no tools are registered', async () => {
|
|
125
130
|
const discoveryRequest = createMockRequest({ path: '/discovery' });
|
|
126
|
-
const response = await toolsService.processRequest(discoveryRequest,
|
|
131
|
+
const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
|
|
127
132
|
|
|
128
133
|
expect(response.status).toBe(200);
|
|
129
134
|
expect(response.bodyAsU8Array).toBeDefined();
|
|
@@ -157,7 +162,7 @@ describe('ToolsService', () => {
|
|
|
157
162
|
);
|
|
158
163
|
|
|
159
164
|
const discoveryRequest = createMockRequest({ path: '/discovery' });
|
|
160
|
-
const response = await toolsService.processRequest(discoveryRequest,
|
|
165
|
+
const response = await toolsService.processRequest(discoveryRequest, mockToolFunction);
|
|
161
166
|
|
|
162
167
|
expect(response.status).toBe(200);
|
|
163
168
|
|
|
@@ -190,37 +195,6 @@ describe('ToolsService', () => {
|
|
|
190
195
|
});
|
|
191
196
|
});
|
|
192
197
|
|
|
193
|
-
describe('/ready endpoint', () => {
|
|
194
|
-
it('should return ready: true when readyCheck returns true', async () => {
|
|
195
|
-
const readyRequest = createMockRequest({ path: '/ready' });
|
|
196
|
-
const mockReadyCheckTrue = jest.fn().mockResolvedValue(true);
|
|
197
|
-
const response = await toolsService.processRequest(readyRequest, mockReadyCheckTrue);
|
|
198
|
-
|
|
199
|
-
expect(response.status).toBe(200);
|
|
200
|
-
expect(response.bodyJSON).toEqual({ ready: true });
|
|
201
|
-
expect(mockReadyCheckTrue).toHaveBeenCalledTimes(1);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should return ready: false when readyCheck returns false', async () => {
|
|
205
|
-
const readyRequest = createMockRequest({ path: '/ready' });
|
|
206
|
-
const mockReadyCheckFalse = jest.fn().mockResolvedValue(false);
|
|
207
|
-
const response = await toolsService.processRequest(readyRequest, mockReadyCheckFalse);
|
|
208
|
-
|
|
209
|
-
expect(response.status).toBe(200);
|
|
210
|
-
expect(response.bodyJSON).toEqual({ ready: false });
|
|
211
|
-
expect(mockReadyCheckFalse).toHaveBeenCalledTimes(1);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('should handle readyCheck throwing an error', async () => {
|
|
215
|
-
const readyRequest = createMockRequest({ path: '/ready' });
|
|
216
|
-
const mockReadyCheckError = jest.fn().mockRejectedValue(new Error('Ready check failed'));
|
|
217
|
-
|
|
218
|
-
await expect(toolsService.processRequest(readyRequest, mockReadyCheckError)).
|
|
219
|
-
rejects.toThrow('Ready check failed');
|
|
220
|
-
expect(mockReadyCheckError).toHaveBeenCalledTimes(1);
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
198
|
describe('tool execution', () => {
|
|
225
199
|
beforeEach(() => {
|
|
226
200
|
toolsService.registerTool(
|
|
@@ -234,11 +208,11 @@ describe('ToolsService', () => {
|
|
|
234
208
|
|
|
235
209
|
it('should execute tool successfully with parameters', async () => {
|
|
236
210
|
const mockRequest = createMockRequest();
|
|
237
|
-
const response = await toolsService.processRequest(mockRequest,
|
|
211
|
+
const response = await toolsService.processRequest(mockRequest, mockToolFunction);
|
|
238
212
|
|
|
239
213
|
expect(response.status).toBe(200);
|
|
240
214
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
241
|
-
|
|
215
|
+
mockToolFunction, // functionContext
|
|
242
216
|
{ param1: 'test-value' },
|
|
243
217
|
undefined
|
|
244
218
|
);
|
|
@@ -248,11 +222,15 @@ describe('ToolsService', () => {
|
|
|
248
222
|
// Create a mock ToolFunction instance
|
|
249
223
|
const mockToolFunctionInstance = {
|
|
250
224
|
someProperty: 'test-value',
|
|
251
|
-
someMethod: jest.fn()
|
|
252
|
-
|
|
225
|
+
someMethod: jest.fn(),
|
|
226
|
+
ready: jest.fn().mockResolvedValue(true),
|
|
227
|
+
perform: jest.fn(),
|
|
228
|
+
validateBearerToken: jest.fn().mockReturnValue(true),
|
|
229
|
+
request: {} as any
|
|
230
|
+
} as any;
|
|
253
231
|
|
|
254
232
|
const mockRequest = createMockRequest();
|
|
255
|
-
const response = await toolsService.processRequest(mockRequest,
|
|
233
|
+
const response = await toolsService.processRequest(mockRequest, mockToolFunctionInstance);
|
|
256
234
|
|
|
257
235
|
expect(response.status).toBe(200);
|
|
258
236
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
@@ -310,7 +288,7 @@ describe('ToolsService', () => {
|
|
|
310
288
|
bodyJSON: { action: 'test' }
|
|
311
289
|
});
|
|
312
290
|
|
|
313
|
-
const response = await toolsService.processRequest(testRequest,
|
|
291
|
+
const response = await toolsService.processRequest(testRequest, mockToolFunctionInstance);
|
|
314
292
|
|
|
315
293
|
expect(response.status).toBe(200);
|
|
316
294
|
expect((response as any).data).toBeDefined();
|
|
@@ -342,11 +320,11 @@ describe('ToolsService', () => {
|
|
|
342
320
|
})
|
|
343
321
|
});
|
|
344
322
|
|
|
345
|
-
const response = await toolsService.processRequest(requestWithAuth,
|
|
323
|
+
const response = await toolsService.processRequest(requestWithAuth, mockToolFunction);
|
|
346
324
|
|
|
347
325
|
expect(response.status).toBe(200);
|
|
348
326
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
349
|
-
|
|
327
|
+
mockToolFunction, // functionContext
|
|
350
328
|
{ param1: 'test-value' },
|
|
351
329
|
authData
|
|
352
330
|
);
|
|
@@ -358,11 +336,11 @@ describe('ToolsService', () => {
|
|
|
358
336
|
body: JSON.stringify({ param1: 'test-value' })
|
|
359
337
|
});
|
|
360
338
|
|
|
361
|
-
const response = await toolsService.processRequest(requestWithoutWrapper,
|
|
339
|
+
const response = await toolsService.processRequest(requestWithoutWrapper, mockToolFunction);
|
|
362
340
|
|
|
363
341
|
expect(response.status).toBe(200);
|
|
364
342
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
365
|
-
|
|
343
|
+
mockToolFunction, // functionContext
|
|
366
344
|
{ param1: 'test-value' },
|
|
367
345
|
undefined
|
|
368
346
|
);
|
|
@@ -373,7 +351,7 @@ describe('ToolsService', () => {
|
|
|
373
351
|
jest.mocked(mockTool.handler).mockRejectedValueOnce(new Error(errorMessage));
|
|
374
352
|
|
|
375
353
|
const mockRequest = createMockRequest();
|
|
376
|
-
const response = await toolsService.processRequest(mockRequest,
|
|
354
|
+
const response = await toolsService.processRequest(mockRequest, mockToolFunction);
|
|
377
355
|
|
|
378
356
|
expect(response.status).toBe(500);
|
|
379
357
|
expect(logger.error).toHaveBeenCalledWith(
|
|
@@ -386,7 +364,7 @@ describe('ToolsService', () => {
|
|
|
386
364
|
jest.mocked(mockTool.handler).mockRejectedValueOnce({});
|
|
387
365
|
|
|
388
366
|
const mockRequest = createMockRequest();
|
|
389
|
-
const response = await toolsService.processRequest(mockRequest,
|
|
367
|
+
const response = await toolsService.processRequest(mockRequest, mockToolFunction);
|
|
390
368
|
|
|
391
369
|
expect(response.status).toBe(500);
|
|
392
370
|
});
|
|
@@ -408,10 +386,10 @@ describe('ToolsService', () => {
|
|
|
408
386
|
body: JSON.stringify({ data: { param1: 'test-value' } })
|
|
409
387
|
});
|
|
410
388
|
|
|
411
|
-
const response = await toolsService.processRequest(interactionRequest,
|
|
389
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
412
390
|
|
|
413
391
|
expect(response.status).toBe(200);
|
|
414
|
-
expect(mockInteraction.handler).toHaveBeenCalledWith(
|
|
392
|
+
expect(mockInteraction.handler).toHaveBeenCalledWith(mockToolFunction, { param1: 'test-value' }, undefined);
|
|
415
393
|
});
|
|
416
394
|
|
|
417
395
|
it('should handle interaction request body without data wrapper', async () => {
|
|
@@ -421,10 +399,10 @@ describe('ToolsService', () => {
|
|
|
421
399
|
body: JSON.stringify({ param1: 'test-value' })
|
|
422
400
|
});
|
|
423
401
|
|
|
424
|
-
const response = await toolsService.processRequest(interactionRequest,
|
|
402
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
425
403
|
|
|
426
404
|
expect(response.status).toBe(200);
|
|
427
|
-
expect(mockInteraction.handler).toHaveBeenCalledWith(
|
|
405
|
+
expect(mockInteraction.handler).toHaveBeenCalledWith(mockToolFunction, { param1: 'test-value' }, undefined);
|
|
428
406
|
});
|
|
429
407
|
|
|
430
408
|
it('should execute interaction with OptiID auth data when provided', async () => {
|
|
@@ -445,11 +423,11 @@ describe('ToolsService', () => {
|
|
|
445
423
|
})
|
|
446
424
|
});
|
|
447
425
|
|
|
448
|
-
const response = await toolsService.processRequest(interactionRequest,
|
|
426
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
449
427
|
|
|
450
428
|
expect(response.status).toBe(200);
|
|
451
429
|
expect(mockInteraction.handler).toHaveBeenCalledWith(
|
|
452
|
-
|
|
430
|
+
mockToolFunction, // functionContext
|
|
453
431
|
{ param1: 'test-value' },
|
|
454
432
|
authData
|
|
455
433
|
);
|
|
@@ -473,11 +451,11 @@ describe('ToolsService', () => {
|
|
|
473
451
|
})
|
|
474
452
|
});
|
|
475
453
|
|
|
476
|
-
const response = await toolsService.processRequest(interactionRequest,
|
|
454
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
477
455
|
|
|
478
456
|
expect(response.status).toBe(200);
|
|
479
457
|
expect(mockInteraction.handler).toHaveBeenCalledWith(
|
|
480
|
-
|
|
458
|
+
mockToolFunction, // functionContext
|
|
481
459
|
{
|
|
482
460
|
param1: 'test-value',
|
|
483
461
|
auth: authData
|
|
@@ -495,7 +473,7 @@ describe('ToolsService', () => {
|
|
|
495
473
|
bodyJSON: { data: { param1: 'test-value' } }
|
|
496
474
|
});
|
|
497
475
|
|
|
498
|
-
const response = await toolsService.processRequest(interactionRequest,
|
|
476
|
+
const response = await toolsService.processRequest(interactionRequest, mockToolFunction);
|
|
499
477
|
|
|
500
478
|
expect(response.status).toBe(500);
|
|
501
479
|
expect(logger.error).toHaveBeenCalledWith(
|
|
@@ -508,7 +486,7 @@ describe('ToolsService', () => {
|
|
|
508
486
|
describe('error cases', () => {
|
|
509
487
|
it('should return 404 when no matching tool or interaction is found', async () => {
|
|
510
488
|
const unknownRequest = createMockRequest({ path: '/unknown-endpoint' });
|
|
511
|
-
const response = await toolsService.processRequest(unknownRequest,
|
|
489
|
+
const response = await toolsService.processRequest(unknownRequest, mockToolFunction);
|
|
512
490
|
|
|
513
491
|
expect(response.status).toBe(404);
|
|
514
492
|
});
|
|
@@ -531,7 +509,7 @@ describe('ToolsService', () => {
|
|
|
531
509
|
path: '/optid-auth-tool'
|
|
532
510
|
});
|
|
533
511
|
|
|
534
|
-
const response = await toolsService.processRequest(authRequest,
|
|
512
|
+
const response = await toolsService.processRequest(authRequest, mockToolFunction);
|
|
535
513
|
|
|
536
514
|
expect(response.status).toBe(200);
|
|
537
515
|
});
|
|
@@ -552,10 +530,10 @@ describe('ToolsService', () => {
|
|
|
552
530
|
body: null
|
|
553
531
|
});
|
|
554
532
|
|
|
555
|
-
const response = await toolsService.processRequest(requestWithNullBody,
|
|
533
|
+
const response = await toolsService.processRequest(requestWithNullBody, mockToolFunction);
|
|
556
534
|
|
|
557
535
|
expect(response.status).toBe(200);
|
|
558
|
-
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
536
|
+
expect(mockTool.handler).toHaveBeenCalledWith(mockToolFunction, null, undefined);
|
|
559
537
|
});
|
|
560
538
|
|
|
561
539
|
it('should handle request with undefined bodyJSON', async () => {
|
|
@@ -572,10 +550,10 @@ describe('ToolsService', () => {
|
|
|
572
550
|
body: undefined
|
|
573
551
|
});
|
|
574
552
|
|
|
575
|
-
const response = await toolsService.processRequest(requestWithUndefinedBody,
|
|
553
|
+
const response = await toolsService.processRequest(requestWithUndefinedBody, mockToolFunction);
|
|
576
554
|
|
|
577
555
|
expect(response.status).toBe(200);
|
|
578
|
-
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
556
|
+
expect(mockTool.handler).toHaveBeenCalledWith(mockToolFunction, undefined, undefined);
|
|
579
557
|
});
|
|
580
558
|
|
|
581
559
|
it('should extract auth data from bodyJSON when body exists', async () => {
|
|
@@ -603,11 +581,11 @@ describe('ToolsService', () => {
|
|
|
603
581
|
})
|
|
604
582
|
});
|
|
605
583
|
|
|
606
|
-
const response = await toolsService.processRequest(requestWithAuth,
|
|
584
|
+
const response = await toolsService.processRequest(requestWithAuth, mockToolFunction);
|
|
607
585
|
|
|
608
586
|
expect(response.status).toBe(200);
|
|
609
587
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
610
|
-
|
|
588
|
+
mockToolFunction, // functionContext
|
|
611
589
|
{ param1: 'test-value' },
|
|
612
590
|
authData
|
|
613
591
|
);
|
|
@@ -632,11 +610,11 @@ describe('ToolsService', () => {
|
|
|
632
610
|
})
|
|
633
611
|
});
|
|
634
612
|
|
|
635
|
-
const response = await toolsService.processRequest(requestWithoutAuth,
|
|
613
|
+
const response = await toolsService.processRequest(requestWithoutAuth, mockToolFunction);
|
|
636
614
|
|
|
637
615
|
expect(response.status).toBe(200);
|
|
638
616
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
639
|
-
|
|
617
|
+
mockToolFunction, // functionContext
|
|
640
618
|
{ param1: 'test-value' },
|
|
641
619
|
undefined
|
|
642
620
|
);
|
|
@@ -664,11 +642,11 @@ describe('ToolsService', () => {
|
|
|
664
642
|
body: ''
|
|
665
643
|
});
|
|
666
644
|
|
|
667
|
-
const response = await toolsService.processRequest(requestWithAuthButNoBody,
|
|
645
|
+
const response = await toolsService.processRequest(requestWithAuthButNoBody, mockToolFunction);
|
|
668
646
|
|
|
669
647
|
expect(response.status).toBe(200);
|
|
670
648
|
expect(mockTool.handler).toHaveBeenCalledWith(
|
|
671
|
-
|
|
649
|
+
mockToolFunction, // functionContext
|
|
672
650
|
{ param1: 'test-value' },
|
|
673
651
|
authData
|
|
674
652
|
);
|
package/src/service/Service.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
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';
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
@@ -22,7 +23,7 @@ export class Interaction<TAuthData> {
|
|
|
22
23
|
public constructor(
|
|
23
24
|
public name: string,
|
|
24
25
|
public endpoint: string,
|
|
25
|
-
public handler: (functionContext:
|
|
26
|
+
public handler: (functionContext: ToolFunction, data: unknown, authData?: TAuthData) => Promise<InteractionResult>
|
|
26
27
|
) {}
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -49,7 +50,7 @@ export class Tool<TAuthData> {
|
|
|
49
50
|
public description: string,
|
|
50
51
|
public parameters: Parameter[],
|
|
51
52
|
public endpoint: string,
|
|
52
|
-
public handler: (functionContext:
|
|
53
|
+
public handler: (functionContext: ToolFunction, params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
53
54
|
public authRequirements?: AuthRequirement[]
|
|
54
55
|
) {}
|
|
55
56
|
|
|
@@ -104,7 +105,7 @@ export class ToolsService {
|
|
|
104
105
|
public registerTool<TAuthData>(
|
|
105
106
|
name: string,
|
|
106
107
|
description: string,
|
|
107
|
-
handler: (functionContext:
|
|
108
|
+
handler: (functionContext: ToolFunction, params: unknown, authData?: TAuthData) => Promise<unknown>,
|
|
108
109
|
parameters: Parameter[],
|
|
109
110
|
endpoint: string,
|
|
110
111
|
authRequirements?: AuthRequirement[]
|
|
@@ -121,7 +122,7 @@ export class ToolsService {
|
|
|
121
122
|
*/
|
|
122
123
|
public registerInteraction<TAuthData>(
|
|
123
124
|
name: string,
|
|
124
|
-
handler: (functionContext:
|
|
125
|
+
handler: (functionContext: ToolFunction, data: unknown, authData?: TAuthData) => Promise<InteractionResult>,
|
|
125
126
|
endpoint: string
|
|
126
127
|
): void {
|
|
127
128
|
const func = new Interaction<TAuthData>(name, endpoint, handler);
|
|
@@ -129,13 +130,9 @@ export class ToolsService {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
public async processRequest(req: App.Request,
|
|
132
|
-
|
|
133
|
-
functionContext?: any): Promise<App.Response> {
|
|
133
|
+
functionContext: ToolFunction): Promise<App.Response> {
|
|
134
134
|
if (req.path === '/discovery') {
|
|
135
135
|
return new App.Response(200, { functions: Array.from(this.functions.values()).map((f) => f.toJSON()) });
|
|
136
|
-
} else if (req.path === '/ready') {
|
|
137
|
-
const isReady = await readyCheck();
|
|
138
|
-
return new App.Response(200, { ready: isReady });
|
|
139
136
|
} else {
|
|
140
137
|
const func = this.functions.get(req.path);
|
|
141
138
|
if (func) {
|