@mieubrisse/notion-mcp-server 2.0.0
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/.devcontainer/devcontainer.json +4 -0
- package/.dockerignore +3 -0
- package/.github/pull_request_template.md +8 -0
- package/.github/workflows/ci.yml +42 -0
- package/Dockerfile +36 -0
- package/LICENSE +7 -0
- package/README.md +412 -0
- package/docker-compose.yml +6 -0
- package/docs/images/connections.png +0 -0
- package/docs/images/integration-access.png +0 -0
- package/docs/images/integrations-capabilities.png +0 -0
- package/docs/images/integrations-creation.png +0 -0
- package/docs/images/page-access-edit.png +0 -0
- package/package.json +63 -0
- package/scripts/build-cli.js +30 -0
- package/scripts/notion-openapi.json +2238 -0
- package/scripts/start-server.ts +243 -0
- package/src/init-server.ts +50 -0
- package/src/openapi-mcp-server/README.md +3 -0
- package/src/openapi-mcp-server/auth/index.ts +2 -0
- package/src/openapi-mcp-server/auth/template.ts +24 -0
- package/src/openapi-mcp-server/auth/types.ts +26 -0
- package/src/openapi-mcp-server/client/__tests__/http-client-upload.test.ts +205 -0
- package/src/openapi-mcp-server/client/__tests__/http-client.integration.test.ts +282 -0
- package/src/openapi-mcp-server/client/__tests__/http-client.test.ts +537 -0
- package/src/openapi-mcp-server/client/http-client.ts +198 -0
- package/src/openapi-mcp-server/client/polyfill-headers.ts +42 -0
- package/src/openapi-mcp-server/index.ts +3 -0
- package/src/openapi-mcp-server/mcp/__tests__/proxy.test.ts +479 -0
- package/src/openapi-mcp-server/mcp/proxy.ts +250 -0
- package/src/openapi-mcp-server/openapi/__tests__/file-upload.test.ts +100 -0
- package/src/openapi-mcp-server/openapi/__tests__/parser-multipart.test.ts +602 -0
- package/src/openapi-mcp-server/openapi/__tests__/parser.test.ts +1448 -0
- package/src/openapi-mcp-server/openapi/file-upload.ts +40 -0
- package/src/openapi-mcp-server/openapi/parser.ts +529 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { HttpClient, HttpClientError } from '../http-client'
|
|
2
|
+
import { OpenAPIV3 } from 'openapi-types'
|
|
3
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
4
|
+
import OpenAPIClientAxios from 'openapi-client-axios'
|
|
5
|
+
|
|
6
|
+
// Mock the OpenAPIClientAxios initialization
|
|
7
|
+
vi.mock('openapi-client-axios', () => {
|
|
8
|
+
const mockApi = {
|
|
9
|
+
getPet: vi.fn(),
|
|
10
|
+
testOperation: vi.fn(),
|
|
11
|
+
complexOperation: vi.fn(),
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
default: vi.fn().mockImplementation(() => ({
|
|
15
|
+
init: vi.fn().mockResolvedValue(mockApi),
|
|
16
|
+
})),
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('HttpClient', () => {
|
|
21
|
+
let client: HttpClient
|
|
22
|
+
let mockApi: any
|
|
23
|
+
|
|
24
|
+
const sampleSpec: OpenAPIV3.Document = {
|
|
25
|
+
openapi: '3.0.0',
|
|
26
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
27
|
+
paths: {
|
|
28
|
+
'/pets/{petId}': {
|
|
29
|
+
get: {
|
|
30
|
+
operationId: 'getPet',
|
|
31
|
+
parameters: [
|
|
32
|
+
{
|
|
33
|
+
name: 'petId',
|
|
34
|
+
in: 'path',
|
|
35
|
+
required: true,
|
|
36
|
+
schema: { type: 'integer' },
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
responses: {
|
|
40
|
+
'200': {
|
|
41
|
+
description: 'OK',
|
|
42
|
+
content: {
|
|
43
|
+
'application/json': {
|
|
44
|
+
schema: { type: 'object' },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const getPetOperation = sampleSpec.paths['/pets/{petId}']?.get as OpenAPIV3.OperationObject & { method: string; path: string }
|
|
55
|
+
if (!getPetOperation) {
|
|
56
|
+
throw new Error('Test setup error: getPet operation not found in sample spec')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
beforeEach(async () => {
|
|
60
|
+
// Create a new instance of HttpClient
|
|
61
|
+
client = new HttpClient({ baseUrl: 'https://api.example.com' }, sampleSpec)
|
|
62
|
+
// Await the initialization to ensure mockApi is set correctly
|
|
63
|
+
mockApi = await client['api']
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
vi.clearAllMocks()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('successfully executes an operation', async () => {
|
|
71
|
+
const mockResponse = {
|
|
72
|
+
data: { id: 1, name: 'Fluffy' },
|
|
73
|
+
status: 200,
|
|
74
|
+
headers: {
|
|
75
|
+
'content-type': 'application/json',
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
mockApi.getPet.mockResolvedValueOnce(mockResponse)
|
|
80
|
+
|
|
81
|
+
const response = await client.executeOperation(getPetOperation, { petId: 1 })
|
|
82
|
+
|
|
83
|
+
// Note GET requests should have a null Content-Type header!
|
|
84
|
+
expect(mockApi.getPet).toHaveBeenCalledWith({ petId: 1 }, undefined, { headers: { 'Content-Type': null } })
|
|
85
|
+
expect(response.data).toEqual(mockResponse.data)
|
|
86
|
+
expect(response.status).toBe(200)
|
|
87
|
+
expect(response.headers).toBeInstanceOf(Headers)
|
|
88
|
+
expect(response.headers.get('content-type')).toBe('application/json')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('throws error when operation ID is missing', async () => {
|
|
92
|
+
const operationWithoutId: OpenAPIV3.OperationObject & { method: string; path: string } = {
|
|
93
|
+
method: 'GET',
|
|
94
|
+
path: '/unknown',
|
|
95
|
+
responses: {
|
|
96
|
+
'200': {
|
|
97
|
+
description: 'OK',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await expect(client.executeOperation(operationWithoutId)).rejects.toThrow('Operation ID is required')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('throws error when operation is not found', async () => {
|
|
106
|
+
const operation: OpenAPIV3.OperationObject & { method: string; path: string } = {
|
|
107
|
+
method: 'GET',
|
|
108
|
+
path: '/unknown',
|
|
109
|
+
operationId: 'nonexistentOperation',
|
|
110
|
+
responses: {
|
|
111
|
+
'200': {
|
|
112
|
+
description: 'OK',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await expect(client.executeOperation(operation)).rejects.toThrow('Operation nonexistentOperation not found')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('handles API errors correctly', async () => {
|
|
121
|
+
const error = {
|
|
122
|
+
response: {
|
|
123
|
+
status: 404,
|
|
124
|
+
statusText: 'Not Found',
|
|
125
|
+
data: {
|
|
126
|
+
code: 'RESOURCE_NOT_FOUND',
|
|
127
|
+
message: 'Pet not found',
|
|
128
|
+
petId: 999,
|
|
129
|
+
},
|
|
130
|
+
headers: {
|
|
131
|
+
'content-type': 'application/json',
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
mockApi.getPet.mockRejectedValueOnce(error)
|
|
136
|
+
|
|
137
|
+
await expect(client.executeOperation(getPetOperation, { petId: 999 })).rejects.toMatchObject({
|
|
138
|
+
status: 404,
|
|
139
|
+
message: '404 Not Found',
|
|
140
|
+
data: {
|
|
141
|
+
code: 'RESOURCE_NOT_FOUND',
|
|
142
|
+
message: 'Pet not found',
|
|
143
|
+
petId: 999,
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('handles validation errors (400) correctly', async () => {
|
|
149
|
+
const error = {
|
|
150
|
+
response: {
|
|
151
|
+
status: 400,
|
|
152
|
+
statusText: 'Bad Request',
|
|
153
|
+
data: {
|
|
154
|
+
code: 'VALIDATION_ERROR',
|
|
155
|
+
message: 'Invalid input data',
|
|
156
|
+
errors: [
|
|
157
|
+
{
|
|
158
|
+
field: 'age',
|
|
159
|
+
message: 'Age must be a positive number',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
field: 'name',
|
|
163
|
+
message: 'Name is required',
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
headers: {
|
|
168
|
+
'content-type': 'application/json',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
mockApi.getPet.mockRejectedValueOnce(error)
|
|
173
|
+
|
|
174
|
+
await expect(client.executeOperation(getPetOperation, { petId: 1 })).rejects.toMatchObject({
|
|
175
|
+
status: 400,
|
|
176
|
+
message: '400 Bad Request',
|
|
177
|
+
data: {
|
|
178
|
+
code: 'VALIDATION_ERROR',
|
|
179
|
+
message: 'Invalid input data',
|
|
180
|
+
errors: [
|
|
181
|
+
{
|
|
182
|
+
field: 'age',
|
|
183
|
+
message: 'Age must be a positive number',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
field: 'name',
|
|
187
|
+
message: 'Name is required',
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('handles server errors (500) with HTML response', async () => {
|
|
195
|
+
const error = {
|
|
196
|
+
response: {
|
|
197
|
+
status: 500,
|
|
198
|
+
statusText: 'Internal Server Error',
|
|
199
|
+
data: '<html><body><h1>500 Internal Server Error</h1></body></html>',
|
|
200
|
+
headers: {
|
|
201
|
+
'content-type': 'text/html',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
mockApi.getPet.mockRejectedValueOnce(error)
|
|
206
|
+
|
|
207
|
+
await expect(client.executeOperation(getPetOperation, { petId: 1 })).rejects.toMatchObject({
|
|
208
|
+
status: 500,
|
|
209
|
+
message: '500 Internal Server Error',
|
|
210
|
+
data: '<html><body><h1>500 Internal Server Error</h1></body></html>',
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it('handles rate limit errors (429)', async () => {
|
|
215
|
+
const error = {
|
|
216
|
+
response: {
|
|
217
|
+
status: 429,
|
|
218
|
+
statusText: 'Too Many Requests',
|
|
219
|
+
data: {
|
|
220
|
+
code: 'RATE_LIMIT_EXCEEDED',
|
|
221
|
+
message: 'Rate limit exceeded',
|
|
222
|
+
retryAfter: 60,
|
|
223
|
+
},
|
|
224
|
+
headers: {
|
|
225
|
+
'content-type': 'application/json',
|
|
226
|
+
'retry-after': '60',
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
mockApi.getPet.mockRejectedValueOnce(error)
|
|
231
|
+
|
|
232
|
+
await expect(client.executeOperation(getPetOperation, { petId: 1 })).rejects.toMatchObject({
|
|
233
|
+
status: 429,
|
|
234
|
+
message: '429 Too Many Requests',
|
|
235
|
+
data: {
|
|
236
|
+
code: 'RATE_LIMIT_EXCEEDED',
|
|
237
|
+
message: 'Rate limit exceeded',
|
|
238
|
+
retryAfter: 60,
|
|
239
|
+
},
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('should send body parameters in request body for POST operations', async () => {
|
|
244
|
+
// Setup mock API with the new operation
|
|
245
|
+
mockApi.testOperation = vi.fn().mockResolvedValue({
|
|
246
|
+
data: {},
|
|
247
|
+
status: 200,
|
|
248
|
+
headers: {},
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const testSpec: OpenAPIV3.Document = {
|
|
252
|
+
openapi: '3.0.0',
|
|
253
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
254
|
+
paths: {
|
|
255
|
+
'/test': {
|
|
256
|
+
post: {
|
|
257
|
+
operationId: 'testOperation',
|
|
258
|
+
requestBody: {
|
|
259
|
+
content: {
|
|
260
|
+
'application/json': {
|
|
261
|
+
schema: {
|
|
262
|
+
type: 'object',
|
|
263
|
+
properties: {
|
|
264
|
+
foo: { type: 'string' },
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
responses: {
|
|
271
|
+
'200': {
|
|
272
|
+
description: 'Success response',
|
|
273
|
+
content: {
|
|
274
|
+
'application/json': {
|
|
275
|
+
schema: {
|
|
276
|
+
type: 'object',
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const postOperation = testSpec.paths['/test']?.post as OpenAPIV3.OperationObject & { method: string; path: string }
|
|
288
|
+
if (!postOperation) {
|
|
289
|
+
throw new Error('Test setup error: post operation not found')
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const client = new HttpClient({ baseUrl: 'http://test.com' }, testSpec)
|
|
293
|
+
|
|
294
|
+
await client.executeOperation(postOperation, { foo: 'bar' })
|
|
295
|
+
|
|
296
|
+
expect(mockApi.testOperation).toHaveBeenCalledWith({}, { foo: 'bar' }, { headers: { 'Content-Type': 'application/json' } })
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('should handle query, path, and body parameters correctly', async () => {
|
|
300
|
+
mockApi.complexOperation = vi.fn().mockResolvedValue({
|
|
301
|
+
data: { success: true },
|
|
302
|
+
status: 200,
|
|
303
|
+
headers: {
|
|
304
|
+
'content-type': 'application/json',
|
|
305
|
+
},
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
const complexSpec: OpenAPIV3.Document = {
|
|
309
|
+
openapi: '3.0.0',
|
|
310
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
311
|
+
paths: {
|
|
312
|
+
'/users/{userId}/posts': {
|
|
313
|
+
post: {
|
|
314
|
+
operationId: 'complexOperation',
|
|
315
|
+
parameters: [
|
|
316
|
+
{
|
|
317
|
+
name: 'userId',
|
|
318
|
+
in: 'path',
|
|
319
|
+
required: true,
|
|
320
|
+
schema: { type: 'integer' },
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'include',
|
|
324
|
+
in: 'query',
|
|
325
|
+
required: false,
|
|
326
|
+
schema: { type: 'string' },
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
requestBody: {
|
|
330
|
+
content: {
|
|
331
|
+
'application/json': {
|
|
332
|
+
schema: {
|
|
333
|
+
type: 'object',
|
|
334
|
+
properties: {
|
|
335
|
+
title: { type: 'string' },
|
|
336
|
+
content: { type: 'string' },
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
responses: {
|
|
343
|
+
'200': {
|
|
344
|
+
description: 'Success response',
|
|
345
|
+
content: {
|
|
346
|
+
'application/json': {
|
|
347
|
+
schema: {
|
|
348
|
+
type: 'object',
|
|
349
|
+
properties: {
|
|
350
|
+
success: { type: 'boolean' },
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const complexOperation = complexSpec.paths['/users/{userId}/posts']?.post as OpenAPIV3.OperationObject & {
|
|
363
|
+
method: string
|
|
364
|
+
path: string
|
|
365
|
+
}
|
|
366
|
+
if (!complexOperation) {
|
|
367
|
+
throw new Error('Test setup error: complex operation not found')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const client = new HttpClient({ baseUrl: 'http://test.com' }, complexSpec)
|
|
371
|
+
|
|
372
|
+
await client.executeOperation(complexOperation, {
|
|
373
|
+
// Path parameter
|
|
374
|
+
userId: 123,
|
|
375
|
+
// Query parameter
|
|
376
|
+
include: 'comments',
|
|
377
|
+
// Body parameters
|
|
378
|
+
title: 'Test Post',
|
|
379
|
+
content: 'Test Content',
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
expect(mockApi.complexOperation).toHaveBeenCalledWith(
|
|
383
|
+
{
|
|
384
|
+
userId: 123,
|
|
385
|
+
include: 'comments',
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
title: 'Test Post',
|
|
389
|
+
content: 'Test Content',
|
|
390
|
+
},
|
|
391
|
+
{ headers: { 'Content-Type': 'application/json' } },
|
|
392
|
+
)
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
const mockOpenApiSpec: OpenAPIV3.Document = {
|
|
396
|
+
openapi: '3.0.0',
|
|
397
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
398
|
+
paths: {
|
|
399
|
+
'/test': {
|
|
400
|
+
post: {
|
|
401
|
+
operationId: 'testOperation',
|
|
402
|
+
parameters: [
|
|
403
|
+
{
|
|
404
|
+
name: 'queryParam',
|
|
405
|
+
in: 'query',
|
|
406
|
+
schema: { type: 'string' },
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: 'pathParam',
|
|
410
|
+
in: 'path',
|
|
411
|
+
schema: { type: 'string' },
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
requestBody: {
|
|
415
|
+
content: {
|
|
416
|
+
'application/json': {
|
|
417
|
+
schema: {
|
|
418
|
+
type: 'object',
|
|
419
|
+
properties: {
|
|
420
|
+
bodyParam: { type: 'string' },
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
responses: {
|
|
427
|
+
'200': {
|
|
428
|
+
description: 'Success',
|
|
429
|
+
},
|
|
430
|
+
'400': {
|
|
431
|
+
description: 'Bad Request',
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const mockConfig = {
|
|
440
|
+
baseUrl: 'http://test-api.com',
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
beforeEach(() => {
|
|
444
|
+
vi.clearAllMocks()
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
it('should properly propagate structured error responses', async () => {
|
|
448
|
+
const errorResponse = {
|
|
449
|
+
response: {
|
|
450
|
+
data: {
|
|
451
|
+
code: 'VALIDATION_ERROR',
|
|
452
|
+
message: 'Invalid input',
|
|
453
|
+
details: ['Field x is required'],
|
|
454
|
+
},
|
|
455
|
+
status: 400,
|
|
456
|
+
statusText: 'Bad Request',
|
|
457
|
+
headers: {
|
|
458
|
+
'content-type': 'application/json',
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Mock axios instance
|
|
464
|
+
const mockAxiosInstance = {
|
|
465
|
+
testOperation: vi.fn().mockRejectedValue(errorResponse),
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Mock the OpenAPIClientAxios initialization
|
|
469
|
+
const MockOpenAPIClientAxios = vi.fn().mockImplementation(() => ({
|
|
470
|
+
init: () => Promise.resolve(mockAxiosInstance),
|
|
471
|
+
}))
|
|
472
|
+
|
|
473
|
+
vi.mocked(OpenAPIClientAxios).mockImplementation(() => MockOpenAPIClientAxios())
|
|
474
|
+
|
|
475
|
+
const client = new HttpClient(mockConfig, mockOpenApiSpec)
|
|
476
|
+
const operation = mockOpenApiSpec.paths['/test']?.post
|
|
477
|
+
if (!operation) {
|
|
478
|
+
throw new Error('Operation not found in mock spec')
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
await client.executeOperation(operation as OpenAPIV3.OperationObject & { method: string; path: string }, {})
|
|
483
|
+
// Should not reach here
|
|
484
|
+
expect(true).toBe(false)
|
|
485
|
+
} catch (error: any) {
|
|
486
|
+
expect(error.status).toBe(400)
|
|
487
|
+
expect(error.data).toEqual({
|
|
488
|
+
code: 'VALIDATION_ERROR',
|
|
489
|
+
message: 'Invalid input',
|
|
490
|
+
details: ['Field x is required'],
|
|
491
|
+
})
|
|
492
|
+
expect(error.message).toBe('400 Bad Request')
|
|
493
|
+
}
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it('should handle query, path, and body parameters correctly', async () => {
|
|
497
|
+
const mockAxiosInstance = {
|
|
498
|
+
testOperation: vi.fn().mockResolvedValue({
|
|
499
|
+
data: { success: true },
|
|
500
|
+
status: 200,
|
|
501
|
+
headers: { 'content-type': 'application/json' },
|
|
502
|
+
}),
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const MockOpenAPIClientAxios = vi.fn().mockImplementation(() => ({
|
|
506
|
+
init: () => Promise.resolve(mockAxiosInstance),
|
|
507
|
+
}))
|
|
508
|
+
|
|
509
|
+
vi.mocked(OpenAPIClientAxios).mockImplementation(() => MockOpenAPIClientAxios())
|
|
510
|
+
|
|
511
|
+
const client = new HttpClient(mockConfig, mockOpenApiSpec)
|
|
512
|
+
const operation = mockOpenApiSpec.paths['/test']?.post
|
|
513
|
+
if (!operation) {
|
|
514
|
+
throw new Error('Operation not found in mock spec')
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const response = await client.executeOperation(operation as OpenAPIV3.OperationObject & { method: string; path: string }, {
|
|
518
|
+
queryParam: 'query1',
|
|
519
|
+
pathParam: 'path1',
|
|
520
|
+
bodyParam: 'body1',
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
expect(mockAxiosInstance.testOperation).toHaveBeenCalledWith(
|
|
524
|
+
{
|
|
525
|
+
queryParam: 'query1',
|
|
526
|
+
pathParam: 'path1',
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
bodyParam: 'body1',
|
|
530
|
+
},
|
|
531
|
+
{ headers: { 'Content-Type': 'application/json' } },
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
// Additional check to ensure headers are correctly processed
|
|
535
|
+
expect(response.headers.get('content-type')).toBe('application/json')
|
|
536
|
+
})
|
|
537
|
+
})
|