mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-17 16:15:22 +00:00
383 lines
13 KiB
TypeScript
383 lines
13 KiB
TypeScript
import { describe, expect, it } from 'bun:test';
|
|
|
|
import { parseOpenAPI, traverse } from '@gitbook/openapi-parser';
|
|
import serverPrecedenceSpec from './fixtures/spec-server-precedence.json';
|
|
import { resolveOpenAPIOperation } from './resolveOpenAPIOperation';
|
|
|
|
async function loadFixture(spec: object) {
|
|
const { filesystem } = await parseOpenAPI({
|
|
value: JSON.stringify(spec),
|
|
rootURL: 'memory://spec.json',
|
|
});
|
|
return filesystem;
|
|
}
|
|
|
|
async function fetchFilesystem(url: string) {
|
|
const response = await fetch(url);
|
|
const text = await response.text();
|
|
const { filesystem } = await parseOpenAPI({ value: text, rootURL: url });
|
|
const transformedFs = await traverse(filesystem, async (node) => {
|
|
if ('description' in node && typeof node.description === 'string' && node.description) {
|
|
node['x-gitbook-description-html'] = node.description;
|
|
}
|
|
return node;
|
|
});
|
|
return transformedFs;
|
|
}
|
|
|
|
describe('#resolveOpenAPIOperation', () => {
|
|
it('should resolve refs', async () => {
|
|
const filesystem = await fetchFilesystem(
|
|
'https://petstore3.swagger.io/api/v3/openapi.json'
|
|
);
|
|
const resolved = await resolveOpenAPIOperation(filesystem, { method: 'put', path: '/pet' });
|
|
|
|
expect(resolved).toMatchObject({
|
|
servers: [
|
|
{
|
|
url: '/api/v3',
|
|
},
|
|
],
|
|
operation: {
|
|
tags: ['pet'],
|
|
summary: 'Update an existing pet.',
|
|
description: 'Update an existing pet by Id.',
|
|
requestBody: {
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['name', 'photoUrls'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should support yaml', async () => {
|
|
const filesystem = await fetchFilesystem(
|
|
'https://petstore3.swagger.io/api/v3/openapi.yaml'
|
|
);
|
|
const resolved = await resolveOpenAPIOperation(filesystem, { method: 'put', path: '/pet' });
|
|
|
|
expect(resolved).toMatchObject({
|
|
servers: [
|
|
{
|
|
url: '/api/v3',
|
|
},
|
|
],
|
|
operation: {
|
|
tags: ['pet'],
|
|
summary: 'Update an existing pet.',
|
|
description: 'Update an existing pet by Id.',
|
|
requestBody: {
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['name', 'photoUrls'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should resolve circular refs', async () => {
|
|
const filesystem = await fetchFilesystem('https://api.gitbook.com/openapi.json');
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/spaces/{spaceId}/content/page/{pageId}',
|
|
});
|
|
|
|
expect(resolved).toMatchObject({
|
|
servers: [
|
|
{
|
|
url: '{host}/v1',
|
|
},
|
|
],
|
|
operation: {
|
|
operationId: 'getPageById',
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should resolve to null if the method is not supported', async () => {
|
|
const filesystem = await fetchFilesystem(
|
|
'https://petstore3.swagger.io/api/v3/openapi.json'
|
|
);
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'dontexist',
|
|
path: '/pet',
|
|
});
|
|
|
|
expect(resolved).toBe(null);
|
|
});
|
|
|
|
it('should parse Swagger 2.0', async () => {
|
|
const filesystem = await fetchFilesystem('https://petstore.swagger.io/v2/swagger.json');
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'put',
|
|
path: '/pet',
|
|
});
|
|
|
|
expect(resolved).toMatchObject({
|
|
servers: [
|
|
{
|
|
url: 'https://petstore.swagger.io/v2',
|
|
},
|
|
{
|
|
url: 'http://petstore.swagger.io/v2',
|
|
},
|
|
],
|
|
operation: {
|
|
tags: ['pet'],
|
|
summary: 'Update an existing pet',
|
|
description: '',
|
|
requestBody: {
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['name', 'photoUrls'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should resolve a ref with whitespace', async () => {
|
|
const filesystem = await fetchFilesystem(
|
|
' https://petstore3.swagger.io/api/v3/openapi.json'
|
|
);
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'put',
|
|
path: '/pet',
|
|
});
|
|
|
|
expect(resolved).toMatchObject({
|
|
servers: [
|
|
{
|
|
url: '/api/v3',
|
|
},
|
|
],
|
|
operation: {
|
|
tags: ['pet'],
|
|
summary: 'Update an existing pet.',
|
|
description: 'Update an existing pet by Id.',
|
|
requestBody: {
|
|
content: {
|
|
'application/json': {
|
|
schema: {
|
|
type: 'object',
|
|
required: ['name', 'photoUrls'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
describe('x-enable-proxy', () => {
|
|
it('should extract x-enable-proxy when set to true', async () => {
|
|
const filesystem = await loadFixture({
|
|
openapi: '3.1.0',
|
|
info: { title: 'Test', version: '1.0' },
|
|
'x-enable-proxy': true,
|
|
paths: {
|
|
'/test': {
|
|
get: { responses: { '200': { description: 'OK' } } },
|
|
},
|
|
},
|
|
});
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/test',
|
|
});
|
|
|
|
expect(resolved?.['x-enable-proxy']).toBe(true);
|
|
});
|
|
|
|
it('should extract x-enable-proxy when set to false', async () => {
|
|
const filesystem = await loadFixture({
|
|
openapi: '3.1.0',
|
|
info: { title: 'Test', version: '1.0' },
|
|
'x-enable-proxy': false,
|
|
paths: {
|
|
'/test': {
|
|
get: { responses: { '200': { description: 'OK' } } },
|
|
},
|
|
},
|
|
});
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/test',
|
|
});
|
|
|
|
expect(resolved?.['x-enable-proxy']).toBe(false);
|
|
});
|
|
|
|
it('should return undefined when x-enable-proxy is not set', async () => {
|
|
const filesystem = await loadFixture({
|
|
openapi: '3.1.0',
|
|
info: { title: 'Test', version: '1.0' },
|
|
paths: {
|
|
'/test': {
|
|
get: { responses: { '200': { description: 'OK' } } },
|
|
},
|
|
},
|
|
});
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/test',
|
|
});
|
|
|
|
expect(resolved?.['x-enable-proxy']).toBeUndefined();
|
|
});
|
|
|
|
it('should ignore x-enable-proxy when not a boolean', async () => {
|
|
const filesystem = await loadFixture({
|
|
openapi: '3.1.0',
|
|
info: { title: 'Test', version: '1.0' },
|
|
'x-enable-proxy': 'yes',
|
|
paths: {
|
|
'/test': {
|
|
get: { responses: { '200': { description: 'OK' } } },
|
|
},
|
|
},
|
|
});
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/test',
|
|
});
|
|
|
|
expect(resolved?.['x-enable-proxy']).toBeUndefined();
|
|
});
|
|
|
|
it('should be available at the operation level', async () => {
|
|
const filesystem = await loadFixture({
|
|
openapi: '3.1.0',
|
|
info: { title: 'Test', version: '1.0' },
|
|
paths: {
|
|
'/test': {
|
|
get: {
|
|
'x-enable-proxy': true,
|
|
responses: { '200': { description: 'OK' } },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/test',
|
|
});
|
|
|
|
expect(resolved?.operation['x-enable-proxy']).toBe(true);
|
|
});
|
|
|
|
it('should allow operation-level to override spec-level', async () => {
|
|
const filesystem = await loadFixture({
|
|
openapi: '3.1.0',
|
|
info: { title: 'Test', version: '1.0' },
|
|
'x-enable-proxy': true,
|
|
paths: {
|
|
'/test': {
|
|
get: {
|
|
'x-enable-proxy': false,
|
|
responses: { '200': { description: 'OK' } },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/test',
|
|
});
|
|
|
|
expect(resolved?.['x-enable-proxy']).toBe(true);
|
|
expect(resolved?.operation['x-enable-proxy']).toBe(false);
|
|
});
|
|
|
|
it('should allow operation-level true to override spec-level false', async () => {
|
|
const filesystem = await loadFixture({
|
|
openapi: '3.1.0',
|
|
info: { title: 'Test', version: '1.0' },
|
|
'x-enable-proxy': false,
|
|
paths: {
|
|
'/test': {
|
|
get: {
|
|
'x-enable-proxy': true,
|
|
responses: { '200': { description: 'OK' } },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/test',
|
|
});
|
|
|
|
expect(resolved?.['x-enable-proxy']).toBe(false);
|
|
expect(resolved?.operation['x-enable-proxy']).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('server precedence', () => {
|
|
it('should use root-level servers when no path or operation servers are defined', async () => {
|
|
const filesystem = await loadFixture(serverPrecedenceSpec);
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/root-only',
|
|
});
|
|
|
|
expect(resolved?.servers).toEqual([{ url: 'https://root.example.com' }]);
|
|
});
|
|
|
|
it('should use path-level servers over root-level servers', async () => {
|
|
const filesystem = await loadFixture(serverPrecedenceSpec);
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/path-override',
|
|
});
|
|
|
|
expect(resolved?.servers).toEqual([{ url: 'https://path.example.com' }]);
|
|
});
|
|
|
|
it('should use operation-level servers over path and root-level servers', async () => {
|
|
const filesystem = await loadFixture(serverPrecedenceSpec);
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/operation-override',
|
|
});
|
|
|
|
expect(resolved?.servers).toEqual([{ url: 'https://operation.example.com' }]);
|
|
});
|
|
|
|
it('should use operation-level servers over root-level when no path servers exist', async () => {
|
|
const filesystem = await loadFixture(serverPrecedenceSpec);
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/operation-skip-path',
|
|
});
|
|
|
|
expect(resolved?.servers).toEqual([{ url: 'https://operation.example.com' }]);
|
|
});
|
|
|
|
it('should fallback to root-level servers for endpoints without overrides', async () => {
|
|
const filesystem = await loadFixture(serverPrecedenceSpec);
|
|
const resolved = await resolveOpenAPIOperation(filesystem, {
|
|
method: 'get',
|
|
path: '/no-servers',
|
|
});
|
|
|
|
expect(resolved?.servers).toEqual([{ url: 'https://root.example.com' }]);
|
|
});
|
|
});
|
|
});
|