2019-11-02 03:02:25 +00:00
|
|
|
import { expect } from 'chai';
|
2019-11-11 03:52:56 +00:00
|
|
|
import { LogLevel, NullLogger } from 'noicejs';
|
2019-11-02 03:02:25 +00:00
|
|
|
import { stub } from 'sinon';
|
|
|
|
|
2019-11-03 19:42:43 +00:00
|
|
|
import { friendlyError, SchemaRule } from '../../src/rule/SchemaRule';
|
2019-11-02 03:02:25 +00:00
|
|
|
import { VisitorContext } from '../../src/visitor/VisitorContext';
|
|
|
|
import { describeLeaks, itLeaks } from '../helpers/async';
|
|
|
|
|
2019-11-09 23:41:55 +00:00
|
|
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
|
|
|
2019-11-02 03:02:25 +00:00
|
|
|
const TEST_NAME = 'test-rule';
|
|
|
|
|
|
|
|
describeLeaks('schema rule', async () => {
|
2019-11-03 19:05:11 +00:00
|
|
|
itLeaks('should pick items from the scope', async () => {
|
|
|
|
const ctx = new VisitorContext({
|
2019-11-12 14:25:09 +00:00
|
|
|
logger: NullLogger.global,
|
|
|
|
schemaOptions: {
|
2019-11-03 19:05:11 +00:00
|
|
|
coerce: false,
|
|
|
|
defaults: false,
|
|
|
|
mutate: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const data = {
|
|
|
|
foo: 3,
|
|
|
|
};
|
|
|
|
const rule = new SchemaRule({
|
|
|
|
check: {},
|
|
|
|
desc: '',
|
2019-11-11 03:52:56 +00:00
|
|
|
level: LogLevel.Info,
|
2019-11-03 19:05:11 +00:00
|
|
|
name: 'foo',
|
|
|
|
select: '$.foo',
|
|
|
|
tags: [],
|
|
|
|
});
|
|
|
|
const results = await rule.pick(ctx, data);
|
|
|
|
|
|
|
|
expect(results).to.deep.equal([data.foo]);
|
|
|
|
});
|
|
|
|
|
2019-11-14 05:38:41 +00:00
|
|
|
itLeaks('should pick no items', async () => {
|
|
|
|
const ctx = new VisitorContext({
|
|
|
|
logger: NullLogger.global,
|
|
|
|
schemaOptions: {
|
|
|
|
coerce: false,
|
|
|
|
defaults: false,
|
|
|
|
mutate: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const data = {
|
|
|
|
bar: 3,
|
|
|
|
};
|
|
|
|
const rule = new SchemaRule({
|
|
|
|
check: {},
|
|
|
|
desc: '',
|
|
|
|
level: LogLevel.Info,
|
|
|
|
name: 'foo',
|
|
|
|
select: '$.foo',
|
|
|
|
tags: [],
|
|
|
|
});
|
|
|
|
const results = await rule.pick(ctx, data);
|
|
|
|
|
|
|
|
expect(results).to.deep.equal([]);
|
|
|
|
});
|
|
|
|
|
2019-11-03 19:05:11 +00:00
|
|
|
itLeaks('should filter out items', async () => {
|
|
|
|
const ctx = new VisitorContext({
|
2019-11-12 14:25:09 +00:00
|
|
|
logger: NullLogger.global,
|
|
|
|
schemaOptions: {
|
2019-11-03 19:05:11 +00:00
|
|
|
coerce: false,
|
|
|
|
defaults: false,
|
|
|
|
mutate: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const data = {
|
|
|
|
foo: 3,
|
|
|
|
};
|
|
|
|
const rule = new SchemaRule({
|
|
|
|
check: {},
|
|
|
|
desc: '',
|
|
|
|
filter: {
|
|
|
|
properties: {
|
|
|
|
foo: {
|
|
|
|
type: 'number',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
type: 'object',
|
|
|
|
},
|
2019-11-11 03:52:56 +00:00
|
|
|
level: LogLevel.Info,
|
2019-11-03 19:05:11 +00:00
|
|
|
name: 'foo',
|
|
|
|
select: '$.foo',
|
|
|
|
tags: [],
|
|
|
|
});
|
|
|
|
|
|
|
|
const results = await rule.visit(ctx, data);
|
|
|
|
expect(results.errors.length).to.equal(0);
|
|
|
|
});
|
|
|
|
|
2019-11-02 03:02:25 +00:00
|
|
|
itLeaks('should pick items from the root', async () => {
|
|
|
|
const ctx = new VisitorContext({
|
2019-11-12 14:25:09 +00:00
|
|
|
logger: NullLogger.global,
|
|
|
|
schemaOptions: {
|
2019-11-02 03:02:25 +00:00
|
|
|
coerce: false,
|
|
|
|
defaults: false,
|
|
|
|
mutate: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const rule = new SchemaRule({
|
|
|
|
check: undefined,
|
|
|
|
desc: TEST_NAME,
|
2019-11-11 03:52:56 +00:00
|
|
|
level: LogLevel.Info,
|
2019-11-02 03:02:25 +00:00
|
|
|
name: TEST_NAME,
|
|
|
|
select: '$.foo',
|
|
|
|
tags: [],
|
|
|
|
});
|
|
|
|
const results = await rule.pick(ctx, {
|
|
|
|
foo: [1, 2, 3],
|
|
|
|
});
|
|
|
|
expect(Array.isArray(results)).to.equal(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
itLeaks('should visit selected items', async () => {
|
|
|
|
const ctx = new VisitorContext({
|
2019-11-12 14:25:09 +00:00
|
|
|
logger: NullLogger.global,
|
|
|
|
schemaOptions: {
|
2019-11-02 03:02:25 +00:00
|
|
|
coerce: false,
|
|
|
|
defaults: false,
|
|
|
|
mutate: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const check = {};
|
|
|
|
const checkSpy = stub().returns(true);
|
|
|
|
const filter = {};
|
|
|
|
const filterSpy = stub().returns(true);
|
|
|
|
ctx.compile = stub().onFirstCall().returns(checkSpy).onSecondCall().returns(filterSpy);
|
|
|
|
|
|
|
|
const rule = new SchemaRule({
|
|
|
|
check,
|
|
|
|
desc: TEST_NAME,
|
|
|
|
filter,
|
2019-11-11 03:52:56 +00:00
|
|
|
level: LogLevel.Info,
|
2019-11-02 03:02:25 +00:00
|
|
|
name: TEST_NAME,
|
|
|
|
select: '$.foo',
|
|
|
|
tags: [],
|
|
|
|
});
|
|
|
|
|
|
|
|
const data = {};
|
|
|
|
await rule.visit(ctx, data);
|
|
|
|
|
|
|
|
expect(filterSpy, 'filter spy should have been called with data').to.have.callCount(1).and.been.calledWithExactly(data);
|
|
|
|
expect(checkSpy, 'check spy should have been called with data').to.have.callCount(1).and.been.calledWithExactly(data);
|
|
|
|
});
|
|
|
|
|
|
|
|
itLeaks('should skip filtered items', async () => {
|
|
|
|
const ctx = new VisitorContext({
|
2019-11-12 14:25:09 +00:00
|
|
|
logger: NullLogger.global,
|
|
|
|
schemaOptions: {
|
2019-11-02 03:02:25 +00:00
|
|
|
coerce: false,
|
|
|
|
defaults: false,
|
|
|
|
mutate: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const checkSpy = stub().throws(new Error('check spy error'));
|
|
|
|
const filterSpy = stub().returns(false);
|
|
|
|
ctx.compile = stub().onFirstCall().returns(checkSpy).onSecondCall().returns(filterSpy);
|
|
|
|
|
|
|
|
const rule = new SchemaRule({
|
|
|
|
check: undefined,
|
|
|
|
desc: TEST_NAME,
|
|
|
|
filter: {},
|
2019-11-11 03:52:56 +00:00
|
|
|
level: LogLevel.Info,
|
2019-11-02 03:02:25 +00:00
|
|
|
name: TEST_NAME,
|
|
|
|
select: '$.foo',
|
|
|
|
tags: [],
|
|
|
|
});
|
|
|
|
|
|
|
|
const data = {};
|
|
|
|
await rule.visit(ctx, data);
|
|
|
|
|
|
|
|
expect(filterSpy, 'filter spy should have been called with data').to.have.callCount(1).and.been.calledWithExactly(data);
|
|
|
|
expect(checkSpy, 'check spy should not have been called').to.have.callCount(0);
|
|
|
|
});
|
|
|
|
});
|
2019-11-03 19:42:43 +00:00
|
|
|
|
2019-11-13 13:26:09 +00:00
|
|
|
function createErrorContext() {
|
|
|
|
const rule = new SchemaRule({
|
|
|
|
check: {},
|
|
|
|
desc: TEST_NAME,
|
|
|
|
level: LogLevel.Info,
|
|
|
|
name: TEST_NAME,
|
|
|
|
select: '',
|
|
|
|
tags: [TEST_NAME],
|
|
|
|
});
|
|
|
|
const ctx = new VisitorContext({
|
|
|
|
logger: NullLogger.global,
|
|
|
|
schemaOptions: {
|
|
|
|
coerce: false,
|
|
|
|
defaults: false,
|
|
|
|
mutate: false,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
ctx.visitData = {
|
|
|
|
itemIndex: 0,
|
|
|
|
rule,
|
|
|
|
};
|
|
|
|
|
|
|
|
return { ctx, rule };
|
|
|
|
}
|
|
|
|
|
2019-11-03 19:42:43 +00:00
|
|
|
describe('friendly errors', () => {
|
|
|
|
it('should have a message', () => {
|
2019-11-13 13:26:09 +00:00
|
|
|
const { ctx } = createErrorContext();
|
2019-11-03 22:11:25 +00:00
|
|
|
const err = friendlyError(ctx, {
|
2019-11-03 19:42:43 +00:00
|
|
|
dataPath: 'test-path',
|
|
|
|
keyword: TEST_NAME,
|
|
|
|
params: { /* ? */ },
|
|
|
|
schemaPath: 'test-path',
|
2019-11-03 22:11:25 +00:00
|
|
|
});
|
|
|
|
expect(err.msg).to.include(TEST_NAME);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should handle errors with an existing message', () => {
|
2019-11-13 13:26:09 +00:00
|
|
|
const { ctx } = createErrorContext();
|
2019-11-03 22:11:25 +00:00
|
|
|
const TEST_MESSAGE = 'test-message';
|
|
|
|
const err = friendlyError(ctx, {
|
|
|
|
dataPath: 'test-path',
|
|
|
|
keyword: TEST_NAME,
|
|
|
|
message: TEST_MESSAGE,
|
|
|
|
params: { /* ? */ },
|
|
|
|
schemaPath: 'test-path',
|
|
|
|
});
|
|
|
|
expect(err.msg).to.include(TEST_MESSAGE);
|
2019-11-03 19:42:43 +00:00
|
|
|
});
|
|
|
|
});
|