librechat-data-provider 0.7.4 → 0.7.7
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/check_updates.sh +1 -0
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/react-query/index.es.js +1 -1
- package/dist/react-query/index.es.js.map +1 -1
- package/dist/react-query/package.json +1 -1
- package/package.json +6 -6
- package/react-query/package.json +1 -1
- package/server-rollup.config.js +3 -3
- package/specs/actions.spec.ts +700 -36
- package/specs/azure.spec.ts +8 -5
- package/specs/filetypes.spec.ts +1 -7
- package/specs/mcp.spec.ts +52 -0
- package/specs/openapiSpecs.ts +127 -0
- package/specs/utils.spec.ts +129 -0
- package/src/actions.ts +311 -101
- package/src/api-endpoints.ts +70 -13
- package/src/artifacts.ts +3104 -0
- package/src/azure.ts +40 -33
- package/src/bedrock.ts +227 -0
- package/src/config.ts +344 -78
- package/src/createPayload.ts +3 -1
- package/src/data-service.ts +353 -90
- package/src/file-config.ts +13 -2
- package/src/generate.ts +31 -2
- package/src/index.ts +12 -4
- package/src/keys.ts +17 -0
- package/src/mcp.ts +87 -0
- package/src/models.ts +1 -1
- package/src/parsers.ts +118 -60
- package/src/react-query/react-query-service.ts +54 -115
- package/src/request.ts +31 -7
- package/src/roles.ts +91 -2
- package/src/schemas.ts +513 -340
- package/src/types/agents.ts +276 -0
- package/src/types/assistants.ts +181 -27
- package/src/types/files.ts +6 -0
- package/src/types/mutations.ts +170 -7
- package/src/types/queries.ts +43 -21
- package/src/types/runs.ts +23 -0
- package/src/types.ts +132 -67
- package/src/utils.ts +44 -0
- package/src/zod.spec.ts +526 -0
- package/src/zod.ts +86 -0
- package/tsconfig.json +1 -2
- package/specs/parsers.spec.ts +0 -48
- package/src/sse.js +0 -242
package/specs/actions.spec.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
import { OpenAPIV3 } from 'openapi-types';
|
|
3
4
|
import {
|
|
4
5
|
createURL,
|
|
@@ -8,13 +9,19 @@ import {
|
|
|
8
9
|
FunctionSignature,
|
|
9
10
|
validateAndParseOpenAPISpec,
|
|
10
11
|
} from '../src/actions';
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getWeatherOpenapiSpec,
|
|
14
|
+
whimsicalOpenapiSpec,
|
|
15
|
+
scholarAIOpenapiSpec,
|
|
16
|
+
swapidev,
|
|
17
|
+
} from './openapiSpecs';
|
|
12
18
|
import { AuthorizationTypeEnum, AuthTypeEnum } from '../src/types/assistants';
|
|
13
19
|
import type { FlowchartSchema } from './openapiSpecs';
|
|
14
20
|
import type { ParametersSchema } from '../src/actions';
|
|
15
21
|
|
|
16
22
|
jest.mock('axios');
|
|
17
23
|
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
24
|
+
mockedAxios.create.mockReturnValue(mockedAxios);
|
|
18
25
|
|
|
19
26
|
describe('FunctionSignature', () => {
|
|
20
27
|
it('creates a function signature and converts to JSON tool', () => {
|
|
@@ -59,7 +66,7 @@ describe('ActionRequest', () => {
|
|
|
59
66
|
false,
|
|
60
67
|
'application/json',
|
|
61
68
|
);
|
|
62
|
-
|
|
69
|
+
actionRequest.setParams({ param1: 'value1' });
|
|
63
70
|
const response = await actionRequest.execute();
|
|
64
71
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', expect.anything());
|
|
65
72
|
expect(response.data).toEqual({ success: true, method: 'GET' });
|
|
@@ -84,7 +91,7 @@ describe('ActionRequest', () => {
|
|
|
84
91
|
false,
|
|
85
92
|
'application/json',
|
|
86
93
|
);
|
|
87
|
-
|
|
94
|
+
actionRequest.setParams({ param: 'test' });
|
|
88
95
|
const response = await actionRequest.execute();
|
|
89
96
|
expect(mockedAxios.get).toHaveBeenCalled();
|
|
90
97
|
expect(response.data.success).toBe(true);
|
|
@@ -100,7 +107,7 @@ describe('ActionRequest', () => {
|
|
|
100
107
|
false,
|
|
101
108
|
'application/json',
|
|
102
109
|
);
|
|
103
|
-
|
|
110
|
+
actionRequest.setParams({ param: 'test' });
|
|
104
111
|
const response = await actionRequest.execute();
|
|
105
112
|
expect(mockedAxios.post).toHaveBeenCalled();
|
|
106
113
|
expect(response.data.success).toBe(true);
|
|
@@ -116,7 +123,7 @@ describe('ActionRequest', () => {
|
|
|
116
123
|
false,
|
|
117
124
|
'application/json',
|
|
118
125
|
);
|
|
119
|
-
|
|
126
|
+
actionRequest.setParams({ param: 'test' });
|
|
120
127
|
const response = await actionRequest.execute();
|
|
121
128
|
expect(mockedAxios.put).toHaveBeenCalled();
|
|
122
129
|
expect(response.data.success).toBe(true);
|
|
@@ -132,7 +139,7 @@ describe('ActionRequest', () => {
|
|
|
132
139
|
false,
|
|
133
140
|
'application/json',
|
|
134
141
|
);
|
|
135
|
-
|
|
142
|
+
actionRequest.setParams({ param: 'test' });
|
|
136
143
|
const response = await actionRequest.execute();
|
|
137
144
|
expect(mockedAxios.delete).toHaveBeenCalled();
|
|
138
145
|
expect(response.data.success).toBe(true);
|
|
@@ -148,7 +155,7 @@ describe('ActionRequest', () => {
|
|
|
148
155
|
false,
|
|
149
156
|
'application/json',
|
|
150
157
|
);
|
|
151
|
-
|
|
158
|
+
actionRequest.setParams({ param: 'test' });
|
|
152
159
|
const response = await actionRequest.execute();
|
|
153
160
|
expect(mockedAxios.patch).toHaveBeenCalled();
|
|
154
161
|
expect(response.data.success).toBe(true);
|
|
@@ -163,7 +170,7 @@ describe('ActionRequest', () => {
|
|
|
163
170
|
false,
|
|
164
171
|
'application/json',
|
|
165
172
|
);
|
|
166
|
-
await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method:
|
|
173
|
+
await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method: invalid');
|
|
167
174
|
});
|
|
168
175
|
|
|
169
176
|
it('replaces path parameters with values from toolInput', async () => {
|
|
@@ -176,20 +183,21 @@ describe('ActionRequest', () => {
|
|
|
176
183
|
'application/json',
|
|
177
184
|
);
|
|
178
185
|
|
|
179
|
-
|
|
186
|
+
const executor = actionRequest.createExecutor();
|
|
187
|
+
executor.setParams({
|
|
180
188
|
stocksTicker: 'AAPL',
|
|
181
189
|
multiplier: 5,
|
|
182
190
|
startDate: '2023-01-01',
|
|
183
191
|
endDate: '2023-12-31',
|
|
184
192
|
});
|
|
185
193
|
|
|
186
|
-
expect(
|
|
187
|
-
expect(
|
|
194
|
+
expect(executor.path).toBe('/stocks/AAPL/bars/5');
|
|
195
|
+
expect(executor.params).toEqual({
|
|
188
196
|
startDate: '2023-01-01',
|
|
189
197
|
endDate: '2023-12-31',
|
|
190
198
|
});
|
|
191
199
|
|
|
192
|
-
await
|
|
200
|
+
await executor.execute();
|
|
193
201
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/stocks/AAPL/bars/5', {
|
|
194
202
|
headers: expect.anything(),
|
|
195
203
|
params: {
|
|
@@ -209,7 +217,271 @@ describe('ActionRequest', () => {
|
|
|
209
217
|
false,
|
|
210
218
|
'application/json',
|
|
211
219
|
);
|
|
212
|
-
await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method:
|
|
220
|
+
await expect(actionRequest.execute()).rejects.toThrow('Unsupported HTTP method: invalid');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('ActionRequest Concurrent Execution', () => {
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
jest.clearAllMocks();
|
|
226
|
+
mockedAxios.get.mockImplementation(async (url, config) => ({
|
|
227
|
+
data: { url, params: config?.params, headers: config?.headers },
|
|
228
|
+
}));
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('maintains isolated state between concurrent executions with different parameters', async () => {
|
|
232
|
+
const actionRequest = new ActionRequest(
|
|
233
|
+
'https://example.com',
|
|
234
|
+
'/math/sqrt/{number}',
|
|
235
|
+
'GET',
|
|
236
|
+
'getSqrt',
|
|
237
|
+
false,
|
|
238
|
+
'application/json',
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// Simulate concurrent requests with different numbers
|
|
242
|
+
const numbers = [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30];
|
|
243
|
+
const requests = numbers.map((num) => ({
|
|
244
|
+
number: num.toString(),
|
|
245
|
+
precision: '2',
|
|
246
|
+
}));
|
|
247
|
+
|
|
248
|
+
const responses = await Promise.all(
|
|
249
|
+
requests.map((params) => {
|
|
250
|
+
const executor = actionRequest.createExecutor();
|
|
251
|
+
return executor.setParams(params).execute();
|
|
252
|
+
}),
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// Verify each response used the correct path parameter
|
|
256
|
+
responses.forEach((response, index) => {
|
|
257
|
+
const expectedUrl = `https://example.com/math/sqrt/${numbers[index]}`;
|
|
258
|
+
expect(response.data.url).toBe(expectedUrl);
|
|
259
|
+
expect(response.data.params).toEqual({ precision: '2' });
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Verify the correct number of calls were made
|
|
263
|
+
expect(mockedAxios.get).toHaveBeenCalledTimes(numbers.length);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('maintains isolated authentication state between concurrent executions', async () => {
|
|
267
|
+
const actionRequest = new ActionRequest(
|
|
268
|
+
'https://example.com',
|
|
269
|
+
'/secure/resource/{id}',
|
|
270
|
+
'GET',
|
|
271
|
+
'getResource',
|
|
272
|
+
false,
|
|
273
|
+
'application/json',
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
const requests = [
|
|
277
|
+
{
|
|
278
|
+
params: { id: '1' },
|
|
279
|
+
auth: {
|
|
280
|
+
auth: {
|
|
281
|
+
type: AuthTypeEnum.ServiceHttp,
|
|
282
|
+
authorization_type: AuthorizationTypeEnum.Bearer,
|
|
283
|
+
},
|
|
284
|
+
api_key: 'token1',
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
params: { id: '2' },
|
|
289
|
+
auth: {
|
|
290
|
+
auth: {
|
|
291
|
+
type: AuthTypeEnum.ServiceHttp,
|
|
292
|
+
authorization_type: AuthorizationTypeEnum.Bearer,
|
|
293
|
+
},
|
|
294
|
+
api_key: 'token2',
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
const responses = await Promise.all(
|
|
300
|
+
requests.map(async ({ params, auth }) => {
|
|
301
|
+
const executor = actionRequest.createExecutor();
|
|
302
|
+
return (await executor.setParams(params).setAuth(auth)).execute();
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// Verify each response had its own auth token
|
|
307
|
+
responses.forEach((response, index) => {
|
|
308
|
+
const expectedUrl = `https://example.com/secure/resource/${index + 1}`;
|
|
309
|
+
expect(response.data.url).toBe(expectedUrl);
|
|
310
|
+
expect(response.data.headers).toMatchObject({
|
|
311
|
+
Authorization: `Bearer token${index + 1}`,
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('handles mixed authentication types concurrently', async () => {
|
|
317
|
+
const actionRequest = new ActionRequest(
|
|
318
|
+
'https://example.com',
|
|
319
|
+
'/api/{version}/data',
|
|
320
|
+
'GET',
|
|
321
|
+
'getData',
|
|
322
|
+
false,
|
|
323
|
+
'application/json',
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const requests = [
|
|
327
|
+
{
|
|
328
|
+
params: { version: 'v1' },
|
|
329
|
+
auth: {
|
|
330
|
+
auth: {
|
|
331
|
+
type: AuthTypeEnum.ServiceHttp,
|
|
332
|
+
authorization_type: AuthorizationTypeEnum.Bearer,
|
|
333
|
+
},
|
|
334
|
+
api_key: 'bearer_token',
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
params: { version: 'v2' },
|
|
339
|
+
auth: {
|
|
340
|
+
auth: {
|
|
341
|
+
type: AuthTypeEnum.ServiceHttp,
|
|
342
|
+
authorization_type: AuthorizationTypeEnum.Basic,
|
|
343
|
+
},
|
|
344
|
+
api_key: 'basic:auth',
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
params: { version: 'v3' },
|
|
349
|
+
auth: {
|
|
350
|
+
auth: {
|
|
351
|
+
type: AuthTypeEnum.ServiceHttp,
|
|
352
|
+
authorization_type: AuthorizationTypeEnum.Custom,
|
|
353
|
+
custom_auth_header: 'X-API-Key',
|
|
354
|
+
},
|
|
355
|
+
api_key: 'custom_key',
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
const responses = await Promise.all(
|
|
361
|
+
requests.map(async ({ params, auth }) => {
|
|
362
|
+
const executor = actionRequest.createExecutor();
|
|
363
|
+
return (await executor.setParams(params).setAuth(auth)).execute();
|
|
364
|
+
}),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Verify each response had the correct auth type and headers
|
|
368
|
+
expect(responses[0].data.headers).toMatchObject({
|
|
369
|
+
Authorization: 'Bearer bearer_token',
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(responses[1].data.headers).toMatchObject({
|
|
373
|
+
Authorization: `Basic ${Buffer.from('basic:auth').toString('base64')}`,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
expect(responses[2].data.headers).toMatchObject({
|
|
377
|
+
'X-API-Key': 'custom_key',
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('maintains parameter integrity during concurrent path parameter replacement', async () => {
|
|
382
|
+
const actionRequest = new ActionRequest(
|
|
383
|
+
'https://example.com',
|
|
384
|
+
'/users/{userId}/posts/{postId}',
|
|
385
|
+
'GET',
|
|
386
|
+
'getUserPost',
|
|
387
|
+
false,
|
|
388
|
+
'application/json',
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
const requests = [
|
|
392
|
+
{ userId: '1', postId: 'a', filter: 'recent' },
|
|
393
|
+
{ userId: '2', postId: 'b', filter: 'popular' },
|
|
394
|
+
{ userId: '3', postId: 'c', filter: 'trending' },
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
const responses = await Promise.all(
|
|
398
|
+
requests.map((params) => {
|
|
399
|
+
const executor = actionRequest.createExecutor();
|
|
400
|
+
return executor.setParams(params).execute();
|
|
401
|
+
}),
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
responses.forEach((response, index) => {
|
|
405
|
+
const expectedUrl = `https://example.com/users/${requests[index].userId}/posts/${requests[index].postId}`;
|
|
406
|
+
expect(response.data.url).toBe(expectedUrl);
|
|
407
|
+
expect(response.data.params).toEqual({ filter: requests[index].filter });
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('preserves original ActionRequest state after multiple executions', async () => {
|
|
412
|
+
const actionRequest = new ActionRequest(
|
|
413
|
+
'https://example.com',
|
|
414
|
+
'/original/{param}',
|
|
415
|
+
'GET',
|
|
416
|
+
'testOp',
|
|
417
|
+
false,
|
|
418
|
+
'application/json',
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
// Store original values
|
|
422
|
+
const originalPath = actionRequest.path;
|
|
423
|
+
const originalDomain = actionRequest.domain;
|
|
424
|
+
const originalMethod = actionRequest.method;
|
|
425
|
+
|
|
426
|
+
// Perform multiple concurrent executions
|
|
427
|
+
await Promise.all([
|
|
428
|
+
actionRequest.createExecutor().setParams({ param: '1' }).execute(),
|
|
429
|
+
actionRequest.createExecutor().setParams({ param: '2' }).execute(),
|
|
430
|
+
actionRequest.createExecutor().setParams({ param: '3' }).execute(),
|
|
431
|
+
]);
|
|
432
|
+
|
|
433
|
+
// Verify original ActionRequest remains unchanged
|
|
434
|
+
expect(actionRequest.path).toBe(originalPath);
|
|
435
|
+
expect(actionRequest.domain).toBe(originalDomain);
|
|
436
|
+
expect(actionRequest.method).toBe(originalMethod);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('shares immutable configuration between executors from the same ActionRequest', () => {
|
|
440
|
+
const actionRequest = new ActionRequest(
|
|
441
|
+
'https://example.com',
|
|
442
|
+
'/api/{version}/data',
|
|
443
|
+
'GET',
|
|
444
|
+
'getData',
|
|
445
|
+
false,
|
|
446
|
+
'application/json',
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
// Create multiple executors
|
|
450
|
+
const executor1 = actionRequest.createExecutor();
|
|
451
|
+
const executor2 = actionRequest.createExecutor();
|
|
452
|
+
const executor3 = actionRequest.createExecutor();
|
|
453
|
+
|
|
454
|
+
// Test that the configuration properties are shared
|
|
455
|
+
[executor1, executor2, executor3].forEach((executor) => {
|
|
456
|
+
expect(executor.getConfig()).toBeDefined();
|
|
457
|
+
expect(executor.getConfig()).toEqual({
|
|
458
|
+
domain: 'https://example.com',
|
|
459
|
+
basePath: '/api/{version}/data',
|
|
460
|
+
method: 'GET',
|
|
461
|
+
operation: 'getData',
|
|
462
|
+
isConsequential: false,
|
|
463
|
+
contentType: 'application/json',
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Verify that config objects are the exact same instance (shared reference)
|
|
468
|
+
expect(executor1.getConfig()).toBe(executor2.getConfig());
|
|
469
|
+
expect(executor2.getConfig()).toBe(executor3.getConfig());
|
|
470
|
+
|
|
471
|
+
// Verify that modifying mutable state doesn't affect other executors
|
|
472
|
+
executor1.setParams({ version: 'v1' });
|
|
473
|
+
executor2.setParams({ version: 'v2' });
|
|
474
|
+
executor3.setParams({ version: 'v3' });
|
|
475
|
+
|
|
476
|
+
expect(executor1.path).toBe('/api/v1/data');
|
|
477
|
+
expect(executor2.path).toBe('/api/v2/data');
|
|
478
|
+
expect(executor3.path).toBe('/api/v3/data');
|
|
479
|
+
|
|
480
|
+
// Verify that the original config remains unchanged
|
|
481
|
+
expect(executor1.getConfig().basePath).toBe('/api/{version}/data');
|
|
482
|
+
expect(executor2.getConfig().basePath).toBe('/api/{version}/data');
|
|
483
|
+
expect(executor3.getConfig().basePath).toBe('/api/{version}/data');
|
|
484
|
+
});
|
|
213
485
|
});
|
|
214
486
|
});
|
|
215
487
|
|
|
@@ -227,7 +499,8 @@ describe('Authentication Handling', () => {
|
|
|
227
499
|
const api_key = 'user:pass';
|
|
228
500
|
const encodedCredentials = Buffer.from('user:pass').toString('base64');
|
|
229
501
|
|
|
230
|
-
actionRequest.
|
|
502
|
+
const executor = actionRequest.createExecutor();
|
|
503
|
+
await executor.setParams({ param1: 'value1' }).setAuth({
|
|
231
504
|
auth: {
|
|
232
505
|
type: AuthTypeEnum.ServiceHttp,
|
|
233
506
|
authorization_type: AuthorizationTypeEnum.Basic,
|
|
@@ -235,13 +508,13 @@ describe('Authentication Handling', () => {
|
|
|
235
508
|
api_key,
|
|
236
509
|
});
|
|
237
510
|
|
|
238
|
-
await
|
|
239
|
-
await actionRequest.execute();
|
|
511
|
+
await executor.execute();
|
|
240
512
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', {
|
|
241
513
|
headers: expect.objectContaining({
|
|
242
514
|
Authorization: `Basic ${encodedCredentials}`,
|
|
515
|
+
'Content-Type': 'application/json',
|
|
243
516
|
}),
|
|
244
|
-
params:
|
|
517
|
+
params: { param1: 'value1' },
|
|
245
518
|
});
|
|
246
519
|
});
|
|
247
520
|
|
|
@@ -254,20 +527,23 @@ describe('Authentication Handling', () => {
|
|
|
254
527
|
false,
|
|
255
528
|
'application/json',
|
|
256
529
|
);
|
|
257
|
-
|
|
530
|
+
|
|
531
|
+
const executor = actionRequest.createExecutor();
|
|
532
|
+
await executor.setParams({ param1: 'value1' }).setAuth({
|
|
258
533
|
auth: {
|
|
259
534
|
type: AuthTypeEnum.ServiceHttp,
|
|
260
535
|
authorization_type: AuthorizationTypeEnum.Bearer,
|
|
261
536
|
},
|
|
262
537
|
api_key: 'token123',
|
|
263
538
|
});
|
|
264
|
-
|
|
265
|
-
await
|
|
539
|
+
|
|
540
|
+
await executor.execute();
|
|
266
541
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', {
|
|
267
542
|
headers: expect.objectContaining({
|
|
268
543
|
Authorization: 'Bearer token123',
|
|
544
|
+
'Content-Type': 'application/json',
|
|
269
545
|
}),
|
|
270
|
-
params:
|
|
546
|
+
params: { param1: 'value1' },
|
|
271
547
|
});
|
|
272
548
|
});
|
|
273
549
|
|
|
@@ -280,22 +556,24 @@ describe('Authentication Handling', () => {
|
|
|
280
556
|
false,
|
|
281
557
|
'application/json',
|
|
282
558
|
);
|
|
283
|
-
|
|
284
|
-
actionRequest.
|
|
559
|
+
|
|
560
|
+
const executor = actionRequest.createExecutor();
|
|
561
|
+
await executor.setParams({ param1: 'value1' }).setAuth({
|
|
285
562
|
auth: {
|
|
286
|
-
type: AuthTypeEnum.ServiceHttp,
|
|
287
|
-
authorization_type: AuthorizationTypeEnum.Custom,
|
|
563
|
+
type: AuthTypeEnum.ServiceHttp,
|
|
564
|
+
authorization_type: AuthorizationTypeEnum.Custom,
|
|
288
565
|
custom_auth_header: 'X-API-KEY',
|
|
289
566
|
},
|
|
290
567
|
api_key: 'abc123',
|
|
291
568
|
});
|
|
292
|
-
|
|
293
|
-
await
|
|
569
|
+
|
|
570
|
+
await executor.execute();
|
|
294
571
|
expect(mockedAxios.get).toHaveBeenCalledWith('https://example.com/test', {
|
|
295
572
|
headers: expect.objectContaining({
|
|
296
573
|
'X-API-KEY': 'abc123',
|
|
574
|
+
'Content-Type': 'application/json',
|
|
297
575
|
}),
|
|
298
|
-
params:
|
|
576
|
+
params: { param1: 'value1' },
|
|
299
577
|
});
|
|
300
578
|
});
|
|
301
579
|
});
|
|
@@ -306,22 +584,100 @@ describe('resolveRef', () => {
|
|
|
306
584
|
const flowchartRequestRef = (
|
|
307
585
|
openapiSpec.paths['/ai.chatgpt.render-flowchart']?.post
|
|
308
586
|
?.requestBody as OpenAPIV3.RequestBodyObject
|
|
309
|
-
)
|
|
587
|
+
).content['application/json'].schema;
|
|
588
|
+
|
|
310
589
|
expect(flowchartRequestRef).toBeDefined();
|
|
311
|
-
|
|
312
|
-
|
|
590
|
+
|
|
591
|
+
const resolvedSchemaObject = resolveRef(
|
|
592
|
+
flowchartRequestRef as OpenAPIV3.ReferenceObject,
|
|
313
593
|
openapiSpec.components,
|
|
314
|
-
);
|
|
594
|
+
) as OpenAPIV3.SchemaObject;
|
|
595
|
+
|
|
596
|
+
expect(resolvedSchemaObject).toBeDefined();
|
|
597
|
+
expect(resolvedSchemaObject.type).toBe('object');
|
|
598
|
+
expect(resolvedSchemaObject.properties).toBeDefined();
|
|
315
599
|
|
|
316
|
-
|
|
317
|
-
expect(resolvedFlowchartRequest.type).toBe('object');
|
|
318
|
-
const properties = resolvedFlowchartRequest.properties as FlowchartSchema;
|
|
319
|
-
expect(properties).toBeDefined();
|
|
600
|
+
const properties = resolvedSchemaObject.properties as FlowchartSchema;
|
|
320
601
|
expect(properties.mermaid).toBeDefined();
|
|
321
602
|
expect(properties.mermaid.type).toBe('string');
|
|
322
603
|
});
|
|
323
604
|
});
|
|
324
605
|
|
|
606
|
+
describe('resolveRef general cases', () => {
|
|
607
|
+
const spec = {
|
|
608
|
+
openapi: '3.0.0',
|
|
609
|
+
info: { title: 'TestSpec', version: '1.0.0' },
|
|
610
|
+
paths: {},
|
|
611
|
+
components: {
|
|
612
|
+
schemas: {
|
|
613
|
+
TestSchema: { type: 'string' },
|
|
614
|
+
},
|
|
615
|
+
parameters: {
|
|
616
|
+
TestParam: {
|
|
617
|
+
name: 'myParam',
|
|
618
|
+
in: 'query',
|
|
619
|
+
required: false,
|
|
620
|
+
schema: { $ref: '#/components/schemas/TestSchema' },
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
requestBodies: {
|
|
624
|
+
TestRequestBody: {
|
|
625
|
+
content: {
|
|
626
|
+
'application/json': {
|
|
627
|
+
schema: { $ref: '#/components/schemas/TestSchema' },
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
},
|
|
633
|
+
} satisfies OpenAPIV3.Document;
|
|
634
|
+
|
|
635
|
+
it('resolves schema refs correctly', () => {
|
|
636
|
+
const schemaRef: OpenAPIV3.ReferenceObject = { $ref: '#/components/schemas/TestSchema' };
|
|
637
|
+
const resolvedSchema = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>(
|
|
638
|
+
schemaRef,
|
|
639
|
+
spec.components,
|
|
640
|
+
);
|
|
641
|
+
expect(resolvedSchema.type).toEqual('string');
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('resolves parameter refs correctly, then schema within parameter', () => {
|
|
645
|
+
const paramRef: OpenAPIV3.ReferenceObject = { $ref: '#/components/parameters/TestParam' };
|
|
646
|
+
const resolvedParam = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject>(
|
|
647
|
+
paramRef,
|
|
648
|
+
spec.components,
|
|
649
|
+
);
|
|
650
|
+
expect(resolvedParam.name).toEqual('myParam');
|
|
651
|
+
expect(resolvedParam.in).toEqual('query');
|
|
652
|
+
expect(resolvedParam.required).toBe(false);
|
|
653
|
+
|
|
654
|
+
const paramSchema = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>(
|
|
655
|
+
resolvedParam.schema as OpenAPIV3.ReferenceObject,
|
|
656
|
+
spec.components,
|
|
657
|
+
);
|
|
658
|
+
expect(paramSchema.type).toEqual('string');
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('resolves requestBody refs correctly, then schema within requestBody', () => {
|
|
662
|
+
const requestBodyRef: OpenAPIV3.ReferenceObject = {
|
|
663
|
+
$ref: '#/components/requestBodies/TestRequestBody',
|
|
664
|
+
};
|
|
665
|
+
const resolvedRequestBody = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject>(
|
|
666
|
+
requestBodyRef,
|
|
667
|
+
spec.components,
|
|
668
|
+
);
|
|
669
|
+
|
|
670
|
+
expect(resolvedRequestBody.content['application/json']).toBeDefined();
|
|
671
|
+
|
|
672
|
+
const schemaInRequestBody = resolveRef<OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject>(
|
|
673
|
+
resolvedRequestBody.content['application/json'].schema as OpenAPIV3.ReferenceObject,
|
|
674
|
+
spec.components,
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
expect(schemaInRequestBody.type).toEqual('string');
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
|
|
325
681
|
describe('openapiToFunction', () => {
|
|
326
682
|
it('converts OpenAPI spec to function signatures and request builders', () => {
|
|
327
683
|
const { functionSignatures, requestBuilders } = openapiToFunction(getWeatherOpenapiSpec);
|
|
@@ -548,4 +904,312 @@ describe('createURL', () => {
|
|
|
548
904
|
'https://example.com/subdirectory/api/v1/users',
|
|
549
905
|
);
|
|
550
906
|
});
|
|
907
|
+
|
|
908
|
+
describe('openapiToFunction zodSchemas', () => {
|
|
909
|
+
describe('getWeatherOpenapiSpec', () => {
|
|
910
|
+
const { zodSchemas } = openapiToFunction(getWeatherOpenapiSpec, true);
|
|
911
|
+
|
|
912
|
+
it('generates correct Zod schema for GetCurrentWeather', () => {
|
|
913
|
+
expect(zodSchemas).toBeDefined();
|
|
914
|
+
expect(zodSchemas?.GetCurrentWeather).toBeDefined();
|
|
915
|
+
|
|
916
|
+
const GetCurrentWeatherSchema = zodSchemas?.GetCurrentWeather;
|
|
917
|
+
|
|
918
|
+
expect(GetCurrentWeatherSchema instanceof z.ZodObject).toBe(true);
|
|
919
|
+
|
|
920
|
+
if (!(GetCurrentWeatherSchema instanceof z.ZodObject)) {
|
|
921
|
+
throw new Error('GetCurrentWeatherSchema is not a ZodObject');
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const shape = GetCurrentWeatherSchema.shape;
|
|
925
|
+
expect(shape.location instanceof z.ZodString).toBe(true);
|
|
926
|
+
|
|
927
|
+
// Check locations property
|
|
928
|
+
expect(shape.locations).toBeDefined();
|
|
929
|
+
expect(shape.locations instanceof z.ZodOptional).toBe(true);
|
|
930
|
+
|
|
931
|
+
if (!(shape.locations instanceof z.ZodOptional)) {
|
|
932
|
+
throw new Error('locations is not a ZodOptional');
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const locationsInnerType = shape.locations._def.innerType;
|
|
936
|
+
expect(locationsInnerType instanceof z.ZodArray).toBe(true);
|
|
937
|
+
|
|
938
|
+
if (!(locationsInnerType instanceof z.ZodArray)) {
|
|
939
|
+
throw new Error('locationsInnerType is not a ZodArray');
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const locationsItemSchema = locationsInnerType.element;
|
|
943
|
+
expect(locationsItemSchema instanceof z.ZodObject).toBe(true);
|
|
944
|
+
|
|
945
|
+
if (!(locationsItemSchema instanceof z.ZodObject)) {
|
|
946
|
+
throw new Error('locationsItemSchema is not a ZodObject');
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Validate the structure of locationsItemSchema
|
|
950
|
+
expect(locationsItemSchema.shape.city instanceof z.ZodString).toBe(true);
|
|
951
|
+
expect(locationsItemSchema.shape.state instanceof z.ZodString).toBe(true);
|
|
952
|
+
expect(locationsItemSchema.shape.countryCode instanceof z.ZodString).toBe(true);
|
|
953
|
+
|
|
954
|
+
// Check if time is optional
|
|
955
|
+
const timeSchema = locationsItemSchema.shape.time;
|
|
956
|
+
expect(timeSchema instanceof z.ZodOptional).toBe(true);
|
|
957
|
+
|
|
958
|
+
if (!(timeSchema instanceof z.ZodOptional)) {
|
|
959
|
+
throw new Error('timeSchema is not a ZodOptional');
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
expect(timeSchema._def.innerType instanceof z.ZodString).toBe(true);
|
|
963
|
+
|
|
964
|
+
// Check the description
|
|
965
|
+
expect(shape.locations._def.description).toBe(
|
|
966
|
+
'A list of locations to retrieve the weather for.',
|
|
967
|
+
);
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
it('validates correct data for GetCurrentWeather', () => {
|
|
971
|
+
const GetCurrentWeatherSchema = zodSchemas?.GetCurrentWeather as z.ZodTypeAny;
|
|
972
|
+
const validData = {
|
|
973
|
+
location: 'New York',
|
|
974
|
+
locations: [
|
|
975
|
+
{ city: 'New York', state: 'NY', countryCode: 'US', time: '2023-12-04T14:00:00Z' },
|
|
976
|
+
],
|
|
977
|
+
};
|
|
978
|
+
expect(() => GetCurrentWeatherSchema.parse(validData)).not.toThrow();
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
it('throws error for invalid data for GetCurrentWeather', () => {
|
|
982
|
+
const GetCurrentWeatherSchema = zodSchemas?.GetCurrentWeather as z.ZodTypeAny;
|
|
983
|
+
const invalidData = {
|
|
984
|
+
location: 123,
|
|
985
|
+
locations: [{ city: 'New York', state: 'NY', countryCode: 'US', time: 'invalid-time' }],
|
|
986
|
+
};
|
|
987
|
+
expect(() => GetCurrentWeatherSchema.parse(invalidData)).toThrow();
|
|
988
|
+
});
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
describe('whimsicalOpenapiSpec', () => {
|
|
992
|
+
const { zodSchemas } = openapiToFunction(whimsicalOpenapiSpec, true);
|
|
993
|
+
|
|
994
|
+
it('generates correct Zod schema for postRenderFlowchart', () => {
|
|
995
|
+
expect(zodSchemas).toBeDefined();
|
|
996
|
+
expect(zodSchemas?.postRenderFlowchart).toBeDefined();
|
|
997
|
+
|
|
998
|
+
const PostRenderFlowchartSchema = zodSchemas?.postRenderFlowchart;
|
|
999
|
+
expect(PostRenderFlowchartSchema).toBeInstanceOf(z.ZodObject);
|
|
1000
|
+
|
|
1001
|
+
if (!(PostRenderFlowchartSchema instanceof z.ZodObject)) {
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
const shape = PostRenderFlowchartSchema.shape;
|
|
1006
|
+
expect(shape.mermaid).toBeInstanceOf(z.ZodString);
|
|
1007
|
+
expect(shape.title).toBeInstanceOf(z.ZodOptional);
|
|
1008
|
+
expect((shape.title as z.ZodOptional<z.ZodString>)._def.innerType).toBeInstanceOf(
|
|
1009
|
+
z.ZodString,
|
|
1010
|
+
);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it('validates correct data for postRenderFlowchart', () => {
|
|
1014
|
+
const PostRenderFlowchartSchema = zodSchemas?.postRenderFlowchart;
|
|
1015
|
+
const validData = {
|
|
1016
|
+
mermaid: 'graph TD; A-->B; B-->C; C-->D;',
|
|
1017
|
+
title: 'Test Flowchart',
|
|
1018
|
+
};
|
|
1019
|
+
expect(() => PostRenderFlowchartSchema?.parse(validData)).not.toThrow();
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
it('throws error for invalid data for postRenderFlowchart', () => {
|
|
1023
|
+
const PostRenderFlowchartSchema = zodSchemas?.postRenderFlowchart;
|
|
1024
|
+
const invalidData = {
|
|
1025
|
+
mermaid: 123,
|
|
1026
|
+
title: 42,
|
|
1027
|
+
};
|
|
1028
|
+
expect(() => PostRenderFlowchartSchema?.parse(invalidData)).toThrow();
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
describe('scholarAIOpenapiSpec', () => {
|
|
1033
|
+
const result = validateAndParseOpenAPISpec(scholarAIOpenapiSpec);
|
|
1034
|
+
const spec = result.spec as OpenAPIV3.Document;
|
|
1035
|
+
const { zodSchemas } = openapiToFunction(spec, true);
|
|
1036
|
+
|
|
1037
|
+
it('generates correct Zod schema for searchAbstracts', () => {
|
|
1038
|
+
expect(zodSchemas).toBeDefined();
|
|
1039
|
+
expect(zodSchemas?.searchAbstracts).toBeDefined();
|
|
1040
|
+
|
|
1041
|
+
const SearchAbstractsSchema = zodSchemas?.searchAbstracts;
|
|
1042
|
+
expect(SearchAbstractsSchema).toBeInstanceOf(z.ZodObject);
|
|
1043
|
+
|
|
1044
|
+
if (!(SearchAbstractsSchema instanceof z.ZodObject)) {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
const shape = SearchAbstractsSchema.shape;
|
|
1049
|
+
expect(shape.keywords).toBeInstanceOf(z.ZodString);
|
|
1050
|
+
expect(shape.sort).toBeInstanceOf(z.ZodOptional);
|
|
1051
|
+
expect(
|
|
1052
|
+
(shape.sort as z.ZodOptional<z.ZodEnum<[string, ...string[]]>>)._def.innerType,
|
|
1053
|
+
).toBeInstanceOf(z.ZodEnum);
|
|
1054
|
+
expect(shape.query).toBeInstanceOf(z.ZodString);
|
|
1055
|
+
expect(shape.peer_reviewed_only).toBeInstanceOf(z.ZodOptional);
|
|
1056
|
+
expect(shape.start_year).toBeInstanceOf(z.ZodOptional);
|
|
1057
|
+
expect(shape.end_year).toBeInstanceOf(z.ZodOptional);
|
|
1058
|
+
expect(shape.offset).toBeInstanceOf(z.ZodOptional);
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
it('validates correct data for searchAbstracts', () => {
|
|
1062
|
+
const SearchAbstractsSchema = zodSchemas?.searchAbstracts;
|
|
1063
|
+
const validData = {
|
|
1064
|
+
keywords: 'machine learning',
|
|
1065
|
+
sort: 'cited_by_count',
|
|
1066
|
+
query: 'AI applications',
|
|
1067
|
+
peer_reviewed_only: 'true',
|
|
1068
|
+
start_year: '2020',
|
|
1069
|
+
end_year: '2023',
|
|
1070
|
+
offset: '0',
|
|
1071
|
+
};
|
|
1072
|
+
expect(() => SearchAbstractsSchema?.parse(validData)).not.toThrow();
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
it('throws error for invalid data for searchAbstracts', () => {
|
|
1076
|
+
const SearchAbstractsSchema = zodSchemas?.searchAbstracts;
|
|
1077
|
+
const invalidData = {
|
|
1078
|
+
keywords: 123,
|
|
1079
|
+
sort: 'invalid_sort',
|
|
1080
|
+
query: 42,
|
|
1081
|
+
peer_reviewed_only: 'maybe',
|
|
1082
|
+
start_year: 2020,
|
|
1083
|
+
end_year: 2023,
|
|
1084
|
+
offset: 0,
|
|
1085
|
+
};
|
|
1086
|
+
expect(() => SearchAbstractsSchema?.parse(invalidData)).toThrow();
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
it('generates correct Zod schema for getFullText', () => {
|
|
1090
|
+
expect(zodSchemas?.getFullText).toBeDefined();
|
|
1091
|
+
|
|
1092
|
+
const GetFullTextSchema = zodSchemas?.getFullText;
|
|
1093
|
+
expect(GetFullTextSchema).toBeInstanceOf(z.ZodObject);
|
|
1094
|
+
|
|
1095
|
+
if (!(GetFullTextSchema instanceof z.ZodObject)) {
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
const shape = GetFullTextSchema.shape;
|
|
1100
|
+
expect(shape.pdf_url).toBeInstanceOf(z.ZodString);
|
|
1101
|
+
expect(shape.chunk).toBeInstanceOf(z.ZodOptional);
|
|
1102
|
+
expect((shape.chunk as z.ZodOptional<z.ZodNumber>)._def.innerType).toBeInstanceOf(
|
|
1103
|
+
z.ZodNumber,
|
|
1104
|
+
);
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it('generates correct Zod schema for saveCitation', () => {
|
|
1108
|
+
expect(zodSchemas?.saveCitation).toBeDefined();
|
|
1109
|
+
|
|
1110
|
+
const SaveCitationSchema = zodSchemas?.saveCitation;
|
|
1111
|
+
expect(SaveCitationSchema).toBeInstanceOf(z.ZodObject);
|
|
1112
|
+
|
|
1113
|
+
if (!(SaveCitationSchema instanceof z.ZodObject)) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const shape = SaveCitationSchema.shape;
|
|
1118
|
+
expect(shape.doi).toBeInstanceOf(z.ZodString);
|
|
1119
|
+
expect(shape.zotero_user_id).toBeInstanceOf(z.ZodString);
|
|
1120
|
+
expect(shape.zotero_api_key).toBeInstanceOf(z.ZodString);
|
|
1121
|
+
});
|
|
1122
|
+
});
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
describe('openapiToFunction zodSchemas for SWAPI', () => {
|
|
1126
|
+
const result = validateAndParseOpenAPISpec(swapidev);
|
|
1127
|
+
const spec = result.spec as OpenAPIV3.Document;
|
|
1128
|
+
const { zodSchemas } = openapiToFunction(spec, true);
|
|
1129
|
+
|
|
1130
|
+
describe('getPeople schema', () => {
|
|
1131
|
+
it('does not generate Zod schema for getPeople (no parameters)', () => {
|
|
1132
|
+
expect(zodSchemas).toBeDefined();
|
|
1133
|
+
expect(zodSchemas?.getPeople).toBeUndefined();
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
it('validates correct data for getPeople', () => {
|
|
1137
|
+
const GetPeopleSchema = zodSchemas?.getPeople;
|
|
1138
|
+
expect(GetPeopleSchema).toBeUndefined();
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
it('does not throw for invalid data for getPeople', () => {
|
|
1142
|
+
const GetPeopleSchema = zodSchemas?.getPeople;
|
|
1143
|
+
expect(GetPeopleSchema).toBeUndefined();
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
describe('getPersonById schema', () => {
|
|
1148
|
+
it('generates correct Zod schema for getPersonById', () => {
|
|
1149
|
+
expect(zodSchemas).toBeDefined();
|
|
1150
|
+
expect(zodSchemas?.getPersonById).toBeDefined();
|
|
1151
|
+
|
|
1152
|
+
const GetPersonByIdSchema = zodSchemas?.getPersonById;
|
|
1153
|
+
expect(GetPersonByIdSchema).toBeInstanceOf(z.ZodObject);
|
|
1154
|
+
|
|
1155
|
+
if (!(GetPersonByIdSchema instanceof z.ZodObject)) {
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const shape = GetPersonByIdSchema.shape;
|
|
1160
|
+
expect(shape.id).toBeInstanceOf(z.ZodString);
|
|
1161
|
+
});
|
|
1162
|
+
|
|
1163
|
+
it('validates correct data for getPersonById', () => {
|
|
1164
|
+
const GetPersonByIdSchema = zodSchemas?.getPersonById;
|
|
1165
|
+
const validData = { id: '1' };
|
|
1166
|
+
expect(() => GetPersonByIdSchema?.parse(validData)).not.toThrow();
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
it('throws error for invalid data for getPersonById', () => {
|
|
1170
|
+
const GetPersonByIdSchema = zodSchemas?.getPersonById;
|
|
1171
|
+
const invalidData = { id: 1 }; // should be string
|
|
1172
|
+
expect(() => GetPersonByIdSchema?.parse(invalidData)).toThrow();
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
describe('openapiToFunction parameter refs resolution', () => {
|
|
1178
|
+
const weatherSpec = {
|
|
1179
|
+
openapi: '3.0.0',
|
|
1180
|
+
info: { title: 'Weather', version: '1.0.0' },
|
|
1181
|
+
servers: [{ url: 'https://api.weather.gov' }],
|
|
1182
|
+
paths: {
|
|
1183
|
+
'/points/{point}': {
|
|
1184
|
+
get: {
|
|
1185
|
+
operationId: 'getPoint',
|
|
1186
|
+
parameters: [{ $ref: '#/components/parameters/PathPoint' }],
|
|
1187
|
+
responses: { '200': { description: 'ok' } },
|
|
1188
|
+
},
|
|
1189
|
+
},
|
|
1190
|
+
},
|
|
1191
|
+
components: {
|
|
1192
|
+
parameters: {
|
|
1193
|
+
PathPoint: {
|
|
1194
|
+
name: 'point',
|
|
1195
|
+
in: 'path',
|
|
1196
|
+
required: true,
|
|
1197
|
+
schema: { type: 'string', pattern: '^(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)$' },
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
} satisfies OpenAPIV3.Document;
|
|
1202
|
+
|
|
1203
|
+
it('correctly resolves $ref for parameters', () => {
|
|
1204
|
+
const { functionSignatures } = openapiToFunction(weatherSpec, true);
|
|
1205
|
+
const func = functionSignatures.find((sig) => sig.name === 'getPoint');
|
|
1206
|
+
expect(func).toBeDefined();
|
|
1207
|
+
expect(func?.parameters.properties).toHaveProperty('point');
|
|
1208
|
+
expect(func?.parameters.required).toContain('point');
|
|
1209
|
+
|
|
1210
|
+
const paramSchema = func?.parameters.properties['point'] as OpenAPIV3.SchemaObject;
|
|
1211
|
+
expect(paramSchema.type).toEqual('string');
|
|
1212
|
+
expect(paramSchema.pattern).toEqual('^(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)$');
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
551
1215
|
});
|