diff --git a/lib/index.js b/lib/index.js index 0677b63..379f710 100644 --- a/lib/index.js +++ b/lib/index.js @@ -24,6 +24,7 @@ exports.Contract = require('./contract') exports.Blueprint = require('./blueprint') exports.buildTemplate = require('./partials').buildTemplate +exports.checkContract = require('./validation').checkContract exports.query = (universe, layout, skeleton) => { const blueprint = new exports.Blueprint(layout, skeleton) diff --git a/lib/schema/contract.json b/lib/schema/contract.json new file mode 100644 index 0000000..3eaecec --- /dev/null +++ b/lib/schema/contract.json @@ -0,0 +1,190 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "contract.json", + + "definitions": { + "baseContract": { + "$id": "baseContract", + "type": "object", + "additionalProperties": false, + "properties": { + "slug": { + "type": "string", + "pattern": "^[a-z0-9-]+$" + }, + "version": { + "type": "string", + "minLength": 1, + "pattern": "^[~\\^]*\\d+(\\.[~\\^]*\\d+(\\.[~\\^]*\\d+)*)*$" + }, + "componentVersion": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "pattern": "^[a-z0-9-.]+$" + }, + "name": { + "type": "string", + "pattern": "^.*\\S.*$" + }, + "aliases": { + "type": "array", + "additionalItems": false, + "uniqueItems": true, + "items": { + "$ref": "#/properties/slug" + } + }, + "data": { + "type": "object" + }, + "extends": { + "allOf": [ + { + "$ref": "#" + }, + { + "required": [ "slug", "aliases" ] + } + ] + }, + "requires": { + "$ref": "contract.json#/definitions/recursiveOR" + }, + "tags": { + "type": "array", + "uniqueItems": true, + "additionalItems": false, + "items": { + "type": "string", + "pattern": "^[\\S]+(?: [\\S]+)*$" + } + }, + "capabilities": { + "type": "array", + "additionalItems": false, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "slug": { + "$ref": "#/properties/slug" + }, + "componentVersion": { + "$ref": "#/properties/componentVersion" + } + }, + "required": [ "slug" ] + } + }, + "conflicts": { + "$ref": "contract.json#/definitions/recursiveOR" + }, + "assets": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.*$": { + "type": "object", + "properties": { + "url": { + "type": "string", + "pattern": "^[a-z0-9-]+:\/\/(([a-z0-9-.])+\/)*[a-z0-9-.]+$" + }, + "name": { + "type": "string", + "pattern": "[a-z0-9-.]+$" + }, + "checksum": { + "type": "string", + "minLength": 1 + }, + "checksumType": { + "type": "string", + "enum": [ "sha256" ] + } + }, + "required": [ "url" ], + "dependencies": { + "checksum": [ "checksumType" ] + } + } + } + }, + "children": { + "$id": "childNamespace", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.*$": { + "oneOf": [ + { + "$ref": "contract.json" + }, + { + "$ref": "childNamespace" + } + ] + } + } + } + } + }, + + "partialContract": { + "$id": "partialContract", + "type": "object", + "allOf": [ + { + "$ref": "contract.json#/definitions/baseContract" + }, + { + "dependencies": { + "version": [ "slug" ] + } + } + ] + }, + + "recursiveOR": { + "$id": "recursiveOR", + "type": "array", + "additionalItems": false, + "items": { + "anyOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "or": { + "$ref": "recursiveOR" + } + } + }, + { + "$ref": "contract.json#/definitions/partialContract" + } + ] + } + } + }, + + "allOf": [ + { + "$ref": "#/definitions/baseContract" + }, + { + "required": [ + "slug", + "version", + "type", + "componentVersion", + "aliases", + "tags", + "data" + ] + } + ] +} diff --git a/lib/validation.js b/lib/validation.js new file mode 100644 index 0000000..aa5ed68 --- /dev/null +++ b/lib/validation.js @@ -0,0 +1,96 @@ +/* + * Copyright 2017 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const Ajv = require('ajv') +const ajv = new Ajv({ + schemaId: '$id' +}) +const baseContractSchema = require('./schema/contract.json') +ajv.addSchema(baseContractSchema) + +// eslint-disable-next-line jsdoc/require-example +/** + * @summary Merge supplied schema with base contract schema + * @function + * @private + * + * @param {Object} schema - JSON schema + * @returns {Object} - Merged schema + * + */ +const mergeWithBase = (schema) => { + return { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: schema.$id, + allOf: [ + { + $ref: 'contract.json' + }, + schema + ] + } +} + +/** + * @module validation + */ + +/** + * @summary Checks if a contract is valid. + * @function + * @memberof module:validation + * @public + * + * @param {Object} contract - the contract to validate + * @param {Object} schema - optional schema + * @returns {Object} result + * + * @description + * This function will validate that the supplied contract is valid according to + * the json schema specification. Accepts an optional schema that can reference and extend + * the base schema. If a custom schema is passed, both the generic and custom validation + * rules will apply + * + * @example + * validation.checkContract({ + * slug: 'slug', + * type: 'type', + * version: '1', + * componentVersion: 'componentVersion', + * aliases: [], + * data: {}, + * tags: [] + * } + */ +exports.checkContract = (contract, schema) => { + let success = false + if (schema) { + success = ajv.validate(mergeWithBase(schema), contract) + } else { + success = ajv.validate('contract.json', contract) + } + + if (success) { + return { + success: true, errors: [] + } + } + return { + success: false, errors: [ ajv.errorsText() ] + } +} diff --git a/package-lock.json b/package-lock.json index ed0d902..ba76d2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "contrato", - "version": "0.0.4", + "version": "0.0.5", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -104,15 +104,21 @@ } }, "ajv": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.3.tgz", - "integrity": "sha1-wG9Zh3jETGsWGrr+NGa4GtGBTtI=", - "dev": true, + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.0.tgz", + "integrity": "sha512-VDUX1oSajablmiyFyED9L1DFndg0P9h7p1F+NO8FkIzei6EPrR6Zu1n18rd5P8PqaSRd/FrWv3G1TVBqpM83gA==", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" + "uri-js": "4.2.1" + }, + "dependencies": { + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + } } }, "ajv-keywords": { @@ -1500,12 +1506,6 @@ } } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, "co-with-promise": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co-with-promise/-/co-with-promise-4.6.0.tgz", @@ -2035,7 +2035,6 @@ "integrity": "sha1-Ip7w41Tg5h2DfHqA/fuoJeGZgV4=", "dev": true, "requires": { - "ajv": "5.2.3", "babel-code-frame": "6.26.0", "chalk": "2.1.0", "concat-stream": "1.6.0", @@ -2417,18 +2416,17 @@ } } }, - "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "dev": true - }, "fast-diff": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", "integrity": "sha1-S2LEK44D3j+EhGC2OQeZIGldAVQ=", "dev": true }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", @@ -4850,8 +4848,7 @@ "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" }, "json-stable-stringify": { "version": "1.0.1", @@ -7764,6 +7761,11 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=" + }, "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", @@ -8596,7 +8598,6 @@ "integrity": "sha1-ozRHN1OR52atNNNIbm4q7chNLjY=", "dev": true, "requires": { - "ajv": "5.2.3", "ajv-keywords": "2.1.0", "chalk": "2.1.0", "lodash": "4.17.4", @@ -9052,6 +9053,14 @@ "xdg-basedir": "3.0.0" } }, + "uri-js": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.1.tgz", + "integrity": "sha512-jpKCA3HjsBfSDOEgxRDAxQCNyHfCPSbq57PqCkd3gAyBuPb3IWxw54EHncqESznIdqSetHfw3D7ylThu2Kcc9A==", + "requires": { + "punycode": "2.1.0" + } + }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", diff --git a/package.json b/package.json index 27f1c77..c28e5fd 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "concurrency": 5 }, "dependencies": { + "ajv": "^6.5.0", "debug": "^3.0.1", "handlebars": "^4.0.10", "handlebars-helpers": "^0.10.0", diff --git a/test/validation/common/baseContract.json b/test/validation/common/baseContract.json new file mode 100644 index 0000000..c458c1f --- /dev/null +++ b/test/validation/common/baseContract.json @@ -0,0 +1,9 @@ +{ + "slug": "slug", + "type": "type", + "version": "1", + "componentVersion": "componentVersion", + "aliases": [], + "data": {}, + "tags": [] +} diff --git a/test/validation/validate-assets.spec.js b/test/validation/validate-assets.spec.js new file mode 100644 index 0000000..aa00fd4 --- /dev/null +++ b/test/validation/validate-assets.spec.js @@ -0,0 +1,55 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +const assetsContract = _.merge({}, baseContract, { + assets: { + asset: { + url: 'https://test.url', + checksum: 'checksum', + checksumType: 'sha256' + } + } +}) + +ava.test('should validate assets contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(assetsContract) + ) +}) + +ava.test('should reject bad assets contract', (test) => { + const result = validation.checkContract(_.omit(assetsContract, 'assets.asset.url')) + test.is(false, result.success) + test.is('data.assets[\'asset\'] should have required property \'url\'', result.errors[0]) +}) + +ava.test('should reject bad assets contract', (test) => { + const result = validation.checkContract(_.omit(assetsContract, 'assets.asset.checksumType')) + test.is(false, result.success) + test.is('data.assets[\'asset\'] should have property checksumType when property checksum is present', result.errors[0]) +}) diff --git a/test/validation/validate-base-contracts.spec.js b/test/validation/validate-base-contracts.spec.js new file mode 100644 index 0000000..eace715 --- /dev/null +++ b/test/validation/validate-base-contracts.spec.js @@ -0,0 +1,39 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +ava.test('should validate base contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(baseContract) + ) +}) + +ava.test('should reject invalid base contract', (test) => { + const result = validation.checkContract(_.omit(baseContract, 'slug')) + test.is(false, result.success) + test.is('data should have required property \'.slug\'', result.errors[0]) +}) diff --git a/test/validation/validate-capabilities.spec.js b/test/validation/validate-capabilities.spec.js new file mode 100644 index 0000000..83df8fb --- /dev/null +++ b/test/validation/validate-capabilities.spec.js @@ -0,0 +1,59 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +const capabilitiesContract = _.merge({}, baseContract, { + capabilities: [ + { + slug: 'slug', + componentVersion: 'componentVersion' + }, + { + slug: 'slug' + } + ] +}) + +const badCapabilitiesContract = _.merge({}, baseContract, { + capabilities: [ + { + componentVersion: 'componentVersion' + } + ] +}) + +ava.test('should validate capabilities contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(capabilitiesContract) + ) +}) + +ava.test('should reject bad capabilities contract', (test) => { + const result = validation.checkContract(badCapabilitiesContract) + test.is(false, result.success) + test.is('data.capabilities[0] should have required property \'slug\'', result.errors[0]) +}) diff --git a/test/validation/validate-children.spec.js b/test/validation/validate-children.spec.js new file mode 100644 index 0000000..2eef409 --- /dev/null +++ b/test/validation/validate-children.spec.js @@ -0,0 +1,73 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +const fatherContract = _.merge({}, baseContract, { + children: { + base: baseContract + } +}) + +const grampaContract = _.merge({}, baseContract, { + children: { + father: { + child1: baseContract, + child2: baseContract + } + } +}) + +const badFamilyContract = _.merge({}, baseContract, { + children: { + father: { + child: { + prop: true + } + } + } +}) + +ava.test('should validate father contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(fatherContract) + ) +}) + +ava.test('should validate grampa contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(grampaContract) + ) +}) + +ava.test('should reject bad family contract', (test) => { + const result = validation.checkContract(badFamilyContract) + test.is(false, result.success) +}) diff --git a/test/validation/validate-conflicts.spec.js b/test/validation/validate-conflicts.spec.js new file mode 100644 index 0000000..b37642f --- /dev/null +++ b/test/validation/validate-conflicts.spec.js @@ -0,0 +1,58 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +const conflictsContract = _.merge({}, baseContract, { + conflicts: [ + { + slug: 'slug', + version: '1' + }, + { + slug: 'slug' + } + ] +}) + +const badConflictsContract = _.merge({}, baseContract, { + conflicts: [ + { + prop: 'prop' + } + ] +}) + +ava.test('should validate conflicts contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(conflictsContract) + ) +}) + +ava.test('should reject bad conflicts contract', (test) => { + const result = validation.checkContract(badConflictsContract) + test.is(false, result.success) +}) diff --git a/test/validation/validate-extended-schema.spec.js b/test/validation/validate-extended-schema.spec.js new file mode 100644 index 0000000..3738ae7 --- /dev/null +++ b/test/validation/validate-extended-schema.spec.js @@ -0,0 +1,66 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +const extendedSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'extended.schema', + type: 'object', + properties: { + data: { + type: 'object', + properties: { + test: { + type: 'string' + }, + optional: { + type: 'string' + } + }, + required: [ 'test' ] + } + } +} + +const extendedContract = _.merge({}, baseContract, { + data: + { + test: 'test' + } +}) + +ava.test('should validate extended contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(extendedContract, extendedSchema) + ) +}) + +ava.test('Should reject invalid extended contract', (test) => { + const result = validation.checkContract(baseContract, extendedSchema) + test.is(false, result.success) + test.is('data.data should have required property \'test\'', result.errors[0]) +}) diff --git a/test/validation/validate-overlapping-schema.spec.js b/test/validation/validate-overlapping-schema.spec.js new file mode 100644 index 0000000..fef682a --- /dev/null +++ b/test/validation/validate-overlapping-schema.spec.js @@ -0,0 +1,72 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +const overlappingSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'overlapping.schema', + type: 'object', + properties: { + data: { + type: 'object', + properties: { + test: { + type: 'string' + }, + optional: { + type: 'string' + } + }, + required: [ 'test' ] + } + }, + required: [ 'data' ] +} + +const overlappingContract = _.merge({}, baseContract, { + data: { + test: 'test' + } +}) + +ava.test('should validate overlapping contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(overlappingContract, overlappingSchema) + ) +}) + +ava.test('Should reject invalid overlapping contract', (test) => { + const result = validation.checkContract(baseContract, overlappingSchema) + test.is(false, result.success) + test.is('data.data should have required property \'test\'', result.errors[0]) +}) + +ava.test('Should reject invalid overlapping contract', (test) => { + const result = validation.checkContract(_.omit(overlappingContract, 'data.test'), overlappingSchema) + test.is(false, result.success) + test.is('data.data should have required property \'test\'', result.errors[0]) +}) diff --git a/test/validation/validate-requires.spec.js b/test/validation/validate-requires.spec.js new file mode 100644 index 0000000..54b3e10 --- /dev/null +++ b/test/validation/validate-requires.spec.js @@ -0,0 +1,112 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +const requireContract = _.merge({}, baseContract, { + requires: [ + { + or: [ + { + type: 'type' + }, + { + or: [ + { + slug: 'slug' + } + ] + } + ] + } + ] +}) + +const externalRequireContract = _.merge({}, baseContract, { + requires: [ + { + data: { + prop: 'prop' + } + } + ] +}) + +const badExternalRequireContract = _.merge({}, baseContract, { + requires: [ + { + or: [ + { + type: 'type' + }, + { + or: [ + { + prop: 'prop' + } + ] + } + ] + } + ] +}) + +const badExternalRequireContract2 = _.merge({}, baseContract, { + requires: [ + { + or: [ + { + version: '1' + } + ] + } + ] +}) + +ava.test('should validate require contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(requireContract) + ) +}) + +ava.test('should validate require contract with unknown fields', (test) => { + test.deepEqual( + { + success: true, errors: [] + }, + validation.checkContract(externalRequireContract) + ) +}) + +ava.test('should reject bad require contract with unknown fields', (test) => { + const result = validation.checkContract(badExternalRequireContract) + test.is(false, result.success) +}) + +ava.test('should reject bad require contract that only specifies version', (test) => { + const result = validation.checkContract(badExternalRequireContract2) + test.is(false, result.success) +}) diff --git a/test/validation/validate-self-referencing-contract.spec.js b/test/validation/validate-self-referencing-contract.spec.js new file mode 100644 index 0000000..1e2c181 --- /dev/null +++ b/test/validation/validate-self-referencing-contract.spec.js @@ -0,0 +1,56 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +const referencingSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + $id: 'referencing.schema', + type: 'object', + properties: { + data: + { + $ref: 'contract.json' + } + }, + required: [ 'data' ] +} + +const referencingContract = _.merge({}, baseContract, { + data: baseContract +}) + +ava.test('should validate self referencing contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(referencingContract, referencingSchema) + ) +}) + +ava.test('Should reject invalid self referencing contract', (test) => { + const result = validation.checkContract(_.omit(referencingContract, 'data.slug'), referencingSchema) + test.is(false, result.success) + test.is('data.data should have required property \'.slug\'', result.errors[0]) +}) diff --git a/test/validation/validate-tags.spec.js b/test/validation/validate-tags.spec.js new file mode 100644 index 0000000..91010f7 --- /dev/null +++ b/test/validation/validate-tags.spec.js @@ -0,0 +1,53 @@ +/* + * Copyright 2018 resin.io + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict' + +const ava = require('ava') +const _ = require('lodash') +const validation = require('../../lib/validation') + +const baseContract = require('./common/baseContract') + +const taggedContract = _.merge({}, baseContract, { + tags: [ 'valid' ] +}) + +ava.test('Should validate tagged contract', (test) => { + test.deepEqual( + { + success: true, + errors: [] + }, + validation.checkContract(taggedContract) + ) +}) + +ava.test('Should reject invalid tagged contract', (test) => { + taggedContract.tags.push('valid') + + const result = validation.checkContract(taggedContract) + test.is(false, result.success) + test.is('data.tags should NOT have duplicate items (items ## 1 and 0 are identical)', result.errors[0]) +}) + +ava.test('Should reject invalid tagged contract', (test) => { + taggedContract.tags.push(' non valid ') + + const result = validation.checkContract(taggedContract) + test.is(false, result.success) + test.is('data.tags[2] should match pattern "^[\\S]+(?: [\\S]+)*$"', result.errors[0]) +})