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,115 @@
|
|
|
1
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
2
|
+
import { RenderablePaths } from '../../../../src/rendering/paths';
|
|
3
|
+
import { RenderContext } from '../../../../src/rendering/types';
|
|
4
|
+
import { IFormatter, JsonFormatter } from '../../../../src/services/formatters';
|
|
5
|
+
|
|
6
|
+
// Mock Formatter & Context
|
|
7
|
+
const mockFormatter: IFormatter = new JsonFormatter();
|
|
8
|
+
const mockContext: RenderContext = {
|
|
9
|
+
formatter: mockFormatter,
|
|
10
|
+
baseUri: 'openapi://',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Sample Paths Object Fixture
|
|
14
|
+
const samplePaths: OpenAPIV3.PathsObject = {
|
|
15
|
+
'/users': {
|
|
16
|
+
get: {
|
|
17
|
+
summary: 'List Users',
|
|
18
|
+
responses: { '200': { description: 'OK' } },
|
|
19
|
+
},
|
|
20
|
+
post: {
|
|
21
|
+
summary: 'Create User',
|
|
22
|
+
responses: { '201': { description: 'Created' } },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
'/users/{userId}': {
|
|
26
|
+
get: {
|
|
27
|
+
summary: 'Get User by ID',
|
|
28
|
+
responses: { '200': { description: 'OK' } },
|
|
29
|
+
},
|
|
30
|
+
delete: {
|
|
31
|
+
// No summary
|
|
32
|
+
responses: { '204': { description: 'No Content' } },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
// Removed /ping path with custom operation to avoid type errors
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const emptyPaths: OpenAPIV3.PathsObject = {};
|
|
39
|
+
|
|
40
|
+
describe('RenderablePaths', () => {
|
|
41
|
+
describe('renderList', () => {
|
|
42
|
+
it('should render a list of paths and methods correctly', () => {
|
|
43
|
+
const renderablePaths = new RenderablePaths(samplePaths);
|
|
44
|
+
const result = renderablePaths.renderList(mockContext);
|
|
45
|
+
|
|
46
|
+
expect(result).toHaveLength(1);
|
|
47
|
+
expect(result[0].uriSuffix).toBe('paths');
|
|
48
|
+
expect(result[0].renderAsList).toBe(true);
|
|
49
|
+
expect(result[0].isError).toBeUndefined();
|
|
50
|
+
|
|
51
|
+
// Define expected output lines based on the new format
|
|
52
|
+
const expectedLineUsers = 'GET POST /users'; // Methods sorted alphabetically and uppercased
|
|
53
|
+
const expectedLineUserDetail = 'DELETE GET /users/{userId}'; // Methods sorted alphabetically and uppercased
|
|
54
|
+
|
|
55
|
+
// Check essential parts instead of exact match
|
|
56
|
+
expect(result[0].data).toContain('Hint:');
|
|
57
|
+
expect(result[0].data).toContain('openapi://paths/{encoded_path}');
|
|
58
|
+
expect(result[0].data).toContain('openapi://paths/{encoded_path}/{method}');
|
|
59
|
+
expect(result[0].data).toContain(expectedLineUsers);
|
|
60
|
+
expect(result[0].data).toContain(expectedLineUserDetail);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should handle empty paths object', () => {
|
|
64
|
+
const renderablePaths = new RenderablePaths(emptyPaths);
|
|
65
|
+
const result = renderablePaths.renderList(mockContext);
|
|
66
|
+
|
|
67
|
+
expect(result).toHaveLength(1);
|
|
68
|
+
expect(result[0]).toEqual({
|
|
69
|
+
uriSuffix: 'paths',
|
|
70
|
+
data: 'No paths found in the specification.',
|
|
71
|
+
renderAsList: true,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should handle undefined paths object', () => {
|
|
76
|
+
const renderablePaths = new RenderablePaths(undefined);
|
|
77
|
+
const result = renderablePaths.renderList(mockContext);
|
|
78
|
+
|
|
79
|
+
expect(result).toHaveLength(1);
|
|
80
|
+
expect(result[0]).toEqual({
|
|
81
|
+
uriSuffix: 'paths',
|
|
82
|
+
data: 'No paths found in the specification.',
|
|
83
|
+
renderAsList: true,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('renderDetail', () => {
|
|
89
|
+
it('should delegate to renderList', () => {
|
|
90
|
+
const renderablePaths = new RenderablePaths(samplePaths);
|
|
91
|
+
const listResult = renderablePaths.renderList(mockContext);
|
|
92
|
+
const detailResult = renderablePaths.renderDetail(mockContext);
|
|
93
|
+
// Check if the output is the same as renderList
|
|
94
|
+
expect(detailResult).toEqual(listResult);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('getPathItem', () => {
|
|
99
|
+
it('should return the correct PathItemObject', () => {
|
|
100
|
+
const renderablePaths = new RenderablePaths(samplePaths);
|
|
101
|
+
expect(renderablePaths.getPathItem('/users')).toBe(samplePaths['/users']);
|
|
102
|
+
expect(renderablePaths.getPathItem('/users/{userId}')).toBe(samplePaths['/users/{userId}']);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should return undefined for non-existent path', () => {
|
|
106
|
+
const renderablePaths = new RenderablePaths(samplePaths);
|
|
107
|
+
expect(renderablePaths.getPathItem('/nonexistent')).toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should return undefined if paths object is undefined', () => {
|
|
111
|
+
const renderablePaths = new RenderablePaths(undefined);
|
|
112
|
+
expect(renderablePaths.getPathItem('/users')).toBeUndefined();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { dump as yamlDump } from 'js-yaml';
|
|
2
|
+
import {
|
|
3
|
+
JsonFormatter,
|
|
4
|
+
YamlFormatter,
|
|
5
|
+
MinifiedJsonFormatter,
|
|
6
|
+
createFormatter,
|
|
7
|
+
} from '../../../../src/services/formatters.js';
|
|
8
|
+
|
|
9
|
+
describe('Formatters', () => {
|
|
10
|
+
const testData = {
|
|
11
|
+
method: 'GET',
|
|
12
|
+
path: '/test',
|
|
13
|
+
summary: 'Test endpoint',
|
|
14
|
+
parameters: [
|
|
15
|
+
{
|
|
16
|
+
name: 'id',
|
|
17
|
+
in: 'path',
|
|
18
|
+
required: true,
|
|
19
|
+
schema: { type: 'string' },
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('JsonFormatter', () => {
|
|
25
|
+
const formatter = new JsonFormatter();
|
|
26
|
+
|
|
27
|
+
it('should format data as JSON with proper indentation', () => {
|
|
28
|
+
const result = formatter.format(testData);
|
|
29
|
+
expect(result).toBe(JSON.stringify(testData, null, 2));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return application/json mime type', () => {
|
|
33
|
+
expect(formatter.getMimeType()).toBe('application/json');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should handle empty objects', () => {
|
|
37
|
+
expect(formatter.format({})).toBe('{}');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should handle null values', () => {
|
|
41
|
+
expect(formatter.format(null)).toBe('null');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('YamlFormatter', () => {
|
|
46
|
+
const formatter = new YamlFormatter();
|
|
47
|
+
|
|
48
|
+
it('should format data as YAML', () => {
|
|
49
|
+
const result = formatter.format(testData);
|
|
50
|
+
expect(result).toBe(
|
|
51
|
+
yamlDump(testData, {
|
|
52
|
+
indent: 2,
|
|
53
|
+
lineWidth: -1,
|
|
54
|
+
noRefs: true,
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return text/yaml mime type', () => {
|
|
60
|
+
expect(formatter.getMimeType()).toBe('text/yaml');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should handle empty objects', () => {
|
|
64
|
+
expect(formatter.format({})).toBe('{}\n');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle null values', () => {
|
|
68
|
+
expect(formatter.format(null)).toBe('null\n');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('MinifiedJsonFormatter', () => {
|
|
73
|
+
const formatter = new MinifiedJsonFormatter();
|
|
74
|
+
|
|
75
|
+
it('should format data as minified JSON', () => {
|
|
76
|
+
const result = formatter.format(testData);
|
|
77
|
+
expect(result).toBe(JSON.stringify(testData));
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should return application/json mime type', () => {
|
|
81
|
+
expect(formatter.getMimeType()).toBe('application/json');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle empty objects', () => {
|
|
85
|
+
expect(formatter.format({})).toBe('{}');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should handle null values', () => {
|
|
89
|
+
expect(formatter.format(null)).toBe('null');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('createFormatter', () => {
|
|
94
|
+
it('should create JsonFormatter for json format', () => {
|
|
95
|
+
const formatter = createFormatter('json');
|
|
96
|
+
expect(formatter).toBeInstanceOf(JsonFormatter);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should create YamlFormatter for yaml format', () => {
|
|
100
|
+
const formatter = createFormatter('yaml');
|
|
101
|
+
expect(formatter).toBeInstanceOf(YamlFormatter);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should create MinifiedJsonFormatter for json-minified format', () => {
|
|
105
|
+
const formatter = createFormatter('json-minified');
|
|
106
|
+
expect(formatter).toBeInstanceOf(MinifiedJsonFormatter);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { OpenAPIV3 } from 'openapi-types';
|
|
2
|
+
import {
|
|
3
|
+
OpenAPITransformer,
|
|
4
|
+
ReferenceTransformService,
|
|
5
|
+
TransformContext,
|
|
6
|
+
} from '../../../../src/services/reference-transform';
|
|
7
|
+
|
|
8
|
+
describe('ReferenceTransformService', () => {
|
|
9
|
+
let service: ReferenceTransformService;
|
|
10
|
+
let transformer: OpenAPITransformer;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
service = new ReferenceTransformService();
|
|
14
|
+
transformer = new OpenAPITransformer();
|
|
15
|
+
service.registerTransformer('openapi', transformer);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('throws error for unknown format', () => {
|
|
19
|
+
const context: TransformContext = {
|
|
20
|
+
resourceType: 'endpoint',
|
|
21
|
+
format: 'unknown' as 'openapi' | 'asyncapi' | 'graphql',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
expect(() => service.transformDocument({}, context)).toThrow(
|
|
25
|
+
'No transformer registered for format: unknown'
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('transforms document using registered transformer', () => {
|
|
30
|
+
const context: TransformContext = {
|
|
31
|
+
resourceType: 'endpoint',
|
|
32
|
+
format: 'openapi',
|
|
33
|
+
path: '/tasks',
|
|
34
|
+
method: 'get',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const doc: OpenAPIV3.Document = {
|
|
38
|
+
openapi: '3.0.0',
|
|
39
|
+
info: {
|
|
40
|
+
title: 'Test API',
|
|
41
|
+
version: '1.0.0',
|
|
42
|
+
},
|
|
43
|
+
paths: {
|
|
44
|
+
'/tasks': {
|
|
45
|
+
get: {
|
|
46
|
+
responses: {
|
|
47
|
+
'200': {
|
|
48
|
+
description: 'Success',
|
|
49
|
+
content: {
|
|
50
|
+
'application/json': {
|
|
51
|
+
schema: {
|
|
52
|
+
$ref: '#/components/schemas/Task',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const result = service.transformDocument(doc, context);
|
|
64
|
+
const operation = result.paths?.['/tasks']?.get;
|
|
65
|
+
const response = operation?.responses?.['200'];
|
|
66
|
+
expect(response).toBeDefined();
|
|
67
|
+
expect('content' in response!).toBeTruthy();
|
|
68
|
+
const responseObj = response! as OpenAPIV3.ResponseObject;
|
|
69
|
+
expect(responseObj.content?.['application/json']?.schema).toBeDefined();
|
|
70
|
+
// Expect the new format
|
|
71
|
+
expect(responseObj.content!['application/json'].schema).toEqual({
|
|
72
|
+
$ref: 'openapi://components/schemas/Task',
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('OpenAPITransformer', () => {
|
|
78
|
+
let transformer: OpenAPITransformer;
|
|
79
|
+
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
transformer = new OpenAPITransformer();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('transforms schema references', () => {
|
|
85
|
+
const context: TransformContext = {
|
|
86
|
+
resourceType: 'endpoint',
|
|
87
|
+
format: 'openapi',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const doc: OpenAPIV3.Document = {
|
|
91
|
+
openapi: '3.0.0',
|
|
92
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
93
|
+
paths: {},
|
|
94
|
+
components: {
|
|
95
|
+
schemas: {
|
|
96
|
+
Task: {
|
|
97
|
+
$ref: '#/components/schemas/TaskId',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const result = transformer.transformRefs(doc, context);
|
|
104
|
+
// Expect the new format
|
|
105
|
+
expect(result.components?.schemas?.Task).toEqual({
|
|
106
|
+
$ref: 'openapi://components/schemas/TaskId',
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('handles nested references', () => {
|
|
111
|
+
const context: TransformContext = {
|
|
112
|
+
resourceType: 'endpoint',
|
|
113
|
+
format: 'openapi',
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const doc: OpenAPIV3.Document = {
|
|
117
|
+
openapi: '3.0.0',
|
|
118
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
119
|
+
paths: {
|
|
120
|
+
'/tasks': {
|
|
121
|
+
post: {
|
|
122
|
+
requestBody: {
|
|
123
|
+
required: true,
|
|
124
|
+
description: 'Task creation',
|
|
125
|
+
content: {
|
|
126
|
+
'application/json': {
|
|
127
|
+
schema: {
|
|
128
|
+
$ref: '#/components/schemas/Task',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
responses: {
|
|
134
|
+
'201': {
|
|
135
|
+
description: 'Created',
|
|
136
|
+
content: {
|
|
137
|
+
'application/json': {
|
|
138
|
+
schema: {
|
|
139
|
+
$ref: '#/components/schemas/Task',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const result = transformer.transformRefs(doc, context);
|
|
151
|
+
const taskPath = result.paths?.['/tasks'];
|
|
152
|
+
expect(taskPath?.post).toBeDefined();
|
|
153
|
+
const operation = taskPath!.post!;
|
|
154
|
+
expect(operation.requestBody).toBeDefined();
|
|
155
|
+
expect('content' in operation.requestBody!).toBeTruthy();
|
|
156
|
+
const requestBody = operation.requestBody! as OpenAPIV3.RequestBodyObject;
|
|
157
|
+
expect(requestBody.content?.['application/json']?.schema).toBeDefined();
|
|
158
|
+
// Expect the new format
|
|
159
|
+
expect(requestBody.content['application/json'].schema).toEqual({
|
|
160
|
+
$ref: 'openapi://components/schemas/Task',
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Also check the response reference in the same test
|
|
164
|
+
const response = operation.responses?.['201'];
|
|
165
|
+
expect(response).toBeDefined();
|
|
166
|
+
expect('content' in response).toBeTruthy();
|
|
167
|
+
const responseObj = response as OpenAPIV3.ResponseObject;
|
|
168
|
+
expect(responseObj.content?.['application/json']?.schema).toBeDefined();
|
|
169
|
+
// Expect the new format
|
|
170
|
+
expect(responseObj.content!['application/json'].schema).toEqual({
|
|
171
|
+
$ref: 'openapi://components/schemas/Task',
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('keeps external references unchanged', () => {
|
|
176
|
+
const context: TransformContext = {
|
|
177
|
+
resourceType: 'endpoint',
|
|
178
|
+
format: 'openapi',
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const doc: OpenAPIV3.Document = {
|
|
182
|
+
openapi: '3.0.0',
|
|
183
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
184
|
+
paths: {},
|
|
185
|
+
components: {
|
|
186
|
+
schemas: {
|
|
187
|
+
Task: {
|
|
188
|
+
$ref: 'https://example.com/schemas/Task',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const result = transformer.transformRefs(doc, context);
|
|
195
|
+
const task = result.components?.schemas?.Task as OpenAPIV3.ReferenceObject;
|
|
196
|
+
expect(task).toEqual({
|
|
197
|
+
$ref: 'https://example.com/schemas/Task',
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// This test is now invalid as non-schema internal refs *should* be transformed
|
|
202
|
+
// it('keeps non-schema internal references unchanged', () => { ... });
|
|
203
|
+
|
|
204
|
+
it('transforms parameter references', () => {
|
|
205
|
+
const context: TransformContext = {
|
|
206
|
+
resourceType: 'endpoint',
|
|
207
|
+
format: 'openapi',
|
|
208
|
+
};
|
|
209
|
+
const doc: OpenAPIV3.Document = {
|
|
210
|
+
openapi: '3.0.0',
|
|
211
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
212
|
+
paths: {},
|
|
213
|
+
components: {
|
|
214
|
+
parameters: {
|
|
215
|
+
UserIdParam: {
|
|
216
|
+
$ref: '#/components/parameters/UserId', // Reference another parameter
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
const result = transformer.transformRefs(doc, context);
|
|
222
|
+
expect(result.components?.parameters?.UserIdParam).toEqual({
|
|
223
|
+
$ref: 'openapi://components/parameters/UserId', // Expect transformation
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('transforms response references', () => {
|
|
228
|
+
const context: TransformContext = {
|
|
229
|
+
resourceType: 'endpoint',
|
|
230
|
+
format: 'openapi',
|
|
231
|
+
};
|
|
232
|
+
const doc: OpenAPIV3.Document = {
|
|
233
|
+
openapi: '3.0.0',
|
|
234
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
235
|
+
paths: {},
|
|
236
|
+
components: {
|
|
237
|
+
responses: {
|
|
238
|
+
GenericError: {
|
|
239
|
+
$ref: '#/components/responses/ErrorModel', // Reference another response
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
const result = transformer.transformRefs(doc, context);
|
|
245
|
+
expect(result.components?.responses?.GenericError).toEqual({
|
|
246
|
+
$ref: 'openapi://components/responses/ErrorModel', // Expect transformation
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Add tests for other component types if needed (examples, requestBodies, etc.)
|
|
251
|
+
|
|
252
|
+
it('handles arrays properly', () => {
|
|
253
|
+
const context: TransformContext = {
|
|
254
|
+
resourceType: 'endpoint',
|
|
255
|
+
format: 'openapi',
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const doc: OpenAPIV3.Document = {
|
|
259
|
+
openapi: '3.0.0',
|
|
260
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
261
|
+
paths: {},
|
|
262
|
+
components: {
|
|
263
|
+
schemas: {
|
|
264
|
+
TaskList: {
|
|
265
|
+
type: 'object',
|
|
266
|
+
properties: {
|
|
267
|
+
items: {
|
|
268
|
+
type: 'array',
|
|
269
|
+
items: {
|
|
270
|
+
$ref: '#/components/schemas/Task',
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const result = transformer.transformRefs(doc, context);
|
|
280
|
+
const schema = result.components?.schemas?.TaskList;
|
|
281
|
+
expect(schema).toBeDefined();
|
|
282
|
+
expect('properties' in schema!).toBeTruthy();
|
|
283
|
+
const schemaObject = schema! as OpenAPIV3.SchemaObject;
|
|
284
|
+
expect(schemaObject.properties?.items).toBeDefined();
|
|
285
|
+
const arraySchema = schemaObject.properties!.items as OpenAPIV3.ArraySchemaObject;
|
|
286
|
+
// Expect the new format
|
|
287
|
+
expect(arraySchema.items).toEqual({
|
|
288
|
+
$ref: 'openapi://components/schemas/Task',
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('preserves non-reference values', () => {
|
|
293
|
+
const context: TransformContext = {
|
|
294
|
+
resourceType: 'endpoint',
|
|
295
|
+
format: 'openapi',
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const doc: OpenAPIV3.Document = {
|
|
299
|
+
openapi: '3.0.0',
|
|
300
|
+
info: {
|
|
301
|
+
title: 'Test API',
|
|
302
|
+
version: '1.0.0',
|
|
303
|
+
},
|
|
304
|
+
paths: {},
|
|
305
|
+
components: {
|
|
306
|
+
schemas: {
|
|
307
|
+
Test: {
|
|
308
|
+
type: 'object',
|
|
309
|
+
properties: {
|
|
310
|
+
name: { type: 'string' },
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const result = transformer.transformRefs(doc, context);
|
|
318
|
+
expect(result).toEqual(doc);
|
|
319
|
+
});
|
|
320
|
+
});
|