@ebarahona/loopback-openapi-v3 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/LICENSE +21 -0
- package/README.md +132 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +6 -0
- package/dist/keys.js +10 -0
- package/dist/keys.js.map +1 -0
- package/dist/openapi-version.component.d.ts +4 -0
- package/dist/openapi-version.component.js +18 -0
- package/dist/openapi-version.component.js.map +1 -0
- package/dist/openapi-version.enhancer.d.ts +8 -0
- package/dist/openapi-version.enhancer.js +46 -0
- package/dist/openapi-version.enhancer.js.map +1 -0
- package/dist/transform.d.ts +19 -0
- package/dist/transform.js +500 -0
- package/dist/transform.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +72 -0
- package/src/__tests__/openapi-version.enhancer.spec.ts +456 -0
- package/src/__tests__/transform.spec.ts +879 -0
- package/src/index.ts +10 -0
- package/src/keys.ts +9 -0
- package/src/openapi-version.component.ts +27 -0
- package/src/openapi-version.enhancer.ts +61 -0
- package/src/transform.ts +702 -0
- package/src/types.ts +20 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import {describe, it, expect} from 'vitest';
|
|
2
|
+
import {OpenApiVersionEnhancer} from '../openapi-version.enhancer';
|
|
3
|
+
import {OpenApiSpec} from '@loopback/openapi-v3';
|
|
4
|
+
|
|
5
|
+
function createTestSpec(): OpenApiSpec {
|
|
6
|
+
return {
|
|
7
|
+
openapi: '3.0.0',
|
|
8
|
+
info: {title: 'Test', version: '1.0.0'},
|
|
9
|
+
paths: {
|
|
10
|
+
'/items': {
|
|
11
|
+
get: {
|
|
12
|
+
operationId: 'getItems',
|
|
13
|
+
parameters: [
|
|
14
|
+
{
|
|
15
|
+
name: 'filter',
|
|
16
|
+
in: 'query',
|
|
17
|
+
schema: {type: 'string', nullable: true},
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
responses: {
|
|
21
|
+
'200': {
|
|
22
|
+
description: 'OK',
|
|
23
|
+
content: {
|
|
24
|
+
'application/json': {
|
|
25
|
+
schema: {
|
|
26
|
+
type: 'array',
|
|
27
|
+
items: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
id: {type: 'integer'},
|
|
31
|
+
name: {type: 'string', nullable: true},
|
|
32
|
+
tags: {
|
|
33
|
+
type: 'array',
|
|
34
|
+
items: {type: 'string', nullable: true},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
post: {
|
|
45
|
+
operationId: 'createItem',
|
|
46
|
+
requestBody: {
|
|
47
|
+
content: {
|
|
48
|
+
'application/json': {
|
|
49
|
+
schema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
name: {type: 'string'},
|
|
53
|
+
description: {type: 'string', nullable: true},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
responses: {
|
|
60
|
+
'200': {description: 'Created'},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
components: {
|
|
66
|
+
schemas: {
|
|
67
|
+
Item: {
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: {
|
|
70
|
+
id: {type: 'integer'},
|
|
71
|
+
name: {type: 'string'},
|
|
72
|
+
notes: {type: 'string', nullable: true},
|
|
73
|
+
metadata: {
|
|
74
|
+
type: 'object',
|
|
75
|
+
nullable: true,
|
|
76
|
+
additionalProperties: {type: 'string'},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
tags: [{name: 'items', description: 'Item operations'}],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function createSpec32(): Record<string, unknown> {
|
|
87
|
+
return {
|
|
88
|
+
openapi: '3.2.0',
|
|
89
|
+
info: {title: 'Test', version: '1.0.0'},
|
|
90
|
+
$self: 'https://api.example.com/openapi.json',
|
|
91
|
+
paths: {
|
|
92
|
+
'/items': {
|
|
93
|
+
get: {
|
|
94
|
+
operationId: 'getItems',
|
|
95
|
+
parameters: [
|
|
96
|
+
{
|
|
97
|
+
name: 'qs',
|
|
98
|
+
in: 'querystring',
|
|
99
|
+
schema: {type: 'string'},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
responses: {
|
|
103
|
+
'200': {
|
|
104
|
+
description: 'OK',
|
|
105
|
+
content: {
|
|
106
|
+
'text/event-stream': {
|
|
107
|
+
schema: {type: 'string'},
|
|
108
|
+
itemSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
id: {type: 'integer'},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
query: {
|
|
120
|
+
operationId: 'queryItems',
|
|
121
|
+
responses: {'200': {description: 'OK'}},
|
|
122
|
+
},
|
|
123
|
+
additionalOperations: {
|
|
124
|
+
PURGE: {
|
|
125
|
+
operationId: 'purgeItems',
|
|
126
|
+
responses: {'200': {description: 'Purged'}},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
components: {
|
|
132
|
+
schemas: {
|
|
133
|
+
Item: {
|
|
134
|
+
type: 'object',
|
|
135
|
+
properties: {
|
|
136
|
+
id: {type: 'integer'},
|
|
137
|
+
name: {type: ['string', 'null']},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
securitySchemes: {
|
|
142
|
+
oauth2: {
|
|
143
|
+
type: 'oauth2',
|
|
144
|
+
flows: {
|
|
145
|
+
authorizationCode: {
|
|
146
|
+
authorizationUrl: 'https://auth.example.com/authorize',
|
|
147
|
+
tokenUrl: 'https://auth.example.com/token',
|
|
148
|
+
scopes: {read: 'Read access'},
|
|
149
|
+
},
|
|
150
|
+
device: {
|
|
151
|
+
tokenUrl: 'https://auth.example.com/device',
|
|
152
|
+
scopes: {read: 'Read access'},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
examples: {
|
|
158
|
+
ItemExample: {
|
|
159
|
+
summary: 'An item',
|
|
160
|
+
dataValue: {id: 1, name: 'Test'},
|
|
161
|
+
serializedValue: '{"id":1,"name":"Test"}',
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
tags: [
|
|
166
|
+
{
|
|
167
|
+
name: 'items',
|
|
168
|
+
summary: 'Item endpoints',
|
|
169
|
+
parent: 'resources',
|
|
170
|
+
kind: 'nav',
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// -------------------------------------------------------------------
|
|
177
|
+
// 3.0 -> 3.0 (no-op)
|
|
178
|
+
// -------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
describe('OpenApiVersionEnhancer', () => {
|
|
181
|
+
describe('version 3.0.0 (no-op)', () => {
|
|
182
|
+
it('returns spec unchanged', () => {
|
|
183
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
184
|
+
const spec = createTestSpec();
|
|
185
|
+
const result = enhancer.modifySpec(spec);
|
|
186
|
+
expect(result.openapi).toBe('3.0.0');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('preserves nullable in 3.0 mode', () => {
|
|
190
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
191
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
192
|
+
const item = result.components?.schemas?.Item as Record<string, unknown>;
|
|
193
|
+
const props = item?.properties as Record<string, unknown>;
|
|
194
|
+
const notes = props?.notes as Record<string, unknown>;
|
|
195
|
+
expect(notes?.nullable).toBe(true);
|
|
196
|
+
expect(notes?.type).toBe('string');
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// -------------------------------------------------------------------
|
|
201
|
+
// 3.0 -> 3.1 upgrades
|
|
202
|
+
// -------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
describe('version 3.1.0', () => {
|
|
205
|
+
it('updates the openapi version field', () => {
|
|
206
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.1.0'});
|
|
207
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
208
|
+
expect(result.openapi).toBe('3.1.0');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('transforms nullable in component schemas', () => {
|
|
212
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.1.0'});
|
|
213
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
214
|
+
const item = result.components?.schemas?.Item as Record<string, unknown>;
|
|
215
|
+
const props = item?.properties as Record<string, unknown>;
|
|
216
|
+
const notes = props?.notes as Record<string, unknown>;
|
|
217
|
+
expect(notes?.type).toEqual(['string', 'null']);
|
|
218
|
+
expect(notes).not.toHaveProperty('nullable');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('transforms nullable in nested object schemas', () => {
|
|
222
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.1.0'});
|
|
223
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
224
|
+
const item = result.components?.schemas?.Item as Record<string, unknown>;
|
|
225
|
+
const props = item?.properties as Record<string, unknown>;
|
|
226
|
+
const metadata = props?.metadata as Record<string, unknown>;
|
|
227
|
+
expect(metadata?.type).toEqual(['object', 'null']);
|
|
228
|
+
expect(metadata).not.toHaveProperty('nullable');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('transforms nullable in response schemas', () => {
|
|
232
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.1.0'});
|
|
233
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
234
|
+
const getOp = (result.paths?.['/items'] as Record<string, unknown>)
|
|
235
|
+
?.get as Record<string, unknown>;
|
|
236
|
+
const resp = (getOp?.responses as Record<string, unknown>)?.[
|
|
237
|
+
'200'
|
|
238
|
+
] as Record<string, unknown>;
|
|
239
|
+
const content = resp?.content as Record<string, unknown>;
|
|
240
|
+
const json = content?.['application/json'] as Record<string, unknown>;
|
|
241
|
+
const schema = json?.schema as Record<string, unknown>;
|
|
242
|
+
const items = schema?.items as Record<string, unknown>;
|
|
243
|
+
const props = items?.properties as Record<string, unknown>;
|
|
244
|
+
const name = props?.name as Record<string, unknown>;
|
|
245
|
+
expect(name?.type).toEqual(['string', 'null']);
|
|
246
|
+
expect(name).not.toHaveProperty('nullable');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('transforms nullable in deeply nested array items', () => {
|
|
250
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.1.0'});
|
|
251
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
252
|
+
const getOp = (result.paths?.['/items'] as Record<string, unknown>)
|
|
253
|
+
?.get as Record<string, unknown>;
|
|
254
|
+
const resp = (getOp?.responses as Record<string, unknown>)?.[
|
|
255
|
+
'200'
|
|
256
|
+
] as Record<string, unknown>;
|
|
257
|
+
const content = resp?.content as Record<string, unknown>;
|
|
258
|
+
const json = content?.['application/json'] as Record<string, unknown>;
|
|
259
|
+
const schema = json?.schema as Record<string, unknown>;
|
|
260
|
+
const items = schema?.items as Record<string, unknown>;
|
|
261
|
+
const props = items?.properties as Record<string, unknown>;
|
|
262
|
+
const tags = props?.tags as Record<string, unknown>;
|
|
263
|
+
const tagItems = tags?.items as Record<string, unknown>;
|
|
264
|
+
expect(tagItems?.type).toEqual(['string', 'null']);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('transforms nullable in request body schemas', () => {
|
|
268
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.1.0'});
|
|
269
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
270
|
+
const postOp = (result.paths?.['/items'] as Record<string, unknown>)
|
|
271
|
+
?.post as Record<string, unknown>;
|
|
272
|
+
const reqBody = postOp?.requestBody as Record<string, unknown>;
|
|
273
|
+
const content = reqBody?.content as Record<string, unknown>;
|
|
274
|
+
const json = content?.['application/json'] as Record<string, unknown>;
|
|
275
|
+
const schema = json?.schema as Record<string, unknown>;
|
|
276
|
+
const props = schema?.properties as Record<string, unknown>;
|
|
277
|
+
const desc = props?.description as Record<string, unknown>;
|
|
278
|
+
expect(desc?.type).toEqual(['string', 'null']);
|
|
279
|
+
expect(desc).not.toHaveProperty('nullable');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('transforms nullable in parameter schemas', () => {
|
|
283
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.1.0'});
|
|
284
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
285
|
+
const getOp = (result.paths?.['/items'] as Record<string, unknown>)
|
|
286
|
+
?.get as Record<string, unknown>;
|
|
287
|
+
const params = getOp?.parameters as Record<string, unknown>[];
|
|
288
|
+
const param = params?.[0];
|
|
289
|
+
const schema = param?.schema as Record<string, unknown>;
|
|
290
|
+
expect(schema?.type).toEqual(['string', 'null']);
|
|
291
|
+
expect(schema).not.toHaveProperty('nullable');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('skips transform when transformNullable is false', () => {
|
|
295
|
+
const enhancer = new OpenApiVersionEnhancer({
|
|
296
|
+
version: '3.1.0',
|
|
297
|
+
transformNullable: false,
|
|
298
|
+
});
|
|
299
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
300
|
+
expect(result.openapi).toBe('3.1.0');
|
|
301
|
+
const item = result.components?.schemas?.Item as Record<string, unknown>;
|
|
302
|
+
const props = item?.properties as Record<string, unknown>;
|
|
303
|
+
const notes = props?.notes as Record<string, unknown>;
|
|
304
|
+
expect(notes?.nullable).toBe(true);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// -------------------------------------------------------------------
|
|
309
|
+
// 3.0 -> 3.2
|
|
310
|
+
// -------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
describe('version 3.2.0', () => {
|
|
313
|
+
it('updates the openapi version field', () => {
|
|
314
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.2.0'});
|
|
315
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
316
|
+
expect(result.openapi).toBe('3.2.0');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('transforms nullable schemas same as 3.1', () => {
|
|
320
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.2.0'});
|
|
321
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
322
|
+
const item = result.components?.schemas?.Item as Record<string, unknown>;
|
|
323
|
+
const props = item?.properties as Record<string, unknown>;
|
|
324
|
+
const notes = props?.notes as Record<string, unknown>;
|
|
325
|
+
expect(notes?.type).toEqual(['string', 'null']);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// -------------------------------------------------------------------
|
|
330
|
+
// 3.2 -> 3.0 downgrade (strip 3.2 features)
|
|
331
|
+
// -------------------------------------------------------------------
|
|
332
|
+
|
|
333
|
+
describe('3.2 -> 3.0 downgrade', () => {
|
|
334
|
+
it('strips $self from root', () => {
|
|
335
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
336
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
337
|
+
expect(result).not.toHaveProperty('$self');
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('strips query method from paths', () => {
|
|
341
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
342
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
343
|
+
const pathItem = result.paths?.['/items'] as Record<string, unknown>;
|
|
344
|
+
expect(pathItem).not.toHaveProperty('query');
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('strips additionalOperations from paths', () => {
|
|
348
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
349
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
350
|
+
const pathItem = result.paths?.['/items'] as Record<string, unknown>;
|
|
351
|
+
expect(pathItem).not.toHaveProperty('additionalOperations');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('strips tag summary, parent, kind', () => {
|
|
355
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
356
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
357
|
+
const tag = result.tags?.[0] as Record<string, unknown>;
|
|
358
|
+
expect(tag?.name).toBe('items');
|
|
359
|
+
expect(tag).not.toHaveProperty('summary');
|
|
360
|
+
expect(tag).not.toHaveProperty('parent');
|
|
361
|
+
expect(tag).not.toHaveProperty('kind');
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('strips OAuth2 device flow', () => {
|
|
365
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
366
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
367
|
+
const schemes = result.components?.securitySchemes as Record<
|
|
368
|
+
string,
|
|
369
|
+
unknown
|
|
370
|
+
>;
|
|
371
|
+
const oauth2 = schemes?.oauth2 as Record<string, unknown>;
|
|
372
|
+
const flows = oauth2?.flows as Record<string, unknown>;
|
|
373
|
+
expect(flows).not.toHaveProperty('device');
|
|
374
|
+
expect(flows).toHaveProperty('authorizationCode');
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('strips itemSchema from media types', () => {
|
|
378
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
379
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
380
|
+
const getOp = (result.paths?.['/items'] as Record<string, unknown>)
|
|
381
|
+
?.get as Record<string, unknown>;
|
|
382
|
+
const resp = (getOp?.responses as Record<string, unknown>)?.[
|
|
383
|
+
'200'
|
|
384
|
+
] as Record<string, unknown>;
|
|
385
|
+
const content = resp?.content as Record<string, unknown>;
|
|
386
|
+
const sse = content?.['text/event-stream'] as Record<string, unknown>;
|
|
387
|
+
expect(sse).not.toHaveProperty('itemSchema');
|
|
388
|
+
expect(sse).toHaveProperty('schema');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('strips dataValue and serializedValue from examples', () => {
|
|
392
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
393
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
394
|
+
const examples = result.components?.examples as Record<string, unknown>;
|
|
395
|
+
const item = examples?.ItemExample as Record<string, unknown>;
|
|
396
|
+
expect(item).not.toHaveProperty('dataValue');
|
|
397
|
+
expect(item).not.toHaveProperty('serializedValue');
|
|
398
|
+
expect(item?.summary).toBe('An item');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('converts querystring parameter location to query', () => {
|
|
402
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
403
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
404
|
+
const getOp = (result.paths?.['/items'] as Record<string, unknown>)
|
|
405
|
+
?.get as Record<string, unknown>;
|
|
406
|
+
const params = getOp?.parameters as Record<string, unknown>[];
|
|
407
|
+
expect(params?.[0]?.in).toBe('query');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('reverts type arrays to nullable for 3.0', () => {
|
|
411
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.0.0'});
|
|
412
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
413
|
+
const item = result.components?.schemas?.Item as Record<string, unknown>;
|
|
414
|
+
const props = item?.properties as Record<string, unknown>;
|
|
415
|
+
const name = props?.name as Record<string, unknown>;
|
|
416
|
+
expect(name?.type).toBe('string');
|
|
417
|
+
expect(name?.nullable).toBe(true);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// -------------------------------------------------------------------
|
|
422
|
+
// 3.2 -> 3.1 downgrade (strip 3.2 features, keep 3.1 features)
|
|
423
|
+
// -------------------------------------------------------------------
|
|
424
|
+
|
|
425
|
+
describe('3.2 -> 3.1 downgrade', () => {
|
|
426
|
+
it('strips 3.2 features but keeps type arrays', () => {
|
|
427
|
+
const enhancer = new OpenApiVersionEnhancer({version: '3.1.0'});
|
|
428
|
+
const result = enhancer.modifySpec(createSpec32() as OpenApiSpec);
|
|
429
|
+
expect(result.openapi).toBe('3.1.0');
|
|
430
|
+
|
|
431
|
+
// 3.2 features stripped
|
|
432
|
+
const pathItem = result.paths?.['/items'] as Record<string, unknown>;
|
|
433
|
+
expect(pathItem).not.toHaveProperty('query');
|
|
434
|
+
expect(result).not.toHaveProperty('$self');
|
|
435
|
+
|
|
436
|
+
// 3.1 features preserved (type arrays stay)
|
|
437
|
+
const item = result.components?.schemas?.Item as Record<string, unknown>;
|
|
438
|
+
const props = item?.properties as Record<string, unknown>;
|
|
439
|
+
const name = props?.name as Record<string, unknown>;
|
|
440
|
+
expect(name?.type).toEqual(['string', 'null']);
|
|
441
|
+
expect(name).not.toHaveProperty('nullable');
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// -------------------------------------------------------------------
|
|
446
|
+
// Default config
|
|
447
|
+
// -------------------------------------------------------------------
|
|
448
|
+
|
|
449
|
+
describe('default config', () => {
|
|
450
|
+
it('defaults to 3.0.0 (no-op)', () => {
|
|
451
|
+
const enhancer = new OpenApiVersionEnhancer();
|
|
452
|
+
const result = enhancer.modifySpec(createTestSpec());
|
|
453
|
+
expect(result.openapi).toBe('3.0.0');
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
});
|