Skip to content

Commit

Permalink
Parameter value validation helpers (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
itowlson authored Aug 2, 2019
1 parent c2e6ccd commit d6ff730
Show file tree
Hide file tree
Showing 10 changed files with 432 additions and 13 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# top-most EditorConfig file
root = true

# Tab indentation
[*]
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
charset = utf-8
insert_final_newline = true
39 changes: 39 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "cnabjs",
"version": "0.0.1",
"version": "0.0.2",
"description": "A library for loading and working with CNAB (Cloud Native Application Bundle) manifests",
"main": "js/index.js",
"types": "js/index.d.ts",
"main": "js/ts/index.js",
"types": "js/ts/index.d.ts",
"files": [
"js/**/*"
"js/ts/**/*"
],
"scripts": {
"compile": "tsc -p ./",
Expand Down Expand Up @@ -33,5 +33,8 @@
"ts-node": "^8.3.0",
"tslint": "^5.9.1",
"typescript": "^3.5.3"
},
"dependencies": {
"ajv": "^6.10.2"
}
}
8 changes: 0 additions & 8 deletions test/test.ts

This file was deleted.

227 changes: 227 additions & 0 deletions test/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import 'mocha';
import assert from 'assert';

import * as cnab from '../ts/index';
import { Validator, Validity } from '../ts/index';

const TEST_BUNDLE: cnab.Bundle = {
name: 'test',
schemaVersion: 'v1',
version: '1.0.0',
invocationImages: [],

definitions: {
simpleString: { type: 'string' },
lengthyString: { type: 'string', minLength: 4, maxLength: 7 },
simpleInt: { type: 'integer' },
constrainedInt: { type: 'integer', minimum: 1, maximum: 100 },
simpleFloat: { type: 'number' },
simpleBool: { type: 'boolean' },
},

parameters: {
simpleString: { definition: 'simpleString', destination: {} },
lengthyString: { definition: 'lengthyString', destination: { } },
simpleNum: { definition: 'simpleInt', destination: {} },
constrainedNum: { definition: 'constrainedInt', destination: { } },
simpleFloat: { definition: 'simpleFloat', destination: {} },
simpleBool: { definition: 'simpleBool', destination: {} },
noDef: { definition: 'doesnt have one', destination: { } },
},
};

const TEST_VALIDATOR = Validator.for(TEST_BUNDLE);

function expectValid(validity: Validity) {
if (!validity.isValid) {
assert.fail(`should have been valid but '${validity.reason}'`);
}
}

function expectInvalid(validity: Validity) {
if (validity.isValid) {
assert.fail(`should NOT have been valid`);
}
}

describe('a string parameter', () => {

it('should accept a string value', () => {
const validity = TEST_VALIDATOR.validate('simpleString', 'some text');
expectValid(validity);
});

it('should not accept a numeric value', () => {
const validity = TEST_VALIDATOR.validate('simpleString', 123);
expectInvalid(validity);
});

it('should validate against constraints in the schema', () => {
// NOTE: purpose of this is not to exercise JSON Schema - we lean on ajv for that.
// It is solely to confirm that we are successfully passing the schema into ajv.
const validity6 = TEST_VALIDATOR.validate('lengthyString', '6chars');
expectValid(validity6);
const validity3 = TEST_VALIDATOR.validate('lengthyString', '3ch');
expectInvalid(validity3);
const validity12 = TEST_VALIDATOR.validate('lengthyString', '12characters');
expectInvalid(validity12);
});

it('should accept a string value via the text API', () => {
const validity = TEST_VALIDATOR.validateText('simpleString', 'some text');
expectValid(validity);
});

});

describe('an integer parameter', () => {

it('should accept an integer value', () => {
const validity = TEST_VALIDATOR.validate('simpleNum', 123);
expectValid(validity);
});

it('should not accept a value with a fractional part', () => {
const validity = TEST_VALIDATOR.validate('simpleNum', 123.5);
expectInvalid(validity);
});

it('should not accept a string value', () => {
const validity = TEST_VALIDATOR.validate('simpleNum', '123');
expectInvalid(validity);
});

it('should validate against constraints in the schema', () => {
// NOTE: purpose of this is not to exercise JSON Schema - we lean on ajv for that.
// It is solely to confirm that we are successfully passing the schema into ajv.
const validity70 = TEST_VALIDATOR.validate('constrainedNum', 70);
expectValid(validity70);
const validity0 = TEST_VALIDATOR.validate('constrainedNum', 0);
expectInvalid(validity0);
const validity150 = TEST_VALIDATOR.validate('constrainedNum', 150);
expectInvalid(validity150);
});

it('should accept a stringised number via the text API', () => {
const validity = TEST_VALIDATOR.validateText('simpleNum', '123');
expectValid(validity);
});

it('should not accept a stringised non-number via the text API', () => {
const validity = TEST_VALIDATOR.validateText('simpleNum', 'xyz123');
expectInvalid(validity);
});

});

describe('a number parameter', () => {

it('should accept an integer value', () => {
const validity = TEST_VALIDATOR.validate('simpleFloat', 123);
expectValid(validity);
});

it('should accept a value with a fractional part', () => {
const validity = TEST_VALIDATOR.validate('simpleFloat', 123.5);
expectValid(validity);
});

it('should not accept a string value', () => {
const validity = TEST_VALIDATOR.validate('simpleFloat', '123');
expectInvalid(validity);
});

it('should accept a stringised number via the text API', () => {
const validity = TEST_VALIDATOR.validateText('simpleFloat', '123');
expectValid(validity);
});

it('should not accept a stringised non-number via the text API', () => {
const validity = TEST_VALIDATOR.validateText('simpleFloat', 'xyz123');
expectInvalid(validity);
});

});

describe('a boolean parameter', () => {

it('should accept true', () => {
const validity = TEST_VALIDATOR.validate('simpleBool', true);
expectValid(validity);
});

it('should accept false', () => {
const validity = TEST_VALIDATOR.validate('simpleBool', false);
expectValid(validity);
});

it('should not accept a string value', () => {
const validity = TEST_VALIDATOR.validate('simpleBool', 'true');
expectInvalid(validity);
});

it('should accept a stringised boolean via the text API', () => {
const validityT = TEST_VALIDATOR.validateText('simpleBool', 'true');
expectValid(validityT);
const validityF = TEST_VALIDATOR.validateText('simpleBool', 'false');
expectValid(validityF);
});

it('should not accept a stringised non-boolean via the text API', () => {
const validity = TEST_VALIDATOR.validateText('simpleBool', 'FILE_NOT_FOUND');
expectInvalid(validity);
});

});

describe('a parameter with no definition', () => {

it('should fail validation', () => {
const validity = TEST_VALIDATOR.validate('noDef', 123);
expectInvalid(validity);
});

});

describe('an undefined parameter', () => {

it('should fail validation', () => {
const validity = TEST_VALIDATOR.validate('there is no parameter with this name', 123);
expectInvalid(validity);
});

});

const PARAMETERLESS_BUNDLE: cnab.Bundle = {
name: 'test',
schemaVersion: 'v1',
version: '1.0.0',
invocationImages: [],
definitions: {
foo: { type: 'integer' }
}
};

const DEFINITIONLESS_BUNDLE: cnab.Bundle = {
name: 'test',
schemaVersion: 'v1',
version: '1.0.0',
invocationImages: [],
parameters: {
foo: { definition: 'foo', destination: {} }
}
};

describe('if the bundle has no...', () => {

it('definitions, parameters should fail validation', () => {
const validity = Validator.for(DEFINITIONLESS_BUNDLE).validate('foo', 123);
expectInvalid(validity);
});

it('parameters, parameters should fail validation', () => {
const validity = Validator.for(PARAMETERLESS_BUNDLE).validate('foo', 123);
expectInvalid(validity);
});

});
4 changes: 4 additions & 0 deletions ts/bundle-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export interface Definition {
* The permitted values of the value.
*/
enum?: any[];
/**
* Property bag to prevent object literal errors in TypeScript.
*/
[key: string]: any;
}

/**
Expand Down
1 change: 1 addition & 0 deletions ts/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './bundle-manifest';
export * from './validation';
3 changes: 3 additions & 0 deletions ts/utils/never.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function cantHappen(n: never): never {
return n;
}
Loading

0 comments on commit d6ff730

Please sign in to comment.