mcp-openapi-schema-explorer 1.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 +24 -0
- package/.github/dependabot.yml +13 -0
- package/.github/workflows/ci.yml +111 -0
- package/.husky/pre-commit +6 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +12 -0
- package/.releaserc.json +23 -0
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +67 -0
- package/Dockerfile +3 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/src/config.d.ts +15 -0
- package/dist/src/config.js +19 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/handlers/component-detail-handler.d.ts +14 -0
- package/dist/src/handlers/component-detail-handler.js +87 -0
- package/dist/src/handlers/component-detail-handler.js.map +1 -0
- package/dist/src/handlers/component-map-handler.d.ts +14 -0
- package/dist/src/handlers/component-map-handler.js +63 -0
- package/dist/src/handlers/component-map-handler.js.map +1 -0
- package/dist/src/handlers/handler-utils.d.ts +69 -0
- package/dist/src/handlers/handler-utils.js +180 -0
- package/dist/src/handlers/handler-utils.js.map +1 -0
- package/dist/src/handlers/operation-handler.d.ts +14 -0
- package/dist/src/handlers/operation-handler.js +86 -0
- package/dist/src/handlers/operation-handler.js.map +1 -0
- package/dist/src/handlers/path-item-handler.d.ts +14 -0
- package/dist/src/handlers/path-item-handler.js +66 -0
- package/dist/src/handlers/path-item-handler.js.map +1 -0
- package/dist/src/handlers/top-level-field-handler.d.ts +14 -0
- package/dist/src/handlers/top-level-field-handler.js +72 -0
- package/dist/src/handlers/top-level-field-handler.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +177 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/rendering/components.d.ts +67 -0
- package/dist/src/rendering/components.js +177 -0
- package/dist/src/rendering/components.js.map +1 -0
- package/dist/src/rendering/document.d.ts +36 -0
- package/dist/src/rendering/document.js +147 -0
- package/dist/src/rendering/document.js.map +1 -0
- package/dist/src/rendering/path-item.d.ts +45 -0
- package/dist/src/rendering/path-item.js +141 -0
- package/dist/src/rendering/path-item.js.map +1 -0
- package/dist/src/rendering/paths.d.ts +26 -0
- package/dist/src/rendering/paths.js +78 -0
- package/dist/src/rendering/paths.js.map +1 -0
- package/dist/src/rendering/types.d.ts +50 -0
- package/dist/src/rendering/types.js +12 -0
- package/dist/src/rendering/types.js.map +1 -0
- package/dist/src/rendering/utils.d.ts +31 -0
- package/dist/src/rendering/utils.js +79 -0
- package/dist/src/rendering/utils.js.map +1 -0
- package/dist/src/services/formatters.d.ts +36 -0
- package/dist/src/services/formatters.js +52 -0
- package/dist/src/services/formatters.js.map +1 -0
- package/dist/src/services/reference-transform.d.ts +27 -0
- package/dist/src/services/reference-transform.js +75 -0
- package/dist/src/services/reference-transform.js.map +1 -0
- package/dist/src/services/spec-loader.d.ts +27 -0
- package/dist/src/services/spec-loader.js +77 -0
- package/dist/src/services/spec-loader.js.map +1 -0
- package/dist/src/types.d.ts +11 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/uri-builder.d.ts +81 -0
- package/dist/src/utils/uri-builder.js +121 -0
- package/dist/src/utils/uri-builder.js.map +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +4 -0
- package/dist/src/version.js.map +1 -0
- package/eslint.config.js +88 -0
- package/jest.config.js +32 -0
- package/justfile +66 -0
- package/memory-bank/activeContext.md +139 -0
- package/memory-bank/productContext.md +39 -0
- package/memory-bank/progress.md +141 -0
- package/memory-bank/projectbrief.md +50 -0
- package/memory-bank/systemPatterns.md +224 -0
- package/memory-bank/techContext.md +131 -0
- package/package.json +76 -0
- package/scripts/generate-version.js +49 -0
- package/src/config.ts +33 -0
- package/src/handlers/component-detail-handler.ts +121 -0
- package/src/handlers/component-map-handler.ts +92 -0
- package/src/handlers/handler-utils.ts +230 -0
- package/src/handlers/operation-handler.ts +114 -0
- package/src/handlers/path-item-handler.ts +88 -0
- package/src/handlers/top-level-field-handler.ts +92 -0
- package/src/index.ts +222 -0
- package/src/rendering/components.ts +228 -0
- package/src/rendering/document.ts +167 -0
- package/src/rendering/path-item.ts +157 -0
- package/src/rendering/paths.ts +87 -0
- package/src/rendering/types.ts +63 -0
- package/src/rendering/utils.ts +107 -0
- package/src/services/formatters.ts +71 -0
- package/src/services/reference-transform.ts +105 -0
- package/src/services/spec-loader.ts +88 -0
- package/src/types.ts +17 -0
- package/src/utils/uri-builder.ts +134 -0
- package/src/version.ts +4 -0
- package/test/__tests__/e2e/format.test.ts +224 -0
- package/test/__tests__/e2e/resources.test.ts +369 -0
- package/test/__tests__/e2e/spec-loading.test.ts +172 -0
- package/test/__tests__/unit/config.test.ts +39 -0
- package/test/__tests__/unit/handlers/component-detail-handler.test.ts +241 -0
- package/test/__tests__/unit/handlers/component-map-handler.test.ts +187 -0
- package/test/__tests__/unit/handlers/handler-utils.test.ts +255 -0
- package/test/__tests__/unit/handlers/operation-handler.test.ts +202 -0
- package/test/__tests__/unit/handlers/path-item-handler.test.ts +153 -0
- package/test/__tests__/unit/handlers/top-level-field-handler.test.ts +182 -0
- package/test/__tests__/unit/rendering/components.test.ts +269 -0
- package/test/__tests__/unit/rendering/document.test.ts +172 -0
- package/test/__tests__/unit/rendering/path-item.test.ts +197 -0
- package/test/__tests__/unit/rendering/paths.test.ts +115 -0
- package/test/__tests__/unit/services/formatters.test.ts +109 -0
- package/test/__tests__/unit/services/reference-transform.test.ts +320 -0
- package/test/__tests__/unit/services/spec-loader.test.ts +214 -0
- package/test/__tests__/unit/utils/uri-builder.test.ts +103 -0
- package/test/fixtures/complex-endpoint.json +146 -0
- package/test/fixtures/empty-api.json +8 -0
- package/test/fixtures/multi-component-types.json +55 -0
- package/test/fixtures/paths-test.json +61 -0
- package/test/fixtures/sample-api.json +68 -0
- package/test/fixtures/sample-v2-api.json +39 -0
- package/test/setup.ts +32 -0
- package/test/utils/console-helpers.ts +48 -0
- package/test/utils/mcp-test-helpers.ts +66 -0
- package/test/utils/test-types.ts +54 -0
- package/tsconfig.json +25 -0
- package/tsconfig.test.json +5 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { SpecLoaderService } from '../../../../src/services/spec-loader.js';
|
|
2
|
+
import { ReferenceTransformService } from '../../../../src/services/reference-transform.js';
|
|
3
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
4
|
+
|
|
5
|
+
// Define mock implementations first
|
|
6
|
+
const mockConvertUrlImplementation = jest.fn();
|
|
7
|
+
const mockConvertFileImplementation = jest.fn();
|
|
8
|
+
|
|
9
|
+
// Mock the module, referencing the defined implementations
|
|
10
|
+
// IMPORTANT: The factory function for jest.mock runs BEFORE top-level variable assignments in the module scope.
|
|
11
|
+
// We need to access the mocks indirectly.
|
|
12
|
+
interface Swagger2OpenapiResult {
|
|
13
|
+
openapi: OpenAPIV3.Document;
|
|
14
|
+
options: unknown; // Use unknown for options as we don't have precise types here
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
jest.mock('swagger2openapi', () => {
|
|
18
|
+
// Return an object where the properties are functions that call our mocks
|
|
19
|
+
return {
|
|
20
|
+
convertUrl: (url: string, options: unknown): Promise<Swagger2OpenapiResult> =>
|
|
21
|
+
mockConvertUrlImplementation(url, options) as Promise<Swagger2OpenapiResult>, // Cast return type
|
|
22
|
+
convertFile: (filename: string, options: unknown): Promise<Swagger2OpenapiResult> =>
|
|
23
|
+
mockConvertFileImplementation(filename, options) as Promise<Swagger2OpenapiResult>, // Cast return type
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('SpecLoaderService', () => {
|
|
28
|
+
const mockV3Spec: OpenAPIV3.Document = {
|
|
29
|
+
openapi: '3.0.0',
|
|
30
|
+
info: {
|
|
31
|
+
title: 'Test V3 API',
|
|
32
|
+
version: '1.0.0',
|
|
33
|
+
},
|
|
34
|
+
paths: {},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Simulate the structure returned by swagger2openapi
|
|
38
|
+
const mockS2OResult = {
|
|
39
|
+
openapi: mockV3Spec,
|
|
40
|
+
options: {}, // Add other properties if needed by tests
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
let referenceTransform: ReferenceTransformService;
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
// Reset the mock implementations
|
|
47
|
+
mockConvertUrlImplementation.mockReset();
|
|
48
|
+
mockConvertFileImplementation.mockReset();
|
|
49
|
+
referenceTransform = new ReferenceTransformService();
|
|
50
|
+
// Mock the transformDocument method for simplicity in these tests
|
|
51
|
+
jest.spyOn(referenceTransform, 'transformDocument').mockImplementation(spec => spec);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('loadSpec', () => {
|
|
55
|
+
it('loads local v3 spec using convertFile', async () => {
|
|
56
|
+
mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
|
|
57
|
+
const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
|
|
58
|
+
const spec = await loader.loadSpec();
|
|
59
|
+
|
|
60
|
+
expect(mockConvertFileImplementation).toHaveBeenCalledWith(
|
|
61
|
+
'/path/to/spec.json',
|
|
62
|
+
expect.any(Object)
|
|
63
|
+
);
|
|
64
|
+
expect(mockConvertUrlImplementation).not.toHaveBeenCalled();
|
|
65
|
+
expect(spec).toEqual(mockV3Spec);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('loads remote v3 spec using convertUrl', async () => {
|
|
69
|
+
mockConvertUrlImplementation.mockResolvedValue(mockS2OResult);
|
|
70
|
+
const loader = new SpecLoaderService('http://example.com/spec.json', referenceTransform);
|
|
71
|
+
const spec = await loader.loadSpec();
|
|
72
|
+
|
|
73
|
+
expect(mockConvertUrlImplementation).toHaveBeenCalledWith(
|
|
74
|
+
'http://example.com/spec.json',
|
|
75
|
+
expect.any(Object)
|
|
76
|
+
);
|
|
77
|
+
expect(mockConvertFileImplementation).not.toHaveBeenCalled();
|
|
78
|
+
expect(spec).toEqual(mockV3Spec);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('loads and converts local v2 spec using convertFile', async () => {
|
|
82
|
+
// Assume convertFile handles v2 internally and returns v3
|
|
83
|
+
mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
|
|
84
|
+
const loader = new SpecLoaderService('/path/to/v2spec.json', referenceTransform);
|
|
85
|
+
const spec = await loader.loadSpec();
|
|
86
|
+
|
|
87
|
+
expect(mockConvertFileImplementation).toHaveBeenCalledWith(
|
|
88
|
+
'/path/to/v2spec.json',
|
|
89
|
+
expect.any(Object)
|
|
90
|
+
);
|
|
91
|
+
expect(mockConvertUrlImplementation).not.toHaveBeenCalled();
|
|
92
|
+
expect(spec).toEqual(mockV3Spec); // Should be the converted v3 spec
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('loads and converts remote v2 spec using convertUrl', async () => {
|
|
96
|
+
// Assume convertUrl handles v2 internally and returns v3
|
|
97
|
+
mockConvertUrlImplementation.mockResolvedValue(mockS2OResult);
|
|
98
|
+
const loader = new SpecLoaderService('https://example.com/v2spec.yaml', referenceTransform);
|
|
99
|
+
const spec = await loader.loadSpec();
|
|
100
|
+
|
|
101
|
+
expect(mockConvertUrlImplementation).toHaveBeenCalledWith(
|
|
102
|
+
'https://example.com/v2spec.yaml',
|
|
103
|
+
expect.any(Object)
|
|
104
|
+
);
|
|
105
|
+
expect(mockConvertFileImplementation).not.toHaveBeenCalled();
|
|
106
|
+
expect(spec).toEqual(mockV3Spec); // Should be the converted v3 spec
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('throws error if convertFile fails', async () => {
|
|
110
|
+
const loadError = new Error('File not found');
|
|
111
|
+
mockConvertFileImplementation.mockRejectedValue(loadError);
|
|
112
|
+
const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
|
|
113
|
+
|
|
114
|
+
await expect(loader.loadSpec()).rejects.toThrow(
|
|
115
|
+
'Failed to load/convert OpenAPI spec from /path/to/spec.json: File not found'
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('throws error if convertUrl fails', async () => {
|
|
120
|
+
const loadError = new Error('Network error');
|
|
121
|
+
mockConvertUrlImplementation.mockRejectedValue(loadError);
|
|
122
|
+
const loader = new SpecLoaderService('http://example.com/spec.json', referenceTransform);
|
|
123
|
+
|
|
124
|
+
await expect(loader.loadSpec()).rejects.toThrow(
|
|
125
|
+
'Failed to load/convert OpenAPI spec from http://example.com/spec.json: Network error'
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('throws error if result object is invalid', async () => {
|
|
130
|
+
mockConvertFileImplementation.mockResolvedValue({ options: {} }); // Missing openapi property
|
|
131
|
+
const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
|
|
132
|
+
|
|
133
|
+
await expect(loader.loadSpec()).rejects.toThrow(
|
|
134
|
+
'Failed to load/convert OpenAPI spec from /path/to/spec.json: Conversion or parsing failed to produce an OpenAPI document.'
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('getSpec', () => {
|
|
140
|
+
it('returns loaded spec after loadSpec called', async () => {
|
|
141
|
+
mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
|
|
142
|
+
const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
|
|
143
|
+
await loader.loadSpec(); // Load first
|
|
144
|
+
const spec = await loader.getSpec();
|
|
145
|
+
|
|
146
|
+
expect(spec).toEqual(mockV3Spec);
|
|
147
|
+
// Ensure loadSpec was only called once implicitly by the first await
|
|
148
|
+
expect(mockConvertFileImplementation).toHaveBeenCalledTimes(1);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('loads spec via convertFile if not already loaded', async () => {
|
|
152
|
+
mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
|
|
153
|
+
const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
|
|
154
|
+
const spec = await loader.getSpec(); // Should trigger loadSpec
|
|
155
|
+
|
|
156
|
+
expect(mockConvertFileImplementation).toHaveBeenCalledWith(
|
|
157
|
+
'/path/to/spec.json',
|
|
158
|
+
expect.any(Object)
|
|
159
|
+
);
|
|
160
|
+
expect(spec).toEqual(mockV3Spec);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('loads spec via convertUrl if not already loaded', async () => {
|
|
164
|
+
mockConvertUrlImplementation.mockResolvedValue(mockS2OResult);
|
|
165
|
+
const loader = new SpecLoaderService('http://example.com/spec.json', referenceTransform);
|
|
166
|
+
const spec = await loader.getSpec(); // Should trigger loadSpec
|
|
167
|
+
|
|
168
|
+
expect(mockConvertUrlImplementation).toHaveBeenCalledWith(
|
|
169
|
+
'http://example.com/spec.json',
|
|
170
|
+
expect.any(Object)
|
|
171
|
+
);
|
|
172
|
+
expect(spec).toEqual(mockV3Spec);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('getTransformedSpec', () => {
|
|
177
|
+
// Mock the transformer to return a distinctly modified object
|
|
178
|
+
const mockTransformedSpec = {
|
|
179
|
+
...mockV3Spec,
|
|
180
|
+
info: { ...mockV3Spec.info, title: 'Transformed API' },
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
beforeEach(() => {
|
|
184
|
+
jest
|
|
185
|
+
.spyOn(referenceTransform, 'transformDocument')
|
|
186
|
+
.mockImplementation(() => mockTransformedSpec);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('returns transformed spec after loading', async () => {
|
|
190
|
+
mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
|
|
191
|
+
const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
|
|
192
|
+
const spec = await loader.getTransformedSpec({ resourceType: 'endpoint', format: 'openapi' }); // Should load then transform
|
|
193
|
+
|
|
194
|
+
expect(mockConvertFileImplementation).toHaveBeenCalledTimes(1); // Ensure loading happened
|
|
195
|
+
const transformSpy = jest.spyOn(referenceTransform, 'transformDocument');
|
|
196
|
+
expect(transformSpy).toHaveBeenCalledWith(
|
|
197
|
+
mockV3Spec,
|
|
198
|
+
expect.objectContaining({ resourceType: 'endpoint', format: 'openapi' })
|
|
199
|
+
);
|
|
200
|
+
expect(spec).toEqual(mockTransformedSpec);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('loads spec if not loaded before transforming', async () => {
|
|
204
|
+
mockConvertFileImplementation.mockResolvedValue(mockS2OResult);
|
|
205
|
+
const loader = new SpecLoaderService('/path/to/spec.json', referenceTransform);
|
|
206
|
+
await loader.getTransformedSpec({ resourceType: 'endpoint', format: 'openapi' }); // Trigger load
|
|
207
|
+
|
|
208
|
+
expect(mockConvertFileImplementation).toHaveBeenCalledWith(
|
|
209
|
+
'/path/to/spec.json',
|
|
210
|
+
expect.any(Object)
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildComponentDetailUri,
|
|
3
|
+
buildComponentMapUri,
|
|
4
|
+
buildOperationUri,
|
|
5
|
+
buildPathItemUri,
|
|
6
|
+
buildTopLevelFieldUri,
|
|
7
|
+
buildComponentDetailUriSuffix,
|
|
8
|
+
buildComponentMapUriSuffix,
|
|
9
|
+
buildOperationUriSuffix,
|
|
10
|
+
buildPathItemUriSuffix,
|
|
11
|
+
buildTopLevelFieldUriSuffix,
|
|
12
|
+
} from '../../../../src/utils/uri-builder';
|
|
13
|
+
|
|
14
|
+
describe('URI Builder Utilities', () => {
|
|
15
|
+
// --- Full URI Builders ---
|
|
16
|
+
|
|
17
|
+
test('buildComponentDetailUri builds correct URI', () => {
|
|
18
|
+
expect(buildComponentDetailUri('schemas', 'MySchema')).toBe(
|
|
19
|
+
'openapi://components/schemas/MySchema'
|
|
20
|
+
);
|
|
21
|
+
expect(buildComponentDetailUri('responses', 'NotFound')).toBe(
|
|
22
|
+
'openapi://components/responses/NotFound'
|
|
23
|
+
);
|
|
24
|
+
// Test with characters that might need encoding if rules change (but currently don't)
|
|
25
|
+
expect(buildComponentDetailUri('parameters', 'user-id')).toBe(
|
|
26
|
+
'openapi://components/parameters/user-id'
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('buildComponentMapUri builds correct URI', () => {
|
|
31
|
+
expect(buildComponentMapUri('schemas')).toBe('openapi://components/schemas');
|
|
32
|
+
expect(buildComponentMapUri('parameters')).toBe('openapi://components/parameters');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('buildOperationUri builds correct URI and encodes path (no leading slash)', () => {
|
|
36
|
+
expect(buildOperationUri('/users', 'get')).toBe('openapi://paths/users/get'); // No leading slash encoded
|
|
37
|
+
expect(buildOperationUri('/users/{userId}', 'post')).toBe(
|
|
38
|
+
'openapi://paths/users%2F%7BuserId%7D/post' // Path encoded, no leading %2F
|
|
39
|
+
);
|
|
40
|
+
expect(buildOperationUri('/pets/{petId}/uploadImage', 'post')).toBe(
|
|
41
|
+
'openapi://paths/pets%2F%7BpetId%7D%2FuploadImage/post' // Path encoded, no leading %2F
|
|
42
|
+
);
|
|
43
|
+
expect(buildOperationUri('users', 'get')).toBe('openapi://paths/users/get'); // Handles no leading slash input
|
|
44
|
+
expect(buildOperationUri('users/{userId}', 'post')).toBe(
|
|
45
|
+
'openapi://paths/users%2F%7BuserId%7D/post' // Handles no leading slash input
|
|
46
|
+
);
|
|
47
|
+
expect(buildOperationUri('/users', 'GET')).toBe('openapi://paths/users/get'); // Method lowercased
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('buildPathItemUri builds correct URI and encodes path (no leading slash)', () => {
|
|
51
|
+
expect(buildPathItemUri('/users')).toBe('openapi://paths/users'); // No leading slash encoded
|
|
52
|
+
expect(buildPathItemUri('/users/{userId}')).toBe('openapi://paths/users%2F%7BuserId%7D'); // Path encoded, no leading %2F
|
|
53
|
+
expect(buildPathItemUri('/pets/{petId}/uploadImage')).toBe(
|
|
54
|
+
'openapi://paths/pets%2F%7BpetId%7D%2FuploadImage' // Path encoded, no leading %2F
|
|
55
|
+
);
|
|
56
|
+
expect(buildPathItemUri('users')).toBe('openapi://paths/users'); // Handles no leading slash input
|
|
57
|
+
expect(buildPathItemUri('users/{userId}')).toBe('openapi://paths/users%2F%7BuserId%7D'); // Handles no leading slash input
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('buildTopLevelFieldUri builds correct URI', () => {
|
|
61
|
+
expect(buildTopLevelFieldUri('info')).toBe('openapi://info');
|
|
62
|
+
expect(buildTopLevelFieldUri('paths')).toBe('openapi://paths');
|
|
63
|
+
expect(buildTopLevelFieldUri('components')).toBe('openapi://components');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// --- URI Suffix Builders ---
|
|
67
|
+
|
|
68
|
+
test('buildComponentDetailUriSuffix builds correct suffix', () => {
|
|
69
|
+
expect(buildComponentDetailUriSuffix('schemas', 'MySchema')).toBe(
|
|
70
|
+
'components/schemas/MySchema'
|
|
71
|
+
);
|
|
72
|
+
expect(buildComponentDetailUriSuffix('responses', 'NotFound')).toBe(
|
|
73
|
+
'components/responses/NotFound'
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('buildComponentMapUriSuffix builds correct suffix', () => {
|
|
78
|
+
expect(buildComponentMapUriSuffix('schemas')).toBe('components/schemas');
|
|
79
|
+
expect(buildComponentMapUriSuffix('parameters')).toBe('components/parameters');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('buildOperationUriSuffix builds correct suffix and encodes path (no leading slash)', () => {
|
|
83
|
+
expect(buildOperationUriSuffix('/users', 'get')).toBe('paths/users/get'); // No leading slash encoded
|
|
84
|
+
expect(buildOperationUriSuffix('/users/{userId}', 'post')).toBe(
|
|
85
|
+
'paths/users%2F%7BuserId%7D/post' // Path encoded, no leading %2F
|
|
86
|
+
);
|
|
87
|
+
expect(buildOperationUriSuffix('users/{userId}', 'post')).toBe(
|
|
88
|
+
'paths/users%2F%7BuserId%7D/post' // Handles no leading slash input
|
|
89
|
+
);
|
|
90
|
+
expect(buildOperationUriSuffix('/users', 'GET')).toBe('paths/users/get'); // Method lowercased
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('buildPathItemUriSuffix builds correct suffix and encodes path (no leading slash)', () => {
|
|
94
|
+
expect(buildPathItemUriSuffix('/users')).toBe('paths/users'); // No leading slash encoded
|
|
95
|
+
expect(buildPathItemUriSuffix('/users/{userId}')).toBe('paths/users%2F%7BuserId%7D'); // Path encoded, no leading %2F
|
|
96
|
+
expect(buildPathItemUriSuffix('users/{userId}')).toBe('paths/users%2F%7BuserId%7D'); // Handles no leading slash input
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('buildTopLevelFieldUriSuffix builds correct suffix', () => {
|
|
100
|
+
expect(buildTopLevelFieldUriSuffix('info')).toBe('info');
|
|
101
|
+
expect(buildTopLevelFieldUriSuffix('paths')).toBe('paths');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.3",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Complex Endpoint Test API",
|
|
5
|
+
"version": "1.0.0"
|
|
6
|
+
},
|
|
7
|
+
"paths": {
|
|
8
|
+
"/api/v1/organizations/{orgId}/projects/{projectId}/tasks": {
|
|
9
|
+
"get": {
|
|
10
|
+
"operationId": "getProjectTasks",
|
|
11
|
+
"summary": "Get Tasks",
|
|
12
|
+
"description": "Retrieve a list of tasks for a specific project.",
|
|
13
|
+
"parameters": [
|
|
14
|
+
{
|
|
15
|
+
"name": "orgId",
|
|
16
|
+
"in": "path",
|
|
17
|
+
"required": true,
|
|
18
|
+
"schema": { "type": "string" }
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "projectId",
|
|
22
|
+
"in": "path",
|
|
23
|
+
"required": true,
|
|
24
|
+
"schema": { "type": "string" }
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "status",
|
|
28
|
+
"in": "query",
|
|
29
|
+
"schema": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"enum": ["active", "completed"]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "sort",
|
|
36
|
+
"in": "query",
|
|
37
|
+
"schema": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"enum": ["created", "updated", "priority"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"responses": {
|
|
44
|
+
"200": {
|
|
45
|
+
"description": "List of tasks",
|
|
46
|
+
"content": {
|
|
47
|
+
"application/json": {
|
|
48
|
+
"schema": {
|
|
49
|
+
"$ref": "#/components/schemas/TaskList"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"post": {
|
|
57
|
+
"operationId": "createProjectTask",
|
|
58
|
+
"summary": "Create Task",
|
|
59
|
+
"description": "Create a new task within a project.",
|
|
60
|
+
"requestBody": {
|
|
61
|
+
"required": true,
|
|
62
|
+
"content": {
|
|
63
|
+
"application/json": {
|
|
64
|
+
"schema": {
|
|
65
|
+
"$ref": "#/components/schemas/CreateTaskRequest"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"responses": {
|
|
71
|
+
"201": {
|
|
72
|
+
"description": "Task created",
|
|
73
|
+
"content": {
|
|
74
|
+
"application/json": {
|
|
75
|
+
"schema": {
|
|
76
|
+
"$ref": "#/components/schemas/Task"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"components": {
|
|
86
|
+
"schemas": {
|
|
87
|
+
"Task": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"required": ["id", "title", "status"],
|
|
90
|
+
"properties": {
|
|
91
|
+
"id": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"format": "uuid"
|
|
94
|
+
},
|
|
95
|
+
"title": {
|
|
96
|
+
"type": "string"
|
|
97
|
+
},
|
|
98
|
+
"status": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"enum": ["active", "completed"]
|
|
101
|
+
},
|
|
102
|
+
"priority": {
|
|
103
|
+
"type": "integer",
|
|
104
|
+
"minimum": 1,
|
|
105
|
+
"maximum": 5
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"TaskList": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"required": ["items"],
|
|
112
|
+
"properties": {
|
|
113
|
+
"items": {
|
|
114
|
+
"type": "array",
|
|
115
|
+
"items": {
|
|
116
|
+
"$ref": "#/components/schemas/Task"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"totalCount": {
|
|
120
|
+
"type": "integer"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"CreateTaskRequest": {
|
|
125
|
+
"type": "object",
|
|
126
|
+
"required": ["title"],
|
|
127
|
+
"properties": {
|
|
128
|
+
"title": {
|
|
129
|
+
"type": "string"
|
|
130
|
+
},
|
|
131
|
+
"status": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"enum": ["active", "completed"],
|
|
134
|
+
"default": "active"
|
|
135
|
+
},
|
|
136
|
+
"priority": {
|
|
137
|
+
"type": "integer",
|
|
138
|
+
"minimum": 1,
|
|
139
|
+
"maximum": 5,
|
|
140
|
+
"default": 3
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.0",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Multi Component Type Test API",
|
|
5
|
+
"version": "1.0.0"
|
|
6
|
+
},
|
|
7
|
+
"paths": {
|
|
8
|
+
"/ping": {
|
|
9
|
+
"get": {
|
|
10
|
+
"summary": "Ping",
|
|
11
|
+
"operationId": "ping",
|
|
12
|
+
"responses": {
|
|
13
|
+
"200": {
|
|
14
|
+
"description": "OK",
|
|
15
|
+
"content": {
|
|
16
|
+
"application/json": {
|
|
17
|
+
"schema": {
|
|
18
|
+
"$ref": "#/components/schemas/Pong"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"parameters": [
|
|
25
|
+
{
|
|
26
|
+
"$ref": "#/components/parameters/TraceId"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"components": {
|
|
33
|
+
"schemas": {
|
|
34
|
+
"Pong": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"message": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"parameters": {
|
|
44
|
+
"TraceId": {
|
|
45
|
+
"name": "X-Trace-ID",
|
|
46
|
+
"in": "header",
|
|
47
|
+
"required": false,
|
|
48
|
+
"schema": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"format": "uuid"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.3",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Path Testing API",
|
|
5
|
+
"version": "1.0.0"
|
|
6
|
+
},
|
|
7
|
+
"paths": {
|
|
8
|
+
"/project/tasks/{taskId}": {
|
|
9
|
+
"get": {
|
|
10
|
+
"summary": "Get task details",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"name": "taskId",
|
|
14
|
+
"in": "path",
|
|
15
|
+
"required": true,
|
|
16
|
+
"schema": { "type": "string" }
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"responses": {
|
|
20
|
+
"200": {
|
|
21
|
+
"description": "Task details"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"/article/{articleId}/comment/{commentId}": {
|
|
27
|
+
"get": {
|
|
28
|
+
"summary": "Get comment on article",
|
|
29
|
+
"parameters": [
|
|
30
|
+
{
|
|
31
|
+
"name": "articleId",
|
|
32
|
+
"in": "path",
|
|
33
|
+
"required": true,
|
|
34
|
+
"schema": { "type": "string" }
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "commentId",
|
|
38
|
+
"in": "path",
|
|
39
|
+
"required": true,
|
|
40
|
+
"schema": { "type": "string" }
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"responses": {
|
|
44
|
+
"200": {
|
|
45
|
+
"description": "Comment details"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"/sub/sub/sub/sub/folded/entrypoint": {
|
|
51
|
+
"post": {
|
|
52
|
+
"summary": "Deeply nested endpoint",
|
|
53
|
+
"responses": {
|
|
54
|
+
"201": {
|
|
55
|
+
"description": "Created"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.3",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Sample API",
|
|
5
|
+
"version": "1.0.0"
|
|
6
|
+
},
|
|
7
|
+
"paths": {
|
|
8
|
+
"/users": {
|
|
9
|
+
"get": {
|
|
10
|
+
"summary": "List users",
|
|
11
|
+
"operationId": "listUsers",
|
|
12
|
+
"responses": {
|
|
13
|
+
"200": {
|
|
14
|
+
"description": "List of users",
|
|
15
|
+
"content": {
|
|
16
|
+
"application/json": {
|
|
17
|
+
"schema": {
|
|
18
|
+
"$ref": "#/components/schemas/UserList"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"components": {
|
|
28
|
+
"schemas": {
|
|
29
|
+
"User": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"required": ["id", "email"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"id": {
|
|
34
|
+
"type": "integer",
|
|
35
|
+
"format": "int64"
|
|
36
|
+
},
|
|
37
|
+
"email": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"format": "email"
|
|
40
|
+
},
|
|
41
|
+
"name": {
|
|
42
|
+
"type": "string"
|
|
43
|
+
},
|
|
44
|
+
"status": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"enum": ["active", "inactive"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"UserList": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"required": ["users"],
|
|
53
|
+
"properties": {
|
|
54
|
+
"users": {
|
|
55
|
+
"type": "array",
|
|
56
|
+
"items": {
|
|
57
|
+
"$ref": "#/components/schemas/User"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"total": {
|
|
61
|
+
"type": "integer",
|
|
62
|
+
"format": "int32"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|