json-schema-remote
Version:
Node.js module to validate JSON objects against a JSON Schema, including remote references ($ref)
677 lines (659 loc) • 20.5 kB
JavaScript
/* eslint-disable no-unused-expressions */
const chai = require('chai');
const { expect } = chai;
const localPackageJsonSchema = require('../schema/package.json');
const validator = require('../');
describe('basic check of testing library', () => {
it('assert that JavaScript is still a little crazy', () => {
expect([] + []).to.equal('');
});
});
describe('promise style', () => {
describe('local schema and data', () => {
it('valid data', () => {
const data = { intKey: 1, stringKey: 'one' };
const schema = {
properties: {
intKey: { type: 'integer' },
stringKey: { type: 'string' },
},
};
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.be.true;
});
});
it('invalid data', () => {
const data = { intKey: 1, stringKey: false };
const schema = {
properties: {
intKey: { type: 'integer' },
stringKey: { type: 'string' },
},
};
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.not.be.true;
})
.catch((error) => {
expect(error).to.have.nested.property('errors.0.message', 'Invalid type: boolean (expected string)');
return Promise.resolve();
});
});
it('definition path invalid', () => {
const data = {
b: {},
};
const schema = {
$schema: 'http://json-schema.org/draft-04/schema#',
id: 'https://example.com/schema',
type: 'object',
properties: {
b: {
$ref: '#/definitions/plainError',
definitions: {
plainError: {
type: 'object',
},
},
},
},
};
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.not.be.true;
})
.catch((error) => {
expect(error).to.have.property('message', 'json-schema-remote: tv4 is missing schema it already has in cache. possible faulty schema.');
return Promise.resolve();
});
});
it('string data', () => {
const data = 'hello';
const schema = { type: 'string' };
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.be.true;
});
});
it('19.99', () => {
const data = 19.99;
const schema = { type: 'number', multipleOf: 0.01 };
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.be.true;
});
});
});
describe('remote schema and local data', () => {
it('valid data', () => {
const data = { latitude: 48.778611, longitude: 9.179749 };
const schema = 'https://entrecode.de/schema/geo';
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.be.true;
});
});
it('invalid data', () => {
const data = { latitude: 48.778611, longitude: '9.179749' };
const schema = 'https://entrecode.de/schema/geo';
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.not.be.true;
})
.catch((error) => {
expect(error).to.have.nested.property('errors.0.message', 'Invalid type: string (expected number)');
return Promise.resolve();
});
});
it.skip('remote schema with remote $refs', () => {
const data = {
collection: {
version: '1.0',
href: 'http://example.org/friends/',
links: [
{
rel: 'feed',
href: 'http://example.org/friends/rss',
},
],
items: [
{
href: 'http://example.org/friends/jdoe',
data: [
{
name: 'full-name',
value: 'J. Doe',
prompt: 'Full Name',
},
{
name: 'email',
value: 'jdoe@example.org',
prompt: 'Email',
},
],
links: [
{
rel: 'blog',
href: 'http://examples.org/blogs/jdoe',
prompt: 'Blog',
},
{
rel: 'avatar',
href: 'http://examples.org/images/jdoe',
prompt: 'Avatar',
render: 'image',
},
],
},
{
href: 'http://example.org/friends/msmith',
data: [
{
name: 'full-name',
value: 'M. Smith',
prompt: 'Full Name',
},
{
name: 'email',
value: 'msmith@example.org',
prompt: 'Email',
},
],
links: [
{
rel: 'blog',
href: 'http://examples.org/blogs/msmith',
prompt: 'Blog',
},
{
rel: 'avatar',
href: 'http://examples.org/images/msmith',
prompt: 'Avatar',
render: 'image',
},
],
},
{
href: 'http://example.org/friends/rwilliams',
data: [
{
name: 'full-name',
value: 'R. Williams',
prompt: 'Full Name',
},
{
name: 'email',
value: 'rwilliams@example.org',
prompt: 'Email',
},
],
links: [
{
rel: 'blog',
href: 'http://examples.org/blogs/rwilliams',
prompt: 'Blog',
},
{
rel: 'avatar',
href: 'http://examples.org/images/rwilliams',
prompt: 'Avatar',
render: 'image',
},
],
},
],
queries: [
{
rel: 'search',
href: 'http://example.org/friends/search',
prompt: 'Search',
data: [
{
name: 'search',
value: '',
},
],
},
],
template: {
data: [
{
name: 'full-name',
value: '',
prompt: 'Full Name',
},
{
name: 'email',
value: '',
prompt: 'Email',
},
{
name: 'blog',
value: '',
prompt: 'Blog',
},
{
name: 'avatar',
value: '',
prompt: 'Avatar',
},
],
},
},
};
const schema = 'http://hyperschema.org/mediatypes/collection-json';
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.be.true;
});
});
});
describe('local schema and remote data', () => {
it('valid data', () => {
const data = 'https://raw.githubusercontent.com/geraintluff/tv4/master/package.json';
const schema = localPackageJsonSchema;
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.be.true;
});
});
it('invalid data', () => {
const data = 'https://entrecode.de/schema/hal';
const schema = localPackageJsonSchema;
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.not.be.true;
})
.catch((error) => {
expect(error).to.have.nested.property('errors.0.message', 'Missing required property: name');
return Promise.resolve();
});
});
});
describe('remote schema and data', () => {
it('valid data', () => {
const data = 'https://raw.githubusercontent.com/geraintluff/tv4/master/package.json';
const schema = 'https://gitlab.com/mjkaye/hal-json-schema/raw/master/hal-schema.json';
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.be.true;
});
});
it('paralell requests', () => {
const data = 'https://raw.githubusercontent.com/geraintluff/tv4/master/package.json';
const schema = 'https://gitlab.com/mjkaye/hal-json-schema/raw/master/hal-schema.json';
const promise = () => validator.validate(data, schema);
return Promise.all([
promise(),
promise(),
])
.then((results) => {
expect(results[0]).to.be.true;
expect(results[1]).to.be.true;
});
});
it.skip('invalid data', () => {
const data = 'https://entrecode.de/schema/hal';
const schema = 'http://json.schemastore.org/package';
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.not.be.true;
})
.catch((error) => {
expect(error).to.have.nested.property('errors.0.message', 'Data does not match any schemas from "anyOf"');
return Promise.resolve();
});
});
});
describe('preload remote schema', () => {
before(() => validator.preload({
$schema: 'http://json-schema.org/draft-04/schema#',
id: 'https://nonexistent.tld/some/schema',
type: 'string',
}));
it('validate (passing)', () => {
const data = { x: 'string' };
const schema = {
type: 'object',
properties: {
x: { $ref: 'https://nonexistent.tld/some/schema' },
},
};
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.be.true;
});
});
it('validate (not passing)', () => {
const data = { x: 0 };
const schema = {
type: 'object',
properties: {
x: { $ref: 'https://nonexistent.tld/some/schema' },
},
};
return validator.validate(data, schema)
.then((isValid) => {
expect(isValid).to.not.be.true;
})
.catch((error) => {
expect(error).to.have.nested.property('errors.0.message', 'Invalid type: number (expected string)');
return Promise.resolve();
});
});
});
describe('get schema from cache', () => {
before(() => validator.preload({
$schema: 'http://json-schema.org/draft-04/schema#',
id: 'https://nonexistent.tld/some/schema',
type: 'string',
}));
it('load schema from cache', () => {
const schema = validator.getSchema('https://nonexistent.tld/some/schema');
expect(schema).to.have.property('type', 'string');
});
it('no cached version', () => {
const schema = validator.getSchema('https://nonexistent.tld/no/schema');
expect(schema).to.be.undefined;
});
});
});
after(() => {
// this is not ideal, should be a proper test but it must run as last test
validator.preload({
$schema: 'http://json-schema.org/draft-04/schema#',
id: 'https://nonexistent.tld/some/schema',
type: 'string',
});
const shouldExist = validator.getSchema('https://nonexistent.tld/some/schema');
expect(shouldExist).to.have.property('type', 'string');
validator.dropSchemas();
const shouldNotExist = validator.getSchema('https://nonexistent.tld/some/schema');
expect(shouldNotExist).to.be.undefined;
});
describe('callback style', () => {
describe('local schema and data', () => {
it('valid data', (done) => {
const data = { intKey: 1, stringKey: 'one' };
const schema = {
properties: {
intKey: { type: 'integer' },
stringKey: { type: 'string' },
},
};
validator.validate(data, schema, (error, isValid) => {
if (error) {
done(error);
}
expect(isValid).to.be.true;
done();
});
});
it('invalid data', (done) => {
const data = { intKey: 1, stringKey: false };
const schema = {
properties: {
intKey: { type: 'integer' },
stringKey: { type: 'string' },
},
};
validator.validate(data, schema, (error, isValid) => {
expect(isValid).to.not.be.true;
expect(error).to.have.nested.property('errors.0.message', 'Invalid type: boolean (expected string)');
done();
});
});
});
describe('remote schema and local data', () => {
it('valid data', (done) => {
const data = { latitude: 48.778611, longitude: 9.179749 };
const schema = 'https://entrecode.de/schema/geo';
validator.validate(data, schema, (error, isValid) => {
if (error) {
done(error);
}
expect(isValid).to.be.true;
done();
});
});
it('invalid data', (done) => {
const data = { latitude: 48.778611, longitude: '9.179749' };
const schema = 'https://entrecode.de/schema/geo';
validator.validate(data, schema, (error, isValid) => {
expect(isValid).to.not.be.true;
expect(error).to.have.nested.property('errors.0.message', 'Invalid type: string (expected number)');
done();
});
});
it.skip('remote schema with remote $refs', (done) => {
const data = {
collection: {
version: '1.0',
href: 'http://example.org/friends/',
links: [
{
rel: 'feed',
href: 'http://example.org/friends/rss',
},
],
items: [
{
href: 'http://example.org/friends/jdoe',
data: [
{
name: 'full-name',
value: 'J. Doe',
prompt: 'Full Name',
},
{
name: 'email',
value: 'jdoe@example.org',
prompt: 'Email',
},
],
links: [
{
rel: 'blog',
href: 'http://examples.org/blogs/jdoe',
prompt: 'Blog',
},
{
rel: 'avatar',
href: 'http://examples.org/images/jdoe',
prompt: 'Avatar',
render: 'image',
},
],
},
{
href: 'http://example.org/friends/msmith',
data: [
{
name: 'full-name',
value: 'M. Smith',
prompt: 'Full Name',
},
{
name: 'email',
value: 'msmith@example.org',
prompt: 'Email',
},
],
links: [
{
rel: 'blog',
href: 'http://examples.org/blogs/msmith',
prompt: 'Blog',
},
{
rel: 'avatar',
href: 'http://examples.org/images/msmith',
prompt: 'Avatar',
render: 'image',
},
],
},
{
href: 'http://example.org/friends/rwilliams',
data: [
{
name: 'full-name',
value: 'R. Williams',
prompt: 'Full Name',
},
{
name: 'email',
value: 'rwilliams@example.org',
prompt: 'Email',
},
],
links: [
{
rel: 'blog',
href: 'http://examples.org/blogs/rwilliams',
prompt: 'Blog',
},
{
rel: 'avatar',
href: 'http://examples.org/images/rwilliams',
prompt: 'Avatar',
render: 'image',
},
],
},
],
queries: [
{
rel: 'search',
href: 'http://example.org/friends/search',
prompt: 'Search',
data: [
{
name: 'search',
value: '',
},
],
},
],
template: {
data: [
{
name: 'full-name',
value: '',
prompt: 'Full Name',
},
{
name: 'email',
value: '',
prompt: 'Email',
},
{
name: 'blog',
value: '',
prompt: 'Blog',
},
{
name: 'avatar',
value: '',
prompt: 'Avatar',
},
],
},
},
};
const schema = 'http://hyperschema.org/mediatypes/collection-json';
validator.validate(data, schema, (error, isValid) => {
if (error) {
done(error);
}
expect(isValid).to.be.true;
done();
});
});
});
describe('local schema and remote data', () => {
it('valid data', (done) => {
const data = 'https://raw.githubusercontent.com/geraintluff/tv4/master/package.json';
const schema = localPackageJsonSchema;
validator.validate(data, schema, (error, isValid) => {
if (error) {
done(error);
}
expect(isValid).to.be.true;
done();
});
});
it('invalid data', (done) => {
const data = 'https://entrecode.de/schema/hal';
const schema = localPackageJsonSchema;
validator.validate(data, schema, (error, isValid) => {
expect(isValid).to.not.be.true;
expect(error).to.have.nested.property('errors.0.message', 'Missing required property: name');
done();
});
});
});
describe('remote schema and data', () => {
it('valid data', (done) => {
const data = 'https://raw.githubusercontent.com/geraintluff/tv4/master/package.json';
const schema = 'https://gitlab.com/mjkaye/hal-json-schema/raw/master/hal-schema.json';
validator.validate(data, schema, (error, isValid) => {
if (error) {
done(error);
}
expect(isValid).to.be.true;
done();
});
});
it.skip('invalid data', (done) => {
const data = 'http://hyperschema.org/mediatypes/hal';
const schema = 'http://json.schemastore.org/package';
validator.validate(data, schema, (error, isValid) => {
expect(isValid).to.not.be.true;
expect(error).to.have.nested.property('errors.0.message', 'Data does not match any schemas from "anyOf"');
done();
});
});
});
describe('preload remote schema', () => {
before((done) => {
validator.preload({
$schema: 'http://json-schema.org/draft-04/schema#',
id: 'https://nonexistent.tld/some/schema',
type: 'string',
});
done();
});
it('validate (passing)', (done) => {
const data = { x: 'string' };
const schema = {
type: 'object',
properties: {
x: { $ref: 'https://nonexistent.tld/some/schema' },
},
};
validator.validate(data, schema, (error, isValid) => {
if (error) {
done(error);
}
expect(isValid).to.be.true;
done();
});
});
it('validate (not passing)', (done) => {
const data = { x: 0 };
const schema = {
type: 'object',
properties: {
x: { $ref: 'https://nonexistent.tld/some/schema' },
},
};
validator.validate(data, schema, (error, isValid) => {
expect(isValid).to.not.be.true;
expect(error).to.have.nested.property('errors.0.message', 'Invalid type: number (expected string)');
done();
});
});
});
});