ibm-openapi-validator
Version:
Configurable and extensible validator/linter for OpenAPI documents
489 lines (441 loc) • 17.9 kB
JavaScript
/**
* Copyright 2017 - 2024 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/
const { getCapturedText } = require('../../test-utils');
const {
getDefaultConfig,
loadConfig,
processArgs,
} = require('../../../src/cli-validator/utils');
// Use these parse options since we're not actually retrieving process args.
const cliParseOptions = { from: 'user' };
describe('Configuration Manager tests', function () {
let consoleSpy;
const originalWarn = console.warn;
const originalError = console.error;
const originalInfo = console.info;
beforeEach(() => {
consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
console.warn = console.log;
console.error = console.log;
console.info = console.log;
});
afterEach(() => {
consoleSpy.mockRestore();
console.warn = originalWarn;
console.error = originalError;
console.info = originalInfo;
});
describe('getDefaultConfig()', function () {
it('should return correct default configuration object', async function () {
const defaultConfig = getDefaultConfig();
expect(typeof defaultConfig).toBe('object');
expect(defaultConfig.colorizeOutput).toBe(true);
expect(defaultConfig.errorsOnly).toBe(false);
expect(defaultConfig.files).toStrictEqual([]);
expect(defaultConfig.limits).toMatchObject({ warnings: -1 });
expect(defaultConfig.ignoreFiles).toStrictEqual([]);
expect(defaultConfig.logLevels).toMatchObject({});
expect(defaultConfig.outputFormat).toBe('text');
expect(defaultConfig.ruleset).toBe(null);
expect(defaultConfig.summaryOnly).toBe(false);
expect(defaultConfig.produceImpactScore).toBe(false);
expect(defaultConfig.markdownReport).toBe(false);
});
});
describe('loadConfig()', function () {
const expectedConfig = {
limits: {
warnings: 10,
},
ignoreFiles: ['ignored/file/numero_uno', 'another/ignored/file'],
logLevels: {
logger1: 'debug',
root: 'info',
logger2: 'error',
},
};
it('should return correct config object for .json', async function () {
const config = await loadConfig(
'./test/cli-validator/mock-files/config/valid-config.json'
);
// originalError(`config = ${JSON.stringify(config, null, 2)}`);
expect(config).toMatchObject(expectedConfig);
});
it('should return correct config object for .yaml', async function () {
const config = await loadConfig(
'./test/cli-validator/mock-files/config/valid-config.yaml'
);
expect(config).toMatchObject(expectedConfig);
});
it('should return correct config object for .js', async function () {
const config = await loadConfig(
'./test/cli-validator/mock-files/config/valid-config.js'
);
expect(config).toMatchObject(expectedConfig);
});
it('should return correct config object for five-warnings.json', async function () {
const config = await loadConfig(
'./test/cli-validator/mock-files/config/five-warnings.json'
);
expect(config.limits.warnings).toBe(5);
});
it('should log error and return default config for invalid config file', async function () {
const defaultConfig = getDefaultConfig();
const config = await loadConfig(
'./test/cli-validator/mock-files/config/invalid-config.yaml'
);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(2);
expect(capturedText[0]).toMatch(/Invalid configuration file/);
expect(capturedText[0]).toMatch(
/schema validation error: '\/errorsOnly': must be boolean/
);
expect(capturedText[1]).toMatch(
/The validator will use a default config/
);
expect(config).toMatchObject(defaultConfig);
});
});
describe('processArgs()', function () {
it('should return default config when no CLI args', async function () {
const defaultConfig = getDefaultConfig();
const { context } = await processArgs([], cliParseOptions);
// const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(context).toBeDefined();
expect(context).toMatchObject({ config: defaultConfig });
});
it('should return default config if invalid config file (bad JSON)', async function () {
const defaultConfig = getDefaultConfig();
const { context } = await processArgs(
['-c', './test/cli-validator/mock-files/bad-json.json'],
cliParseOptions
);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(2);
expect(capturedText[0]).toMatch(
/\[ERROR\] Unable to load config file.*SyntaxError:/
);
expect(capturedText[1]).toMatch(
/The validator will use a default config/
);
expect(context).toBeDefined();
expect(context).toMatchObject({ config: defaultConfig });
});
it('should return default config if invalid config file (schema)', async function () {
const defaultConfig = getDefaultConfig();
const { context } = await processArgs(
['-c', './test/cli-validator/mock-files/config/invalid-config.yaml'],
cliParseOptions
);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(2);
expect(capturedText[0]).toMatch(/Invalid configuration file/);
expect(capturedText[0]).toMatch(
/schema validation error: '\/errorsOnly': must be boolean/
);
expect(capturedText[1]).toMatch(
/The validator will use a default config/
);
expect(context).toBeDefined();
expect(context).toMatchObject({ config: defaultConfig });
});
it('should return correct config if valid config file', async function () {
const { context } = await processArgs(
['-c', './test/cli-validator/mock-files/config/config1.yaml'],
cliParseOptions
);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject({
colorizeOutput: true,
errorsOnly: true,
files: ['file1.yaml', 'file2.json'],
ignoreFiles: ['ignored/file1'],
limits: {
warnings: 5,
},
logLevels: {
'ibm-schema-description-exists': 'debug',
root: 'info',
},
outputFormat: 'text',
summaryOnly: false,
produceImpactScore: false,
markdownReport: false,
ruleset: null,
});
});
it('should return correct config if valid config file AND cli options used', async function () {
const { context } = await processArgs(
[
'-c',
'./test/cli-validator/mock-files/config/config1.yaml',
'file3.json',
'--log-level',
'debug',
'-n',
'--json',
'file4.yaml',
'--ignore',
'ignored/file2.json',
'--ruleset',
'my-rules.yml',
'--summary-only',
'--warnings-limit',
'-1',
'--impact-score',
'--markdown-report',
],
cliParseOptions
);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject({
colorizeOutput: false,
errorsOnly: true,
files: ['file3.json', 'file4.yaml'],
ignoreFiles: ['ignored/file2.json'],
limits: {
warnings: -1,
},
logLevels: {
root: 'debug',
},
ruleset: 'my-rules.yml',
summaryOnly: true,
produceImpactScore: true,
markdownReport: true,
});
});
it('should log error and use default if invalid warnings value', async function () {
const { context } = await processArgs(
['--warnings-limit', 'foo'],
cliParseOptions
);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(1);
expect(capturedText[0]).toBe(
`error: option '-w, --warnings-limit <number>' argument 'foo' is invalid; using default (-1) instead`
);
expect(context).toBeDefined();
expect(context.config).toBeDefined();
expect(context.config.limits).toBeDefined();
expect(context.config.limits.warnings).toBe(-1);
});
describe('CLI options', function () {
it.each(['-c', '--config'])(
`should load correct config with -c/--config option`,
async function (option) {
const expectedConfig = {
colorizeOutput: true,
errorsOnly: true,
files: ['file1.yaml', 'file2.json'],
limits: {
warnings: 5,
},
logLevels: {
root: 'info',
'ibm-schema-description-exists': 'debug',
},
outputFormat: 'text',
summaryOnly: false,
produceImpactScore: false,
markdownReport: false,
};
const { context } = await processArgs(
[option, './test/cli-validator/mock-files/config/config1.yaml'],
cliParseOptions
);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-e', '--errors-only'])(
`should produce correct config with -e/--errors-only option`,
async function (option) {
const expectedConfig = {
errorsOnly: true,
};
const { context } = await processArgs([option], cliParseOptions);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-i', '--ignore'])(
`should produce correct config with -i/--ignore option`,
async function (option) {
const expectedConfig = {
ignoreFiles: ['ignoredFile1.yaml', 'ignoredFile2.json'],
};
const { context } = await processArgs(
[option, 'ignoredFile1.yaml', option, 'ignoredFile2.json'],
cliParseOptions
);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-j', '--json'])(
`should produce correct config with -j/--json option`,
async function (option) {
const expectedConfig = {
outputFormat: 'json',
};
const { context } = await processArgs([option], cliParseOptions);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-lroot=debug', '--log-level=root=debug'])(
`should produce correct config with -l/--log-level option`,
async function (option) {
const expectedConfig = {
logLevels: {
root: 'debug',
},
};
const { context } = await processArgs([option], cliParseOptions);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-n', '--no-colors'])(
`should produce correct config with -n/--no-colors option`,
async function (option) {
const expectedConfig = {
colorizeOutput: false,
};
const { context } = await processArgs([option], cliParseOptions);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-r', '--ruleset'])(
`should produce correct config with -r/--ruleset option`,
async function (option) {
const expectedConfig = {
ruleset: 'my-custom-rules.yaml',
};
const { context } = await processArgs(
[option, 'my-custom-rules.yaml'],
cliParseOptions
);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-s', '--summary-only'])(
`should produce correct config with -s/--summary-only option`,
async function (option) {
const expectedConfig = {
summaryOnly: true,
};
const { context } = await processArgs([option], cliParseOptions);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-q', '--impact-score'])(
`should produce correct config with -q/--impact-score option`,
async function (option) {
const expectedConfig = {
produceImpactScore: true,
};
const { context } = await processArgs([option], cliParseOptions);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-m', '--markdown-report'])(
`should produce correct config with -m/--markdown-report option`,
async function (option) {
const expectedConfig = {
markdownReport: true,
};
const { context } = await processArgs([option], cliParseOptions);
const capturedText = getCapturedText(consoleSpy.mock.calls);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it.each(['-w', '--warnings-limit'])(
`should produce correct config with -2/--warnings-limit option`,
async function (option) {
const expectedConfig = {
limits: {
warnings: 38,
},
};
const { context } = await processArgs([option, 38], cliParseOptions);
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(0);
expect(context).toBeDefined();
expect(context.config).toMatchObject(expectedConfig);
}
);
it('should not throw error for --version option', async function () {
const { command } = await processArgs(['--version'], cliParseOptions);
expect(command.opts().version).toBe(true);
const capturedText = getCapturedText(consoleSpy.mock.calls);
expect(capturedText).toHaveLength(0);
});
it('should throw error for --help option', async function () {
let caughtException;
try {
await processArgs(['--help'], cliParseOptions);
} catch (err) {
caughtException = true;
expect(err.exitCode).toBe(0);
}
const capturedText = getCapturedText(consoleSpy.mock.calls);
// originalError(`Captured text: ${JSON.stringify(capturedText, null, 2)}`);
expect(capturedText).toHaveLength(2);
expect(capturedText[0]).toMatch(
/IBM OpenAPI Validator.*validator:.*Copyright.*/
);
expect(capturedText[1]).toMatch(/Usage:/);
expect(caughtException).toBe(true);
});
});
});
});