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.
Files changed (133) hide show
  1. package/.devcontainer/devcontainer.json +24 -0
  2. package/.github/dependabot.yml +13 -0
  3. package/.github/workflows/ci.yml +111 -0
  4. package/.husky/pre-commit +6 -0
  5. package/.prettierignore +3 -0
  6. package/.prettierrc.json +12 -0
  7. package/.releaserc.json +23 -0
  8. package/CHANGELOG.md +32 -0
  9. package/CONTRIBUTING.md +67 -0
  10. package/Dockerfile +3 -0
  11. package/LICENSE +21 -0
  12. package/README.md +127 -0
  13. package/dist/src/config.d.ts +15 -0
  14. package/dist/src/config.js +19 -0
  15. package/dist/src/config.js.map +1 -0
  16. package/dist/src/handlers/component-detail-handler.d.ts +14 -0
  17. package/dist/src/handlers/component-detail-handler.js +87 -0
  18. package/dist/src/handlers/component-detail-handler.js.map +1 -0
  19. package/dist/src/handlers/component-map-handler.d.ts +14 -0
  20. package/dist/src/handlers/component-map-handler.js +63 -0
  21. package/dist/src/handlers/component-map-handler.js.map +1 -0
  22. package/dist/src/handlers/handler-utils.d.ts +69 -0
  23. package/dist/src/handlers/handler-utils.js +180 -0
  24. package/dist/src/handlers/handler-utils.js.map +1 -0
  25. package/dist/src/handlers/operation-handler.d.ts +14 -0
  26. package/dist/src/handlers/operation-handler.js +86 -0
  27. package/dist/src/handlers/operation-handler.js.map +1 -0
  28. package/dist/src/handlers/path-item-handler.d.ts +14 -0
  29. package/dist/src/handlers/path-item-handler.js +66 -0
  30. package/dist/src/handlers/path-item-handler.js.map +1 -0
  31. package/dist/src/handlers/top-level-field-handler.d.ts +14 -0
  32. package/dist/src/handlers/top-level-field-handler.js +72 -0
  33. package/dist/src/handlers/top-level-field-handler.js.map +1 -0
  34. package/dist/src/index.d.ts +2 -0
  35. package/dist/src/index.js +177 -0
  36. package/dist/src/index.js.map +1 -0
  37. package/dist/src/rendering/components.d.ts +67 -0
  38. package/dist/src/rendering/components.js +177 -0
  39. package/dist/src/rendering/components.js.map +1 -0
  40. package/dist/src/rendering/document.d.ts +36 -0
  41. package/dist/src/rendering/document.js +147 -0
  42. package/dist/src/rendering/document.js.map +1 -0
  43. package/dist/src/rendering/path-item.d.ts +45 -0
  44. package/dist/src/rendering/path-item.js +141 -0
  45. package/dist/src/rendering/path-item.js.map +1 -0
  46. package/dist/src/rendering/paths.d.ts +26 -0
  47. package/dist/src/rendering/paths.js +78 -0
  48. package/dist/src/rendering/paths.js.map +1 -0
  49. package/dist/src/rendering/types.d.ts +50 -0
  50. package/dist/src/rendering/types.js +12 -0
  51. package/dist/src/rendering/types.js.map +1 -0
  52. package/dist/src/rendering/utils.d.ts +31 -0
  53. package/dist/src/rendering/utils.js +79 -0
  54. package/dist/src/rendering/utils.js.map +1 -0
  55. package/dist/src/services/formatters.d.ts +36 -0
  56. package/dist/src/services/formatters.js +52 -0
  57. package/dist/src/services/formatters.js.map +1 -0
  58. package/dist/src/services/reference-transform.d.ts +27 -0
  59. package/dist/src/services/reference-transform.js +75 -0
  60. package/dist/src/services/reference-transform.js.map +1 -0
  61. package/dist/src/services/spec-loader.d.ts +27 -0
  62. package/dist/src/services/spec-loader.js +77 -0
  63. package/dist/src/services/spec-loader.js.map +1 -0
  64. package/dist/src/types.d.ts +11 -0
  65. package/dist/src/types.js +2 -0
  66. package/dist/src/types.js.map +1 -0
  67. package/dist/src/utils/uri-builder.d.ts +81 -0
  68. package/dist/src/utils/uri-builder.js +121 -0
  69. package/dist/src/utils/uri-builder.js.map +1 -0
  70. package/dist/src/version.d.ts +1 -0
  71. package/dist/src/version.js +4 -0
  72. package/dist/src/version.js.map +1 -0
  73. package/eslint.config.js +88 -0
  74. package/jest.config.js +32 -0
  75. package/justfile +66 -0
  76. package/memory-bank/activeContext.md +139 -0
  77. package/memory-bank/productContext.md +39 -0
  78. package/memory-bank/progress.md +141 -0
  79. package/memory-bank/projectbrief.md +50 -0
  80. package/memory-bank/systemPatterns.md +224 -0
  81. package/memory-bank/techContext.md +131 -0
  82. package/package.json +76 -0
  83. package/scripts/generate-version.js +49 -0
  84. package/src/config.ts +33 -0
  85. package/src/handlers/component-detail-handler.ts +121 -0
  86. package/src/handlers/component-map-handler.ts +92 -0
  87. package/src/handlers/handler-utils.ts +230 -0
  88. package/src/handlers/operation-handler.ts +114 -0
  89. package/src/handlers/path-item-handler.ts +88 -0
  90. package/src/handlers/top-level-field-handler.ts +92 -0
  91. package/src/index.ts +222 -0
  92. package/src/rendering/components.ts +228 -0
  93. package/src/rendering/document.ts +167 -0
  94. package/src/rendering/path-item.ts +157 -0
  95. package/src/rendering/paths.ts +87 -0
  96. package/src/rendering/types.ts +63 -0
  97. package/src/rendering/utils.ts +107 -0
  98. package/src/services/formatters.ts +71 -0
  99. package/src/services/reference-transform.ts +105 -0
  100. package/src/services/spec-loader.ts +88 -0
  101. package/src/types.ts +17 -0
  102. package/src/utils/uri-builder.ts +134 -0
  103. package/src/version.ts +4 -0
  104. package/test/__tests__/e2e/format.test.ts +224 -0
  105. package/test/__tests__/e2e/resources.test.ts +369 -0
  106. package/test/__tests__/e2e/spec-loading.test.ts +172 -0
  107. package/test/__tests__/unit/config.test.ts +39 -0
  108. package/test/__tests__/unit/handlers/component-detail-handler.test.ts +241 -0
  109. package/test/__tests__/unit/handlers/component-map-handler.test.ts +187 -0
  110. package/test/__tests__/unit/handlers/handler-utils.test.ts +255 -0
  111. package/test/__tests__/unit/handlers/operation-handler.test.ts +202 -0
  112. package/test/__tests__/unit/handlers/path-item-handler.test.ts +153 -0
  113. package/test/__tests__/unit/handlers/top-level-field-handler.test.ts +182 -0
  114. package/test/__tests__/unit/rendering/components.test.ts +269 -0
  115. package/test/__tests__/unit/rendering/document.test.ts +172 -0
  116. package/test/__tests__/unit/rendering/path-item.test.ts +197 -0
  117. package/test/__tests__/unit/rendering/paths.test.ts +115 -0
  118. package/test/__tests__/unit/services/formatters.test.ts +109 -0
  119. package/test/__tests__/unit/services/reference-transform.test.ts +320 -0
  120. package/test/__tests__/unit/services/spec-loader.test.ts +214 -0
  121. package/test/__tests__/unit/utils/uri-builder.test.ts +103 -0
  122. package/test/fixtures/complex-endpoint.json +146 -0
  123. package/test/fixtures/empty-api.json +8 -0
  124. package/test/fixtures/multi-component-types.json +55 -0
  125. package/test/fixtures/paths-test.json +61 -0
  126. package/test/fixtures/sample-api.json +68 -0
  127. package/test/fixtures/sample-v2-api.json +39 -0
  128. package/test/setup.ts +32 -0
  129. package/test/utils/console-helpers.ts +48 -0
  130. package/test/utils/mcp-test-helpers.ts +66 -0
  131. package/test/utils/test-types.ts +54 -0
  132. package/tsconfig.json +25 -0
  133. 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
+ });