From eec358cc9756ba3ba95b677d9bbb0c696ac779cb Mon Sep 17 00:00:00 2001 From: Simon Stone Date: Thu, 6 Jul 2017 23:38:07 +0100 Subject: [PATCH] Complete PersistentModel API tests (resolves #930) --- .../lib/businessnetworkconnector.js | 118 ++-- .../test/assets.js | 392 ++++++++++- .../test/businessnetworkconnector.js | 314 +++++---- .../loopback-connector-composer/test/index.js | 6 + .../test/participants.js | 633 ++++++++++++++++++ 5 files changed, 1239 insertions(+), 224 deletions(-) create mode 100644 packages/loopback-connector-composer/test/participants.js diff --git a/packages/loopback-connector-composer/lib/businessnetworkconnector.js b/packages/loopback-connector-composer/lib/businessnetworkconnector.js index b4b6c7e22f..099adb2275 100644 --- a/packages/loopback-connector-composer/lib/businessnetworkconnector.js +++ b/packages/loopback-connector-composer/lib/businessnetworkconnector.js @@ -257,49 +257,30 @@ class BusinessNetworkConnector extends Connector { let filterKeys = Object.keys(filter); if(filterKeys.indexOf('where') >= 0) { - debug('where', JSON.stringify(filter.where)); - let whereKeys = Object.keys(filter.where); - debug('where keys', whereKeys); + const keys = Object.keys(filter.where); + if (keys.length === 0) { + throw new Error('The destroyAll operation without a where clause is not supported'); + } let identifierField = this.getClassIdentifier(composerModelName); - debug('identifierField', identifierField); + if(!filter.where[identifierField]) { + throw new Error('The specified filter does not match the identifier in the model'); + } // Check we have the right identifier for the object type - if(whereKeys.indexOf(identifierField) >= 0) { - let objectId = filter.where[identifierField]; - if(doResolve) { - return registry.resolve(objectId) - .then((result) => { - debug('Got Result:', result); - return [ result ]; - }) - .catch((error) => { - // check the error - it might be ok just an error indicating that the object doesn't exist - debug('all: error ', error); - if(error.toString().indexOf('does not exist') >= 0) { - return {}; - } else { - throw error; - } - }); - - } else { - return registry.get(objectId) - .then((result) => { - debug('Got Result:', result); - return [ this.serializer.toJSON(result) ]; - }) - .catch((error) => { - // check the error - it might be ok just an error indicating that the object doesn't exist - debug('all: error ', error); - if(error.toString().indexOf('does not exist') >= 0) { - return {}; - } else { - throw error; - } - }); - } + let objectId = filter.where[identifierField]; + if(doResolve) { + return registry.resolve(objectId) + .then((result) => { + debug('Got Result:', result); + return [ result ]; + }); + } else { - throw new Error('The specified filter does not match the identifier in the model'); + return registry.get(objectId) + .then((result) => { + debug('Got Result:', result); + return [ this.serializer.toJSON(result) ]; + }); } } else if(doResolve) { debug('no where filter, about to resolve on all'); @@ -326,6 +307,10 @@ class BusinessNetworkConnector extends Connector { callback(null, result); }) .catch((error) => { + if (error.message.match(/does not exist/)) { + callback(null, []); + return; + } callback(error); }); } @@ -469,13 +454,21 @@ class BusinessNetworkConnector extends Connector { data.$class = composerModelName; } - let resource; + let registry; return this.ensureConnected(options) .then((businessNetworkConnection) => { - resource = this.serializer.fromJSON(data); return this.getRegistryForModel(businessNetworkConnection, composerModelName); }) - .then((registry) => { + .then((registry_) => { + registry = registry_; + return registry.get(objectId); + }) + .then((resource) => { + const object = this.serializer.toJSON(resource); + Object.keys(data).forEach((key) => { + object[key] = data[key]; + }); + resource = this.serializer.fromJSON(object); return registry.update(resource); }) .then(() => { @@ -667,13 +660,14 @@ class BusinessNetworkConnector extends Connector { * Update an instance of an object in Composer. For assets, this method * updates the asset to the default asset registry. * @param {string} lbModelName the fully qualified model name. + * @param {string} where The filter to identify the asset or participant to be removed. * @param {Object} data the data for the asset or transaction. * @param {Object} options the options provided by Loopback. * @param {function} callback the callback to call when complete. * @returns {Promise} A promise that is resolved when complete. */ - update(lbModelName, data, options, callback) { - debug('update', lbModelName, data, options); + update(lbModelName, where, data, options, callback) { + debug('update', lbModelName, where, data, options); let composerModelName = this.getComposerModelName(lbModelName); // If the $class property has not been provided, add it now. @@ -681,17 +675,28 @@ class BusinessNetworkConnector extends Connector { data.$class = composerModelName; } + let idField; return this.ensureConnected(options) .then((businessNetworkConnection) => { + const keys = Object.keys(where); + if (keys.length === 0) { + throw new Error('The update operation without a where clause is not supported'); + } + idField = keys[0]; + if(!this.isValidId(composerModelName, idField)) { + throw new Error('The specified filter does not match the identifier in the model'); + } + return this.getRegistryForModel(businessNetworkConnection, composerModelName); + }) + .then((registry) => { + // Convert the JSON data into a resource. let serializer = this.businessNetworkDefinition.getSerializer(); let resource = serializer.fromJSON(data); - - // The create action is based on the type of the resource. - return this.getRegistryForModel(businessNetworkConnection, composerModelName) - .then((registry) => { - return registry.update(resource); - }); + if (resource.getIdentifier() !== where[idField]) { + throw new Error('The specified resource does not match the identifier in the filter'); + } + return registry.update(resource); }) .then(() => { @@ -721,12 +726,15 @@ class BusinessNetworkConnector extends Connector { let idField, registry; return this.ensureConnected(options) .then((businessNetworkConnection) => { - idField = Object.keys(where)[0]; - if(this.isValidId(composerModelName, idField)) { - return this.getRegistryForModel(businessNetworkConnection, composerModelName); - } else { - callback(new Error('The specified filter does not match the identifier in the model')); + const keys = Object.keys(where); + if (keys.length === 0) { + throw new Error('The destroyAll operation without a where clause is not supported'); } + idField = keys[0]; + if(!this.isValidId(composerModelName, idField)) { + throw new Error('The specified filter does not match the identifier in the model'); + } + return this.getRegistryForModel(businessNetworkConnection, composerModelName); }) .then((registry_) => { registry = registry_; diff --git a/packages/loopback-connector-composer/test/assets.js b/packages/loopback-connector-composer/test/assets.js index 15839e7d52..bd0a52ce98 100644 --- a/packages/loopback-connector-composer/test/assets.js +++ b/packages/loopback-connector-composer/test/assets.js @@ -22,6 +22,7 @@ const connector = require('..'); const fs = require('fs'); const loopback = require('loopback'); const path = require('path'); +const Util = require('composer-common').Util; const chai = require('chai'); const should = chai.should(); @@ -213,6 +214,20 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); }); }); + beforeEach(() => { + return Util.invokeChainCode(businessNetworkConnection.securityContext, 'resetBusinessNetwork', []) + .then(() => { + return businessNetworkConnection.getAssetRegistry('org.acme.bond.BondAsset'); + }) + .then((assetRegistry_) => { + assetRegistry = assetRegistry_; + return assetRegistry.addAll([ + serializer.fromJSON(assetData[0]), + serializer.fromJSON(assetData[1]) + ]); + }); + }); + describe(`#count namespaces[${namespaces}]`, () => { it('should count all of the assets', () => { @@ -248,7 +263,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); .then((asset) => { let json = serializer.toJSON(asset); json.should.deep.equal(assetData[2]); - return assetRegistry.remove('ISIN_3'); }); }); @@ -261,7 +275,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); let json = serializer.toJSON(asset); delete json.$class; json.should.deep.equal(assetData[3]); - return assetRegistry.remove('ISIN_4'); }); }); @@ -285,7 +298,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); .then((asset) => { let json = serializer.toJSON(asset); json.should.deep.equal(assetData[2]); - return assetRegistry.remove('ISIN_3'); }) .then(() => { return assetRegistry.get('ISIN_4'); @@ -294,7 +306,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); let json = serializer.toJSON(asset); delete json.$class; json.should.deep.equal(assetData[3]); - return assetRegistry.remove('ISIN_4'); }); }); @@ -302,10 +313,46 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); describe(`#destroyAll namespaces[${namespaces}]`, () => { + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'BondAsset'].destroyAll() + .should.be.rejectedWith(/is not supported/); + + }); + + it('should remove a single specified asset', () => { + return app.models[prefix + 'BondAsset'].destroyAll({ ISINCode: 'ISIN_1' }) + .then(() => { + return assetRegistry.exists('ISIN_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + it('should return an error if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].destroyAll({ ISINCode: 'ISIN_999' }) + .should.be.rejected; + }); + }); describe(`#destroyById namespaces[${namespaces}]`, () => { + it('should delete the specified asset', () => { + return app.models[prefix + 'BondAsset'].destroyById('ISIN_1') + .then(() => { + return assetRegistry.exists('ISIN_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + it('should return an error if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].destroyById('ISIN_999') + .should.be.rejected; + }); + }); describe(`#exists namespaces[${namespaces}]`, () => { @@ -410,7 +457,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); .then((asset) => { let json = serializer.toJSON(asset); json.should.deep.equal(assetData[2]); - return assetRegistry.remove('ISIN_3'); }); }); @@ -422,7 +468,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); .then((asset) => { let json = serializer.toJSON(asset); json.should.deep.equal(assetData[2]); - return assetRegistry.remove('ISIN_3'); }); }); @@ -430,38 +475,373 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); describe(`#replaceById namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should update the specified asset', () => { + return app.models[prefix + 'BondAsset'].replaceById('ISIN_1', updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should return an error if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].replaceById('ISIN_999', updatedAsset) + .should.be.rejected; + }); + }); describe(`#replaceOrCreate namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should update the specified asset', () => { + return app.models[prefix + 'BondAsset'].replaceOrCreate(updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should create a new asset if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].replaceOrCreate(assetData[2]) + .then(() => { + return assetRegistry.get('ISIN_3'); + }) + .then((asset) => { + let json = serializer.toJSON(asset); + json.should.deep.equal(assetData[2]); + }); + }); + }); describe(`#updateAll namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'BondAsset'].updateAll(updatedAsset) + .should.be.rejectedWith(/is not supported/); + + }); + + it('should remove a single specified asset', () => { + return app.models[prefix + 'BondAsset'].updateAll({ ISINCode: 'ISIN_1' }, updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should return an error if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].updateAll({ ISINCode: 'ISIN_999' }, updatedAsset) + .should.be.rejected; + }); + }); describe(`#upsert namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should update the specified asset', () => { + return app.models[prefix + 'BondAsset'].upsert(updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should create a new asset if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].upsert(assetData[2]) + .then(() => { + return assetRegistry.get('ISIN_3'); + }) + .then((asset) => { + let json = serializer.toJSON(asset); + json.should.deep.equal(assetData[2]); + }); + }); + }); describe(`#upsertWithWhere namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'BondAsset'].upsertWithWhere({}, updatedAsset) + .should.be.rejectedWith(/is not supported/); + + }); + + it('should update the specified asset', () => { + return app.models[prefix + 'BondAsset'].upsertWithWhere({ ISINCode: 'ISIN_1' }, updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should create a new asset if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].upsertWithWhere({ ISINCode: 'ISIN_3' }, assetData[2]) + .then(() => { + return assetRegistry.get('ISIN_3'); + }) + .then((asset) => { + let json = serializer.toJSON(asset); + json.should.deep.equal(assetData[2]); + }); + }); + }); describe(`#destroy namespaces[${namespaces}]`, () => { + it('should delete the specified asset', () => { + return app.models[prefix + 'BondAsset'].findById('ISIN_1') + .then((asset) => { + return asset.destroy(); + }) + .then(() => { + return assetRegistry.exists('ISIN_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + }); describe(`#replaceAttributes namespaces[${namespaces}]`, () => { + it('should replace attributes in the specified asset', () => { + return app.models[prefix + 'BondAsset'].findById('ISIN_1') + .then((asset) => { + return asset.replaceAttributes({ + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }); + }) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + }); describe(`#updateAttribute namespaces[${namespaces}]`, () => { + it('should replace attribute in the specified asset', () => { + return app.models[prefix + 'BondAsset'].findById('ISIN_1') + .then((asset) => { + return asset.updateAttribute('bond', { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + }); + }) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + }); describe(`#updateAttributes namespaces[${namespaces}]`, () => { + it('should replace attributes in the specified asset', () => { + return app.models[prefix + 'BondAsset'].findById('ISIN_1') + .then((asset) => { + return asset.updateAttributes({ + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }); + }) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + }); }); diff --git a/packages/loopback-connector-composer/test/businessnetworkconnector.js b/packages/loopback-connector-composer/test/businessnetworkconnector.js index e74c994379..cf1997881c 100644 --- a/packages/loopback-connector-composer/test/businessnetworkconnector.js +++ b/packages/loopback-connector-composer/test/businessnetworkconnector.js @@ -14,21 +14,18 @@ 'use strict'; -const AssetDeclaration = require('composer-common/lib/introspect/assetdeclaration'); const AssetRegistry = require('composer-client/lib/assetregistry'); const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; const BusinessNetworkConnector = require('../lib/businessnetworkconnector'); const BusinessNetworkConnectionWrapper = require('../lib/businessnetworkconnectionwrapper'); const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition; +const Factory = require('composer-common').Factory; const Introspector = require('composer-common').Introspector; const LoopbackVisitor = require('composer-common').LoopbackVisitor; const ModelManager = require('composer-common').ModelManager; const NodeCache = require('node-cache'); -const ParticipantDeclaration = require('composer-common/lib/introspect/participantdeclaration'); const ParticipantRegistry = require('composer-client/lib/participantregistry'); -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'); @@ -63,6 +60,7 @@ describe('BusinessNetworkConnector', () => { let sandbox; let testConnector; let modelManager; + let factory; let introspector; beforeEach(() => { @@ -90,6 +88,7 @@ describe('BusinessNetworkConnector', () => { modelManager = new ModelManager(); modelManager.addModelFile(MODEL_FILE); introspector = new Introspector(modelManager); + factory = new Factory(modelManager); sandbox = sinon.sandbox.create(); @@ -551,7 +550,7 @@ describe('BusinessNetworkConnector', () => { .then((result) => { sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); - result.should.deep.equal({}); + result.should.deep.equal([]); }); }); @@ -569,7 +568,7 @@ describe('BusinessNetworkConnector', () => { .then((result) => { sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); - result.should.deep.equal({}); + result.should.deep.equal([]); }); }); @@ -881,7 +880,7 @@ describe('BusinessNetworkConnector', () => { describe('#updateAttributes', () => { let mockAssetRegistry; - let mockResourceToUpdate; + let resource; beforeEach(() => { sinon.spy(testConnector, 'getRegistryForModel'); @@ -893,12 +892,14 @@ describe('BusinessNetworkConnector', () => { testConnector.connected = true; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockResourceToUpdate = sinon.createStubInstance(Resource); + resource = factory.newResource('org.acme.base', 'BaseAsset', 'theId'); + mockAssetRegistry.get.withArgs('theId').resolves(resource); + mockSerializer.toJSON.withArgs(resource).returns({ theValue: 'theId', prop1: 'woohoo' }); }); it('should update the attributes for the given object id on the blockchain with no $class attribute', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.resolves(); testConnector.updateAttributes('org.acme.base.BaseAsset', 'theId', { 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -908,18 +909,18 @@ describe('BusinessNetworkConnector', () => { }); }) .then((result) => { - sinon.assert.calledWith(mockSerializer.fromJSON, { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated' }); + sinon.assert.calledWith(mockSerializer.fromJSON, { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated', prop1: 'woohoo' }); sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(testConnector.getRegistryForModel); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResourceToUpdate); + sinon.assert.calledWith(mockAssetRegistry.update, resource); }); }); it('should update the attributes for the given object id on the blockchain with a $class attribute', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.resolves(); testConnector.updateAttributes('org.acme.base.BaseAsset', 'theId', { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -929,18 +930,18 @@ describe('BusinessNetworkConnector', () => { }); }) .then((result) => { - sinon.assert.calledWith(mockSerializer.fromJSON, { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated' }); + sinon.assert.calledWith(mockSerializer.fromJSON, { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated', prop1: 'woohoo' }); sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(testConnector.getRegistryForModel); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResourceToUpdate); + sinon.assert.calledWith(mockAssetRegistry.update, resource); }); }); it('should handle the error when an invalid model is specified', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.resolves(); testConnector.updateAttributes('org.acme.base.WrongBaseAsset', 'theId', { 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -959,7 +960,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an update error from the composer api', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.rejects(new Error('Update error from Composer')); testConnector.updateAttributes('org.acme.base.BaseAsset', 'theId', { 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -979,7 +980,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an update error from the composer api for an asset that does not exist', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.rejects(new Error('does not exist')); testConnector.updateAttributes('org.acme.base.BaseAsset', 'theId', { 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1004,7 +1005,7 @@ describe('BusinessNetworkConnector', () => { describe('#replaceById', () => { let mockAssetRegistry; - let mockResourceToUpdate; + let asset; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1016,12 +1017,12 @@ describe('BusinessNetworkConnector', () => { testConnector.connected = true; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockResourceToUpdate = sinon.createStubInstance(Resource); + asset = factory.newResource('org.acme.base', 'BaseAsset', 'myId'); }); it('should update the attributes for the given object id on the blockchain with no $class attribute', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.resolves(); testConnector.replaceById('org.acme.base.BaseAsset', '1', { 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1036,13 +1037,13 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(testConnector.getRegistryForModel); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResourceToUpdate); + sinon.assert.calledWith(mockAssetRegistry.update, asset); }); }); it('should update the attributes for the given object id on the blockchain with a $class attribute', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.resolves(); testConnector.replaceById('org.acme.base.BaseAsset', '1', { '$class': 'org.acme.base.BaseAsset', 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1057,13 +1058,13 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(testConnector.getRegistryForModel); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResourceToUpdate); + sinon.assert.calledWith(mockAssetRegistry.update, asset); }); }); it('should handle the error when an invalid model is specified', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.resolves(); testConnector.replaceById('org.acme.base.WrongBaseAsset', '1', { 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1082,7 +1083,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an update error from the composer api', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.rejects(new Error('Update error from Composer')); testConnector.replaceById('org.acme.base.BaseAsset', '1', { 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1102,7 +1103,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an update error from the composer api for an asset that does not exist', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.rejects(new Error('does not exist')); testConnector.replaceById('org.acme.base.BaseAsset', '1', { 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1129,10 +1130,9 @@ describe('BusinessNetworkConnector', () => { let mockAssetRegistry; let mockParticipantRegistry; - let mockAssetDeclaration; - let mockParticipantDeclaration; - let mockTransactionDeclaration; - let mockResource; + let asset; + let participant; + let transaction; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1140,22 +1140,17 @@ describe('BusinessNetworkConnector', () => { testConnector.modelManager = modelManager; testConnector.introspector = introspector; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); + mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); - mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); - mockParticipantDeclaration = sinon.createStubInstance(ParticipantDeclaration); - mockTransactionDeclaration = sinon.createStubInstance(TransactionDeclaration); - mockResource = sinon.createStubInstance(Resource); + mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); mockBusinessNetworkDefinition.getSerializer.returns(mockSerializer); - mockSerializer.fromJSON.onFirstCall().returns(mockResource); - + asset = factory.newResource('org.acme.base', 'BaseAsset', 'myId'); + participant = factory.newResource('org.acme.base', 'BaseParticipant', 'myId'); + transaction = factory.newResource('org.acme.base', 'BaseTransaction', 'myId'); }); it('should use the model name as the class name if not specified', () => { - - mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); - + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseAsset', { @@ -1178,9 +1173,8 @@ describe('BusinessNetworkConnector', () => { }); it('should throw if the type is not an asset or a transaction', () => { - mockBusinessNetworkConnection.getAssetRegistry.onFirstCall().resolves(mockAssetRegistry); - mockResource.getClassDeclaration.onFirstCall().returns({}); - + let concept = factory.newConcept('org.acme.base', 'BaseConcept'); + mockSerializer.fromJSON.onFirstCall().returns(concept); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseConcept', { some : 'data' @@ -1194,10 +1188,7 @@ describe('BusinessNetworkConnector', () => { }); it('should add an asset to the default asset registry', () => { - mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); - + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseAsset', { $class : 'org.acme.base.BaseAsset', @@ -1215,16 +1206,14 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(mockBusinessNetworkConnection.getAssetRegistry); sinon.assert.calledWith(mockBusinessNetworkConnection.getAssetRegistry, 'org.acme.base.BaseAsset'); sinon.assert.calledOnce(mockAssetRegistry.add); - sinon.assert.calledWith(mockAssetRegistry.add, mockResource); + sinon.assert.calledWith(mockAssetRegistry.add, asset); should.equal(identifier, undefined); }); }); it('should handle an error adding an asset to the default asset registry', () => { - mockAssetRegistry.add.onFirstCall().throws(new Error('expected error')); - mockBusinessNetworkConnection.getAssetRegistry.onFirstCall().resolves(mockAssetRegistry); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); + mockAssetRegistry.add.onFirstCall().rejects(new Error('expected error')); + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseAsset', { @@ -1240,9 +1229,7 @@ describe('BusinessNetworkConnector', () => { }); it('should add a participant to the default participant registry', () => { - mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); - mockParticipantDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseParticipant'); - mockResource.getClassDeclaration.onFirstCall().returns(mockParticipantDeclaration); + mockSerializer.fromJSON.onFirstCall().returns(participant); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseParticipant', { @@ -1261,16 +1248,14 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(mockBusinessNetworkConnection.getParticipantRegistry); sinon.assert.calledWith(mockBusinessNetworkConnection.getParticipantRegistry, 'org.acme.base.BaseParticipant'); sinon.assert.calledOnce(mockParticipantRegistry.add); - sinon.assert.calledWith(mockParticipantRegistry.add, mockResource); + sinon.assert.calledWith(mockParticipantRegistry.add, participant); should.equal(identifier, undefined); }); }); it('should handle an error adding a participant to the default participant registry', () => { - mockParticipantRegistry.add.onFirstCall().throws(new Error('expected error')); - mockBusinessNetworkConnection.getParticipantRegistry.onFirstCall().resolves(mockParticipantRegistry); - mockParticipantDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseParticipant'); - mockResource.getClassDeclaration.onFirstCall().returns(mockParticipantDeclaration); + mockParticipantRegistry.add.onFirstCall().rejects(new Error('expected error')); + mockSerializer.fromJSON.onFirstCall().returns(participant); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseParticipant', { @@ -1286,9 +1271,7 @@ describe('BusinessNetworkConnector', () => { }); it('should submit a transaction', () => { - mockTransactionDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.Transaction'); - mockResource.getIdentifier.returns('f7cf42d6-492f-4b7e-8b6a-2150ac5bcc5f'); - mockResource.getClassDeclaration.onFirstCall().returns(mockTransactionDeclaration); + mockSerializer.fromJSON.onFirstCall().returns(transaction); return new Promise((resolve, reject) => { testConnector.create('org.acme.Transaction', { @@ -1305,15 +1288,14 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(mockBusinessNetworkConnection.submitTransaction); - sinon.assert.calledWith(mockBusinessNetworkConnection.submitTransaction, mockResource); - identifier.should.equal('f7cf42d6-492f-4b7e-8b6a-2150ac5bcc5f'); + sinon.assert.calledWith(mockBusinessNetworkConnection.submitTransaction, transaction); + identifier.should.equal('myId'); }); }); it('should handle an error submitting a transaction', () => { mockBusinessNetworkConnection.submitTransaction.onFirstCall().rejects(new Error('expected error')); - mockTransactionDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.Transaction'); - mockResource.getClassDeclaration.onFirstCall().returns(mockTransactionDeclaration); + mockSerializer.fromJSON.onFirstCall().returns(transaction); return new Promise((resolve, reject) => { testConnector.create('org.acme.Transaction', { @@ -1332,6 +1314,9 @@ describe('BusinessNetworkConnector', () => { describe('#retrieve', () => { let mockAssetRegistry; + let mockParticipantRegistry; + let asset; + let participant; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1342,17 +1327,15 @@ describe('BusinessNetworkConnector', () => { testConnector.serializer = mockSerializer; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); + mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); + mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); + asset = factory.newResource('org.acme.base', 'BaseAsset', 'theId'); + participant = factory.newResource('org.acme.base', 'BaseParticipant', 'theId'); }); it('should retrieve an asset', () => { - mockAssetRegistry.get.resolves({assetId : 'myId', stringValue : 'a big car'}); + mockAssetRegistry.get.resolves(asset); mockSerializer.toJSON.onFirstCall().returns({assetId : 'myId', stringValue : 'a big car'}); - mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - let mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); - mockModelManager.getType.returns(mockAssetDeclaration); return new Promise((resolve, reject) => { @@ -1376,15 +1359,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle asset errors', () => { - let mockAssetRegistry = sinon.createStubInstance(AssetRegistry); - mockAssetRegistry.get.onFirstCall().throws(new Error('expected error')); - mockBusinessNetworkConnection.getAssetRegistry.onFirstCall().resolves(mockAssetRegistry); - - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - let mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); - mockModelManager.getType.returns(mockAssetDeclaration); - + mockAssetRegistry.get.onFirstCall().rejects(new Error('expected error')); return new Promise((resolve, reject) => { testConnector.retrieve('org.acme.base.BaseAsset', { @@ -1399,15 +1374,8 @@ describe('BusinessNetworkConnector', () => { }); it('should retrieve a participant', () => { - let mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); - mockParticipantRegistry.get.resolves({participantId : 'myId', stringValue : 'a big car'}); + mockParticipantRegistry.get.resolves(participant); mockSerializer.toJSON.onFirstCall().returns({participantId : 'myId', stringValue : 'a big car'}); - mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); - - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - let mockParticipantDeclaration = sinon.createStubInstance(ParticipantDeclaration); - mockModelManager.getType.returns(mockParticipantDeclaration); return new Promise((resolve, reject) => { testConnector.retrieve('org.acme.base.BaseParticipant', 'myId', { test: 'options' }, (error, result) => { @@ -1430,14 +1398,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle participant errors', () => { - let mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); - mockParticipantRegistry.get.onFirstCall().throws(new Error('expected error')); - mockBusinessNetworkConnection.getParticipantRegistry.onFirstCall().resolves(mockParticipantRegistry); - - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - let mockParticipantDeclaration = sinon.createStubInstance(ParticipantDeclaration); - mockModelManager.getType.returns(mockParticipantDeclaration); + mockParticipantRegistry.get.onFirstCall().rejects(new Error('expected error')); return new Promise((resolve, reject) => { testConnector.retrieve('org.acme.base.BaseParticipant', { @@ -1452,10 +1413,6 @@ describe('BusinessNetworkConnector', () => { }); it('should throw error on unsupported type', () => { - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - mockModelManager.getType.returns({}); - return new Promise((resolve, reject) => { testConnector.retrieve('org.acme.base.BaseConcept', 'myId', { test: 'options' }, (error, result) => { if (error) { @@ -1471,9 +1428,9 @@ describe('BusinessNetworkConnector', () => { let mockAssetRegistry; let mockParticipantRegistry; - let mockResource; - let mockAssetDeclaration; - let mockParticipantDeclaration; + let asset; + let participant; + beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); testConnector.businessNetworkDefinition = mockBusinessNetworkDefinition; @@ -1484,21 +1441,16 @@ describe('BusinessNetworkConnector', () => { mockBusinessNetworkDefinition.getSerializer.returns(mockSerializer); mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); - mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); - mockParticipantDeclaration = sinon.createStubInstance(ParticipantDeclaration); - mockResource = sinon.createStubInstance(Resource); - mockSerializer.fromJSON.onFirstCall().returns(mockResource); + asset = factory.newResource('org.acme.base', 'BaseAsset', 'myId'); + participant = factory.newResource('org.acme.base', 'BaseParticipant', 'myId'); mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); }); it('should update an asset', ()=> { - + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); - - testConnector.update('org.acme.base.BaseAsset', { + testConnector.update('org.acme.base.BaseAsset', { theValue: 'myId' }, { $class : 'org.acme.base.BaseAsset', some : 'data' }, { test: 'options' }, (error) => { @@ -1514,16 +1466,14 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(mockBusinessNetworkConnection.getAssetRegistry); sinon.assert.calledWith(mockBusinessNetworkConnection.getAssetRegistry, 'org.acme.base.BaseAsset'); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResource); + sinon.assert.calledWith(mockAssetRegistry.update, asset); }); }); it('should update a participant', ()=> { - mockParticipantDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseParticipant'); - mockResource.getClassDeclaration.onFirstCall().returns(mockParticipantDeclaration); - + mockSerializer.fromJSON.onFirstCall().returns(participant); return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseParticipant', { + testConnector.update('org.acme.base.BaseParticipant', { theValue: 'myId' }, { $class : 'org.acme.base.BaseParticipant', some : 'data' }, { test: 'options' }, (error) => { @@ -1539,13 +1489,13 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(mockBusinessNetworkConnection.getParticipantRegistry); sinon.assert.calledWith(mockBusinessNetworkConnection.getParticipantRegistry, 'org.acme.base.BaseParticipant'); sinon.assert.calledOnce(mockParticipantRegistry.update); - sinon.assert.calledWith(mockParticipantRegistry.update, mockResource); + sinon.assert.calledWith(mockParticipantRegistry.update, participant); }); }); - it('should handle error if unsupported class', () => { + it('should handle error if no where clause', () => { return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseConcept', { + testConnector.update('org.acme.base.BaseConcept', { }, { assetId : 'myId', stringValue : 'a bigger car' }, { test: 'options' }, (error, result) => { @@ -1554,16 +1504,43 @@ describe('BusinessNetworkConnector', () => { } resolve(result); }); - }).should.be.rejectedWith(/No registry for specified model name/); + }).should.be.rejectedWith(/is not supported/); + }); + + it('should handle error if unsupported ID field', () => { + return new Promise((resolve, reject) => { + testConnector.update('org.acme.base.BaseConcept', { doge: 'myId' }, { + assetId : 'myId', + stringValue : 'a bigger car' + }, { test: 'options' }, (error, result) => { + if (error) { + return reject(error); + } + resolve(result); + }); + }).should.be.rejectedWith(/does not match the identifier/); + }); + + it('should handle error if mismatched ID field', () => { + mockSerializer.fromJSON.onFirstCall().returns(asset); + return new Promise((resolve, reject) => { + testConnector.update('org.acme.base.BaseConcept', { theValue: 'doge' }, { + theValue : 'lolz' + }, { test: 'options' }, (error, result) => { + if (error) { + return reject(error); + } + resolve(result); + }); + }).should.be.rejectedWith(/does not match the identifier/); }); it('should handle asset errors', () => { - mockAssetRegistry.update.onFirstCall().throws(new Error('expected error')); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); + mockAssetRegistry.update.onFirstCall().rejects(new Error('expected error')); + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseAsset', { + testConnector.update('org.acme.base.BaseAsset', { theValue: 'myId' }, { assetId : 'myId', stringValue : 'value' }, { test: 'options' }, (error) => { @@ -1576,13 +1553,11 @@ describe('BusinessNetworkConnector', () => { }); it('should handle participant errors', () => { - mockParticipantRegistry.update.onFirstCall().throws(new Error('expected error')); - mockSerializer.fromJSON.onFirstCall().returns(mockResource); - mockParticipantDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseParticipant'); - mockResource.getClassDeclaration.onFirstCall().returns(mockParticipantDeclaration); + mockParticipantRegistry.update.onFirstCall().rejects(new Error('expected error')); + mockSerializer.fromJSON.onFirstCall().returns(participant); return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseParticipant', { + testConnector.update('org.acme.base.BaseParticipant', { theValue: 'myId' }, { participantId : 'myId', stringValue : 'value' }, { test: 'options' }, (error) => { @@ -1595,12 +1570,11 @@ describe('BusinessNetworkConnector', () => { }); it('should handle asset errors for assets that do not exist', () => { - mockAssetRegistry.update.onFirstCall().throws(new Error('does not exist')); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); + mockAssetRegistry.update.onFirstCall().rejects(new Error('does not exist')); + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseAsset', { + testConnector.update('org.acme.base.BaseAsset', { theValue: 'myId' }, { assetId : 'myId', stringValue : 'value' }, { test: 'options' }, (error) => { @@ -1622,7 +1596,7 @@ describe('BusinessNetworkConnector', () => { describe('#destroy', () => { let mockAssetRegistry; - let mockResourceToDelete; + let resourceToDelete; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1633,11 +1607,11 @@ describe('BusinessNetworkConnector', () => { testConnector.connected = true; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockResourceToDelete = sinon.createStubInstance(Resource); + resourceToDelete = factory.newResource('org.acme.base', 'BaseAsset', 'foo'); }); it('should delete the object for the given id from the blockchain', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.resolves(); return new Promise((resolve, reject) => { testConnector.destroy('org.acme.base.BaseAsset','foo' , { test: 'options' }, (error) => { @@ -1676,7 +1650,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle an error when calling composer remove for the given id', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.rejects(new Error('removal error')); return new Promise((resolve, reject) => { testConnector.destroy('org.acme.base.BaseAsset','foo', { test: 'options' }, (error) => { @@ -1696,7 +1670,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle an error when calling composer remove for an asset that does not exist', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.rejects(new Error('does not exist')); return new Promise((resolve, reject) => { testConnector.destroy('org.acme.base.BaseAsset', 'foo' , { test: 'options' }, (error) => { @@ -1722,7 +1696,7 @@ describe('BusinessNetworkConnector', () => { describe('#destroyAll', () => { let mockAssetRegistry; - let mockResourceToDelete; + let resourceToDelete; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1733,11 +1707,11 @@ describe('BusinessNetworkConnector', () => { testConnector.connected = true; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockResourceToDelete = sinon.createStubInstance(Resource); + resourceToDelete = factory.newResource('org.acme.base', 'BaseAsset', 'foo'); }); it('should delete the object for the given id from the blockchain', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.resolves(); return new Promise((resolve, reject) => { testConnector.destroyAll('org.acme.base.BaseAsset', { 'theValue' : 'foo' }, { test: 'options' }, (error) => { @@ -1755,6 +1729,23 @@ describe('BusinessNetworkConnector', () => { }); }); + it('should handle an error when an empty where clause is specified', () => { + mockAssetRegistry.get.rejects(new Error('get error')); + return new Promise((resolve, reject) => { + testConnector.destroyAll('org.acme.base.BaseAsset', {}, { test: 'options' }, (error) => { + if(error) { + return reject(error); + } + resolve(); + }); + }) + .should.be.rejectedWith(/is not supported/) + .then(() => { + sinon.assert.calledOnce(testConnector.ensureConnected); + sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); + }); + }); + it('should handle an error when an invalid Object identifier is specified', () => { mockAssetRegistry.get.rejects(new Error('get error')); return new Promise((resolve, reject) => { @@ -1791,7 +1782,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle an error when calling composer remove for the given id', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.rejects(new Error('removal error')); return new Promise((resolve, reject) => { testConnector.destroyAll('org.acme.base.BaseAsset', { 'theValue' : 'foo' }, { test: 'options' }, (error) => { @@ -1811,7 +1802,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle an error when calling composer remove for an asset that does not exist', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.rejects(new Error('does not exist')); return new Promise((resolve, reject) => { testConnector.destroyAll('org.acme.base.BaseAsset', { 'theValue' : 'foo' }, { test: 'options' }, (error) => { @@ -1922,25 +1913,23 @@ describe('BusinessNetworkConnector', () => { describe('#getAllTransactions', () => { let mockTransactionRegistry; - let mockTransaction1, mockTransaction2; + let transaction1, transaction2; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); testConnector.connected = true; mockTransactionRegistry = sinon.createStubInstance(TransactionRegistry); mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); - mockTransaction1 = sinon.createStubInstance(Resource); - mockTransaction1.transactionId = 'tx1'; - mockTransaction2 = sinon.createStubInstance(Resource); - mockTransaction2.transactionId = 'tx2'; + transaction1 = factory.newResource('org.acme.base', 'BaseTransaction', 'tx1'); + transaction2 = factory.newResource('org.acme.base', 'BaseTransaction', 'tx2'); testConnector.serializer = mockSerializer; }); it('should get all of the transactions in the transaction registry', () => { mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); - mockTransactionRegistry.getAll.resolves([mockTransaction1, mockTransaction2]); - mockSerializer.toJSON.withArgs(mockTransaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); - mockSerializer.toJSON.withArgs(mockTransaction2).returns({ transactionId: 'tx2', $class: 'sometx' }); + mockTransactionRegistry.getAll.resolves([transaction1, transaction2]); + mockSerializer.toJSON.withArgs(transaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); + mockSerializer.toJSON.withArgs(transaction2).returns({ transactionId: 'tx2', $class: 'sometx' }); const cb = sinon.stub(); return testConnector.getAllTransactions({ test: 'options' }, cb) .then(() => { @@ -1979,22 +1968,21 @@ describe('BusinessNetworkConnector', () => { describe('#getTransactionByID', () => { let mockTransactionRegistry; - let mockTransaction1; + let transaction; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); testConnector.connected = true; mockTransactionRegistry = sinon.createStubInstance(TransactionRegistry); mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); - mockTransaction1 = sinon.createStubInstance(Resource); - mockTransaction1.transactionId = 'tx1'; + transaction = factory.newResource('org.acme.base', 'BaseTransaction', 'tx1'); testConnector.serializer = mockSerializer; }); it('should get the specified transaction in the transaction registry', () => { mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); - mockTransactionRegistry.get.withArgs('tx1').resolves(mockTransaction1); - mockSerializer.toJSON.withArgs(mockTransaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); + mockTransactionRegistry.get.withArgs('tx1').resolves(transaction); + mockSerializer.toJSON.withArgs(transaction).returns({ transactionId: 'tx1', $class: 'sometx' }); const cb = sinon.stub(); return testConnector.getTransactionByID('tx1', { test: 'options' }, cb) .then(() => { @@ -2013,7 +2001,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an error getting the specified transaction in the transaction registry', () => { mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); mockTransactionRegistry.get.withArgs('tx1').rejects(new Error('such error')); - mockSerializer.toJSON.withArgs(mockTransaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); + mockSerializer.toJSON.withArgs(transaction).returns({ transactionId: 'tx1', $class: 'sometx' }); const cb = sinon.stub(); return testConnector.getTransactionByID('tx1', { test: 'options' }, cb) .then(() => { @@ -2029,7 +2017,7 @@ describe('BusinessNetworkConnector', () => { it('should return a 404 error getting the specified transaction in the transaction registry', () => { mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); mockTransactionRegistry.get.withArgs('tx1').rejects(new Error('the thing does not exist')); - mockSerializer.toJSON.withArgs(mockTransaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); + mockSerializer.toJSON.withArgs(transaction).returns({ transactionId: 'tx1', $class: 'sometx' }); const cb = sinon.stub(); return testConnector.getTransactionByID('tx1', { test: 'options' }, cb) .then(() => { diff --git a/packages/loopback-connector-composer/test/index.js b/packages/loopback-connector-composer/test/index.js index 65c569ecad..f0cbd51988 100644 --- a/packages/loopback-connector-composer/test/index.js +++ b/packages/loopback-connector-composer/test/index.js @@ -74,6 +74,7 @@ describe('loopback-connector-composer', () => { sinon.assert.calledOnce(connectorModule.createConnector); sinon.assert.calledWith(connectorModule.createConnector, dataSource.settings); sinon.assert.calledOnce(mockBusinessNetworkConnector.connect); + mockBusinessNetworkConnector.connecting = true; }); }); @@ -94,6 +95,7 @@ describe('loopback-connector-composer', () => { sinon.assert.calledOnce(connectorModule.createConnector); sinon.assert.calledWith(connectorModule.createConnector, {}); sinon.assert.calledOnce(mockBusinessNetworkConnector.connect); + mockBusinessNetworkConnector.connecting = true; }); }); @@ -102,6 +104,10 @@ describe('loopback-connector-composer', () => { settings : {} }; connectorModule.initialize(dataSource); + dataSource.connector.should.be.an.instanceOf(BusinessNetworkConnector); + sinon.assert.calledOnce(connectorModule.createConnector); + sinon.assert.calledWith(connectorModule.createConnector, {}); + mockBusinessNetworkConnector.connecting = false; }); }); }); diff --git a/packages/loopback-connector-composer/test/participants.js b/packages/loopback-connector-composer/test/participants.js new file mode 100644 index 0000000000..72d46bb54b --- /dev/null +++ b/packages/loopback-connector-composer/test/participants.js @@ -0,0 +1,633 @@ +/* + * 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 AdminConnection = require('composer-admin').AdminConnection; +const BrowserFS = require('browserfs/dist/node/index'); +const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; +const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition; +const connector = require('..'); +const fs = require('fs'); +const loopback = require('loopback'); +const path = require('path'); +const Util = require('composer-common').Util; + +const chai = require('chai'); +const should = chai.should(); +chai.use(require('chai-as-promised')); + +const bfs_fs = BrowserFS.BFSRequire('fs'); + +['always', 'never'].forEach((namespaces) => { + + const prefix = namespaces === 'always' ? 'org_acme_bond_' : ''; + + const participantData = [{ + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alice' + }, { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_2', + name: 'Bob' + }, { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_3', + name: 'Charlie' + }, { + // $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_4', + name: 'Daria' + }]; + + describe(`Participant persisted model unit tests namespaces[${namespaces}]`, () => { + + let app; + let dataSource; + let businessNetworkConnection; + let participantRegistry; + let serializer; + + before(() => { + BrowserFS.initialize(new BrowserFS.FileSystem.InMemory()); + const adminConnection = new AdminConnection({ fs: bfs_fs }); + return adminConnection.createProfile('defaultProfile', { + type : 'embedded' + }) + .then(() => { + return adminConnection.connect('defaultProfile', 'admin', 'Xurw3yU9zI0l'); + }) + .then(() => { + const banana = fs.readFileSync(path.resolve(__dirname, 'bond-network.bna')); + return BusinessNetworkDefinition.fromArchive(banana); + }) + .then((businessNetworkDefinition) => { + serializer = businessNetworkDefinition.getSerializer(); + return adminConnection.deploy(businessNetworkDefinition); + }) + .then(() => { + app = loopback(); + const connectorSettings = { + name: 'composer', + connector: connector, + connectionProfileName: 'defaultProfile', + businessNetworkIdentifier: 'bond-network', + participantId: 'admin', + participantPwd: 'adminpw', + namespaces: namespaces, + fs: bfs_fs + }; + dataSource = app.loopback.createDataSource('composer', connectorSettings); + return new Promise((resolve, reject) => { + console.log('Discovering types from business network definition ...'); + dataSource.discoverModelDefinitions({}, (error, modelDefinitions) => { + if (error) { + return reject(error); + } + resolve(modelDefinitions); + }); + }); + }) + .then((modelDefinitions) => { + console.log('Discovered types from business network definition'); + console.log('Generating schemas for all types in business network definition ...'); + return modelDefinitions.reduce((promise, modelDefinition) => { + return promise.then((schemas) => { + return new Promise((resolve, reject) => { + dataSource.discoverSchemas(modelDefinition.name, { visited: {}, associations: true }, (error, modelSchema) => { + if (error) { + return reject(error); + } + schemas.push(modelSchema); + resolve(schemas); + }); + }); + }); + }, Promise.resolve([])); + }) + .then((modelSchemas) => { + console.log('Generated schemas for all types in business network definition'); + console.log('Adding schemas for all types to Loopback ...'); + modelSchemas.forEach((modelSchema) => { + let model = app.loopback.createModel(modelSchema); + app.model(model, { + dataSource: dataSource, + public: true + }); + }); + businessNetworkConnection = new BusinessNetworkConnection({ fs: bfs_fs }); + return businessNetworkConnection.connect('defaultProfile', 'bond-network', 'admin', 'Xurw3yU9zI0l'); + }) + .then(() => { + return businessNetworkConnection.getParticipantRegistry('org.acme.bond.Issuer'); + }) + .then((participantRegistry_) => { + participantRegistry = participantRegistry_; + return participantRegistry.addAll([ + serializer.fromJSON(participantData[0]), + serializer.fromJSON(participantData[1]) + ]); + }); + }); + + beforeEach(() => { + return Util.invokeChainCode(businessNetworkConnection.securityContext, 'resetBusinessNetwork', []) + .then(() => { + return businessNetworkConnection.getParticipantRegistry('org.acme.bond.Issuer'); + }) + .then((participantRegistry_) => { + participantRegistry = participantRegistry_; + return participantRegistry.addAll([ + serializer.fromJSON(participantData[0]), + serializer.fromJSON(participantData[1]) + ]); + }); + }); + + describe(`#count namespaces[${namespaces}]`, () => { + + it('should count all of the participants', () => { + return app.models[prefix + 'Issuer'].count() + .then((count) => { + count.should.equal(2); + }); + }); + + it('should count an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].count({ memberId: 'MEMBER_1' }) + .then((count) => { + count.should.equal(1); + }); + }); + + it('should count an non-existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].count({ memberId: 'MEMBER_999' }) + .then((count) => { + count.should.equal(0); + }); + }); + + }); + + describe(`#create namespaces[${namespaces}]`, () => { + + it('should create the specified participant', () => { + return app.models[prefix + 'Issuer'].create(participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + it('should create the specified participant without a $class property', () => { + return app.models[prefix + 'Issuer'].create(participantData[3]) + .then(() => { + return participantRegistry.get('MEMBER_4'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + delete json.$class; + json.should.deep.equal(participantData[3]); + }); + }); + + it('should return an error if the specified participant already exists', () => { + return app.models[prefix + 'Issuer'].create(participantData[0]) + .should.be.rejected; + }); + + it('should create the specified array of participants', () => { + return new Promise((resolve, reject) => { + return app.models[prefix + 'Issuer'].create([ participantData[2], participantData[3] ], (err) => { + if (err) { + return reject(err); + } + resolve(); + }); + }) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }) + .then(() => { + return participantRegistry.get('MEMBER_4'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + delete json.$class; + json.should.deep.equal(participantData[3]); + }); + }); + + }); + + describe(`#destroyAll namespaces[${namespaces}]`, () => { + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'Issuer'].destroyAll() + .should.be.rejectedWith(/is not supported/); + + }); + + it('should remove a single specified participant', () => { + return app.models[prefix + 'Issuer'].destroyAll({ memberId: 'MEMBER_1' }) + .then(() => { + return participantRegistry.exists('MEMBER_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + it('should return an error if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].destroyAll({ memberId: 'MEMBER_999' }) + .should.be.rejected; + }); + + }); + + describe(`#destroyById namespaces[${namespaces}]`, () => { + + it('should delete the specified participant', () => { + return app.models[prefix + 'Issuer'].destroyById('MEMBER_1') + .then(() => { + return participantRegistry.exists('MEMBER_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + it('should return an error if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].destroyById('MEMBER_999') + .should.be.rejected; + }); + + }); + + describe(`#exists namespaces[${namespaces}]`, () => { + + it('should check the existence of an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].exists('MEMBER_1') + .then((exists) => { + exists.should.be.true; + }); + }); + + it('should check the existence of an non-existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].exists('MEMBER_999') + .then((exists) => { + exists.should.be.false; + }); + }); + + }); + + describe(`#find namespaces[${namespaces}]`, () => { + + it('should find all existing participants', () => { + return app.models[prefix + 'Issuer'].find() + .then((participants) => { + JSON.parse(JSON.stringify(participants)).should.deep.equal([participantData[0], participantData[1]]); + }); + }); + + it('should find an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].find({ where: { memberId: 'MEMBER_1' } }) + .then((participants) => { + JSON.parse(JSON.stringify(participants)).should.deep.equal([participantData[0]]); + }); + }); + + }); + + describe(`#findById namespaces[${namespaces}]`, () => { + + it('should find an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + }); + }); + + it('should not find an non-existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_999') + .then((participant) => { + should.equal(participant, null); + }); + }); + + }); + + describe(`#findOne namespaces[${namespaces}]`, () => { + + it('should find the first of all existing participants', () => { + return app.models[prefix + 'Issuer'].findOne() + .then((participant) => { + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + }); + }); + + it('should find an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].findOne({ where: { memberId: 'MEMBER_1' } }) + .then((participant) => { + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + }); + }); + + }); + + describe(`#findOrCreate namespaces[${namespaces}]`, () => { + + it('should find an existing participant using the input participant', () => { + return app.models[prefix + 'Issuer'].findOrCreate(participantData[0]) + .then((parts) => { + const participant = parts[0]; + const created = parts[1]; + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + created.should.be.false; + }); + }); + + it('should find an existing participant using a where clause', () => { + return app.models[prefix + 'Issuer'].findOrCreate({ where: { memberId: 'MEMBER_1' } }, participantData[0]) + .then((parts) => { + const participant = parts[0]; + const created = parts[1]; + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + created.should.be.false; + }); + }); + + it('should not find and create the specified participant using the input participant', () => { + return app.models[prefix + 'Issuer'].findOrCreate(participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + it('should not find and create the specified participant using a where clause', () => { + return app.models[prefix + 'Issuer'].findOrCreate({ where: { memberId: 'MEMBER_3' } }, participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + }); + + describe(`#replaceById namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should update the specified participant', () => { + return app.models[prefix + 'Issuer'].replaceById('MEMBER_1', updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should return an error if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].replaceById('MEMBER_999', updatedParticipant) + .should.be.rejected; + }); + + }); + + describe(`#replaceOrCreate namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should update the specified participant', () => { + return app.models[prefix + 'Issuer'].replaceOrCreate(updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should create a new participant if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].replaceOrCreate(participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + }); + + describe(`#updateAll namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'Issuer'].updateAll(updatedParticipant) + .should.be.rejectedWith(/is not supported/); + + }); + + it('should remove a single specified participant', () => { + return app.models[prefix + 'Issuer'].updateAll({ memberId: 'MEMBER_1' }, updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should return an error if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].updateAll({ memberId: 'MEMBER_999' }, updatedParticipant) + .should.be.rejected; + }); + + }); + + describe(`#upsert namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should update the specified participant', () => { + return app.models[prefix + 'Issuer'].upsert(updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should create a new participant if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].upsert(participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + }); + + describe(`#upsertWithWhere namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'Issuer'].upsertWithWhere({}, updatedParticipant) + .should.be.rejectedWith(/is not supported/); + + }); + + it('should update the specified participant', () => { + return app.models[prefix + 'Issuer'].upsertWithWhere({ memberId: 'MEMBER_1' }, updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should create a new participant if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].upsertWithWhere({ memberId: 'MEMBER_3' }, participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + }); + + describe(`#destroy namespaces[${namespaces}]`, () => { + + it('should delete the specified participant', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + return participant.destroy(); + }) + .then(() => { + return participantRegistry.exists('MEMBER_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + }); + + describe(`#replaceAttributes namespaces[${namespaces}]`, () => { + + it('should replace attributes in the specified participant', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + return participant.replaceAttributes({ + name: 'Alexa' + }); + }) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + }); + + describe(`#updateAttribute namespaces[${namespaces}]`, () => { + + it('should replace attribute in the specified participant', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + return participant.updateAttribute('name', 'Alexa'); + }) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + }); + + describe(`#updateAttributes namespaces[${namespaces}]`, () => { + + it('should replace attributes in the specified participant', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + return participant.updateAttributes({ + name: 'Alexa' + }); + }) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + }); + + }); + +});