Skip to content

Commit d6ff730

Browse files
authored
Parameter value validation helpers (#4)
1 parent c2e6ccd commit d6ff730

10 files changed

+432
-13
lines changed

.editorconfig

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# top-most EditorConfig file
2+
root = true
3+
4+
# Tab indentation
5+
[*]
6+
indent_style = space
7+
indent_size = 4
8+
trim_trailing_whitespace = true
9+
charset = utf-8
10+
insert_final_newline = true

package-lock.json

+39
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "cnabjs",
3-
"version": "0.0.1",
3+
"version": "0.0.2",
44
"description": "A library for loading and working with CNAB (Cloud Native Application Bundle) manifests",
5-
"main": "js/index.js",
6-
"types": "js/index.d.ts",
5+
"main": "js/ts/index.js",
6+
"types": "js/ts/index.d.ts",
77
"files": [
8-
"js/**/*"
8+
"js/ts/**/*"
99
],
1010
"scripts": {
1111
"compile": "tsc -p ./",
@@ -33,5 +33,8 @@
3333
"ts-node": "^8.3.0",
3434
"tslint": "^5.9.1",
3535
"typescript": "^3.5.3"
36+
},
37+
"dependencies": {
38+
"ajv": "^6.10.2"
3639
}
3740
}

test/test.ts

-8
This file was deleted.

test/validation.ts

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import 'mocha';
2+
import assert from 'assert';
3+
4+
import * as cnab from '../ts/index';
5+
import { Validator, Validity } from '../ts/index';
6+
7+
const TEST_BUNDLE: cnab.Bundle = {
8+
name: 'test',
9+
schemaVersion: 'v1',
10+
version: '1.0.0',
11+
invocationImages: [],
12+
13+
definitions: {
14+
simpleString: { type: 'string' },
15+
lengthyString: { type: 'string', minLength: 4, maxLength: 7 },
16+
simpleInt: { type: 'integer' },
17+
constrainedInt: { type: 'integer', minimum: 1, maximum: 100 },
18+
simpleFloat: { type: 'number' },
19+
simpleBool: { type: 'boolean' },
20+
},
21+
22+
parameters: {
23+
simpleString: { definition: 'simpleString', destination: {} },
24+
lengthyString: { definition: 'lengthyString', destination: { } },
25+
simpleNum: { definition: 'simpleInt', destination: {} },
26+
constrainedNum: { definition: 'constrainedInt', destination: { } },
27+
simpleFloat: { definition: 'simpleFloat', destination: {} },
28+
simpleBool: { definition: 'simpleBool', destination: {} },
29+
noDef: { definition: 'doesnt have one', destination: { } },
30+
},
31+
};
32+
33+
const TEST_VALIDATOR = Validator.for(TEST_BUNDLE);
34+
35+
function expectValid(validity: Validity) {
36+
if (!validity.isValid) {
37+
assert.fail(`should have been valid but '${validity.reason}'`);
38+
}
39+
}
40+
41+
function expectInvalid(validity: Validity) {
42+
if (validity.isValid) {
43+
assert.fail(`should NOT have been valid`);
44+
}
45+
}
46+
47+
describe('a string parameter', () => {
48+
49+
it('should accept a string value', () => {
50+
const validity = TEST_VALIDATOR.validate('simpleString', 'some text');
51+
expectValid(validity);
52+
});
53+
54+
it('should not accept a numeric value', () => {
55+
const validity = TEST_VALIDATOR.validate('simpleString', 123);
56+
expectInvalid(validity);
57+
});
58+
59+
it('should validate against constraints in the schema', () => {
60+
// NOTE: purpose of this is not to exercise JSON Schema - we lean on ajv for that.
61+
// It is solely to confirm that we are successfully passing the schema into ajv.
62+
const validity6 = TEST_VALIDATOR.validate('lengthyString', '6chars');
63+
expectValid(validity6);
64+
const validity3 = TEST_VALIDATOR.validate('lengthyString', '3ch');
65+
expectInvalid(validity3);
66+
const validity12 = TEST_VALIDATOR.validate('lengthyString', '12characters');
67+
expectInvalid(validity12);
68+
});
69+
70+
it('should accept a string value via the text API', () => {
71+
const validity = TEST_VALIDATOR.validateText('simpleString', 'some text');
72+
expectValid(validity);
73+
});
74+
75+
});
76+
77+
describe('an integer parameter', () => {
78+
79+
it('should accept an integer value', () => {
80+
const validity = TEST_VALIDATOR.validate('simpleNum', 123);
81+
expectValid(validity);
82+
});
83+
84+
it('should not accept a value with a fractional part', () => {
85+
const validity = TEST_VALIDATOR.validate('simpleNum', 123.5);
86+
expectInvalid(validity);
87+
});
88+
89+
it('should not accept a string value', () => {
90+
const validity = TEST_VALIDATOR.validate('simpleNum', '123');
91+
expectInvalid(validity);
92+
});
93+
94+
it('should validate against constraints in the schema', () => {
95+
// NOTE: purpose of this is not to exercise JSON Schema - we lean on ajv for that.
96+
// It is solely to confirm that we are successfully passing the schema into ajv.
97+
const validity70 = TEST_VALIDATOR.validate('constrainedNum', 70);
98+
expectValid(validity70);
99+
const validity0 = TEST_VALIDATOR.validate('constrainedNum', 0);
100+
expectInvalid(validity0);
101+
const validity150 = TEST_VALIDATOR.validate('constrainedNum', 150);
102+
expectInvalid(validity150);
103+
});
104+
105+
it('should accept a stringised number via the text API', () => {
106+
const validity = TEST_VALIDATOR.validateText('simpleNum', '123');
107+
expectValid(validity);
108+
});
109+
110+
it('should not accept a stringised non-number via the text API', () => {
111+
const validity = TEST_VALIDATOR.validateText('simpleNum', 'xyz123');
112+
expectInvalid(validity);
113+
});
114+
115+
});
116+
117+
describe('a number parameter', () => {
118+
119+
it('should accept an integer value', () => {
120+
const validity = TEST_VALIDATOR.validate('simpleFloat', 123);
121+
expectValid(validity);
122+
});
123+
124+
it('should accept a value with a fractional part', () => {
125+
const validity = TEST_VALIDATOR.validate('simpleFloat', 123.5);
126+
expectValid(validity);
127+
});
128+
129+
it('should not accept a string value', () => {
130+
const validity = TEST_VALIDATOR.validate('simpleFloat', '123');
131+
expectInvalid(validity);
132+
});
133+
134+
it('should accept a stringised number via the text API', () => {
135+
const validity = TEST_VALIDATOR.validateText('simpleFloat', '123');
136+
expectValid(validity);
137+
});
138+
139+
it('should not accept a stringised non-number via the text API', () => {
140+
const validity = TEST_VALIDATOR.validateText('simpleFloat', 'xyz123');
141+
expectInvalid(validity);
142+
});
143+
144+
});
145+
146+
describe('a boolean parameter', () => {
147+
148+
it('should accept true', () => {
149+
const validity = TEST_VALIDATOR.validate('simpleBool', true);
150+
expectValid(validity);
151+
});
152+
153+
it('should accept false', () => {
154+
const validity = TEST_VALIDATOR.validate('simpleBool', false);
155+
expectValid(validity);
156+
});
157+
158+
it('should not accept a string value', () => {
159+
const validity = TEST_VALIDATOR.validate('simpleBool', 'true');
160+
expectInvalid(validity);
161+
});
162+
163+
it('should accept a stringised boolean via the text API', () => {
164+
const validityT = TEST_VALIDATOR.validateText('simpleBool', 'true');
165+
expectValid(validityT);
166+
const validityF = TEST_VALIDATOR.validateText('simpleBool', 'false');
167+
expectValid(validityF);
168+
});
169+
170+
it('should not accept a stringised non-boolean via the text API', () => {
171+
const validity = TEST_VALIDATOR.validateText('simpleBool', 'FILE_NOT_FOUND');
172+
expectInvalid(validity);
173+
});
174+
175+
});
176+
177+
describe('a parameter with no definition', () => {
178+
179+
it('should fail validation', () => {
180+
const validity = TEST_VALIDATOR.validate('noDef', 123);
181+
expectInvalid(validity);
182+
});
183+
184+
});
185+
186+
describe('an undefined parameter', () => {
187+
188+
it('should fail validation', () => {
189+
const validity = TEST_VALIDATOR.validate('there is no parameter with this name', 123);
190+
expectInvalid(validity);
191+
});
192+
193+
});
194+
195+
const PARAMETERLESS_BUNDLE: cnab.Bundle = {
196+
name: 'test',
197+
schemaVersion: 'v1',
198+
version: '1.0.0',
199+
invocationImages: [],
200+
definitions: {
201+
foo: { type: 'integer' }
202+
}
203+
};
204+
205+
const DEFINITIONLESS_BUNDLE: cnab.Bundle = {
206+
name: 'test',
207+
schemaVersion: 'v1',
208+
version: '1.0.0',
209+
invocationImages: [],
210+
parameters: {
211+
foo: { definition: 'foo', destination: {} }
212+
}
213+
};
214+
215+
describe('if the bundle has no...', () => {
216+
217+
it('definitions, parameters should fail validation', () => {
218+
const validity = Validator.for(DEFINITIONLESS_BUNDLE).validate('foo', 123);
219+
expectInvalid(validity);
220+
});
221+
222+
it('parameters, parameters should fail validation', () => {
223+
const validity = Validator.for(PARAMETERLESS_BUNDLE).validate('foo', 123);
224+
expectInvalid(validity);
225+
});
226+
227+
});

ts/bundle-manifest.ts

+4
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ export interface Definition {
128128
* The permitted values of the value.
129129
*/
130130
enum?: any[];
131+
/**
132+
* Property bag to prevent object literal errors in TypeScript.
133+
*/
134+
[key: string]: any;
131135
}
132136

133137
/**

ts/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './bundle-manifest';
2+
export * from './validation';

ts/utils/never.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function cantHappen(n: never): never {
2+
return n;
3+
}

0 commit comments

Comments
 (0)