@ibm-cloud/openapi-ruleset
Version:
Spectral ruleset for validating IBM Cloud services
410 lines (377 loc) • 11.6 kB
JavaScript
/**
* Copyright 2024 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/
const {
getOffsetParamIndex,
getPageTokenParamIndex,
getSuccessCode,
getResponseSchema,
getPaginatedOperationFromPath,
} = require('../../src/utils');
describe('Pagination utility functions', () => {
describe('getOffsetParamIndex()', () => {
it('should return -1 if an array is not provided', () => {
expect(getOffsetParamIndex('parameters')).toBe(-1);
});
it('should return -1 if there is no parameter named "offset"', () => {
const parameters = [{ in: 'query', name: 'token' }];
expect(getOffsetParamIndex(parameters)).toBe(-1);
});
it('should return -1 if there is no query parameter named "offset"', () => {
const parameters = [{ in: 'header', name: 'offset' }];
expect(getOffsetParamIndex(parameters)).toBe(-1);
});
it('should return the index of an offset parameter', () => {
const parameters = [{ in: 'query', name: 'offset' }];
expect(getOffsetParamIndex(parameters)).toBe(0);
});
});
describe('getPageTokenParamIndex()', () => {
it('should return -1 if an array is not provided', () => {
expect(getPageTokenParamIndex('parameters')).toBe(-1);
});
it('should return -1 if there is no token parameter', () => {
const parameters = [{ in: 'query', name: 'offset' }];
expect(getPageTokenParamIndex(parameters)).toBe(-1);
});
describe.each(['start', 'token', 'cursor', 'page', 'page_token'])(
'test each allowable token parameter name',
paramName => {
it('should return -1 if no token-named query parameters are found', () => {
const parameters = [{ in: 'header', name: paramName }];
expect(getPageTokenParamIndex(parameters)).toBe(-1);
});
it('should return the index of a token-named query parameter', () => {
const parameters = [{ in: 'query', name: paramName }];
expect(getPageTokenParamIndex(parameters)).toBe(0);
});
}
);
});
describe('getSuccessCode()', () => {
it('should return undefined if operation is undefined', () => {
const operation = undefined;
expect(getSuccessCode(operation)).toBeUndefined();
});
it('should return undefined if operation is an empty object', () => {
const operation = {};
expect(getSuccessCode(operation)).toBeUndefined();
});
it('should return undefined if operation has no responses', () => {
const operation = {
responses: {},
};
expect(getSuccessCode(operation)).toBeUndefined();
});
it('should return undefined if operation has no success responses', () => {
const operation = {
responses: {
400: {},
},
};
expect(getSuccessCode(operation)).toBeUndefined();
});
it('should return the first success code in the operation responses', () => {
const operation = {
responses: {
200: {},
},
};
expect(getSuccessCode(operation)).toBe('200');
});
});
describe('getResponseSchema()', () => {
it('should return undefined if response is undefined', () => {
const response = undefined;
expect(getResponseSchema(response)).toBeUndefined();
});
it('should return undefined if response has no content', () => {
const response = {};
expect(getResponseSchema(response)).toBeUndefined();
});
it('should return undefined if response content is not json', () => {
const response = {
content: {
'application/pdf': {},
},
};
expect(getResponseSchema(response)).toBeUndefined();
});
it('should return undefined if json response content has no schema', () => {
const response = {
content: {
'application/json': {},
},
};
expect(getResponseSchema(response)).toBeUndefined();
});
it('should return simple json response content schema', () => {
const schema = { type: 'object' };
const response = {
content: {
'application/json': {
schema,
},
},
};
expect(getResponseSchema(response)).toEqual(schema);
});
it('should return merged json response content schema if it contains an allOf', () => {
const schema = {
allOf: [
{
type: 'object',
},
{
description: 'a very good object',
},
],
};
const response = {
content: {
'application/json': {
schema,
},
},
};
const mergedSchema = {
type: 'object',
description: 'a very good object',
};
expect(getResponseSchema(response)).toEqual(mergedSchema);
});
});
describe('getPaginatedOperationFromPath()', () => {
const mockLogger = jest.fn();
const logInfo = {
logger: { debug: mockLogger },
ruleId: 'test',
};
afterEach(() => {
mockLogger.mockClear();
});
it('should return undefined if path is not resource-generic', () => {
const pathItem = {};
const path = ['paths', '/v1/resources/{id}'];
expect(
getPaginatedOperationFromPath(pathItem, path, logInfo)
).toBeUndefined();
expect(mockLogger.mock.calls[0][0]).toBe(
"test: 'get' operation is absent or excluded at path '/v1/resources/{id}'"
);
});
it('should return undefined if resource-generic path does not have a get operation', () => {
const pathItem = {};
const path = ['paths', '/v1/resources'];
expect(
getPaginatedOperationFromPath(pathItem, path, logInfo)
).toBeUndefined();
expect(mockLogger.mock.calls[0][0]).toBe(
"test: 'get' operation is absent or excluded at path '/v1/resources'"
);
});
it('should return undefined if get operation has no responses', () => {
const pathItem = {
get: {},
};
const path = ['paths', '/v1/resources'];
expect(
getPaginatedOperationFromPath(pathItem, path, logInfo)
).toBeUndefined();
expect(mockLogger.mock.calls[0][0]).toBe(
'test: No success response code found!'
);
});
it('should return undefined if get operation has no success responses', () => {
const pathItem = {
get: {
responses: {
400: {},
},
},
};
const path = ['paths', '/v1/resources'];
expect(
getPaginatedOperationFromPath(pathItem, path, logInfo)
).toBeUndefined();
expect(mockLogger.mock.calls[0][0]).toBe(
'test: No success response code found!'
);
});
it('should return undefined if get operation success response has no schema', () => {
const pathItem = {
get: {
responses: {
200: {},
},
},
};
const path = ['paths', '/v1/resources'];
expect(
getPaginatedOperationFromPath(pathItem, path, logInfo)
).toBeUndefined();
expect(mockLogger.mock.calls[0][0]).toBe(
'test: No response schema found!'
);
});
it('should return undefined if get operation success response schema has no properties', () => {
const pathItem = {
get: {
responses: {
200: {
content: {
'application/json': {
schema: {},
},
},
},
},
},
};
const path = ['paths', '/v1/resources'];
expect(
getPaginatedOperationFromPath(pathItem, path, logInfo)
).toBeUndefined();
expect(mockLogger.mock.calls[0][0]).toBe(
'test: Resolved response schema has no properties!'
);
});
it('should return undefined if get operation success response schema has no array property', () => {
const pathItem = {
get: {
responses: {
200: {
content: {
'application/json': {
schema: {
type: 'object',
properties: {
total_count: {
type: 'integer',
},
},
},
},
},
},
},
},
};
const path = ['paths', '/v1/resources'];
expect(
getPaginatedOperationFromPath(pathItem, path, logInfo)
).toBeUndefined();
expect(mockLogger.mock.calls[0][0]).toBe(
'test: Response schema has no array property!'
);
});
it('should return undefined if get operation has no parameters', () => {
const pathItem = {
get: {
responses: {
200: {
content: {
'application/json': {
schema: {
type: 'object',
properties: {
resources: {
type: 'array',
},
},
},
},
},
},
},
},
};
const path = ['paths', '/v1/resources'];
expect(
getPaginatedOperationFromPath(pathItem, path, logInfo)
).toBeUndefined();
expect(mockLogger.mock.calls[0][0]).toBe(
'test: Operation has no parameters!'
);
});
it('should return undefined if get operation has no pagination parameters', () => {
const pathItem = {
get: {
parameters: [
{
name: 'sort',
in: 'query',
},
],
responses: {
200: {
content: {
'application/json': {
schema: {
type: 'object',
properties: {
resources: {
type: 'array',
},
},
},
},
},
},
},
},
};
const path = ['paths', '/v1/resources'];
expect(
getPaginatedOperationFromPath(pathItem, path, logInfo)
).toBeUndefined();
expect(mockLogger.mock.calls[0][0]).toBe(
'test: No start or offset query param!'
);
});
const paginationParameters = [
'offset',
'start',
'token',
'cursor',
'page',
'page_token',
];
it.each(paginationParameters)(
'should return operation if it has a pagination query parameter',
param => {
const pathItem = {
get: {
parameters: [
{
name: param,
in: 'query',
},
],
responses: {
200: {
content: {
'application/json': {
schema: {
type: 'object',
properties: {
resources: {
type: 'array',
},
},
},
},
},
},
},
},
};
const path = ['paths', '/v1/resources'];
expect(getPaginatedOperationFromPath(pathItem, path, logInfo)).toEqual(
pathItem.get
);
}
);
});
});