@ibm-cloud/openapi-ruleset
Version:
Spectral ruleset for validating IBM Cloud services
253 lines (211 loc) • 8.51 kB
JavaScript
/**
* Copyright 2017 - 2024 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/
const { requestBodyName } = require('../../src/rules');
const {
makeCopy,
rootDocument,
testRule,
severityCodes,
} = require('../test-utils');
const ruleId = 'ibm-requestbody-name';
const rule = requestBodyName;
const expectedSeverity = severityCodes.warning;
const expectedMsg = `Operations with non-form request bodies should set a name with the 'x-codegen-request-body-name' extension`;
// This rule is turned off by default - enable it to run tests
// but still verify it is defined in the rule as "off".
const originalSeverity = makeCopy(rule.severity);
rule.severity = 'warn';
// `Operation with non-form requestBody should set a name with the ${EXTENSION_NAME} extension.`
describe(`Spectral rule: ${ruleId}`, () => {
it('Should originally be set to severity: "off"', () => {
expect(originalSeverity).toBe('off');
});
describe('Should not yield errors', () => {
it('Clean spec', async () => {
const results = await testRule(ruleId, rule, rootDocument);
expect(results).toHaveLength(0);
});
it('Request body has multi-content, extension present', async () => {
const testDocument = makeCopy(rootDocument);
// Add the extension and an additional requestBody content entry to the "create_movie" operation.
testDocument.paths['/v1/movies'].post['x-codegen-request-body-name'] =
'movie';
testDocument.paths['/v1/movies'].post.requestBody.content[
'application/octet-stream'
] = {
schema: {
$ref: '#/components/schemas/Movie',
},
};
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});
it('Request body schema is dynamic, extension present', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post['x-codegen-request-body-name'] =
'movie';
testDocument.components.schemas.Movie.additionalProperties = true;
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});
it('Request body schema has discriminator, extension present', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post['x-codegen-request-body-name'] =
'movie';
testDocument.components.schemas.Movie.discriminator = {
propertyName: 'type',
};
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});
it('Request body has non-json content, extension present', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post['x-codegen-request-body-name'] =
'movie';
testDocument.paths['/v1/movies'].post.requestBody.content = {
'application/octet-stream': {
schema: {
type: 'string',
format: 'binary',
},
},
};
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});
it('Request body is an array, extension present', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post['x-codegen-request-body-name'] =
'movie';
testDocument.paths['/v1/movies'].post.requestBody.content = {
'application/json': {
schema: {
type: 'array',
items: {
type: 'string',
},
},
},
};
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});
it('Request body has form content, no extension needed', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post.requestBody.content = {
'multipart/form-data': {
schema: {
type: 'object',
properties: {
form_param_1: {
$ref: '#/components/schemas/NormalString',
},
form_param_2: {
$ref: '#/components/schemas/NormalString',
},
form_param_3: {
type: 'string',
format: 'binary',
},
},
},
},
};
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(0);
});
});
describe('Should yield errors', () => {
it('Request body has multi-content, extension missing', async () => {
const testDocument = makeCopy(rootDocument);
// Add a second mimeType to the requestBody content field.
testDocument.paths['/v1/movies'].post.requestBody.content[
'application/octet-stream'
] = {
schema: {
$ref: '#/components/schemas/Movie',
},
};
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(1);
expect(results[0].code).toBe(ruleId);
expect(results[0].message).toBe(expectedMsg);
expect(results[0].severity).toBe(expectedSeverity);
expect(results[0].path.join('.')).toBe('paths./v1/movies.post');
});
it('Request body schema is abstract, extension missing', async () => {
const testDocument = makeCopy(rootDocument);
// The Drink schema is already abstract, so just remove the extension
// to simulate the error.
delete testDocument.paths['/v1/drinks'].post[
'x-codegen-request-body-name'
];
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(1);
expect(results[0].code).toBe(ruleId);
expect(results[0].message).toBe(expectedMsg);
expect(results[0].severity).toBe(expectedSeverity);
expect(results[0].path.join('.')).toBe('paths./v1/drinks.post');
});
it('Request body schema is dynamic, extension missing', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.components.schemas.MoviePrototype.additionalProperties = true;
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(1);
expect(results[0].code).toBe(ruleId);
expect(results[0].message).toBe(expectedMsg);
expect(results[0].severity).toBe(expectedSeverity);
expect(results[0].path.join('.')).toBe('paths./v1/movies.post');
});
it('Request body schema has discriminator, extension missing', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.components.schemas.MoviePrototype.discriminator = {
propertyName: 'type',
};
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(1);
expect(results[0].code).toBe(ruleId);
expect(results[0].message).toBe(expectedMsg);
expect(results[0].severity).toBe(expectedSeverity);
expect(results[0].path.join('.')).toBe('paths./v1/movies.post');
});
it('Request body has non-json content, extension missing', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post.requestBody.content = {
'application/octet-stream': {
schema: {
type: 'string',
format: 'binary',
},
},
};
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(1);
expect(results[0].code).toBe(ruleId);
expect(results[0].message).toBe(expectedMsg);
expect(results[0].severity).toBe(expectedSeverity);
expect(results[0].path.join('.')).toBe('paths./v1/movies.post');
});
it('Request body is an array, extension missing', async () => {
const testDocument = makeCopy(rootDocument);
testDocument.paths['/v1/movies'].post.requestBody.content = {
'application/json': {
schema: {
type: 'array',
items: {
type: 'string',
},
},
},
};
const results = await testRule(ruleId, rule, testDocument);
expect(results).toHaveLength(1);
expect(results[0].code).toBe(ruleId);
expect(results[0].message).toBe(expectedMsg);
expect(results[0].severity).toBe(expectedSeverity);
expect(results[0].path.join('.')).toBe('paths./v1/movies.post');
});
});
});