@optimizely-opal/opal-tool-ocp-sdk 0.0.0-dev.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 (42) hide show
  1. package/README.md +437 -0
  2. package/dist/decorator/Decorator.d.ts +46 -0
  3. package/dist/decorator/Decorator.d.ts.map +1 -0
  4. package/dist/decorator/Decorator.js +31 -0
  5. package/dist/decorator/Decorator.js.map +1 -0
  6. package/dist/decorator/Decorator.test.d.ts +2 -0
  7. package/dist/decorator/Decorator.test.d.ts.map +1 -0
  8. package/dist/decorator/Decorator.test.js +418 -0
  9. package/dist/decorator/Decorator.test.js.map +1 -0
  10. package/dist/function/ToolFunction.d.ts +15 -0
  11. package/dist/function/ToolFunction.d.ts.map +1 -0
  12. package/dist/function/ToolFunction.js +25 -0
  13. package/dist/function/ToolFunction.js.map +1 -0
  14. package/dist/function/ToolFunction.test.d.ts +2 -0
  15. package/dist/function/ToolFunction.test.d.ts.map +1 -0
  16. package/dist/function/ToolFunction.test.js +189 -0
  17. package/dist/function/ToolFunction.test.js.map +1 -0
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +25 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/service/Service.d.ts +78 -0
  23. package/dist/service/Service.d.ts.map +1 -0
  24. package/dist/service/Service.js +204 -0
  25. package/dist/service/Service.js.map +1 -0
  26. package/dist/service/Service.test.d.ts +2 -0
  27. package/dist/service/Service.test.d.ts.map +1 -0
  28. package/dist/service/Service.test.js +341 -0
  29. package/dist/service/Service.test.js.map +1 -0
  30. package/dist/types/Models.d.ts +126 -0
  31. package/dist/types/Models.d.ts.map +1 -0
  32. package/dist/types/Models.js +181 -0
  33. package/dist/types/Models.js.map +1 -0
  34. package/package.json +58 -0
  35. package/src/decorator/Decorator.test.ts +523 -0
  36. package/src/decorator/Decorator.ts +83 -0
  37. package/src/function/ToolFunction.test.ts +224 -0
  38. package/src/function/ToolFunction.ts +25 -0
  39. package/src/index.ts +4 -0
  40. package/src/service/Service.test.ts +550 -0
  41. package/src/service/Service.ts +182 -0
  42. package/src/types/Models.ts +163 -0
@@ -0,0 +1,224 @@
1
+ import { ToolFunction } from './ToolFunction';
2
+ import { toolsService } from '../service/Service';
3
+ import { Response } from '@zaiusinc/app-sdk';
4
+
5
+ // Mock the toolsService
6
+ jest.mock('../service/Service', () => ({
7
+ toolsService: {
8
+ processRequest: jest.fn(),
9
+ extractBearerToken: jest.fn(),
10
+ },
11
+ }));
12
+
13
+ // Mock the Request and Response classes and Function base class
14
+ jest.mock('@zaiusinc/app-sdk', () => ({
15
+ Function: class {
16
+ protected request: any;
17
+ public constructor(_name?: string) {
18
+ // Mock constructor that accepts optional name parameter
19
+ this.request = {};
20
+ }
21
+ },
22
+ Request: jest.fn().mockImplementation(() => ({})),
23
+ Response: jest.fn().mockImplementation((status, data) => ({
24
+ status,
25
+ data,
26
+ bodyJSON: data,
27
+ bodyAsU8Array: new Uint8Array()
28
+ })),
29
+ }));
30
+
31
+ // Create a concrete implementation for testing
32
+ class TestToolFunction extends ToolFunction {
33
+ private mockValidateBearerToken: jest.MockedFunction<(token: string) => boolean>;
34
+
35
+ public constructor(request?: any) {
36
+ super(request || {}); // Pass the request parameter properly
37
+ // Set the request directly without defaulting to empty object
38
+ (this as any).request = request;
39
+
40
+ // Create a mock implementation of the abstract method
41
+ this.mockValidateBearerToken = jest.fn().mockReturnValue(true);
42
+ }
43
+
44
+ // Implement the abstract method
45
+ protected validateBearerToken(bearerToken: string): boolean {
46
+ return this.mockValidateBearerToken(bearerToken);
47
+ }
48
+
49
+ // Expose request and validation mock for testing
50
+ public getRequest() {
51
+ return (this as any).request;
52
+ }
53
+
54
+ public getMockValidateBearerToken() {
55
+ return this.mockValidateBearerToken;
56
+ }
57
+ }
58
+
59
+ describe('ToolFunction', () => {
60
+ let mockRequest: any;
61
+ let mockResponse: Response;
62
+ let toolFunction: TestToolFunction;
63
+ let mockProcessRequest: jest.MockedFunction<typeof toolsService.processRequest>;
64
+ let mockExtractBearerToken: jest.MockedFunction<typeof toolsService.extractBearerToken>;
65
+
66
+ beforeEach(() => {
67
+ jest.clearAllMocks();
68
+
69
+ // Create mock instances
70
+ mockRequest = {
71
+ headers: new Map(),
72
+ method: 'POST',
73
+ path: '/test'
74
+ };
75
+ mockResponse = {} as Response;
76
+
77
+ // Setup the mocks
78
+ mockProcessRequest = jest.mocked(toolsService.processRequest);
79
+ mockExtractBearerToken = jest.mocked(toolsService.extractBearerToken);
80
+
81
+ // Create test instance
82
+ toolFunction = new TestToolFunction(mockRequest);
83
+ });
84
+
85
+ describe('bearer token validation', () => {
86
+ it('should extract bearer token from headers and validate it', async () => {
87
+ // Arrange
88
+ const bearerToken = 'valid-token-123';
89
+ mockExtractBearerToken.mockReturnValue(bearerToken);
90
+ toolFunction.getMockValidateBearerToken().mockReturnValue(true);
91
+ mockProcessRequest.mockResolvedValue(mockResponse);
92
+
93
+ // Act
94
+ const result = await toolFunction.perform();
95
+
96
+ // Assert
97
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
98
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(bearerToken);
99
+ expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest);
100
+ expect(result).toBe(mockResponse);
101
+ });
102
+
103
+ it('should return 403 Forbidden when bearer token validation fails', async () => {
104
+ // Arrange
105
+ const bearerToken = 'invalid-token-456';
106
+ mockExtractBearerToken.mockReturnValue(bearerToken);
107
+ toolFunction.getMockValidateBearerToken().mockReturnValue(false);
108
+
109
+ // Act
110
+ const result = await toolFunction.perform();
111
+
112
+ // Assert
113
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
114
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(bearerToken);
115
+ expect(mockProcessRequest).not.toHaveBeenCalled();
116
+ expect(result).toEqual(new Response(403, { error: 'Forbidden' }));
117
+ });
118
+
119
+ it('should handle complex bearer token validation scenarios', async () => {
120
+ // Arrange - simulate a token that should be valid based on some complex logic
121
+ const complexToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9l' +
122
+ 'IiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
123
+ mockExtractBearerToken.mockReturnValue(complexToken);
124
+ toolFunction.getMockValidateBearerToken().mockReturnValue(true);
125
+ mockProcessRequest.mockResolvedValue(mockResponse);
126
+
127
+ // Act
128
+ const result = await toolFunction.perform();
129
+
130
+ // Assert
131
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
132
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(complexToken);
133
+ expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest);
134
+ expect(result).toBe(mockResponse);
135
+ });
136
+
137
+ it('should handle bearer token validation throwing an error', async () => {
138
+ // Arrange
139
+ const bearerToken = 'malformed-token';
140
+ mockExtractBearerToken.mockReturnValue(bearerToken);
141
+ toolFunction.getMockValidateBearerToken().mockImplementation(() => {
142
+ throw new Error('Token validation error');
143
+ });
144
+
145
+ // Act & Assert
146
+ await expect(toolFunction.perform()).rejects.toThrow('Token validation error');
147
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
148
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(bearerToken);
149
+ expect(mockProcessRequest).not.toHaveBeenCalled();
150
+ });
151
+
152
+ it('should call validateBearerToken only once per request', async () => {
153
+ // Arrange
154
+ const bearerToken = 'test-token';
155
+ mockExtractBearerToken.mockReturnValue(bearerToken);
156
+ toolFunction.getMockValidateBearerToken().mockReturnValue(true);
157
+ mockProcessRequest.mockResolvedValue(mockResponse);
158
+
159
+ // Act
160
+ await toolFunction.perform();
161
+
162
+ // Assert
163
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledTimes(1);
164
+ expect(toolFunction.getMockValidateBearerToken()).toHaveBeenCalledWith(bearerToken);
165
+ });
166
+
167
+ it('should extract bearer token only once per request', async () => {
168
+ // Arrange
169
+ const bearerToken = 'test-token';
170
+ mockExtractBearerToken.mockReturnValue(bearerToken);
171
+ toolFunction.getMockValidateBearerToken().mockReturnValue(true);
172
+ mockProcessRequest.mockResolvedValue(mockResponse);
173
+
174
+ // Act
175
+ await toolFunction.perform();
176
+
177
+ // Assert
178
+ expect(mockExtractBearerToken).toHaveBeenCalledTimes(1);
179
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
180
+ });
181
+
182
+ it('should skip validation when bearer token is undefined', async () => {
183
+ // Arrange
184
+ mockExtractBearerToken.mockReturnValue(undefined);
185
+ mockProcessRequest.mockResolvedValue(mockResponse);
186
+
187
+ // Act
188
+ const result = await toolFunction.perform();
189
+
190
+ // Assert
191
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
192
+ expect(toolFunction.getMockValidateBearerToken()).not.toHaveBeenCalled();
193
+ expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest);
194
+ expect(result).toBe(mockResponse);
195
+ });
196
+
197
+ it('should skip validation when bearer token is empty string', async () => {
198
+ // Arrange
199
+ mockExtractBearerToken.mockReturnValue('');
200
+ mockProcessRequest.mockResolvedValue(mockResponse);
201
+
202
+ // Act
203
+ const result = await toolFunction.perform();
204
+
205
+ // Assert
206
+ expect(mockExtractBearerToken).toHaveBeenCalledWith(mockRequest.headers);
207
+ expect(toolFunction.getMockValidateBearerToken()).not.toHaveBeenCalled();
208
+ expect(mockProcessRequest).toHaveBeenCalledWith(mockRequest);
209
+ expect(result).toBe(mockResponse);
210
+ });
211
+ });
212
+
213
+ describe('inheritance', () => {
214
+ it('should be an instance of Function', () => {
215
+ // Assert
216
+ expect(toolFunction).toBeInstanceOf(ToolFunction);
217
+ });
218
+
219
+ it('should have access to the request property', () => {
220
+ // Assert
221
+ expect(toolFunction.getRequest()).toBe(mockRequest);
222
+ });
223
+ });
224
+ });
@@ -0,0 +1,25 @@
1
+ import { Function, Response } from '@zaiusinc/app-sdk';
2
+ import { toolsService } from '../service/Service';
3
+
4
+ /**
5
+ * Abstract base class for tool-based function execution
6
+ * Provides a standard interface for processing requests through registered tools
7
+ */
8
+ export abstract class ToolFunction extends Function {
9
+
10
+ /**
11
+ * Process the incoming request using the tools service
12
+ *
13
+ * @returns Response as the HTTP response
14
+ */
15
+ public async perform(): Promise<Response> {
16
+ const bearerToken = toolsService.extractBearerToken(this.request.headers);
17
+ if (bearerToken && !this.validateBearerToken(bearerToken)) {
18
+ return new Response(403, { error: 'Forbidden' });
19
+ }
20
+
21
+ return toolsService.processRequest(this.request);
22
+ }
23
+
24
+ protected abstract validateBearerToken(bearerToken: string): boolean;
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './function/ToolFunction';
2
+ export * from './types/Models';
3
+ export * from './decorator/Decorator';
4
+ export { Tool, Interaction, InteractionResult } from './service/Service';