diff --git a/packages/concerto-core/api.txt b/packages/concerto-core/api.txt index b4f35ef91..10062d3ba 100644 --- a/packages/concerto-core/api.txt +++ b/packages/concerto-core/api.txt @@ -162,7 +162,6 @@ class MapDeclaration extends Declaration { + string getName() + string getKey() + string getValue() - + array getProperties() + String toString() + string declarationKind() + boolean isMapDeclaration() @@ -174,6 +173,8 @@ class MapKeyType extends Decorated { + MapDeclaration getParent() + string getType() + String toString() + + boolean isKey() + + boolean isValue() } class MapValueType extends Decorated { + void constructor(MapDeclaration,Object) throws IllegalModelException @@ -182,6 +183,8 @@ class MapValueType extends Decorated { + MapDeclaration getParent() + string getType() + String toString() + + boolean isKey() + + boolean isValue() } + ModelManager newMetaModelManager() + object validateMetaModel() diff --git a/packages/concerto-core/changelog.txt b/packages/concerto-core/changelog.txt index e2fb3352c..4e229504d 100644 --- a/packages/concerto-core/changelog.txt +++ b/packages/concerto-core/changelog.txt @@ -24,6 +24,21 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # +Version 3.8.5 {9cd54e67c4a39a3041706e4b37dce9f1} 2023-08-14 +- Makes MapKeyType MapValueType processType() private + +Version 3.8.4 {a99786670d2561bf2b84cb3eb1bb1a08} 2023-08-14 +- Move isValidMapKey & isValidMapValue to ModelUtils + +Version 3.8.3 {7dd828f29c49cedfb8eee2f3c6fefa6e} 2023-08-10 +- Removes private identifiers from MapKeyType & MapValueType + +Version 3.8.2 {1a497711c4cca84d1dfa92c0b581fadf} 2023-08-10 +- Add isValidMapKey(Object) isValidMapValue(Object) + +Version 3.8.1 {794268f69b81f05f711d38a9ef1a7833} 2023-08-1 +- Add To MapKeyType, MapValue functionality + Version 3.7.0 {a97cb6ebd45679354ba4da1940d2bb8d} 2023-05-19 - Add MapDeclaration, MapKeyType, AggregateValueType diff --git a/packages/concerto-core/lib/introspect/mapdeclaration.js b/packages/concerto-core/lib/introspect/mapdeclaration.js index 28823c877..a83e735aa 100644 --- a/packages/concerto-core/lib/introspect/mapdeclaration.js +++ b/packages/concerto-core/lib/introspect/mapdeclaration.js @@ -14,8 +14,6 @@ 'use strict'; -const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); - const Declaration = require('./declaration'); const IllegalModelException = require('./illegalmodelexception'); const MapValueType = require('./mapvaluetype'); @@ -65,24 +63,21 @@ class MapDeclaration extends Declaration { process() { super.process(); - if (this.ast.properties.length !== 2) { - throw new IllegalModelException(`MapDeclaration must contain exactly two properties - MapKeyType & MapyValueType ${this.ast.name}`, this.modelFile, this.ast.location); + if (!this.ast.key || !this.ast.value) { + throw new IllegalModelException(`MapDeclaration must contain Key & Value properties ${this.ast.name}`, this.modelFile, this.ast.location); } - const key = this.ast.properties.find(property => property.$class === `${MetaModelNamespace}.MapKeyType`); - const value = this.ast.properties.find(property => property.$class === `${MetaModelNamespace}.AggregateValueType` || property.$class === `${MetaModelNamespace}.AggregateRelationshipValueType`); - - if (!key) { - throw new IllegalModelException(`MapDeclaration must contain MapKeyType ${this.ast.name}`, this.modelFile, this.ast.location); + if (!ModelUtil.isValidMapKey(this.ast.key)) { + throw new IllegalModelException(`MapDeclaration must contain valid MapKeyType ${this.ast.name}`, this.modelFile, this.ast.location); } - if (!value) { - throw new IllegalModelException(`MapDeclaration must contain AggregateValueType ${this.ast.name}`, this.modelFile, this.ast.location); + if (!ModelUtil.isValidMapValue(this.ast.value)) { + throw new IllegalModelException(`MapDeclaration must contain valid MapValueType, for MapDeclaration ${this.ast.name}` , this.modelFile, this.ast.location); } this.name = this.ast.name; - this.key = new MapKeyType(this, key); - this.value = new MapValueType(this, value); + this.key = new MapKeyType(this, this.ast.key); + this.value = new MapValueType(this, this.ast.value); this.fqn = ModelUtil.getFullyQualifiedName(this.modelFile.getNamespace(), this.ast.name); } @@ -146,15 +141,6 @@ class MapDeclaration extends Declaration { return this.value; } - /** - * Returns the MapDeclaration properties - * - * @return {array} the MapDeclaration properties - */ - getProperties() { - return this.ast.properties; - } - /** * Returns the string representation of this class * @return {String} the string representation of the class diff --git a/packages/concerto-core/lib/introspect/mapkeytype.js b/packages/concerto-core/lib/introspect/mapkeytype.js index e8a6743da..81f647711 100644 --- a/packages/concerto-core/lib/introspect/mapkeytype.js +++ b/packages/concerto-core/lib/introspect/mapkeytype.js @@ -14,6 +14,7 @@ 'use strict'; +const ModelUtil = require('../modelutil'); const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); const Decorated = require('./decorated'); @@ -46,7 +47,6 @@ class MapKeyType extends Decorated { constructor(parent, ast) { super(ast); this.parent = parent; - this.type = this.ast.name; this.process(); } @@ -58,6 +58,7 @@ class MapKeyType extends Decorated { */ process() { super.process(); + this.processType(this.ast); } /** @@ -67,30 +68,44 @@ class MapKeyType extends Decorated { * @protected */ validate() { - const declaration = this.getModelFile().getAllDeclarations(); - const key = declaration.find(decl => decl.name === this.type); - - if (!key?.isConcept?.() && - !key?.isEnum?.() && - !key?.isScalarDeclaration?.() && - !['String', 'DateTime'].includes(this.type)) { - throw new IllegalModelException(`MapKeyType has invalid Type: ${this.type}`); - } - if (key?.isConcept?.() && !key.isIdentified()) { - throw new IllegalModelException( - `ConceptDeclaration must be identified in context of MapKeyType: ${this.type}` - ); - } + if (!ModelUtil.isPrimitiveType(this.type)) { + let decl = this.parent.getModelFile().getAllDeclarations().find(d => d.name === this.ast.type?.name); + + if (!ModelUtil.isValidMapKeyScalar(decl)) { + throw new IllegalModelException( + `Scalar must be one of StringScalar, DateTimeScalar in context of MapKeyType. Invalid Scalar: ${this.type}, for MapDeclaration ${this.parent.name}` + ); + } - if (key?.isScalarDeclaration?.() && - !(key?.ast.$class === `${MetaModelNamespace}.StringScalar`) && - !(key?.ast.$class === `${MetaModelNamespace}.DateTimeScalar` )) { - throw new IllegalModelException( - `Scalar must be one of StringScalar, DateTimeScalar in context of MapKeyType. Invalid Scalar: ${this.type}` - ); + if (decl?.isConcept?.() || decl?.isClassDeclaration?.()) { + throw new IllegalModelException( + `Invalid Map key type in MapDeclaration ${this.parent.name}. Only String and DateTime types are supported for Map key types` + ); + } } + } + /** + * Sets the Type name for the Map Key + * + * @param {Object} ast - The AST created by the parser + * @private + */ + processType(ast) { + let decl; + switch(this.ast.$class) { + case `${MetaModelNamespace}.DateTimeMapKeyType`: + this.type = 'DateTime'; + break; + case `${MetaModelNamespace}.StringMapKeyType`: + this.type = 'String'; + break; + case `${MetaModelNamespace}.ObjectMapKeyType`: + decl = this.parent.getModelFile().getAllDeclarations().find(d => d.name === this.ast.type.name); + this.type = decl.getName(); + break; + } } /** @@ -129,6 +144,24 @@ class MapKeyType extends Decorated { toString() { return 'MapKeyType {id=' + this.getType() + '}'; } + + /** + * Returns true if this class is the definition of a Map Key. + * + * @return {boolean} true if the class is a Map Key + */ + isKey() { + return true; + } + + /** + * Returns true if this class is the definition of a Map Value. + * + * @return {boolean} true if the class is a Map Value + */ + isValue() { + return false; + } } module.exports = MapKeyType; diff --git a/packages/concerto-core/lib/introspect/mapvaluetype.js b/packages/concerto-core/lib/introspect/mapvaluetype.js index 36dc7af00..8b2898a05 100644 --- a/packages/concerto-core/lib/introspect/mapvaluetype.js +++ b/packages/concerto-core/lib/introspect/mapvaluetype.js @@ -15,7 +15,10 @@ 'use strict'; const Decorated = require('./decorated'); +const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); const IllegalModelException = require('../../lib/introspect/illegalmodelexception'); +const ModelUtil = require('../modelutil'); + // Types needed for TypeScript generation. /* eslint-disable no-unused-vars */ @@ -43,7 +46,6 @@ class MapValueType extends Decorated { constructor(parent, ast) { super(ast); this.parent = parent; - this.type = this.ast.name; this.process(); } @@ -55,6 +57,7 @@ class MapValueType extends Decorated { */ process() { super.process(); + this.processType(this.ast); } /** @@ -64,23 +67,65 @@ class MapValueType extends Decorated { * @protected */ validate() { - const declarations = this.getModelFile().getAllDeclarations(); - - const value = declarations.find(decl => decl.name === this.type); - - if (!value?.isConcept?.() && - !value?.isEnum?.() && - !value?.isAsset?.() && - !value?.isEvent?.() && - !value?.isParticipant?.() && - !value?.isTransaction?.() && - !value?.isMapDeclaration?.() && - !value?.isScalarDeclaration?.() && - !['String', 'Long', 'Integer', 'Double', 'Boolean', 'DateTime'].includes(this.type)) { - - throw new IllegalModelException( - `MapPropertyType has invalid Type: ${this.type}` - ); + if (!ModelUtil.isPrimitiveType(this.type)) { + let decl = this.parent.getModelFile().getAllDeclarations().find(d => d.name === this.ast.type?.name); + + // All declarations, with the exception of MapDeclarations are valid Values. + if(decl.isMapDeclaration?.()) { + throw new IllegalModelException( + `MapDeclaration as Map Type Value is not supported: ${this.type}` + ); + } + } + } + + /** + * Sets the Type name for the Map Value + * + * @param {Object} ast - The AST created by the parser + * @private + */ + processType(ast) { + let decl; + switch(this.ast.$class) { + case `${MetaModelNamespace}.ObjectMapValueType`: + decl = this.parent.getModelFile().getAllDeclarations().find(d => d.name === this.ast.type.name); + + // ObjectMapValueType must have TypeIdentifier. + if (!('type' in ast)) { + throw new IllegalModelException(`ObjectMapValueType must contain property 'type', for MapDeclaration named ${this.parent.name}`); + } + + // ObjectMapValueType TypeIdentifier must be properly formed. + if (!('$class' in ast.type) || !('name' in ast.type)) { + throw new IllegalModelException(`ObjectMapValueType type must contain property '$class' and property 'name', for MapDeclaration named ${this.parent.name}`); + } + + // And the $class must be valid. + if (ast.type.$class !== 'concerto.metamodel@1.0.0.TypeIdentifier') { + throw new IllegalModelException(`ObjectMapValueType type $class must be of TypeIdentifier for MapDeclaration named ${this.parent.name}`); + } + + this.type = decl.getName(); + break; + case `${MetaModelNamespace}.BooleanMapValueType`: + this.type = 'Boolean'; + break; + case `${MetaModelNamespace}.DateTimeMapValueType`: + this.type = 'DateTime'; + break; + case `${MetaModelNamespace}.StringMapValueType`: + this.type = 'String'; + break; + case `${MetaModelNamespace}.IntegerMapValueType`: + this.type = 'Integer'; + break; + case `${MetaModelNamespace}.LongMapValueType`: + this.type = 'Long'; + break; + case `${MetaModelNamespace}.DoubleMapValueType`: + this.type = 'Double'; + break; } } @@ -121,6 +166,24 @@ class MapValueType extends Decorated { return 'MapValueType {id=' + this.getType() + '}'; } + /** + * Returns true if this class is the definition of a Map Key. + * + * @return {boolean} true if the class is a Map Key + */ + isKey() { + return false; + } + + /** + * Returns true if this class is the definition of a Map Value. + * + * @return {boolean} true if the class is a Map Value + */ + isValue() { + return true; + } + } module.exports = MapValueType; diff --git a/packages/concerto-core/lib/modelutil.js b/packages/concerto-core/lib/modelutil.js index 8defb7b99..502badb58 100644 --- a/packages/concerto-core/lib/modelutil.js +++ b/packages/concerto-core/lib/modelutil.js @@ -14,6 +14,7 @@ 'use strict'; +const { MetaModelNamespace } = require('@accordproject/concerto-metamodel'); const { MetaModelUtil } = require('@accordproject/concerto-metamodel'); const semver = require('semver'); const Globalize = require('./globalize'); @@ -286,6 +287,50 @@ class ModelUtil { static isPrivateSystemProperty(propertyName) { return privateReservedProperties.includes(propertyName); } + + /** + * Returns true if this Key is a valid Map Key. + * + * @param {Object} key - the Key of the Map Declaration + * @return {boolean} true if the Key is a valid Map Key + */ + static isValidMapKey(key) { + return [ + `${MetaModelNamespace}.StringMapKeyType`, + `${MetaModelNamespace}.DateTimeMapKeyType`, + `${MetaModelNamespace}.ObjectMapKeyType`, + ].includes(key.$class); + } + + /** + * Returns true if this Key is a valid Map Key Scalar Value. + * + * @param {Object} decl - the Map Key Scalar declaration + * @return {boolean} true if the Key is a valid Map Key Scalar type + */ + static isValidMapKeyScalar(decl) { + return (decl?.isScalarDeclaration?.() && + (decl?.ast.$class !== `${MetaModelNamespace}.StringScalar` || + decl?.ast.$class !== `${MetaModelNamespace}.DateTimeScalar`)); + } + + /** + * Returns true if this Value is a valid Map Value. + * + * @param {Object} value - the Value of the Map Declaration + * @return {boolean} true if the Value is a valid Map Value + */ + static isValidMapValue(value) { + return [ + `${MetaModelNamespace}.BooleanMapValueType`, + `${MetaModelNamespace}.DateTimeMapValueType`, + `${MetaModelNamespace}.StringMapValueType`, + `${MetaModelNamespace}.IntegerMapValueType`, + `${MetaModelNamespace}.LongMapValueType`, + `${MetaModelNamespace}.DoubleMapValueType`, + `${MetaModelNamespace}.ObjectMapValueType` + ].includes(value.$class); + } } module.exports = ModelUtil; diff --git a/packages/concerto-core/lib/serializer/jsongenerator.js b/packages/concerto-core/lib/serializer/jsongenerator.js index 2cef9574a..85fad1f8a 100644 --- a/packages/concerto-core/lib/serializer/jsongenerator.js +++ b/packages/concerto-core/lib/serializer/jsongenerator.js @@ -85,7 +85,34 @@ class JSONGenerator { */ visitMapDeclaration(mapDeclaration, parameters) { const obj = parameters.stack.pop(); - return Object.fromEntries(obj); + + // initialise Map with $class property + let map = new Map([['$class',obj.get('$class')]]); + + obj.forEach((value, key) => { + + // don't serialize System Properties, other than $class + if(ModelUtil.isSystemProperty(key) && key !== '$class') { + return; + } + + // Key is always a string, but value might be a ValidatedResource. + if (typeof value === 'object') { + let decl = mapDeclaration.getModelFile() + .getAllDeclarations() + .find(decl => decl.name === value.getType()); + + // convert declaration to JSON representation + parameters.stack.push(value); + const jsonValue = decl.accept(this, parameters); + + value = JSON.stringify(jsonValue); + } + + map.set(key, value); + }); + + return Object.fromEntries(map); } /** diff --git a/packages/concerto-core/lib/serializer/jsonpopulator.js b/packages/concerto-core/lib/serializer/jsonpopulator.js index 33f89fb39..734b2494a 100644 --- a/packages/concerto-core/lib/serializer/jsonpopulator.js +++ b/packages/concerto-core/lib/serializer/jsonpopulator.js @@ -170,13 +170,62 @@ class JSONPopulator { * @private */ visitMapDeclaration(mapDeclaration, parameters) { - const jsonObj = parameters.jsonStack.pop(); + let jsonObj = parameters.jsonStack.pop(); parameters.path ?? (parameters.path = new TypedStack('$')); - // throws if Map contains private reserved properties as keys. + // Throws if Map contains reserved properties - a Map containing reserved Properties should not be serialized. getAssignableProperties(jsonObj, mapDeclaration); - return new Map(Object.entries(jsonObj)); + jsonObj = new Map(Object.entries(jsonObj)); + + let map = new Map(); + + jsonObj.forEach((value, key) => { + + if (key === '$class') { + map.set(key, value); + return; + } + + if (!ModelUtil.isPrimitiveType(mapDeclaration.getKey().getType())) { + key = this.processMapType(mapDeclaration, parameters, key, mapDeclaration.getKey().getType()); + } + + if (!ModelUtil.isPrimitiveType(mapDeclaration.getValue().getType())) { + value = this.processMapType(mapDeclaration, parameters, value, mapDeclaration.getValue().getType()); + } + + map.set(key, value); + }); + + return map; + } + + /** + * Visitor design pattern + * @param {MapDeclaration} mapDeclaration - the object being visited + * @param {Object} parameters - the parameter + * @param {Object} value - the key or value belonging to the Map Entry. + * @param {Object} type - the Type associated with the Key or Value Map Entry. + * @return {Object} value - the key or value belonging to the Map Entry. + * @private + */ + processMapType(mapDeclaration, parameters, value, type) { + let decl = mapDeclaration.getModelFile() + .getAllDeclarations() + .find(decl => decl.name === type); + + // if its a ClassDeclaration, populate the Concept. + if (decl?.isClassDeclaration()) { + let subResource = parameters.factory.newConcept(decl.getNamespace(), + decl.getName(), decl.getIdentifierFieldName() ); + + parameters.jsonStack.push(JSON.parse(value)); + parameters.resourceStack.push(subResource); + return decl.accept(this, parameters); + } + // otherwise its a scalar value, we only need to return the primitve value of the scalar. + return value; } /** diff --git a/packages/concerto-core/lib/serializer/resourcevalidator.js b/packages/concerto-core/lib/serializer/resourcevalidator.js index d303d3294..3df7c4224 100644 --- a/packages/concerto-core/lib/serializer/resourcevalidator.js +++ b/packages/concerto-core/lib/serializer/resourcevalidator.js @@ -22,6 +22,10 @@ const ModelUtil = require('../modelutil'); const ValidationException = require('./validationexception'); const Globalize = require('../globalize'); +const dayjs = require('dayjs'); +const utc = require('dayjs/plugin/utc'); +dayjs.extend(utc); + /** *
* Validates a Resource or Field against the models defined in the ModelManager.
@@ -106,21 +110,79 @@ class ResourceValidator {
return null;
}
+
+ /**
+ * Check a Type that is declared as a Map Type.
+ * @param {Object} type - the type in scope for validation, can be MapTypeKey or MapTypeValue
+ * @param {Object} value - the object being validated
+ * @param {Object} parameters - the parameter
+ * @param {Map} mapDeclaration - the object being visited
+ * @private
+ */
+ checkMapType(type, value, parameters, mapDeclaration, ) {
+
+ if (!ModelUtil.isPrimitiveType(type.getType())) {
+
+ // thing might be a Concept, Scalar String, Scalar DateTime
+ let thing = mapDeclaration.getModelFile()
+ .getAllDeclarations()
+ .find(decl => decl.name === type.getType());
+
+ // if Key or Value is Scalar, get the Base Type of the Scalar for primitive validation.
+ if (ModelUtil.isScalar(mapDeclaration.getKey())) {
+ type = thing.getType();
+ }
+
+ if (thing?.isClassDeclaration?.()) {
+ parameters.stack.push(value);
+ thing.accept(this, parameters);
+ return;
+ }
+ } else {
+ // otherwise its a primitive
+ type = type.getType();
+
+ }
+
+ // validate the primitive
+ switch(type) {
+ case 'String':
+ if (typeof value !== 'string') {
+ throw new Error(`Model violation in ${mapDeclaration.getFullyQualifiedName()}. Expected Type of String but found '${value}' instead.`);
+ }
+ break;
+ case 'DateTime':
+ if (!dayjs.utc(value).isValid()) {
+ throw new Error(`Model violation in ${mapDeclaration.getFullyQualifiedName()}. Expected Type of DateTime but found '${value}' instead.`);
+ }
+ break;
+ case 'Boolean':
+ if (typeof value !== 'boolean') {
+ const type = typeof value;
+ throw new Error(`Model violation in ${mapDeclaration.getFullyQualifiedName()}. Expected Type of Boolean but found ${type} instead, for value '${value}'.`);
+ }
+ break;
+ }
+ }
+
/**
* Visitor design pattern
*
* @param {MapDeclaration} mapDeclaration - the object being visited
* @param {Object} parameters - the parameter
+ * @return {Object} the result of visiting or null
+ *
* @private
*/
visitMapDeclaration(mapDeclaration, parameters) {
+
const obj = parameters.stack.pop();
if (!((obj instanceof Map))) {
throw new Error('Expected a Map, but found ' + JSON.stringify(obj));
}
- if(!obj.has('$class')) {
+ if (!obj.has('$class')) {
throw new Error('Invalid Map. Map must contain a properly formatted $class property');
}
@@ -128,17 +190,16 @@ class ResourceValidator {
throw new Error(`$class value must match ${mapDeclaration.getFullyQualifiedName()}`);
}
-
obj.forEach((value, key) => {
- if(!ModelUtil.isSystemProperty(key)) {
- if (typeof key !== 'string') {
- ResourceValidator.reportInvalidMap(parameters.rootResourceIdentifier, mapDeclaration, obj);
- }
- if (typeof value !== 'string') {
- ResourceValidator.reportInvalidMap(parameters.rootResourceIdentifier, mapDeclaration, obj);
- }
+ if (!ModelUtil.isSystemProperty(key)) {
+ // Validate Key
+ this.checkMapType(mapDeclaration.getKey(), key, parameters, mapDeclaration);
+ // Validate Value
+ this.checkMapType(mapDeclaration.getValue(), value, parameters, mapDeclaration);
}
});
+
+ return null;
}
/**
@@ -500,22 +561,6 @@ class ResourceValidator {
}));
}
- /**
- * Throw a new error for a model violation.
- * @param {string} id - the identifier of this instance.
- * @param {MapDeclaration} mapDeclaration - the declaration of the map
- * @param {Object} value - the value of the field.
- * @private
- */
- static reportInvalidMap(id, mapDeclaration, value) {
- let formatter = Globalize.messageFormatter('resourcevalidator-invalidmap');
- throw new ValidationException(formatter({
- resourceId: id,
- classFQN: mapDeclaration.getFullyQualifiedName(),
- invalidValue: value.toString()
- }));
- }
-
/**
* Throw a new error for a model violation.
* @param {string} id - the identifier of this instance.
diff --git a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.enum.cto b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.enum.cto
index 3989eb351..cfb5c3ca1 100644
--- a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.enum.cto
+++ b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.enum.cto
@@ -13,7 +13,12 @@
*/
namespace com.testing@1.0.0
+enum Phase {
+ o ONE
+ o TWO
+}
map Dictionary {
- o NotDeclared
+ o Phase
o String
}
+
diff --git a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodkey.declaration.concept.cto b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badkey.identified.declaration.concept.cto
similarity index 88%
rename from packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodkey.declaration.concept.cto
rename to packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badkey.identified.declaration.concept.cto
index 7ad76e688..9ef43072d 100644
--- a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodkey.declaration.concept.cto
+++ b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badkey.identified.declaration.concept.cto
@@ -12,7 +12,7 @@
* limitations under the License.
*/
-namespace com.testing@1.0.0 //todo makek sure namesapce is same for all test concepts
+namespace com.testing@1.0.0
concept Person identified {}
diff --git a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.map.cto b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badvalue.declaration.map.cto
similarity index 100%
rename from packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.map.cto
rename to packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badvalue.declaration.map.cto
diff --git a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.relationship.cto b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badvalue.declaration.relationship.cto
similarity index 100%
rename from packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.relationship.cto
rename to packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badvalue.declaration.relationship.cto
diff --git a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badvalue.nontype.cto b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badvalue.nontype.cto
index 3b7c95230..9b2623e96 100644
--- a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badvalue.nontype.cto
+++ b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.badvalue.nontype.cto
@@ -14,10 +14,6 @@
namespace com.acme@1.0.0
-enum Phase {
- o ONE
- o TWO
-}
map Dictionary {
o Phase
o NONTYPE
diff --git a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.identified.declaration.concept.cto b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.identified.declaration.concept.cto
new file mode 100644
index 000000000..177158799
--- /dev/null
+++ b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.identified.declaration.concept.cto
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+namespace com.testing@1.0.0
+
+concept Person identified {}
+
+map Dictionary {
+ o String
+ o Person
+}
diff --git a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.boolean.cto b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.boolean.cto
new file mode 100644
index 000000000..d4eb2ef66
--- /dev/null
+++ b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.boolean.cto
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+namespace com.acme@1.0.0
+
+map Dictionary {
+ o String
+ o Boolean
+}
diff --git a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.datetime.cto b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.datetime.cto
new file mode 100644
index 000000000..f2ccd0202
--- /dev/null
+++ b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.datetime.cto
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+namespace com.acme@1.0.0
+
+scalar DATE extends DateTime
+
+map Dictionary {
+ o String
+ o DATE
+}
diff --git a/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.string.cto b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.string.cto
new file mode 100644
index 000000000..361875814
--- /dev/null
+++ b/packages/concerto-core/test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.string.cto
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+namespace com.acme@1.0.0
+
+scalar GUID extends String
+
+map Dictionary {
+ o String
+ o GUID
+}
diff --git a/packages/concerto-core/test/introspect/mapdeclaration.js b/packages/concerto-core/test/introspect/mapdeclaration.js
index e473d0824..77d437e90 100644
--- a/packages/concerto-core/test/introspect/mapdeclaration.js
+++ b/packages/concerto-core/test/introspect/mapdeclaration.js
@@ -27,6 +27,7 @@ const ModelManager = require('../../lib/modelmanager');
const Util = require('../composer/composermodelutility');
const sinon = require('sinon');
+const expect = require('chai').expect;
describe('MapDeclaration', () => {
@@ -45,55 +46,26 @@ describe('MapDeclaration', () => {
describe('#constructor', () => {
- it('should throw if ast contains no MapKeyType', () => {
+ it('should throw if ast contains no Map Key Property', () => {
(() => {
new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- name: 'String'
- }
- ]
- });
- }).should.throw(IllegalModelException);
- });
-
- it('should throw if ast contains no MapValueType', () => {
- (() => {
- new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- name: 'Integer'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.SomeRandomInvalidType',
- name: 'String'
- },]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
});
}).should.throw(IllegalModelException);
});
- it('should throw if ast does not contain exactly two properties', () => {
+ it('should throw if ast contains no Map Value Property', () => {
(() => {
new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- name: 'String'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.AggregateValueType',
- name: 'String'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.StringProperty',
- name: 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ }
});
}).should.throw(IllegalModelException);
});
@@ -103,240 +75,365 @@ describe('MapDeclaration', () => {
(() =>
{
new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- name: 'String'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.AggregateValueType',
- name: 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
});
}).should.throw(/MapType feature is not enabled. Please set the environment variable "ENABLE_MAP_TYPE=true" to access this functionality./);
process.env.ENABLE_MAP_TYPE = 'true'; // enable after the test run. This is necessary to ensure functioning of other tests.
});
- it('should throw if ast contains properties other than MapKeyType, AggregateValueType & AggregateRelationshipValueType', () => {
- (() => {
+
+
+ it('should throw if invalid $class provided for Map Key', () => {
+ (() =>
+ {
new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.StringProperty',
- name: 'String'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.StringProperty',
- name: 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.BadMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
});
}).should.throw(IllegalModelException);
});
- });
- describe('#validate success scenarios', () => {
- it('should not throw when map key is an identified concept declaration', () => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.declaration.concept.cto', MapDeclaration);
- decl.validate();
- });
-
- it('should not throw when map key is an enum declaration', () => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.declaration.enum.cto', MapDeclaration);
- decl.validate();
+ it('should throw if invalid $class provided for Map Value', () => {
+ (() =>
+ {
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.BadMapValueType'
+ }
+ });
+ }).should.throw(IllegalModelException);
});
+ });
- it('should not throw when map key is primitive type datetime', () => {
+ describe('#validate success scenarios - Map Key', () => {
+ it('should validate when map key is primitive type datetime', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.datetime.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map key is primitive type string', () => {
+ it('should validate when map key is primitive type string', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map key is primitive type string datetime', () => {
+ it('should validate when map key is primitive type scalar datetime', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.scalar.datetime.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map key is primitive type scalar string', () => {
+ it('should validate when map key is primitive type scalar string', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.scalar.string.cto', MapDeclaration);
decl.validate();
});
+ });
- it('should not throw when map value is an identified concept declaration', () => {
+ describe('#validate success scenarios - Map Value', () => {
+ it('should validate when map value is a concept declaration', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.concept.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is a concept derived from another concept declaration', () => {
+ it('should validate when map value is an identified concept declaration', () => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.identified.declaration.concept.cto', MapDeclaration);
+ decl.validate();
+ });
+
+ it('should validate when map value is a concept derived from another concept declaration', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.derived.concept.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is an event declaration', () => {
+ it('should validate when map value is an event declaration', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.event.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is an asset declaration', () => {
+ it('should validate when map value is an asset declaration', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.asset.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is an transaction declaration', () => {
+ it('should validate when map value is an transaction declaration', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.transaction.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is an participant declaration', () => {
+ it('should validate when map value is an participant declaration', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.participant.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is a map declaration', () => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.map.cto', MapDeclaration);
- decl.validate();
- });
-
- it('should not throw when map value is a relationship', () => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.declaration.relationship.cto', MapDeclaration);
+ it('should validate when map value is a primitive boolean', () => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.boolean.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is a primitive string', () => {
+ it('should validate when map value is a primitive string', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.string.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is a primitive datetime', () => {
+ it('should validate when map value is a primitive datetime', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.datetime.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is a primitive double', () => {
+ it('should validate when map value is a primitive double', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.double.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is a primitive integer', () => {
+ it('should validate when map value is a primitive integer', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.integer.cto', MapDeclaration);
decl.validate();
});
- it('should not throw when map value is a primitive long', () => {
+ it('should validate when map value is a primitive long', () => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.primitive.long.cto', MapDeclaration);
decl.validate();
});
-
});
- describe('#validate failure scenarios', () => {
- it('should throw validating with a non-identified concept declaration as key', function() {
+ describe('#validate failure scenarios - Map Key', () => {
+
+ it('should throw if ast contains illegal Map Key Property', () => {
(() => {
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.UnSupportedMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
+ });
+ }).should.throw(IllegalModelException);
+ });
+
+ it('should throw if ast contains illegal Map Key Property - Concept', () => {
+ (() => {
let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.concept.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/ConceptDeclaration must be identified in context of MapKeyType: NotIdentified/);
+ decl.validate().should.throw(IllegalModelException);
+ });
});
- it('should throw when an enum key declaration missing', function() {
- (() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.enum.cto', MapDeclaration);
+ it('should throw if ast contains illegal Map Key Property - Scalar Long', () => {
+ (() => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.long.cto', MapDeclaration);
decl.validate();
- }).should.throw(/MapKeyType has invalid Type: NotDeclared/);
+ });
+ });
+
+ it('should throw if ast contains illegal Map Key Property - Scalar Integer', () => {
+ (() => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.integer.cto', MapDeclaration);
+ decl.validate().should.throw(IllegalModelException);
+ });
});
- it('should throw when map value is an illegal type', function() {
+ it('should throw if ast contains illegal Map Key Property - Scalar Double', () => {
+ (() => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.double.cto', MapDeclaration);
+ decl.validate().should.throw(IllegalModelException);
+ });
+ });
+
+ it('should throw if ast contains illegal Map Key Property - Scalar Boolean', () => {
+ (() => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.boolean.cto', MapDeclaration);
+ decl.validate().should.throw(IllegalModelException);
+ });
+ });
+
+ it('should throw if ast contains illegal Map Key Property - Boolean', () => {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badvalue.nontype.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/MapPropertyType has invalid Type: NONTYPE/);
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.BooleanMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
+ });
+ }).should.throw(IllegalModelException);
});
- it('should throw when map key is a boolean', function() {
+ it('should throw if ast contains illegal Map Key Property - Integer', () => {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.boolean.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/Scalar must be one of StringScalar, DateTimeScalar in context of MapKeyType. Invalid Scalar: BOOL/);
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.IntegerMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
+ });
+ }).should.throw(IllegalModelException);
});
- it('should throw when map key is an event declaration', function() {
+ it('should throw if ast contains illegal Map Key Property - Long', () => {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.event.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/MapKeyType has invalid Type: Activity/);
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.LongMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
+ });
+ }).should.throw(IllegalModelException);
});
- it('should throw when map key is of type MapDeclaration', function() {
+ it('should throw if ast contains illegal Map Key Property - Double', () => {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.declaration.map.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/MapKeyType has invalid Type: IllegalMapKey/);
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.DoubleMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
+ });
+ }).should.throw(IllegalModelException);
});
- it('should throw when map key is of primitive type Double', function() {
+ it('should throw if ast contains illegal Map Key Property - Enum', () => {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.primitive.double.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/MapKeyType has invalid Type: Double/);
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.EnumMapKeyType',
+ type: {
+ $class: 'concerto.metamodel@1.0.0.TypeIdentifier',
+ name: 'States'
+ }
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
+ });
+ }).should.throw(IllegalModelException);
});
+ });
+
+ describe('#validate failure scenarios - Map Value', () => {
- it('should throw when map key is of primitive type Integer', function() {
+ it('should throw if ObjectMapValueType does not contain type property ', () => {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.primitive.integer.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/MapKeyType has invalid Type: Integer/);
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.ObjectMapValueType',
+ }
+ });
+ }).should.throw(IllegalModelException);
});
- it('should throw when map key is of primitive type Long', function() {
+ it('should throw if ObjectMapValueType TypeIdentifier does not contain name property', () => {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.primitive.long.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/MapKeyType has invalid Type: Long/);
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.ObjectMapValueType',
+ type: {
+ $class: 'concerto.metamodel@1.0.0.TypeIdentifier',
+ }
+ }
+ });
+ }).should.throw(IllegalModelException);
});
- it('should throw when map key is of scalar type Double', function() {
+ it('should throw if ObjectMapValueType TypeIdentifier has bad $class', () => {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.double.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/Scalar must be one of StringScalar, DateTimeScalar in context of MapKeyType. Invalid Scalar: BAD/);
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.ObjectMapValueType',
+ type: {
+ $class: 'concerto.metamodel@1.0.0.BAD_$CLASS',
+ name: 'Person',
+ }
+ }
+ });
+ }).should.throw(IllegalModelException);
});
- it('should throw when map key is of scalar type Integer', function() {
+ it('should throw if ast contains illegal Map Value Property', () => {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.integer.cto', MapDeclaration);
- decl.validate();
- }).should.throw(/Scalar must be one of StringScalar, DateTimeScalar in context of MapKeyType. Invalid Scalar: BAD/);
+ new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.EnumMapValueType'
+ }
+ });
+ }).should.throw(IllegalModelException);
});
- it('should throw when map key is of scalar type Long', function() {
+
+ it('should throw when map value is a map declaration', function() {
(() => {
- let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badkey.scalar.long.cto', MapDeclaration);
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.badvalue.declaration.map.cto', MapDeclaration);
decl.validate();
- }).should.throw(/Scalar must be one of StringScalar, DateTimeScalar in context of MapKeyType. Invalid Scalar: BAD/);
+ }).should.throw(/MapDeclaration as Map Type Value is not supported:/);
});
});
describe('#accept', () => {
it('should call the visitor', () => {
let clz = new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- name: 'String'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.AggregateValueType',
- name: 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
});
let visitor = {
visit: sinon.stub()
@@ -351,113 +448,211 @@ describe('MapDeclaration', () => {
describe('#getKey', () => {
it('should return the map key property', () => {
let clz = new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- name: 'DateTime'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.AggregateValueType',
- name: 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
});
(clz.getKey() instanceof MapKeyType).should.be.equal(true);
- clz.getKey().ast.$class.should.equal('concerto.metamodel@1.0.0.MapKeyType');
- clz.getKey().ast.name.should.equal('DateTime');
+ clz.getKey().ast.$class.should.equal('concerto.metamodel@1.0.0.StringMapKeyType');
});
- it('should return the correct values when called', () => {
+ it('should return the correct Type when called - String', () => {
let clz = new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- name: 'DateTime'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.AggregateValueType',
- name: 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
+ });
+ clz.getKey().getType().should.equal('String');
+ });
+
+ it('should return the correct Type when called - DateTime', () => {
+ let clz = new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.DateTimeMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
});
clz.getKey().getType().should.equal('DateTime');
});
+
+
+ it('should return the correct Type when called - Scalar DateTime', () => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.scalar.datetime.cto', MapDeclaration);
+ decl.getKey().getType().should.equal('DATE');
+ });
+
+ it('should return the correct Type when called - Scalar String', () => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.scalar.string.cto', MapDeclaration);
+ decl.getKey().getType().should.equal('GUID');
+ });
+
+ it('should return the correct boolean value introspecting isValue or isKey', () => {
+ let clz = new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.DoubleMapValueType'
+ }
+ });
+ expect(clz.getKey().isKey()).to.be.true;
+ expect(clz.getKey().isValue()).to.be.false;
+ });
});
describe('#getValue', () => {
it('should return the map value property', () => {
let clz = new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- name: 'DateTime'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.AggregateValueType',
- name: 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
});
(clz.getValue() instanceof MapValueType).should.be.equal(true);
- clz.getValue().ast.$class.should.equal('concerto.metamodel@1.0.0.AggregateValueType');
- clz.getValue().ast.name.should.equal('String');
+ clz.getValue().ast.$class.should.equal('concerto.metamodel@1.0.0.StringMapValueType');
});
- it('should return the correct values when called', () => {
+ it('should return the correct Type when called - Boolean', () => {
let clz = new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- name: 'DateTime'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.AggregateValueType',
- name: 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.BooleanMapValueType'
+ }
+ });
+ clz.getValue().getType().should.equal('Boolean');
+ });
+
+ it('should return the correct Type when called - DateTime', () => {
+ let clz = new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.DateTimeMapValueType'
+ }
+ });
+ clz.getValue().getType().should.equal('DateTime');
+ });
+
+ it('should return the correct Type when called - String', () => {
+ let clz = new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.StringMapValueType'
+ }
});
clz.getValue().getType().should.equal('String');
});
- });
- describe('#getProperties', () => {
- it('should return the map properties', () => {
+ it('should return the correct Type when called - Integer', () => {
let clz = new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- 'name': 'String'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.AggregateValueType',
- 'name': 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.IntegerMapValueType'
+ }
});
- clz.getProperties().length.should.be.equal(2);
+ clz.getValue().getType().should.equal('Integer');
+ });
+
+ it('should return the correct Type when called - Long', () => {
+ let clz = new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.LongMapValueType'
+ }
+ });
+ clz.getValue().getType().should.equal('Long');
});
- });
+ it('should return the correct Type when called - Double', () => {
+ let clz = new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.DoubleMapValueType'
+ }
+ });
+ clz.getValue().getType().should.equal('Double');
+ });
+
+ it('should return the correct values when called - Scalar DateTime', () => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.datetime.cto', MapDeclaration);
+ decl.getValue().getType().should.equal('DATE');
+ });
+
+ it('should return the correct values when called - Scalar String', () => {
+ let decl = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodvalue.scalar.string.cto', MapDeclaration);
+ decl.getValue().getType().should.equal('GUID');
+ });
+
+ it('should return the correct boolean value introspecting isValue or isKey', () => {
+ let clz = new MapDeclaration(modelFile, {
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.DoubleMapValueType'
+ }
+ });
+ expect(clz.getValue().isValue()).to.be.true;
+ expect(clz.getValue().isKey()).to.be.false;
+ });
+ });
describe('#Introspect', () => {
it('should return the correct model file', () => {
let clz = new MapDeclaration(modelFile, {
- name: 'MapTest',
- properties: [
- {
- '$class': 'concerto.metamodel@1.0.0.MapKeyType',
- 'name': 'String'
- },
- {
- '$class': 'concerto.metamodel@1.0.0.AggregateValueType',
- 'name': 'String'
- }
- ]
+ $class: 'concerto.metamodel@1.0.0.MapDeclaration',
+ name: 'MapPermutation1',
+ key: {
+ $class: 'concerto.metamodel@1.0.0.StringMapKeyType'
+ },
+ value: {
+ $class: 'concerto.metamodel@1.0.0.DoubleMapValueType'
+ }
});
clz.getModelFile().should.equal(modelFile);
clz.getKey().getModelFile().should.equal(modelFile);
@@ -465,25 +660,25 @@ describe('MapDeclaration', () => {
});
it('should return the correct value on introspection', () => {
- let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.declaration.concept.cto', MapDeclaration);
+ let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration);
declaration.declarationKind().should.equal('MapDeclaration');
- declaration.getFullyQualifiedName().should.equal('com.testing@1.0.0.Dictionary');
+ declaration.getFullyQualifiedName().should.equal('com.acme@1.0.0.Dictionary');
declaration.isMapDeclaration().should.equal(true);
});
});
describe('#toString', () => {
it('should give the correct value for Map Declaration', () => {
- let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.declaration.concept.cto', MapDeclaration);
- declaration.toString().should.equal('MapDeclaration {id=com.testing@1.0.0.Dictionary}');
- declaration.getKey().toString().should.equal('MapKeyType {id=Person}');
+ let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration);
+ declaration.toString().should.equal('MapDeclaration {id=com.acme@1.0.0.Dictionary}');
+ declaration.getKey().toString().should.equal('MapKeyType {id=String}');
declaration.getValue().toString().should.equal('MapValueType {id=String}');
});
});
describe('#getParent', () => {
it('should return the correct parent MapDeclaration value ', () => {
- let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.declaration.concept.cto', MapDeclaration);
+ let declaration = introspectUtils.loadLastDeclaration('test/data/parser/mapdeclaration/mapdeclaration.goodkey.primitive.string.cto', MapDeclaration);
declaration.getKey().getParent().should.equal(declaration);
declaration.getValue().getParent().should.equal(declaration);
});
diff --git a/packages/concerto-core/test/serializer.js b/packages/concerto-core/test/serializer.js
index 226163d10..de416f7af 100644
--- a/packages/concerto-core/test/serializer.js
+++ b/packages/concerto-core/test/serializer.js
@@ -66,17 +66,6 @@ describe('Serializer', () => {
o String country
o Double elevation
o PostalCode postcode optional
- o Dictionary dict optional
- }
-
- map Dictionary {
- o String
- o String
- }
-
- map PhoneBook {
- o String
- o String
}
concept DateTimeTest {
@@ -243,58 +232,6 @@ describe('Serializer', () => {
});
});
- it('should generate concept with a Map value', () => {
- let address = factory.newConcept('org.acme.sample', 'Address');
- address.city = 'Winchester';
- address.country = 'UK';
- address.elevation = 3.14;
- address.postcode = 'SO21 2JN';
- address.dict = new Map();
- address.dict.set('$class', 'org.acme.sample.Dictionary');
- address.dict.set('Lorem', 'Ipsum');
-
- // todo test for reserved identifiers in keys ($class)
- const json = serializer.toJSON(address);
- json.should.deep.equal({
- $class: 'org.acme.sample.Address',
- country: 'UK',
- elevation: 3.14,
- city: 'Winchester',
- postcode: 'SO21 2JN',
- dict: {
- $class: 'org.acme.sample.Dictionary',
- Lorem: 'Ipsum'
- }
- });
- });
-
- it('should throw if the value for a Map is not a Map instance', () => {
- let address = factory.newConcept('org.acme.sample', 'Address');
- address.city = 'Winchester';
- address.country = 'UK';
- address.elevation = 3.14;
- address.postcode = 'SO21 2JN';
- address.dict = 'xyz'; // bad value
- (() => {
- serializer.toJSON(address);
- }).should.throw(`Expected a Map, but found ${JSON.stringify(address.dict)}`);
- });
-
- it('should throw validation error if there is a mismatch on map $class property', () => {
- let address = factory.newConcept('org.acme.sample', 'Address');
- address.city = 'Winchester';
- address.country = 'UK';
- address.elevation = 3.14;
- address.postcode = 'SO21 2JN';
- address.dict = new Map();
- address.dict.set('$class', 'org.acme.sample.PhoneBook'); // dict is not a PhoneBook.
- address.dict.set('Lorem', 'Ipsum');
-
- (() => {
- serializer.toJSON(address);
- }).should.throw('$class value must match org.acme.sample.Dictionary');
- });
-
it('should generate a field if an empty string is specififed', () => {
let resource = factory.newResource('org.acme.sample', 'SampleAsset', '1');
resource.owner = factory.newRelationship('org.acme.sample', 'SampleParticipant', 'alice@email.com');
@@ -395,67 +332,6 @@ describe('Serializer', () => {
resource.postcode.should.equal('SO21 2JN');
});
- it('should deserialize a valid concept with a Map', () => {
- let json = {
- $class: 'org.acme.sample.Address',
- city: 'Winchester',
- country: 'UK',
- elevation: 3.14,
- postcode: 'SO21 2JN',
- dict: {
- '$class': 'org.acme.sample.Dictionary',
- 'Lorem': 'Ipsum'
- }
- };
- let resource = serializer.fromJSON(json);
-
- resource.should.be.an.instanceOf(Resource);
- resource.city.should.equal('Winchester');
- resource.country.should.equal('UK');
- resource.elevation.should.equal(3.14);
- resource.postcode.should.equal('SO21 2JN');
- resource.dict.should.be.an.instanceOf(Map);
- resource.dict.get('$class').should.equal('org.acme.sample.Dictionary');
- resource.dict.get('Lorem').should.equal('Ipsum');
- });
-
- it('should throw an error when deserializing a Map without a $class property', () => {
-
- let json = {
- $class: 'org.acme.sample.Address',
- city: 'Winchester',
- country: 'UK',
- elevation: 3.14,
- postcode: 'SO21 2JN',
- dict: {
- // '$class': 'org.acme.sample.Dictionary',
- 'Lorem': 'Ipsum'
- }
- };
- (() => {
- serializer.fromJSON(json);
- }).should.throw('Invalid Map. Map must contain a properly formatted $class property');
- });
-
-
- it('should throw an error when deserializing a Map with a private reserved property', () => {
-
- let json = {
- $class: 'org.acme.sample.Address',
- city: 'Winchester',
- country: 'UK',
- elevation: 3.14,
- postcode: 'SO21 2JN',
- dict: {
- '$class': 'org.acme.sample.Dictionary',
- '$namespace': 'com.reserved.property',
- 'Lorem': 'Ipsum'
- }
- };
- (() => {
- serializer.fromJSON(json);
- }).should.throw('Unexpected reserved properties for type org.acme.sample.Dictionary: $namespace');
- });
it('should throw validation errors if the validate flag is not specified', () => {
let json = {
diff --git a/packages/concerto-core/test/serializer/maptype/serializer.js b/packages/concerto-core/test/serializer/maptype/serializer.js
new file mode 100644
index 000000000..0c3d8456e
--- /dev/null
+++ b/packages/concerto-core/test/serializer/maptype/serializer.js
@@ -0,0 +1,1157 @@
+/*
+ * 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 Factory = require('../../../lib/factory');
+const ModelManager = require('../../../lib/modelmanager');
+const Resource = require('../../../lib/model/resource');
+const Serializer = require('../../../lib/serializer');
+const Util = require('../../composer/composermodelutility');
+
+require('chai').should();
+const sinon = require('sinon');
+
+describe('Serializer', () => {
+
+ let sandbox;
+ let factory;
+ let modelManager;
+ let serializer;
+
+ beforeEach(() => {
+ process.env.ENABLE_MAP_TYPE = 'true';
+ sandbox = sinon.createSandbox();
+
+ modelManager = new ModelManager();
+ Util.addComposerModel(modelManager);
+ modelManager.addCTOModel(`
+ namespace org.acme.sample
+
+ concept Concepts {
+ o Dictionary dict optional
+ o Diary diary optional
+ o Timer timer optional
+ o StopWatch stopwatch optional
+ o RSVP rsvp optional
+ o Database database optional
+ o Appointment appointment optional
+ o Birthday birthday optional
+ o Celebration celebration optional
+ o Rolodex rolodex optional
+ o Directory directory optional
+ o Score score optional
+ o Points points optional
+ o Balance balance optional
+ }
+
+ scalar GUID extends String
+
+ scalar Time extends DateTime
+
+ scalar PostalCode extends String
+
+ concept Person identified by name {
+ o String name
+ }
+
+ map Dictionary {
+ o String
+ o String
+ }
+
+ map Diary {
+ o DateTime
+ o String
+ }
+
+ map Timer {
+ o DateTime
+ o DateTime
+ }
+
+ map StopWatch {
+ o Time
+ o Time
+ }
+
+ map RSVP {
+ o String
+ o Boolean
+ }
+
+ map Database {
+ o GUID
+ o String
+ }
+
+ map Directory {
+ o GUID
+ o Person
+ }
+
+ map Appointment {
+ o Time
+ o String
+ }
+
+ map Birthday {
+ o String
+ o DateTime
+ }
+
+ map Celebration {
+ o String
+ o Time
+ }
+
+ map Rolodex {
+ o String
+ o Person
+ }
+
+ map Score {
+ o String
+ o Integer
+ }
+
+ map Points {
+ o String
+ o Long
+ }
+
+ map Balance {
+ o String
+ o Double
+ }
+
+ `);
+ factory = new Factory(modelManager);
+
+ serializer = new Serializer(factory, modelManager);
+ });
+
+ afterEach(() => {
+ sandbox.restore();
+ });
+
+ describe('# toJSON <> fromJSON', () => {
+ it('should serialize -> deserialize with a Map