@optimizely-opal/opal-tool-ocp-sdk 1.0.0-beta.1 → 1.0.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 +42 -0
- package/dist/service/Service.d.ts.map +1 -1
- package/dist/service/Service.js +17 -0
- package/dist/service/Service.js.map +1 -1
- package/dist/service/Service.test.js +114 -6
- package/dist/service/Service.test.js.map +1 -1
- package/dist/validation/ParameterValidator.d.ts +42 -0
- package/dist/validation/ParameterValidator.d.ts.map +1 -0
- package/dist/validation/ParameterValidator.js +122 -0
- package/dist/validation/ParameterValidator.js.map +1 -0
- package/dist/validation/ParameterValidator.test.d.ts +2 -0
- package/dist/validation/ParameterValidator.test.d.ts.map +1 -0
- package/dist/validation/ParameterValidator.test.js +282 -0
- package/dist/validation/ParameterValidator.test.js.map +1 -0
- package/package.json +1 -1
- package/src/service/Service.test.ts +155 -14
- package/src/service/Service.ts +19 -1
- package/src/validation/ParameterValidator.test.ts +341 -0
- package/src/validation/ParameterValidator.ts +153 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ParameterValidator_1 = require("./ParameterValidator");
|
|
4
|
+
const Models_1 = require("../types/Models");
|
|
5
|
+
describe('ParameterValidator', () => {
|
|
6
|
+
describe('validate', () => {
|
|
7
|
+
it('should pass validation for valid parameters', () => {
|
|
8
|
+
const paramDefs = [
|
|
9
|
+
new Models_1.Parameter('name', Models_1.ParameterType.String, 'User name', true),
|
|
10
|
+
new Models_1.Parameter('age', Models_1.ParameterType.Integer, 'User age', false),
|
|
11
|
+
new Models_1.Parameter('score', Models_1.ParameterType.Number, 'User score', false),
|
|
12
|
+
new Models_1.Parameter('active', Models_1.ParameterType.Boolean, 'Is active', false),
|
|
13
|
+
new Models_1.Parameter('tags', Models_1.ParameterType.List, 'User tags', false),
|
|
14
|
+
new Models_1.Parameter('config', Models_1.ParameterType.Dictionary, 'User config', false)
|
|
15
|
+
];
|
|
16
|
+
const validParams = {
|
|
17
|
+
name: 'John Doe',
|
|
18
|
+
age: 25,
|
|
19
|
+
score: 85.5,
|
|
20
|
+
active: true,
|
|
21
|
+
tags: ['admin', 'user'],
|
|
22
|
+
config: { theme: 'dark', language: 'en' }
|
|
23
|
+
};
|
|
24
|
+
const result = ParameterValidator_1.ParameterValidator.validate(validParams, paramDefs);
|
|
25
|
+
expect(result.isValid).toBe(true);
|
|
26
|
+
expect(result.errors).toHaveLength(0);
|
|
27
|
+
});
|
|
28
|
+
it('should fail validation for missing required parameters', () => {
|
|
29
|
+
const paramDefs = [
|
|
30
|
+
new Models_1.Parameter('name', Models_1.ParameterType.String, 'User name', true),
|
|
31
|
+
new Models_1.Parameter('email', Models_1.ParameterType.String, 'User email', true)
|
|
32
|
+
];
|
|
33
|
+
const params = {
|
|
34
|
+
name: 'John Doe'
|
|
35
|
+
// email is missing
|
|
36
|
+
};
|
|
37
|
+
const result = ParameterValidator_1.ParameterValidator.validate(params, paramDefs);
|
|
38
|
+
expect(result.isValid).toBe(false);
|
|
39
|
+
expect(result.errors).toHaveLength(1);
|
|
40
|
+
expect(result.errors[0]).toEqual({
|
|
41
|
+
field: 'email',
|
|
42
|
+
message: "Required parameter 'email' is missing"
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
it('should fail validation for wrong parameter types', () => {
|
|
46
|
+
const paramDefs = [
|
|
47
|
+
new Models_1.Parameter('name', Models_1.ParameterType.String, 'User name', true),
|
|
48
|
+
new Models_1.Parameter('age', Models_1.ParameterType.Integer, 'User age', true),
|
|
49
|
+
new Models_1.Parameter('active', Models_1.ParameterType.Boolean, 'Is active', true),
|
|
50
|
+
new Models_1.Parameter('tags', Models_1.ParameterType.List, 'User tags', true),
|
|
51
|
+
new Models_1.Parameter('config', Models_1.ParameterType.Dictionary, 'User config', true)
|
|
52
|
+
];
|
|
53
|
+
const invalidParams = {
|
|
54
|
+
name: 123, // should be string
|
|
55
|
+
age: '25', // should be integer
|
|
56
|
+
active: 'true', // should be boolean
|
|
57
|
+
tags: 'tag1,tag2', // should be array
|
|
58
|
+
config: 'invalid' // should be object
|
|
59
|
+
};
|
|
60
|
+
const result = ParameterValidator_1.ParameterValidator.validate(invalidParams, paramDefs);
|
|
61
|
+
expect(result.isValid).toBe(false);
|
|
62
|
+
expect(result.errors).toHaveLength(5);
|
|
63
|
+
expect(result.errors[0].field).toBe('name');
|
|
64
|
+
expect(result.errors[0].message).toContain('must be a string');
|
|
65
|
+
expect(result.errors[1].field).toBe('age');
|
|
66
|
+
expect(result.errors[1].message).toContain('must be an integer');
|
|
67
|
+
expect(result.errors[2].field).toBe('active');
|
|
68
|
+
expect(result.errors[2].message).toContain('must be a boolean');
|
|
69
|
+
expect(result.errors[3].field).toBe('tags');
|
|
70
|
+
expect(result.errors[3].message).toContain('must be an array');
|
|
71
|
+
expect(result.errors[4].field).toBe('config');
|
|
72
|
+
expect(result.errors[4].message).toContain('must be an object');
|
|
73
|
+
});
|
|
74
|
+
it('should handle null/undefined parameters correctly', () => {
|
|
75
|
+
const paramDefs = [
|
|
76
|
+
new Models_1.Parameter('required', Models_1.ParameterType.String, 'Required param', true),
|
|
77
|
+
new Models_1.Parameter('optional', Models_1.ParameterType.String, 'Optional param', false)
|
|
78
|
+
];
|
|
79
|
+
const result1 = ParameterValidator_1.ParameterValidator.validate(null, paramDefs);
|
|
80
|
+
expect(result1.isValid).toBe(false);
|
|
81
|
+
expect(result1.errors).toHaveLength(1);
|
|
82
|
+
expect(result1.errors[0].field).toBe('required');
|
|
83
|
+
const result2 = ParameterValidator_1.ParameterValidator.validate(undefined, paramDefs);
|
|
84
|
+
expect(result2.isValid).toBe(false);
|
|
85
|
+
expect(result2.errors).toHaveLength(1);
|
|
86
|
+
expect(result2.errors[0].field).toBe('required');
|
|
87
|
+
});
|
|
88
|
+
it('should allow optional parameters to be missing', () => {
|
|
89
|
+
const paramDefs = [
|
|
90
|
+
new Models_1.Parameter('name', Models_1.ParameterType.String, 'User name', true),
|
|
91
|
+
new Models_1.Parameter('age', Models_1.ParameterType.Integer, 'User age', false)
|
|
92
|
+
];
|
|
93
|
+
const params = {
|
|
94
|
+
name: 'John Doe'
|
|
95
|
+
// age is optional and missing
|
|
96
|
+
};
|
|
97
|
+
const result = ParameterValidator_1.ParameterValidator.validate(params, paramDefs);
|
|
98
|
+
expect(result.isValid).toBe(true);
|
|
99
|
+
expect(result.errors).toHaveLength(0);
|
|
100
|
+
});
|
|
101
|
+
it('should distinguish between integer and number types', () => {
|
|
102
|
+
const paramDefs = [
|
|
103
|
+
new Models_1.Parameter('count', Models_1.ParameterType.Integer, 'Item count', true),
|
|
104
|
+
new Models_1.Parameter('score', Models_1.ParameterType.Number, 'Score value', true)
|
|
105
|
+
];
|
|
106
|
+
const params1 = {
|
|
107
|
+
count: 25.5, // should be integer, not float
|
|
108
|
+
score: 85.5 // number is fine
|
|
109
|
+
};
|
|
110
|
+
const result1 = ParameterValidator_1.ParameterValidator.validate(params1, paramDefs);
|
|
111
|
+
expect(result1.isValid).toBe(false);
|
|
112
|
+
expect(result1.errors).toHaveLength(1);
|
|
113
|
+
expect(result1.errors[0].field).toBe('count');
|
|
114
|
+
expect(result1.errors[0].message).toContain('must be an integer');
|
|
115
|
+
const params2 = {
|
|
116
|
+
count: 25, // integer is fine
|
|
117
|
+
score: 85.5 // number is fine
|
|
118
|
+
};
|
|
119
|
+
const result2 = ParameterValidator_1.ParameterValidator.validate(params2, paramDefs);
|
|
120
|
+
expect(result2.isValid).toBe(true);
|
|
121
|
+
expect(result2.errors).toHaveLength(0);
|
|
122
|
+
});
|
|
123
|
+
it('should handle array vs object distinction', () => {
|
|
124
|
+
const paramDefs = [
|
|
125
|
+
new Models_1.Parameter('tags', Models_1.ParameterType.List, 'Tag list', true),
|
|
126
|
+
new Models_1.Parameter('config', Models_1.ParameterType.Dictionary, 'Config object', true)
|
|
127
|
+
];
|
|
128
|
+
const params = {
|
|
129
|
+
tags: { 0: 'tag1', 1: 'tag2' }, // object that looks like array
|
|
130
|
+
config: ['key1', 'key2'] // array instead of object
|
|
131
|
+
};
|
|
132
|
+
const result = ParameterValidator_1.ParameterValidator.validate(params, paramDefs);
|
|
133
|
+
expect(result.isValid).toBe(false);
|
|
134
|
+
expect(result.errors).toHaveLength(2);
|
|
135
|
+
expect(result.errors[0].field).toBe('tags');
|
|
136
|
+
expect(result.errors[0].message).toContain('must be an array');
|
|
137
|
+
expect(result.errors[1].field).toBe('config');
|
|
138
|
+
expect(result.errors[1].message).toContain('must be an object');
|
|
139
|
+
});
|
|
140
|
+
it('should handle null values for optional parameters', () => {
|
|
141
|
+
const paramDefs = [
|
|
142
|
+
new Models_1.Parameter('name', Models_1.ParameterType.String, 'User name', true),
|
|
143
|
+
new Models_1.Parameter('age', Models_1.ParameterType.Integer, 'User age', false)
|
|
144
|
+
];
|
|
145
|
+
const params = {
|
|
146
|
+
name: 'John Doe',
|
|
147
|
+
age: null // null for optional parameter should be allowed
|
|
148
|
+
};
|
|
149
|
+
const result = ParameterValidator_1.ParameterValidator.validate(params, paramDefs);
|
|
150
|
+
expect(result.isValid).toBe(true);
|
|
151
|
+
expect(result.errors).toHaveLength(0);
|
|
152
|
+
});
|
|
153
|
+
it('should handle edge cases for number validation', () => {
|
|
154
|
+
const paramDefs = [
|
|
155
|
+
new Models_1.Parameter('score', Models_1.ParameterType.Number, 'Score value', true)
|
|
156
|
+
];
|
|
157
|
+
// Test NaN
|
|
158
|
+
const params1 = { score: NaN };
|
|
159
|
+
const result1 = ParameterValidator_1.ParameterValidator.validate(params1, paramDefs);
|
|
160
|
+
expect(result1.isValid).toBe(false);
|
|
161
|
+
expect(result1.errors[0].message).toContain('must be a number');
|
|
162
|
+
// Test Infinity
|
|
163
|
+
const params2 = { score: Infinity };
|
|
164
|
+
const result2 = ParameterValidator_1.ParameterValidator.validate(params2, paramDefs);
|
|
165
|
+
expect(result2.isValid).toBe(true);
|
|
166
|
+
expect(result2.errors).toHaveLength(0);
|
|
167
|
+
// Test negative numbers
|
|
168
|
+
const params3 = { score: -42.5 };
|
|
169
|
+
const result3 = ParameterValidator_1.ParameterValidator.validate(params3, paramDefs);
|
|
170
|
+
expect(result3.isValid).toBe(true);
|
|
171
|
+
expect(result3.errors).toHaveLength(0);
|
|
172
|
+
});
|
|
173
|
+
it('should handle edge cases for integer validation', () => {
|
|
174
|
+
const paramDefs = [
|
|
175
|
+
new Models_1.Parameter('count', Models_1.ParameterType.Integer, 'Item count', true)
|
|
176
|
+
];
|
|
177
|
+
// Test negative integers
|
|
178
|
+
const params1 = { count: -5 };
|
|
179
|
+
const result1 = ParameterValidator_1.ParameterValidator.validate(params1, paramDefs);
|
|
180
|
+
expect(result1.isValid).toBe(true);
|
|
181
|
+
expect(result1.errors).toHaveLength(0);
|
|
182
|
+
// Test zero
|
|
183
|
+
const params2 = { count: 0 };
|
|
184
|
+
const result2 = ParameterValidator_1.ParameterValidator.validate(params2, paramDefs);
|
|
185
|
+
expect(result2.isValid).toBe(true);
|
|
186
|
+
expect(result2.errors).toHaveLength(0);
|
|
187
|
+
// Test very large integers
|
|
188
|
+
const params3 = { count: Number.MAX_SAFE_INTEGER };
|
|
189
|
+
const result3 = ParameterValidator_1.ParameterValidator.validate(params3, paramDefs);
|
|
190
|
+
expect(result3.isValid).toBe(true);
|
|
191
|
+
expect(result3.errors).toHaveLength(0);
|
|
192
|
+
});
|
|
193
|
+
it('should handle empty arrays and objects', () => {
|
|
194
|
+
const paramDefs = [
|
|
195
|
+
new Models_1.Parameter('tags', Models_1.ParameterType.List, 'Tag list', true),
|
|
196
|
+
new Models_1.Parameter('config', Models_1.ParameterType.Dictionary, 'Config object', true)
|
|
197
|
+
];
|
|
198
|
+
const params = {
|
|
199
|
+
tags: [], // empty array should be valid
|
|
200
|
+
config: {} // empty object should be valid
|
|
201
|
+
};
|
|
202
|
+
const result = ParameterValidator_1.ParameterValidator.validate(params, paramDefs);
|
|
203
|
+
expect(result.isValid).toBe(true);
|
|
204
|
+
expect(result.errors).toHaveLength(0);
|
|
205
|
+
});
|
|
206
|
+
it('should handle special string values', () => {
|
|
207
|
+
const paramDefs = [
|
|
208
|
+
new Models_1.Parameter('text', Models_1.ParameterType.String, 'Text value', true)
|
|
209
|
+
];
|
|
210
|
+
// Test empty string
|
|
211
|
+
const params1 = { text: '' };
|
|
212
|
+
const result1 = ParameterValidator_1.ParameterValidator.validate(params1, paramDefs);
|
|
213
|
+
expect(result1.isValid).toBe(true);
|
|
214
|
+
expect(result1.errors).toHaveLength(0);
|
|
215
|
+
// Test string with special characters
|
|
216
|
+
const params2 = { text: 'Hello\nWorld\t!' };
|
|
217
|
+
const result2 = ParameterValidator_1.ParameterValidator.validate(params2, paramDefs);
|
|
218
|
+
expect(result2.isValid).toBe(true);
|
|
219
|
+
expect(result2.errors).toHaveLength(0);
|
|
220
|
+
});
|
|
221
|
+
it('should handle multiple validation errors', () => {
|
|
222
|
+
const paramDefs = [
|
|
223
|
+
new Models_1.Parameter('name', Models_1.ParameterType.String, 'User name', true),
|
|
224
|
+
new Models_1.Parameter('age', Models_1.ParameterType.Integer, 'User age', true),
|
|
225
|
+
new Models_1.Parameter('email', Models_1.ParameterType.String, 'User email', true)
|
|
226
|
+
];
|
|
227
|
+
const params = {
|
|
228
|
+
name: 123, // wrong type
|
|
229
|
+
age: '25' // wrong type
|
|
230
|
+
// email is missing
|
|
231
|
+
};
|
|
232
|
+
const result = ParameterValidator_1.ParameterValidator.validate(params, paramDefs);
|
|
233
|
+
expect(result.isValid).toBe(false);
|
|
234
|
+
expect(result.errors).toHaveLength(3);
|
|
235
|
+
expect(result.errors.some((e) => e.field === 'name')).toBe(true);
|
|
236
|
+
expect(result.errors.some((e) => e.field === 'age')).toBe(true);
|
|
237
|
+
expect(result.errors.some((e) => e.field === 'email')).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
it('should handle tools with no parameter definitions', () => {
|
|
240
|
+
const result = ParameterValidator_1.ParameterValidator.validate({ someParam: 'value' }, []);
|
|
241
|
+
expect(result.isValid).toBe(true);
|
|
242
|
+
expect(result.errors).toHaveLength(0);
|
|
243
|
+
});
|
|
244
|
+
it('should handle extra parameters not in definition', () => {
|
|
245
|
+
const paramDefs = [
|
|
246
|
+
new Models_1.Parameter('name', Models_1.ParameterType.String, 'User name', true)
|
|
247
|
+
];
|
|
248
|
+
const params = {
|
|
249
|
+
name: 'John Doe',
|
|
250
|
+
extraParam: 'should be ignored'
|
|
251
|
+
};
|
|
252
|
+
const result = ParameterValidator_1.ParameterValidator.validate(params, paramDefs);
|
|
253
|
+
expect(result.isValid).toBe(true);
|
|
254
|
+
expect(result.errors).toHaveLength(0);
|
|
255
|
+
});
|
|
256
|
+
it('should handle nested objects and arrays', () => {
|
|
257
|
+
const paramDefs = [
|
|
258
|
+
new Models_1.Parameter('config', Models_1.ParameterType.Dictionary, 'Config object', true),
|
|
259
|
+
new Models_1.Parameter('matrix', Models_1.ParameterType.List, 'Matrix data', true)
|
|
260
|
+
];
|
|
261
|
+
const params = {
|
|
262
|
+
config: {
|
|
263
|
+
nested: {
|
|
264
|
+
deep: {
|
|
265
|
+
value: 'test'
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
array: [1, 2, 3]
|
|
269
|
+
},
|
|
270
|
+
matrix: [
|
|
271
|
+
[1, 2, 3],
|
|
272
|
+
[4, 5, 6],
|
|
273
|
+
{ nested: 'object in array' }
|
|
274
|
+
]
|
|
275
|
+
};
|
|
276
|
+
const result = ParameterValidator_1.ParameterValidator.validate(params, paramDefs);
|
|
277
|
+
expect(result.isValid).toBe(true);
|
|
278
|
+
expect(result.errors).toHaveLength(0);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
//# sourceMappingURL=ParameterValidator.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ParameterValidator.test.js","sourceRoot":"","sources":["../../src/validation/ParameterValidator.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -11,11 +11,30 @@ jest.mock('@zaiusinc/app-sdk', () => ({
|
|
|
11
11
|
Function: class {
|
|
12
12
|
public constructor(public request: any) {}
|
|
13
13
|
},
|
|
14
|
-
|
|
14
|
+
Headers: class {
|
|
15
|
+
public constructor(headers: string[][] = []) {
|
|
16
|
+
this.headers = {};
|
|
17
|
+
headers.forEach(([name, value]) => {
|
|
18
|
+
this.headers[name.toLowerCase()] = value;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
public headers: { [key: string]: string };
|
|
22
|
+
public set(name: string, value: string) {
|
|
23
|
+
this.headers[name.toLowerCase()] = value;
|
|
24
|
+
}
|
|
25
|
+
public get(name: string) {
|
|
26
|
+
return this.headers[name.toLowerCase()];
|
|
27
|
+
}
|
|
28
|
+
public has(name: string) {
|
|
29
|
+
return name.toLowerCase() in this.headers;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
Response: jest.fn().mockImplementation((status, data, headers) => ({
|
|
15
33
|
status,
|
|
16
34
|
data,
|
|
17
35
|
bodyJSON: data,
|
|
18
|
-
bodyAsU8Array: new Uint8Array()
|
|
36
|
+
bodyAsU8Array: new Uint8Array(),
|
|
37
|
+
headers: headers || { get: jest.fn(), has: jest.fn(), set: jest.fn() }
|
|
19
38
|
}))
|
|
20
39
|
}));
|
|
21
40
|
|
|
@@ -522,15 +541,25 @@ describe('ToolsService', () => {
|
|
|
522
541
|
|
|
523
542
|
describe('edge cases', () => {
|
|
524
543
|
it('should handle request with null bodyJSON', async () => {
|
|
544
|
+
// Create a tool without required parameters
|
|
545
|
+
const toolWithoutRequiredParams = {
|
|
546
|
+
name: 'no_required_params_tool',
|
|
547
|
+
description: 'Tool without required parameters',
|
|
548
|
+
handler: jest.fn().mockResolvedValue({ result: 'success' }),
|
|
549
|
+
parameters: [], // No parameters defined
|
|
550
|
+
endpoint: '/no-required-params-tool'
|
|
551
|
+
};
|
|
552
|
+
|
|
525
553
|
toolsService.registerTool(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
554
|
+
toolWithoutRequiredParams.name,
|
|
555
|
+
toolWithoutRequiredParams.description,
|
|
556
|
+
toolWithoutRequiredParams.handler,
|
|
557
|
+
toolWithoutRequiredParams.parameters,
|
|
558
|
+
toolWithoutRequiredParams.endpoint
|
|
531
559
|
);
|
|
532
560
|
|
|
533
561
|
const requestWithNullBody = createMockRequest({
|
|
562
|
+
path: '/no-required-params-tool',
|
|
534
563
|
bodyJSON: null,
|
|
535
564
|
body: null
|
|
536
565
|
});
|
|
@@ -538,19 +567,29 @@ describe('ToolsService', () => {
|
|
|
538
567
|
const response = await toolsService.processRequest(requestWithNullBody, mockToolFunction);
|
|
539
568
|
|
|
540
569
|
expect(response.status).toBe(200);
|
|
541
|
-
expect(
|
|
570
|
+
expect(toolWithoutRequiredParams.handler).toHaveBeenCalledWith(mockToolFunction, null, undefined);
|
|
542
571
|
});
|
|
543
572
|
|
|
544
573
|
it('should handle request with undefined bodyJSON', async () => {
|
|
574
|
+
// Create a tool without required parameters
|
|
575
|
+
const toolWithoutRequiredParams = {
|
|
576
|
+
name: 'no_required_params_tool_2',
|
|
577
|
+
description: 'Tool without required parameters',
|
|
578
|
+
handler: jest.fn().mockResolvedValue({ result: 'success' }),
|
|
579
|
+
parameters: [], // No parameters defined
|
|
580
|
+
endpoint: '/no-required-params-tool-2'
|
|
581
|
+
};
|
|
582
|
+
|
|
545
583
|
toolsService.registerTool(
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
584
|
+
toolWithoutRequiredParams.name,
|
|
585
|
+
toolWithoutRequiredParams.description,
|
|
586
|
+
toolWithoutRequiredParams.handler,
|
|
587
|
+
toolWithoutRequiredParams.parameters,
|
|
588
|
+
toolWithoutRequiredParams.endpoint
|
|
551
589
|
);
|
|
552
590
|
|
|
553
591
|
const requestWithUndefinedBody = createMockRequest({
|
|
592
|
+
path: '/no-required-params-tool-2',
|
|
554
593
|
bodyJSON: undefined,
|
|
555
594
|
body: undefined
|
|
556
595
|
});
|
|
@@ -558,7 +597,7 @@ describe('ToolsService', () => {
|
|
|
558
597
|
const response = await toolsService.processRequest(requestWithUndefinedBody, mockToolFunction);
|
|
559
598
|
|
|
560
599
|
expect(response.status).toBe(200);
|
|
561
|
-
expect(
|
|
600
|
+
expect(toolWithoutRequiredParams.handler).toHaveBeenCalledWith(mockToolFunction, undefined, undefined);
|
|
562
601
|
});
|
|
563
602
|
|
|
564
603
|
it('should extract auth data from bodyJSON when body exists', async () => {
|
|
@@ -657,5 +696,107 @@ describe('ToolsService', () => {
|
|
|
657
696
|
);
|
|
658
697
|
});
|
|
659
698
|
});
|
|
699
|
+
|
|
700
|
+
describe('parameter validation', () => {
|
|
701
|
+
beforeEach(() => {
|
|
702
|
+
jest.clearAllMocks();
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('should validate parameters and return 400 for invalid types', async () => {
|
|
706
|
+
// Register a tool with specific parameter types
|
|
707
|
+
const toolWithTypedParams = {
|
|
708
|
+
name: 'typed_tool',
|
|
709
|
+
description: 'Tool with typed parameters',
|
|
710
|
+
handler: jest.fn().mockResolvedValue({ result: 'success' }),
|
|
711
|
+
parameters: [
|
|
712
|
+
new Parameter('name', ParameterType.String, 'User name', true),
|
|
713
|
+
new Parameter('age', ParameterType.Integer, 'User age', true),
|
|
714
|
+
new Parameter('active', ParameterType.Boolean, 'Is active', false)
|
|
715
|
+
],
|
|
716
|
+
endpoint: '/typed-tool'
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
toolsService.registerTool(
|
|
720
|
+
toolWithTypedParams.name,
|
|
721
|
+
toolWithTypedParams.description,
|
|
722
|
+
toolWithTypedParams.handler,
|
|
723
|
+
toolWithTypedParams.parameters,
|
|
724
|
+
toolWithTypedParams.endpoint
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
// Send invalid parameter types
|
|
728
|
+
const invalidRequest = createMockRequest({
|
|
729
|
+
path: '/typed-tool',
|
|
730
|
+
bodyJSON: {
|
|
731
|
+
parameters: {
|
|
732
|
+
name: 123, // should be string
|
|
733
|
+
age: '25', // should be integer
|
|
734
|
+
active: 'true' // should be boolean
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
const response = await toolsService.processRequest(invalidRequest, mockToolFunction);
|
|
740
|
+
|
|
741
|
+
expect(response.status).toBe(400);
|
|
742
|
+
|
|
743
|
+
// Expect RFC 9457 Problem Details format
|
|
744
|
+
expect(response.bodyJSON).toHaveProperty('title', 'One or more validation errors occurred.');
|
|
745
|
+
expect(response.bodyJSON).toHaveProperty('status', 400);
|
|
746
|
+
expect(response.bodyJSON).toHaveProperty('detail', 'See \'errors\' field for details.');
|
|
747
|
+
expect(response.bodyJSON).toHaveProperty('instance', '/typed-tool');
|
|
748
|
+
expect(response.bodyJSON).toHaveProperty('errors');
|
|
749
|
+
expect(response.bodyJSON.errors).toHaveLength(3);
|
|
750
|
+
|
|
751
|
+
// Check error structure - field and message
|
|
752
|
+
const errors = response.bodyJSON.errors;
|
|
753
|
+
expect(errors[0]).toHaveProperty('field', 'name');
|
|
754
|
+
expect(errors[0]).toHaveProperty('message', "Parameter 'name' must be a string, but received number");
|
|
755
|
+
|
|
756
|
+
// Check that the content type is set to application/problem+json for RFC 9457 compliance
|
|
757
|
+
expect(response.headers).toBeDefined();
|
|
758
|
+
expect(response.headers.get('content-type')).toBe('application/problem+json');
|
|
759
|
+
|
|
760
|
+
// Verify the handler was not called
|
|
761
|
+
expect(toolWithTypedParams.handler).not.toHaveBeenCalled();
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it('should skip validation for tools with no parameter definitions', async () => {
|
|
765
|
+
const toolWithoutParams = {
|
|
766
|
+
name: 'no_params_tool',
|
|
767
|
+
description: 'Tool without parameters',
|
|
768
|
+
handler: jest.fn().mockResolvedValue({ result: 'success' }),
|
|
769
|
+
parameters: [], // No parameters defined
|
|
770
|
+
endpoint: '/no-params-tool'
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
toolsService.registerTool(
|
|
774
|
+
toolWithoutParams.name,
|
|
775
|
+
toolWithoutParams.description,
|
|
776
|
+
toolWithoutParams.handler,
|
|
777
|
+
toolWithoutParams.parameters,
|
|
778
|
+
toolWithoutParams.endpoint
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
// Send request with any data (should be ignored)
|
|
782
|
+
const request = createMockRequest({
|
|
783
|
+
path: '/no-params-tool',
|
|
784
|
+
bodyJSON: {
|
|
785
|
+
parameters: {
|
|
786
|
+
unexpected: 'value'
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
const response = await toolsService.processRequest(request, mockToolFunction);
|
|
792
|
+
|
|
793
|
+
expect(response.status).toBe(200);
|
|
794
|
+
expect(toolWithoutParams.handler).toHaveBeenCalledWith(
|
|
795
|
+
mockToolFunction,
|
|
796
|
+
{ unexpected: 'value' },
|
|
797
|
+
undefined
|
|
798
|
+
);
|
|
799
|
+
});
|
|
800
|
+
});
|
|
660
801
|
});
|
|
661
802
|
});
|
package/src/service/Service.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* eslint-disable max-classes-per-file */
|
|
2
2
|
import { AuthRequirement, Parameter } from '../types/Models';
|
|
3
3
|
import * as App from '@zaiusinc/app-sdk';
|
|
4
|
-
import { logger } from '@zaiusinc/app-sdk';
|
|
4
|
+
import { logger, Headers } from '@zaiusinc/app-sdk';
|
|
5
5
|
import { ToolFunction } from '../function/ToolFunction';
|
|
6
6
|
import { GlobalToolFunction } from '../function/GlobalToolFunction';
|
|
7
|
+
import { ParameterValidator } from '../validation/ParameterValidator';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Default OptiID authentication requirement that will be enforced for all tools
|
|
@@ -173,6 +174,23 @@ export class ToolsService {
|
|
|
173
174
|
params = req.bodyJSON;
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
// Validate parameters before calling the handler (only if tool has parameter definitions)
|
|
178
|
+
if (func.parameters && func.parameters.length > 0) {
|
|
179
|
+
const validationResult = ParameterValidator.validate(params, func.parameters);
|
|
180
|
+
if (!validationResult.isValid) {
|
|
181
|
+
return new App.Response(400, {
|
|
182
|
+
title: 'One or more validation errors occurred.',
|
|
183
|
+
status: 400,
|
|
184
|
+
detail: "See 'errors' field for details.",
|
|
185
|
+
instance: func.endpoint,
|
|
186
|
+
errors: validationResult.errors.map((error) => ({
|
|
187
|
+
field: error.field,
|
|
188
|
+
message: error.message
|
|
189
|
+
}))
|
|
190
|
+
}, new Headers([['content-type', 'application/problem+json']]));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
176
194
|
// Extract auth data from body JSON
|
|
177
195
|
const authData = req.bodyJSON ? req.bodyJSON.auth : undefined;
|
|
178
196
|
|