Skip to content

Commit a95e1fd

Browse files
committed
test(json-schema) : test coverage
Signed-off-by: Dan Selman <[email protected]>
1 parent 503fbb6 commit a95e1fd

File tree

8 files changed

+575
-47
lines changed

8 files changed

+575
-47
lines changed

packages/concerto-core/test/introspect/loaders/githubmodelfileloader.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ describe('GitHubModelFileLoader', () => {
7575

7676
it('should load github URIs', () => {
7777
// Match against an exact URL value
78-
moxios.stubRequest('https://raw.githubusercontent.com/accordproject/models/master/usa/business.cto', {
78+
moxios.stubRequest('https://raw.githubusercontent.com/accordproject/models/master/src/usa/business.cto', {
7979
status: 200,
8080
responseText: model
8181
});
8282

8383
const ml = new GitHubModelFileLoader(modelManager);
84-
return ml.load( 'github://accordproject/models/master/usa/business.cto', {foo: 'bar' })
84+
return ml.load( 'github://accordproject/models/master/src/usa/business.cto', {foo: 'bar' })
8585
.then((mf) => {
8686
mf.getDefinitions().should.be.deep.equal(model);
8787
});

packages/concerto-core/test/introspect/loaders/httpmodelfileloader.js

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,9 @@ describe('HTTPModeFilelLoader', () => {
2929
let modelManager;
3030
let sandbox;
3131

32-
let model = `namespace org.accordproject.usa.business
33-
34-
/**
35-
* Types of businesses in the USA
36-
* Taken from: https://en.wikipedia.org/wiki/List_of_business_entities#United_States
37-
*/
38-
enum BusinessEntity {
39-
o GENERAL_PARTNERSHIP
40-
o LP
41-
o LLP
42-
o LLLP
43-
o LLC
44-
o PLLC
45-
o CORP
46-
o PC
47-
o DBA
32+
let model = `namespace test
33+
enum Test {
34+
o ONE
4835
}`;
4936

5037
beforeEach(() => {
@@ -81,13 +68,15 @@ describe('HTTPModeFilelLoader', () => {
8168
it('should load https URIs', () => {
8269

8370
// Match against an exact URL value
84-
moxios.stubRequest('https://raw.githubusercontent.com/accordproject/models/master/usa/business.cto', {
71+
const url = 'https://raw.githubusercontent.com/accordproject/models/master/src/usa/business.cto';
72+
73+
moxios.stubRequest(url, {
8574
status: 200,
8675
responseText: model
8776
});
8877

8978
const ml = new HTTPModelFileLoader(modelManager);
90-
return ml.load('https://raw.githubusercontent.com/accordproject/models/master/usa/business.cto')
79+
return ml.load(url)
9180
.then((mf) => {
9281
mf.getDefinitions().should.be.deep.equal(model);
9382
});

packages/concerto-core/test/introspect/numbervalidator.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,20 @@ describe('NumberValidator', () => {
4343
describe('#constructor', () => {
4444
it('should accept valid constructor parms VALID_UPPER_AND_LOWER_BOUND_AST', () => {
4545
let validator = new NumberValidator(mockField, VALID_UPPER_AND_LOWER_BOUND_AST);
46-
validator.lowerBound.should.equal(0);
47-
validator.upperBound.should.equal(100);
46+
validator.getLowerBound().should.equal(0);
47+
validator.getUpperBound().should.equal(100);
4848
});
4949

5050
it('should accept valid constructor parms NO_LOWER_BOUND_AST', () => {
5151
let validator = new NumberValidator(mockField, NO_LOWER_BOUND_AST);
52-
should.equal(validator.lowerBound, null);
53-
validator.upperBound.should.equal(100);
52+
should.equal(validator.getLowerBound(), null);
53+
validator.getUpperBound().should.equal(100);
5454
});
5555

5656
it('should accept valid constructor parms NO_UPPER_BOUND_AST', () => {
5757
let validator = new NumberValidator(mockField, NO_UPPER_BOUND_AST);
58-
validator.lowerBound.should.equal(0);
59-
should.equal(validator.upperBound, null);
58+
validator.getLowerBound().should.equal(0);
59+
should.equal(validator.getUpperBound(), null);
6060
});
6161

6262
it('should throw an error for constructor parms NO_PARMS_IN_AST', () => {

packages/concerto-core/test/introspect/stringvalidator.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ describe('StringValidator', () => {
4343

4444
it('should ignore a null string', () => {
4545
let v = new StringValidator(mockField, '/^[A-z][A-z][0-9]{7}/' );
46+
v.getRegex().toString().should.equal('/^[A-z][A-z][0-9]{7}/');
4647
v.validate('id', null);
4748
});
4849

packages/concerto-tools/lib/codegen/fromcto/jsonschema/jsonschemavisitor.js

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,22 @@ const RelationshipDeclaration = require('@accordproject/concerto-core').Relation
2626
const TransactionDeclaration = require('@accordproject/concerto-core').TransactionDeclaration;
2727
const debug = require('debug')('concerto-core:jsonschemavisitor');
2828
const util = require('util');
29+
const RecursionDetectionVisitor = require('./recursionvisitor');
2930

3031
/**
31-
* Convert the contents of a {@link ModelManager} instance to a set of JSON
32-
* Schema v4 files - one per concrete asset and transaction type.
33-
* Set a fileWriter property (instance of {@link FileWriter}) on the parameters
34-
* object to control where the generated code is written to disk.
32+
* Convert the contents of a {@link ModelManager} to a JSON Schema, returning
33+
* the schema for all types under the 'definitions' key. If the 'rootType'
34+
* parameter option is set to a fully-qualified type name, then the properties
35+
* of the type are also added to the root of the schema object.
36+
*
37+
* If the fileWriter parameter is set then the JSONSchema will be written to disk.
38+
*
39+
* Note that by default $ref is used to references types, unless
40+
* the `inlineTypes` parameter is set, in which case types are expanded inline,
41+
* UNLESS they contain recursive references, in which case $ref is used.
42+
*
43+
* The meta schema used is http://json-schema.org/draft-07/schema#
44+
*
3545
* @private
3646
* @class
3747
* @memberof module:concerto-tools
@@ -53,6 +63,21 @@ class JSONSchemaVisitor {
5363
: null;
5464
}
5565

66+
/**
67+
* Returns true if the class declaration contains recursive references.
68+
*
69+
* Basic example:
70+
* concept Person {
71+
* o Person[] children
72+
* }
73+
*
74+
* @param {object} classDeclaration the class being visited
75+
*/
76+
isModelRecursive(classDeclaration) {
77+
const visitor = new RecursionDetectionVisitor();
78+
return classDeclaration.accept( visitor, {stack : []} );
79+
}
80+
5681
/**
5782
* Visitor design pattern
5883
* @param {Object} thing - the object being visited
@@ -96,9 +121,6 @@ class JSONSchemaVisitor {
96121
visitModelManager(modelManager, parameters) {
97122
debug('entering visitModelManager');
98123

99-
// Save the model manager so that we have access to it later.
100-
parameters.modelManager = modelManager;
101-
102124
// Visit all of the files in the model manager.
103125
let result = {
104126
$schema : 'http://json-schema.org/draft-07/schema#' // default for https://github.com/ajv-validator/ajv
@@ -113,6 +135,13 @@ class JSONSchemaVisitor {
113135
result = { ... result, ... schema.schema };
114136
}
115137

138+
if(parameters.fileWriter) {
139+
const fileName = parameters.rootType ? `${parameters.rootType}.json` : 'schema.json';
140+
parameters.fileWriter.openFile(fileName);
141+
parameters.fileWriter.writeLine(0, JSON.stringify(result, null, 2));
142+
parameters.fileWriter.closeFile();
143+
}
144+
116145
return result;
117146
}
118147

@@ -126,9 +155,6 @@ class JSONSchemaVisitor {
126155
visitModelFile(modelFile, parameters) {
127156
debug('entering visitModelFile', modelFile.getNamespace());
128157

129-
// Save the model file so that we have access to it later.
130-
parameters.modelFile = modelFile;
131-
132158
// Visit all of the asset and transaction declarations, but ignore the abstract ones.
133159
let result = {
134160
definitions : {}
@@ -203,6 +229,8 @@ class JSONSchemaVisitor {
203229
visitClassDeclarationCommon(classDeclaration, parameters) {
204230
debug('entering visitClassDeclarationCommon', classDeclaration.getName());
205231

232+
parameters.inlineTypes = parameters.inlineTypes ? !this.isModelRecursive(classDeclaration) : false;
233+
206234
const result = {
207235
$id: classDeclaration.getFullyQualifiedName(),
208236
schema: {
@@ -217,7 +245,7 @@ class JSONSchemaVisitor {
217245
result.schema.properties.$class = {
218246
type: 'string',
219247
default: classDeclaration.getFullyQualifiedName(),
220-
pattern: classDeclaration.getFullyQualifiedName().split('.').join('\\.'),
248+
pattern: `^${classDeclaration.getFullyQualifiedName().split('.').join('\\.')}$`,
221249
description: 'The class identifier for this type'
222250
};
223251

@@ -319,10 +347,14 @@ class JSONSchemaVisitor {
319347

320348
// Not primitive, so must be a class or enumeration!
321349
} else {
322-
323350
// Look up the type of the property.
324-
let type = parameters.modelFile.getModelManager().getType(field.getFullyQualifiedTypeName());
325-
jsonSchema = { $ref: `#/definitions/${type.getFullyQualifiedName()}` };
351+
let type = field.getParent().getModelFile().getModelManager().getType(field.getFullyQualifiedTypeName());
352+
if(!parameters.inlineTypes) {
353+
jsonSchema = { $ref: `#/definitions/${type.getFullyQualifiedName()}` };
354+
} else {
355+
// inline the schema
356+
jsonSchema = this.visit( type, parameters ).schema;
357+
}
326358
}
327359

328360
// Is the type an array?
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
'use strict';
16+
17+
const ClassDeclaration = require('@accordproject/concerto-core').ClassDeclaration;
18+
const EnumDeclaration = require('@accordproject/concerto-core').EnumDeclaration;
19+
const Field = require('@accordproject/concerto-core').Field;
20+
const RelationshipDeclaration = require('@accordproject/concerto-core').RelationshipDeclaration;
21+
const debug = require('debug')('concerto-core:recursiondetectionvisitor');
22+
const util = require('util');
23+
24+
/**
25+
* Detects whether ClassDeclaration contains recursive references.
26+
* Basic example:
27+
* concept Person {
28+
* o Person[] children
29+
* }
30+
*
31+
* parameters.stack should be initialized to []
32+
* @private
33+
* @class
34+
* @memberof module:concerto-tools
35+
*/
36+
class RecursionDetectionVisitor {
37+
38+
/**
39+
* Visitor design pattern
40+
* @param {Object} thing - the object being visited
41+
* @param {Object} parameters - the parameter
42+
* @return {Object} the result of visiting or null
43+
* @private
44+
*/
45+
visit(thing, parameters) {
46+
// the order of these matters!
47+
if (thing instanceof EnumDeclaration) {
48+
return this.visitEnumDeclaration(thing, parameters);
49+
}
50+
else if (thing instanceof ClassDeclaration) {
51+
return this.visitClassDeclaration(thing, parameters);
52+
} else if (thing instanceof Field) {
53+
return this.visitField(thing, parameters);
54+
} else if (thing instanceof RelationshipDeclaration) {
55+
return this.visitRelationshipDeclaration(thing, parameters);
56+
} else {
57+
throw new Error('Unrecognised type: ' + typeof thing + ', value: ' + util.inspect(thing, { showHidden: true, depth: null }));
58+
}
59+
}
60+
61+
/**
62+
* Visitor design pattern
63+
* @param {ClassDeclaration} classDeclaration - the object being visited
64+
* @param {Object} parameters - the parameter
65+
* @return {Object} the result of visiting or null
66+
* @private
67+
*/
68+
visitClassDeclaration(classDeclaration, parameters) {
69+
debug('entering visitClassDeclaration', classDeclaration.getName());
70+
71+
parameters.stack.push(classDeclaration.getFullyQualifiedName());
72+
73+
// Walk over all of the properties of this class and its super classes.
74+
const properties = classDeclaration.getProperties();
75+
for(let n=0; n < properties.length; n++) {
76+
const property = properties[n];
77+
if( property.accept(this, parameters) ) {
78+
return true;
79+
}
80+
}
81+
82+
parameters.stack.pop();
83+
return false;
84+
}
85+
86+
/**
87+
* Visitor design pattern
88+
* @param {Field} field - the object being visited
89+
* @param {Object} parameters - the parameter
90+
* @return {Object} the result of visiting or null
91+
* @private
92+
*/
93+
visitField(field, parameters) {
94+
debug('entering visitField', field.getName());
95+
if(field.isPrimitive()) {
96+
return false;
97+
}
98+
99+
let type = field.getParent().getModelFile().
100+
getModelManager().getType(field.getFullyQualifiedTypeName());
101+
102+
debug('stack', parameters.stack );
103+
if( parameters.stack.includes(type.getFullyQualifiedName()) ) {
104+
return true;
105+
}
106+
else {
107+
return this.visit(type, parameters);
108+
}
109+
}
110+
111+
/**
112+
* Visitor design pattern
113+
* @param {EnumDeclaration} enumDeclaration - the object being visited
114+
* @param {Object} parameters - the parameter
115+
* @return {Object} the result of visiting or null
116+
* @private
117+
*/
118+
visitEnumDeclaration(enumDeclaration, parameters) {
119+
debug('entering visitEnumDeclaration', enumDeclaration.getName());
120+
return false;
121+
}
122+
123+
/**
124+
* Visitor design pattern
125+
* @param {RelationshipDeclaration} relationshipDeclaration - the object being visited
126+
* @param {Object} parameters - the parameter
127+
* @return {Object} the result of visiting or null
128+
* @private
129+
*/
130+
visitRelationshipDeclaration(relationshipDeclaration, parameters) {
131+
debug('entering visitRelationship', relationshipDeclaration.getName());
132+
return false;
133+
}
134+
}
135+
136+
module.exports = RecursionDetectionVisitor;

0 commit comments

Comments
 (0)