diff --git a/packages/composer-connector-embedded/lib/embeddedconnection.js b/packages/composer-connector-embedded/lib/embeddedconnection.js index d0a9b15766..b13436b6aa 100644 --- a/packages/composer-connector-embedded/lib/embeddedconnection.js +++ b/packages/composer-connector-embedded/lib/embeddedconnection.js @@ -444,6 +444,18 @@ class EmbeddedConnection extends Connection { return null; } + /** + * Undeploy a business network definition. + * @param {SecurityContext} securityContext The participant's security context. + * @param {String} networkName Name of the business network to remove + * @async + */ + async undeploy(securityContext, networkName) { + await this.dataService.removeAllData(); + delete businessNetworks[networkName]; + delete chaincodes[networkName]; + } + /** * Get the native API for this connection. The native API returned is specific * to the underlying blockchain platform, and may throw an error if there is no diff --git a/packages/composer-connector-embedded/test/embeddedconnection.js b/packages/composer-connector-embedded/test/embeddedconnection.js index c8b6023e2c..d78a37b074 100644 --- a/packages/composer-connector-embedded/test/embeddedconnection.js +++ b/packages/composer-connector-embedded/test/embeddedconnection.js @@ -626,6 +626,15 @@ describe('EmbeddedConnection', () => { }); + describe('#undeploy', () => { + it('should remove prevoiusly installed business network', async () => { + await connection.install(mockSecurityContext, businessNetworkDefinition); + await connection.undeploy(mockSecurityContext, businessNetworkDefinition.getName()); + await connection.install(mockSecurityContext, businessNetworkDefinition) + .should.not.be.rejected; + }); + }); + describe('#getNativeAPI', () => { it('should throw as not supported', () => { diff --git a/packages/composer-connector-web/lib/webconnection.js b/packages/composer-connector-web/lib/webconnection.js index 9601fd70cb..27ed92de7c 100644 --- a/packages/composer-connector-web/lib/webconnection.js +++ b/packages/composer-connector-web/lib/webconnection.js @@ -156,7 +156,7 @@ class WebConnection extends Connection { * @async */ async undeploy(securityContext, networkName) { - await WebDataService.newNetworkDataService(networkName, true).destroy(); + await WebDataService.newNetworkDataService(networkName, true).removeAllData(); const chaincodeStore = await this.getChaincodeStore(); await chaincodeStore.removeNetwork(networkName); this.savedNetwork = null; diff --git a/packages/composer-playground/.istanbul.yml b/packages/composer-playground/.istanbul.yml index 316edfe317..cb5462f889 100644 --- a/packages/composer-playground/.istanbul.yml +++ b/packages/composer-playground/.istanbul.yml @@ -7,6 +7,6 @@ instrumentation: check: global: statements: 99.76 - branches: 98.65 - functions: 98.83 + branches: 98.66 + functions: 98.82 lines: 99.8 diff --git a/packages/composer-playground/src/app/app.component.spec.ts b/packages/composer-playground/src/app/app.component.spec.ts index 5ade706d3c..0cc1cddc78 100644 --- a/packages/composer-playground/src/app/app.component.spec.ts +++ b/packages/composer-playground/src/app/app.component.spec.ts @@ -18,10 +18,10 @@ /* tslint:disable:use-host-property-decorator*/ /* tslint:disable:no-input-rename*/ /* tslint:disable:member-ordering*/ -import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { BehaviorSubject, Subject } from 'rxjs/Rx'; -import { Directive, Input, Injectable } from '@angular/core'; +import { Directive, Injectable, Input } from '@angular/core'; import { AppComponent } from './app.component'; import { ClientService } from './services/client.service'; import { InitializationService } from './services/initialization.service'; @@ -29,7 +29,7 @@ import { IdentityCardService } from './services/identity-card.service'; import { LocalStorageService } from 'angular-2-local-storage'; import { AlertService } from './basic-modals/alert.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { ActivatedRoute, Router, NavigationEnd, NavigationStart } from '@angular/router'; +import { ActivatedRoute, NavigationEnd, NavigationStart, Router } from '@angular/router'; import { BusinessNetworkConnection } from 'composer-client'; import { AdminService } from './services/admin.service'; import { AboutService } from './services/about.service'; @@ -66,7 +66,6 @@ class RouterStub { } set eventParams(event) { - console.log('I AM A CHICKEN'); let nav; if (event.nav === 'end') { nav = new NavigationEnd(0, event.url, event.urlAfterRedirects); diff --git a/packages/composer-playground/src/app/connection-profile/connection-profile.component.spec.ts b/packages/composer-playground/src/app/connection-profile/connection-profile.component.spec.ts index 00c848509f..719b4d56a4 100644 --- a/packages/composer-playground/src/app/connection-profile/connection-profile.component.spec.ts +++ b/packages/composer-playground/src/app/connection-profile/connection-profile.component.spec.ts @@ -16,9 +16,8 @@ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ /* tslint:disable:object-literal-key-quotes */ -import { ComponentFixture, TestBed, fakeAsync, tick, async } from '@angular/core/testing'; -import { EventEmitter } from '@angular/core'; -import { FormsModule, Validators } from '@angular/forms'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; import { ConnectionProfileComponent } from './connection-profile.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ConnectionProfileService } from '../services/connectionprofile.service'; diff --git a/packages/composer-playground/src/app/identity/issue-identity/issue-identity.component.spec.ts b/packages/composer-playground/src/app/identity/issue-identity/issue-identity.component.spec.ts index 71a69237fa..6fe28b50d2 100644 --- a/packages/composer-playground/src/app/identity/issue-identity/issue-identity.component.spec.ts +++ b/packages/composer-playground/src/app/identity/issue-identity/issue-identity.component.spec.ts @@ -15,8 +15,8 @@ /* tslint:disable:no-unused-expression */ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ -import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { Directive, Input, Component } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { Directive, Input } from '@angular/core'; import { FormsModule } from '@angular/forms'; import * as sinon from 'sinon'; @@ -28,7 +28,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { IssueIdentityComponent } from './issue-identity.component'; import { AlertService } from '../../basic-modals/alert.service'; import { ClientService } from '../../services/client.service'; -import { BusinessNetworkConnection, ParticipantRegistry } from 'composer-client'; +import { BusinessNetworkConnection } from 'composer-client'; import { Resource } from 'composer-common'; @Directive({ @@ -127,7 +127,6 @@ describe('IssueIdentityComponent', () => { component['loadParticipants'](); let expected = ['Emperor', 'King', 'Macaroni']; - console.log('PARTICIPANT FQIs', component['participantFQIs']); component['participantFQIs'].should.deep.equal(expected); })); }); diff --git a/packages/composer-playground/src/app/login/login.component.spec.ts b/packages/composer-playground/src/app/login/login.component.spec.ts index 9061ed221b..94ec279dd4 100644 --- a/packages/composer-playground/src/app/login/login.component.spec.ts +++ b/packages/composer-playground/src/app/login/login.component.spec.ts @@ -18,9 +18,9 @@ /* tslint:disable:use-host-property-decorator*/ /* tslint:disable:no-input-rename*/ /* tslint:disable:member-ordering*/ -import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { Input, Component, Output, EventEmitter } from '@angular/core'; -import { Router, NavigationEnd, NavigationStart, ActivatedRoute } from '@angular/router'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ActivatedRoute, NavigationEnd, NavigationStart, Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { BehaviorSubject } from 'rxjs/Rx'; @@ -32,10 +32,9 @@ import { AlertService } from '../basic-modals/alert.service'; import { ConfigService } from '../services/config.service'; import { Config } from '../services/config/configStructure.service'; import { SampleBusinessNetworkService } from '../services/samplebusinessnetwork.service'; -import { BusinessNetworkDefinition } from 'composer-common'; +import { BusinessNetworkDefinition, IdCard } from 'composer-common'; -import { DrawerService, DrawerDismissReasons } from '../common/drawer'; -import { IdCard } from 'composer-common'; +import { DrawerDismissReasons, DrawerService } from '../common/drawer'; import { LoginComponent } from './login.component'; import * as fileSaver from 'file-saver'; @@ -764,6 +763,10 @@ describe(`LoginComponent`, () => { mockIdentityCardService.deleteIdentityCard.should.have.been.calledWith('myCardRef'); loadIdentityCardsStub.should.have.been.called; + mockAlertService.busyStatus$.next.should.have.been.calledWith({ + title: 'Undeploying business network', + force: true + }); mockAlertService.successStatus$.next.should.have.been.called; mockAlertService.errorStatus$.next.should.not.have.been.called; })); diff --git a/packages/composer-playground/src/app/login/login.component.ts b/packages/composer-playground/src/app/login/login.component.ts index 46e9f5df33..0c41d844ab 100644 --- a/packages/composer-playground/src/app/login/login.component.ts +++ b/packages/composer-playground/src/app/login/login.component.ts @@ -12,7 +12,7 @@ * limitations under the License. */ import { Component, OnInit } from '@angular/core'; -import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'; +import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { IdentityService } from '../services/identity.service'; import { ClientService } from '../services/client.service'; import { InitializationService } from '../services/initialization.service'; @@ -22,8 +22,8 @@ import { ConnectConfirmComponent } from '../basic-modals/connect-confirm/connect import { IdentityCardService } from '../services/identity-card.service'; import { ConfigService } from '../services/config.service'; import { Config } from '../services/config/configStructure.service'; -import { NgbModal, ModalDismissReasons } from '@ng-bootstrap/ng-bootstrap'; -import { DrawerService, DrawerDismissReasons } from '../common/drawer'; +import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { DrawerDismissReasons, DrawerService } from '../common/drawer'; import { ImportIdentityComponent } from './import-identity'; import { IdCard } from 'composer-common'; @@ -89,16 +89,19 @@ export class LoginComponent implements OnInit { handleRouteChange() { switch (this.route.snapshot.fragment) { - case 'deploy': this.deployNetwork(decodeURIComponent(this.route.snapshot.queryParams['ref'])); - break; - case 'create-card': this.createIdCard(); - break; - default: if (this.route.snapshot.fragment || Object.keys(this.route.snapshot.queryParams).length > 0) { - this.goLoginMain(); - } else { - this.closeSubView(); - } - break; + case 'deploy': + this.deployNetwork(decodeURIComponent(this.route.snapshot.queryParams['ref'])); + break; + case 'create-card': + this.createIdCard(); + break; + default: + if (this.route.snapshot.fragment || Object.keys(this.route.snapshot.queryParams).length > 0) { + this.goLoginMain(); + } else { + this.closeSubView(); + } + break; } } @@ -351,8 +354,12 @@ export class LoginComponent implements OnInit { let deletePromise: Promise; let cards = this.identityCardService.getAllCardsForBusinessNetwork(card.getBusinessNetworkName(), this.identityCardService.getQualifiedProfileName(card.getConnectionProfile())); if (card.getConnectionProfile()['x-type'] === 'web' && cards.size === 1) { - deletePromise = this.adminService.connect(cardRef, card, true) + deletePromise = this.adminService.connect(cardRef, card, true) .then(() => { + this.alertService.busyStatus$.next({ + title: 'Undeploying business network', + force: true + }); return this.adminService.undeploy(card.getBusinessNetworkName()); }); } else { @@ -363,6 +370,7 @@ export class LoginComponent implements OnInit { .then(() => { return this.identityCardService.deleteIdentityCard(cardRef) .then(() => { + this.alertService.busyStatus$.next(null); this.alertService.successStatus$.next({ title: 'ID Card Removed', text: 'The ID card was successfully removed from My Wallet.', diff --git a/packages/composer-playground/src/app/services/admin.service.spec.ts b/packages/composer-playground/src/app/services/admin.service.spec.ts index 9bac1504f5..a8704ac976 100644 --- a/packages/composer-playground/src/app/services/admin.service.spec.ts +++ b/packages/composer-playground/src/app/services/admin.service.spec.ts @@ -15,20 +15,18 @@ /* tslint:disable:no-unused-expression */ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ -import { TestBed, inject, fakeAsync, tick } from '@angular/core/testing'; +import { fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { AdminService } from './admin.service'; -import { IdCard } from 'composer-common'; +import { BusinessNetworkCardStore, BusinessNetworkDefinition, IdCard } from 'composer-common'; import * as sinon from 'sinon'; import * as chai from 'chai'; - -let should = chai.should(); - import { AlertService } from '../basic-modals/alert.service'; -import { BusinessNetworkDefinition, BusinessNetworkCardStore } from 'composer-common'; import { AdminConnection } from 'composer-admin'; import { BusinessNetworkCardStoreService } from './cardStores/businessnetworkcardstore.service'; +let should = chai.should(); + describe('AdminService', () => { let sandbox; @@ -94,9 +92,9 @@ describe('AdminService', () => { beforeEach(() => { mockIdCard = new IdCard({userName: 'banana', businessNetwork: 'myNetwork'}, { 'x-type': 'web', - 'name': 'myProfile' + 'name': '$default' }); - mockIdCard1 = new IdCard({userName: 'banana'}, {'x-type': 'web', 'name': 'myProfile'}); + mockIdCard1 = new IdCard({userName: 'banana'}, {'x-type': 'hlfv1', 'name': 'myProfile'}); }); it('should return if connected', fakeAsync(inject([AdminService], (service: AdminService) => { @@ -131,7 +129,8 @@ describe('AdminService', () => { alertMock.busyStatus$.next.should.have.been.calledWith({ title: 'Connecting to Business Network myNetwork', - text: 'using connection profile myProfile' + text: 'using connection profile web', + force: true }); mockGetAdminConnection.should.have.been.called; @@ -153,7 +152,8 @@ describe('AdminService', () => { alertMock.busyStatus$.next.should.have.been.calledWith({ title: 'Connecting without a business network', - text: 'using connection profile myProfile' + text: 'using connection profile myProfile', + force: true }); mockGetAdminConnection.should.have.been.called; @@ -177,7 +177,8 @@ describe('AdminService', () => { alertMock.busyStatus$.next.should.have.been.calledWith({ title: 'Connecting to Business Network myNetwork', - text: 'using connection profile myProfile' + text: 'using connection profile web', + force: true }); mockGetAdminConnection.should.have.been.called; @@ -205,7 +206,8 @@ describe('AdminService', () => { alertMock.busyStatus$.next.should.have.been.calledWith({ title: 'Connecting to Business Network myNetwork', - text: 'using connection profile myProfile' + text: 'using connection profile web', + force: true }); mockGetAdminConnection.should.have.been.called; diff --git a/packages/composer-playground/src/app/services/admin.service.ts b/packages/composer-playground/src/app/services/admin.service.ts index e9c6699fbc..c6fa370c81 100644 --- a/packages/composer-playground/src/app/services/admin.service.ts +++ b/packages/composer-playground/src/app/services/admin.service.ts @@ -17,8 +17,7 @@ import { AlertService } from '../basic-modals/alert.service'; import { BusinessNetworkCardStoreService } from './cardStores/businessnetworkcardstore.service'; import { AdminConnection } from 'composer-admin'; -import { ConnectionProfileManager, Logger, BusinessNetworkDefinition, IdCard } from 'composer-common'; - +import { BusinessNetworkDefinition, ConnectionProfileManager, IdCard } from 'composer-common'; import ProxyConnectionManager = require('composer-connector-proxy'); import WebConnectionManager = require('composer-connector-web'); @@ -61,9 +60,11 @@ export class AdminService { console.log('Establishing admin connection ...'); + const connectionProfileName = card.getConnectionProfile()['x-type'] === 'web' ? 'web' : card.getConnectionProfile().name; this.alertService.busyStatus$.next({ title: card.getBusinessNetworkName() ? 'Connecting to Business Network ' + card.getBusinessNetworkName() : 'Connecting without a business network', - text: 'using connection profile ' + card.getConnectionProfile().name + text: 'using connection profile ' + connectionProfileName, + force: true }); this.connectingPromise = this.getAdminConnection().connect(cardName) diff --git a/packages/composer-playground/src/app/services/client.service.spec.ts b/packages/composer-playground/src/app/services/client.service.spec.ts index 750fcb9204..e45a9d16d4 100644 --- a/packages/composer-playground/src/app/services/client.service.spec.ts +++ b/packages/composer-playground/src/app/services/client.service.spec.ts @@ -15,31 +15,30 @@ /* tslint:disable:no-unused-expression */ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ -import { TestBed, inject, fakeAsync, tick } from '@angular/core/testing'; +import { fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { ClientService } from './client.service'; import * as sinon from 'sinon'; import * as chai from 'chai'; - -let should = chai.should(); -let expect = chai.expect; - import { AdminService } from './admin.service'; import { AlertService } from '../basic-modals/alert.service'; import { + AclFile, + BusinessNetworkCardStore, BusinessNetworkDefinition, + IdCard, ModelFile, - Script, - AclFile, QueryFile, - IdCard, - BusinessNetworkCardStore + Script } from 'composer-common'; import { BusinessNetworkConnection } from 'composer-client'; import { IdentityService } from './identity.service'; import { IdentityCardService } from './identity-card.service'; import { BusinessNetworkCardStoreService } from './cardStores/businessnetworkcardstore.service'; +let should = chai.should(); +let expect = chai.expect; + describe('ClientService', () => { let sandbox; @@ -75,7 +74,7 @@ describe('ClientService', () => { alertMock.errorStatus$ = {next: sinon.stub()}; alertMock.busyStatus$ = {next: sinon.stub()}; - idCard = new IdCard({userName: 'banana'}, {type: 'web', name: 'myProfile'}); + idCard = new IdCard({userName: 'banana'}, {'x-type': 'web', 'name': '$default'}); identityCardServiceMock.getCurrentIdentityCard.returns(idCard); identityCardServiceMock.getCurrentCardRef.returns('cardRef'); @@ -213,7 +212,36 @@ describe('ClientService', () => { alertMock.busyStatus$.next.should.have.been.calledTwice; alertMock.busyStatus$.next.firstCall.should.have.been.calledWith({ title: 'Establishing connection', - text: 'Using the connection profile myProfile' + text: 'Using the connection profile web', + force: true + }); + + adminMock.connect.should.have.been.calledWith('cardRef', idCard, false); + + refreshMock.should.have.been.called; + + alertMock.busyStatus$.next.secondCall.should.have.been.calledWith(null); + + service['isConnected'].should.equal(true); + should.not.exist(service['connectingPromise']); + }))); + + it('should connect if not connected to hlfv1 connection', fakeAsync(inject([ClientService], (service: ClientService) => { + idCard = new IdCard({userName: 'banana'}, {'x-type': 'hlfv1', 'name': 'myProfile'}); + identityCardServiceMock.getCurrentIdentityCard.returns(idCard); + + adminMock.connect.returns(Promise.resolve()); + let refreshMock = sinon.stub(service, 'refresh').returns(Promise.resolve()); + + service.ensureConnected(false); + + tick(); + + alertMock.busyStatus$.next.should.have.been.calledTwice; + alertMock.busyStatus$.next.firstCall.should.have.been.calledWith({ + title: 'Establishing connection', + text: 'Using the connection profile myProfile', + force: true }); adminMock.connect.should.have.been.calledWith('cardRef', idCard, false); @@ -272,7 +300,29 @@ describe('ClientService', () => { businessNetworkConMock.connect.should.have.been.calledWith('cardRef'); alertMock.busyStatus$.next.should.have.been.calledWith({ title: 'Refreshing Connection', - text: 'refreshing the connection to myProfile' + text: 'refreshing the connection to web', + force: true + }); + }))); + + it('should diconnect and reconnect the business network connection with hlfv1 connection', fakeAsync(inject([ClientService], (service: ClientService) => { + idCard = new IdCard({userName: 'banana'}, {'x-type': 'hlfv1', 'name': 'myProfile'}); + identityCardServiceMock.getCurrentIdentityCard.returns(idCard); + + let businessNetworkConnectionMock = sinon.stub(service, 'getBusinessNetworkConnection').returns(businessNetworkConMock); + businessNetworkConMock.disconnect.returns(Promise.resolve()); + + service.refresh(); + + tick(); + + businessNetworkConMock.disconnect.should.have.been.calledOnce; + businessNetworkConMock.connect.should.have.been.calledOnce; + businessNetworkConMock.connect.should.have.been.calledWith('cardRef'); + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Refreshing Connection', + text: 'refreshing the connection to myProfile', + force: true }); }))); @@ -289,7 +339,8 @@ describe('ClientService', () => { businessNetworkConMock.connect.should.have.been.calledWith('cardRef'); alertMock.busyStatus$.next.should.have.been.calledWith({ title: 'Refreshing Connection', - text: 'refreshing the connection to myProfile' + text: 'refreshing the connection to web', + force: true }); }))); }); diff --git a/packages/composer-playground/src/app/services/client.service.ts b/packages/composer-playground/src/app/services/client.service.ts index 654b38d473..7e03419eac 100644 --- a/packages/composer-playground/src/app/services/client.service.ts +++ b/packages/composer-playground/src/app/services/client.service.ts @@ -19,10 +19,7 @@ import { AlertService } from '../basic-modals/alert.service'; import { BusinessNetworkCardStoreService } from './cardStores/businessnetworkcardstore.service'; import { BusinessNetworkConnection } from 'composer-client'; -import { - BusinessNetworkDefinition, - TransactionDeclaration -} from 'composer-common'; +import { BusinessNetworkDefinition, TransactionDeclaration } from 'composer-common'; @Injectable() export class ClientService { @@ -79,9 +76,11 @@ export class ClientService { let cardName = this.identityCardService.getCurrentCardRef(); let card = this.identityCardService.getCurrentIdentityCard(); + const connectionProfileName = card.getConnectionProfile()['x-type'] === 'web' ? 'web' : card.getConnectionProfile().name; this.alertService.busyStatus$.next({ title: 'Establishing connection', - text: 'Using the connection profile ' + card.getConnectionProfile().name + text: 'Using the connection profile ' + connectionProfileName, + force: true }); this.connectingPromise = this.adminService.connect(cardName, card, force) @@ -89,7 +88,6 @@ export class ClientService { return this.refresh(); }) .then(() => { - console.log('connected'); this.isConnected = true; this.connectingPromise = null; this.alertService.busyStatus$.next(null); @@ -109,9 +107,12 @@ export class ClientService { let cardRef = this.identityCardService.getCurrentCardRef(); let card = this.identityCardService.getCurrentIdentityCard(); + const connectionProfileName = card.getConnectionProfile()['x-type'] === 'web' ? 'web' : card.getConnectionProfile().name; + this.alertService.busyStatus$.next({ title: 'Refreshing Connection', - text: 'refreshing the connection to ' + card.getConnectionProfile().name + text: 'refreshing the connection to ' + connectionProfileName, + force: true }); return this.getBusinessNetworkConnection().disconnect() diff --git a/packages/composer-playground/src/app/test/resource/resource.component.spec.ts b/packages/composer-playground/src/app/test/resource/resource.component.spec.ts index 0c7cf5aa66..a0c875fcfa 100644 --- a/packages/composer-playground/src/app/test/resource/resource.component.spec.ts +++ b/packages/composer-playground/src/app/test/resource/resource.component.spec.ts @@ -15,7 +15,7 @@ /* tslint:disable:no-unused-expression */ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ -import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Component, Input } from '@angular/core'; import { FormsModule } from '@angular/forms'; @@ -25,24 +25,24 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { ClientService } from '../../services/client.service'; import { - Resource, - BusinessNetworkDefinition, - Serializer, - Introspector, AssetDeclaration, - ParticipantDeclaration, - TransactionDeclaration, + BusinessNetworkDefinition, ClassDeclaration, Factory, - ModelFile - + Introspector, + ModelFile, + ParticipantDeclaration, + Resource, + Serializer, + TransactionDeclaration } from 'composer-common'; -import { BusinessNetworkConnection, AssetRegistry } from 'composer-client'; +import { AssetRegistry, BusinessNetworkConnection } from 'composer-client'; import { ResourceComponent } from './resource.component'; import * as sinon from 'sinon'; + let should = chai.should(); @Component({ diff --git a/packages/composer-playground/src/app/test/resource/resource.component.ts b/packages/composer-playground/src/app/test/resource/resource.component.ts index e4530ae235..45818e4bf2 100644 --- a/packages/composer-playground/src/app/test/resource/resource.component.ts +++ b/packages/composer-playground/src/app/test/resource/resource.component.ts @@ -11,19 +11,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, OnInit, Input } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { Component, Input, OnInit } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { ClientService } from '../../services/client.service'; -import { InitializationService } from '../../services/initialization.service'; import { - ClassDeclaration, AssetDeclaration, + ClassDeclaration, + Field, ParticipantDeclaration, - TransactionDeclaration, - Field + TransactionDeclaration } from 'composer-common'; -import leftPad = require('left-pad'); import 'codemirror/mode/javascript/javascript'; import 'codemirror/addon/fold/foldcode'; @@ -33,6 +30,7 @@ import 'codemirror/addon/fold/comment-fold'; import 'codemirror/addon/fold/markdown-fold'; import 'codemirror/addon/fold/xml-fold'; import 'codemirror/addon/scroll/simplescrollbars'; +import leftPad = require('left-pad'); @Component({ selector: 'resource-modal', diff --git a/packages/composer-playground/src/app/test/test.component.spec.ts b/packages/composer-playground/src/app/test/test.component.spec.ts index f214aacc42..05407f64f2 100644 --- a/packages/composer-playground/src/app/test/test.component.spec.ts +++ b/packages/composer-playground/src/app/test/test.component.spec.ts @@ -15,25 +15,19 @@ /* tslint:disable:no-unused-expression */ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ -import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { Directive, Input, Component } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { Component, Directive, Input } from '@angular/core'; import { TestComponent } from './test.component'; import { ClientService } from '../services/client.service'; -import { InitializationService } from '../services/initialization.service'; import { AlertService } from '../basic-modals/alert.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { Resource } from 'composer-common'; +import { BusinessNetworkDefinition, Introspector, Resource, TransactionDeclaration } from 'composer-common'; import { DrawerDismissReasons } from '../common/drawer'; import * as sinon from 'sinon'; import * as chai from 'chai'; import { BusinessNetworkConnection } from 'composer-client'; -import { - Introspector, - BusinessNetworkDefinition, - TransactionDeclaration -} from 'composer-common'; let should = chai.should(); diff --git a/packages/composer-playground/src/app/test/test.component.ts b/packages/composer-playground/src/app/test/test.component.ts index 6e7444314b..637b37ee7d 100644 --- a/packages/composer-playground/src/app/test/test.component.ts +++ b/packages/composer-playground/src/app/test/test.component.ts @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ClientService } from '../services/client.service'; import { AlertService } from '../basic-modals/alert.service'; diff --git a/packages/composer-playground/src/app/test/test.module.ts b/packages/composer-playground/src/app/test/test.module.ts index c3fecd517a..51a47f08a7 100644 --- a/packages/composer-playground/src/app/test/test.module.ts +++ b/packages/composer-playground/src/app/test/test.module.ts @@ -11,9 +11,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { CodemirrorModule } from 'ng2-codemirror'; diff --git a/packages/composer-playground/src/app/version-check/version-check.component.spec.ts b/packages/composer-playground/src/app/version-check/version-check.component.spec.ts index a854cf5b95..7fdc8f20ba 100644 --- a/packages/composer-playground/src/app/version-check/version-check.component.spec.ts +++ b/packages/composer-playground/src/app/version-check/version-check.component.spec.ts @@ -15,7 +15,7 @@ /* tslint:disable:no-unused-expression */ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ -import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { DebugElement, NgZone } from '@angular/core'; @@ -24,8 +24,6 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { LocalStorageService } from 'angular-2-local-storage'; import * as sinon from 'sinon'; -import { IdentityCardService } from '../services/identity-card.service'; -import { IdCard } from 'composer-common'; describe('VersionCheckComponent', () => { let component: VersionCheckComponent; @@ -39,21 +37,18 @@ describe('VersionCheckComponent', () => { }; let localStorageServiceMock; - let identityCardServiceMock; let indexDBMock = sinon.stub(indexedDB, 'deleteDatabase').returns(Promise.resolve()); beforeEach(async(() => { localStorageServiceMock = sinon.createStubInstance(LocalStorageService); - identityCardServiceMock = sinon.createStubInstance(IdentityCardService); TestBed.configureTestingModule({ declarations: [VersionCheckComponent], providers: [ {provide: NgbActiveModal, useValue: ngbActiveModalMock}, {provide: NgZone, useValue: new NgZone({})}, - {provide: LocalStorageService, useValue: localStorageServiceMock}, - {provide: IdentityCardService, useValue: identityCardServiceMock} + {provide: LocalStorageService, useValue: localStorageServiceMock} ] }).compileComponents(); })); @@ -65,18 +60,6 @@ describe('VersionCheckComponent', () => { element = debug.nativeElement; fixture.detectChanges(); - - let cardOne = new IdCard({userName : 'bob', businessNetwork: 'bn1'}, {'name' : 'cp1', 'x-type': 'hlfv1' }); - let cardTwo = new IdCard({userName : 'fred', businessNetwork: 'bn2'}, {'name' : 'cp1', 'x-type': 'web' }); - let cardThree = new IdCard({userName : 'jim'}, {'name' : 'cp1', 'x-type': 'web' }); - - let cardMap: Map = new Map(); - - cardMap.set('cardOne', cardOne); - cardMap.set('cardTwo', cardTwo); - cardMap.set('cardThree', cardThree); - - identityCardServiceMock.getIdentityCards.returns(Promise.resolve(cardMap)); }); it('should create component', () => { @@ -95,9 +78,8 @@ describe('VersionCheckComponent', () => { tick(); - indexDBMock.should.have.been.calledTwice; - indexDBMock.firstCall.should.have.been.calledWith('_pouch_Composer:bn2'); - indexDBMock.secondCall.should.have.been.calledWith('_pouch_Composer'); + indexDBMock.should.have.been.calledOnce; + indexDBMock.firstCall.should.have.been.calledWith('_pouch_Composer'); localStorageServiceMock.clearAll.should.have.been.called; runOutsideAngularStub.should.have.been.called; diff --git a/packages/composer-playground/src/app/version-check/version-check.component.ts b/packages/composer-playground/src/app/version-check/version-check.component.ts index 06ccabe546..5988104651 100644 --- a/packages/composer-playground/src/app/version-check/version-check.component.ts +++ b/packages/composer-playground/src/app/version-check/version-check.component.ts @@ -14,8 +14,6 @@ import { Component, NgZone } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { LocalStorageService } from 'angular-2-local-storage'; -import { IdentityCardService } from '../services/identity-card.service'; -import { IdCard } from 'composer-common'; @Component({ selector: 'version-check-modal', @@ -26,38 +24,17 @@ export class VersionCheckComponent { constructor(public activeModal: NgbActiveModal, private zone: NgZone, - private localStorageService: LocalStorageService, - private identityCardService: IdentityCardService) { + private localStorageService: LocalStorageService) { } public clearLocalStorage() { - this.identityCardService.getIdentityCards(true).then((idCards: Map) => { - let cardRefs = Array.from(idCards.keys()) - .filter((cardRef) => { - return idCards.get(cardRef).getConnectionProfile()['x-type'] === 'web'; - }); - - return cardRefs.reduce((promise, cardRef) => { - return promise.then(() => { - let idCard = idCards.get(cardRef); - let bn = idCard.getBusinessNetworkName(); - if (bn) { - return indexedDB.deleteDatabase('_pouch_Composer:' + bn); - } - }); - }, Promise.resolve(null)) - .then(() => { - return indexedDB.deleteDatabase('_pouch_Composer'); - }) - .then(() => { - if (this.localStorageService.clearAll()) { - this.zone.runOutsideAngular(() => { - location.reload(); - }); - } else { - throw new Error('Failed to clear local storage'); - } - }); - }); + indexedDB.deleteDatabase('_pouch_Composer'); + if (this.localStorageService.clearAll()) { + this.zone.runOutsideAngular(() => { + location.reload(); + }); + } else { + throw new Error('Failed to clear local storage'); + } } } diff --git a/packages/composer-rest-server/test/apikey.js b/packages/composer-rest-server/test/apikey.js index 4ee5285624..34eaf0eb0c 100644 --- a/packages/composer-rest-server/test/apikey.js +++ b/packages/composer-rest-server/test/apikey.js @@ -88,6 +88,10 @@ describe('Access with APIKEY unit tests', () => { }); }); + after(() => { + return adminConnection.undeploy(); + }); + describe('GET /api/system/ping', () => { it('should return 401 Unauthorized if APIKEY is not specified', () => { diff --git a/packages/composer-rest-server/test/assets.js b/packages/composer-rest-server/test/assets.js index 5ff77e7703..c3d51f9a11 100644 --- a/packages/composer-rest-server/test/assets.js +++ b/packages/composer-rest-server/test/assets.js @@ -228,6 +228,10 @@ const clone = require('clone'); }); }); + after(() => { + return adminConnection.undeploy(); + }); + describe(`GET / namespaces[${namespaces}]`, () => { it('should return all of the assets', () => { diff --git a/packages/composer-rest-server/test/authentication.js b/packages/composer-rest-server/test/authentication.js index 61b9368b7c..28c4a3613b 100644 --- a/packages/composer-rest-server/test/authentication.js +++ b/packages/composer-rest-server/test/authentication.js @@ -97,6 +97,7 @@ describe('Authentication REST API unit tests', () => { after(() => { ldapserver.close(); delete process.env.COMPOSER_PROVIDERS; + return adminConnection.undeploy(); }); describe('POST /auth/ldap', () => { diff --git a/packages/composer-rest-server/test/events.js b/packages/composer-rest-server/test/events.js index 58f1ad597d..8b132660fe 100644 --- a/packages/composer-rest-server/test/events.js +++ b/packages/composer-rest-server/test/events.js @@ -32,10 +32,11 @@ describe('Event REST API unit tests', () => { let httpServer; let businessNetworkConnection; let idCard; + let adminConnection; before(() => { const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); - const adminConnection = new AdminConnection({ cardStore }); + adminConnection = new AdminConnection({ cardStore }); let metadata = { version:1, userName: 'admin', enrollmentSecret: 'adminpw', roles: ['PeerAdmin', 'ChannelAdmin'] }; const deployCardName = 'deployer-card'; @@ -73,8 +74,10 @@ describe('Event REST API unit tests', () => { businessNetworkConnection = new BusinessNetworkConnection({ cardStore }); return businessNetworkConnection.connect('admin@bond-network'); }); + }); - + after(() => { + return adminConnection.undeploy(); }); describe('WebSockets', () => { diff --git a/packages/composer-rest-server/test/filter.js b/packages/composer-rest-server/test/filter.js index ffd695dc5f..6a051ee637 100644 --- a/packages/composer-rest-server/test/filter.js +++ b/packages/composer-rest-server/test/filter.js @@ -31,270 +31,287 @@ chai.use(require('chai-http')); describe(`Filter REST API unit tests namespaces[${namespaces}]`, () => { const participants = [{ - $class: 'org.acme.bond.Issuer', - memberId: 'ISSUER_1', - name: 'Billy Banterlope' + $class : 'org.acme.bond.Issuer', + memberId : 'ISSUER_1', + name : 'Billy Banterlope' }, { - $class: 'org.acme.bond.Issuer', - memberId: 'ISSUER_2', - name: 'Conga Block' + $class : 'org.acme.bond.Issuer', + memberId : 'ISSUER_2', + name : 'Conga Block' }, { - $class: 'org.acme.bond.Issuer', - memberId: 'ISSUER_3', - name: 'Percy Penguin' + $class : 'org.acme.bond.Issuer', + memberId : 'ISSUER_3', + name : 'Percy Penguin' }, { - $class: 'org.acme.bond.Issuer', - memberId: 'ISSUER_4', - name: 'Wendy Wombat' + $class : 'org.acme.bond.Issuer', + memberId : 'ISSUER_4', + name : 'Wendy Wombat' }]; const assetData = [{ - $class: 'org.acme.bond.BondAsset', - ISINCode: 'ISIN_1', - bond: { - $class: 'org.acme.bond.Bond', - description: 'A', - dayCountFraction: 'EOM', - currency: 'Sterling', - exchangeId: [ + $class : 'org.acme.bond.BondAsset', + ISINCode : 'ISIN_1', + bond : { + $class : 'org.acme.bond.Bond', + description : 'A', + dayCountFraction : 'EOM', + currency : 'Sterling', + exchangeId : [ 'NYSE' ], - faceAmount: 1000, - instrumentId: [ + faceAmount : 1000, + instrumentId : [ 'AliceCorp' ], - issuer: 'resource:org.acme.bond.Issuer#ISSUER_1', - maturity: '2018-01-27T21:03:52.000Z', - parValue: 1000, - paymentFrequency: { - $class: 'org.acme.bond.PaymentFrequency', - period: 'MONTH', - periodMultiplier: 1 + issuer : 'resource:org.acme.bond.Issuer#ISSUER_1', + maturity : '2018-01-27T21:03:52.000Z', + parValue : 1000, + paymentFrequency : { + $class : 'org.acme.bond.PaymentFrequency', + period : 'MONTH', + periodMultiplier : 1 } } }, { - $class: 'org.acme.bond.BondAsset', - ISINCode: 'ISIN_2', - bond: { - $class: 'org.acme.bond.Bond', - description: 'B', - dayCountFraction: 'EOY', - currency: 'USD', - exchangeId: [ + $class : 'org.acme.bond.BondAsset', + ISINCode : 'ISIN_2', + bond : { + $class : 'org.acme.bond.Bond', + description : 'B', + dayCountFraction : 'EOY', + currency : 'USD', + exchangeId : [ 'NYSE' ], - faceAmount: 2000, - instrumentId: [ + faceAmount : 2000, + instrumentId : [ 'BobCorp' ], - issuer: 'resource:org.acme.bond.Issuer#ISSUER_2', - maturity: '2018-12-27T21:03:52.000Z', - parValue: 2000, - paymentFrequency: { - $class: 'org.acme.bond.PaymentFrequency', - period: 'YEAR', - periodMultiplier: 1 + issuer : 'resource:org.acme.bond.Issuer#ISSUER_2', + maturity : '2018-12-27T21:03:52.000Z', + parValue : 2000, + paymentFrequency : { + $class : 'org.acme.bond.PaymentFrequency', + period : 'YEAR', + periodMultiplier : 1 } } }, { - $class: 'org.acme.bond.BondAsset', - ISINCode: 'ISIN_3', - bond: { - $class: 'org.acme.bond.Bond', - dayCountFraction: 'EOM', - description: 'C', - currency: 'RMB', - exchangeId: [ + $class : 'org.acme.bond.BondAsset', + ISINCode : 'ISIN_3', + bond : { + $class : 'org.acme.bond.Bond', + dayCountFraction : 'EOM', + description : 'C', + currency : 'RMB', + exchangeId : [ 'NYSE' ], - faceAmount: 1500, - instrumentId: [ + faceAmount : 1500, + instrumentId : [ 'CharlieCorp' ], - issuer: 'resource:org.acme.bond.Issuer#ISSUER_3', - maturity: '2017-02-27T21:03:52.000Z', - parValue: 3000, - paymentFrequency: { - $class: 'org.acme.bond.PaymentFrequency', - period: 'MONTH', - periodMultiplier: 4 + issuer : 'resource:org.acme.bond.Issuer#ISSUER_3', + maturity : '2017-02-27T21:03:52.000Z', + parValue : 3000, + paymentFrequency : { + $class : 'org.acme.bond.PaymentFrequency', + period : 'MONTH', + periodMultiplier : 4 } } }, { - $class: 'org.acme.bond.BondAsset', - ISINCode: 'ISIN_4', - bond: { - $class: 'org.acme.bond.Bond', - dayCountFraction: 'EOY', - description: 'D', - currency: 'EURO', - exchangeId: [ + $class : 'org.acme.bond.BondAsset', + ISINCode : 'ISIN_4', + bond : { + $class : 'org.acme.bond.Bond', + dayCountFraction : 'EOY', + description : 'D', + currency : 'EURO', + exchangeId : [ 'NYSE' ], - faceAmount: 4000, - instrumentId: [ + faceAmount : 4000, + instrumentId : [ 'DogeCorp' ], - issuer: 'resource:org.acme.bond.Issuer#ISSUER_4', - maturity: '2016-02-27T21:03:52.000Z', - parValue: 4000, - paymentFrequency: { - $class: 'org.acme.bond.PaymentFrequency', - period: 'MONTH', - periodMultiplier: 6 + issuer : 'resource:org.acme.bond.Issuer#ISSUER_4', + maturity : '2016-02-27T21:03:52.000Z', + parValue : 4000, + paymentFrequency : { + $class : 'org.acme.bond.PaymentFrequency', + period : 'MONTH', + periodMultiplier : 6 } } - }, - { - $class: 'org.acme.bond.BondAsset', - ISINCode: 'ISIN_5', - bond: { - $class: 'org.acme.bond.Bond', - dayCountFraction: 'EOY', - description: 'E', - currency: 'Pound', - exchangeId: [ + }, { + $class : 'org.acme.bond.BondAsset', + ISINCode : 'ISIN_5', + bond : { + $class : 'org.acme.bond.Bond', + dayCountFraction : 'EOY', + description : 'E', + currency : 'Pound', + exchangeId : [ 'NYSE' ], - faceAmount: 5000, - instrumentId: [ + faceAmount : 5000, + instrumentId : [ 'DogeCorp' ], - issuer: 'resource:org.acme.bond.Issuer#ISSUER_5', - maturity: '2016-02-27T21:03:52.000Z', - parValue: 5000, - paymentFrequency: { - $class: 'org.acme.bond.PaymentFrequency', - period: 'MONTH', - periodMultiplier: 6 + issuer : 'resource:org.acme.bond.Issuer#ISSUER_5', + maturity : '2016-02-27T21:03:52.000Z', + parValue : 5000, + paymentFrequency : { + $class : 'org.acme.bond.PaymentFrequency', + period : 'MONTH', + periodMultiplier : 6 } } - }, - { - $class: 'org.acme.bond.BondAsset', - ISINCode: 'ISIN_6', - bond: { - $class: 'org.acme.bond.Bond', - dayCountFraction: 'EOY', - description: 'F', - currency: 'USD', - exchangeId: [ + }, { + $class : 'org.acme.bond.BondAsset', + ISINCode : 'ISIN_6', + bond : { + $class : 'org.acme.bond.Bond', + dayCountFraction : 'EOY', + description : 'F', + currency : 'USD', + exchangeId : [ 'NYSE' ], - faceAmount: 6000, - instrumentId: [ + faceAmount : 6000, + instrumentId : [ 'DogeCorp' ], - issuer: 'resource:org.acme.bond.Issuer#ISSUER_6', - maturity: '2015-02-27T21:03:52.000Z', - parValue: 6000, - paymentFrequency: { - $class: 'org.acme.bond.PaymentFrequency', - period: 'MONTH', - periodMultiplier: 6 + issuer : 'resource:org.acme.bond.Issuer#ISSUER_6', + maturity : '2015-02-27T21:03:52.000Z', + parValue : 6000, + paymentFrequency : { + $class : 'org.acme.bond.PaymentFrequency', + period : 'MONTH', + periodMultiplier : 6 } } - }, - { - $class: 'org.acme.bond.BondAsset', - ISINCode: 'ISIN_7', - bond: { - $class: 'org.acme.bond.Bond', - dayCountFraction: 'EOY', - description: 'A', - currency: 'USD', - exchangeId: [ + }, { + $class : 'org.acme.bond.BondAsset', + ISINCode : 'ISIN_7', + bond : { + $class : 'org.acme.bond.Bond', + dayCountFraction : 'EOY', + description : 'A', + currency : 'USD', + exchangeId : [ 'NYSE' ], - faceAmount: 60000, - instrumentId: [ + faceAmount : 60000, + instrumentId : [ 'DogeCorp' ], - issuer: 'resource:org.acme.bond.Issuer#ISSUER_3', - owners: ['resource:org.acme.bond.Issuer#ISSUER_1', 'resource:org.acme.bond.Issuer#ISSUER_2'], - maturity: '2010-02-27T21:03:52.000Z', - parValue: 60000, - paymentFrequency: { - $class: 'org.acme.bond.PaymentFrequency', - period: 'MONTH', - periodMultiplier: 6 + issuer : 'resource:org.acme.bond.Issuer#ISSUER_3', + owners : ['resource:org.acme.bond.Issuer#ISSUER_1', 'resource:org.acme.bond.Issuer#ISSUER_2'], + maturity : '2010-02-27T21:03:52.000Z', + parValue : 60000, + paymentFrequency : { + $class : 'org.acme.bond.PaymentFrequency', + period : 'MONTH', + periodMultiplier : 6 } } - } - ]; + }]; let app; let businessNetworkConnection; let serializer; let idCard; + let adminConnection; before(() => { - const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); - const adminConnection = new AdminConnection({ cardStore }); - let metadata = { version:1, userName: 'admin', enrollmentSecret: 'adminpw', roles: ['PeerAdmin', 'ChannelAdmin'] }; + const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore({type : 'composer-wallet-inmemory'}); + adminConnection = new AdminConnection({cardStore}); + let metadata = { + version : 1, + userName : 'admin', + enrollmentSecret : 'adminpw', + roles : ['PeerAdmin', 'ChannelAdmin'] + }; const deployCardName = 'deployer-card'; - let idCard_PeerAdmin = new IdCard(metadata, {'x-type' : 'embedded',name:'defaultProfile'}); + let idCard_PeerAdmin = new IdCard(metadata, {'x-type' : 'embedded', name : 'defaultProfile'}); let businessNetworkDefinition; return adminConnection.importCard(deployCardName, idCard_PeerAdmin) - .then(() => { - return adminConnection.connect(deployCardName); - }) - .then(() => { - return BusinessNetworkDefinition.fromDirectory('./test/data/bond-network'); - }) - .then((result) => { - businessNetworkDefinition = result; - serializer = businessNetworkDefinition.getSerializer(); - return adminConnection.install(businessNetworkDefinition); - }) - .then(()=>{ - return adminConnection.start(businessNetworkDefinition.getName(), businessNetworkDefinition.getVersion(), {networkAdmins :[{userName:'admin',enrollmentSecret:'adminpw'}] }); - }) - .then(() => { - idCard = new IdCard({ userName: 'admin', enrollmentSecret: 'adminpw', businessNetwork: 'bond-network' }, { name: 'defaultProfile', 'x-type': 'embedded' }); - return adminConnection.importCard('admin@bond-network', idCard); - }) - .then(() => { - return server({ - card: 'admin@bond-network', - cardStore, - namespaces: namespaces + .then(() => { + return adminConnection.connect(deployCardName); + }) + .then(() => { + return BusinessNetworkDefinition.fromDirectory('./test/data/bond-network'); + }) + .then((result) => { + businessNetworkDefinition = result; + serializer = businessNetworkDefinition.getSerializer(); + return adminConnection.install(businessNetworkDefinition); + }) + .then(() => { + return adminConnection.start(businessNetworkDefinition.getName(), businessNetworkDefinition.getVersion(), { + networkAdmins : [{ + userName : 'admin', + enrollmentSecret : 'adminpw' + }] + }); + }) + .then(() => { + idCard = new IdCard({ + userName : 'admin', + enrollmentSecret : 'adminpw', + businessNetwork : 'bond-network' + }, {name : 'defaultProfile', 'x-type' : 'embedded'}); + return adminConnection.importCard('admin@bond-network', idCard); + }) + .then(() => { + return server({ + card : 'admin@bond-network', + cardStore, + namespaces : namespaces + }); + }) + .then((result) => { + app = result.app; + businessNetworkConnection = new BusinessNetworkConnection({cardStore}); + return businessNetworkConnection.connect('admin@bond-network'); + }) + + .then(() => { + return businessNetworkConnection.getAssetRegistry('org.acme.bond.BondAsset'); + }) + .then((assetRegistry) => { + return assetRegistry.addAll([ + serializer.fromJSON(assetData[0]), + serializer.fromJSON(assetData[1]), + serializer.fromJSON(assetData[2]), + serializer.fromJSON(assetData[3]), + serializer.fromJSON(assetData[4]), + serializer.fromJSON(assetData[5]), + serializer.fromJSON(assetData[6]) + ]); + }) + .then(() => { + return businessNetworkConnection.getParticipantRegistry('org.acme.bond.Issuer'); + }) + .then((participantRegistry) => { + return participantRegistry.addAll([ + serializer.fromJSON(participants[0]), + serializer.fromJSON(participants[1]), + serializer.fromJSON(participants[2]), + serializer.fromJSON(participants[3]) + ]); + }) + .then(() => { + return businessNetworkConnection.getAssetRegistry('org.acme.bond.BondAsset'); }); - }) - .then((result) => { - app = result.app; - businessNetworkConnection = new BusinessNetworkConnection({ cardStore }); - return businessNetworkConnection.connect('admin@bond-network'); - }) - - .then(() => { - return businessNetworkConnection.getAssetRegistry('org.acme.bond.BondAsset'); - }) - .then((assetRegistry) => { - return assetRegistry.addAll([ - serializer.fromJSON(assetData[0]), - serializer.fromJSON(assetData[1]), - serializer.fromJSON(assetData[2]), - serializer.fromJSON(assetData[3]), - serializer.fromJSON(assetData[4]), - serializer.fromJSON(assetData[5]), - serializer.fromJSON(assetData[6]) - ]); - }) - .then(() => { - return businessNetworkConnection.getParticipantRegistry('org.acme.bond.Issuer'); - }) - .then((participantRegistry) => { - return participantRegistry.addAll([ - serializer.fromJSON(participants[0]), - serializer.fromJSON(participants[1]), - serializer.fromJSON(participants[2]), - serializer.fromJSON(participants[3]) - ]); - }); + }); + after(() => { + return adminConnection.undeploy(); }); describe('Filter Equivalence', () => { @@ -340,74 +357,74 @@ chai.use(require('chai-http')); it('should return matches with a DATETIME property using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":"2018-01-27T21:03:52.000Z"}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":"2018-01-27T21:03:52.000Z"}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches with a DOUBLE property using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.parValue":1000}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.parValue":1000}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches with an INTEGER CONCEPT property using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.periodMultiplier":4}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.periodMultiplier":4}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[2] + ]); + }); }); it('should return matches with an ENUM CONCEPT property using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.period":"YEAR"}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.period":"YEAR"}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[1] + ]); + }); }); it('should return matches with multiple properties, STRING and DATETIME, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.dayCountFraction":"EOM", "bond.maturity":"2018-01-27T21:03:52.000Z"}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.dayCountFraction":"EOM", "bond.maturity":"2018-01-27T21:03:52.000Z"}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches with multiple properties, STRING and DOUBLE, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.dayCountFraction":"EOM", "bond.faceAmount":1000}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.dayCountFraction":"EOM", "bond.faceAmount":1000}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return an empty array if nothing matches the filter on an identifier field, using json format', () => { @@ -422,12 +439,12 @@ chai.use(require('chai-http')); it('should return an empty array if nothing matches the filter on a property field, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter[where][bond.dayCountFraction]="DOES_NOT_EXIST"`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter[where][bond.dayCountFraction]="DOES_NOT_EXIST"`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); it('should return an empty array if nothing matches the filter on an identifier field using object format', () => { @@ -445,122 +462,122 @@ chai.use(require('chai-http')); // valid only for numerical and date values it('should return GREATER THAN matches with a DOUBLE property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.parValue":{"gt":5000}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[5], - assetData[6] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.parValue":{"gt":5000}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[5], + assetData[6] + ]); + }); }); it('should return GREATER THAN matches with an INTEGER property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.periodMultiplier":{"gt":5}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[3], - assetData[4], - assetData[5], - assetData[6] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.periodMultiplier":{"gt":5}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[3], + assetData[4], + assetData[5], + assetData[6] + ]); + }); }); it('should return GREATER THAN matches with a DATETIME property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"gt":"2018-01-27T21:03:52.000Z"}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"gt":"2018-01-27T21:03:52.000Z"}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[1] + ]); + }); }); it('should return LESS THAN matches with a DOUBLE property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"lt":2000}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"lt":2000}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[2] + ]); + }); }); it('should return LESS THAN matches with an INTEGER property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.periodMultiplier":{"lt":5}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.periodMultiplier":{"lt":5}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1], + assetData[2] + ]); + }); }); it('should return LESS THAN matches with a DATETIME property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"lt":"2016-01-27T21:03:52.000Z"}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[5], - assetData[6] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"lt":"2016-01-27T21:03:52.000Z"}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[5], + assetData[6] + ]); + }); }); it('should return an empty array if no matching GREATER THAN DOUBLE property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"gt":200000}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"gt":200000}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); it('should return an empty array if no matching GREATER THAN DATETIME property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"gt":"2019-01-27T21:03:52.000Z"}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"gt":"2019-01-27T21:03:52.000Z"}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); it('should return an empty array if no matching LESS THAN DOUBLE property using, json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"lt":200}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"lt":200}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); it('should return an empty array if no matching LESS THAN DATETIME property using,json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"lt":"2006-01-27T21:03:52.000Z"}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"lt":"2006-01-27T21:03:52.000Z"}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); }); @@ -569,245 +586,245 @@ chai.use(require('chai-http')); // interested in depth and combination it('should return matches with an identifier field AND non-identifier STRING property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"ISINCode":"ISIN_1"},{"bond.description":"A"}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"ISINCode":"ISIN_1"},{"bond.description":"A"}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches with an identifier field with non-identifier STRING property using implicit and, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":"ISIN_1", "bond.description":"A"}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":"ISIN_1", "bond.description":"A"}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches with an identifier field AND non-identifier DATETIME property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"ISINCode":"ISIN_1"},{"bond.maturity":{"lte":"2018-01-27T21:03:52.000Z"}}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"ISINCode":"ISIN_1"},{"bond.maturity":{"lte":"2018-01-27T21:03:52.000Z"}}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches with TWO non-identifier STRING properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.dayCountFraction":"EOY", "bond.description":"B"}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.dayCountFraction":"EOY", "bond.description":"B"}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[1] + ]); + }); }); it('should return matches with THREE non-identifier STRING properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.dayCountFraction":"EOY", "bond.description":"B", "bond.currency":"USD"}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.dayCountFraction":"EOY", "bond.description":"B", "bond.currency":"USD"}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[1] + ]); + }); }); it('should return matches with non-identifier STRING AND DOUBLE properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.dayCountFraction":"EOY"},{"bond.parValue":{"lte":2000}}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.dayCountFraction":"EOY"},{"bond.parValue":{"lte":2000}}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[1] + ]); + }); }); it('should return matches with non-identifier STRING AND LESS THAN DATETIME properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.dayCountFraction":"EOM"},{"bond.maturity":{"lt":"2018-06-27T21:03:52.000Z"}}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.dayCountFraction":"EOM"},{"bond.maturity":{"lt":"2018-06-27T21:03:52.000Z"}}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[2] + ]); + }); }); it('should return matches with non-identifier DOUBLE AND DATETIME properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.parValue":{"lte":2000}},{"bond.maturity":{"lt":"2018-06-27T21:03:52.000Z"}}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.parValue":{"lte":2000}},{"bond.maturity":{"lt":"2018-06-27T21:03:52.000Z"}}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches with non-identifier STRING AND DOUBLE AND DATETIME properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.dayCountFraction":"EOM"},{"bond.parValue":{"lte":2000}},{"bond.maturity":{"lt":"2018-06-27T21:03:52.000Z"}}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.dayCountFraction":"EOM"},{"bond.parValue":{"lte":2000}},{"bond.maturity":{"lt":"2018-06-27T21:03:52.000Z"}}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return an empty array if no matching AND properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"ISINCode":"ISIN_1"},{"bond.description":"B"}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"ISINCode":"ISIN_1"},{"bond.description":"B"}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); }); describe('Filter OR', () => { it('should return matches on the identifier when filtering on the identifier field OR a STRING property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"ISINCode":"ISIN_1"},{"bond.description":"A"}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[6] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"ISINCode":"ISIN_1"},{"bond.description":"A"}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[6] + ]); + }); }); it('should return matches on the property when filtering on the identifier field OR a DOUBLE property, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"ISINCode":"ISIN_1"},{"bond.parValue":{"lte":2000}}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"ISINCode":"ISIN_1"},{"bond.parValue":{"lte":2000}}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1] + ]); + }); }); it('should return matches on the property when filtering on the identifier field OR a DATETIME property, using object format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"ISINCode":"ISIN_1"},{"bond.maturity":{"gte":"2018-01-27T21:03:52.000Z"}}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1] - ]); - }); - }); - - it('should return matches on DOUBLE when filtering on DOUBLE OR STRING properties, using json format', () => { - return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"XYZ"},{"bond.faceAmount":2000}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"ISINCode":"ISIN_1"},{"bond.maturity":{"gte":"2018-01-27T21:03:52.000Z"}}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1] + ]); + }); + }); + + it('should return matches on DOUBLE when filtering on DOUBLE OR STRING properties, using json format', () => { + return chai.request(app) + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"XYZ"},{"bond.faceAmount":2000}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[1] + ]); + }); }); it('should return matches on a STRING when filtering on DOUBLE OR STRING properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"EOM"},{"bond.faceAmount":200000}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"EOM"},{"bond.faceAmount":200000}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[2] + ]); + }); }); it('should return matches on a DATETIME when filtering on DATETIME OR STRING properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"XYZ"},{"bond.maturity":{"gte":"2018-01-27T21:03:52.000Z"}}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"XYZ"},{"bond.maturity":{"gte":"2018-01-27T21:03:52.000Z"}}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1] + ]); + }); }); it('should return matches when filtering on DATETIME OR STRING OR DOUBLE properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"EOM"}, {"bond.maturity":"2018-01-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"EOM"}, {"bond.maturity":"2018-01-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[2] + ]); + }); }); it('should return matches on a DATETIME when filtering on DATETIME OR STRING properties, using object format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter[where][or][0][bond.dayCountFraction]=XYZ&filter[where][or][1][bond.maturity]=2018-01-27T21:03:52.000Z`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter[where][or][0][bond.dayCountFraction]=XYZ&filter[where][or][1][bond.maturity]=2018-01-27T21:03:52.000Z`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return an empty array if no matching OR properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"XYZ"},{"bond.maturity":{"gte":"2028-01-27T21:03:52.000Z"}}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"XYZ"},{"bond.maturity":{"gte":"2028-01-27T21:03:52.000Z"}}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); it('should return an empty array if no matching OR properties, using object format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter[where][or][0][bond.dayCountFraction]=XYZ&filter[where][or][1][bond.maturity]=2020-01-27T21:03:52.000Z`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter[where][or][0][bond.dayCountFraction]=XYZ&filter[where][or][1][bond.maturity]=2020-01-27T21:03:52.000Z`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); }); @@ -816,106 +833,106 @@ chai.use(require('chai-http')); it('should return matches when filtering on the identifier field AND a property OR property, using json format', () => { // (IDENTIFIER) AND (PROPERTY OR PROPERTY) return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"ISINCode":"ISIN_1"},{"or":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"ISINCode":"ISIN_1"},{"or":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches when filtering on the identifier field OR a property AND property, using json format', () => { // (IDENTIFIER) OR (PROPERTY AND PROPERTY) return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"ISINCode":"ISIN_1"},{"and":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"ISINCode":"ISIN_1"},{"and":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches when filtering on the property AND a property OR property, using json format', () => { // (PROPERTY) AND (PROPERTY OR PROPERTY) return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.dayCountFraction":"EOM"},{"or":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"bond.dayCountFraction":"EOM"},{"or":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0] + ]); + }); }); it('should return matches when filtering on the property OR a property AND property, using json format', () => { // (PROPERTY) OR (PROPERTY AND PROPERTY) return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"EOM"},{"and":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.dayCountFraction":"EOM"},{"and":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":1000}]}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[2] + ]); + }); }); it('should return matches when filtering with compound OR/AND clauses on properties, using json format', () => { // (PROPERTY AND PROPERTY) OR (PROPERTY AND PROPERTY) OR (PROPERTY AND PROPERTY) return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"and":[{"bond.description":"A"},{"bond.currency":"Sterling"}]},{"and":[{"bond.dayCountFraction":"EOM"},{"bond.paymentFrequency.periodMultiplier":1}]},{"and":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":{"gte":1000}}]}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"and":[{"bond.description":"A"},{"bond.currency":"Sterling"}]},{"and":[{"bond.dayCountFraction":"EOM"},{"bond.paymentFrequency.periodMultiplier":1}]},{"and":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":{"gte":1000}}]}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1] + ]); + }); }); xit('should return matches when filtering with compound AND/OR clauses on properties, using json format', () => { // (PROPERTY OR PROPERTY) AND (PROPERTY OR PROPERTY) AND (PROPERTY OR PROPERTY) return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"or":[{"bond.description":"A"},{"bond.currency":"Sterling"}]},{"or":[{"bond.dayCountFraction":"EOM"},{"bond.paymentFrequency.periodMultiplier":1}]},{"or":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":{"gte":1000}}]}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"or":[{"bond.description":"A"},{"bond.currency":"Sterling"}]},{"or":[{"bond.dayCountFraction":"EOM"},{"bond.paymentFrequency.periodMultiplier":1}]},{"or":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"bond.faceAmount":{"gte":1000}}]}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1] + ]); + }); }); it('should return matches when filtering with nested AND/OR clauses on properties, using json format', () => { // (PROPERTY OR (PROPERTY AND PROPERTY)) AND (PROPERTY AND (PROPERTY OR PROPERTY)) return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"or":[{"bond.description":"A"},{"and":[{"bond.currency":"Sterling"},{"bond.dayCountFraction":"EOM"}]}]},{"and":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"or":[{"bond.paymentFrequency.periodMultiplier":1}, {"bond.faceAmount":{"gte":1000}}]}]}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"and":[{"or":[{"bond.description":"A"},{"and":[{"bond.currency":"Sterling"},{"bond.dayCountFraction":"EOM"}]}]},{"and":[{"bond.maturity":"2018-12-27T21:03:52.000Z"}, {"or":[{"bond.paymentFrequency.periodMultiplier":1}, {"bond.faceAmount":{"gte":1000}}]}]}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[1] + ]); + }); }); it('should return an empty array if no matching AND/OR properties, using object format', () => { - // (PROPERTY OR (PROPERTY AND PROPERTY)) + // (PROPERTY OR (PROPERTY AND PROPERTY)) return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.description":"X"},{"and":[{"bond.currency":"Sterling"},{"bond.dayCountFraction":"YY"}]}]}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"or":[{"bond.description":"X"},{"and":[{"bond.currency":"Sterling"},{"bond.dayCountFraction":"YY"}]}]}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); }); @@ -923,184 +940,184 @@ chai.use(require('chai-http')); it('should return matches when filtering on the identifier field, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":{"between":["ISIN_1", "ISIN_3"]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":{"between":["ISIN_1", "ISIN_3"]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1], + assetData[2] + ]); + }); }); it('should return matches when filtering on STRING property field, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.description":{"between":["C", "D"]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[2], - assetData[3] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.description":{"between":["C", "D"]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[2], + assetData[3] + ]); + }); }); it('should return matches when filtering on INTEGER property field, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.periodMultiplier":{"between":[1, 5]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.paymentFrequency.periodMultiplier":{"between":[1, 5]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1], + assetData[2] + ]); + }); }); it('should return matches when filtering on DOUBLE property field, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[900, 1501]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[900, 1501]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[2] + ]); + }); }); it('should return matches when filtering on DATETIME property field, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"between":["2017-09-27T21:03:52.000Z", "2018-12-27T21:03:52.000Z"]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"between":["2017-09-27T21:03:52.000Z", "2018-12-27T21:03:52.000Z"]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1] + ]); + }); }); it('should return contained matches when searching BETWEEN STRING when there is a starting match, but unbounded ending match, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.description":{"between":["C", "Z"]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[2], - assetData[3], - assetData[4], - assetData[5] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.description":{"between":["C", "Z"]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[2], + assetData[3], + assetData[4], + assetData[5] + ]); + }); }); it('should return contained matches when searching BETWEEN DOUBLE when there is a starting match, but unbounded ending match, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[2000, 9000]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[1], - assetData[3], - assetData[4], - assetData[5] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[2000, 9000]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[1], + assetData[3], + assetData[4], + assetData[5] + ]); + }); }); it('should return contained matches when searching BETWEEN DATETIME when there is a starting match, but unbounded ending match, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"between":["2017-09-27T21:03:52.000Z", "2118-12-27T21:03:52.000Z"]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[1] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"between":["2017-09-27T21:03:52.000Z", "2118-12-27T21:03:52.000Z"]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[1] + ]); + }); }); it('should return contained matches when searching BETWEEN STRING when there is an ending match, but unbounded starting match, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.description":{"between":["C", "Z"]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[2], - assetData[3], - assetData[4], - assetData[5] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.description":{"between":["C", "Z"]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[2], + assetData[3], + assetData[4], + assetData[5] + ]); + }); }); it('should return contained matches when searching BETWEEN DOUBLE when there is an ending match, but unbounded starting match, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[0, 1500]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[0], - assetData[2] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[0, 1500]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[0], + assetData[2] + ]); + }); }); it('should return contained matches when searching BETWEEN DATETIME when there is an ending match, but unbounded starting match, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"between":["2010-09-27T21:03:52.000Z", "2017-09-27T21:03:52.000Z"]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([ - assetData[2], - assetData[3], - assetData[4], - assetData[5] - ]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.maturity":{"between":["2010-09-27T21:03:52.000Z", "2017-09-27T21:03:52.000Z"]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([ + assetData[2], + assetData[3], + assetData[4], + assetData[5] + ]); + }); }); it('should handle searching BETWEEN when there are unmatched types, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[0, "Penguin"]}}}`) - .catch((err) => { - err.response.should.have.status(500); - err.response.body.error.message.should.contain('Property faceAmount cannot be compared with Penguin (string) expected type Double'); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[0, "Penguin"]}}}`) + .catch((err) => { + err.response.should.have.status(500); + err.response.body.error.message.should.contain('Property faceAmount cannot be compared with Penguin (string) expected type Double'); + }); }); it('should return an empty array if no matching BETWEEN STRING properties, using json format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.description":{"between":["X", "Z"]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.description":{"between":["X", "Z"]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); it('should return an empty array if no matching BETWEEN DOUBLE properties, using object format', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[9000, 10000]}}}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.deep.equal([]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.faceAmount":{"between":[9000, 10000]}}}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.deep.equal([]); + }); }); }); @@ -1109,68 +1126,68 @@ chai.use(require('chai-http')); it('should return a single fully resolved Resource', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":"ISIN_1"}, "include":"resolve"}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.be.a('array'); - res.body.should.have.length(1); - res.body[0].bond.issuer.should.deep.equal(participants[0]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":"ISIN_1"}, "include":"resolve"}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.be.a('array'); + res.body.should.have.length(1); + res.body[0].bond.issuer.should.deep.equal(participants[0]); + }); }); it('should return a single fully resolved Resource with both a single relation and multiple relationships', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":"ISIN_7"}, "include":"resolve"}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.be.a('array'); - res.body.should.have.length(1); - res.body[0].bond.issuer.should.deep.equal(participants[2]); - res.body[0].bond.owners.should.have.length(2); - res.body[0].bond.owners[0].should.deep.equal(participants[0]); - res.body[0].bond.owners[1].should.deep.equal(participants[1]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":"ISIN_7"}, "include":"resolve"}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.be.a('array'); + res.body.should.have.length(1); + res.body[0].bond.issuer.should.deep.equal(participants[2]); + res.body[0].bond.owners.should.have.length(2); + res.body[0].bond.owners[0].should.deep.equal(participants[0]); + res.body[0].bond.owners[1].should.deep.equal(participants[1]); + }); }); it('should return multiple fully resolved Resources', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":{"between":["ISIN_1", "ISIN_3"]}}, "include":"resolve"}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.be.a('array'); - res.body.should.have.length(3); - res.body[0].bond.issuer.should.deep.equal(participants[0]); - res.body[1].bond.issuer.should.deep.equal(participants[1]); - res.body[2].bond.issuer.should.deep.equal(participants[2]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":{"between":["ISIN_1", "ISIN_3"]}}, "include":"resolve"}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.be.a('array'); + res.body.should.have.length(3); + res.body[0].bond.issuer.should.deep.equal(participants[0]); + res.body[1].bond.issuer.should.deep.equal(participants[1]); + res.body[2].bond.issuer.should.deep.equal(participants[2]); + }); }); it('should return the missing reference as a string value in resolve process of single Resource', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":"ISIN_5"}, "include":"resolve"}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.be.a('array'); - res.body.should.have.length(1); - res.body[0].should.deep.equal(assetData[4]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":"ISIN_5"}, "include":"resolve"}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.be.a('array'); + res.body.should.have.length(1); + res.body[0].should.deep.equal(assetData[4]); + }); }); it('should return missing references with string values as they are not resolved for multiple Resources', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":{"between":["ISIN_5", "ISIN_6"]}}, "include":"resolve"}`) - .then((res) => { - res.should.be.json; - res.should.have.status(200); - res.body.should.be.a('array'); - res.body.should.have.length(2); - res.body[0].should.deep.equal(assetData[4]); - res.body[1].should.deep.equal(assetData[5]); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"ISINCode":{"between":["ISIN_5", "ISIN_6"]}}, "include":"resolve"}`) + .then((res) => { + res.should.be.json; + res.should.have.status(200); + res.body.should.be.a('array'); + res.body.should.have.length(2); + res.body[0].should.deep.equal(assetData[4]); + res.body[1].should.deep.equal(assetData[5]); + }); }); }); @@ -1178,40 +1195,40 @@ chai.use(require('chai-http')); xit('should return an error message when trying to use NEAR', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.perValue":{"near": 10000}}}`) - .catch((err) => { - err.response.should.have.status(500); - err.response.body.error.message.should.match(/The key nlike operator is not supported by the Composer filter where/); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.perValue":{"near": 10000}}}`) + .catch((err) => { + err.response.should.have.status(500); + err.response.body.error.message.should.match(/The key nlike operator is not supported by the Composer filter where/); + }); }); it('should return an error message when trying to use LIKE', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.currency":{"like": "Sterling"}}}`) - .catch((err) => { - err.response.should.have.status(500); - err.response.body.error.message.should.match(/The key like operator is not supported by the Composer filter where/); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.currency":{"like": "Sterling"}}}`) + .catch((err) => { + err.response.should.have.status(500); + err.response.body.error.message.should.match(/The key like operator is not supported by the Composer filter where/); + }); }); it('should return an error message when trying to use NLIKE', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.currency":{"nlike": "Sterling"}}}`) - .catch((err) => { - err.response.should.have.status(500); - err.response.body.error.message.should.match(/The key nlike operator is not supported by the Composer filter where/); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.currency":{"nlike": "Sterling"}}}`) + .catch((err) => { + err.response.should.have.status(500); + err.response.body.error.message.should.match(/The key nlike operator is not supported by the Composer filter where/); + }); }); it('should return an error message when trying to use REGEXP', () => { return chai.request(app) - .get(`/api/${prefix}BondAsset?filter={"where":{"bond.currency":{"regexp": "Sterling"}}}`) - .catch((err) => { - err.response.should.have.status(500); - err.response.body.error.message.should.match(/The key regexp operator is not supported by the Composer filter where/); - }); + .get(`/api/${prefix}BondAsset?filter={"where":{"bond.currency":{"regexp": "Sterling"}}}`) + .catch((err) => { + err.response.should.have.status(500); + err.response.body.error.message.should.match(/The key regexp operator is not supported by the Composer filter where/); + }); }); }); }); -}); \ No newline at end of file +}); diff --git a/packages/composer-rest-server/test/multiuser.js b/packages/composer-rest-server/test/multiuser.js index b554489442..efd5660ecb 100644 --- a/packages/composer-rest-server/test/multiuser.js +++ b/packages/composer-rest-server/test/multiuser.js @@ -56,6 +56,7 @@ describe('Multiple user REST API unit tests', () => { let aliceAdminCard, aliceAdminCardData; let bobCard, bobCardData; let idCard; + let adminConnection; const binaryParser = (res, cb) => { res.setEncoding('binary'); @@ -70,7 +71,7 @@ describe('Multiple user REST API unit tests', () => { before(() => { const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); - const adminConnection = new AdminConnection({ cardStore }); + adminConnection = new AdminConnection({ cardStore }); let metadata = { version:1, userName: 'admin', enrollmentSecret: 'adminpw', roles: ['PeerAdmin', 'ChannelAdmin'] }; const deployCardName = 'deployer-card'; @@ -183,6 +184,7 @@ describe('Multiple user REST API unit tests', () => { after(() => { ldapserver.close(); delete process.env.COMPOSER_PROVIDERS; + return adminConnection.undeploy(); }); describe('GET /api/system/ping', () => { @@ -448,4 +450,4 @@ describe('Multiple user REST API unit tests', () => { }); -}); \ No newline at end of file +}); diff --git a/packages/composer-rest-server/test/participants.js b/packages/composer-rest-server/test/participants.js index 8ad1ffc8ae..c463e3c8bc 100644 --- a/packages/composer-rest-server/test/participants.js +++ b/packages/composer-rest-server/test/participants.js @@ -59,10 +59,11 @@ const clone = require('clone'); let participantRegistry; let serializer; let idCard; + let adminConnection; before(() => { const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); - const adminConnection = new AdminConnection({ cardStore }); + adminConnection = new AdminConnection({ cardStore }); let metadata = { version:1, userName: 'admin', enrollmentSecret: 'adminpw', roles: ['PeerAdmin', 'ChannelAdmin'] }; const deployCardName = 'deployer-card'; @@ -111,6 +112,10 @@ const clone = require('clone'); }); }); + after(() => { + return adminConnection.undeploy(); + }); + describe(`GET / namespaces[${namespaces}]`, () => { it('should return all of the participants', () => { diff --git a/packages/composer-rest-server/test/queries.js b/packages/composer-rest-server/test/queries.js index 33b83810f9..06b0954a4a 100644 --- a/packages/composer-rest-server/test/queries.js +++ b/packages/composer-rest-server/test/queries.js @@ -216,12 +216,11 @@ chai.use(require('chai-http')); let assetRegistry; let participantRegistry; let serializer; - - + let adminConnection; before(() => { const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); - const adminConnection = new AdminConnection({ cardStore }); + adminConnection = new AdminConnection({ cardStore }); let metadata = { version:1, userName: 'admin', enrollmentSecret: 'adminpw', roles: ['PeerAdmin', 'ChannelAdmin'] }; const deployCardName = 'deployer-card'; @@ -297,7 +296,9 @@ chai.use(require('chai-http')); }); }); - + after(() => { + return adminConnection.undeploy(); + }); describe(`GET / namespaces[${namespaces}]`, () => { diff --git a/packages/composer-rest-server/test/root.js b/packages/composer-rest-server/test/root.js index b02f5f549b..8392b56658 100644 --- a/packages/composer-rest-server/test/root.js +++ b/packages/composer-rest-server/test/root.js @@ -28,10 +28,11 @@ describe('Root REST API unit tests', () => { let app; let idCard; + let adminConnection; before(() => { const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); - const adminConnection = new AdminConnection({ cardStore }); + adminConnection = new AdminConnection({ cardStore }); let metadata = { version:1, userName: 'admin', enrollmentSecret: 'adminpw', roles: ['PeerAdmin', 'ChannelAdmin'] }; const deployCardName = 'deployer-card'; @@ -69,6 +70,10 @@ describe('Root REST API unit tests', () => { }); }); + after(() => { + return adminConnection.undeploy(); + }); + describe('GET /', () => { it('should redirect to the REST API explorer', () => { diff --git a/packages/composer-rest-server/test/system.js b/packages/composer-rest-server/test/system.js index 79234cbb47..21ea593ab5 100644 --- a/packages/composer-rest-server/test/system.js +++ b/packages/composer-rest-server/test/system.js @@ -90,6 +90,7 @@ describe('System REST API unit tests', () => { let participantRegistry; let serializer; let idCard; + let adminConnection; const binaryParser = (res, cb) => { res.setEncoding('binary'); @@ -104,7 +105,7 @@ describe('System REST API unit tests', () => { before(() => { const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); - const adminConnection = new AdminConnection({ cardStore }); + adminConnection = new AdminConnection({ cardStore }); let metadata = { version:1, userName: 'admin', enrollmentSecret: 'adminpw', roles: ['PeerAdmin', 'ChannelAdmin'] }; const deployCardName = 'deployer-card'; @@ -196,6 +197,10 @@ describe('System REST API unit tests', () => { }); }); + after(() => { + return adminConnection.undeploy(); + }); + describe('GET /ping', () => { it('should ping the business network', () => { diff --git a/packages/composer-rest-server/test/transactions.js b/packages/composer-rest-server/test/transactions.js index a5d77d1a63..dc70546ce1 100644 --- a/packages/composer-rest-server/test/transactions.js +++ b/packages/composer-rest-server/test/transactions.js @@ -133,10 +133,11 @@ chai.use(require('chai-http')); let participantRegistry; let serializer; let idCard; + let adminConnection; before(() => { const cardStore = require('composer-common').NetworkCardStoreManager.getCardStore( { type: 'composer-wallet-inmemory' } ); - const adminConnection = new AdminConnection({ cardStore }); + adminConnection = new AdminConnection({ cardStore }); let metadata = { version:1, userName: 'admin', enrollmentSecret: 'adminpw', roles: ['PeerAdmin', 'ChannelAdmin'] }; const deployCardName = 'deployer-card'; @@ -191,6 +192,9 @@ chai.use(require('chai-http')); }); }); + after(() => { + return adminConnection.undeploy(); + }); describe(`POST / namespaces[${namespaces}]`, () => { diff --git a/packages/composer-runtime-pouchdb/lib/pouchdbdatacollection.js b/packages/composer-runtime-pouchdb/lib/pouchdbdatacollection.js index 5eb7d8b07f..bffca96325 100644 --- a/packages/composer-runtime-pouchdb/lib/pouchdbdatacollection.js +++ b/packages/composer-runtime-pouchdb/lib/pouchdbdatacollection.js @@ -33,12 +33,14 @@ class PouchDBDataCollection extends DataCollection { * @param {DataService} dataService The owning data service. * @param {Dexie} db The database to use. * @param {string} collectionId The collection ID to use. + * @param {string} uuid The uuid to use */ - constructor(dataService, db, collectionId) { + constructor(dataService, db, collectionId, uuid) { super(dataService); const method = 'constructor'; LOG.entry(method, dataService, db, collectionId); this.db = db; + this.uuid = uuid; this.collectionId = collectionId; LOG.exit(method); } @@ -52,8 +54,15 @@ class PouchDBDataCollection extends DataCollection { getAll() { const method = 'getAll'; LOG.entry(method); - const startKey = pouchCollate.toIndexableString([this.collectionId]); - const endKey = pouchCollate.toIndexableString([this.collectionId, '\uffff']); + let compositeKey = [this.collectionId]; + if(this.uuid) { + compositeKey.unshift(this.uuid); + } + + const startKey = pouchCollate.toIndexableString(compositeKey); + const endCompositeKey = compositeKey; + endCompositeKey.push('\uffff'); + const endKey = pouchCollate.toIndexableString(endCompositeKey); return this.db.allDocs({ include_docs: true, startkey: startKey, endkey: endKey, inclusive_end: false }) .then((response) => { const result = response.rows.map((row) => { @@ -76,7 +85,12 @@ class PouchDBDataCollection extends DataCollection { get(id) { const method = 'get'; LOG.entry(method, id); - const key = pouchCollate.toIndexableString([this.collectionId, id]); + let compositeKey = [this.collectionId, id]; + if(this.uuid) { + compositeKey.unshift(this.uuid); + } + + const key = pouchCollate.toIndexableString(compositeKey); return PouchDBUtils.getDocument(this.db, key) .then((doc) => { if (!doc) { @@ -97,7 +111,12 @@ class PouchDBDataCollection extends DataCollection { exists(id) { const method = 'exists'; LOG.entry(method, id); - const key = pouchCollate.toIndexableString([this.collectionId, id]); + let compositeKey = [this.collectionId, id]; + if(this.uuid) { + compositeKey.unshift(this.uuid); + } + + const key = pouchCollate.toIndexableString(compositeKey); return PouchDBUtils.getDocument(this.db, key) .then((doc) => { LOG.exit(method, !!doc); @@ -118,7 +137,14 @@ class PouchDBDataCollection extends DataCollection { const method = 'add'; LOG.entry(method, id, object, force); force = !!force; - const key = pouchCollate.toIndexableString([this.collectionId, id]); + + let compositeKey = [this.collectionId, id]; + if(this.uuid) { + compositeKey.unshift(this.uuid); + object.$networkId = this.uuid; + } + + const key = pouchCollate.toIndexableString(compositeKey); return PouchDBUtils.getDocument(this.db, key) .then((doc) => { if (doc && !force) { @@ -141,7 +167,13 @@ class PouchDBDataCollection extends DataCollection { update(id, object) { const method = 'update'; LOG.entry(method, id, object); - const key = pouchCollate.toIndexableString([this.collectionId, id]); + let compositeKey = [this.collectionId, id]; + if(this.uuid) { + compositeKey.unshift(this.uuid); + object.$networkId = this.uuid; + } + + const key = pouchCollate.toIndexableString(compositeKey); return PouchDBUtils.getDocument(this.db, key) .then((doc) => { if (!doc) { @@ -163,7 +195,12 @@ class PouchDBDataCollection extends DataCollection { remove(id) { const method = 'remove'; LOG.entry(method, id); - const key = pouchCollate.toIndexableString([this.collectionId, id]); + let compositeKey = [this.collectionId, id]; + if(this.uuid) { + compositeKey.unshift(this.uuid); + } + + const key = pouchCollate.toIndexableString(compositeKey); return PouchDBUtils.getDocument(this.db, key) .then((doc) => { if (!doc) { diff --git a/packages/composer-runtime-pouchdb/lib/pouchdbdataservice.js b/packages/composer-runtime-pouchdb/lib/pouchdbdataservice.js index f3ca7e807a..6897b4e1b9 100644 --- a/packages/composer-runtime-pouchdb/lib/pouchdbdataservice.js +++ b/packages/composer-runtime-pouchdb/lib/pouchdbdataservice.js @@ -39,7 +39,7 @@ class PouchDBDataService extends DataService { * Register the specified PouchDB plugin with PouchDB. * @param {*} plugin The PouchDB plugin to register. */ - static registerPouchDBPlugin(plugin) { + static registerPouchDBPlugin (plugin) { // No logging here as this is called during static initialization // at startup, and we don't want to try and load the logger yet. PouchDB.plugin(plugin); @@ -51,7 +51,7 @@ class PouchDBDataService extends DataService { * @param {Object} [options] Optional options for PouchDB. * @return {PouchDB} The new instance of PouchDB. */ - static createPouchDB(name, options) { + static createPouchDB (name, options) { const method = 'createPouchDB'; LOG.entry(method, name, options); let result = new PouchDB(name, options); @@ -65,15 +65,13 @@ class PouchDBDataService extends DataService { * @param {boolean} [autocommit] Should this data service auto commit? * @param {Object} [options] Optional options for PouchDB. */ - constructor(uuid, autocommit, options) { + constructor (uuid, autocommit, options) { super(); const method = 'constructor'; LOG.entry(method, uuid, autocommit, options); - if (uuid) { - this.db = PouchDBDataService.createPouchDB(`Composer:${uuid}`, options); - } else { - this.db = PouchDBDataService.createPouchDB('Composer', options); - } + this.uuid = uuid; + this.db = PouchDBDataService.createPouchDB('Composer', options); + this.autocommit = !!autocommit; this.pendingActions = []; LOG.exit(method); @@ -84,7 +82,7 @@ class PouchDBDataService extends DataService { * @return {Promise} A promise that will be resolved when destroyed, or * rejected with an error. */ - destroy() { + destroy () { const method = 'destroy'; LOG.entry(method); return this.db.destroy() @@ -100,10 +98,17 @@ class PouchDBDataService extends DataService { * @return {Promise} A promise that will be resolved with a {@link DataCollection} * when complete, or rejected with an error. */ - createCollection(id, force) { + createCollection (id, force) { const method = 'createCollection'; LOG.entry(method, id, force); - const key = pouchCollate.toIndexableString([collectionObjectType, id]); + let compositeKey = [collectionObjectType]; + if (this.uuid) { + compositeKey.unshift(this.uuid); + } + + compositeKey.push(id); + + const key = pouchCollate.toIndexableString(compositeKey); return PouchDBUtils.getDocument(this.db, key) .then((doc) => { if (doc && !force) { @@ -114,7 +119,7 @@ class PouchDBDataService extends DataService { }); }) .then(() => { - let result = new PouchDBDataCollection(this, this.db, id); + let result = new PouchDBDataCollection(this, this.db, id, this.uuid); LOG.exit(method, result); return result; }); @@ -126,10 +131,17 @@ class PouchDBDataService extends DataService { * @return {Promise} A promise that will be resolved when complete, or rejected * with an error. */ - deleteCollection(id) { + deleteCollection (id) { const method = 'deleteCollection'; LOG.entry(method, id); - const key = pouchCollate.toIndexableString([collectionObjectType, id]); + let compositeKey = [collectionObjectType]; + if (this.uuid) { + compositeKey.unshift(this.uuid); + } + + compositeKey.push(id); + + const key = pouchCollate.toIndexableString(compositeKey); return PouchDBUtils.getDocument(this.db, key) .then((doc) => { if (!doc) { @@ -159,31 +171,78 @@ class PouchDBDataService extends DataService { LOG.entry(method, id); if (bypass) { - let result = new PouchDBDataCollection(this, this.db, id); + let result = new PouchDBDataCollection(this, this.db, id, this.uuid); LOG.exit(method, result); return result; } else { - const key = pouchCollate.toIndexableString([collectionObjectType, id]); + let compositeKey = [collectionObjectType]; + if(this.uuid) { + compositeKey.unshift(this.uuid); + } + + compositeKey.push(id); + + const key = pouchCollate.toIndexableString(compositeKey); let doc = await PouchDBUtils.getDocument(this.db, key); if (!doc) { throw new Error(`Collection with ID '${id}' does not exist`); } - let result = new PouchDBDataCollection(this, this.db, id); + let result = new PouchDBDataCollection(this, this.db, id, this.uuid); LOG.exit(method, result); return result; } } - /** - * Determine whether the collection with the specified ID exists. - * @param {string} id The ID of the collection. - * @return {Promise} A promise that will be resolved with a boolean - * indicating whether the collection exists. - */ - existsCollection(id) { + /** + * Remove all the data + * @return {Promise} A promise that will be resolved when complete, or rejected + * with an error. + */ + removeAllData () { + const method = 'removeAllData'; + LOG.entry(method); + let compositeKey = []; + if (this.uuid) { + compositeKey.unshift(this.uuid); + } + + const startKey = pouchCollate.toIndexableString(compositeKey); + const endCompositeKey = compositeKey; + endCompositeKey.push('\uffff'); + const endKey = pouchCollate.toIndexableString(endCompositeKey); + return this.db.allDocs({include_docs : true, startkey : startKey, endkey : endKey, inclusive_end : false}) + .then((response) => { + const docs = response.rows.map((row) => { + return { + _id : row.id, + _rev : row.value.rev, + _deleted : true + }; + }); + return this.db.bulkDocs(docs); + }) + .then(() => { + LOG.exit(method); + }); + } + + /** + * Determine whether the collection with the specified ID exists. + * @param {string} id The ID of the collection. + * @return {Promise} A promise that will be resolved with a boolean + * indicating whether the collection exists. + */ + existsCollection (id) { const method = 'existsCollection'; LOG.entry(method, id); - const key = pouchCollate.toIndexableString([collectionObjectType, id]); + let compositeKey = [collectionObjectType]; + if (this.uuid) { + compositeKey.unshift(this.uuid); + } + + compositeKey.push(id); + + const key = pouchCollate.toIndexableString(compositeKey); return PouchDBUtils.getDocument(this.db, key) .then((doc) => { LOG.exit(method, !!doc); @@ -198,7 +257,7 @@ class PouchDBDataService extends DataService { * @return {Promise} A promise that will be resolved with an array of objects * when complete, or rejected with an error. */ - executeQuery(queryString) { + executeQuery (queryString) { const method = 'executeQuery'; LOG.entry(method, queryString); const query = JSON.parse(queryString); @@ -210,6 +269,11 @@ class PouchDBDataService extends DataService { delete query.selector[`\\${prop}`]; } }); + + if (this.uuid) { + query.selector.$networkId = this.uuid; + } + return this.db.find(query) .then((response) => { const docs = response.docs.map((doc) => { @@ -228,18 +292,27 @@ class PouchDBDataService extends DataService { * @return {Promise} A promise that will be resolved when complete, or rejected * with an error. */ - clearCollection(id) { + clearCollection (id) { const method = 'clearCollection'; LOG.entry(method, id); - const startKey = pouchCollate.toIndexableString([id]); - const endKey = pouchCollate.toIndexableString([id, '\uffff']); - return this.db.allDocs({ startkey: startKey, endkey: endKey, inclusive_end: false }) + + let compositeKey = [id]; + if (this.uuid) { + compositeKey.unshift(this.uuid); + } + + const startKey = pouchCollate.toIndexableString(compositeKey); + + const endCompositeKey = compositeKey; + endCompositeKey.push('\uffff'); + const endKey = pouchCollate.toIndexableString(endCompositeKey); + return this.db.allDocs({startkey : startKey, endkey : endKey, inclusive_end : false}) .then((response) => { const docs = response.rows.map((row) => { return { - _id: row.id, - _rev: row.value.rev, - _deleted: true + _id : row.id, + _rev : row.value.rev, + _deleted : true }; }); return this.db.bulkDocs(docs); @@ -257,7 +330,7 @@ class PouchDBDataService extends DataService { * @return {Promise} A promise that will be resolved when complete, or rejected * with an error. */ - handleAction(actionFunction) { + handleAction (actionFunction) { const method = 'handleAction'; LOG.entry(method, actionFunction); return Promise.resolve() @@ -280,7 +353,7 @@ class PouchDBDataService extends DataService { * @return {Promise} A promise that will be resolved when complete, or rejected * with an error. */ - transactionStart(readOnly) { + transactionStart (readOnly) { const method = 'transactionStart'; LOG.entry(method, readOnly); return super.transactionStart(readOnly) @@ -295,7 +368,7 @@ class PouchDBDataService extends DataService { * @return {Promise} A promise that will be resolved when complete, or rejected * with an error. */ - transactionPrepare() { + transactionPrepare () { const method = 'transactionPrepare'; LOG.entry(method); return super.transactionPrepare() diff --git a/packages/composer-runtime-pouchdb/test/pouchdbdatacollection.js b/packages/composer-runtime-pouchdb/test/pouchdbdatacollection.js index a5feca1a12..8906279358 100644 --- a/packages/composer-runtime-pouchdb/test/pouchdbdatacollection.js +++ b/packages/composer-runtime-pouchdb/test/pouchdbdatacollection.js @@ -36,22 +36,45 @@ describe('PouchDBDataCollection', () => { let db; let dataCollection; - beforeEach(() => { + /** + * Create a database + * @param {string} uuid The uuid to use + * @returns {Promise} A promise + */ + function createDatabase (uuid) { mockDataService = sinon.createStubInstance(PouchDBDataService); mockDataService.handleAction.resolves(); - mockDataService.db = db = new PouchDB('Composer', { adapter: 'memory' }); - dataCollection = new PouchDBDataCollection(mockDataService, db, 'doge'); - return db.bulkDocs([ - { - _id: pouchCollate.toIndexableString(['doge', 'thing1']), - thing: 1 - }, - { - _id: pouchCollate.toIndexableString(['doge', 'thing2']), - thing: 2 - }, - ]); - }); + mockDataService.db = db = new PouchDB('Composer', {adapter : 'memory'}); + + if (uuid) { + dataCollection = new PouchDBDataCollection(mockDataService, db, 'doge', uuid); + + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString([uuid, 'doge', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString([uuid, 'doge', 'thing2']), + thing : 2 + }, + ]); + } else { + dataCollection = new PouchDBDataCollection(mockDataService, db, 'doge'); + + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString(['doge', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString(['doge', 'thing2']), + thing : 2 + }, + ]); + } + + } afterEach(() => { return db.destroy(); @@ -60,17 +83,39 @@ describe('PouchDBDataCollection', () => { describe('#constructor', () => { it('should create a data service', () => { - dataCollection.should.be.an.instanceOf(DataCollection); + return createDatabase().then(() => { + dataCollection.should.be.an.instanceOf(DataCollection); + }); + }); + + it('should create a data service with uuid', () => { + return createDatabase('myNetwork').then(() => { + dataCollection.should.be.an.instanceOf(DataCollection); + dataCollection.uuid.should.equal('myNetwork'); + }); }); }); describe('#getAll', () => { - it('should get all the objects', () => { - return dataCollection.getAll() + return createDatabase() + .then(() => { + return dataCollection.getAll(); + }) .then((objects) => { - objects.should.deep.equal([{ thing: 1 }, { thing: 2 }]); + objects.should.deep.equal([{thing : 1}, {thing : 2}]); + }); + + }); + + it('should get all the objects with uuid', () => { + return createDatabase('myNetwork') + .then(() => { + return dataCollection.getAll(); + }) + .then((objects) => { + objects.should.deep.equal([{thing : 1}, {thing : 2}]); }); }); @@ -79,13 +124,27 @@ describe('PouchDBDataCollection', () => { describe('#get', () => { it('should throw if the specified object does not exist', () => { - return dataCollection.get('thing3') - .should.be.rejectedWith(/Object with ID 'thing3' in collection with ID 'doge' does not exist/); + return createDatabase() + .then(() => { + return dataCollection.get('thing3') + .should.be.rejectedWith(/Object with ID 'thing3' in collection with ID 'doge' does not exist/); + }); }); it('should get the specified object', () => { - return dataCollection.get('thing1') - .should.eventually.deep.equal({ thing: 1 }); + return createDatabase() + .then(() => { + return dataCollection.get('thing1') + .should.eventually.deep.equal({thing : 1}); + }); + }); + + it('should get the specified object with uuid', () => { + return createDatabase('myNetwork') + .then(() => { + return dataCollection.get('thing1') + .should.eventually.deep.equal({thing : 1}); + }); }); }); @@ -93,13 +152,35 @@ describe('PouchDBDataCollection', () => { describe('#exists', () => { it('should return true if the specified object exists', () => { - return dataCollection.exists('thing1') - .should.eventually.be.true; + return createDatabase() + .then(() => { + return dataCollection.exists('thing1') + .should.eventually.be.true; + }); + }); + + it('should return true if the specified object exists with uuid', () => { + return createDatabase('myNetwork') + .then(() => { + return dataCollection.exists('thing1') + .should.eventually.be.true; + }); }); it('should return false if the specified object does not exist', () => { - return dataCollection.exists('thing3') - .should.eventually.be.false; + return createDatabase() + .then(() => { + return dataCollection.exists('thing3') + .should.eventually.be.false; + }); + }); + + it('should return false if the specified object does not exist with uuid', () => { + return createDatabase('myNetwork') + .then(() => { + return dataCollection.exists('thing3') + .should.eventually.be.false; + }); }); }); @@ -107,7 +188,10 @@ describe('PouchDBDataCollection', () => { describe('#add', () => { it('should add a new object to the collection', () => { - return dataCollection.add('thing4', { thing: 4 }) + return createDatabase() + .then(() => { + return dataCollection.add('thing4', {thing : 4}); + }) .then(() => { sinon.assert.calledOnce(mockDataService.handleAction); sinon.assert.calledWith(mockDataService.handleAction, sinon.match.func); @@ -119,13 +203,36 @@ describe('PouchDBDataCollection', () => { .then((doc) => { delete doc._id; delete doc._rev; - doc.should.deep.equal({ thing: 4 }); + doc.should.deep.equal({thing : 4}); + }); + }); + + it('should add a new object to the collection with a uuid', () => { + return createDatabase('myNetwork') + .then(() => { + return dataCollection.add('thing4', {thing : 4}); + }) + .then(() => { + sinon.assert.calledOnce(mockDataService.handleAction); + sinon.assert.calledWith(mockDataService.handleAction, sinon.match.func); + return mockDataService.handleAction.args[0][0](); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['myNetwork', 'doge', 'thing4'])); + }) + .then((doc) => { + delete doc._id; + delete doc._rev; + doc.should.deep.equal({thing : 4, $networkId : 'myNetwork'}); }); }); it('should throw an error adding an existing object to the collection', () => { - return dataCollection.add('thing1', { thing: 1 }) - .should.be.rejectedWith(/Failed to add object with ID .* as the object already exists/); + return createDatabase('myNetwork') + .then(() => { + return dataCollection.add('thing1', {thing : 1}) + .should.be.rejectedWith(/Failed to add object with ID .* as the object already exists/); + }); }); }); @@ -133,7 +240,10 @@ describe('PouchDBDataCollection', () => { describe('#update', () => { it('should update an existing object in the collection', () => { - return dataCollection.update('thing1', { thing: 100 }) + return createDatabase() + .then(() => { + return dataCollection.update('thing1', {thing : 100}); + }) .then(() => { sinon.assert.calledOnce(mockDataService.handleAction); sinon.assert.calledWith(mockDataService.handleAction, sinon.match.func); @@ -145,13 +255,36 @@ describe('PouchDBDataCollection', () => { .then((doc) => { delete doc._id; delete doc._rev; - doc.should.deep.equal({ thing: 100 }); + doc.should.deep.equal({thing : 100}); + }); + }); + + it('should update an existing object in the collection with uuid', () => { + return createDatabase('myNetwork') + .then(() => { + return dataCollection.update('thing1', {thing : 100}); + }) + .then(() => { + sinon.assert.calledOnce(mockDataService.handleAction); + sinon.assert.calledWith(mockDataService.handleAction, sinon.match.func); + return mockDataService.handleAction.args[0][0](); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['myNetwork', 'doge', 'thing1'])); + }) + .then((doc) => { + delete doc._id; + delete doc._rev; + doc.should.deep.equal({thing : 100, $networkId : 'myNetwork'}); }); }); it('should throw an error updating a non-existent object in the collection', () => { - return dataCollection.update('thing4', { thing: 4 }) - .should.be.rejectedWith(/Object with ID .* in collection with ID .* does not exist/); + return createDatabase() + .then(() => { + return dataCollection.update('thing4', {thing : 4}) + .should.be.rejectedWith(/Object with ID .* in collection with ID .* does not exist/); + }); }); }); @@ -159,7 +292,10 @@ describe('PouchDBDataCollection', () => { describe('#remove', () => { it('should remove an existing object from the collection', () => { - return dataCollection.remove('thing1') + return createDatabase() + .then(() => { + return dataCollection.remove('thing1'); + }) .then(() => { sinon.assert.calledOnce(mockDataService.handleAction); sinon.assert.calledWith(mockDataService.handleAction, sinon.match.func); @@ -171,11 +307,28 @@ describe('PouchDBDataCollection', () => { }); }); - it('should throw an error removing a non-existent object from the collection', () => { - return dataCollection.remove('thing4', { thing: 4 }) - .should.be.rejectedWith(/Object with ID .* in collection with ID .* does not exist/); + it('should remove an existing object from the collection with a uuid', () => { + return createDatabase('myNetwork') + .then(() => { + return dataCollection.remove('thing1'); + }) + .then(() => { + sinon.assert.calledOnce(mockDataService.handleAction); + sinon.assert.calledWith(mockDataService.handleAction, sinon.match.func); + return mockDataService.handleAction.args[0][0](); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['doge', 'thing1'])) + .should.be.rejectedWith(/missing/); + }); }); + it('should throw an error removing a non-existent object from the collection', () => { + return createDatabase() + .then(() => { + return dataCollection.remove('thing4', {thing : 4}) + .should.be.rejectedWith(/Object with ID .* in collection with ID .* does not exist/); + }); + }); }); - }); diff --git a/packages/composer-runtime-pouchdb/test/pouchdbdataservice.js b/packages/composer-runtime-pouchdb/test/pouchdbdataservice.js index 3659b975dc..bade4ce824 100644 --- a/packages/composer-runtime-pouchdb/test/pouchdbdataservice.js +++ b/packages/composer-runtime-pouchdb/test/pouchdbdataservice.js @@ -21,7 +21,7 @@ const PouchDBDataCollection = require('..').PouchDBDataCollection; const PouchDBDataService = require('..').PouchDBDataService; const chai = require('chai'); -chai.should(); +const should = chai.should(); chai.use(require('chai-as-promised')); const sinon = require('sinon'); @@ -39,20 +39,39 @@ describe('PouchDBDataService', () => { let db; let sandbox; - beforeEach(() => { - db = new PouchDB('Composer', { adapter: 'memory' }); + /** + * Create a database + * @param {string} uuid The uuid + * @returns {promise} The returned promise + */ + function createDatabase (uuid) { + db = new PouchDB('Composer', {adapter : 'memory'}); sandbox = sinon.sandbox.create(); sandbox.stub(PouchDBDataService, 'createPouchDB').returns(db); - dataService = new PouchDBDataService('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', { adapter: 'memory' }); - return db.bulkDocs([ - { - _id: pouchCollate.toIndexableString([collectionObjectType, 'doges1']) - }, - { - _id: pouchCollate.toIndexableString([collectionObjectType, 'doges2']) - }, - ]); - }); + + if (uuid) { + dataService = new PouchDBDataService(uuid, {adapter : 'memory'}); + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', collectionObjectType, 'doges1']) + }, + { + _id : pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', collectionObjectType, 'doges2']) + }, + ]); + } else { + dataService = new PouchDBDataService(null, {adapter : 'memory'}); + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString([collectionObjectType, 'doges1']) + }, + { + _id : pouchCollate.toIndexableString([collectionObjectType, 'doges2']) + }, + ]); + } + + } afterEach(() => { sandbox.restore(); @@ -62,25 +81,28 @@ describe('PouchDBDataService', () => { describe('#registerPouchDBPlugin', () => { beforeEach(() => { - sandbox.stub(PouchDB, 'plugin'); + return createDatabase().then(() => { + sandbox.stub(PouchDB, 'plugin'); + }); }); it('should register a PouchDB plugin', () => { - PouchDBDataService.registerPouchDBPlugin({ foo: 'bar' }); + PouchDBDataService.registerPouchDBPlugin({foo : 'bar'}); sinon.assert.calledOnce(PouchDB.plugin); - sinon.assert.calledWith(PouchDB.plugin, { foo: 'bar' }); + sinon.assert.calledWith(PouchDB.plugin, {foo : 'bar'}); }); - }); describe('#createPouchDB', () => { beforeEach(() => { - PouchDBDataService.createPouchDB.restore(); + return createDatabase().then(() => { + PouchDBDataService.createPouchDB.restore(); + }); }); it('should create a PouchDB instance', () => { - let db = PouchDBDataService.createPouchDB('Composer:3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', { adapter: 'memory' }); + let db = PouchDBDataService.createPouchDB('Composer:3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', {adapter : 'memory'}); db.should.be.an.instanceOf(PouchDB); return db.destroy(); }); @@ -89,29 +111,36 @@ describe('PouchDBDataService', () => { describe('#constructor', () => { - beforeEach(() => { - PouchDBDataService.createPouchDB.restore(); - }); - it('should create a data service with a UUID', () => { - let spy = sandbox.spy(PouchDBDataService, 'createPouchDB'); - dataService = new PouchDBDataService('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); - dataService.should.be.an.instanceOf(DataService); - sinon.assert.calledWith(spy, 'Composer:3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c').then(() => { + PouchDBDataService.createPouchDB.restore(); + let spy = sandbox.spy(PouchDBDataService, 'createPouchDB'); + dataService = new PouchDBDataService('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + dataService.should.be.an.instanceOf(DataService); + sinon.assert.calledWith(spy, 'Composer'); + dataService.uuid.should.equal('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }); }); it('should create a data service without a UUID', () => { - let spy = sandbox.spy(PouchDBDataService, 'createPouchDB'); - dataService = new PouchDBDataService(); - dataService.should.be.an.instanceOf(DataService); - sinon.assert.calledWith(spy, 'Composer'); + return createDatabase().then(() => { + PouchDBDataService.createPouchDB.restore(); + let spy = sandbox.spy(PouchDBDataService, 'createPouchDB'); + dataService = new PouchDBDataService(); + dataService.should.be.an.instanceOf(DataService); + sinon.assert.calledWith(spy, 'Composer'); + should.not.exist(dataService.uuid); + }); }); it('should create a data service with options', () => { - let spy = sandbox.spy(PouchDBDataService, 'createPouchDB'); - dataService = new PouchDBDataService('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', undefined, { adapter: 'memory' }); - dataService.should.be.an.instanceOf(DataService); - sinon.assert.calledWith(spy, 'Composer:3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', { adapter: 'memory' }); + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c').then(() => { + PouchDBDataService.createPouchDB.restore(); + let spy = sandbox.spy(PouchDBDataService, 'createPouchDB'); + dataService = new PouchDBDataService('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', undefined, {adapter : 'memory'}); + dataService.should.be.an.instanceOf(DataService); + sinon.assert.calledWith(spy, 'Composer', {adapter : 'memory'}); + }); }); }); @@ -119,8 +148,11 @@ describe('PouchDBDataService', () => { describe('#destroy', () => { it('should destroy the database', () => { - sinon.stub(dataService.db, 'destroy').resolves(); - return dataService.destroy() + return createDatabase() + .then(() => { + sinon.stub(dataService.db, 'destroy').resolves(); + return dataService.destroy(); + }) .then(() => { sinon.assert.calledOnce(dataService.db.destroy); }); @@ -131,13 +163,18 @@ describe('PouchDBDataService', () => { describe('#createCollection', () => { it('should throw if the collection already exists', () => { - return dataService.createCollection('doges1') - .should.be.rejectedWith(/Failed to add collection with ID .* as the collection already exists/); + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c').then(() => { + return dataService.createCollection('doges1') + .should.be.rejectedWith(/Failed to add collection with ID .* as the collection already exists/); + }); }); it('should create a new collection with autocommit enabled', () => { - dataService.autocommit = true; - return dataService.createCollection('doges3') + return createDatabase() + .then(() => { + dataService.autocommit = true; + return dataService.createCollection('doges3'); + }) .then((result) => { result.should.be.an.instanceOf(PouchDBDataCollection); result.db.should.equal(db); @@ -146,9 +183,26 @@ describe('PouchDBDataService', () => { }); }); + it('should create a new collection with autocommit enabled with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + dataService.autocommit = true; + return dataService.createCollection('doges3'); + }) + .then((result) => { + result.should.be.an.instanceOf(PouchDBDataCollection); + result.db.should.equal(db); + result.collectionId.should.equal('doges3'); + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', collectionObjectType, 'doges3'])); + }); + }); + it('should create a new collection with autocommit disabled', () => { - dataService.autocommit = false; - return dataService.createCollection('doges3') + return createDatabase() + .then(() => { + dataService.autocommit = false; + return dataService.createCollection('doges3'); + }) .then((result) => { result.should.be.an.instanceOf(PouchDBDataCollection); result.db.should.equal(db); @@ -164,18 +218,44 @@ describe('PouchDBDataService', () => { }); }); + it('should create a new collection with autocommit disabled with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + dataService.autocommit = false; + return dataService.createCollection('doges3'); + }) + .then((result) => { + result.should.be.an.instanceOf(PouchDBDataCollection); + result.db.should.equal(db); + result.collectionId.should.equal('doges3'); + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', collectionObjectType, 'doges3'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return dataService.transactionPrepare(); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', collectionObjectType, 'doges3'])); + }); + }); }); describe('#deleteCollection', () => { it('should throw if the collection does not exist', () => { - return dataService.deleteCollection('doges3') - .should.be.rejectedWith(/Collection with ID .* does not exist/); + return createDatabase() + .then(() => { + return dataService.deleteCollection('doges3') + .should.be.rejectedWith(/Collection with ID .* does not exist/); + }); }); it('should delete an existing collection with autocommit enabled', () => { - dataService.autocommit = true; - return dataService.transactionStart(false) + return createDatabase() + .then(() => { + dataService.autocommit = true; + return dataService.transactionStart(false); + }) .then(() => { return dataService.deleteCollection('doges1'); }) @@ -185,9 +265,27 @@ describe('PouchDBDataService', () => { }); }); + it('should delete an existing collection with autocommit enabled with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + dataService.autocommit = true; + return dataService.transactionStart(false); + }) + .then(() => { + return dataService.deleteCollection('doges1'); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', collectionObjectType, 'doges1'])) + .should.be.rejectedWith(/missing/); + }); + }); + it('should delete an existing collection with autocommit disabled', () => { - dataService.autocommit = false; - return dataService.transactionStart(false) + return createDatabase() + .then(() => { + dataService.autocommit = false; + return dataService.transactionStart(false); + }) .then(() => { return dataService.deleteCollection('doges1'); }) @@ -203,40 +301,119 @@ describe('PouchDBDataService', () => { }); }); + it('should delete an existing collection with autocommit disabled with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + dataService.autocommit = false; + return dataService.transactionStart(false); + }) + .then(() => { + return dataService.deleteCollection('doges1'); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', collectionObjectType, 'doges1'])); + }) + .then(() => { + return dataService.transactionPrepare(); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', collectionObjectType, 'doges1'])) + .should.be.rejectedWith(/missing/); + }); + }); }); describe('#getCollection', () => { it('should throw if the collection does not exist', () => { - return dataService.getCollection('doges3') - .should.be.rejectedWith(/Collection with ID .* does not exist/); + return createDatabase() + .then(() => { + return dataService.getCollection('doges3') + .should.be.rejectedWith(/Collection with ID .* does not exist/); + }); }); it('should not perform a retrieve via getDocument() if passed a boolean true bypass parameter', () => { let bypass = true; - return dataService.getCollection('doges1', bypass) - .then((result) => { + return createDatabase() + .then(() => { + return dataService.getCollection('doges1', bypass); + }) + .then(() => { + pouchCollate.toIndexableString.should.not.have.been.called; + }); + }); + + it('should not perform a retrieve via getDocument() if passed a boolean true bypass parameter with uuid', () => { + let bypass = true; + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return dataService.getCollection('doges1', bypass); + }) + .then(() => { pouchCollate.toIndexableString.should.not.have.been.called; }); }); it('should perform a retrieve via getDocument() if passed a boolean false bypass parameter', () => { let bypass = false; - return dataService.getCollection('doges1', bypass) - .then((result) => { + return createDatabase() + .then(() => { + return dataService.getCollection('doges1', bypass); + }) + .then(() => { + pouchCollate.toIndexableString.should.have.been.called; + }); + }); + + it('should perform a retrieve via getDocument() if passed a boolean false bypass parameter with uuid', () => { + let bypass = false; + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return dataService.getCollection('doges1', bypass); + }) + .then(() => { pouchCollate.toIndexableString.should.have.been.called; }); }); it('should perform a retrieve via getDocument() if not passed a bypass parameter', () => { - return dataService.getCollection('doges1') - .then((result) => { + return createDatabase() + .then(() => { + return dataService.getCollection('doges1'); + }) + .then(() => { + pouchCollate.toIndexableString.should.have.been.called; + }); + }); + + it('should perform a retrieve via getDocument() if not passed a bypass parameter with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return dataService.getCollection('doges1'); + }) + .then(() => { pouchCollate.toIndexableString.should.have.been.called; }); }); it('should return an existing collection if no bypass flags are passed', () => { - return dataService.getCollection('doges1') + return createDatabase() + .then(() => { + return dataService.getCollection('doges1'); + }) + .then((result) => { + result.should.be.an.instanceOf(PouchDBDataCollection); + result.db.should.equal(dataService.db); + result.collectionId.should.equal('doges1'); + }); + }); + + it('should return an existing collection if no bypass flags are passed with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return dataService.getCollection('doges1'); + }) .then((result) => { result.should.be.an.instanceOf(PouchDBDataCollection); result.db.should.equal(dataService.db); @@ -245,7 +422,22 @@ describe('PouchDBDataService', () => { }); it('should return an existing collection when retrieving', () => { - return dataService.getCollection('doges1', false) + return createDatabase() + .then(() => { + return dataService.getCollection('doges1', false); + }) + .then((result) => { + result.should.be.an.instanceOf(PouchDBDataCollection); + result.db.should.equal(dataService.db); + result.collectionId.should.equal('doges1'); + }); + }); + + it('should return an existing collection when retrieving with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return dataService.getCollection('doges1', false); + }) .then((result) => { result.should.be.an.instanceOf(PouchDBDataCollection); result.db.should.equal(dataService.db); @@ -254,7 +446,10 @@ describe('PouchDBDataService', () => { }); it('should return an existing collection when bypassing retrieve', () => { - return dataService.getCollection('doges1', true) + return createDatabase() + .then(() => { + return dataService.getCollection('doges1', true); + }) .then((result) => { result.should.be.an.instanceOf(PouchDBDataCollection); result.db.should.equal(dataService.db); @@ -262,18 +457,51 @@ describe('PouchDBDataService', () => { }); }); + it('should return an existing collection when bypassing retrieve with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return dataService.getCollection('doges1', true); + }) + .then((result) => { + result.should.be.an.instanceOf(PouchDBDataCollection); + result.db.should.equal(dataService.db); + result.collectionId.should.equal('doges1'); + }); + }); }); describe('#existsCollection', () => { it('should return true if a collection exists', () => { - return dataService.existsCollection('doges1') - .should.eventually.be.true; + return createDatabase() + .then(() => { + return dataService.existsCollection('doges1') + .should.eventually.be.true; + }); + }); + + it('should return true if a collection exists with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return dataService.existsCollection('doges1') + .should.eventually.be.true; + }); }); it('should return false if a collection does not exist', () => { - return dataService.existsCollection('doges3') - .should.eventually.be.false; + return createDatabase() + .then(() => { + return dataService.existsCollection('doges3') + .should.eventually.be.false; + }); + }); + + it('should return false if a collection does not exist with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return dataService.existsCollection('doges3') + .should.eventually.be.false; + }); }); @@ -281,138 +509,316 @@ describe('PouchDBDataService', () => { describe('#executeQuery', () => { - beforeEach(() => { - return db.bulkDocs([ - { - _id: pouchCollate.toIndexableString(['doges1', 'thing1']), - thingId: 1, - colour: 'red', - $class: 'org.acme.Foo1', - $registryType: 'Asset', - $registryId: 'org.acme.Foo1' - }, - { - _id: pouchCollate.toIndexableString(['doges1', 'thing2']), - thingId: 2, - colour: 'black', - $class: 'org.acme.Foo1', - $registryType: 'Asset', - $registryId: 'DogesBagOfFoos' - }, - { - _id: pouchCollate.toIndexableString(['doges1', 'thing3']), - thingId: 3, - colour: 'red', - $class: 'org.acme.Foo2', - $registryType: 'Participant', - $registryId: 'org.acme.Foo2' - }, - { - _id: pouchCollate.toIndexableString(['doges1', 'thing4']), - thingId: 4, - colour: 'green', - $class: 'org.acme.Foo2', - $registryType: 'Participant', - $registryId: 'DogesBagOfFoos' - } - ]); - }); + /** + * Create some data + * @param {string} uuid The uuid + * @returns {promise} The returned promise + */ + function createData (uuid) { + if (uuid) { + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges1', 'thing1']), + thingId : 1, + colour : 'red', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'org.acme.Foo1', + $networkId : 'other' + }, + { + _id : pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges1', 'thing2']), + thingId : 2, + colour : 'black', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'DogesBagOfFoos', + $networkId : '3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c' + }, + { + _id : pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges1', 'thing3']), + thingId : 3, + colour : 'red', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'org.acme.Foo2', + $networkId : '3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c' + }, + { + _id : pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges1', 'thing4']), + thingId : 4, + colour : 'green', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'DogesBagOfFoos', + $networkId : '3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c' + } + ]); + } else { + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString(['doges1', 'thing1']), + thingId : 1, + colour : 'red', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'org.acme.Foo1', + }, + { + _id : pouchCollate.toIndexableString(['doges1', 'thing2']), + thingId : 2, + colour : 'black', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'DogesBagOfFoos', + }, + { + _id : pouchCollate.toIndexableString(['doges1', 'thing3']), + thingId : 3, + colour : 'red', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'org.acme.Foo2', + }, + { + _id : pouchCollate.toIndexableString(['doges1', 'thing4']), + thingId : 4, + colour : 'green', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'DogesBagOfFoos', + } + ]); + } + } it('should return the query results', () => { - return dataService.executeQuery('{"selector":{"colour":{"$eq":"red"}}}') - .should.eventually.be.deep.equal([{ - thingId: 1, - colour: 'red', - $class: 'org.acme.Foo1', - $registryType: 'Asset', - $registryId: 'org.acme.Foo1' - }, { - thingId: 3, - colour: 'red', - $class: 'org.acme.Foo2', - $registryType: 'Participant', - $registryId: 'org.acme.Foo2' - }]); + return createDatabase() + .then(() => { + return createData(); + }).then(() => { + return dataService.executeQuery('{"selector":{"colour":{"$eq":"red"}}}') + .should.eventually.be.deep.equal([{ + thingId : 1, + colour : 'red', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'org.acme.Foo1' + }, { + thingId : 3, + colour : 'red', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'org.acme.Foo2', + }]); + }); + }); + + it('should return the query results with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return createData('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }).then(() => { + return dataService.executeQuery('{"selector":{"colour":{"$eq":"red"}}}') + .should.eventually.be.deep.equal([{ + thingId : 3, + colour : 'red', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'org.acme.Foo2', + $networkId : '3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c' + }]); + }); }); it('should return the query results after transforming the $class variable', () => { - return dataService.executeQuery('{"selector":{"\\\\$class":"org.acme.Foo1"}}') - .should.eventually.be.deep.equal([{ - thingId: 1, - colour: 'red', - $class: 'org.acme.Foo1', - $registryType: 'Asset', - $registryId: 'org.acme.Foo1' - }, { - thingId: 2, - colour: 'black', - $class: 'org.acme.Foo1', - $registryType: 'Asset', - $registryId: 'DogesBagOfFoos' - }]); + return createDatabase() + .then(() => { + return createData(); + }).then(() => { + return dataService.executeQuery('{"selector":{"\\\\$class":"org.acme.Foo1"}}') + .should.eventually.be.deep.equal([{ + thingId : 1, + colour : 'red', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'org.acme.Foo1' + }, { + thingId : 2, + colour : 'black', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'DogesBagOfFoos' + }]); + }); + }); + + it('should return the query results after transforming the $class variable with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return createData('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }).then(() => { + return dataService.executeQuery('{"selector":{"\\\\$class":"org.acme.Foo1"}}') + .should.eventually.be.deep.equal([{ + thingId : 2, + colour : 'black', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'DogesBagOfFoos', + $networkId : '3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c' + }]); + }); }); it('should return the query results after transforming the $registryType variable', () => { - return dataService.executeQuery('{"selector":{"\\\\$registryType":"Participant"}}') - .should.eventually.be.deep.equal([{ - thingId: 3, - colour: 'red', - $class: 'org.acme.Foo2', - $registryType: 'Participant', - $registryId: 'org.acme.Foo2' - }, { - thingId: 4, - colour: 'green', - $class: 'org.acme.Foo2', - $registryType: 'Participant', - $registryId: 'DogesBagOfFoos' - }]); + return createDatabase() + .then(() => { + return createData(); + }).then(() => { + return dataService.executeQuery('{"selector":{"\\\\$registryType":"Participant"}}') + .should.eventually.be.deep.equal([{ + thingId : 3, + colour : 'red', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'org.acme.Foo2', + }, { + thingId : 4, + colour : 'green', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'DogesBagOfFoos' + }]); + }); + }); + + it('should return the query results after transforming the $registryType variable with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return createData('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }).then(() => { + return dataService.executeQuery('{"selector":{"\\\\$registryType":"Participant"}}') + .should.eventually.be.deep.equal([{ + thingId : 3, + colour : 'red', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'org.acme.Foo2', + $networkId : '3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c' + }, { + thingId : 4, + colour : 'green', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'DogesBagOfFoos', + $networkId : '3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c' + }]); + }); }); it('should return the query results after transforming the $registryId variable', () => { - return dataService.executeQuery('{"selector":{"\\\\$registryId":"DogesBagOfFoos"}}') - .should.eventually.be.deep.equal([{ - thingId: 2, - colour: 'black', - $class: 'org.acme.Foo1', - $registryType: 'Asset', - $registryId: 'DogesBagOfFoos' - }, { - thingId: 4, - colour: 'green', - $class: 'org.acme.Foo2', - $registryType: 'Participant', - $registryId: 'DogesBagOfFoos' - }]); + return createDatabase() + .then(() => { + return createData(); + }).then(() => { + return dataService.executeQuery('{"selector":{"\\\\$registryId":"DogesBagOfFoos"}}') + .should.eventually.be.deep.equal([{ + thingId : 2, + colour : 'black', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'DogesBagOfFoos' + }, { + thingId : 4, + colour : 'green', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'DogesBagOfFoos' + }]); + }); }); + it('should return the query results after transforming the $registryId variable with uuid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return createData('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }).then(() => { + return dataService.executeQuery('{"selector":{"\\\\$registryId":"DogesBagOfFoos"}}') + .should.eventually.be.deep.equal([{ + thingId : 2, + colour : 'black', + $class : 'org.acme.Foo1', + $registryType : 'Asset', + $registryId : 'DogesBagOfFoos', + $networkId : '3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c' + }, { + thingId : 4, + colour : 'green', + $class : 'org.acme.Foo2', + $registryType : 'Participant', + $registryId : 'DogesBagOfFoos', + $networkId : '3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c' + }]); + }); + }); }); describe('#clearCollection', () => { - beforeEach(() => { - return db.bulkDocs([ - { - _id: pouchCollate.toIndexableString(['doges0', 'thing1']), - thing: 1 - }, - { - _id: pouchCollate.toIndexableString(['doges1', 'thing1']), - thing: 1 - }, - { - _id: pouchCollate.toIndexableString(['doges1', 'thing2']), - thing: 2 - }, - { - _id: pouchCollate.toIndexableString(['doges2', 'thing1']), - thing: 1 - } - ]); - }); + /** + * Create some data + * @param {string} uuid The uuid + * @returns {promise} The returned promise + */ + function createData (uuid) { + if (uuid) { + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString([uuid, 'doges0', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString([uuid, 'doges1', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString([uuid, 'doges1', 'thing2']), + thing : 2 + }, + { + _id : pouchCollate.toIndexableString([uuid, 'doges2', 'thing1']), + thing : 1 + } + ]); + } else { + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString(['doges0', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString(['doges1', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString(['doges1', 'thing2']), + thing : 2 + }, + { + _id : pouchCollate.toIndexableString(['doges2', 'thing1']), + thing : 1 + } + ]); + } + } it('should remove all of the documents from a collection', () => { - return dataService.clearCollection('doges1') + return createDatabase() + .then(() => { + return createData(); + }).then(() => { + return dataService.clearCollection('doges1'); + }) .then(() => { return db.get(pouchCollate.toIndexableString(['doges0', 'thing1'])); }) @@ -429,10 +835,153 @@ describe('PouchDBDataService', () => { }); }); + + it('should remove all of the documents from a collection with uid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return createData('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }).then(() => { + return dataService.clearCollection('doges1'); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges0', 'thing1'])); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges1', 'thing1'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges1', 'thing2'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges2', 'thing1'])); + }); + }); + }); + + describe('#removeAllData', () => { + + /** + * Create some data + * @param {string} uuid The uuid + * @returns {promise} The returned promise + */ + function createData (uuid) { + if (uuid) { + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString([uuid, 'doges0', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString([uuid, 'doges1', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString([uuid, 'doges1', 'thing2']), + thing : 2 + }, + { + _id : pouchCollate.toIndexableString([uuid, 'doges2', 'thing1']), + thing : 1 + } + ]); + } else { + return db.bulkDocs([ + { + _id : pouchCollate.toIndexableString(['doges0', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString(['doges1', 'thing1']), + thing : 1 + }, + { + _id : pouchCollate.toIndexableString(['doges1', 'thing2']), + thing : 2 + }, + { + _id : pouchCollate.toIndexableString(['doges2', 'thing1']), + thing : 1 + } + ]); + } + } + + it('should remove all of the documents', () => { + return createDatabase() + .then(() => { + return createData(); + }).then(() => { + return dataService.removeAllData(); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['doges0', 'thing1'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['doges1', 'thing1'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['doges1', 'thing2'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['doges2', 'thing1'])) + .should.be.rejectedWith(/missing/); + }); + }); + + + it('should remove all of the documents with uid', () => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c') + .then(() => { + return createData('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }) + .then(() => { + return createData('another-id'); + }).then(() => { + return dataService.removeAllData(); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges0', 'thing1'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges1', 'thing1'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges1', 'thing2'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c', 'doges2', 'thing1'])) + .should.be.rejectedWith(/missing/); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['another-id', 'doges0', 'thing1'])); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['another-id', 'doges1', 'thing1'])); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['another-id', 'doges1', 'thing2'])); + }) + .then(() => { + return db.get(pouchCollate.toIndexableString(['another-id', 'doges2', 'thing1'])); + }); + }); }); describe('#handleAction', () => { + beforeEach(() => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }); + it('should call the action immediately with autocommit enabled', () => { dataService.autocommit = true; const cb = sinon.stub(); @@ -450,38 +999,41 @@ describe('PouchDBDataService', () => { return dataService.handleAction(cb) .then(() => { sinon.assert.notCalled(cb); - dataService.pendingActions.should.deep.equal([ cb ]); + dataService.pendingActions.should.deep.equal([cb]); }); }); - }); describe('#transactionStart', () => { + beforeEach(() => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }); it('should reset the list of queued actions', () => { - dataService.pendingActions = [ 1, 2, 3 ]; + dataService.pendingActions = [1, 2, 3]; return dataService.transactionStart(false) .then(() => { dataService.pendingActions.should.deep.equal([]); }); }); - }); describe('#transactionPrepare', () => { + beforeEach(() => { + return createDatabase('3a4b69c9-239c-4e3d-9c33-9c24d2bdbb1c'); + }); + it('should call all of the queued actions', () => { const cb1 = sinon.stub(), cb2 = sinon.stub(); cb1.resolves(); cb2.resolves(); - dataService.pendingActions = [ cb1, cb2 ]; + dataService.pendingActions = [cb1, cb2]; return dataService.transactionPrepare() .then(() => { sinon.assert.calledOnce(cb1); sinon.assert.calledOnce(cb2); }); }); - }); - });