diff --git a/packages/composer-playground/e2e/tests/editor-define.spec.ts b/packages/composer-playground/e2e/tests/editor-define.spec.ts index 20c1033ee5..f436106e7e 100644 --- a/packages/composer-playground/e2e/tests/editor-define.spec.ts +++ b/packages/composer-playground/e2e/tests/editor-define.spec.ts @@ -10,7 +10,6 @@ import { dragDropFile, waitForFileToExist, retrieveZipContentList } from '../uti import * as chai from 'chai'; import * as fs from 'fs'; -import * as JSZip from 'jszip'; let should = chai.should(); @@ -18,7 +17,7 @@ describe('Editor Define', (() => { // Navigate to Editor base page and move past welcome splash beforeAll(() => { - OperationsHelper.navigatePastWelcome(); + OperationsHelper.navigatePastWelcomeAndLogin(); }); describe('On initialise', (() => { @@ -183,7 +182,7 @@ describe('Editor Define', (() => { // ExportBND appears to kick off processes that cause timeout in protractor // -current work around is to refresh the page before continuing tests // -this was previously at the start as a beforeEach, but consumes time - OperationsHelper.navigatePastWelcome(); + OperationsHelper.navigatePastWelcomeAndLogin(); }); })); })); @@ -256,7 +255,7 @@ describe('Editor Define', (() => { }); })); - it('should enable the addition of a script file via radio button selection', (() => { + fit('should enable the addition of a script file via radio button selection', (() => { let startFiles = EditorHelper.retrieveNavigatorFileNames() .then((names) => { startFiles = names; @@ -275,22 +274,27 @@ describe('Editor Define', (() => { .then((list: any) => { // Previous files should still exist startFiles.forEach((element) => { + console.log('CAZ', list); + console.log('ISAAC', element); list.includes(element).should.be.true; }); // We should have added one file list.length.should.be.equal(startFiles.length + 1); + console.log('BOB'); list.includes('Script File\nlib/script.js').should.be.true; browser.waitForAngularEnabled(true); }); // -deploy enabled EditorHelper.retrieveNavigatorFileActionButtons() .then((array: any) => { + console.log('CAKE'); array[1].enabled.should.be.equal(true); }); // -active file EditorHelper.retrieveNavigatorActiveFile() .then((list: any) => { list.length.should.equal(1); + console.log('FISH'); list.includes('Script File\nlib/script.js').should.be.true; }); // deploy new item diff --git a/packages/composer-playground/e2e/tests/welcome.spec.ts b/packages/composer-playground/e2e/tests/welcome.spec.ts index 372519dd4f..f4580870d2 100644 --- a/packages/composer-playground/e2e/tests/welcome.spec.ts +++ b/packages/composer-playground/e2e/tests/welcome.spec.ts @@ -7,30 +7,36 @@ let should = chai.should(); describe('Welcome Splash', (() => { - // Navigate to Editor base page - beforeEach(() => { - browser.get(browser.baseUrl); - }); - - it('should welcome the user to Composer Playground', (() => { - let myElement = element(by.css('.welcome')).getText() - .then((myText) => { - myText.should.contain('Welcome to Hyperledger Composer Playground!'); + // Navigate to Editor base page + beforeEach(() => { + return browser.get(browser.baseUrl) + .then(() => { + return browser.executeScript('window.localStorage.clear();'); + }) + .then(() => { + return browser.refresh(); + }); }); - })); - it('should dissappear when the user clicks cancel button', (() => { - let myButton = element(by.id('welcome_exit')).click() - .then(() => { - browser.wait(ExpectedConditions.invisibilityOf(element(by.css('.welcome'))), 5000); - }); - })); - - it('should dissappear when the user clicks "Let\'s Blockchain" button', (() => { - let myButton = element(by.id('welcome_start')).click() - .then(() => { - browser.wait(ExpectedConditions.invisibilityOf(element(by.css('.welcome'))), 5000); - }); - })); + it('should welcome the user to Composer Playground', (() => { + let myElement = element(by.css('.welcome')).getText() + .then((myText) => { + myText.should.contain('Welcome to Hyperledger Composer Playground!'); + }); + })); + + it('should dissappear when the user clicks cancel button', (() => { + let myButton = element(by.id('welcome_exit')).click() + .then(() => { + browser.wait(ExpectedConditions.invisibilityOf(element(by.css('.welcome'))), 5000); + }); + })); + + it('should dissappear when the user clicks "Let\'s Blockchain" button', (() => { + let myButton = element(by.id('welcome_start')).click() + .then(() => { + browser.wait(ExpectedConditions.invisibilityOf(element(by.css('.welcome'))), 5000); + }); + })); })); diff --git a/packages/composer-playground/e2e/utils/login-helper.ts b/packages/composer-playground/e2e/utils/login-helper.ts new file mode 100644 index 0000000000..4963b4ac71 --- /dev/null +++ b/packages/composer-playground/e2e/utils/login-helper.ts @@ -0,0 +1,10 @@ +import { browser, element, by } from 'protractor'; +import { ExpectedConditions } from 'protractor'; + +export class LoginHelper { + + // Click login button + static login() { + return element(by.id('login-0-admin')).click(); + } +} diff --git a/packages/composer-playground/e2e/utils/operations-helper.ts b/packages/composer-playground/e2e/utils/operations-helper.ts index 96039d45aa..19e597c32e 100644 --- a/packages/composer-playground/e2e/utils/operations-helper.ts +++ b/packages/composer-playground/e2e/utils/operations-helper.ts @@ -1,16 +1,30 @@ import { browser, element, by } from 'protractor'; import { ExpectedConditions } from 'protractor'; +import { LoginHelper } from '../utils/login-helper.ts'; + export class OperationsHelper { - // Navigate to Editor base page and move past welcome splash - static navigatePastWelcome() { - browser.get(browser.baseUrl) - .then(() => { - return element(by.id('welcome_start')).click() - .then(() => { - return browser.wait(ExpectedConditions.invisibilityOf(element(by.css('.welcome'))), 5000); - }); - }); - }; + // Navigate to Editor base page and move past welcome splash and login + static navigatePastWelcomeAndLogin() { + return browser.get(browser.baseUrl) + .then(() => { + return browser.isElementPresent(element(by.css('.welcome'))); + }) + .then((welcomeOpen) => { + if (welcomeOpen) { + return element(by.id('welcome_start')).click() + .then(() => { + return browser.wait(ExpectedConditions.invisibilityOf(element(by.css('.welcome'))), 5000); + }) + .then(() => { + return LoginHelper.login(); + }) + .then(() => { + return browser.wait(ExpectedConditions.invisibilityOf(element(by.tagName('login'))), 5000); + }); + } + }); + + } } diff --git a/packages/composer-playground/src/app/app-routing.module.ts b/packages/composer-playground/src/app/app-routing.module.ts index 45e226ddd2..da3baf2c7c 100644 --- a/packages/composer-playground/src/app/app-routing.module.ts +++ b/packages/composer-playground/src/app/app-routing.module.ts @@ -1,18 +1,23 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; import { NoContentComponent } from './no-content'; +import { CanActivateViaLogin } from './can-activate'; +import { LoginComponent } from './login'; export const ROUTES: Routes = [ - {path: 'editor', loadChildren: 'app/editor/editor.module#EditorModule'}, - {path: 'test', loadChildren: 'app/test/test.module#TestModule'}, - {path: 'identity', loadChildren: 'app/identity/identity.module#IdentityModule'}, - {path: 'profile', loadChildren: 'app/connection-profile/connection-profile.module#ConnectionProfileModule'}, + {path: 'editor', loadChildren: 'app/editor/editor.module#EditorModule', canActivate: [CanActivateViaLogin]}, + {path: 'test', loadChildren: 'app/test/test.module#TestModule', canActivate: [CanActivateViaLogin]}, + {path: 'identity', loadChildren: 'app/identity/identity.module#IdentityModule', canActivate: [CanActivateViaLogin]}, + {path: 'profile', loadChildren: 'app/connection-profile/connection-profile.module#ConnectionProfileModule', + canActivate: [CanActivateViaLogin]}, + {path: 'login', component: LoginComponent}, {path: '', redirectTo: 'editor', pathMatch: 'full'}, {path: '**', component: NoContentComponent} ]; @NgModule({ imports: [RouterModule.forRoot(ROUTES, {useHash: false, preloadingStrategy: PreloadAllModules})], + providers: [CanActivateViaLogin], exports: [RouterModule] }) diff --git a/packages/composer-playground/src/app/app.component.html b/packages/composer-playground/src/app/app.component.html index 7e780bb912..a1169dd394 100644 --- a/packages/composer-playground/src/app/app.component.html +++ b/packages/composer-playground/src/app/app.component.html @@ -4,37 +4,40 @@ - - +
+ + +
diff --git a/packages/composer-playground/src/app/app.component.spec.ts b/packages/composer-playground/src/app/app.component.spec.ts index a6ba18c02d..3cbf6b4380 100644 --- a/packages/composer-playground/src/app/app.component.spec.ts +++ b/packages/composer-playground/src/app/app.component.spec.ts @@ -54,7 +54,7 @@ class RouterStub { set eventParams(event) { let nav; if (event.nav === 'end') { - nav = new NavigationEnd(0, event.url, null); + nav = new NavigationEnd(0, event.url, event.urlAfterRedirects); } else { nav = new NavigationStart(0, event.url); } @@ -210,7 +210,7 @@ describe('AppComponent', () => { ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(async(() => { @@ -224,11 +224,11 @@ describe('AppComponent', () => { // find DebugElements with an attached RouterLinkStubDirective linkDes = fixture.debugElement - .queryAll(By.directive(MockRouterLinkDirective)); + .queryAll(By.directive(MockRouterLinkDirective)); // get the attached link directive instances using the DebugElement injectors links = linkDes - .map((de) => de.injector.get(MockRouterLinkDirective) as MockRouterLinkDirective); + .map((de) => de.injector.get(MockRouterLinkDirective) as MockRouterLinkDirective); } describe('ngOnInit', () => { @@ -289,7 +289,7 @@ describe('AppComponent', () => { it('should open the welcome modal', () => { let welcomeModalStub = sinon.stub(component, 'openWelcomeModal'); - routerStub.eventParams = {url: '/', nav: 'end'}; + routerStub.eventParams = {url: '/login', nav: 'end'}; updateComponent(); @@ -338,7 +338,45 @@ describe('AppComponent', () => { checkVersionStub.should.not.have.been.called; welcomeModalStub.should.not.have.been.called; + })); + + it('should show header links if logged in', fakeAsync(() => { + let checkVersionStub = sinon.stub(component, 'checkVersion').returns(Promise.resolve(true)); + routerStub.eventParams = {url: '/editor', nav: 'end'}; + + updateComponent(); + + tick(); + + component['showHeaderLinks'].should.equal(true); + + checkVersionStub.should.have.been.called; + })); + + it('should not show header links if not logged in', fakeAsync(() => { + let checkVersionStub = sinon.stub(component, 'checkVersion').returns(Promise.resolve(true)); + routerStub.eventParams = {url: '/login', nav: 'end'}; + + updateComponent(); + + tick(); + + component['showHeaderLinks'].should.equal(false); + checkVersionStub.should.have.been.called; + })); + + it('should not show header links if redirected to login', fakeAsync(() => { + let checkVersionStub = sinon.stub(component, 'checkVersion').returns(Promise.resolve(true)); + routerStub.eventParams = {url: '/editor', nav: 'end', urlAfterRedirects: '/login'}; + + updateComponent(); + + tick(); + + component['showHeaderLinks'].should.equal(false); + + checkVersionStub.should.have.been.called; })); }); @@ -359,6 +397,7 @@ describe('AppComponent', () => { it('can get RouterLinks from template', () => { activatedRoute.testParams = {}; + component['showHeaderLinks'] = true; updateComponent(); @@ -372,6 +411,7 @@ describe('AppComponent', () => { activatedRoute.testParams = {}; component['usingLocally'] = true; + component['showHeaderLinks'] = true; updateComponent(); @@ -382,7 +422,16 @@ describe('AppComponent', () => { links[3].linkParams.should.deep.equal(['profile']); }); + it('should not show links when not logged in', () => { + activatedRoute.testParams = {}; + + updateComponent(); + + links.length.should.equal(0); + }); + it('can click test link in template', () => { + component['showHeaderLinks'] = true; updateComponent(); const testLinkDe = linkDes[1]; @@ -397,6 +446,7 @@ describe('AppComponent', () => { }); it('can click editor link in template', () => { + component['showHeaderLinks'] = true; updateComponent(); const testLinkDe = linkDes[0]; @@ -411,6 +461,7 @@ describe('AppComponent', () => { }); it('can click identity link in template', () => { + component['showHeaderLinks'] = true; updateComponent(); const testLinkDe = linkDes[2]; @@ -425,6 +476,7 @@ describe('AppComponent', () => { }); it('can click profile link in template', () => { + component['showHeaderLinks'] = true; component['usingLocally'] = true; updateComponent(); @@ -456,90 +508,68 @@ describe('AppComponent', () => { errorStatusSpy = sinon.spy(mockAlertService.errorStatus$, 'next'); })); - it('should deal with an invitation when already in wallet', fakeAsync(() => { - mockIdentityService.setIdentity.returns(Promise.resolve()); - mockAdminService.getAdminConnection.returns(mockAdminConnection); - mockWallet.contains.returns(Promise.resolve(true)); - mockWalletService.getWallet.returns(mockWallet); - activatedRoute.testParams = {invitation: 'N4Igxg9gdlCmYBcCW0AKAnCAzJAbWAcgIYC2sIABAFwUgBGEdIANLZDPMmpjvpTSBIBPDNjzlWIAK4BnWOgCSAEX61hAVTmKVk2fIDK8dLASrBQw2GOmAvkA'}; - - updateComponent(); - - tick(); - - mockAdminService.getAdminConnection.should.have.been.called; - mockAdminConnection.createProfile.should.have.been.calledWith('bob', 'myProfile'); - mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('bob'); - mockWalletService.getWallet.should.have.been.calledWith('bob'); - - mockWallet.contains.should.have.been.called; // With('myUserID'); - mockWallet.update.should.have.been.calledWith('myUserID', 'mySecret'); - mockIdentityService.setIdentity.should.have.been.calledWith('bob', 'myUserID'); + it('should load the connection profiles when local and logged in', fakeAsync(() => { - routerStub.navigate.should.have.been.calledWith(['/editor']); - - // This happens to avoid doing the window.location.reload which breaks the test and is really hard to stub - mockAlertService.errorStatus$.next.should.have.been.called; - })); + component['showHeaderLinks'] = true; + mockIdentityService.getCurrentIdentity.returns(Promise.resolve('bob')); + mockInitializationService.isWebOnly.returns(Promise.resolve(false)); + let updateConnectionDataMock = sinon.stub(component, 'updateConnectionData').returns(Promise.resolve()); - it('should deal with an invitation when not in wallet', fakeAsync(() => { - mockIdentityService.setIdentity.returns(Promise.resolve()); - mockAdminService.getAdminConnection.returns(mockAdminConnection); - mockWallet.contains.returns(Promise.resolve(false)); - mockWalletService.getWallet.returns(mockWallet); - activatedRoute.testParams = {invitation: 'N4Igxg9gdlCmYBcCW0AKAnCAzJAbWAcgIYC2sIABAFwUgBGEdIANLZDPMmpjvpTSBIBPDNjzlWIAK4BnWOgCSAEX61hAVTmKVk2fIDK8dLASrBQw2GOmAvkA'}; + activatedRoute.testParams = {}; updateComponent(); tick(); - mockAdminService.getAdminConnection.should.have.been.called; - mockAdminConnection.createProfile.should.have.been.calledWith('bob', 'myProfile'); - mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('bob'); - mockWalletService.getWallet.should.have.been.calledWith('bob'); + // update now got info back about if local or not + updateComponent(); + + mockConnectionProfileService.getCurrentConnectionProfile.should.have.been.called; + updateConnectionDataMock.should.have.been.calledTwice; - mockWallet.contains.should.have.been.calledWith('myUserID'); - mockWallet.add.should.have.been.calledWith('myUserID', 'mySecret'); - mockIdentityService.setIdentity.should.have.been.calledWith('bob', 'myUserID'); + mockInitializationService.initialize.should.have.been.called; - routerStub.navigate.should.have.been.calledWith(['/editor']); + component['usingLocally'].should.equal(true); - // This happens to avoid doing the window.location.reload which breaks the test and is really hard to stub - mockAlertService.errorStatus$.next.should.have.been.called; + links.length.should.equal(4); + links[0].linkParams.should.deep.equal(['editor']); + links[1].linkParams.should.deep.equal(['test']); + links[2].linkParams.should.deep.equal(['identity']); + links[3].linkParams.should.deep.equal(['profile']); })); - it('should deal with an invitation that errors', fakeAsync(() => { - mockIdentityService.setIdentity.returns(Promise.resolve()); - mockAdminService.getAdminConnection.returns(mockAdminConnection); - mockWallet.contains.returns(Promise.reject('some error')); - mockWalletService.getWallet.returns(mockWallet); - activatedRoute.testParams = {invitation: 'N4Igxg9gdlCmYBcCW0AKAnCAzJAbWAcgIYC2sIABAFwUgBGEdIANLZDPMmpjvpTSBIBPDNjzlWIAK4BnWOgCSAEX61hAVTmKVk2fIDK8dLASrBQw2GOmAvkA'}; + it('should load the connection profiles when web only and logged in', fakeAsync(() => { + + component['showHeaderLinks'] = true; + mockIdentityService.getCurrentIdentity.returns(Promise.resolve('bob')); + mockInitializationService.isWebOnly.returns(Promise.resolve(true)); + let updateConnectionDataMock = sinon.stub(component, 'updateConnectionData').returns(Promise.resolve()); + + activatedRoute.testParams = {}; updateComponent(); tick(); - mockAdminService.getAdminConnection.should.have.been.called; - mockAdminConnection.createProfile.should.have.been.calledWith('bob', 'myProfile'); - mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('bob'); - mockWalletService.getWallet.should.have.been.calledWith('bob'); + // update now got info back about if local or not + updateComponent(); + + mockConnectionProfileService.getCurrentConnectionProfile.should.have.been.called; + updateConnectionDataMock.should.have.been.calledTwice; - mockWallet.contains.should.have.been.calledWith('myUserID'); - mockWallet.add.should.not.have.been.called; - mockWallet.update.should.not.have.been.called; - mockIdentityService.setIdentity.should.not.have.been.called; + mockInitializationService.initialize.should.have.been.called; - routerStub.navigate.should.not.have.been.called; + component['usingLocally'].should.equal(false); - // This happens to avoid doing the window.location.reload which breaks the test and is really hard to stub - mockAlertService.errorStatus$.next.should.have.been.called; + links.length.should.equal(3); + links[0].linkParams.should.deep.equal(['editor']); + links[1].linkParams.should.deep.equal(['test']); + links[2].linkParams.should.deep.equal(['identity']); })); - it('should load the connection profiles when local', fakeAsync(() => { + it('should load the connection profiles when local but not logged in', fakeAsync(() => { mockIdentityService.getCurrentIdentity.returns(Promise.resolve('bob')); mockInitializationService.isWebOnly.returns(Promise.resolve(false)); - mockBusinessNetworkConnection.ping.returns(Promise.resolve({version: 1.0, participant: 'bob'})); - mockClientService.getBusinessNetworkConnection.returns(mockBusinessNetworkConnection); let updateConnectionDataMock = sinon.stub(component, 'updateConnectionData').returns(Promise.resolve()); activatedRoute.testParams = {}; @@ -555,30 +585,15 @@ describe('AppComponent', () => { updateConnectionDataMock.should.have.been.calledTwice; mockInitializationService.initialize.should.have.been.called; - mockClientService.getBusinessNetworkConnection.should.have.been.called; - mockBusinessNetworkConnection.ping.should.have.been.called; - - mockIdentityService.getCurrentIdentity.should.have.been.called; component['usingLocally'].should.equal(true); - component['currentIdentity'].should.equal('bob'); - component['composerRuntimeVersion'].should.equal(1.0); - component['participantFQI'].should.equal('bob'); - links.length.should.equal(4); - links[0].linkParams.should.deep.equal(['editor']); - links[1].linkParams.should.deep.equal(['test']); - links[2].linkParams.should.deep.equal(['identity']); - links[3].linkParams.should.deep.equal(['profile']); + links.length.should.equal(0); })); - it('should load the connection profiles but get no info from ping', fakeAsync(() => { - component['composerRuntimeVersion'] = '1.0'; - component['participantFQI'] = 'bob'; + it('should load the connection profiles when web only but not logged in', fakeAsync(() => { mockIdentityService.getCurrentIdentity.returns(Promise.resolve('bob')); mockInitializationService.isWebOnly.returns(Promise.resolve(true)); - mockBusinessNetworkConnection.ping.returns(Promise.resolve({})); - mockClientService.getBusinessNetworkConnection.returns(mockBusinessNetworkConnection); let updateConnectionDataMock = sinon.stub(component, 'updateConnectionData').returns(Promise.resolve()); activatedRoute.testParams = {}; @@ -587,19 +602,17 @@ describe('AppComponent', () => { tick(); + // update now got info back about if local or not + updateComponent(); + mockConnectionProfileService.getCurrentConnectionProfile.should.have.been.called; updateConnectionDataMock.should.have.been.calledTwice; mockInitializationService.initialize.should.have.been.called; - mockClientService.getBusinessNetworkConnection.should.have.been.called; - mockBusinessNetworkConnection.ping.should.have.been.called; - - mockIdentityService.getCurrentIdentity.should.have.been.called; component['usingLocally'].should.equal(false); - component['currentIdentity'].should.equal('bob'); - component['composerRuntimeVersion'].should.equal('1.0'); - component['participantFQI'].should.equal('bob'); + + links.length.should.equal(0); })); }); @@ -971,4 +984,33 @@ describe('AppComponent', () => { }); }); + describe('logout', () => { + let mockOnBusy; + let mockOnError; + let mockQueryParamsUpdated; + + beforeEach(async(() => { + mockOnBusy = sinon.stub(component, 'onBusyStatus'); + mockOnError = sinon.stub(component, 'onErrorStatus'); + mockQueryParamsUpdated = sinon.stub(component, 'queryParamsUpdated'); + + })); + + it('should log the user out', fakeAsync(() => { + routerStub.navigate.returns(Promise.resolve(true)); + activatedRoute.testParams = {}; + updateComponent(); + + component.logout(); + + tick(); + + mockClientService.disconnect.should.have.been.called; + mockIdentityService.setCurrentIdentity.should.have.been.calledWith(null); + mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith(null); + mockIdentityService.setLoggedIn.should.have.been.calledWith(false); + routerStub.navigate.should.have.been.calledWith(['/login']); + })); + }); + }); diff --git a/packages/composer-playground/src/app/app.component.ts b/packages/composer-playground/src/app/app.component.ts index d6b6609e53..338928e5b7 100644 --- a/packages/composer-playground/src/app/app.component.ts +++ b/packages/composer-playground/src/app/app.component.ts @@ -45,6 +45,7 @@ export class AppComponent implements OnInit, OnDestroy { private subs: any = null; private usingLocally = false; + private showHeaderLinks = false; private composerRuntimeVersion = ''; private participantFQI = ''; @@ -82,16 +83,7 @@ export class AppComponent implements OnInit, OnDestroy { this.onEvent(eventStatus); }), this.router.events.filter((e) => e instanceof NavigationEnd).subscribe((e) => { - if (e['url'] === '/') { - this.openWelcomeModal(); - } else { - return this.checkVersion().then((success) => { - if (!success) { - this.openVersionModal(); - } - }); - } - + this.processRouteEvent(e); }) ]; } @@ -102,97 +94,80 @@ export class AppComponent implements OnInit, OnDestroy { }); } - queryParamsUpdated(queryParams: Object): Promise { - // Check for the invitation if specified. - let invitation = queryParams['invitation']; - if (invitation) { - let invitationData = JSON.parse(LZString.decompressFromEncodedURIComponent(invitation)); - let connectionProfileName = invitationData.connectionProfileName; - let connectionProfile = invitationData.connectionProfile; - let userID = invitationData.userID; - let userSecret = invitationData.userSecret; - // Create the connection profile and set it as the default. - this.adminService.getAdminConnection().createProfile(connectionProfileName, connectionProfile); - this.connectionProfileService.setCurrentConnectionProfile(connectionProfileName); - // Add the credentials to the wallet. - let wallet = this.walletService.getWallet(connectionProfileName); - return wallet.contains(userID) - .then((exists) => { - if (exists) { - return wallet.update(userID, userSecret); - } else { - return wallet.add(userID, userSecret); + logout() { + this.clientService.disconnect(); + this.identityService.setCurrentIdentity(null); + this.connectionProfileService.setCurrentConnectionProfile(null); + this.identityService.setLoggedIn(false); + + return this.router.navigate(['/login']); + + } + + processRouteEvent(event): Promise { + let welcomePromise; + if (event['url'] === '/login') { + welcomePromise = this.openWelcomeModal(); + } else { + welcomePromise = this.checkVersion().then((success) => { + if (!success) { + this.openVersionModal(); } - }) - .then(() => { - return this.identityService.setIdentity(connectionProfileName, userID); - }) - .then(() => { - return this.router.navigate(['/editor']) - .then((result) => { - if (result) { - window.location.reload(); - } else { - throw new Error('Failed to navigate to main page'); - } - }); - }) - .catch((error) => { - this.alertService.errorStatus$.next(error); }); } + if (event['url'] === '/login' || event['urlAfterRedirects'] === '/login') { + this.showHeaderLinks = false; + } else { + this.showHeaderLinks = true; + } + + return welcomePromise; + } + + queryParamsUpdated(queryParams: Object): Promise { // We load the connection profiles now, so we can immediately populate the menu. this.currentConnectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); return this.updateConnectionData() - .then(() => { - return this.initializationService.initialize(); - }) - .then(() => { - return this.clientService.getBusinessNetworkConnection().ping(); - }) - .then((ping) => { - this.composerRuntimeVersion = ping.version || this.composerRuntimeVersion; - this.participantFQI = ping.participant || this.participantFQI; - // We then load the connection profiles again, as the connect calls may have - // created versions of the default connection profiles. - return this.updateConnectionData(); - }) - .then(() => { - return this.identityService.getCurrentIdentity(); - }) - .then((currentIdentity) => { - this.currentIdentity = currentIdentity; - return this.initializationService.isWebOnly(); - }) - .then((webOnly) => { - if (webOnly) { - this.usingLocally = false; - } else { - this.usingLocally = true; - } - }); + .then(() => { + return this.initializationService.initialize(); + }) + .then(() => { + // We then load the connection profiles again, as the connect calls may have + // created versions of the default connection profiles. + return this.updateConnectionData(); + }) + .then(() => { + return this.initializationService.isWebOnly(); + }) + .then((webOnly) => { + if (webOnly) { + this.usingLocally = false; + } else { + this.usingLocally = true; + } + }); } updateConnectionData(): Promise { let newConnectionProfiles = []; return this.adminService.getAdminConnection().getAllProfiles() - .then((connectionProfiles) => { - let keys = Object.keys(connectionProfiles).sort(); - keys.forEach((key) => { - let connectionProfile = connectionProfiles[key]; - newConnectionProfiles.push({ - name: key, - profile: connectionProfile, - default: key === '$default' + .then((connectionProfiles) => { + let keys = Object.keys(connectionProfiles).sort(); + keys.forEach((key) => { + let connectionProfile = connectionProfiles[key]; + newConnectionProfiles.push({ + name: key, + profile: connectionProfile, + default: key === '$default' + }); }); + this.connectionProfiles = newConnectionProfiles; + return this.identityService.getCurrentIdentities(); + }) + .then((identities) => { + this.identities = identities; }); - this.connectionProfiles = newConnectionProfiles; - return this.identityService.getCurrentIdentities(); - }) - .then((identities) => { - this.identities = identities; - }); } onBusyStatus(busyStatus) { diff --git a/packages/composer-playground/src/app/app.module.ts b/packages/composer-playground/src/app/app.module.ts index b71815ab1d..f6b5c4a340 100644 --- a/packages/composer-playground/src/app/app.module.ts +++ b/packages/composer-playground/src/app/app.module.ts @@ -20,6 +20,7 @@ import { AppState, InternalStateType } from './app.service'; import { AboutComponent } from './about'; import { BasicModalsModule } from './basic-modals/basic-models.module'; import { WelcomeComponent } from './welcome'; +import { LoginComponent } from './login'; import { NoContentComponent } from './no-content'; import { VersionCheckComponent } from './version-check'; import { ServicesModule } from './services/services.module'; @@ -57,6 +58,7 @@ type StoreType = { declarations: [ AboutComponent, AppComponent, + LoginComponent, NoContentComponent, VersionCheckComponent, WelcomeComponent @@ -77,7 +79,7 @@ type StoreType = { providers: [ // expose our Services and Providers into Angular's dependency injection ENV_PROVIDERS, APP_PROVIDERS, - {provide: APP_BASE_HREF, useValue: '/'}, + {provide: APP_BASE_HREF, useValue: '/'} ] }) export class AppModule { diff --git a/packages/composer-playground/src/app/can-activate.ts b/packages/composer-playground/src/app/can-activate.ts new file mode 100644 index 0000000000..a7b9df302b --- /dev/null +++ b/packages/composer-playground/src/app/can-activate.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router } from '@angular/router'; +import { IdentityService } from './services/identity.service'; + +@Injectable() +export class CanActivateViaLogin implements CanActivate { + + constructor(private identityService: IdentityService, + private router: Router) { + + } + + canActivate(): boolean { + let loggedIn = this.identityService.getLoggedIn(); + if (loggedIn) { + return true; + } else { + this.router.navigate(['login']); + return false; + } + } +} diff --git a/packages/composer-playground/src/app/editor/editor.component.spec.ts b/packages/composer-playground/src/app/editor/editor.component.spec.ts index cea95b2bc6..8063425bea 100644 --- a/packages/composer-playground/src/app/editor/editor.component.spec.ts +++ b/packages/composer-playground/src/app/editor/editor.component.spec.ts @@ -12,7 +12,6 @@ import { EditorComponent } from './editor.component'; import { AdminService } from '../services/admin.service'; import { ClientService } from '../services/client.service'; import { EditorService } from './editor.service'; -import { InitializationService } from '../services/initialization.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AlertService } from '../basic-modals/alert.service'; import { ModelFile, Script, AclFile } from 'composer-common'; @@ -50,7 +49,6 @@ describe('EditorComponent', () => { let mockAlertService; let mockClientService; let mockModal; - let mockInitializationService; let mockModelFile; let mockScriptFile; let mockRuleFile; @@ -61,7 +59,6 @@ describe('EditorComponent', () => { mockAlertService = sinon.createStubInstance(AlertService); mockClientService = sinon.createStubInstance(ClientService); mockModal = sinon.createStubInstance(NgbModal); - mockInitializationService = sinon.createStubInstance(InitializationService); mockModelFile = sinon.createStubInstance(ModelFile); mockScriptFile = sinon.createStubInstance(Script); mockRuleFile = sinon.createStubInstance(AclFile); @@ -79,7 +76,6 @@ describe('EditorComponent', () => { {provide: ClientService, useValue: mockClientService}, {provide: NgbModal, useValue: mockModal}, {provide: AlertService, useValue: mockAlertService}, - {provide: InitializationService, useValue: mockInitializationService}, {provide: EditorService, useValue: editorService}] }); @@ -91,7 +87,7 @@ describe('EditorComponent', () => { let mockEditorFilesValidate; beforeEach(() => { - mockInitializationService.initialize.returns(Promise.resolve()); + mockClientService.ensureConnected.returns(Promise.resolve()); mockClientService.businessNetworkChanged$ = { takeWhile: sinon.stub().returns({ subscribe: (callback) => { @@ -867,6 +863,7 @@ describe('EditorComponent', () => { let mockUpdateFiles = sinon.stub(component, 'updateFiles'); mockModal.open = sinon.stub().returns({ + componentInstance: {}, result: Promise.resolve() }); @@ -886,6 +883,7 @@ describe('EditorComponent', () => { component['files'] = [{readme: true}, {model: true}]; mockModal.open = sinon.stub().returns({ + componentInstance: {}, result: Promise.resolve() }); @@ -907,6 +905,7 @@ describe('EditorComponent', () => { component['files'] = [{model: true}, {script: true}]; mockModal.open = sinon.stub().returns({ + componentInstance: {}, result: Promise.resolve() }); @@ -925,6 +924,7 @@ describe('EditorComponent', () => { let mockUpdateFiles = sinon.stub(component, 'updateFiles'); mockModal.open = sinon.stub().returns({ + componentInstance: {}, result: Promise.reject('some error') }); @@ -943,6 +943,7 @@ describe('EditorComponent', () => { let mockUpdateFiles = sinon.stub(component, 'updateFiles'); mockModal.open = sinon.stub().returns({ + componentInstance: {}, result: Promise.reject(1) }); @@ -1145,7 +1146,6 @@ describe('EditorComponent', () => { it('should open add file modal and handle cancel', fakeAsync(() => { mockModal.open = sinon.stub().returns({ componentInstance: { - businessNetwork: {} }, result: Promise.reject(1) }); @@ -1163,9 +1163,7 @@ describe('EditorComponent', () => { mockAddModel.throws('some error'); mockModal.open = sinon.stub().returns({ - componentInstance: { - businessNetwork: {} - }, + componentInstance: {}, result: Promise.resolve(mockModelFile) }); diff --git a/packages/composer-playground/src/app/editor/editor.component.ts b/packages/composer-playground/src/app/editor/editor.component.ts index 1e2165c04b..d7c8f6b3bd 100644 --- a/packages/composer-playground/src/app/editor/editor.component.ts +++ b/packages/composer-playground/src/app/editor/editor.component.ts @@ -8,7 +8,6 @@ import { ReplaceComponent } from '../basic-modals/replace-confirm'; import { AdminService } from '../services/admin.service'; import { ClientService } from '../services/client.service'; -import { InitializationService } from '../services/initialization.service'; import { AlertService } from '../basic-modals/alert.service'; import { EditorService } from './editor.service'; @@ -58,7 +57,6 @@ export class EditorComponent implements OnInit, OnDestroy { constructor(private adminService: AdminService, private clientService: ClientService, - private initializationService: InitializationService, private modalService: NgbModal, private alertService: AlertService, private editorService: EditorService) { @@ -66,36 +64,35 @@ export class EditorComponent implements OnInit, OnDestroy { } ngOnInit(): Promise { - return this.initializationService.initialize() - .then(() => { - this.clientService.businessNetworkChanged$.takeWhile(() => this.alive) - .subscribe((noError) => { - if (this.editorFilesValidate() && noError) { - this.noError = noError; - this.dirty = true; - } else { - this.noError = false; - } - }); + return this.clientService.ensureConnected().then(() => { + this.clientService.businessNetworkChanged$.takeWhile(() => this.alive) + .subscribe((noError) => { + if (this.editorFilesValidate() && noError) { + this.noError = noError; + this.dirty = true; + } else { + this.noError = false; + } + }); - this.clientService.namespaceChanged$.takeWhile(() => this.alive) - .subscribe((newName) => { - if (this.currentFile !== null) { - this.updateFiles(); - let index = this.findFileIndex(true, newName); - this.setCurrentFile(this.files[index]); - } - }); + this.clientService.namespaceChanged$.takeWhile(() => this.alive) + .subscribe((newName) => { + if (this.currentFile !== null) { + this.updateFiles(); + let index = this.findFileIndex(true, newName); + this.setCurrentFile(this.files[index]); + } + }); - this.updatePackageInfo(); - this.updateFiles(); + this.updatePackageInfo(); + this.updateFiles(); - if (this.editorService.getCurrentFile() !== null) { - this.setCurrentFile(this.editorService.getCurrentFile()); - } else { - this.setInitialFile(); - } - }); + if (this.editorService.getCurrentFile() !== null) { + this.setCurrentFile(this.editorService.getCurrentFile()); + } else { + this.setInitialFile(); + } + }); } ngOnDestroy() { @@ -336,7 +333,10 @@ export class EditorComponent implements OnInit, OnDestroy { } openImportModal() { - this.modalService.open(ImportComponent).result.then((result) => { + const importModalRef = this.modalService.open(ImportComponent); + // only want to update here not deploy + importModalRef.componentInstance.deployNetwork = false; + importModalRef.result.then((result) => { this.updatePackageInfo(); this.updateFiles(); if (this.files.length) { @@ -412,7 +412,7 @@ export class EditorComponent implements OnInit, OnDestroy { .then(() => { this.dirty = false; this.deploying = false; - return this.clientService.refresh(); + return this.clientService.refresh(this.clientService.getBusinessNetworkName()); }) .then(() => { this.updatePackageInfo(); diff --git a/packages/composer-playground/src/app/editor/editor.service.ts b/packages/composer-playground/src/app/editor/editor.service.ts index 0bec110cf2..13166875e0 100644 --- a/packages/composer-playground/src/app/editor/editor.service.ts +++ b/packages/composer-playground/src/app/editor/editor.service.ts @@ -12,5 +12,4 @@ export class EditorService { setCurrentFile(cf: any) { this.currentFile = cf; } - } diff --git a/packages/composer-playground/src/app/editor/import/import.component.spec.ts b/packages/composer-playground/src/app/editor/import/import.component.spec.ts index efe43c079b..1cec422f8e 100644 --- a/packages/composer-playground/src/app/editor/import/import.component.spec.ts +++ b/packages/composer-playground/src/app/editor/import/import.component.spec.ts @@ -134,7 +134,7 @@ describe('ImportComponent', () => { let onShowMock; beforeEach(() => { - mockClientService.ensureConnected.returns(Promise.resolve()); + mockAdminService.connectWithoutNetwork.returns(Promise.resolve()); onShowMock = sinon.stub(component, 'onShow'); }); @@ -279,7 +279,7 @@ describe('ImportComponent', () => { }); describe('deploy', () => { - it('should deploy a business network from github', fakeAsync(() => { + it('should deploy a business network from npm', fakeAsync(() => { let deployNpmMock = sinon.stub(component, 'deployFromNpm').returns(Promise.resolve()); @@ -305,36 +305,39 @@ describe('ImportComponent', () => { result: Promise.resolve(true) }); - component['currentBusinessNetwork'] = {network: 'my network'}; + component['deployNetwork'] = true; + component['currentBusinessNetwork'] = {network: 'my network', getName : sinon.stub()}; mockBusinessNetworkService.deployBusinessNetwork.returns(Promise.resolve()); component.deploy(); tick(); - mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network'}); + mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network', getName : sinon.match.func}, true); component['deployInProgress'].should.equal(false); mockActiveModal.close.should.have.been.called; })); - it('should handle rate limit error', fakeAsync(() => { + it('should update a business network from business network', fakeAsync(() => { mockNgbModal.open = sinon.stub().returns({ - result: Promise.resolve(true), - componentInstance: {} + componentInstance: {}, + result: Promise.resolve(true) }); - component['currentBusinessNetwork'] = {network: 'my network'}; - mockBusinessNetworkService.deployBusinessNetwork.returns(Promise.reject({message: 'API rate limit exceeded'})); + component['deployNetwork'] = false; + component['currentBusinessNetwork'] = {network: 'my network', getName : sinon.stub()}; + mockBusinessNetworkService.deployBusinessNetwork.returns(Promise.resolve()); component.deploy(); tick(); - mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network'}); + mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network', getName : sinon.match.func}, false); + component['deployInProgress'].should.equal(false); - component.modalService.open.should.have.been.called; + mockActiveModal.close.should.have.been.called; })); it('should handle error', fakeAsync(() => { @@ -344,27 +347,38 @@ describe('ImportComponent', () => { componentInstance: {} }); - component['currentBusinessNetwork'] = {network: 'my network'}; + component['currentBusinessNetwork'] = {network: 'my network', getName : sinon.stub()}; mockBusinessNetworkService.deployBusinessNetwork.returns(Promise.reject({message: 'some error'})); component.deploy(); tick(); - mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network'}); + mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network', getName : sinon.match.func}); component['deployInProgress'].should.equal(false); component.modalService.open.should.have.been.called; })); }); describe('deployFromNpm', () => { - it('should deploy from npm', () => { + it('should get sample from npm and deploy', () => { + component['sampleNetworks'] = [{name: 'bob'}, {name: 'fred'}]; + component['chosenNetwork'] = 'fred'; + component['deployNetwork'] = true; + + component.deployFromNpm(); + + mockBusinessNetworkService.deployChosenSample.should.have.been.calledWith({name: 'fred'}, true); + }); + + it('should get sample from npm and update', () => { component['sampleNetworks'] = [{name: 'bob'}, {name: 'fred'}]; component['chosenNetwork'] = 'fred'; + component['deployNetwork'] = false; component.deployFromNpm(); - mockBusinessNetworkService.deployChosenSample.should.have.been.calledWith({name: 'fred'}); + mockBusinessNetworkService.deployChosenSample.should.have.been.calledWith({name: 'fred'}, false); }); it('should deploy the empty business network if chosen', () => { diff --git a/packages/composer-playground/src/app/editor/import/import.component.ts b/packages/composer-playground/src/app/editor/import/import.component.ts index 45e24b1f7d..64f4010ef9 100644 --- a/packages/composer-playground/src/app/editor/import/import.component.ts +++ b/packages/composer-playground/src/app/editor/import/import.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input } from '@angular/core'; import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AdminService } from '../../services/admin.service'; @@ -20,6 +20,9 @@ const fabricComposerRepository = 'composer-sample-networks'; }) export class ImportComponent implements OnInit { + // choose whether to deploy or update the business network + @Input() deployNetwork: boolean; + private deployInProgress: boolean = false; private gitHubInProgress: boolean = false; private sampleNetworks = []; @@ -40,15 +43,15 @@ export class ImportComponent implements OnInit { public activeModal: NgbActiveModal, public modalService: NgbModal, private sampleBusinessNetworkService: SampleBusinessNetworkService, - private alertService: AlertService) { + private alertService: AlertService, + private adminService: AdminService) { } ngOnInit(): Promise { - // TODO: try and do this when we close modal this.currentBusinessNetwork = null; - return this.clientService.ensureConnected(false) + return this.adminService.connectWithoutNetwork(false) .then(() => { this.onShow(); }); @@ -105,14 +108,15 @@ export class ImportComponent implements OnInit { this.deployInProgress = true; let deployPromise; if (this.currentBusinessNetwork) { - deployPromise = this.sampleBusinessNetworkService.deployBusinessNetwork(this.currentBusinessNetwork); + deployPromise = this.sampleBusinessNetworkService.deployBusinessNetwork(this.currentBusinessNetwork, this.deployNetwork); } else { deployPromise = this.deployFromNpm(); } deployPromise.then(() => { this.deployInProgress = false; - this.activeModal.close(); + let deployedBusinessNetwork = this.currentBusinessNetwork ? this.currentBusinessNetwork.getName() : this.chosenNetwork; + this.activeModal.close(deployedBusinessNetwork); }) .catch((error) => { this.deployInProgress = false; @@ -174,7 +178,7 @@ export class ImportComponent implements OnInit { }; let emptyBizNetDef = new BusinessNetworkDefinition('', '', packageJson, readme); this.currentBusinessNetwork = emptyBizNetDef; - return this.sampleBusinessNetworkService.deployBusinessNetwork(this.currentBusinessNetwork); + return this.sampleBusinessNetworkService.deployBusinessNetwork(this.currentBusinessNetwork, this.deployNetwork); } else { @@ -182,7 +186,7 @@ export class ImportComponent implements OnInit { return sampleNetwork.name === this.chosenNetwork; }); - return this.sampleBusinessNetworkService.deployChosenSample(chosenSampleNetwork); + return this.sampleBusinessNetworkService.deployChosenSample(chosenSampleNetwork, this.deployNetwork); } } diff --git a/packages/composer-playground/src/app/identity/identity.component.spec.ts b/packages/composer-playground/src/app/identity/identity.component.spec.ts index 29e1ef71e9..0cc97822a9 100644 --- a/packages/composer-playground/src/app/identity/identity.component.spec.ts +++ b/packages/composer-playground/src/app/identity/identity.component.spec.ts @@ -266,7 +266,7 @@ describe(`IdentityComponent`, () => { component['currentIdentity'].should.equal('bob'); mockIdentityService.setCurrentIdentity.should.have.been.calledWith('bob'); - mockClientService.ensureConnected.should.have.been.calledWith(true); + mockClientService.ensureConnected.should.have.been.calledWith(null, true); mockAlertService.busyStatus$.next.should.have.been.calledTwice; })); diff --git a/packages/composer-playground/src/app/identity/identity.component.ts b/packages/composer-playground/src/app/identity/identity.component.ts index 71c889077c..cd7b0f5c4e 100644 --- a/packages/composer-playground/src/app/identity/identity.component.ts +++ b/packages/composer-playground/src/app/identity/identity.component.ts @@ -91,7 +91,7 @@ export class IdentityComponent implements OnInit { this.currentIdentity = newIdentity; this.alertService.busyStatus$.next({title: 'Reconnecting...', text: 'Using identity ' + this.currentIdentity}); - return this.clientService.ensureConnected(true) + return this.clientService.ensureConnected(null, true) .then(() => { this.alertService.busyStatus$.next(null); }) diff --git a/packages/composer-playground/src/app/login/index.ts b/packages/composer-playground/src/app/login/index.ts new file mode 100644 index 0000000000..69c16441fb --- /dev/null +++ b/packages/composer-playground/src/app/login/index.ts @@ -0,0 +1 @@ +export * from './login.component'; diff --git a/packages/composer-playground/src/app/login/login.component.html b/packages/composer-playground/src/app/login/login.component.html new file mode 100644 index 0000000000..72759eefb2 --- /dev/null +++ b/packages/composer-playground/src/app/login/login.component.html @@ -0,0 +1,58 @@ +
+ My Wallet +
+
+
+

Identities for {{connectionProfile}} +
+ + +
+

+
+
+
+
+ {{identity.userId.substring(0,2)}} +
+
+ {{identity.userId}} +
+
+ + +
+
+
+

BUSINESS NETWORK

+

{{identity.businessNetwork}}

+
+ +
+
+
+
+
+
+ diff --git a/packages/composer-playground/src/app/login/login.component.scss b/packages/composer-playground/src/app/login/login.component.scss new file mode 100644 index 0000000000..13463efd87 --- /dev/null +++ b/packages/composer-playground/src/app/login/login.component.scss @@ -0,0 +1,125 @@ +@import '../../assets/styles/base/_colors.scss'; +@import '../../assets/styles/base/_variables.scss'; + +login { + width: 100vw; + padding: $space-large $space-medium; + background-color: $fourth-highlight; + + .header { + margin-bottom: $space-medium; + } + + .main-view { + overflow-y: auto; + height: 80vh; + } + + div.connection-profile:first-child { + margin-top: 0; + } + + div.connection-profile { + margin-top: $space-large; + + .connection-profile-buttons { + display: inline-block; + margin-left: $space-medium; + } + + .identities { + display: flex; + flex-wrap: wrap; + + .identity { + background-color: $white; + padding: $space-large 0 $space-smedium 0; + box-shadow: 3px 6px 15px 0 rgba(0, 0, 0, 0.20); + margin-left: $space-large; + margin-bottom: $space-large; + width: 275px; + + .top-section { + + padding: 0 $space-large; + + & > * { + margin-top: $space-medium; + } + + .userId-image { + background-color: $keyline-highlight; + border: 1px solid $keyline-highlight; + border-radius: 50%; + width: 75px; + height: 75px; + margin: 0 auto; + position: relative; + + span { + position: absolute; + top: 26px; + right: 26px; + color: $second-highlight; + text-transform: uppercase; + } + } + + .userId { + text-align: center; + } + + .action-buttons { + display: flex; + justify-content: center; + + button:first-child { + margin-right: $space-large; + } + + svg { + fill: $second-highlight; + } + } + } + + .bottom-section { + + & > * { + padding: 0 $space-large; + text-align: center; + } + + .business-network { + border-top: 1px solid $fourth-highlight; + text-align: center; + color: $secondary-text; + font-size: x-small; + padding-top: $space-medium; + } + + .connect { + margin-top: $space-medium; + border-top: 1px solid $fourth-highlight; + text-align: center; + padding-top: $space-small; + + button { + span { + color: $second-highlight; + margin-right: $space-smedium; + } + svg { + fill: $second-highlight; + } + } + } + } + } + } + + } + +} + + diff --git a/packages/composer-playground/src/app/login/login.component.spec.ts b/packages/composer-playground/src/app/login/login.component.spec.ts new file mode 100644 index 0000000000..0889ad4847 --- /dev/null +++ b/packages/composer-playground/src/app/login/login.component.spec.ts @@ -0,0 +1,153 @@ +/* tslint:disable:no-unused-variable */ +/* tslint:disable:no-unused-expression */ +/* tslint:disable:no-var-requires */ +/* tslint:disable:max-classes-per-file */ +/* 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 { FormsModule } from '@angular/forms'; +import { IdentityService } from '../services/identity.service'; +import { ClientService } from '../services/client.service'; +import { BehaviorSubject } from 'rxjs/Rx'; + +import { ActivatedRoute, Router, NavigationEnd, NavigationStart } from '@angular/router'; + +import * as chai from 'chai'; + +import * as sinon from 'sinon'; +import { ConnectionProfileService } from '../services/connectionprofile.service'; +import { AdminService } from '../services/admin.service'; +import { InitializationService } from '../services/initialization.service'; + +import { LoginComponent } from './login.component'; + +let should = chai.should(); + +class RouterStub { + + // Route.events is Observable + private subject = new BehaviorSubject(this.eventParams); + public events = this.subject.asObservable(); + + // Event parameters + private _eventParams; + get eventParams() { + return this._eventParams; + } + + set eventParams(event) { + let nav; + if (event.nav === 'end') { + nav = new NavigationEnd(0, event.url, event.urlAfterRedirects); + } else { + nav = new NavigationStart(0, event.url); + } + this._eventParams = nav; + this.subject.next(nav); + } + + // Route.snapshot.events + get snapshot() { + return {params: this.events}; + } + + navigateByUrl(url: string) { + return url; + } + + navigate = sinon.stub(); + +} + +describe(`LoginComponent`, () => { + + let component: LoginComponent; + let fixture: ComponentFixture; + + let mockAdminService; + let mockIdentityService; + let mockClientService; + let mockConnectionProfileService; + let mockInitializationService; + let routerStub; + + beforeEach(() => { + + mockIdentityService = sinon.createStubInstance(IdentityService); + mockClientService = sinon.createStubInstance(ClientService); + mockConnectionProfileService = sinon.createStubInstance(ConnectionProfileService); + mockAdminService = sinon.createStubInstance(AdminService); + mockInitializationService = sinon.createStubInstance(InitializationService); + + routerStub = new RouterStub(); + + TestBed.configureTestingModule({ + imports: [FormsModule], + declarations: [ + LoginComponent + ], + providers: [ + {provide: IdentityService, useValue: mockIdentityService}, + {provide: ClientService, useValue: mockClientService}, + {provide: ConnectionProfileService, useValue: mockConnectionProfileService}, + {provide: Router, useValue: routerStub}, + {provide: AdminService, useValue: mockAdminService}, + {provide: InitializationService, useValue: mockInitializationService} + ] + }); + + fixture = TestBed.createComponent(LoginComponent); + + component = fixture.componentInstance; + }); + + describe('ngOnInit', () => { + it('should create the component', () => { + component.should.be.ok; + }); + + it('should get all identities', fakeAsync(() => { + mockInitializationService.initialize.returns(Promise.resolve()); + mockConnectionProfileService.getAllProfiles.returns(Promise.resolve({myProfile: {name: 'myProfile'}})); + + mockIdentityService.getIdentities.returns(Promise.resolve(['bob'])); + + component.ngOnInit(); + + tick(); + + mockInitializationService.initialize.should.have.been.called; + mockConnectionProfileService.getAllProfiles.should.have.been.called; + + mockIdentityService.getIdentities.should.have.been.calledWith('myProfile'); + + component['identities'].should.deep.equal({ + myProfile: [{ + userId: 'bob', + connectionProfile: 'myProfile', + businessNetwork: 'org-acme-biznet' + }]}); + })); + }); + + describe('changeIdentity', () => { + it('should change identity', fakeAsync(() => { + let identity = {userId: 'bob', connectionProfile: 'myProfile'}; + mockAdminService.list.returns(Promise.resolve(['myNetwork'])); + mockClientService.ensureConnected.returns(Promise.resolve()); + + component.changeIdentity(identity); + + tick(); + + mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('myProfile'); + mockIdentityService.setCurrentIdentity.should.have.been.calledWith('bob'); + mockAdminService.list.should.have.been.called; + mockClientService.ensureConnected.should.have.been.calledWith('myNetwork', true); + + mockIdentityService.setLoggedIn.should.have.been.calledWith(true); + routerStub.navigate.should.have.been.calledWith(['editor']); + })); + }); +}); diff --git a/packages/composer-playground/src/app/login/login.component.ts b/packages/composer-playground/src/app/login/login.component.ts new file mode 100644 index 0000000000..de7c489059 --- /dev/null +++ b/packages/composer-playground/src/app/login/login.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { IdentityService } from '../services/identity.service'; +import { AdminService } from '../services/admin.service'; +import { ConnectionProfileService } from '../services/connectionprofile.service'; +import { ClientService } from '../services/client.service'; +import { InitializationService } from '../services/initialization.service'; + +@Component({ + selector: 'login', + templateUrl: './login.component.html', + styleUrls: [ + './login.component.scss'.toString() + ] +}) +export class LoginComponent implements OnInit { + + private identities = {}; + private connectionProfiles = []; + + constructor(private identityService: IdentityService, + private router: Router, + private adminService: AdminService, + private connectionProfileService: ConnectionProfileService, + private clientService: ClientService, + private initializationService: InitializationService) { + + } + + ngOnInit() { + return this.initializationService.initialize() + .then(() => { + return this.connectionProfileService.getAllProfiles(); + }) + .then((profiles) => { + this.connectionProfiles = Object.keys(profiles); + this.connectionProfiles.forEach((profile) => { + this.identities[profile] = []; + return this.identityService.getIdentities(profile) + .then((identities) => { + identities.forEach((identity) => { + this.identities[profile].push({ + userId: identity, + connectionProfile: profile, + businessNetwork: 'org-acme-biznet' + }); + }); + }); + }); + }); + } + + changeIdentity(identity): Promise { + this.connectionProfileService.setCurrentConnectionProfile(identity.connectionProfile); + this.identityService.setCurrentIdentity(identity.userId); + return this.adminService.list() + .then((businessNetworks) => { + return this.clientService.ensureConnected(businessNetworks[0], true); + }) + .then(() => { + this.identityService.setLoggedIn(true); + return this.router.navigate(['editor']); + }); + } +} 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 3f246580aa..cd77ea70e5 100644 --- a/packages/composer-playground/src/app/services/admin.service.spec.ts +++ b/packages/composer-playground/src/app/services/admin.service.spec.ts @@ -66,131 +66,143 @@ describe('AdminService', () => { })); }); - describe('ensureConnected', () => { + describe('connect', () => { it('should return if connected', fakeAsync(inject([AdminService], (service: AdminService) => { service['isConnected'] = true; - let connectSpy = sinon.spy(service, 'connect'); - - service.ensureConnected(); + service.connect('myNetwork'); - connectSpy.should.not.have.been.called; + connectionProfileMock.getCurrentConnectionProfile.should.not.have.been.called; }))); it('should return if connecting', inject([AdminService], (service: AdminService) => { service['connectingPromise'] = Promise.resolve(); - let connectSpy = sinon.spy(service, 'connect'); - - service.ensureConnected(); + service.connect('myNetwork'); - connectSpy.should.not.have.been.called; + connectionProfileMock.getCurrentConnectionProfile.should.not.have.been.called; })); - it('should connect if not connected', fakeAsync(inject([AdminService], (service: AdminService) => { - let connectMock = sinon.stub(service, 'connect').returns(Promise.resolve()); - service.ensureConnected(false); + it('should connect', fakeAsync(inject([AdminService], (service: AdminService) => { + connectionProfileMock.getCurrentConnectionProfile.returns('my profile'); + + let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); + + identityMock.getUserID.returns(Promise.resolve('myId')); + identityMock.getUserSecret.returns(Promise.resolve('myPassword')); + + service.connect('myNetwork'); tick(); - connectMock.should.have.been.called; + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; - service['isConnected'].should.equal(true); - should.not.exist(service['connectingPromise']); - }))); + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Connecting to Business Network myNetwork', + text: 'using connection profile my profile' + }); - it('should connect without id if not deployed', fakeAsync(inject([AdminService], (service: AdminService) => { - let connectMock = sinon.stub(service, 'connect').returns(Promise.reject('error')); - let connectWithoutMock = sinon.stub(service, 'connectWithOutID').returns(Promise.resolve()); - service['madeItToConnect'] = true; - service.ensureConnected(); + identityMock.getUserID.should.have.been.called; + identityMock.getUserSecret.should.have.been.called; - tick(); + mockGetAdminConnection.should.have.been.called; - connectMock.should.have.been.called; - connectWithoutMock.should.have.been.called; + adminConnectionMock.connect.should.have.been.calledWith('my profile', 'myId', 'myPassword', 'myNetwork'); service['isConnected'].should.equal(true); - should.not.exist(service['connectingPromise']); + should.not.exist(service['isConnectingPromise']); + alertMock.busyStatus$.next.should.have.been.calledWith(null); }))); - it('should connect without id if not deployed and handle error', fakeAsync(inject([AdminService], (service: AdminService) => { - let connectMock = sinon.stub(service, 'connect').returns(Promise.reject('error')); - let connectWithoutMock = sinon.stub(service, 'connectWithOutID').returns(Promise.reject('some error')); - service['madeItToConnect'] = true; - service.ensureConnected().then(() => { - throw new Error('should not get here'); - }) - .catch((error) => { - error.should.equal('some error'); - }); + it('should connect if forced', fakeAsync(inject([AdminService], (service: AdminService) => { + connectionProfileMock.getCurrentConnectionProfile.returns('my profile'); - tick(); + let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); - alertMock.errorStatus$.next.should.have.been.called; - connectMock.should.have.been.called; - connectWithoutMock.should.have.been.called; + identityMock.getUserID.returns(Promise.resolve('myId')); + identityMock.getUserSecret.returns(Promise.resolve('myPassword')); - service['isConnected'].should.equal(false); - should.not.exist(service['connectingPromise']); - }))); + service['isConnected'] = true; - it('should connect and catch error if not made it to connect', fakeAsync(inject([AdminService], (service: AdminService) => { - let connectMock = sinon.stub(service, 'connect').returns(Promise.reject('some error')); - service['madeItToConnect'] = false; - service.ensureConnected().then(() => { - throw new Error('should not have got here'); - }) - .catch((error) => { - error.should.equal('some error'); - }); + service.connect('myNetwork', true); tick(); - connectMock.should.have.been.called; - alertMock.errorStatus$.next.should.have.been.called; - }))); + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; - it('should always connect when forced', fakeAsync(inject([AdminService], (service: AdminService) => { - service['isConnected'] = true; - let connectMock = sinon.stub(service, 'connect').returns(Promise.resolve()); - service.ensureConnected(true); + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Connecting to Business Network myNetwork', + text: 'using connection profile my profile' + }); - tick(); + identityMock.getUserID.should.have.been.called; + identityMock.getUserSecret.should.have.been.called; + + mockGetAdminConnection.should.have.been.called; - connectMock.should.have.been.called; + adminConnectionMock.connect.should.have.been.calledWith('my profile', 'myId', 'myPassword', 'myNetwork'); service['isConnected'].should.equal(true); - should.not.exist(service['connectingPromise']); + should.not.exist(service['isConnectingPromise']); + alertMock.busyStatus$.next.should.have.been.calledWith(null); }))); - }); - describe('connect', () => { - it('should connect', fakeAsync(inject([AdminService], (service: AdminService) => { + it('should handle error', fakeAsync(inject([AdminService], (service: AdminService) => { connectionProfileMock.getCurrentConnectionProfile.returns('my profile'); + adminConnectionMock.connect.returns(Promise.reject('some error')); let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); identityMock.getUserID.returns(Promise.resolve('myId')); identityMock.getUserSecret.returns(Promise.resolve('myPassword')); - service.connect(); + service.connect('myNetwork'); tick(); - mockGetAdminConnection.should.have.been.called; + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Connecting to Business Network myNetwork', + text: 'using connection profile my profile' + }); - service['userSecret'].should.equal('myPassword'); - service['userID'].should.equal('myId'); - service['madeItToConnect'].should.equal(true); + identityMock.getUserID.should.have.been.called; + identityMock.getUserSecret.should.have.been.called; - adminConnectionMock.connect.should.have.been.calledWith('my profile', 'myId', 'myPassword', 'org-acme-biznet'); + mockGetAdminConnection.should.have.been.called; + + adminConnectionMock.connect.should.have.been.calledWith('my profile', 'myId', 'myPassword', 'myNetwork'); + service['isConnected'].should.equal(false); + should.not.exist(service['isConnectingPromise']); + alertMock.errorStatus$.next.should.have.been.calledWith('some error'); + alertMock.busyStatus$.next.should.have.been.calledWith(null); }))); }); - describe('connectWithOutID', () => { + describe('connectWithOutNetwork', () => { + it('should return if connected', fakeAsync(inject([AdminService], (service: AdminService) => { + service['isConnected'] = true; + + service.connectWithoutNetwork(); + + connectionProfileMock.getCurrentConnectionProfile.should.not.have.been.called; + }))); + + it('should return if connecting', inject([AdminService], (service: AdminService) => { + service['connectingPromise'] = Promise.resolve(); + + service.connectWithoutNetwork(); + + connectionProfileMock.getCurrentConnectionProfile.should.not.have.been.called; + })); + it('should connect without an id', fakeAsync(inject([AdminService], (service: AdminService) => { + connectionProfileMock.getCurrentConnectionProfile.returns('myProfile'); + identityMock.getUserID.returns(Promise.resolve('myUser')); + identityMock.getUserSecret.returns(Promise.resolve('mySecret')); + let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); adminConnectionMock.connect.returns(Promise.resolve()); @@ -200,54 +212,251 @@ describe('AdminService', () => { adminConnectionMock.deploy.returns(Promise.resolve()); - service['userID'] = 'myUser'; - service['userSecret'] = 'mySecret'; - service['connectionProfile'] = 'myProfile'; - - service.connectWithOutID(); + service.connectWithoutNetwork(); tick(); + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Connecting without a business network', + text: 'using connection profile myProfile' + }); + + identityMock.getUserID.should.have.been.called; + identityMock.getUserSecret.should.have.been.called; + mockGetAdminConnection.should.have.been.called; - mockGenerateBusinessNetwork.should.have.been.called; - adminConnectionMock.deploy.should.have.been.calledWith({name: 'myNetwork'}); - service['initialDeploy'].should.equal(true); + adminConnectionMock.connect.should.have.been.calledWith('myProfile', 'myUser', 'mySecret'); - adminConnectionMock.disconnect.should.have.been.called; - adminConnectionMock.connect.should.have.been.calledWith('myProfile', 'myUser', 'mySecret', 'org-acme-biznet'); + service['isConnected'].should.equal(true); + should.not.exist(service['isConnectingPromise']); + alertMock.busyStatus$.next.should.have.been.calledWith(null); }))); - it('should connect without an id but not deploy as already deployed', fakeAsync(inject([AdminService], (service: AdminService) => { - adminConnectionMock.connect.returns(Promise.resolve()); - adminConnectionMock.list.returns(Promise.resolve(['org-acme-biznet'])); + it('should connect if forced', fakeAsync(inject([AdminService], (service: AdminService) => { + connectionProfileMock.getCurrentConnectionProfile.returns('myProfile'); + identityMock.getUserID.returns(Promise.resolve('myUser')); + identityMock.getUserSecret.returns(Promise.resolve('mySecret')); let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); + adminConnectionMock.connect.returns(Promise.resolve()); + adminConnectionMock.list.returns(Promise.resolve([])); + let mockGenerateBusinessNetwork = sinon.stub(service, 'generateDefaultBusinessNetwork').returns({name: 'myNetwork'}); adminConnectionMock.deploy.returns(Promise.resolve()); - service['userID'] = 'myUser'; - service['userSecret'] = 'mySecret'; - service['connectionProfile'] = 'myProfile'; + service['isConnected'] = true; - service.connectWithOutID(); + service.connectWithoutNetwork(true); tick(); + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Connecting without a business network', + text: 'using connection profile myProfile' + }); + + identityMock.getUserID.should.have.been.called; + identityMock.getUserSecret.should.have.been.called; + mockGetAdminConnection.should.have.been.called; - mockGenerateBusinessNetwork.should.not.have.been.called; + adminConnectionMock.connect.should.have.been.calledWith('myProfile', 'myUser', 'mySecret'); + + service['isConnected'].should.equal(true); + should.not.exist(service['isConnectingPromise']); + alertMock.busyStatus$.next.should.have.been.calledWith(null); + }))); + + it('should handle error', fakeAsync(inject([AdminService], (service: AdminService) => { + connectionProfileMock.getCurrentConnectionProfile.returns('myProfile'); + identityMock.getUserID.returns(Promise.resolve('myUser')); + identityMock.getUserSecret.returns(Promise.resolve('mySecret')); + + let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); + + adminConnectionMock.connect.returns(Promise.reject('some error')); + adminConnectionMock.list.returns(Promise.resolve([])); + + let mockGenerateBusinessNetwork = sinon.stub(service, 'generateDefaultBusinessNetwork').returns({name: 'myNetwork'}); + + adminConnectionMock.deploy.returns(Promise.resolve()); + + service.connectWithoutNetwork(); + + tick(); + + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Connecting without a business network', + text: 'using connection profile myProfile' + }); + + identityMock.getUserID.should.have.been.called; + identityMock.getUserSecret.should.have.been.called; + + mockGetAdminConnection.should.have.been.called; + + adminConnectionMock.connect.should.have.been.calledWith('myProfile', 'myUser', 'mySecret'); + + service['isConnected'].should.equal(false); + should.not.exist(service['isConnectingPromise']); + alertMock.errorStatus$.next.should.have.been.calledWith('some error'); + alertMock.busyStatus$.next.should.have.been.calledWith(null); + }))); + }); + + describe('createNewBusinessNetwork', () => { + it('should create a new business network', fakeAsync(inject([AdminService], (service: AdminService) => { + + let stubList = sinon.stub(service, 'list').returns(Promise.resolve(['anotherNetwork'])); + connectionProfileMock.getCurrentConnectionProfile.returns('myProfile'); + identityMock.getUserID.returns(Promise.resolve('myUser')); + identityMock.getUserSecret.returns(Promise.resolve('mySecret')); + + let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); + + adminConnectionMock.connect.returns(Promise.resolve()); + adminConnectionMock.deploy.returns(Promise.resolve()); + + let stubGenerateBusinessNetwork = sinon.stub(service, 'generateDefaultBusinessNetwork').returns({name: 'myNetwork'}); + + service.createNewBusinessNetwork('myNetwork', 'myDescription'); + + tick(); + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Checking Business Network', + text: 'checking if myNetwork exists' + }); + + stubList.should.have.been.called; + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Creating Business Network', + text: 'creating business network myNetwork' + }); + + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; + + identityMock.getUserID.should.have.been.called; + identityMock.getUserSecret.should.have.been.called; + + adminConnectionMock.connect.should.have.been.calledWith('myProfile', 'myUser', 'mySecret'); + + stubGenerateBusinessNetwork.should.have.been.calledWith('myNetwork', 'myDescription'); + + adminConnectionMock.deploy.should.have.been.calledWith({name: 'myNetwork'}); adminConnectionMock.disconnect.should.have.been.called; - adminConnectionMock.connect.should.have.been.calledWith('myProfile', 'myUser', 'mySecret', 'org-acme-biznet'); + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Connecting to Business Network myNetwork', + text: 'using connection profile, connectionProfile' + }); + + adminConnectionMock.connect.should.have.been.calledWith('myProfile', 'myUser', 'mySecret', 'myNetwork'); + + alertMock.busyStatus$.next.should.have.been.calledWith(null); + }))); + + it('should not create if name already exists', fakeAsync(inject([AdminService], (service: AdminService) => { + + let stubList = sinon.stub(service, 'list').returns(Promise.resolve(['myNetwork'])); + + let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); + + adminConnectionMock.connect.returns(Promise.resolve()); + adminConnectionMock.deploy.returns(Promise.resolve()); + + let stubGenerateBusinessNetwork = sinon.stub(service, 'generateDefaultBusinessNetwork').returns({name: 'myNetwork'}); + + service.createNewBusinessNetwork('myNetwork', 'myDescription') + .then(() => { + throw new Error('should not have got here'); + }) + .catch((error) => { + error.message.should.equal('businessNetwork with name myNetwork already exists'); + }); + + tick(); + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Checking Business Network', + text: 'checking if myNetwork exists' + }); + + stubList.should.have.been.called; + + connectionProfileMock.getCurrentConnectionProfile.should.not.have.been.called; + + identityMock.getUserID.should.not.have.been.called; + identityMock.getUserSecret.should.not.have.been.called; + + adminConnectionMock.connect.should.not.have.been.called; + + stubGenerateBusinessNetwork.should.not.have.been.called; + + adminConnectionMock.deploy.should.not.have.been.called; + + adminConnectionMock.disconnect.should.not.have.been.called; + + alertMock.busyStatus$.next.should.have.been.calledWith(null); }))); + + it('should handle error', fakeAsync(inject([AdminService], (service: AdminService) => { + + let stubList = sinon.stub(service, 'list').returns(Promise.reject(new Error('some error'))); + + let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); + + adminConnectionMock.connect.returns(Promise.resolve()); + adminConnectionMock.deploy.returns(Promise.resolve()); + + let stubGenerateBusinessNetwork = sinon.stub(service, 'generateDefaultBusinessNetwork').returns({name: 'myNetwork'}); + + service.createNewBusinessNetwork('myNetwork', 'myDescription'); + + tick(); + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Checking Business Network', + text: 'checking if myNetwork exists' + }); + + stubList.should.have.been.called; + + connectionProfileMock.getCurrentConnectionProfile.should.not.have.been.called; + + identityMock.getUserID.should.not.have.been.called; + identityMock.getUserSecret.should.not.have.been.called; + + adminConnectionMock.connect.should.not.have.been.called; + + stubGenerateBusinessNetwork.should.not.have.been.called; + + adminConnectionMock.deploy.should.not.have.been.called; + + adminConnectionMock.disconnect.should.not.have.been.called; + + alertMock.busyStatus$.next.should.have.been.calledWith(null); + + alertMock.errorStatus$.next.should.have.been.called; + }))); + }); describe('deploy', () => { it('should deploy a business network', fakeAsync(inject([AdminService], (service: AdminService) => { - let connectedMock = sinon.stub(service, 'ensureConnected').returns(Promise.resolve()); + let connectedMock = sinon.stub(service, 'connect').returns(Promise.resolve()); + businessNetworkDefMock.getName.returns('myNetwork'); service['adminConnection'] = adminConnectionMock; @@ -255,22 +464,19 @@ describe('AdminService', () => { tick(); - connectedMock.should.have.been.called; + connectedMock.should.have.been.calledWith('myNetwork'); adminConnectionMock.deploy.should.have.been.calledWith(businessNetworkDefMock); }))); }); describe('update', () => { it('should update a business network', fakeAsync(inject([AdminService], (service: AdminService) => { - let connectedMock = sinon.stub(service, 'ensureConnected').returns(Promise.resolve()); - service['adminConnection'] = adminConnectionMock; service.update(businessNetworkDefMock); tick(); - connectedMock.should.have.been.called; adminConnectionMock.update.should.have.been.calledWith(businessNetworkDefMock); }))); }); @@ -284,4 +490,47 @@ describe('AdminService', () => { service['initialDeploy'].should.equal(false); })); }); + + describe('list', () => { + it('should list the business networks', fakeAsync(inject([AdminService], (service: AdminService) => { + connectionProfileMock.getCurrentConnectionProfile.returns('myProfile'); + identityMock.getUserID.returns(Promise.resolve('myUserId')); + identityMock.getUserSecret.returns(Promise.resolve('mySecret')); + + let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); + + adminConnectionMock.connect.returns(Promise.resolve()); + adminConnectionMock.list.returns(Promise.resolve(['myNetwork'])); + + let disconnectStub = sinon.stub(service, 'disconnect'); + + service.list().then((networks) => { + networks.should.deep.equal(['myNetwork']); + }); + + tick(); + + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; + identityMock.getUserID.should.have.been.called; + identityMock.getUserSecret.should.have.been.called; + + adminConnectionMock.connect.should.have.been.calledWith('myProfile', 'myUserId', 'mySecret'); + adminConnectionMock.list.should.have.been.called; + + disconnectStub.should.have.been.called; + }))); + }); + + describe('disconnect', () => { + it('should disconnect', inject([AdminService], (service: AdminService) => { + service['isConnected'] = true; + + let mockGetAdminConnection = sinon.stub(service, 'getAdminConnection').returns(adminConnectionMock); + + service.disconnect(); + + service['isConnected'].should.equal(false); + adminConnectionMock.disconnect.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 56dad51466..c2eda8483c 100644 --- a/packages/composer-playground/src/app/services/admin.service.ts +++ b/packages/composer-playground/src/app/services/admin.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject, Subject } from 'rxjs/Rx'; import { ConnectionProfileService } from './connectionprofile.service'; import { IdentityService } from './identity.service'; import { AlertService } from '../basic-modals/alert.service'; @@ -11,15 +10,11 @@ import WebConnectionManager = require('composer-connector-web'); @Injectable() export class AdminService { private adminConnection: AdminConnection = null; + private isConnected: boolean = false; private connectingPromise: Promise = null; - private initialDeploy: boolean = false; - private madeItToConnect = false; - private connectionProfile; - private userID; - private userSecret; - private deployed = false; + private initialDeploy: boolean = false; constructor(private connectionProfileService: ConnectionProfileService, private identityService: IdentityService, @@ -40,7 +35,7 @@ export class AdminService { ConnectionProfileManager.registerConnectionManager('web', WebConnectionManager); } - getAdminConnection() { + getAdminConnection(): AdminConnection { if (!this.adminConnection) { this.adminConnection = new AdminConnection(); } @@ -48,7 +43,7 @@ export class AdminService { return this.adminConnection; } - public ensureConnected(force: boolean = false): Promise { + connect(businessNetworkName: string, force = false): Promise { if (this.isConnected && !force) { return Promise.resolve(); } else if (this.connectingPromise) { @@ -56,96 +51,185 @@ export class AdminService { } console.log('Establishing admin connection ...'); - this.connectingPromise = Promise.resolve() - .then(() => { - return this.connect(); - }) - .catch((error) => { - // If we didn't make it to connect, then the business network is probably not deployed. - // Try again with a admin connection with no business network specified so we can deploy it. - if (!this.madeItToConnect) { - throw error; - } - return this.connectWithOutID(); + let connectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); + + this.alertService.busyStatus$.next({ + title: 'Connecting to Business Network ' + businessNetworkName, + text: 'using connection profile ' + connectionProfile + }); + + let userID; + this.connectingPromise = this.identityService.getUserID() + .then((userId) => { + userID = userId; + return this.identityService.getUserSecret(); + }) + .then((userSecret) => { + console.log('Connecting to business network %s with connection profile %s with id %s', businessNetworkName, connectionProfile, userID); + return this.getAdminConnection().connect(connectionProfile, userID, userSecret, businessNetworkName); }) .then(() => { - console.log('Connected'); this.isConnected = true; this.connectingPromise = null; + this.alertService.busyStatus$.next(null); }) .catch((error) => { - this.alertService.errorStatus$.next(`Failed to connect: ${error}`); - this.isConnected = false; this.connectingPromise = null; - throw error; + this.alertService.busyStatus$.next(null); + this.alertService.errorStatus$.next(error); }); + return this.connectingPromise; } - connect(): Promise { - this.connectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); - console.log('Connecting to connection profile (w/ business network ID)', this.connectionProfile); - return this.identityService.getUserID() + connectWithoutNetwork(force = false) { + if (this.isConnected && !force) { + return Promise.resolve(); + } else if (this.connectingPromise) { + return this.connectingPromise; + } + + console.log('Establishing admin connection ...'); + + let connectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); + + this.alertService.busyStatus$.next({ + title: 'Connecting without a business network', + text: 'using connection profile ' + connectionProfile + }); + + let userID; + this.connectingPromise = this.identityService.getUserID() .then((userId) => { - this.userID = userId; + userID = userId; return this.identityService.getUserSecret(); }) .then((userSecret) => { - this.userSecret = userSecret; - this.madeItToConnect = true; - return this.getAdminConnection().connect(this.connectionProfile, this.userID, this.userSecret, 'org-acme-biznet'); + console.log('Connecting with connection profile %s with id %s', connectionProfile, userID); + return this.getAdminConnection().connect(connectionProfile, userID, userSecret); + }) + .then(() => { + this.isConnected = true; + this.connectingPromise = null; + this.alertService.busyStatus$.next(null); + }) + .catch((error) => { + this.connectingPromise = null; + this.alertService.busyStatus$.next(null); + this.alertService.errorStatus$.next(error); }); + + return this.connectingPromise; + } - connectWithOutID(): Promise { - console.log('Connecting to connection profile (w/o business network ID)', this.connectionProfile); - return this.getAdminConnection().connect(this.connectionProfile, this.userID, this.userSecret) - .then(() => { - return this.getAdminConnection().list(); - }) + public createNewBusinessNetwork(name: string, description: string): Promise { + this.alertService.busyStatus$.next({ + title: 'Checking Business Network', + text: 'checking if ' + name + ' exists' + }); + + let userSecret; + let connectionProfile; + let userId; + return this.list() .then((businessNetworks) => { - console.log('Got business networks', businessNetworks); - this.deployed = businessNetworks.some((businessNetwork) => { - return businessNetwork === 'org-acme-biznet'; + // check if business network already exists + let deployed = businessNetworks.some((businessNetwork) => { + return businessNetwork === name; }); - if (!this.deployed) { - this.alertService.busyStatus$.next({ - title: 'Creating Business Network', - text: 'creating business network org-acme-biznet' - }); - let businessNetworkDefinition = this.generateDefaultBusinessNetwork(); - return this.getAdminConnection().deploy(businessNetworkDefinition) - .then(() => { - this.initialDeploy = true; - }); + if (deployed) { + this.initialDeploy = false; + this.alertService.busyStatus$.next(null); + throw Error('businessNetwork with name ' + name + ' already exists'); } + + this.alertService.busyStatus$.next({ + title: 'Creating Business Network', + text: 'creating business network ' + name + }); + connectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); + return this.identityService.getUserID(); + }) + .then((userID) => { + userId = userID; + return this.identityService.getUserSecret(); + }) + .then((secret) => { + userSecret = secret; + return this.getAdminConnection().connect(connectionProfile, userId, userSecret); }) .then(() => { + let businessNetworkDefinition = this.generateDefaultBusinessNetwork(name, description); + return this.getAdminConnection().deploy(businessNetworkDefinition); + }) + .then(() => { + this.initialDeploy = true; return this.getAdminConnection().disconnect(); }) .then(() => { - console.log('Connecting to connection profile (w/ business network ID)', this.connectionProfile); - return this.getAdminConnection().connect(this.connectionProfile, this.userID, this.userSecret, 'org-acme-biznet'); - }); - } + this.alertService.busyStatus$.next({ + title: 'Connecting to Business Network ' + name, + text: 'using connection profile, connectionProfile' + }); - public deploy(businessNetworkDefinition: BusinessNetworkDefinition): Promise { - return this.ensureConnected() + console.log('Connecting to business network %s with connection profile %s with id %s', name, connectionProfile, userId); + return this.getAdminConnection().connect(connectionProfile, userId, userSecret, name); + }) .then(() => { - return this.adminConnection.deploy(businessNetworkDefinition); + this.alertService.busyStatus$.next(null); + }) + .catch((error) => { + this.alertService.busyStatus$.next(null); + if (error.message.startsWith('businessNetwork with name')) { + throw error; + } else { + this.alertService.errorStatus$.next(error); + } }); } - public update(businessNetworkDefinition: BusinessNetworkDefinition): Promise { - return this.ensureConnected() + public list(): Promise { + let connectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); + let userId; + let userSecret; + return this.identityService.getUserID() + .then((userID) => { + userId = userID; + return this.identityService.getUserSecret(); + }) + .then((secret) => { + userSecret = secret; + return this.getAdminConnection().connect(connectionProfile, userId, userSecret); + }) .then(() => { - return this.adminConnection.update(businessNetworkDefinition); + return this.getAdminConnection().list(); + }) + .then((businessNetworks) => { + this.disconnect(); + return businessNetworks; }); } - generateDefaultBusinessNetwork(): BusinessNetworkDefinition { - let businessNetworkDefinition = new BusinessNetworkDefinition('org-acme-biznet@0.0.1', 'Acme Business Network'); + public disconnect(): void { + this.isConnected = false; + this.getAdminConnection().disconnect(); + } + + public deploy(businessNetworkDefinition: BusinessNetworkDefinition): Promise { + return this.connect(businessNetworkDefinition.getName()).then(() => { + return this.getAdminConnection().deploy(businessNetworkDefinition); + }); + + } + + public update(businessNetworkDefinition: BusinessNetworkDefinition): Promise { + return this.getAdminConnection().update(businessNetworkDefinition); + } + + generateDefaultBusinessNetwork(name: string, description: string): BusinessNetworkDefinition { + let businessNetworkDefinition = new BusinessNetworkDefinition(name + '@0.0.1', description); return businessNetworkDefinition; } 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 8af39d2509..a4b614aa1b 100644 --- a/packages/composer-playground/src/app/services/client.service.spec.ts +++ b/packages/composer-playground/src/app/services/client.service.spec.ts @@ -17,6 +17,7 @@ import { BusinessNetworkDefinition, ModelFile, Script, AclFile } from 'composer- import { ConnectionProfileService } from './connectionprofile.service'; import { BusinessNetworkConnection } from 'composer-client'; import { IdentityService } from './identity.service'; +import { LocalStorageService } from 'angular-2-local-storage'; describe('ClientService', () => { @@ -31,6 +32,7 @@ describe('ClientService', () => { let modelFileMock; let scriptFileMock; let aclFileMock; + let mockLocalStorage; beforeEach(() => { sandbox = sinon.sandbox.create(); @@ -44,6 +46,7 @@ describe('ClientService', () => { modelFileMock = sinon.createStubInstance(ModelFile); scriptFileMock = sinon.createStubInstance(Script); aclFileMock = sinon.createStubInstance(AclFile); + mockLocalStorage = sinon.createStubInstance(LocalStorageService); alertMock.errorStatus$ = {next: sinon.stub()}; alertMock.busyStatus$ = {next: sinon.stub()}; @@ -53,7 +56,8 @@ describe('ClientService', () => { {provide: AdminService, useValue: adminMock}, {provide: AlertService, useValue: alertMock}, {provide: ConnectionProfileService, useValue: connectionProfileMock}, - {provide: IdentityService, useValue: identityMock}] + {provide: IdentityService, useValue: identityMock}, + {provide: LocalStorageService, useValue: mockLocalStorage}] }); }); @@ -705,9 +709,9 @@ describe('ClientService', () => { it('should return if connected when not forced', fakeAsync(inject([ClientService], (service: ClientService) => { service['isConnected'] = true; - service.ensureConnected(false); + service.ensureConnected(); - connectionProfileMock.getCurrentConnectionProfile.should.not.have.been.called; + identityMock.getUserID.should.not.have.been.called; }))); it('should return if connecting', fakeAsync(inject([ClientService], (service: ClientService) => { @@ -715,76 +719,132 @@ describe('ClientService', () => { service.ensureConnected(); - connectionProfileMock.getCurrentConnectionProfile.should.not.have.been.called; + identityMock.getUserID.should.not.have.been.called; }))); - it('should connect if not connected and deploy sample', fakeAsync(inject([ClientService], (service: ClientService) => { + it('should connect if not connected', fakeAsync(inject([ClientService], (service: ClientService) => { + + identityMock.getUserID.returns(Promise.resolve('myId')); + connectionProfileMock.getCurrentConnectionProfile.returns('myProfile'); - adminMock.ensureConnected.returns(Promise.resolve()); + adminMock.connect.returns(Promise.resolve()); let refreshMock = sinon.stub(service, 'refresh').returns(Promise.resolve()); + let setBusinessNetworkMock = sinon.stub(service, 'setSavedBusinessNetworkName'); - adminMock.isInitialDeploy.returns(true); + let businessNetworkNameMock = sinon.stub(service, 'getBusinessNetworkName').returns('myNetwork'); - let deployInitialSample = sinon.stub(service, 'deployInitialSample'); - - service.ensureConnected(false); + service.ensureConnected(null, false); tick(); + identityMock.getUserID.should.have.been.called; + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; - adminMock.ensureConnected.should.have.been.calledWith(false); - refreshMock.should.have.been.called; alertMock.busyStatus$.next.should.have.been.calledTwice; alertMock.busyStatus$.next.firstCall.should.have.been.calledWith({ title: 'Establishing connection', text: 'Using the connection profile myProfile' }); + adminMock.connect.should.have.been.calledWith('myNetwork', false); + + refreshMock.should.have.been.calledWith('myNetwork'); + alertMock.busyStatus$.next.secondCall.should.have.been.calledWith(null); + setBusinessNetworkMock.should.have.been.calledWith('myId'); + service['isConnected'].should.equal(true); should.not.exist(service['connectingPromise']); + }))); + + it('should connect if not connected to specified business network', fakeAsync(inject([ClientService], (service: ClientService) => { + + identityMock.getUserID.returns(Promise.resolve('myId')); + + connectionProfileMock.getCurrentConnectionProfile.returns('myProfile'); + adminMock.connect.returns(Promise.resolve()); + let refreshMock = sinon.stub(service, 'refresh').returns(Promise.resolve()); + let setBusinessNetworkMock = sinon.stub(service, 'setSavedBusinessNetworkName'); + + let businessNetworkNameMock = sinon.stub(service, 'getBusinessNetworkName'); + + service.ensureConnected('myNetwork', false); + + tick(); + + identityMock.getUserID.should.have.been.called; + + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; + + alertMock.busyStatus$.next.should.have.been.calledTwice; + alertMock.busyStatus$.next.firstCall.should.have.been.calledWith({ + title: 'Establishing connection', + text: 'Using the connection profile myProfile' + }); - deployInitialSample.should.have.been.called; + adminMock.connect.should.have.been.calledWith('myNetwork', false); + + refreshMock.should.have.been.calledWith('myNetwork'); + + alertMock.busyStatus$.next.secondCall.should.have.been.calledWith(null); + + setBusinessNetworkMock.should.have.been.called; + + businessNetworkNameMock.should.not.have.been.called; + + service['isConnected'].should.equal(true); + should.not.exist(service['connectingPromise']); }))); - it('should connect if not connected and not deploy sample if already deployed', fakeAsync(inject([ClientService], (service: ClientService) => { + it('should connect if not connected with business network from local storage', fakeAsync(inject([ClientService], (service: ClientService) => { + + identityMock.getUserID.returns(Promise.resolve('myId')); + connectionProfileMock.getCurrentConnectionProfile.returns('myProfile'); - adminMock.ensureConnected.returns(Promise.resolve()); + adminMock.connect.returns(Promise.resolve()); let refreshMock = sinon.stub(service, 'refresh').returns(Promise.resolve()); - adminMock.isInitialDeploy.returns(false); + let setBusinessNetworkMock = sinon.stub(service, 'setSavedBusinessNetworkName'); + let getBusinessNetworkMock = sinon.stub(service, 'getSavedBusinessNetworkName').returns('myNetwork'); - let deployInitialSample = sinon.stub(service, 'deployInitialSample'); + let businessNetworkNameMock = sinon.stub(service, 'getBusinessNetworkName').throws(); - service.ensureConnected(false); + service.ensureConnected(null, false); tick(); + identityMock.getUserID.should.have.been.called; + connectionProfileMock.getCurrentConnectionProfile.should.have.been.called; - adminMock.ensureConnected.should.have.been.calledWith(false); - refreshMock.should.have.been.called; alertMock.busyStatus$.next.should.have.been.calledTwice; alertMock.busyStatus$.next.firstCall.should.have.been.calledWith({ title: 'Establishing connection', text: 'Using the connection profile myProfile' }); + getBusinessNetworkMock.should.have.been.calledWith('myId'); + + adminMock.connect.should.have.been.calledWith('myNetwork', false); + + refreshMock.should.have.been.calledWith('myNetwork'); + alertMock.busyStatus$.next.secondCall.should.have.been.calledWith(null); + setBusinessNetworkMock.should.have.been.calledWith('myId'); + service['isConnected'].should.equal(true); should.not.exist(service['connectingPromise']); - - deployInitialSample.should.not.have.been.called; }))); it('should send alert if error thrown', fakeAsync(inject([ClientService], (service: ClientService) => { - adminMock.ensureConnected.returns(Promise.resolve()); + identityMock.getUserID.returns(Promise.resolve('myId')); + adminMock.connect.returns(Promise.resolve()); let refreshMock = sinon.stub(service, 'refresh').returns(Promise.reject('forced error')); - service.ensureConnected(false) + service.ensureConnected('myNetwork', false) .then(() => { throw new Error('should not get here'); }) @@ -796,11 +856,12 @@ describe('ClientService', () => { }))); it('should set connection variables if error thrown', fakeAsync(inject([ClientService], (service: ClientService) => { - adminMock.ensureConnected.returns(Promise.resolve()); + identityMock.getUserID.returns(Promise.resolve('myId')); + adminMock.connect.returns(Promise.resolve()); let refreshMock = sinon.stub(service, 'refresh').returns(Promise.reject('forced error')); alertMock.errorStatus$ = {next: sinon.stub()}; - service.ensureConnected(false) + service.ensureConnected('myNetwork', false) .then(() => { throw new Error('should not get here'); }) @@ -819,15 +880,16 @@ describe('ClientService', () => { let businessNetworkConnectionMock = sinon.stub(service, 'getBusinessNetworkConnection').returns(businessNetworkConMock); connectionProfileMock.getCurrentConnectionProfile.returns('myProfile'); businessNetworkConMock.disconnect.returns(Promise.resolve()); - identityMock.getUserID.returns(Promise.resolve()); - identityMock.getUserSecret.returns(Promise.resolve()); + identityMock.getUserID.returns(Promise.resolve('myUser')); + identityMock.getUserSecret.returns(Promise.resolve('mySecret')); - service.refresh(); + service.refresh('myNetwork'); tick(); businessNetworkConMock.disconnect.should.have.been.calledOnce; businessNetworkConMock.connect.should.have.been.calledOnce; + businessNetworkConMock.connect.should.have.been.calledWith('myProfile', 'myNetwork', 'myUser', 'mySecret'); alertMock.busyStatus$.next.should.have.been.calledWith({ title: 'Refreshing Connection', text: 'refreshing the connection to myProfile' @@ -853,12 +915,56 @@ describe('ClientService', () => { describe('it should deployInitial sample', () => { it('should deploy the initial sample', fakeAsync(inject([ClientService], (service: ClientService) => { - alertMock.busyStatus$ = {next: sinon.stub()}; + let resetMock = sinon.stub(service, 'reset'); + + adminMock.createNewBusinessNetwork.returns(Promise.resolve()); + adminMock.isInitialDeploy.returns(true); + + let businessNetworkMock = sinon.stub(service, 'getBusinessNetworkConnection').returns(businessNetworkConMock); + let businessNetworkFromArchiveMock = sandbox.stub(BusinessNetworkDefinition, 'fromArchive').returns(Promise.resolve({ + name: 'bob', + getName: sinon.stub().returns('myNetwork'), + getDescription: sinon.stub().returns('myDescription') + })); + + service.deployInitialSample(); - let refreshMock = sinon.stub(service, 'refresh'); + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Deploying Business Network', + text: 'deploying sample business network' + }); + + tick(); + + businessNetworkFromArchiveMock.should.have.been.called; + + adminMock.createNewBusinessNetwork.should.have.been.calledWith('myNetwork', 'myDescription'); + + adminMock.isInitialDeploy.should.have.been.called; + + adminMock.update.should.have.been.calledWith({ + name: 'bob', + getName: sinon.match.func, + getDescription: sinon.match.func + }); + resetMock.should.have.been.called; + + businessNetworkConMock.disconnect.should.have.been.called; + businessNetworkConMock.connect.should.have.been.calledWith('$default', 'myNetwork', 'admin', 'adminpw'); + }))); + + it('should not deploy if already deployed', fakeAsync(inject([ClientService], (service: ClientService) => { let resetMock = sinon.stub(service, 'reset'); - let businessNetworkFromArchiveMock = sandbox.stub(BusinessNetworkDefinition, 'fromArchive').returns(Promise.resolve({name: 'bob'})); + adminMock.createNewBusinessNetwork.returns(Promise.reject({message: 'businessNetwork with name myNetwork already exists'})); + adminMock.isInitialDeploy.returns(false); + + let businessNetworkMock = sinon.stub(service, 'getBusinessNetworkConnection').returns(businessNetworkConMock); + let businessNetworkFromArchiveMock = sandbox.stub(BusinessNetworkDefinition, 'fromArchive').returns(Promise.resolve({ + name: 'bob', + getName: sinon.stub().returns('myNetwork'), + getDescription: sinon.stub().returns('myDescription') + })); service.deployInitialSample(); @@ -871,9 +977,52 @@ describe('ClientService', () => { businessNetworkFromArchiveMock.should.have.been.called; - adminMock.update.should.have.been.calledWith({name: 'bob'}); - refreshMock.should.have.been.called; + adminMock.createNewBusinessNetwork.should.have.been.calledWith('myNetwork', 'myDescription'); + + adminMock.isInitialDeploy.should.have.been.called; + + adminMock.update.should.not.have.been.called; resetMock.should.have.been.called; + + businessNetworkConMock.disconnect.should.have.been.called; + businessNetworkConMock.connect.should.have.been.calledWith('$default', 'myNetwork', 'admin', 'adminpw'); + }))); + + it('should handle error', fakeAsync(inject([ClientService], (service: ClientService) => { + let resetMock = sinon.stub(service, 'reset'); + + adminMock.createNewBusinessNetwork.returns(Promise.reject('some error')); + adminMock.isInitialDeploy.returns(true); + + let businessNetworkMock = sinon.stub(service, 'getBusinessNetworkConnection').returns(businessNetworkConMock); + let businessNetworkFromArchiveMock = sandbox.stub(BusinessNetworkDefinition, 'fromArchive').returns(Promise.resolve({ + name: 'bob', + getName: sinon.stub().returns('myNetwork'), + getDescription: sinon.stub().returns('myDescription') + })); + + service.deployInitialSample() + .then(() => { + throw new Error('should not have got here'); + }) + .catch((error) => { + error.should.equal('some error'); + }); + + alertMock.busyStatus$.next.should.have.been.calledWith({ + title: 'Deploying Business Network', + text: 'deploying sample business network' + }); + + tick(); + + businessNetworkFromArchiveMock.should.have.been.called; + + adminMock.createNewBusinessNetwork.should.have.been.calledWith('myNetwork', 'myDescription'); + + adminMock.isInitialDeploy.should.not.have.been.called; + + alertMock.busyStatus$.next.should.have.been.calledWith(null); }))); }); @@ -934,6 +1083,34 @@ describe('ClientService', () => { }))); }); + describe('disconnect', () => { + it('should disconnect', inject([ClientService], (service: ClientService) => { + let businessNetworkMock = sinon.stub(service, 'getBusinessNetworkConnection').returns(businessNetworkConMock); + service.disconnect(); + + service['isConnected'].should.equal(false); + adminMock.disconnect.should.have.been.called; + businessNetworkConMock.disconnect.should.have.been.called; + })); + }); + + describe('getSavedBusinessNetworkName', () => { + it('should get saved the business network name', inject([ClientService], (service: ClientService) => { + service['getSavedBusinessNetworkName']('bob'); + + mockLocalStorage.get.should.have.been.calledWith('currentBusinessNetwork:bob'); + })); + }); + + describe('setSavedBusinessNetworkName', () => { + it('should save the business network name', inject([ClientService], (service: ClientService) => { + let businessNetworkMock = sinon.stub(service, 'getBusinessNetworkName').returns('myNetwork'); + service['setSavedBusinessNetworkName']('bob'); + + mockLocalStorage.set.should.have.been.calledWith('currentBusinessNetwork:bob', 'myNetwork'); + })); + }); + describe('createNewBusinessNetwork', () => { it('should alert on failure', inject([ClientService], (service: ClientService) => { // Set up mocks diff --git a/packages/composer-playground/src/app/services/client.service.ts b/packages/composer-playground/src/app/services/client.service.ts index 43a33a77fc..a111231084 100644 --- a/packages/composer-playground/src/app/services/client.service.ts +++ b/packages/composer-playground/src/app/services/client.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Subject } from 'rxjs/Rx'; +import { LocalStorageService } from 'angular-2-local-storage'; import { AdminService } from './admin.service'; import { ConnectionProfileService } from './connectionprofile.service'; @@ -26,7 +27,8 @@ export class ClientService { constructor(private adminService: AdminService, private connectionProfileService: ConnectionProfileService, private identityService: IdentityService, - private alertService: AlertService) { + private alertService: AlertService, + private localStorageService: LocalStorageService) { } // horrible hack for tests @@ -218,33 +220,48 @@ export class ClientService { this.createNewBusinessNetwork(name, version, description, packageJson, readme); } - ensureConnected(force: boolean = true): Promise { + ensureConnected(name: string = null, force: boolean = false): Promise { if (this.isConnected && !force) { return Promise.resolve(); } else if (this.connectingPromise) { return this.connectingPromise; } - let connectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); - this.alertService.busyStatus$.next({ - title: 'Establishing connection', - text: 'Using the connection profile ' + connectionProfile - }); - console.log('Connecting to connection profile', connectionProfile); - this.connectingPromise = this.adminService.ensureConnected(force) + + let businessNetworkName: string; + let userId; + this.connectingPromise = this.identityService.getUserID() + .then((userID) => { + userId = userID; + if (!name) { + try { + businessNetworkName = this.getBusinessNetworkName(); + } catch (error) { + console.log('business network name not set yet so using from local storage'); + } finally { + if (!businessNetworkName) { + businessNetworkName = this.getSavedBusinessNetworkName(userId); + } + } + } else { + businessNetworkName = name; + } + + let connectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); + this.alertService.busyStatus$.next({ + title: 'Establishing connection', + text: 'Using the connection profile ' + connectionProfile + }); + console.log('Connecting to connection profile', connectionProfile); + return this.adminService.connect(businessNetworkName, force); + }) .then(() => { - return this.refresh(); + return this.refresh(businessNetworkName); }) .then(() => { console.log('connected'); this.isConnected = true; this.connectingPromise = null; - }) - .then(() => { - if (this.adminService.isInitialDeploy()) { - return this.deployInitialSample(); - } - }) - .then(() => { + this.setSavedBusinessNetworkName(userId); this.alertService.busyStatus$.next(null); }) .catch((error) => { @@ -258,14 +275,11 @@ export class ClientService { } reset(): Promise { - return this.ensureConnected() - .then(() => { - // TODO: hack hack hack, this should be in the admin API. - return Util.invokeChainCode(( (this.getBusinessNetworkConnection())).securityContext, 'resetBusinessNetwork', []); - }); + // TODO: hack hack hack, this should be in the admin API. + return Util.invokeChainCode(( (this.getBusinessNetworkConnection())).securityContext, 'resetBusinessNetwork', []); } - refresh(): Promise { + refresh(businessNetworkName): Promise { this.currentBusinessNetwork = null; let connectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); this.alertService.busyStatus$.next({ @@ -282,10 +296,16 @@ export class ClientService { return this.identityService.getUserSecret(); }) .then((userSecret) => { - return this.getBusinessNetworkConnection().connect(connectionProfile, 'org-acme-biznet', userID, userSecret); + return this.getBusinessNetworkConnection().connect(connectionProfile, businessNetworkName, userID, userSecret); }); } + public disconnect() { + this.isConnected = false; + this.adminService.disconnect(); + return this.getBusinessNetworkConnection().disconnect(); + } + public getBusinessNetworkFromArchive(buffer): Promise { return BusinessNetworkDefinition.fromArchive(buffer); } @@ -296,12 +316,28 @@ export class ClientService { text: 'deploying sample business network' }); + let businessNetwork: BusinessNetworkDefinition; return BusinessNetworkDefinition.fromArchive(sampleBusinessNetworkArchive) .then((sampleBusinessNetworkDefinition) => { - return this.adminService.update(sampleBusinessNetworkDefinition); + businessNetwork = sampleBusinessNetworkDefinition; + return this.adminService.createNewBusinessNetwork(businessNetwork.getName(), businessNetwork.getDescription()) + .catch((error) => { + // if it already exists we just carry on otherwise we need to throw the error that happened + if (error.message !== 'businessNetwork with name ' + businessNetwork.getName() + ' already exists') { + throw error; + } + }); }) .then(() => { - return this.refresh(); + if (this.adminService.isInitialDeploy()) { + return this.adminService.update(businessNetwork); + } + }) + .then(() => { + return this.getBusinessNetworkConnection().disconnect(); + }) + .then(() => { + return this.getBusinessNetworkConnection().connect('$default', businessNetwork.getName(), 'admin', 'adminpw'); }) .then(() => { return this.reset(); @@ -367,4 +403,14 @@ export class ClientService { return !model.isSystemModelFile(); }); } + + private getSavedBusinessNetworkName(identity: string): string { + let key = `currentBusinessNetwork:${identity}`; + return this.localStorageService.get(key); + } + + private setSavedBusinessNetworkName(identity: string): void { + let key = `currentBusinessNetwork:${identity}`; + this.localStorageService.set(key, this.getBusinessNetworkName()); + } } diff --git a/packages/composer-playground/src/app/services/identity.service.ts b/packages/composer-playground/src/app/services/identity.service.ts index e9deb5dac8..8c54fa6dae 100644 --- a/packages/composer-playground/src/app/services/identity.service.ts +++ b/packages/composer-playground/src/app/services/identity.service.ts @@ -55,17 +55,13 @@ export class IdentityService { let key = `currentIdentity:${connectionProfile}`; let result = this.localStorageService.get(key); return this.getIdentities(connectionProfile) - .then((identities) => { - if (identities.indexOf(result) > -1) { - return result; - } else if (identities.length > 0) { - result = identities[0]; - this.setIdentity(connectionProfile, result); - return result; - } else { - return null; - } - }); + .then((identities) => { + if (identities.indexOf(result) > -1) { + return result; + } else { + return null; + } + }); } setCurrentIdentity(identity: string) { @@ -93,4 +89,13 @@ export class IdentityService { }); } + setLoggedIn(loggedIn: boolean) { + let key = `loggedIn`; + this.localStorageService.set(key, loggedIn); + } + + getLoggedIn() { + let key = `loggedIn`; + return this.localStorageService.get(key); + } } diff --git a/packages/composer-playground/src/app/services/initialization.service.spec.ts b/packages/composer-playground/src/app/services/initialization.service.spec.ts index fd1fe68410..6493f0f5c4 100644 --- a/packages/composer-playground/src/app/services/initialization.service.spec.ts +++ b/packages/composer-playground/src/app/services/initialization.service.spec.ts @@ -20,6 +20,7 @@ import { WalletService } from './wallet.service'; import { FileWallet } from 'composer-common'; import * as sinon from 'sinon'; +import { IdentityService } from './identity.service'; describe('InitializationService', () => { @@ -27,6 +28,7 @@ describe('InitializationService', () => { let mockAlertService; let mockConnectionProfileService; let mockWalletService; + let mockIdentityService; beforeEach(() => { @@ -34,6 +36,7 @@ describe('InitializationService', () => { mockAlertService = sinon.createStubInstance(AlertService); mockConnectionProfileService = sinon.createStubInstance(ConnectionProfileService); mockWalletService = sinon.createStubInstance(WalletService); + mockIdentityService = sinon.createStubInstance(IdentityService); TestBed.configureTestingModule({ imports: [HttpModule], @@ -42,6 +45,7 @@ describe('InitializationService', () => { {provide: ClientService, useValue: mockClientService}, {provide: AlertService, useValue: mockAlertService}, {provide: ConnectionProfileService, useValue: mockConnectionProfileService}, + {provide: IdentityService, useValue: mockIdentityService}, {provide: WalletService, useValue: mockWalletService}, {provide: XHRBackend, useClass: MockBackend} ] @@ -67,6 +71,8 @@ describe('InitializationService', () => { }))); it('should initialize and deploy sample', fakeAsync(inject([InitializationService], (service: InitializationService) => { + let mockCreateSample = sinon.stub(service, 'deployInitialSample'); + mockCreateSample.returns(Promise.resolve()); let stubLoadConfig = sinon.stub(service, 'loadConfig'); stubLoadConfig.returns(Promise.resolve({})); @@ -80,6 +86,8 @@ describe('InitializationService', () => { mockAlertService.busyStatus$ = {next: sinon.stub()}; + mockIdentityService.getLoggedIn.returns(false); + service.initialize(); tick(); @@ -87,11 +95,13 @@ describe('InitializationService', () => { stubCreateInitialProfiles.should.be.called; stubCreateInitialIdentities.should.be.called; - mockConnectionProfileService.setCurrentConnectionProfile.should.not.have.been.called; - mockClientService.ensureConnected.should.be.called; + mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.called; + mockCreateSample.should.be.called; }))); - it('should initialize and continue if sample is already deployed', fakeAsync(inject([InitializationService], (service: InitializationService) => { + it('should initialize and not deploy sample as logged in', fakeAsync(inject([InitializationService], (service: InitializationService) => { + let mockCreateSample = sinon.stub(service, 'deployInitialSample'); + mockCreateSample.returns(Promise.resolve()); let stubLoadConfig = sinon.stub(service, 'loadConfig'); stubLoadConfig.returns(Promise.resolve({})); @@ -104,32 +114,8 @@ describe('InitializationService', () => { mockClientService.ensureConnected.returns(Promise.resolve()); mockAlertService.busyStatus$ = {next: sinon.stub()}; - mockAlertService.errorStatus$ = {next: sinon.stub()}; - - service.initialize(); - - tick(); - - stubLoadConfig.should.be.called; - stubCreateInitialProfiles.should.be.called; - stubCreateInitialIdentities.should.be.called; - mockConnectionProfileService.setCurrentConnectionProfile.should.not.have.been.called; - mockClientService.ensureConnected.should.be.called; - }))); - it('should connect to profile specified in config', fakeAsync(inject([InitializationService], (service: InitializationService) => { - let stubLoadConfig = sinon.stub(service, 'loadConfig'); - stubLoadConfig.returns(Promise.resolve({defaultConnectionProfile: 'myProfile'})); - - let stubCreateInitialProfiles = sinon.stub(service, 'createInitialProfiles'); - stubCreateInitialProfiles.returns(Promise.resolve()); - - let stubCreateInitialIdentities = sinon.stub(service, 'createInitialIdentities'); - stubCreateInitialIdentities.returns(Promise.resolve()); - - mockClientService.ensureConnected.returns(Promise.resolve()); - - mockAlertService.busyStatus$ = {next: sinon.stub()}; + mockIdentityService.getLoggedIn.returns(true); service.initialize(); @@ -138,8 +124,8 @@ describe('InitializationService', () => { stubCreateInitialProfiles.should.be.called; stubCreateInitialIdentities.should.be.called; - mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('myProfile'); - mockClientService.ensureConnected.should.be.called; + mockConnectionProfileService.setCurrentConnectionProfile.should.not.have.been.called; + mockCreateSample.should.not.have.been.called; }))); it('should handle errors and revert to uninitialized state', fakeAsync(inject([InitializationService], (service: InitializationService) => { @@ -275,4 +261,14 @@ describe('InitializationService', () => { result.should.equal(true); }))); }); + + describe('deployInitialSample', () => { + it('should deploy the initial sample', fakeAsync(inject([InitializationService], (service: InitializationService) => { + service.deployInitialSample(); + + tick(); + + mockClientService.deployInitialSample.should.have.been.called; + }))); + }); }); diff --git a/packages/composer-playground/src/app/services/initialization.service.ts b/packages/composer-playground/src/app/services/initialization.service.ts index f524c9081c..e845c936e7 100644 --- a/packages/composer-playground/src/app/services/initialization.service.ts +++ b/packages/composer-playground/src/app/services/initialization.service.ts @@ -5,6 +5,7 @@ import { ClientService } from './client.service'; import { AlertService } from '../basic-modals/alert.service'; import { ConnectionProfileService } from './connectionprofile.service'; import { WalletService } from './wallet.service'; +import { IdentityService } from './identity.service'; @Injectable() export class InitializationService { @@ -18,6 +19,7 @@ export class InitializationService { private alertService: AlertService, private connectionProfileService: ConnectionProfileService, private walletService: WalletService, + private identityService: IdentityService, private http: Http) { } @@ -40,10 +42,12 @@ export class InitializationService { return this.createInitialIdentities(); }) .then(() => { - if (this.config && this.config.defaultConnectionProfile) { - this.connectionProfileService.setCurrentConnectionProfile(this.config.defaultConnectionProfile); + // only need to check about initial sample if not logged in + if (!this.identityService.getLoggedIn()) { + this.connectionProfileService.setCurrentConnectionProfile('$default'); + this.identityService.setCurrentIdentity('admin'); + return this.deployInitialSample(); } - return this.clientService.ensureConnected(); }) .then(() => { this.initialized = true; @@ -112,6 +116,10 @@ export class InitializationService { }, Promise.resolve()); } + deployInitialSample() { + return this.clientService.deployInitialSample(); + } + isWebOnly(): boolean { if (!this.config) { return false; diff --git a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts index f603bd93bc..12366399b1 100644 --- a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts +++ b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts @@ -99,12 +99,31 @@ describe('SampleBusinessNetworkService', () => { }))); }); - service.deployChosenSample({name: 'bob'}); + service.deployChosenSample({name: 'bob'}, true); tick(); businessNetworkFromArchiveMock.should.have.been.called; - mockDeployNetwork.should.have.been.calledWith({name: 'myNetwork'}); + mockDeployNetwork.should.have.been.calledWith({name: 'myNetwork'}, true); + + }))); + + it('should update the chosen sample', fakeAsync(inject([SampleBusinessNetworkService, XHRBackend], (service: SampleBusinessNetworkService, mockBackend) => { + let mockDeployNetwork = sinon.stub(service, 'deployBusinessNetwork'); + let businessNetworkFromArchiveMock = sandbox.stub(BusinessNetworkDefinition, 'fromArchive').returns(Promise.resolve({name: 'myNetwork'})); + + mockBackend.connections.subscribe((connection) => { + connection.mockRespond(new Response(new ResponseOptions({ + body: '1234' + }))); + }); + + service.deployChosenSample({name: 'bob'}, false); + + tick(); + + businessNetworkFromArchiveMock.should.have.been.called; + mockDeployNetwork.should.have.been.calledWith({name: 'myNetwork'}, false); }))); @@ -116,7 +135,7 @@ describe('SampleBusinessNetworkService', () => { connection.mockError(new Error('some error')); }); - service.deployChosenSample({name: 'bob'}) + service.deployChosenSample({name: 'bob'}, true) .then(() => { throw('should not get here'); }) @@ -133,15 +152,54 @@ describe('SampleBusinessNetworkService', () => { describe('deployBusinessNetwork', () => { it('should deploy the business network definition', fakeAsync(inject([SampleBusinessNetworkService], (service: SampleBusinessNetworkService) => { + adminMock.deploy.returns(Promise.resolve()); + clientMock.refresh.returns(Promise.resolve()); + + alertMock.busyStatus$ = {next: sinon.stub()}; + + service.deployBusinessNetwork(businessNetworkMock, true); + + tick(); + + adminMock.deploy.should.have.been.called; + clientMock.refresh.should.have.been.called; + clientMock.reset.should.have.been.called; + alertMock.busyStatus$.next.should.have.been.calledWith(null); + }))); + + it('should update the business network definition', fakeAsync(inject([SampleBusinessNetworkService], (service: SampleBusinessNetworkService) => { + adminMock.update.returns(Promise.resolve()); clientMock.refresh.returns(Promise.resolve()); + clientMock.getBusinessNetworkName.returns('myNetwork'); + + let modalManagerMock = {addModelFiles: sinon.stub(), getModelFiles: sinon.stub().returns(['model'])}; + let scriptManagerMock = {getScripts: sinon.stub().returns(['script']), addScript: sinon.stub()}; + let aclManagerMock = {getAclFile: sinon.stub().returns('acl'), setAclFile: sinon.stub()}; + let metaData = {getPackageJson: sinon.stub().returns({}), getREADME: sinon.stub()}; + + businessNetworkMock.getModelManager.returns(modalManagerMock); + businessNetworkMock.getScriptManager.returns(scriptManagerMock); + businessNetworkMock.getAclManager.returns(aclManagerMock); + businessNetworkMock.getMetadata.returns(metaData); + + let mockCreateBN = sinon.stub(service, 'createNewBusinessDefinition').returns(businessNetworkMock); alertMock.busyStatus$ = {next: sinon.stub()}; - service.deployBusinessNetwork(businessNetworkMock); + service.deployBusinessNetwork(businessNetworkMock, false); tick(); + metaData.getREADME.should.have.been.called; + metaData.getPackageJson.should.have.been.called; + mockCreateBN.should.have.been.called; + clientMock.filterModelFiles.should.have.been.called; + + modalManagerMock.addModelFiles.should.have.been.called; + scriptManagerMock.addScript.should.have.been.called; + aclManagerMock.setAclFile.should.have.been.called; + adminMock.update.should.have.been.called; clientMock.refresh.should.have.been.called; clientMock.reset.should.have.been.called; @@ -149,11 +207,11 @@ describe('SampleBusinessNetworkService', () => { }))); it('should handle error', fakeAsync(inject([SampleBusinessNetworkService], (service: SampleBusinessNetworkService) => { - adminMock.update.returns(Promise.reject('some error')); + adminMock.deploy.returns(Promise.reject('some error')); alertMock.busyStatus$ = {next: sinon.stub()}; - service.deployBusinessNetwork(businessNetworkMock).then(() => { + service.deployBusinessNetwork(businessNetworkMock, true).then(() => { throw('should not get here'); }) .catch((error) => { diff --git a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts index 3b3564e64b..c9f4afe870 100644 --- a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts +++ b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts @@ -15,6 +15,10 @@ export class SampleBusinessNetworkService { private http: Http) { } + createNewBusinessDefinition(name, description, packageJson, readme) { + return new BusinessNetworkDefinition(name, description, packageJson, readme); + } + public getSampleList() { return this.http.get(PLAYGROUND_API + '/api/getSampleList') .toPromise() @@ -26,7 +30,7 @@ export class SampleBusinessNetworkService { }); } - public deployChosenSample(chosenNetwork: object): Promise { + public deployChosenSample(chosenNetwork: object, deployNetwork: boolean): Promise { let params: URLSearchParams = new URLSearchParams(); let paramNames = Object.keys(chosenNetwork); @@ -44,17 +48,43 @@ export class SampleBusinessNetworkService { return BusinessNetworkDefinition.fromArchive(( response)._body); }) .then((businessNetwork) => { - return this.deployBusinessNetwork(businessNetwork); + return this.deployBusinessNetwork(businessNetwork, deployNetwork); }) .catch((error) => { throw(error); }); } - public deployBusinessNetwork(businessNetworkDefinition: BusinessNetworkDefinition): Promise { - return this.adminService.update(businessNetworkDefinition) + public deployBusinessNetwork(businessNetworkDefinition: BusinessNetworkDefinition, deployNetwork: boolean): Promise { + let deployPromise; + + if (deployNetwork) { + deployPromise = this.adminService.deploy(businessNetworkDefinition); + } else { + let currentBusinessNetworkName = this.clientService.getBusinessNetworkName(); + + let packageJson = businessNetworkDefinition.getMetadata().getPackageJson(); + packageJson.name = currentBusinessNetworkName; + + let newNetwork = this.createNewBusinessDefinition(currentBusinessNetworkName, businessNetworkDefinition.getDescription(), packageJson, businessNetworkDefinition.getMetadata().getREADME()); + + let modelFiles = this.clientService.filterModelFiles(businessNetworkDefinition.getModelManager().getModelFiles()); + + newNetwork.getModelManager().addModelFiles(modelFiles); + businessNetworkDefinition.getScriptManager().getScripts().forEach((script) => { + newNetwork.getScriptManager().addScript(script); + }); + + if (businessNetworkDefinition.getAclManager().getAclFile()) { + newNetwork.getAclManager().setAclFile(businessNetworkDefinition.getAclManager().getAclFile()); + } + + deployPromise = this.adminService.update(newNetwork); + } + + return deployPromise .then(() => { - return this.clientService.refresh(); + return this.clientService.refresh(businessNetworkDefinition.getName()); }) .then(() => { return this.clientService.reset(); diff --git a/packages/composer-playground/src/app/test/switch-identity/switch-identity.component.spec.ts b/packages/composer-playground/src/app/test/switch-identity/switch-identity.component.spec.ts index 3364b1ae7c..09b1c0b7a5 100644 --- a/packages/composer-playground/src/app/test/switch-identity/switch-identity.component.spec.ts +++ b/packages/composer-playground/src/app/test/switch-identity/switch-identity.component.spec.ts @@ -108,7 +108,7 @@ describe('SwitchIdentityComponent', () => { mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('myProfile'); mockIdentityService.setCurrentIdentity.should.have.been.calledWith('bob'); - mockClientService.ensureConnected.should.have.been.calledWith(true); + mockClientService.ensureConnected.should.have.been.calledWith(null, true); component['switchInProgress'].should.equal(false); mockActiveModal.close.should.have.been.called; @@ -141,7 +141,7 @@ describe('SwitchIdentityComponent', () => { mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('myProfile'); mockIdentityService.setCurrentIdentity.should.have.been.calledWith('bob'); - mockClientService.ensureConnected.should.have.been.calledWith(true); + mockClientService.ensureConnected.should.have.been.calledWith(null, true); component['switchInProgress'].should.equal(false); mockActiveModal.close.should.have.been.called; @@ -174,7 +174,7 @@ describe('SwitchIdentityComponent', () => { mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('myProfile'); mockIdentityService.setCurrentIdentity.should.have.been.calledWith('bob'); - mockClientService.ensureConnected.should.have.been.calledWith(true); + mockClientService.ensureConnected.should.have.been.calledWith(null, true); component['switchInProgress'].should.equal(false); mockActiveModal.close.should.have.been.called; @@ -193,7 +193,7 @@ describe('SwitchIdentityComponent', () => { tick(); - mockClientService.ensureConnected.should.have.been.calledWith(true); + mockClientService.ensureConnected.should.have.been.calledWith(null, true); component['switchInProgress'].should.equal(false); mockActiveModal.dismiss.should.have.been.called; diff --git a/packages/composer-playground/src/app/test/switch-identity/switch-identity.component.ts b/packages/composer-playground/src/app/test/switch-identity/switch-identity.component.ts index d88dbb7ad3..c7a2d4a40b 100644 --- a/packages/composer-playground/src/app/test/switch-identity/switch-identity.component.ts +++ b/packages/composer-playground/src/app/test/switch-identity/switch-identity.component.ts @@ -73,7 +73,7 @@ export class SwitchIdentityComponent implements OnInit { this.connectionProfileService.setCurrentConnectionProfile(this.connectionProfileName); this.identityService.setCurrentIdentity(chosenUser); - return this.clientService.ensureConnected(true); + return this.clientService.ensureConnected(null, true); }) .then(() => { this.switchInProgress = false; 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 aeccb6e3e3..c01e5d6887 100644 --- a/packages/composer-playground/src/app/test/test.component.spec.ts +++ b/packages/composer-playground/src/app/test/test.component.spec.ts @@ -6,7 +6,6 @@ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testin import { Directive, Input } from '@angular/core'; import { TestComponent } from './test.component'; import { ClientService } from '../services/client.service'; -import { InitializationService } from '../services/initialization.service'; import { TransactionService } from '../services/transaction.service'; import { AlertService } from '../basic-modals/alert.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -35,7 +34,6 @@ describe('TestComponent', () => { let fixture: ComponentFixture; let mockClientService; - let mockInitializationService; let mockAlertService; let mockTransactionService; let mockModal; @@ -48,7 +46,6 @@ describe('TestComponent', () => { sandbox = sinon.sandbox.create(); mockClientService = sinon.createStubInstance(ClientService); - mockInitializationService = sinon.createStubInstance(InitializationService); mockAlertService = sinon.createStubInstance(AlertService); mockModal = sinon.createStubInstance(NgbModal); mockBusinessNetworkConnection = sinon.createStubInstance(BusinessNetworkConnection); @@ -62,7 +59,6 @@ describe('TestComponent', () => { declarations: [TestComponent, MockRegistryDirective], providers: [ {provide: NgbModal, useValue: mockModal}, - {provide: InitializationService, useValue: mockInitializationService}, {provide: AlertService, useValue: mockAlertService}, {provide: ClientService, useValue: mockClientService}, {provide: TransactionService, useValue: mockTransactionService} @@ -87,7 +83,7 @@ describe('TestComponent', () => { }); it('should load all the registries', fakeAsync(() => { - mockInitializationService.initialize.returns(Promise.resolve()); + mockClientService.ensureConnected.returns(Promise.resolve()); mockBusinessNetworkConnection.getAllAssetRegistries.returns(Promise.resolve([{id: 'asset.fred'}, {id: 'asset.bob'}])); mockBusinessNetworkConnection.getAllParticipantRegistries.returns(Promise.resolve([{id: 'participant.fred'}, {id: 'participant.bob'}])); @@ -121,7 +117,7 @@ describe('TestComponent', () => { })); it('should set chosen registry to first asset one if no participant registries', fakeAsync(() => { - mockInitializationService.initialize.returns(Promise.resolve()); + mockClientService.ensureConnected.returns(Promise.resolve()); mockBusinessNetworkConnection.getAllAssetRegistries.returns(Promise.resolve([{id: 'asset.fred'}, {id: 'asset.bob'}])); mockBusinessNetworkConnection.getAllParticipantRegistries.returns(Promise.resolve([])); @@ -152,7 +148,7 @@ describe('TestComponent', () => { })); it('should set chosen registry to transaction registry if no asset or participant registries', fakeAsync(() => { - mockInitializationService.initialize.returns(Promise.resolve()); + mockClientService.ensureConnected.returns(Promise.resolve()); mockBusinessNetworkConnection.getAllAssetRegistries.returns(Promise.resolve([])); mockBusinessNetworkConnection.getAllParticipantRegistries.returns(Promise.resolve([])); @@ -180,8 +176,7 @@ describe('TestComponent', () => { })); it('should handle error', fakeAsync(() => { - mockInitializationService.initialize.returns(Promise.reject('some error')); - mockClientService.getBusinessNetworkConnection.returns(mockBusinessNetworkConnection); + mockClientService.ensureConnected.returns(Promise.reject('some error')); mockAlertService.errorStatus$ = {next: sinon.stub()}; diff --git a/packages/composer-playground/src/app/test/test.component.ts b/packages/composer-playground/src/app/test/test.component.ts index 9a8bcd1e34..d14f6ce9d3 100644 --- a/packages/composer-playground/src/app/test/test.component.ts +++ b/packages/composer-playground/src/app/test/test.component.ts @@ -1,8 +1,6 @@ - import { Component, OnInit, OnDestroy } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { ClientService } from '../services/client.service'; -import { InitializationService } from '../services/initialization.service'; import { AlertService } from '../basic-modals/alert.service'; import { TransactionComponent } from './transaction/transaction.component'; import { TransactionService } from '../services/transaction.service'; @@ -25,55 +23,54 @@ export class TestComponent implements OnInit, OnDestroy { private eventsTriggered = []; constructor(private clientService: ClientService, - private initializationService: InitializationService, private alertService: AlertService, private transactionService: TransactionService, private modalService: NgbModal) { } ngOnInit(): Promise { - this.initializeEventListener(); - return this.initializationService.initialize() + return this.clientService.ensureConnected() .then(() => { - return this.clientService.getBusinessNetworkConnection().getAllAssetRegistries(); - }) - .then((assetRegistries) => { - assetRegistries.forEach((assetRegistry) => { - let index = assetRegistry.id.lastIndexOf('.'); - let displayName = assetRegistry.id.substring(index + 1); - assetRegistry.displayName = displayName; - }); - - this.assetRegistries = assetRegistries.sort((a, b) => { - return a.id.localeCompare(b.id); - }); - - return this.clientService.getBusinessNetworkConnection().getAllParticipantRegistries(); - }) - .then((participantRegistries) => { - participantRegistries.forEach((participantRegistry) => { - let index = participantRegistry.id.lastIndexOf('.'); - let displayName = participantRegistry.id.substring(index + 1); - participantRegistry.displayName = displayName; - }); - - this.participantRegistries = participantRegistries.sort((a, b) => { - return a.id.localeCompare(b.id); - }); - - return this.clientService.getBusinessNetworkConnection().getTransactionRegistry(); - }) - .then((transactionRegistry) => { - this.transactionRegistry = transactionRegistry; - - // set the default registry selection - if (this.participantRegistries.length !== 0) { - this.chosenRegistry = this.participantRegistries[0]; - } else if (this.assetRegistries.length !== 0) { - this.chosenRegistry = this.assetRegistries[0]; - } else { - this.chosenRegistry = this.transactionRegistry; - } + this.initializeEventListener(); + return this.clientService.getBusinessNetworkConnection().getAllAssetRegistries() + .then((assetRegistries) => { + assetRegistries.forEach((assetRegistry) => { + let index = assetRegistry.id.lastIndexOf('.'); + let displayName = assetRegistry.id.substring(index + 1); + assetRegistry.displayName = displayName; + }); + + this.assetRegistries = assetRegistries.sort((a, b) => { + return a.id.localeCompare(b.id); + }); + + return this.clientService.getBusinessNetworkConnection().getAllParticipantRegistries(); + }) + .then((participantRegistries) => { + participantRegistries.forEach((participantRegistry) => { + let index = participantRegistry.id.lastIndexOf('.'); + let displayName = participantRegistry.id.substring(index + 1); + participantRegistry.displayName = displayName; + }); + + this.participantRegistries = participantRegistries.sort((a, b) => { + return a.id.localeCompare(b.id); + }); + + return this.clientService.getBusinessNetworkConnection().getTransactionRegistry(); + }) + .then((transactionRegistry) => { + this.transactionRegistry = transactionRegistry; + + // set the default registry selection + if (this.participantRegistries.length !== 0) { + this.chosenRegistry = this.participantRegistries[0]; + } else if (this.assetRegistries.length !== 0) { + this.chosenRegistry = this.assetRegistries[0]; + } else { + this.chosenRegistry = this.transactionRegistry; + } + }); }) .catch((error) => { this.alertService.errorStatus$.next(error); @@ -90,7 +87,6 @@ export class TestComponent implements OnInit, OnDestroy { submitTransaction() { const modalRef = this.modalService.open(TransactionComponent); - modalRef.result.then((transaction) => { // refresh current resource list if (this.chosenRegistry === this.transactionRegistry) {