1324 lines
31 KiB
TypeScript
1324 lines
31 KiB
TypeScript
import * as yargsParser from 'yargs-parser';
|
|
import { logger } from './logger';
|
|
import {
|
|
applyVerbosity,
|
|
coerceTypesInOptions,
|
|
combineOptionsForExecutor,
|
|
convertAliases,
|
|
convertSmartDefaultsIntoNamedParams,
|
|
convertToCamelCase,
|
|
lookupUnmatched,
|
|
Schema,
|
|
setDefaults,
|
|
validateOptsAgainstSchema,
|
|
warnDeprecations,
|
|
} from './params';
|
|
import { TargetConfiguration } from './workspace';
|
|
|
|
describe('params', () => {
|
|
describe('combineOptionsForExecutor', () => {
|
|
let schema: Schema;
|
|
beforeEach(() => {
|
|
schema = {
|
|
properties: {
|
|
overriddenOpt: {
|
|
type: 'string',
|
|
alias: 'overriddenOptAlias',
|
|
},
|
|
},
|
|
};
|
|
});
|
|
|
|
it('should use target options', () => {
|
|
const commandLineOpts = {};
|
|
const target: TargetConfiguration = {
|
|
executor: '@nrwl/do:stuff',
|
|
options: {
|
|
overriddenOpt: 'target value',
|
|
},
|
|
configurations: {
|
|
production: {},
|
|
},
|
|
};
|
|
|
|
const options = combineOptionsForExecutor(
|
|
commandLineOpts,
|
|
'production',
|
|
target,
|
|
schema,
|
|
'proj',
|
|
process.cwd()
|
|
);
|
|
|
|
expect(options).toEqual({
|
|
overriddenOpt: 'target value',
|
|
});
|
|
});
|
|
|
|
it('should combine target, configuration', () => {
|
|
const commandLineOpts = {};
|
|
const target: TargetConfiguration = {
|
|
executor: '@nrwl/do:stuff',
|
|
options: {
|
|
overriddenOpt: 'target value',
|
|
},
|
|
configurations: {
|
|
production: {
|
|
overriddenOpt: 'config value',
|
|
},
|
|
},
|
|
};
|
|
|
|
const options = combineOptionsForExecutor(
|
|
commandLineOpts,
|
|
'production',
|
|
target,
|
|
schema,
|
|
'proj',
|
|
process.cwd()
|
|
);
|
|
|
|
expect(options).toEqual({
|
|
overriddenOpt: 'config value',
|
|
});
|
|
});
|
|
|
|
it('should combine target, configuration, and passed options', () => {
|
|
const commandLineOpts = {
|
|
overriddenOpt: 'command value',
|
|
};
|
|
const target: TargetConfiguration = {
|
|
executor: '@nrwl/do:stuff',
|
|
options: {
|
|
overriddenOpt: 'target value',
|
|
},
|
|
configurations: {
|
|
production: {
|
|
overriddenOpt: 'config value',
|
|
},
|
|
},
|
|
};
|
|
|
|
const options = combineOptionsForExecutor(
|
|
commandLineOpts,
|
|
'production',
|
|
target,
|
|
schema,
|
|
'proj',
|
|
process.cwd()
|
|
);
|
|
|
|
expect(options).toEqual({
|
|
overriddenOpt: 'command value',
|
|
});
|
|
});
|
|
|
|
it('should convert aliases in target configuration', () => {
|
|
const commandLineOpts = {
|
|
overriddenOpt: 'command value',
|
|
};
|
|
const target: TargetConfiguration = {
|
|
executor: '@nrwl/do:stuff',
|
|
options: {
|
|
overriddenOptAlias: 'target value',
|
|
},
|
|
configurations: {
|
|
production: {
|
|
overriddenOptAlias: 'config value',
|
|
},
|
|
},
|
|
};
|
|
|
|
const options = combineOptionsForExecutor(
|
|
commandLineOpts,
|
|
'production',
|
|
target,
|
|
schema,
|
|
'proj',
|
|
process.cwd()
|
|
);
|
|
|
|
expect(options).toEqual({
|
|
overriddenOpt: 'command value',
|
|
});
|
|
});
|
|
|
|
it('should convert aliases in command line arguments', () => {
|
|
const commandLineOpts = {
|
|
overriddenOptAlias: 'command value',
|
|
};
|
|
const target: TargetConfiguration = {
|
|
executor: '@nrwl/do:stuff',
|
|
options: {
|
|
overriddenOpt: 'target value',
|
|
},
|
|
configurations: {
|
|
production: {
|
|
overriddenOpt: 'config value',
|
|
},
|
|
},
|
|
};
|
|
|
|
const options = combineOptionsForExecutor(
|
|
commandLineOpts,
|
|
'production',
|
|
target,
|
|
schema,
|
|
'proj',
|
|
process.cwd()
|
|
);
|
|
|
|
expect(options).toEqual({
|
|
overriddenOpt: 'command value',
|
|
});
|
|
});
|
|
|
|
it('should handle targets without options', () => {
|
|
const commandLineOpts = {};
|
|
const target: TargetConfiguration = {
|
|
executor: '@nrwl/do:stuff',
|
|
};
|
|
|
|
const options = combineOptionsForExecutor(
|
|
commandLineOpts,
|
|
'production',
|
|
target,
|
|
schema,
|
|
'proj',
|
|
process.cwd()
|
|
);
|
|
|
|
expect(options).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe('coerceTypes', () => {
|
|
it('should handle booleans', () => {
|
|
const opts = coerceTypesInOptions(
|
|
{ a: true, b: 'true', c: false, d: 'true' },
|
|
{
|
|
properties: {
|
|
a: { type: 'boolean' },
|
|
b: { type: 'boolean' },
|
|
c: { type: 'boolean' },
|
|
d: { type: 'string' },
|
|
},
|
|
} as Schema
|
|
);
|
|
|
|
expect(opts).toEqual({
|
|
a: true,
|
|
b: true,
|
|
c: false,
|
|
d: 'true',
|
|
});
|
|
});
|
|
|
|
it('should handle numbers', () => {
|
|
const opts = coerceTypesInOptions({ a: 1, b: '2', c: '3' }, {
|
|
properties: {
|
|
a: { type: 'number' },
|
|
b: { type: 'number' },
|
|
c: { type: 'string' },
|
|
},
|
|
} as Schema);
|
|
|
|
expect(opts).toEqual({
|
|
a: 1,
|
|
b: 2,
|
|
c: '3',
|
|
});
|
|
});
|
|
|
|
it('should handle arrays', () => {
|
|
const opts = coerceTypesInOptions({ a: 'one,two', b: 'three,four' }, {
|
|
properties: {
|
|
a: { type: 'array' },
|
|
b: { type: 'string' },
|
|
},
|
|
} as Schema);
|
|
|
|
expect(opts).toEqual({
|
|
a: ['one', 'two'],
|
|
b: 'three,four',
|
|
});
|
|
|
|
const opts2 = coerceTypesInOptions({ a: '1,2', b: 'true,false' }, {
|
|
properties: {
|
|
a: { type: 'array', items: { type: 'number' } },
|
|
b: { type: 'array', items: { type: 'boolean' } },
|
|
},
|
|
} as Schema);
|
|
|
|
expect(opts2).toEqual({
|
|
a: [1, 2],
|
|
b: [true, false],
|
|
});
|
|
});
|
|
|
|
it('should handle options with aliases', () => {
|
|
const schema: Schema = {
|
|
properties: {
|
|
name: { type: 'array', alias: 'n' },
|
|
},
|
|
};
|
|
const opts = coerceTypesInOptions({ n: 'one,two' }, schema);
|
|
|
|
expect(opts).toEqual({
|
|
n: ['one', 'two'],
|
|
});
|
|
});
|
|
|
|
it('should handle oneOf', () => {
|
|
const opts = coerceTypesInOptions(
|
|
{ a: 'false' } as any,
|
|
{
|
|
properties: {
|
|
a: { oneOf: [{ type: 'object' }, { type: 'boolean' }] },
|
|
},
|
|
} as Schema
|
|
);
|
|
|
|
expect(opts).toEqual({
|
|
a: false,
|
|
});
|
|
});
|
|
|
|
it('should handle oneOf with enums inside', () => {
|
|
const opts = coerceTypesInOptions(
|
|
{ inspect: 'inspect' } as any,
|
|
{
|
|
properties: {
|
|
inspect: {
|
|
oneOf: [
|
|
{
|
|
type: 'string',
|
|
enum: ['inspect', 'inspect-brk'],
|
|
},
|
|
{
|
|
type: 'number',
|
|
},
|
|
{
|
|
type: 'boolean',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
} as Schema
|
|
);
|
|
|
|
expect(opts).toEqual({
|
|
inspect: 'inspect',
|
|
});
|
|
});
|
|
|
|
it('should only coerce string values', () => {
|
|
const opts = coerceTypesInOptions(
|
|
{ a: true } as any,
|
|
{
|
|
properties: {
|
|
a: { oneOf: [{ type: 'boolean' }, { type: 'number' }] },
|
|
},
|
|
} as Schema
|
|
);
|
|
|
|
expect(opts).toEqual({
|
|
a: true,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('convertToCamelCase', () => {
|
|
it('should convert dash case to camel case', () => {
|
|
expect(
|
|
convertToCamelCase(
|
|
yargsParser(['--one-two', '1'], {
|
|
number: ['oneTwo'],
|
|
})
|
|
)
|
|
).toEqual({
|
|
_: [],
|
|
oneTwo: 1,
|
|
});
|
|
});
|
|
|
|
it('should not convert camel case', () => {
|
|
expect(
|
|
convertToCamelCase(
|
|
yargsParser(['--oneTwo', '1'], {
|
|
number: ['oneTwo'],
|
|
})
|
|
)
|
|
).toEqual({
|
|
_: [],
|
|
oneTwo: 1,
|
|
});
|
|
});
|
|
|
|
it('should handle mixed case', () => {
|
|
expect(
|
|
convertToCamelCase(
|
|
yargsParser(['--one-Two', '1'], {
|
|
number: ['oneTwo'],
|
|
})
|
|
)
|
|
).toEqual({
|
|
_: [],
|
|
oneTwo: 1,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('convertAliases', () => {
|
|
it('should replace aliases with actual keys', () => {
|
|
expect(
|
|
convertAliases(
|
|
{ d: 'test' },
|
|
{
|
|
properties: { directory: { type: 'string', alias: 'd' } },
|
|
required: [],
|
|
description: '',
|
|
},
|
|
true
|
|
)
|
|
).toEqual({ directory: 'test' });
|
|
});
|
|
|
|
it('should replace aliases defined in aliases with actual keys', () => {
|
|
expect(
|
|
convertAliases(
|
|
{ d: 'test' },
|
|
{
|
|
properties: { directory: { type: 'string', aliases: ['d'] } },
|
|
required: [],
|
|
description: '',
|
|
},
|
|
true
|
|
)
|
|
).toEqual({ directory: 'test' });
|
|
});
|
|
|
|
it('should filter unknown keys into the leftovers field when excludeUnmatched is true', () => {
|
|
expect(
|
|
convertAliases(
|
|
{ d: 'test' },
|
|
{
|
|
properties: { directory: { type: 'string' } },
|
|
required: [],
|
|
description: '',
|
|
},
|
|
true
|
|
)
|
|
).toEqual({
|
|
'--': [
|
|
{
|
|
name: 'd',
|
|
possible: [],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it('should not filter unknown keys into the leftovers field when excludeUnmatched is false', () => {
|
|
expect(
|
|
convertAliases(
|
|
{ d: 'test' },
|
|
{
|
|
properties: { directory: { type: 'string' } },
|
|
required: [],
|
|
description: '',
|
|
},
|
|
false
|
|
)
|
|
).toEqual({
|
|
d: 'test',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('lookupUnmatched', () => {
|
|
it('should populate the possible array with near matches', () => {
|
|
expect(
|
|
lookupUnmatched(
|
|
{
|
|
'--': [
|
|
{
|
|
name: 'directoy',
|
|
possible: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
properties: { directory: { type: 'string' } },
|
|
required: [],
|
|
description: '',
|
|
}
|
|
)
|
|
).toEqual({
|
|
'--': [
|
|
{
|
|
name: 'directoy',
|
|
possible: ['directory'],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it('should NOT populate the possible array with far matches', () => {
|
|
expect(
|
|
lookupUnmatched(
|
|
{
|
|
'--': [
|
|
{
|
|
name: 'directoy',
|
|
possible: [],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
properties: { faraway: { type: 'string' } },
|
|
required: [],
|
|
description: '',
|
|
}
|
|
)
|
|
).toEqual({
|
|
'--': [
|
|
{
|
|
name: 'directoy',
|
|
possible: [],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('setDefault', () => {
|
|
it('should set default values', () => {
|
|
const opts = setDefaults(
|
|
{ c: false },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'boolean',
|
|
},
|
|
b: {
|
|
type: 'boolean',
|
|
default: true,
|
|
},
|
|
c: {
|
|
type: 'boolean',
|
|
default: true,
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(opts).toEqual({ b: true, c: false });
|
|
});
|
|
|
|
it('should set defaults in complex cases', () => {
|
|
const opts = setDefaults(
|
|
{ a: [{}, {}] },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
key: {
|
|
type: 'string',
|
|
default: 'inner',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(opts).toEqual({ a: [{ key: 'inner' }, { key: 'inner' }] });
|
|
});
|
|
|
|
it('should set the default array value', () => {
|
|
const opts = setDefaults(
|
|
{},
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
key: {
|
|
type: 'string',
|
|
default: 'inner',
|
|
},
|
|
},
|
|
},
|
|
default: [],
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(opts).toEqual({ a: [] });
|
|
});
|
|
|
|
it('should set the default object value', () => {
|
|
const opts = setDefaults(
|
|
{
|
|
a: {
|
|
key: 'value',
|
|
},
|
|
},
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'object',
|
|
properties: {
|
|
key: {
|
|
type: 'string',
|
|
},
|
|
key2: {
|
|
type: 'string',
|
|
default: 'value2',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(opts).toEqual({ a: { key: 'value', key2: 'value2' } });
|
|
});
|
|
|
|
it('should not default object properties to {}', () => {
|
|
const opts = setDefaults(
|
|
{},
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'object',
|
|
properties: {
|
|
key: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(opts).toEqual({});
|
|
});
|
|
|
|
it('should be able to set defaults for underlying properties', () => {
|
|
const opts = setDefaults(
|
|
{},
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'object',
|
|
properties: {
|
|
minify: {
|
|
type: 'boolean',
|
|
default: true,
|
|
},
|
|
inlineCritical: {
|
|
type: 'boolean',
|
|
default: true,
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(opts).toEqual({
|
|
a: {
|
|
minify: true,
|
|
inlineCritical: true,
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should resolve types using refs', () => {
|
|
const opts = setDefaults(
|
|
{},
|
|
{
|
|
properties: {
|
|
a: {
|
|
$ref: '#/definitions/a',
|
|
},
|
|
},
|
|
definitions: {
|
|
a: {
|
|
type: 'boolean',
|
|
default: true,
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(opts).toEqual({ a: true });
|
|
});
|
|
});
|
|
|
|
describe('convertSmartDefaultsIntoNamedParams', () => {
|
|
it('should use argv', () => {
|
|
const params = {};
|
|
convertSmartDefaultsIntoNamedParams(
|
|
params,
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'string',
|
|
$default: {
|
|
$source: 'argv',
|
|
index: 0,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
['argv-value'],
|
|
null,
|
|
null
|
|
);
|
|
|
|
expect(params).toEqual({ a: 'argv-value' });
|
|
});
|
|
|
|
it('should use projectName', () => {
|
|
const params = {};
|
|
convertSmartDefaultsIntoNamedParams(
|
|
params,
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'string',
|
|
$default: {
|
|
$source: 'projectName',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
[],
|
|
'myProject',
|
|
null
|
|
);
|
|
|
|
expect(params).toEqual({ a: 'myProject' });
|
|
});
|
|
|
|
it('should use relativeCwd to set path', () => {
|
|
const params = {};
|
|
convertSmartDefaultsIntoNamedParams(
|
|
params,
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'string',
|
|
format: 'path',
|
|
visible: false,
|
|
},
|
|
},
|
|
},
|
|
[],
|
|
null,
|
|
'./somepath'
|
|
);
|
|
|
|
expect(params).toEqual({ a: './somepath' });
|
|
});
|
|
});
|
|
|
|
describe('validateOptsAgainstSchema', () => {
|
|
it('should throw if missing the required property', () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{},
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'boolean',
|
|
},
|
|
},
|
|
required: ['a'],
|
|
}
|
|
)
|
|
).toThrow("Required property 'a' is missing");
|
|
});
|
|
|
|
it('should throw if found an unknown property', () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{
|
|
a: true,
|
|
b: false,
|
|
},
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'boolean',
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
}
|
|
)
|
|
).toThrow("'b' is not found in schema");
|
|
});
|
|
|
|
describe('primitive types', () => {
|
|
it("should throw if the type doesn't match", () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: 'string' },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'boolean',
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).toThrow(
|
|
"Property 'a' does not match the schema. 'string' should be a 'boolean'."
|
|
);
|
|
});
|
|
|
|
it('should not throw if the schema type is absent', () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: 'string' },
|
|
{
|
|
properties: {
|
|
a: {
|
|
default: false,
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).not.toThrow();
|
|
});
|
|
|
|
describe('string', () => {
|
|
it('should handle validating patterns', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'string',
|
|
pattern: '^a',
|
|
},
|
|
},
|
|
};
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 'abc' }, schema)
|
|
).not.toThrow();
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 'xyz' }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 'xyz' should match the pattern '^a'."`
|
|
);
|
|
});
|
|
|
|
it('should handle validating minLength', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'string',
|
|
minLength: 2,
|
|
},
|
|
},
|
|
};
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 'a' }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 'a' (1 character(s)) should have at least 2 character(s)."`
|
|
);
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 'abc' }, schema)
|
|
).not.toThrow();
|
|
});
|
|
it('should handle validating maxLength', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'string',
|
|
pattern: '^a',
|
|
},
|
|
},
|
|
};
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 'abc' }, schema)
|
|
).not.toThrow();
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 'xyz' }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 'xyz' should match the pattern '^a'."`
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('number', () => {
|
|
it('should handle validating multiples of', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'number',
|
|
multipleOf: 3,
|
|
},
|
|
},
|
|
};
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 6 }, schema)
|
|
).not.toThrow();
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 5 }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 5 should be a multiple of 3."`
|
|
);
|
|
});
|
|
|
|
it('should handle validating minimum', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'number',
|
|
minimum: 3,
|
|
},
|
|
},
|
|
};
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 2 }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 2 should be at least 3"`
|
|
);
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 3 }, schema)
|
|
).not.toThrow();
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 4 }, schema)
|
|
).not.toThrow();
|
|
});
|
|
|
|
it('should handle validating exclusive minimum', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'number',
|
|
exclusiveMinimum: 3,
|
|
},
|
|
},
|
|
};
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 2 }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 2 should be greater than 3"`
|
|
);
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 3 }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 3 should be greater than 3"`
|
|
);
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 4 }, schema)
|
|
).not.toThrow();
|
|
});
|
|
|
|
it('should handle validating maximum', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'number',
|
|
maximum: 3,
|
|
},
|
|
},
|
|
};
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 2 }, schema)
|
|
).not.toThrow();
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 3 }, schema)
|
|
).not.toThrow();
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 4 }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 4 should be at most 3"`
|
|
);
|
|
});
|
|
|
|
it('should handle vavlidating exclusive maximum', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'number',
|
|
exclusiveMaximum: 3,
|
|
},
|
|
},
|
|
};
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 2 }, schema)
|
|
).not.toThrow();
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 3 }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 3 should be less than 3"`
|
|
);
|
|
expect(() =>
|
|
validateOptsAgainstSchema({ a: 4 }, schema)
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Property 'a' does not match the schema. 4 should be less than 3"`
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should handle one of', () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: 'string' },
|
|
{
|
|
properties: {
|
|
a: {
|
|
oneOf: [
|
|
{
|
|
type: 'string',
|
|
},
|
|
{
|
|
type: 'boolean',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).not.toThrow();
|
|
});
|
|
|
|
it('should handle oneOf with factorized type', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'number',
|
|
oneOf: [{ multipleOf: 5 }, { multipleOf: 3 }],
|
|
},
|
|
},
|
|
};
|
|
expect(() => validateOptsAgainstSchema({ a: 10 }, schema)).not.toThrow();
|
|
expect(() => validateOptsAgainstSchema({ a: 15 }, schema)).toThrow();
|
|
});
|
|
|
|
it('should handle anyOf', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'number',
|
|
anyOf: [{ multipleOf: 5 }, { multipleOf: 3 }],
|
|
},
|
|
},
|
|
};
|
|
expect(() => validateOptsAgainstSchema({ a: 4 }, schema)).toThrow();
|
|
expect(() => validateOptsAgainstSchema({ a: 10 }, schema)).not.toThrow();
|
|
expect(() => validateOptsAgainstSchema({ a: 15 }, schema)).not.toThrow();
|
|
});
|
|
|
|
it('should handle all of', () => {
|
|
const schema = {
|
|
properties: {
|
|
a: {
|
|
type: 'number',
|
|
allOf: [{ multipleOf: 5 }, { multipleOf: 3 }],
|
|
},
|
|
},
|
|
};
|
|
expect(() => validateOptsAgainstSchema({ a: 4 }, schema)).toThrow();
|
|
expect(() => validateOptsAgainstSchema({ a: 10 }, schema)).toThrow();
|
|
expect(() => validateOptsAgainstSchema({ a: 15 }, schema)).not.toThrow();
|
|
});
|
|
|
|
it('should handle one of with string lengths', () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: 'nrwl' },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'string',
|
|
oneOf: [
|
|
{
|
|
maxLength: 0,
|
|
},
|
|
{
|
|
minLength: 1,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).not.toThrow();
|
|
});
|
|
|
|
it('should handle oneOf properties explicit types', () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: true },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'number',
|
|
oneOf: [{ type: 'string' }, { type: 'boolean' }],
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).not.toThrow();
|
|
});
|
|
|
|
it('should handle oneOf properties with enums', () => {
|
|
// matching enum value
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: 'inspect' },
|
|
{
|
|
properties: {
|
|
a: {
|
|
oneOf: [
|
|
{
|
|
type: 'string',
|
|
enum: ['inspect', 'inspect-brk'],
|
|
},
|
|
{
|
|
type: 'boolean',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).not.toThrow();
|
|
|
|
// matching oneOf value
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: true },
|
|
{
|
|
properties: {
|
|
a: {
|
|
oneOf: [
|
|
{
|
|
type: 'string',
|
|
enum: ['inspect', 'inspect-brk'],
|
|
},
|
|
{
|
|
type: 'boolean',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).not.toThrow();
|
|
|
|
// non-matching enum value
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: 'abc' },
|
|
{
|
|
properties: {
|
|
a: {
|
|
oneOf: [
|
|
{
|
|
type: 'string',
|
|
enum: ['inspect', 'inspect-brk'],
|
|
},
|
|
{
|
|
type: 'boolean',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).toThrow();
|
|
});
|
|
|
|
it("should throw if the type doesn't match (arrays)", () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: ['string', 123] },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string',
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).toThrow(
|
|
"Property 'a' does not match the schema. '123' should be a 'string'."
|
|
);
|
|
});
|
|
|
|
it("should throw if the type doesn't match (objects)", () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ a: { key: 'string' } },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'object',
|
|
properties: {
|
|
key: {
|
|
type: 'boolean',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).toThrow(
|
|
"Property 'key' does not match the schema. 'string' should be a 'boolean'."
|
|
);
|
|
});
|
|
|
|
it('should resolve types using refs', () => {
|
|
expect(() =>
|
|
validateOptsAgainstSchema(
|
|
{ key: 'string' },
|
|
{
|
|
properties: {
|
|
key: {
|
|
$ref: '#/definitions/key',
|
|
},
|
|
},
|
|
definitions: {
|
|
key: {
|
|
type: 'boolean',
|
|
},
|
|
},
|
|
}
|
|
)
|
|
).toThrow(
|
|
"Property 'key' does not match the schema. 'string' should be a 'boolean'."
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('warnDeprecations', () => {
|
|
beforeEach(() => {
|
|
jest.spyOn(logger, 'warn').mockImplementation(() => {});
|
|
});
|
|
|
|
it('should not log a warning when an option marked as deprecated is not specified', () => {
|
|
warnDeprecations(
|
|
{ b: true },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'boolean',
|
|
'x-deprecated': true,
|
|
},
|
|
b: {
|
|
type: 'boolean',
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(logger.warn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should log a warning when an option marked as deprecated is specified', () => {
|
|
warnDeprecations(
|
|
{ a: true },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'boolean',
|
|
'x-deprecated': true,
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith('Option "a" is deprecated.');
|
|
});
|
|
|
|
it('should log a warning with the deprecation notice when x-deprecated is a string', () => {
|
|
warnDeprecations(
|
|
{ a: true },
|
|
{
|
|
properties: {
|
|
a: {
|
|
type: 'boolean',
|
|
'x-deprecated':
|
|
'Deprecated since version x.x.x. Use "b" instead.',
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
'Option "a" is deprecated: Deprecated since version x.x.x. Use "b" instead.'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('applyVerbosity', () => {
|
|
const isVerbose = true;
|
|
|
|
it('should not apply verbose if additionalProperties is false and verbose is not in schema', () => {
|
|
const options = {};
|
|
applyVerbosity(
|
|
options,
|
|
{ additionalProperties: false, properties: {} },
|
|
isVerbose
|
|
);
|
|
|
|
expect(options).toEqual({});
|
|
});
|
|
|
|
it('should apply verbose if additionalProperties is true and isVerbose is truthy', () => {
|
|
const options = {};
|
|
applyVerbosity(
|
|
options,
|
|
{ additionalProperties: true, properties: {} },
|
|
isVerbose
|
|
);
|
|
expect(options).toEqual({ verbose: isVerbose });
|
|
});
|
|
|
|
it('should apply verbose if additionalProperties is false but verbose is in schema and isVerbose is truthy', () => {
|
|
const options = {};
|
|
applyVerbosity(
|
|
options,
|
|
{
|
|
additionalProperties: false,
|
|
properties: { verbose: {} },
|
|
},
|
|
isVerbose
|
|
);
|
|
expect(options).toEqual({ verbose: isVerbose });
|
|
});
|
|
|
|
it('should not apply verbose if isVerbose is falsy', () => {
|
|
const options = {};
|
|
applyVerbosity(
|
|
options,
|
|
{
|
|
additionalProperties: false,
|
|
properties: { verbose: {} },
|
|
},
|
|
false
|
|
);
|
|
expect(options).toEqual({});
|
|
});
|
|
});
|
|
});
|