@furystack/rest
Version:
Generic REST package
294 lines • 12.7 kB
JavaScript
import { describe, expect, it } from 'vitest';
import { resolveOpenApiRefs } from './openapi-resolve-refs.js';
describe('resolveOpenApiRefs', () => {
describe('Schema $ref resolution', () => {
it('Should resolve $ref to components/schemas', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/users': {
get: {
responses: {
'200': {
description: 'OK',
content: {
'application/json': {
schema: { $ref: '#/components/schemas/User' },
},
},
},
},
},
},
},
components: {
schemas: {
User: { type: 'object', properties: { id: { type: 'string' }, name: { type: 'string' } } },
},
},
};
const resolved = resolveOpenApiRefs(doc);
const { schema } = (resolved.paths?.['/users']?.get?.responses?.['200']).content['application/json'];
expect(schema).toEqual({ type: 'object', properties: { id: { type: 'string' }, name: { type: 'string' } } });
});
it('Should resolve nested $ref chains', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/items': {
get: {
responses: {
'200': {
description: 'OK',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
category: { $ref: '#/components/schemas/Category' },
},
},
},
},
},
},
},
},
},
components: {
schemas: {
Category: { type: 'object', properties: { id: { type: 'integer' }, name: { type: 'string' } } },
},
},
};
const resolved = resolveOpenApiRefs(doc);
const schema = (resolved.paths?.['/items']?.get?.responses?.['200']).content['application/json'].schema;
const props = schema.properties.category;
expect(props.type).toBe('object');
expect(props.properties).toEqual({ id: { type: 'integer' }, name: { type: 'string' } });
});
});
describe('Parameter $ref resolution', () => {
it('Should resolve $ref parameters', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/items': {
get: {
parameters: [{ $ref: '#/components/parameters/LimitParam' }],
responses: { '200': { description: 'OK' } },
},
},
},
components: {
parameters: {
LimitParam: { name: 'limit', in: 'query', schema: { type: 'integer' } },
},
},
};
const resolved = resolveOpenApiRefs(doc);
const params = resolved.paths?.['/items']?.get?.parameters;
expect(params[0].name).toBe('limit');
expect(params[0].in).toBe('query');
});
});
describe('Response $ref resolution', () => {
it('Should resolve $ref responses', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/items': {
get: {
responses: {
'400': { $ref: '#/components/responses/BadRequest' },
},
},
},
},
components: {
responses: {
BadRequest: {
description: 'Bad request',
content: {
'application/json': {
schema: { type: 'object', properties: { message: { type: 'string' } } },
},
},
},
},
},
};
const resolved = resolveOpenApiRefs(doc);
const resp = resolved.paths?.['/items']?.get?.responses?.['400'];
expect(resp.description).toBe('Bad request');
});
});
describe('RequestBody $ref resolution', () => {
it('Should resolve $ref in requestBody schema', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/items': {
post: {
requestBody: {
content: {
'application/json': {
schema: { $ref: '#/components/schemas/Item' },
},
},
},
responses: { '201': { description: 'Created' } },
},
},
},
components: {
schemas: {
Item: { type: 'object', properties: { name: { type: 'string' } } },
},
},
};
const resolved = resolveOpenApiRefs(doc);
const body = resolved.paths?.['/items']?.post?.requestBody;
const schema = body.content['application/json']
.schema;
expect(schema.type).toBe('object');
});
});
describe('Edge cases', () => {
it('Should handle circular $ref by breaking the cycle with empty object', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
components: {
schemas: {
Node: {
type: 'object',
properties: {
value: { type: 'string' },
child: { $ref: '#/components/schemas/Node' },
},
},
},
},
};
const resolved = resolveOpenApiRefs(doc);
const nodeSchema = resolved.components?.schemas?.Node;
expect(nodeSchema.type).toBe('object');
const props = nodeSchema.properties;
expect(props.value).toEqual({ type: 'string' });
// The circular child ref is resolved, but the nested self-ref within it breaks the cycle
expect(props.child.type).toBe('object');
const childProps = props.child.properties;
expect(childProps.value).toEqual({ type: 'string' });
expect(childProps.child).toEqual({});
});
it('Should not modify the original document', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/items': {
get: {
responses: {
'200': {
description: 'OK',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Item' } } },
},
},
},
},
},
components: { schemas: { Item: { type: 'object' } } },
};
resolveOpenApiRefs(doc);
const schema = doc.paths?.['/items']?.get?.responses?.['200'];
expect(schema.content['application/json'].schema).toEqual({
$ref: '#/components/schemas/Item',
});
});
it('Should leave external $ref as-is', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/items': {
get: {
responses: {
'200': {
description: 'OK',
content: { 'application/json': { schema: { $ref: 'external.json#/Schema' } } },
},
},
},
},
},
};
const resolved = resolveOpenApiRefs(doc);
const { schema } = (resolved.paths?.['/items']?.get?.responses?.['200']).content['application/json'];
expect(schema).toEqual({ $ref: 'external.json#/Schema' });
});
it('Should leave unresolvable $ref as-is', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/items': {
get: {
responses: {
'200': {
description: 'OK',
content: { 'application/json': { schema: { $ref: '#/components/schemas/Missing' } } },
},
},
},
},
},
};
const resolved = resolveOpenApiRefs(doc);
const { schema } = (resolved.paths?.['/items']?.get?.responses?.['200']).content['application/json'];
expect(schema).toEqual({ $ref: '#/components/schemas/Missing' });
});
it('Should handle documents with no $ref', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/health': { get: { responses: { '200': { description: 'OK' } } } },
},
};
const resolved = resolveOpenApiRefs(doc);
expect(resolved.paths?.['/health']?.get?.responses?.['200']).toEqual({ description: 'OK' });
});
it('Should resolve arrays of items with $ref', () => {
const doc = {
openapi: '3.1.0',
info: { title: 'Test', version: '1.0.0' },
paths: {
'/items': {
get: {
parameters: [{ $ref: '#/components/parameters/A' }, { $ref: '#/components/parameters/B' }],
responses: { '200': { description: 'OK' } },
},
},
},
components: {
parameters: {
A: { name: 'a', in: 'query', schema: { type: 'string' } },
B: { name: 'b', in: 'query', schema: { type: 'integer' } },
},
},
};
const resolved = resolveOpenApiRefs(doc);
const params = resolved.paths?.['/items']?.get?.parameters;
expect(params).toHaveLength(2);
expect(params[0].name).toBe('a');
expect(params[1].name).toBe('b');
});
});
});
//# sourceMappingURL=openapi-resolve-refs.spec.js.map