@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.
- package/README.md +437 -0
- package/dist/decorator/Decorator.d.ts +46 -0
- package/dist/decorator/Decorator.d.ts.map +1 -0
- package/dist/decorator/Decorator.js +31 -0
- package/dist/decorator/Decorator.js.map +1 -0
- package/dist/decorator/Decorator.test.d.ts +2 -0
- package/dist/decorator/Decorator.test.d.ts.map +1 -0
- package/dist/decorator/Decorator.test.js +418 -0
- package/dist/decorator/Decorator.test.js.map +1 -0
- package/dist/function/ToolFunction.d.ts +15 -0
- package/dist/function/ToolFunction.d.ts.map +1 -0
- package/dist/function/ToolFunction.js +25 -0
- package/dist/function/ToolFunction.js.map +1 -0
- package/dist/function/ToolFunction.test.d.ts +2 -0
- package/dist/function/ToolFunction.test.d.ts.map +1 -0
- package/dist/function/ToolFunction.test.js +189 -0
- package/dist/function/ToolFunction.test.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/service/Service.d.ts +78 -0
- package/dist/service/Service.d.ts.map +1 -0
- package/dist/service/Service.js +204 -0
- package/dist/service/Service.js.map +1 -0
- package/dist/service/Service.test.d.ts +2 -0
- package/dist/service/Service.test.d.ts.map +1 -0
- package/dist/service/Service.test.js +341 -0
- package/dist/service/Service.test.js.map +1 -0
- package/dist/types/Models.d.ts +126 -0
- package/dist/types/Models.d.ts.map +1 -0
- package/dist/types/Models.js +181 -0
- package/dist/types/Models.js.map +1 -0
- package/package.json +58 -0
- package/src/decorator/Decorator.test.ts +523 -0
- package/src/decorator/Decorator.ts +83 -0
- package/src/function/ToolFunction.test.ts +224 -0
- package/src/function/ToolFunction.ts +25 -0
- package/src/index.ts +4 -0
- package/src/service/Service.test.ts +550 -0
- package/src/service/Service.ts +182 -0
- 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