diff --git a/packages/composer-common/api.txt b/packages/composer-common/api.txt index e5c1659dd2..02a03505ae 100644 --- a/packages/composer-common/api.txt +++ b/packages/composer-common/api.txt @@ -27,9 +27,9 @@ class BusinessNetworkMetadata { class Factory { + void constructor(ModelManager) + Resource newInstance(string,string,string,Object,boolean,string) throws ModelException - + Resource newResource(string,string,string,Object,boolean,string) throws ModelException - + Resource newConcept(string,string,Object,boolean,string) throws ModelException - + Relationship newRelationship(string,string,string) throws ModelException + + Resource newResource(string,string,string,Object,boolean,string) throws TypeNotFoundException + + Resource newConcept(string,string,Object,boolean,string) throws TypeNotFoundException + + Relationship newRelationship(string,string,string) throws TypeNotFoundException + Resource newTransaction(string,string,string,Object,string) + Resource newEvent(string,string,string,Object,string) + Object toJSON() @@ -45,7 +45,7 @@ class FileWallet extends Wallet { + Promise remove(string) } class IllegalModelException extends BaseException { - + void constructor(string,string,string) + + void constructor(String,ModelFile,Object,String,String,String,String) + string getModelFile() + string getFileLocation() } @@ -117,6 +117,10 @@ class Serializer { class ValidationException extends BaseException { + void constructor(string) } +class TypeNotFoundException extends BaseException { + + void constructor(String,String) + + string getTypeName() +} class Wallet { + Wallet getWallet() + void setWallet(Wallet) diff --git a/packages/composer-common/changelog.txt b/packages/composer-common/changelog.txt index d065b34084..705d983e68 100644 --- a/packages/composer-common/changelog.txt +++ b/packages/composer-common/changelog.txt @@ -12,6 +12,11 @@ # Note that the latest public API is documented using JSDocs and is available in api.txt. # +Version 0.7.6 {1dbfded54f54b7861b8eadb64d95f82a} 2017-06-14 +- Added TypeNotFoundException +- Corrected JSDoc for IllegalModelException.constructor +- Corrected JSDoc for Factory.newRelationship + Version 0.7.5 {b2c1c30d370b04c153426500a03c3061} 2017-06-07 - Added optional JSZip options parameter to toArchive diff --git a/packages/composer-common/lib/factory.js b/packages/composer-common/lib/factory.js index fa78dddc08..da5a11b536 100644 --- a/packages/composer-common/lib/factory.js +++ b/packages/composer-common/lib/factory.js @@ -17,6 +17,8 @@ const debug = require('debug')('ibm-concerto'); const Globalize = require('./globalize'); +const ModelUtil = require('./modelutil'); + const InstanceGenerator = require('./serializer/instancegenerator'); const ValueGeneratorFactory = require('./serializer/valuegenerator'); const ResourceValidator = require('./serializer/resourcevalidator'); @@ -86,7 +88,7 @@ class Factory { *
@@ -229,41 +230,37 @@ class ModelManager { /** * Check that the type is valid and returns the FQN of the type. * @param {string} context - error reporting context - * @param {string} type - a short type name + * @param {string} type - fully qualified type name * @return {string} - the resolved type name (fully qualified) * @throws {IllegalModelException} - if the type is not defined * @private */ resolveType(context, type) { // is the type a primitive? - if (!ModelUtil.isPrimitiveType(type)) { - - let ns = ModelUtil.getNamespace(type); - let modelFile = this.getModelFile(ns); - - if (!modelFile) { - let formatter = Globalize.messageFormatter('modelmanager-resolvetype-nonsfortype'); - throw new IllegalModelException(formatter({ - type: type, - context: context - })); - } + if (ModelUtil.isPrimitiveType(type)) { + return type; + } - if (!modelFile.isLocalType(type)) { - let formatter = Globalize.messageFormatter('modelmanager-resolvetype-notypeinnsforcontext'); - throw new IllegalModelException(formatter({ - context: context, - type: type, - namespace: modelFile.getNamespace() - })); - } - else { - return type; - } + let ns = ModelUtil.getNamespace(type); + let modelFile = this.getModelFile(ns); + if (!modelFile) { + let formatter = Globalize.messageFormatter('modelmanager-resolvetype-nonsfortype'); + throw new IllegalModelException(formatter({ + type: type, + context: context + })); } - else { + + if (modelFile.isLocalType(type)) { return type; } + + let formatter = Globalize.messageFormatter('modelmanager-resolvetype-notypeinnsforcontext'); + throw new IllegalModelException(formatter({ + context: context, + type: type, + namespace: modelFile.getNamespace() + })); } /** @@ -294,35 +291,32 @@ class ModelManager { /** * Look up a type in all registered namespaces. * - * @param {string} type - the fully qualified name of a type - * @return {ClassDeclaration} - the class declaration or null for primitive types - * @throws {Error} - if the type cannot be found + * @param {string} qualifiedName - fully qualified type name. + * @return {ClassDeclaration} - the class declaration for the specified type. + * @throws {TypeNotFoundException} - if the type cannot be found or is a primitive type. * @private */ - getType(type) { - // is the type a primitive? - if (!ModelUtil.isPrimitiveType(type)) { - let ns = ModelUtil.getNamespace(type); - let modelFile = this.getModelFile(ns); - - if (!modelFile) { - let formatter = Globalize.messageFormatter('modelmanager-gettype-noregisteredns'); - throw new Error(formatter({ - type: type - })); - } - - let classDecl = modelFile.getType(type); - - if (!classDecl) { - throw new Error('No type ' + type + ' in namespace ' + modelFile.getNamespace()); - } - - return classDecl; + getType(qualifiedName) { + const namespace = ModelUtil.getNamespace(qualifiedName); + + const modelFile = this.getModelFile(namespace); + if (!modelFile) { + const formatter = Globalize.messageFormatter('modelmanager-gettype-noregisteredns'); + throw new TypeNotFoundException(qualifiedName, formatter({ + type: qualifiedName + })); } - else { - return null; + + const classDecl = modelFile.getType(qualifiedName); + if (!classDecl) { + const formatter = Globalize.messageFormatter('modelmanager-gettype-notypeinns'); + throw new TypeNotFoundException(qualifiedName, formatter({ + type: ModelUtil.getShortName(qualifiedName), + namespace: namespace + })); } + + return classDecl; } /** diff --git a/packages/composer-common/lib/modelutil.js b/packages/composer-common/lib/modelutil.js index 32e41b3ace..9c02a645b5 100644 --- a/packages/composer-common/lib/modelutil.js +++ b/packages/composer-common/lib/modelutil.js @@ -134,6 +134,20 @@ class ModelUtil { return (typeDeclaration !== null && typeDeclaration.isEnum()); } + /** + * Get the fully qualified name of a type. + * @param {string} namespace - namespace of the type. + * @param {string} type - short name of the type. + * @returns {string} the fully qualified type name. + */ + static getFullyQualifiedName(namespace, type) { + if (namespace) { + return namespace + '.' + type; + } else { + return type; + } + } + } module.exports = ModelUtil; diff --git a/packages/composer-common/lib/serializer/jsonpopulator.js b/packages/composer-common/lib/serializer/jsonpopulator.js index 7bd6d6a87a..e19896aaa1 100644 --- a/packages/composer-common/lib/serializer/jsonpopulator.js +++ b/packages/composer-common/lib/serializer/jsonpopulator.js @@ -230,9 +230,6 @@ class JSONPopulator { } const classDeclaration = parameters.modelManager.getType(jsonItem.$class); - if(!classDeclaration) { - throw new Error( 'Failed to find type ' + jsonItem.$class + ' in ModelManager.' ); - } // create a new instance, using the identifier field name as the ID. let subResource = parameters.factory.newResource(classDeclaration.getModelFile().getNamespace(), @@ -258,9 +255,6 @@ class JSONPopulator { } const classDeclaration = parameters.modelManager.getType(jsonObj.$class); - if(!classDeclaration) { - throw new Error( 'Failed to find type ' + jsonObj.$class + ' in ModelManager.' ); - } // create a new instance, using the identifier field name as the ID. let subResource = parameters.factory.newResource(classDeclaration.getModelFile().getNamespace(), diff --git a/packages/composer-common/lib/serializer/resourcevalidator.js b/packages/composer-common/lib/serializer/resourcevalidator.js index 0bbac9a0aa..a49350e669 100644 --- a/packages/composer-common/lib/serializer/resourcevalidator.js +++ b/packages/composer-common/lib/serializer/resourcevalidator.js @@ -318,13 +318,13 @@ class ResourceValidator { // a field that points to a transaction, asset, participant... let classDeclaration = parameters.modelManager.getType(field.getFullyQualifiedTypeName()); if(obj instanceof Identifiable) { - classDeclaration = parameters.modelManager.getType(obj.getFullyQualifiedType()); - - if(!classDeclaration) { + try { + classDeclaration = parameters.modelManager.getType(obj.getFullyQualifiedType()); + } catch (err) { ResourceValidator.reportFieldTypeViolation(parameters.rootResourceIdentifier, propName, obj, field); } - // is it compatible? + // is it compatible? if(!ModelUtil.isAssignableTo(classDeclaration.getModelFile(), classDeclaration.getFullyQualifiedName(), field)) { ResourceValidator.reportInvalidFieldAssignment(parameters.rootResourceIdentifier, propName, obj, field); } diff --git a/packages/composer-common/lib/typenotfoundexception.js b/packages/composer-common/lib/typenotfoundexception.js new file mode 100644 index 0000000000..be6fcdbbac --- /dev/null +++ b/packages/composer-common/lib/typenotfoundexception.js @@ -0,0 +1,56 @@ +/* + * 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 BaseException = require('./baseexception'); +const Globalize = require('./globalize'); + +/** + * Error thrown when a Composer type does not exist. + * @extends BaseException + * @see See [BaseException]{@link module:composer-common.BaseException} + * @class + * @memberof module:composer-common + */ +class TypeNotFoundException extends BaseException { + /** + * Constructor. If the optional 'message' argument is not supplied, it will be set to a default value that + * includes the type name. + * @param {String} typeName - fully qualified type name. + * @param {String} [message] - error message. + */ + constructor(typeName, message) { + if (!message) { + const formatter = Globalize.messageFormatter('typenotfounderror-defaultmessage'); + message = formatter({ + typeName: typeName + }); + } + + super(message); + this.typeName = typeName; + } + + /** + * Get the name of the type that was not found. + * @returns {string} fully qualified type name. + */ + getTypeName() { + return this.typeName; + } + +} + +module.exports = TypeNotFoundException; \ No newline at end of file diff --git a/packages/composer-common/messages/en.json b/packages/composer-common/messages/en.json index 6b642f073c..d0065c9aab 100644 --- a/packages/composer-common/messages/en.json +++ b/packages/composer-common/messages/en.json @@ -8,8 +8,10 @@ " other {You and # others liked this}", "}" ], - "hello": "Hello, {0} {1} {2}", - "hey": "Hey, {first} {middle} {last}", + "test-hello-array": "Hello, {0} {1} {2}", + "test-hello-object": "Hello, {first} {middle} {last}", + "test-repeat-array": "{0} {0} {0}", + "test-repeat-object": "{value} {value} {value}", "classdeclaration-constructor-modelastreq": "ModelFile and AST are required to create a ClassDecl.", "classdeclaration-process-unrecmodelelem": "Unrecognised model element {type}", @@ -34,22 +36,17 @@ "concerto-login-noenrollmentsecret": "enrollmentSecret not specified", "concerto-deploy-nosecuritycontext": "securityContext not specified", - "factory-newinstance-notregisteredwithmm": "ModelFile for namespace {namespace} has not been registered with the ModelManager", - "factory-newinstance-typenotdeclaredinns": "Type {type} is not declared in namespace {namespace}", "factory-newinstance-missingidentifier": "Missing identifier for Type {type} in namespace {namespace}", "factory-newinstance-invalididentifier": "Invalid or missing identifier for Type {type} in namespace {namespace}", "factory-newinstance-abstracttype": "Cannot instantiate Abstract Type {type} in namespace {namespace}", - "factory-newrelationship-notregisteredwithmm": "ModelFile for namespace {namespace} has not been registered with the ModelManager", - "factory-newrelationship-typenotdeclaredinns": "Type {type} is not declared in namespace {namespace}", - "instancegenerator-newinstance-noconcreteclass": "No concrete extending type for {type}", "modelmanager-resolvetype-nonsfortype": "No registered namespace for type {type} in {context}", "modelmanager-resolvetype-notypeinnsforcontext": "No type {type} in namespace {namespace} for {context}", - "modelmanager-gettype-noregisteredns": "No registered namespace for type {type}", - "modelmanager-gettype-notypeinns": "No type {type} in namespace {namespace}", + "modelmanager-gettype-noregisteredns": "Namespace is not defined for type {type}", + "modelmanager-gettype-notypeinns": "Type {type} is not defined in namespace {namespace}", "serializer-constructor-factorynull": "Factory cannot be null", "serializer-constructor-modelmanagernull": "ModelManager cannot be null", @@ -68,6 +65,8 @@ "resourcevalidator-abstractclass": "The class {className} is abstract. Should not have an instance!", "resourcevalidator-undeclaredfield": "Instance {resourceId} has a property named {propertyName} which is not declared in {fullyQualifiedTypeName}", "resourcevalidator-invalidfieldassignment": "Instance {resourceId} has property {propertyName} with type {objectType} that is not derived from {fieldType}", - "resourcevalidator-emptyidentifier": "Instance {resourceId} has an empty identifier." + "resourcevalidator-emptyidentifier": "Instance {resourceId} has an empty identifier.", + + "typenotfounderror-defaultmessage": "Type not found: {typeName}" } } diff --git a/packages/composer-common/test/concerto/i18n.js b/packages/composer-common/test/concerto/i18n.js index d8b8cb532d..289afd044c 100644 --- a/packages/composer-common/test/concerto/i18n.js +++ b/packages/composer-common/test/concerto/i18n.js @@ -30,8 +30,34 @@ describe('Globalization', function() { beforeEach(function() { }); - describe.skip('#check globalization library works', function() { - it('check formatters', function() { + describe('#check globalization library works', function() { + it('numbered variables using Array', function() { + const formatter = Globalize('en').messageFormatter('test-hello-array'); + formatter([ 'Wolfgang', 'Amadeus', 'Mozart' ]).should.equal('Hello, Wolfgang Amadeus Mozart'); + }); + + it('named variables using Object key-value pairs', function() { + const formatter = Globalize('en').messageFormatter('test-hello-object'); + formatter({ + first: 'Wolfgang', + middle: 'Amadeus', + last: 'Mozart' + }).should.equal('Hello, Wolfgang Amadeus Mozart'); + }); + + it('repeated numbered variables using Array', function() { + const formatter = Globalize('en').messageFormatter('test-repeat-array'); + formatter([ 'Bonkers' ]).should.equal('Bonkers Bonkers Bonkers'); + }); + + it('repeated named variables using Object key-value pairs', function() { + const formatter = Globalize('en').messageFormatter('test-repeat-object'); + formatter({ + value: 'Bonkers' + }).should.equal('Bonkers Bonkers Bonkers'); + }); + + it.skip('check formatters', function() { // Use Globalize to format a message with plural inflection. let like = Globalize.messageFormatter('like'); like(0).should.equal('Be the first to like this'); @@ -196,7 +222,7 @@ describe('Globalization', function() { }); }); - describe('#check Factory messages are correct', function() { + describe.skip('#check Factory messages are correct', function() { describe('check messages in newInstance()', function() { it('where namespace hasn\'t been registered in the model manager', function() { let formatter = Globalize.messageFormatter('factory-newinstance-notregisteredwithmm'); @@ -306,7 +332,7 @@ describe('Globalization', function() { }); }); - describe('#check ModelManager messages are correct', function() { + describe('ModelManager messages', function() { describe('check messages in resolveType()', function() { it('when no namespace is registered for type', function(){ let formatter = Globalize.messageFormatter('modelmanager-resolvetype-nonsfortype'); @@ -334,26 +360,26 @@ describe('Globalization', function() { }); }); - describe('check messages in getType()', function() { - it('check no type in namespace', function() { + describe('#getType()', function() { + it('Namespace does not exist', function() { let formatter = Globalize.messageFormatter('modelmanager-gettype-noregisteredns'); formatter({ - type: 'foo' - }).should.equal('No registered namespace for type foo'); + type: 'TYPE' + }).should.equal('Namespace is not defined for type TYPE'); const modelManager = new ModelManager(); expect(function() { - modelManager.getType('foo'); - }).to.throw(Error, 'No registered namespace for type foo'); + modelManager.getType('NAMESPACE.TYPE'); + }).to.throw(Error, 'Namespace is not defined for type NAMESPACE.TYPE'); }); it('check type exists in namespace', function() { let formatter = Globalize.messageFormatter('modelmanager-gettype-notypeinns'); formatter({ - type: 'foo', - namespace: 'bar' - }).should.equal('No type foo in namespace bar'); + type: 'TYPE', + namespace: 'NAMESPACE' + }).should.equal('Type TYPE is not defined in namespace NAMESPACE'); // Cannot be tested - will throw error on line 139 modelmanager.js first }); diff --git a/packages/composer-common/test/factory.js b/packages/composer-common/test/factory.js index 6573fac611..02d3f8228f 100644 --- a/packages/composer-common/test/factory.js +++ b/packages/composer-common/test/factory.js @@ -16,6 +16,7 @@ const Factory = require('../lib/factory'); const ModelManager = require('../lib/modelmanager'); +const TypeNotFoundException = require('../lib/typenotfoundexception'); const uuid = require('uuid'); const should = require('chai').should(); @@ -143,13 +144,13 @@ describe('Factory', () => { it('should throw if namespace missing', () => { (() => { factory.newConcept('org.acme.missing', 'MyConcept'); - }).should.throw(/ModelFile for namespace org.acme.missing has not been registered with the ModelManager/); + }).should.throw(TypeNotFoundException); }); it('should throw if Concept missing', () => { (() => { factory.newConcept('org.acme.test', 'MissingConcept'); - }).should.throw(/Type MissingConcept is not declared in namespace org.acme.test/); + }).should.throw(TypeNotFoundException); }); it('should throw if concept is abstract', () => { @@ -178,6 +179,23 @@ describe('Factory', () => { }); + describe('#newRelationship', function() { + it('should throw if namespace missing', function() { + (() => factory.newRelationship('org.acme.missing', 'MyAsset', 'id')). + should.throw(TypeNotFoundException, /org.acme.missing/); + }); + + it('should throw if type missing', function() { + (() => factory.newRelationship('org.acme.test', 'MissingType', 'id')). + should.throw(TypeNotFoundException, /MissingType/); + }); + + it('should succeed for a valid type', function() { + const relationship = factory.newRelationship('org.acme.test', 'MyAsset', 'id'); + relationship.isRelationship().should.be.true; + }); + }); + describe('#newTransaction', () => { it('should throw if ns not specified', () => { diff --git a/packages/composer-common/test/introspect/functiondeclaration.js b/packages/composer-common/test/introspect/functiondeclaration.js index cc188e6d6b..5a4bc8fad7 100644 --- a/packages/composer-common/test/introspect/functiondeclaration.js +++ b/packages/composer-common/test/introspect/functiondeclaration.js @@ -128,7 +128,7 @@ describe('FunctionDeclaration', () => { (() => { let func = loadFunctionDeclaration('test/data/parser/functiondeclaration.missingtx.js'); func.validate(); - }).should.throw(/No type org.acme.TestTransactionLulz/); + }).should.throw(/TestTransactionLulz/); }); it('should throw if the function refers to a transaction that is not a transaction', () => { diff --git a/packages/composer-common/test/introspect/modelfile.js b/packages/composer-common/test/introspect/modelfile.js index c6d184f95f..da3b3cf4e9 100644 --- a/packages/composer-common/test/introspect/modelfile.js +++ b/packages/composer-common/test/introspect/modelfile.js @@ -18,6 +18,7 @@ const AssetDeclaration = require('../../lib/introspect/assetdeclaration'); const ParticipantDeclaration = require('../../lib/introspect/participantdeclaration'); const TransactionDeclaration = require('../../lib/introspect/transactiondeclaration'); const EventDeclaration = require('../../lib/introspect/eventdeclaration'); +const IllegalModelException = require('../../lib/introspect/illegalmodelexception'); const ModelFile = require('../../lib/introspect/modelfile'); const ModelManager = require('../../lib/modelmanager'); const ParseException = require('../../lib/introspect/parseexception'); @@ -178,7 +179,7 @@ describe('ModelFile', () => { let modelFile = new ModelFile(mockModelManager, model); (() => { modelFile.validate(); - }).should.throw(/No registered namespace for type org.acme.ext.MyAsset2/); + }).should.throw(IllegalModelException, /org.acme.ext/); }); it('should throw if a wildcard import exists for an invalid namespace', () => { @@ -191,7 +192,7 @@ describe('ModelFile', () => { let modelFile = new ModelFile(mockModelManager, model); (() => { modelFile.validate(); - }).should.throw(/No registered namespace for type org.acme.ext.\*/); + }).should.throw(IllegalModelException, /org.acme.ext/); }); it('should throw if an import exists for a type that does not exist in a valid namespace', () => { @@ -211,7 +212,7 @@ describe('ModelFile', () => { let modelFile2 = new ModelFile(mockModelManager, model2); (() => { modelFile2.validate(); - }).should.throw(/No type MyAsset3 in namespace org.acme.ext/); + }).should.throw(IllegalModelException, /MyAsset3/); }); it('should not throw if an import exists for a type that exists in a valid namespace', () => { @@ -229,7 +230,7 @@ describe('ModelFile', () => { let modelFile1 = new ModelFile(mockModelManager, model1); mockModelManager.getModelFile.withArgs('org.acme.ext').returns(modelFile1); let modelFile2 = new ModelFile(mockModelManager, model2); - modelFile2.validate(); + (() => modelFile2.validate()).should.not.throw(); }); it('should not throw if a wildcard import exists for a valid namespace', () => { @@ -247,7 +248,7 @@ describe('ModelFile', () => { let modelFile1 = new ModelFile(mockModelManager, model1); mockModelManager.getModelFile.withArgs('org.acme.ext').returns(modelFile1); let modelFile2 = new ModelFile(mockModelManager, model2); - modelFile2.validate(); + (() => modelFile2.validate()).should.not.throw(); }); }); diff --git a/packages/composer-common/test/modelmanager.js b/packages/composer-common/test/modelmanager.js index 7f592df8df..5d1cfa0b3b 100644 --- a/packages/composer-common/test/modelmanager.js +++ b/packages/composer-common/test/modelmanager.js @@ -19,6 +19,7 @@ const EnumDeclaration = require('../lib/introspect/enumdeclaration'); const ModelFile = require('../lib/introspect/modelfile'); const ModelManager = require('../lib/modelmanager'); const ParticipantDeclaration = require('../lib/introspect/participantdeclaration'); +const TypeNotFoundException = require('../lib/typenotfoundexception'); const TransactionDeclaration = require('../lib/introspect/transactiondeclaration'); const fs = require('fs'); @@ -368,6 +369,41 @@ describe('ModelManager', () => { }); + describe('#getType', function() { + it('should throw an error for a primitive type', function() { + modelManager.addModelFile(modelBase); + (function() { + modelManager.getType('String'); + }).should.throw(TypeNotFoundException); + }); + + it('should throw an error for a namespace that does not exist', function() { + modelManager.addModelFile(modelBase); + (function() { + modelManager.getType('org.acme.nosuchns.SimpleAsset'); + }).should.throw(TypeNotFoundException, /org.acme.nosuchns/); + }); + + it('should throw an error for an empty namespace', function() { + modelManager.addModelFile(modelBase); + (function() { + modelManager.getType('NoSuchAsset'); + }).should.throw(TypeNotFoundException, /NoSuchAsset/); + }); + + it('should throw an error for a type that does not exist', function() { + modelManager.addModelFile(modelBase); + (function() { + modelManager.getType('org.acme.base.NoSuchAsset'); + }).should.throw(TypeNotFoundException, /NoSuchAsset/); + }); + + it('should return the class declaration for a valid type', function() { + modelManager.addModelFile(modelBase); + const declaration = modelManager.getType('org.acme.base.AbstractAsset'); + declaration.getFullyQualifiedName().should.equal('org.acme.base.AbstractAsset'); + }); + }); describe('#toJSON', () => { diff --git a/packages/composer-common/test/models/test.js b/packages/composer-common/test/models/test.js index ce56ac3ce5..9f121c55d8 100644 --- a/packages/composer-common/test/models/test.js +++ b/packages/composer-common/test/models/test.js @@ -18,6 +18,7 @@ const Factory = require('../../lib/factory'); const ModelManager = require('../../lib/modelmanager'); const RelationshipDeclaration = require('../../lib/introspect/relationshipdeclaration'); const Serializer = require('../../lib/serializer'); +const TypeNotFoundException = require('../../lib/typenotfoundexception'); const fs = require('fs'); require('chai').should(); @@ -249,7 +250,7 @@ describe('Test Model', function(){ let vehicleDecl = modelManager.getType('org.acme.Vehicle'); vehicleDecl.should.not.be.null; vehicleDecl.getFullyQualifiedName().should.equal('org.acme.Vehicle'); - (modelManager.getType('String') === null).should.be.true; + (() => { modelManager.getType('String'); }).should.throw(TypeNotFoundException); modelManager.getType('org.acme.Base').getFullyQualifiedName().should.equal('org.acme.Base'); modelManager.getType('concerto.Participant').getName().should.equal('Participant'); diff --git a/packages/composer-common/test/modelutil.js b/packages/composer-common/test/modelutil.js index 8672f3d7cc..bbf01f96a4 100644 --- a/packages/composer-common/test/modelutil.js +++ b/packages/composer-common/test/modelutil.js @@ -156,4 +156,17 @@ describe('ModelUtil', function () { }); + describe('#getFullyQualifiedName', function() { + it('valid inputs', function() { + const result = ModelUtil.getFullyQualifiedName('a.namespace', 'type'); + result.should.equal('a.namespace.type'); + }); + + it('empty namespace should return the type with no leading dot', function() { + const result = ModelUtil.getFullyQualifiedName('', 'type'); + result.should.equal('type'); + }); + + }); + }); diff --git a/packages/composer-common/test/serializer.js b/packages/composer-common/test/serializer.js index ffb318f9d0..7a1f8eab70 100644 --- a/packages/composer-common/test/serializer.js +++ b/packages/composer-common/test/serializer.js @@ -19,6 +19,7 @@ const ModelManager = require('../lib/modelmanager'); const Relationship = require('../lib/model/relationship'); const Resource = require('../lib/model/resource'); const Serializer = require('../lib/serializer'); +const TypeNotFoundException = require('../lib/typenotfoundexception'); require('chai').should(); const sinon = require('sinon'); @@ -99,7 +100,7 @@ describe('Serializer', () => { mockResource.getFullyQualifiedType.returns('org.acme.sample.NoSuchAsset'); (() => { serializer.toJSON(mockResource); - }).should.throw(/No type/); + }).should.throw(TypeNotFoundException, /NoSuchAsset/); }); it('should validate if the validate flag is set to false', () => { @@ -164,7 +165,7 @@ describe('Serializer', () => { let serializer = new Serializer(factory, modelManager); (() => { serializer.fromJSON(mockResource); - }).should.throw(/No type/); + }).should.throw(TypeNotFoundException, /NoSuchAsset/); }); it('should deserialize a valid asset', () => { diff --git a/packages/composer-common/test/serializer/jsonpopulator.js b/packages/composer-common/test/serializer/jsonpopulator.js index 3d49ce9acc..59d897f3c3 100644 --- a/packages/composer-common/test/serializer/jsonpopulator.js +++ b/packages/composer-common/test/serializer/jsonpopulator.js @@ -21,6 +21,7 @@ const ModelManager = require('../../lib/modelmanager'); const Relationship = require('../../lib/model/relationship'); const Resource = require('../../lib/model/resource'); const TypedStack = require('../../lib/serializer/typedstack'); +const TypeNotFoundException = require('../../lib/typenotfoundexception'); require('chai').should(); const sinon = require('sinon'); @@ -191,7 +192,7 @@ describe('JSONPopulator', () => { $class: 'org.acme.NOTAREALTYPE', assetId: 'asset1' }, options); - }).should.throw(/No type/); + }).should.throw(TypeNotFoundException, /NOTAREALTYPE/); }); it('should create a new resource from an object using a $class value that matches the model', () => { @@ -347,7 +348,7 @@ describe('JSONPopulator', () => { }; (() => { jsonPopulator.visitRelationshipDeclaration(relationshipDeclaration1, options); - }).should.throw(/No type/); + }).should.throw(TypeNotFoundException, /NoSuchClass/); }); it('should throw if the JSON data is an object with a class that does not exist', () => { @@ -360,10 +361,10 @@ describe('JSONPopulator', () => { factory: mockFactory, modelManager: modelManager }; - sandbox.stub(modelManager, 'getType').withArgs('org.acme.NoSuchClass').returns(null); + sandbox.stub(modelManager, 'getType').withArgs('org.acme.NoSuchClass').throws(new TypeNotFoundException('org.acme.NoSuchClass')); (() => { jsonPopulator.visitRelationshipDeclaration(relationshipDeclaration1, options); - }).should.throw(/Failed to find type/); + }).should.throw(TypeNotFoundException, /NoSuchClass/); }); it('should create a new relationship from an array of strings', () => { @@ -466,7 +467,7 @@ describe('JSONPopulator', () => { }; (() => { jsonPopulator.visitRelationshipDeclaration(relationshipDeclaration2, options); - }).should.throw(/No type/); + }).should.throw(TypeNotFoundException, /NoSuchClass/); }); it('should throw if the JSON data in the array is an object with a class that does not exist', () => { @@ -479,10 +480,10 @@ describe('JSONPopulator', () => { factory: mockFactory, modelManager: modelManager }; - sandbox.stub(modelManager, 'getType').withArgs('org.acme.NoSuchClass').returns(null); + sandbox.stub(modelManager, 'getType').withArgs('org.acme.NoSuchClass').throws(new TypeNotFoundException('org.acme.NoSuchClass')); (() => { jsonPopulator.visitRelationshipDeclaration(relationshipDeclaration2, options); - }).should.throw(/Failed to find type/); + }).should.throw(TypeNotFoundException, /NoSuchClass/); }); }); diff --git a/packages/composer-common/test/serializer/resourcevalidator.js b/packages/composer-common/test/serializer/resourcevalidator.js index 058af6bbba..7f56bba505 100644 --- a/packages/composer-common/test/serializer/resourcevalidator.js +++ b/packages/composer-common/test/serializer/resourcevalidator.js @@ -17,6 +17,7 @@ const ModelManager = require('../../lib/modelmanager'); const Factory = require('../../lib/factory'); const TypedStack = require('../../lib/serializer/typedstack'); +const TypeNotFoundException = require('../../lib/typenotfoundexception'); const ResourceValidator = require('../../lib/serializer/resourcevalidator'); const chai = require('chai'); @@ -311,8 +312,8 @@ describe('ResourceValidator', function () { modelManager.deleteModelFile('org.acme.l1'); (function () { - assetDeclaration.accept(resourceValidator,parameters ); - }).should.throw(/No registered namespace for type org.acme.l1.Base/); + assetDeclaration.accept(resourceValidator, parameters); + }).should.throw(TypeNotFoundException, /org.acme.l1/); }); it('should detect assigning to a missing type', function () { @@ -325,8 +326,8 @@ describe('ResourceValidator', function () { modelManager.deleteModelFile('org.acme.l3'); (function () { - assetDeclaration.accept(resourceValidator,parameters ); - }).should.throw(/No registered namespace for type org.acme.l3.Car/); + assetDeclaration.accept(resourceValidator, parameters); + }).should.throw(TypeNotFoundException, /org.acme.l3/); }); it('should detect assigning to an abstract type', function () { diff --git a/packages/composer-common/test/typenotfoundexception.js b/packages/composer-common/test/typenotfoundexception.js new file mode 100644 index 0000000000..9da71197aa --- /dev/null +++ b/packages/composer-common/test/typenotfoundexception.js @@ -0,0 +1,45 @@ +/* + * 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 TypeNotFoundException = require('../lib/typenotfoundexception'); + +const chai = require('chai'); +chai.should(); + +describe('TypeNotFoundException', function() { + describe('#constructor', function() { + it('with one argument', function() { + const typeName = 'namespace.TypeName'; + const obj = new TypeNotFoundException(typeName); + obj.toString().should.include(typeName); + }); + + it('with two arguments', function() { + const message = 'MESSAGE_TEXT'; + const obj = new TypeNotFoundException('namespace.TypeName', message); + obj.toString().should.include(message); + }); + }); + + describe('#getTypeName', function() { + it('should return the type', function() { + const typeName = 'namespace.TypeName'; + const obj = new TypeNotFoundException(typeName); + obj.getTypeName().should.equal(typeName); + }); + }); + +}); diff --git a/packages/loopback-connector-composer/test/businessnetworkconnector.js b/packages/loopback-connector-composer/test/businessnetworkconnector.js index 2787b68037..e74c994379 100644 --- a/packages/loopback-connector-composer/test/businessnetworkconnector.js +++ b/packages/loopback-connector-composer/test/businessnetworkconnector.js @@ -30,6 +30,7 @@ const Resource = require('composer-common/lib/model/resource'); const Serializer = require('composer-common').Serializer; const TransactionDeclaration = require('composer-common/lib/introspect/transactiondeclaration'); const TransactionRegistry = require('composer-client/lib/transactionregistry'); +const TypeNotFoundException = require('composer-common/lib/typenotfoundexception'); const chai = require('chai'); const should = chai.should(); @@ -434,7 +435,7 @@ describe('BusinessNetworkConnector', () => { it('should throw for a ClassDeclaration that does not exist', () => { (() => { testConnector.getRegistryForModel(mockBusinessNetworkConnection, 'org.acme.base.Thing'); - }).should.throw(/No type org.acme.base.Thing in namespace org.acme.base/); + }).should.throw(TypeNotFoundException); }); }); @@ -509,7 +510,7 @@ describe('BusinessNetworkConnector', () => { } resolve(result); }); - }).should.be.rejectedWith(/No type org.acme.base.WrongBaseAsset in namespace org.acme.base/); + }).should.be.rejectedWith(TypeNotFoundException); }); it('should handle an error when trying to retrieve a specific Asset for a given id in a where clause', () => { @@ -948,7 +949,7 @@ describe('BusinessNetworkConnector', () => { resolve(); }); }) - .should.be.rejectedWith(/No type org.acme.base.WrongBaseAsset in namespace org.acme.base/) + .should.be.rejectedWith(TypeNotFoundException) .then(() => { sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); @@ -1071,7 +1072,7 @@ describe('BusinessNetworkConnector', () => { resolve(); }); }) - .should.be.rejectedWith(/No type org.acme.base.WrongBaseAsset in namespace org.acme.base/) + .should.be.rejectedWith(TypeNotFoundException) .then(() => { sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' });