From a3deabe017e907d559acdc84493f8d8f82f8dc1c Mon Sep 17 00:00:00 2001 From: caroline-church Date: Tue, 31 Oct 2017 10:03:06 +0000 Subject: [PATCH] Fix Editor Validation (#2496) Updated so that when a file is in error changes won't be lost after changing files contributes to hyperledger/composer#1530 Signed-off-by: K. L. Usher --- package.json | 2 +- packages/composer-playground/.istanbul.yml | 8 +- packages/composer-playground/package.json | 436 +-- .../src/app/app.component.spec.ts | 16 +- .../src/app/app.component.ts | 7 +- .../credentials/credentials.component.spec.ts | 2 - .../credentials/credentials.component.ts | 1 - .../add-file/add-file.component.spec.ts | 59 +- .../app/editor/add-file/add-file.component.ts | 89 +- .../editor-file/editor-file.component.spec.ts | 377 +-- .../editor-file/editor-file.component.ts | 90 +- .../src/app/editor/editor.component.html | 10 +- .../src/app/editor/editor.component.spec.ts | 904 +++---- .../src/app/editor/editor.component.ts | 354 ++- .../src/app/editor/editor.module.ts | 3 +- .../src/app/editor/editor.service.spec.ts | 21 - .../src/app/editor/editor.service.ts | 15 - .../app/identity/identity.component.spec.ts | 4 +- .../src/app/identity/identity.component.ts | 2 +- .../src/app/import/update.component.spec.ts | 43 +- .../src/app/import/update.component.ts | 2 +- .../src/app/services/client.service.spec.ts | 803 +----- .../src/app/services/client.service.ts | 208 +- .../src/app/services/editor-file.spec.ts | 425 +++ .../src/app/services/editor-file.ts | 111 + .../src/app/services/file.service.spec.ts | 2375 +++++++++++++++++ .../src/app/services/file.service.ts | 625 +++++ .../samplebusinessnetwork.service.spec.ts | 28 +- .../services/samplebusinessnetwork.service.ts | 8 +- .../src/app/services/services.module.ts | 2 + 30 files changed, 4703 insertions(+), 2327 deletions(-) delete mode 100644 packages/composer-playground/src/app/editor/editor.service.spec.ts delete mode 100644 packages/composer-playground/src/app/editor/editor.service.ts create mode 100644 packages/composer-playground/src/app/services/editor-file.spec.ts create mode 100644 packages/composer-playground/src/app/services/editor-file.ts create mode 100644 packages/composer-playground/src/app/services/file.service.spec.ts create mode 100644 packages/composer-playground/src/app/services/file.service.ts diff --git a/package.json b/package.json index cc8134a63c..14638ea6df 100644 --- a/package.json +++ b/package.json @@ -34,4 +34,4 @@ ], "author": "Hyperledger Composer", "license": "Apache-2.0" -} \ No newline at end of file +} diff --git a/packages/composer-playground/.istanbul.yml b/packages/composer-playground/.istanbul.yml index 7cd705efa0..375c0600c0 100644 --- a/packages/composer-playground/.istanbul.yml +++ b/packages/composer-playground/.istanbul.yml @@ -6,7 +6,7 @@ instrumentation: - "docker/*" check: global: - statements: 99.4 - branches: 96.45 - functions: 98.8 - lines: 99.4 + statements: 99.51 + branches: 97.12 + functions: 98.93 + lines: 99.55 diff --git a/packages/composer-playground/package.json b/packages/composer-playground/package.json index b40350cfdc..49ff76b30c 100644 --- a/packages/composer-playground/package.json +++ b/packages/composer-playground/package.json @@ -1,219 +1,221 @@ { - "name": "composer-playground", - "version": "0.14.3", - "description": "A test harness/UI for the web runtime container for Hyperledger Composer", - "engines": { - "node": ">=6", - "npm": ">=3" - }, - "bin": { - "composer-playground": "cli.js" - }, - "repository": { - "type": "git", - "url": "https://github.com/hyperledger/composer.git" - }, - "keywords": [ - "blockchain", - "hyperledger", - "solutions" - ], - "author": "Hyperledger Composer", - "license": "Apache-2.0", - "scripts": { - "build:dev": "webpack --config config/webpack.dev.js --progress --profile", - "build:docker": "npm run build:prod && docker build -t angular2-webpack-start:latest .", - "build:prod": "webpack --config config/webpack.prod.js --progress --profile --bail", - "build": "npm run build:dev", - "ci": "npm run lint && npm test && npm run e2e", - "clean:dist": "npm run rimraf -- dist", - "clean:install": "npm set progress=false && npm install", - "clean:start": "npm start", - "clean": "npm cache clean && npm run rimraf -- node_modules doc coverage dist", - "docker": "docker", - "docs": "npm run typedoc -- --options typedoc.json --exclude '**/*.spec.ts' ./src/", - "e2e:live": "npm run e2e -- --elementExplorer", - "e2e": "npm run protractor", - "e2e:nobuild": "node ./e2e/test.js", - "e2e:main": "npm run build:prod && node ./e2e/test.js", - "github-deploy:dev": "webpack --config config/webpack.github-deploy.js --progress --profile --env.githubDev", - "github-deploy:prod": "webpack --config config/webpack.github-deploy.js --progress --profile --env.githubProd", - "github-deploy": "npm run github-deploy:dev", - "lint": "npm run tslint \"src/**/*.ts\"", - "postversion": "git push && git push --tags", - "pretest": "npm run lint", - "posttest": "istanbul check-coverage", - "postinstall": "npm list -json --depth=0 2>&1 | grep -iv ERR > ./src/assets/npmlist.json", - "prebuild:dev": "npm run clean:dist", - "prebuild:prod": "npm run clean:dist", - "preclean:install": "npm run clean", - "preclean:start": "npm run clean", - "pree2e": "npm run webdriver:update -- --standalone", - "preversion": "npm test", - "protractor": "protractor", - "rimraf": "rimraf", - "server:dev:hmr": "npm run server:dev -- --inline --hot", - "server:dev": "webpack-dev-server --config config/webpack.dev.js --progress --profile --watch --content-base src/", - "server:prod": "http-server dist --cors --push-state", - "server:prod:ci": "http-server dist -p 3000 --cors --push-state", - "server": "npm run server:dev", - "start:hmr": "npm run server:dev:hmr", - "start": "npm run server:dev", - "test": "karma start ./config/karma.conf.js", - "tslint": "tslint", - "typedoc": "typedoc", - "version": "npm run build", - "watch:dev:hmr": "npm run watch:dev -- --hot", - "watch:dev": "npm run build:dev -- --watch", - "watch:prod": "npm run build:prod -- --watch", - "watch:test": "npm run test -- --auto-watch --no-single-run", - "watch": "npm run watch:dev", - "webdriver-manager": "webdriver-manager", - "webdriver:start": "npm run webdriver-manager start", - "webdriver:update": "npm run webdriver-manager update", - "webpack-dev-server": "webpack-dev-server", - "webpack": "webpack" - }, - "dependencies": { - "@ng-bootstrap/ng-bootstrap": "1.0.0-beta.2", - "cheerio": "0.22.0", - "composer-common": "0.14.3", - "composer-playground-api": "0.14.3", - "express": "4.15.2", - "fast-json-patch": "1.1.8", - "file-saver": "1.3.3", - "is-docker": "1.1.0", - "marked": "0.3.6", - "ngx-clipboard": "8.0.4", - "opener": "1.4.2", - "socket.io": "1.7.3", - "typescript": "2.4.0", - "web-animations-js": "2.2.5", - "webpack": "2.2.1" - }, - "devDependencies": { - "@angular/animations": "4.1.3", - "@angular/common": "4.1.3", - "@angular/compiler": "4.1.3", - "@angular/core": "4.1.3", - "@angular/forms": "4.1.3", - "@angular/http": "4.1.3", - "@angular/platform-browser": "4.1.3", - "@angular/platform-browser-dynamic": "4.1.3", - "@angular/platform-server": "4.1.3", - "@angular/router": "4.1.3", - "@angularclass/conventions-loader": "1.0.13", - "@angularclass/hmr": "1.2.2", - "@angularclass/hmr-loader": "3.0.2", - "@types/chai": "3.4.35", - "@types/dropboxjs": "0.0.29", - "@types/filesystem": "0.0.28", - "@types/hammerjs": "2.0.34", - "@types/jasmine": "2.5.52", - "@types/left-pad": "1.1.0", - "@types/node": "7.0.5", - "@types/selenium-webdriver": "2.53.42", - "@types/sinon": "2.3.3", - "@types/sinon-chai": "2.7.29", - "@types/source-map": "0.5.0", - "@types/uglify-js": "2.6.28", - "@types/webpack": "2.1.0", - "angular-2-local-storage": "1.0.0", - "angular-router-loader": "0.5.0", - "angular2-template-loader": "0.6.2", - "assets-webpack-plugin": "3.5.0", - "awesome-typescript-loader": "3.0.7", - "babel-core": "6.21.0", - "babel-loader": "6.2.10", - "babel-polyfill": "6.23.0", - "babel-preset-latest": "6.24.1", - "buffer-loader": "0.0.1", - "chai": "3.5.0", - "codelyzer": "2.0.1", - "codemirror": "5.26.0", - "composer-admin": "0.14.3", - "composer-client": "0.14.3", - "composer-connector-proxy": "0.14.3", - "composer-connector-web": "0.14.3", - "composer-runtime": "0.14.3", - "composer-runtime-web": "0.14.3", - "copy-webpack-plugin": "4.0.1", - "core-js": "2.4.1", - "css-loader": "0.26.1", - "dexie": "1.5.1", - "doctrine": "2.0.0", - "exports-loader": "0.6.3", - "expose-loader": "0.7.1", - "fast-json-patch": "1.1.4", - "file-loader": "0.10.0", - "gh-pages": "0.12.0", - "homedir": "0.6.0", - "html-webpack-plugin": "2.25.0", - "ie-shim": "0.1.0", - "imports-loader": "0.7.0", - "istanbul": "0.4.5", - "istanbul-instrumenter-loader": "2.0.0", - "jasmine-core": "2.6.4", - "jasmine-spec-reporter": "4.1.0", - "jquery": "3.1.1", - "json-loader": "0.5.4", - "jsonata": "1.2.2", - "jszip": "3.1.3", - "karma": "1.3.0", - "karma-chai": "0.1.0", - "karma-chrome-launcher": "2.0.0", - "karma-coverage": "1.1.1", - "karma-jasmine": "1.1.0", - "karma-mocha-reporter": "2.2.1", - "karma-remap-coverage": "0.1.4", - "karma-sinon": "1.0.5", - "karma-sinon-chai": "1.2.4", - "karma-sourcemap-loader": "0.3.7", - "karma-webpack": "2.0.2", - "left-pad": "1.1.3", - "lz-string": "1.4.4", - "mocha": "3.4.2", - "ng2-bootstrap": "1.1.16-11", - "ng2-codemirror": "1.1.2", - "ngx-perfect-scrollbar": "4.3.0", - "node-sass": "4.5.0", - "object-hash": "1.1.8", - "parse5": "3.0.1", - "protractor": "5.1.2", - "raw-loader": "0.5.1", - "rimraf": "2.5.4", - "rxjs": "5.4.2", - "sass-loader": "6.0.1", - "script-ext-html-webpack-plugin": "1.3.5", - "selenium-webdriver": "3.0.1", - "semver": "5.3.0", - "setimmediate": "1.0.5", - "sinon": "2.3.8", - "sinon-chai": "2.10.0", - "sleep": "5.1.1", - "source-map-loader": "0.1.5", - "spa-http-server": "0.9.0", - "string-replace-loader": "1.0.5", - "style-loader": "0.13.1", - "svg-sprite-loader": "0.3.0", - "thenify": "3.2.1", - "thenify-all": "1.6.0", - "to-string-loader": "1.1.5", - "ts-helpers": "1.1.2", - "ts-loader": "2.0.1", - "ts-node": "2.0.0", - "tslint": "4.5.1", - "tslint-loader": "3.3.0", - "typedoc": "0.5.3", - "typescript": "2.4.0", - "url-loader": "0.5.7", - "v8-lazy-parse-webpack-plugin": "0.3.0", - "webpack": "2.4.1", - "webpack-dev-middleware": "1.10.1", - "webpack-dev-server": "2.4.2", - "webpack-md5-hash": "0.0.5", - "webpack-merge": "4.1.0", - "webpack-sources": "1.0.1", - "zone.js": "0.8.5" - } + "name": "composer-playground", + "version": "0.14.3", + "description": "A test harness/UI for the web runtime container for Hyperledger Composer", + "engines": { + "node": ">=6", + "npm": ">=3" + }, + "bin": { + "composer-playground": "cli.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/composer.git" + }, + "keywords": [ + "blockchain", + "hyperledger", + "solutions" + ], + "author": "Hyperledger Composer", + "license": "Apache-2.0", + "scripts": { + "build:dev": "webpack --config config/webpack.dev.js --progress --profile", + "build:docker": "npm run build:prod && docker build -t angular2-webpack-start:latest .", + "build:prod": "webpack --config config/webpack.prod.js --progress --profile --bail", + "build": "npm run build:dev", + "ci": "npm run lint && npm test && npm run e2e", + "clean:dist": "npm run rimraf -- dist", + "clean:install": "npm set progress=false && npm install", + "clean:start": "npm start", + "clean": "npm cache clean && npm run rimraf -- node_modules doc coverage dist", + "docker": "docker", + "docs": "npm run typedoc -- --options typedoc.json --exclude '**/*.spec.ts' ./src/", + "e2e:live": "npm run e2e -- --elementExplorer", + "e2e": "npm run protractor", + "e2e:nobuild": "node ./e2e/test.js", + "e2e:main": "npm run build:prod && node ./e2e/test.js", + "github-deploy:dev": "webpack --config config/webpack.github-deploy.js --progress --profile --env.githubDev", + "github-deploy:prod": "webpack --config config/webpack.github-deploy.js --progress --profile --env.githubProd", + "github-deploy": "npm run github-deploy:dev", + "lint": "npm run tslint \"src/**/*.ts\"", + "postversion": "git push && git push --tags", + "pretest": "npm run lint", + "posttest": "istanbul check-coverage", + "postinstall": "npm list -json --depth=0 2>&1 | grep -iv ERR > ./src/assets/npmlist.json", + "prebuild:dev": "npm run clean:dist", + "prebuild:prod": "npm run clean:dist", + "preclean:install": "npm run clean", + "preclean:start": "npm run clean", + "pree2e": "npm run webdriver:update -- --standalone", + "preversion": "npm test", + "protractor": "protractor", + "rimraf": "rimraf", + "server:dev:hmr": "npm run server:dev -- --inline --hot", + "server:dev": "webpack-dev-server --config config/webpack.dev.js --progress --profile --watch --content-base src/", + "server:prod": "http-server dist --cors --push-state", + "server:prod:ci": "http-server dist -p 3000 --cors --push-state", + "server": "npm run server:dev", + "start:hmr": "npm run server:dev:hmr", + "start": "npm run server:dev", + "test": "karma start ./config/karma.conf.js", + "tslint": "tslint", + "typedoc": "typedoc", + "version": "npm run build", + "watch:dev:hmr": "npm run watch:dev -- --hot", + "watch:dev": "npm run build:dev -- --watch", + "watch:prod": "npm run build:prod -- --watch", + "watch:test": "npm run test -- --auto-watch --no-single-run", + "watch": "npm run watch:dev", + "webdriver-manager": "webdriver-manager", + "webdriver:start": "npm run webdriver-manager start", + "webdriver:update": "npm run webdriver-manager update", + "webpack-dev-server": "webpack-dev-server", + "webpack": "webpack" + }, + "dependencies": { + "@ng-bootstrap/ng-bootstrap": "1.0.0-beta.2", + "cheerio": "0.22.0", + "composer-common": "0.14.3", + "composer-playground-api": "0.14.3", + "express": "4.15.2", + "fast-json-patch": "1.1.8", + "file-saver": "1.3.3", + "is-docker": "1.1.0", + "lodash": "4.17.4", + "marked": "0.3.6", + "ngx-clipboard": "8.0.4", + "opener": "1.4.2", + "socket.io": "1.7.3", + "typescript": "2.4.0", + "web-animations-js": "2.2.5", + "webpack": "2.2.1" + }, + "devDependencies": { + "@angular/animations": "4.1.3", + "@angular/common": "4.1.3", + "@angular/compiler": "4.1.3", + "@angular/core": "4.1.3", + "@angular/forms": "4.1.3", + "@angular/http": "4.1.3", + "@angular/platform-browser": "4.1.3", + "@angular/platform-browser-dynamic": "4.1.3", + "@angular/platform-server": "4.1.3", + "@angular/router": "4.1.3", + "@angularclass/conventions-loader": "1.0.13", + "@angularclass/hmr": "1.2.2", + "@angularclass/hmr-loader": "3.0.2", + "@types/chai": "3.4.35", + "@types/dropboxjs": "0.0.29", + "@types/filesystem": "0.0.28", + "@types/hammerjs": "2.0.34", + "@types/jasmine": "2.5.52", + "@types/left-pad": "1.1.0", + "@types/lodash": "4.14.76", + "@types/node": "7.0.5", + "@types/selenium-webdriver": "2.53.42", + "@types/sinon": "2.3.3", + "@types/sinon-chai": "2.7.29", + "@types/source-map": "0.5.0", + "@types/uglify-js": "2.6.28", + "@types/webpack": "2.1.0", + "angular-2-local-storage": "1.0.0", + "angular-router-loader": "0.5.0", + "angular2-template-loader": "0.6.2", + "assets-webpack-plugin": "3.5.0", + "awesome-typescript-loader": "3.0.7", + "babel-core": "6.21.0", + "babel-loader": "6.2.10", + "babel-polyfill": "6.23.0", + "babel-preset-latest": "6.24.1", + "buffer-loader": "0.0.1", + "chai": "3.5.0", + "codelyzer": "2.0.1", + "codemirror": "5.26.0", + "composer-admin": "0.14.3", + "composer-client": "0.14.3", + "composer-connector-proxy": "0.14.3", + "composer-connector-web": "0.14.3", + "composer-runtime": "0.14.3", + "composer-runtime-web": "0.14.3", + "copy-webpack-plugin": "4.0.1", + "core-js": "2.4.1", + "css-loader": "0.26.1", + "dexie": "1.5.1", + "doctrine": "2.0.0", + "exports-loader": "0.6.3", + "expose-loader": "0.7.1", + "fast-json-patch": "1.1.4", + "file-loader": "0.10.0", + "gh-pages": "0.12.0", + "homedir": "0.6.0", + "html-webpack-plugin": "2.25.0", + "ie-shim": "0.1.0", + "imports-loader": "0.7.0", + "istanbul": "0.4.5", + "istanbul-instrumenter-loader": "2.0.0", + "jasmine-core": "2.6.4", + "jasmine-spec-reporter": "4.1.0", + "jquery": "3.1.1", + "json-loader": "0.5.4", + "jsonata": "1.2.2", + "jszip": "3.1.3", + "karma": "1.3.0", + "karma-chai": "0.1.0", + "karma-chrome-launcher": "2.0.0", + "karma-coverage": "1.1.1", + "karma-jasmine": "1.1.0", + "karma-mocha-reporter": "2.2.1", + "karma-remap-coverage": "0.1.4", + "karma-sinon": "1.0.5", + "karma-sinon-chai": "1.2.4", + "karma-sourcemap-loader": "0.3.7", + "karma-webpack": "2.0.2", + "left-pad": "1.1.3", + "lz-string": "1.4.4", + "mocha": "3.4.2", + "ng2-bootstrap": "1.1.16-11", + "ng2-codemirror": "1.1.2", + "ngx-perfect-scrollbar": "4.3.0", + "node-sass": "4.5.0", + "object-hash": "1.1.8", + "parse5": "3.0.1", + "protractor": "5.1.2", + "raw-loader": "0.5.1", + "rimraf": "2.5.4", + "rxjs": "5.4.2", + "sass-loader": "6.0.1", + "script-ext-html-webpack-plugin": "1.3.5", + "selenium-webdriver": "3.0.1", + "semver": "5.3.0", + "setimmediate": "1.0.5", + "sinon": "2.3.8", + "sinon-chai": "2.10.0", + "sleep": "5.1.1", + "source-map-loader": "0.1.5", + "spa-http-server": "0.9.0", + "string-replace-loader": "1.0.5", + "style-loader": "0.13.1", + "svg-sprite-loader": "0.3.0", + "thenify": "3.2.1", + "thenify-all": "1.6.0", + "to-string-loader": "1.1.5", + "ts-helpers": "1.1.2", + "ts-loader": "2.0.1", + "ts-node": "2.0.0", + "tslint": "4.5.1", + "tslint-loader": "3.3.0", + "typedoc": "0.5.3", + "typescript": "2.4.0", + "url-loader": "0.5.7", + "v8-lazy-parse-webpack-plugin": "0.3.0", + "webpack": "2.4.1", + "webpack-dev-middleware": "1.10.1", + "webpack-dev-server": "2.4.2", + "webpack-md5-hash": "0.0.5", + "webpack-merge": "4.1.0", + "webpack-sources": "1.0.1", + "zone.js": "0.8.5" + } } diff --git a/packages/composer-playground/src/app/app.component.spec.ts b/packages/composer-playground/src/app/app.component.spec.ts index cca9c6606b..f6d8e46bda 100644 --- a/packages/composer-playground/src/app/app.component.spec.ts +++ b/packages/composer-playground/src/app/app.component.spec.ts @@ -22,13 +22,14 @@ import { BusinessNetworkConnection } from 'composer-client'; import { AdminService } from './services/admin.service'; import { AboutService } from './services/about.service'; import { ConfigService } from './services/config.service'; +import { FileService } from './services/file.service'; import { IdCard } from 'composer-common'; +import { AdminConnection } from 'composer-admin'; import * as sinon from 'sinon'; import * as chai from 'chai'; -import { AdminConnection } from 'composer-admin'; let should = chai.should(); @@ -157,6 +158,7 @@ describe('AppComponent', () => { let mockConfigService; let mockAdminConnection; let mockWindow; + let mockFileService; let linkDes; let links; @@ -169,7 +171,7 @@ describe('AppComponent', () => { beforeEach(async(() => { mockClientService = sinon.createStubInstance(ClientService); mockInitializationService = sinon.createStubInstance(InitializationService); - + mockFileService = sinon.createStubInstance(FileService); mockModal = sinon.createStubInstance(NgbModal); mockBusinessNetworkConnection = sinon.createStubInstance(BusinessNetworkConnection); mockAdminService = sinon.createStubInstance(AdminService); @@ -208,7 +210,8 @@ describe('AppComponent', () => { {provide: IdentityCardService, useValue: mockIdentityCardService}, {provide: LocalStorageService, useValue: mockLocalStorageService}, {provide: AboutService, useValue: mockAboutService}, - {provide: ConfigService, useValue: mockConfigService} + {provide: ConfigService, useValue: mockConfigService}, + {provide: FileService, useValue: mockFileService} ] }) @@ -312,7 +315,7 @@ describe('AppComponent', () => { it('should check version and open version modal', fakeAsync(() => { let openVersionModalStub = sinon.stub(component, 'openVersionModal'); mockClientService.ensureConnected.returns(Promise.resolve()); - mockClientService.getBusinessNetworkName.returns('bob'); + mockClientService.getBusinessNetwork.returns({getName : sinon.stub().returns('bob')}); routerStub.eventParams = {url: '/bob', nav: 'end'}; @@ -328,7 +331,7 @@ describe('AppComponent', () => { it('should check version and not open version modal', fakeAsync(() => { let openVersionModalStub = sinon.stub(component, 'openVersionModal'); mockClientService.ensureConnected.returns(Promise.resolve()); - mockClientService.getBusinessNetworkName.returns('bob'); + mockClientService.getBusinessNetwork.returns({getName : sinon.stub().returns('bob')}); routerStub.eventParams = {url: '/bob', nav: 'end'}; @@ -356,7 +359,7 @@ describe('AppComponent', () => { it('should show header links if logged in', fakeAsync(() => { routerStub.eventParams = {url: '/editor', nav: 'end'}; mockClientService.ensureConnected.returns(Promise.resolve()); - mockClientService.getBusinessNetworkName.returns('bob'); + mockClientService.getBusinessNetwork.returns({getName : sinon.stub().returns('bob')}); updateComponent(); @@ -1038,6 +1041,7 @@ describe('AppComponent', () => { tick(); mockClientService.disconnect.should.have.been.called; + mockFileService.deleteAllFiles.should.have.been.called; 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 08cf5b2eba..2dd8db0213 100644 --- a/packages/composer-playground/src/app/app.component.ts +++ b/packages/composer-playground/src/app/app.component.ts @@ -16,6 +16,7 @@ import { LocalStorageService } from 'angular-2-local-storage'; import { AboutService } from './services/about.service'; import { ConfigService } from './services/config.service'; import { ViewTransactionComponent } from './test/view-transaction'; +import { FileService } from './services/file.service'; import { IdCard } from 'composer-common'; @@ -59,7 +60,8 @@ export class AppComponent implements OnInit, OnDestroy { private modalService: NgbModal, private localStorageService: LocalStorageService, private aboutService: AboutService, - private configService: ConfigService) { + private configService: ConfigService, + private fileService: FileService) { } ngOnInit(): Promise { @@ -97,6 +99,7 @@ export class AppComponent implements OnInit, OnDestroy { logout() { this.clientService.disconnect(); this.identityService.setLoggedIn(false); + this.fileService.deleteAllFiles(); this.composerBanner = ['Hyperledger', 'Composer Playground']; this.showWelcome = false; @@ -118,7 +121,7 @@ export class AppComponent implements OnInit, OnDestroy { let card: IdCard = this.identityCardService.getCurrentIdentityCard(); let connectionProfile = card.getConnectionProfile(); let profileName = 'web' === connectionProfile.type ? 'Web' : connectionProfile.name; - let busNetName = this.clientService.getBusinessNetworkName(); + let busNetName = this.clientService.getBusinessNetwork().getName(); this.composerBanner = [profileName, busNetName]; }); } diff --git a/packages/composer-playground/src/app/common/credentials/credentials.component.spec.ts b/packages/composer-playground/src/app/common/credentials/credentials.component.spec.ts index e371b071df..8015e69260 100644 --- a/packages/composer-playground/src/app/common/credentials/credentials.component.spec.ts +++ b/packages/composer-playground/src/app/common/credentials/credentials.component.spec.ts @@ -380,8 +380,6 @@ describe('CredentialsComponent', () => { fixture.detectChanges(); fixture.whenStable().then(() => { - console.log(component.credentialsForm.control); - // component.credentialsForm.control.controls['userId'].setValue('newValue'); validStub.should.have.been.called; }); })); diff --git a/packages/composer-playground/src/app/common/credentials/credentials.component.ts b/packages/composer-playground/src/app/common/credentials/credentials.component.ts index caceefef77..d7f440be4a 100644 --- a/packages/composer-playground/src/app/common/credentials/credentials.component.ts +++ b/packages/composer-playground/src/app/common/credentials/credentials.component.ts @@ -37,7 +37,6 @@ export class CredentialsComponent implements AfterViewInit { ngAfterViewInit() { this.credentialsForm.control.valueChanges .subscribe(() => { - console.log('banana'); this.validContents(); }); } diff --git a/packages/composer-playground/src/app/editor/add-file/add-file.component.spec.ts b/packages/composer-playground/src/app/editor/add-file/add-file.component.spec.ts index c16ab274fd..bf8f63e5cb 100644 --- a/packages/composer-playground/src/app/editor/add-file/add-file.component.spec.ts +++ b/packages/composer-playground/src/app/editor/add-file/add-file.component.spec.ts @@ -22,6 +22,7 @@ import { ClientService } from '../../services/client.service'; import * as sinon from 'sinon'; import { expect } from 'chai'; +import { FileService } from '../../services/file.service'; class MockAdminService { ensureConnection(): Promise { @@ -64,16 +65,16 @@ describe('AddFileComponent', () => { let mockModelManager; let mockScriptManager; let mockAclManager; - let mockClientService; let mockSystemModelFile; let mockSystemAsset; let mockAclFile; let mockQueryManager; let mockQueryFile; + let mockFileService; beforeEach(() => { - mockClientService = sinon.createStubInstance(ClientService); + mockFileService = sinon.createStubInstance(FileService); TestBed.configureTestingModule({ declarations: [ @@ -87,7 +88,7 @@ describe('AddFileComponent', () => { providers: [ {provide: AdminService, useClass: MockAdminService}, {provide: AlertService, useClass: MockAlertService}, - {provide: ClientService, useValue: mockClientService}, + {provide: FileService, useValue: mockFileService}, NgbActiveModal ] }); @@ -276,7 +277,7 @@ describe('AddFileComponent', () => { it('should create a new script file', async(() => { let mockScript = sinon.createStubInstance(Script); mockScript.getIdentifier.returns('newfile.js'); - mockClientService.createScriptFile.returns(mockScript); + mockFileService.createScriptFile.returns(mockScript); let b = new Blob(['/**JS File*/'], {type: 'text/plain'}); let file = new File([b], 'newfile.js'); @@ -286,7 +287,7 @@ describe('AddFileComponent', () => { // Assertions component.fileType.should.equal('js'); - mockClientService.createScriptFile.calledWith(file.name, 'JS', file.toString()); + mockFileService.createScriptFile.calledWith(file.name, 'JS', file.toString()); component.currentFile.should.deep.equal(mockScript); component.currentFileName.should.equal(mockScript.getIdentifier()); })); @@ -296,7 +297,7 @@ describe('AddFileComponent', () => { component.addScriptFileName = fileName; let mockScript = sinon.createStubInstance(Script); mockScript.getIdentifier.returns(fileName); - mockClientService.createScriptFile.returns(mockScript); + mockFileService.createScriptFile.returns(mockScript); let b = new Blob(['/**JS File*/'], {type: 'text/plain'}); let file = new File([b], ''); @@ -306,7 +307,7 @@ describe('AddFileComponent', () => { // Assertions component.fileType.should.equal('js'); - mockClientService.createScriptFile.calledWith(fileName, 'JS', file.toString()); + mockFileService.createScriptFile.calledWith(fileName, 'JS', file.toString()); component.currentFile.should.deep.equal(mockScript); component.currentFileName.should.equal(mockScript.getIdentifier()); component.currentFileName.should.equal(fileName); @@ -322,14 +323,14 @@ describe('AddFileComponent', () => { let file = new File([b], 'newfile.cto'); let dataBuffer = new Buffer('/**CTO File**/ namespace test'); let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), 'models/' + file.name); - mockClientService.createModelFile.returns(mockModel); + mockFileService.createModelFile.returns(mockModel); // Run method component.createModel(file, dataBuffer); // Assertions component.fileType.should.equal('cto'); - mockClientService.createModelFile.should.have.been.calledWith(dataBuffer.toString(), 'models/' + file.name); + mockFileService.createModelFile.should.have.been.calledWith(dataBuffer.toString(), 'models/' + file.name); component.currentFile.should.deep.equal(mockModel); component.currentFileName.should.equal(mockModel.getName()); })); @@ -344,14 +345,14 @@ describe('AddFileComponent', () => { let file = new File([b], ''); let dataBuffer = new Buffer('/**CTO File**/ namespace test'); let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), fileName); - mockClientService.createModelFile.returns(mockModel); + mockFileService.createModelFile.returns(mockModel); // Run method component.createModel(null, dataBuffer); // Assertions component.fileType.should.equal('cto'); - mockClientService.createModelFile.should.have.been.calledWith(dataBuffer.toString(), fileName); + mockFileService.createModelFile.should.have.been.calledWith(dataBuffer.toString(), fileName); component.currentFile.should.deep.equal(mockModel); component.currentFileName.should.equal(mockModel.getName()); component.currentFileName.should.equal(fileName); @@ -363,14 +364,14 @@ describe('AddFileComponent', () => { let dataBuffer = new Buffer('/**RULE File**/ all the rules'); let filename = 'permissions.acl'; let mockRuleFile = sinon.createStubInstance(AclFile); - mockClientService.createAclFile.returns(mockRuleFile); + mockFileService.createAclFile.returns(mockRuleFile); // Run method component.createRules(dataBuffer); // Assertions component.fileType.should.equal('acl'); - mockClientService.createAclFile.should.have.been.calledWith(filename, dataBuffer.toString()); + mockFileService.createAclFile.should.have.been.calledWith(filename, dataBuffer.toString()); component.currentFile.should.deep.equal(mockRuleFile); component.currentFileName.should.equal(filename); })); @@ -380,14 +381,14 @@ describe('AddFileComponent', () => { it('should create a new query file named queries.qry', async(() => { let dataBuffer = new Buffer('/**QUERY File**/ query things'); let filename = 'queries.qry'; - mockClientService.createQueryFile.returns(mockQueryFile); + mockFileService.createQueryFile.returns(mockQueryFile); // Run method component.createQuery(dataBuffer); // Assertions component.fileType.should.equal('qry'); - mockClientService.createQueryFile.should.have.been.calledWith(filename, dataBuffer.toString()); + mockFileService.createQueryFile.should.have.been.calledWith(filename, dataBuffer.toString()); component.currentFile.should.deep.equal(mockQueryFile); component.currentFileName.should.equal(filename); })); @@ -412,15 +413,15 @@ describe('AddFileComponent', () => { it('should set current file to a script file, created by calling createScript with correct parameters', async(() => { let mockScript = sinon.createStubInstance(Script); mockScript.getIdentifier.returns('lib/script.js'); - mockClientService.getScripts.returns([]); - mockClientService.createScriptFile.returns(mockScript); + mockFileService.getScripts.returns([]); + mockFileService.createScriptFile.returns(mockScript); component.fileType = 'js'; // Run method component.changeCurrentFileType(); // Assertions - mockClientService.createScriptFile.getCall(0).args[0].should.equal('lib/script.js'); + mockFileService.createScriptFile.getCall(0).args[0].should.equal('lib/script.js'); })); it('should increment a script file name if one already exists', async(() => { @@ -430,8 +431,8 @@ describe('AddFileComponent', () => { mockScript.getIdentifier.returns('lib/script.js'); mockScript0.getIdentifier.returns('lib/script0.js'); mockScript1.getIdentifier.returns('lib/script1.js'); - mockClientService.getScripts.returns([mockScript, mockScript0, mockScript1]); - mockClientService.createScriptFile.returns(mockScript); + mockFileService.getScripts.returns([mockScript, mockScript0, mockScript1]); + mockFileService.createScriptFile.returns(mockScript); component.fileType = 'js'; @@ -439,11 +440,11 @@ describe('AddFileComponent', () => { component.changeCurrentFileType(); // Assertions - mockClientService.createScriptFile.getCall(0).args[0].should.equal('lib/script2.js'); + mockFileService.createScriptFile.getCall(0).args[0].should.equal('lib/script2.js'); })); it('should change this.currentFileType to a cto file', async(() => { - mockClientService.getModelFiles.returns([]); + mockFileService.getModelFiles.returns([]); let b = new Blob( [`/** * New model file @@ -459,7 +460,7 @@ namespace org.acme.model`], namespace org.acme.model`); let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), file.name); - mockClientService.createModelFile.returns(mockModel); + mockFileService.createModelFile.returns(mockModel); component.fileType = 'cto'; // Run method @@ -489,7 +490,7 @@ namespace org.acme.model`); let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), file.name); // One element, so the number 0 should be appended - mockClientService.getModelFiles.returns([mockModel]); + mockFileService.getModelFiles.returns([mockModel]); component.fileType = 'cto'; @@ -497,7 +498,7 @@ namespace org.acme.model`); component.changeCurrentFileType(); // Assertions - mockClientService.createModelFile.getCall(0).args[1].should.be.equal('models/org.acme.model0.cto'); + mockFileService.createModelFile.getCall(0).args[1].should.be.equal('models/org.acme.model0.cto'); component.currentFileName.should.equal('models/org.acme.model0.cto'); }); @@ -512,7 +513,7 @@ namespace org.acme.model`); mockFile3.getNamespace.returns('org.acme.model3'); let mockFile4 = sinon.createStubInstance(ModelFile); mockFile4.getNamespace.returns('org.acme.model4'); - mockClientService.getModelFiles.returns([mockFile, mockFile0, mockFile1, mockFile3, mockFile4]); + mockFileService.getModelFiles.returns([mockFile, mockFile0, mockFile1, mockFile3, mockFile4]); let b = new Blob( [`/** @@ -530,7 +531,7 @@ namespace org.acme.model`], namespace org.acme.model`); let mockModel = new ModelFile(mockModelManager, dataBuffer.toString(), file.name); - mockClientService.createModelFile.returns(mockModel); + mockFileService.createModelFile.returns(mockModel); component.fileType = 'cto'; // Run method @@ -547,7 +548,7 @@ namespace org.acme.model`); */`); let mockQuery = new QueryFile('queries.qry', mockQueryManager, dataBuffer.toString()); - mockClientService.createAclFile.returns(mockQuery); + mockFileService.createAclFile.returns(mockQuery); component.fileType = 'qry'; component.changeCurrentFileType(); @@ -570,7 +571,7 @@ namespace org.acme.model`); }`); let mockAcl = new AclFile('permissions.acl', mockAclManager, dataBuffer.toString()); - mockClientService.createAclFile.returns(mockAcl); + mockFileService.createAclFile.returns(mockAcl); component.fileType = 'acl'; component.changeCurrentFileType(); diff --git a/packages/composer-playground/src/app/editor/add-file/add-file.component.ts b/packages/composer-playground/src/app/editor/add-file/add-file.component.ts index 5ac4a4a0eb..eea0ed7f45 100644 --- a/packages/composer-playground/src/app/editor/add-file/add-file.component.ts +++ b/packages/composer-playground/src/app/editor/add-file/add-file.component.ts @@ -1,9 +1,8 @@ -import { Component, Input } from '@angular/core'; +import { Component } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; -import { BusinessNetworkDefinition, ModelFile, AclFile } from 'composer-common'; import { AlertService } from '../../basic-modals/alert.service'; -import { ClientService } from '../../services/client.service'; +import { FileService } from '../../services/file.service'; @Component({ selector: 'add-file-modal', @@ -34,7 +33,7 @@ export class AddFileComponent { constructor(private alertService: AlertService, private activeModal: NgbActiveModal, - private clientService: ClientService) { + private fileService: FileService) { } queryExists() { @@ -73,35 +72,35 @@ export class AddFileComponent { fileAccepted(file: File) { let type = file.name.substr(file.name.lastIndexOf('.') + 1); this.getDataBuffer(file) - .then((data) => { - switch (type) { - case 'js': - this.expandInput = true; - this.createScript(file, data); - break; - case 'cto': - this.expandInput = true; - this.createModel(file, data); - break; - case 'md': - this.expandInput = true; - this.createReadme(data); - break; - case 'acl': - this.expandInput = true; - this.createRules(data); - break; - case 'qry': - this.expandInput = true; - this.createQuery(data); - break; - default: - throw new Error('Unexpected File Type: ' + type); - } - }) - .catch((err) => { - this.fileRejected(err); - }); + .then((data) => { + switch (type) { + case 'js': + this.expandInput = true; + this.createScript(file, data); + break; + case 'cto': + this.expandInput = true; + this.createModel(file, data); + break; + case 'md': + this.expandInput = true; + this.createReadme(data); + break; + case 'acl': + this.expandInput = true; + this.createRules(data); + break; + case 'qry': + this.expandInput = true; + this.createQuery(data); + break; + default: + throw new Error('Unexpected File Type: ' + type); + } + }) + .catch((err) => { + this.fileRejected(err); + }); } getDataBuffer(file: File) { @@ -122,14 +121,14 @@ export class AddFileComponent { createScript(file: File, dataBuffer) { this.fileType = 'js'; let filename = (file && file.name) ? 'lib/' + file.name : this.addScriptFileName; - this.currentFile = this.clientService.createScriptFile(filename, 'JS', dataBuffer.toString()); + this.currentFile = this.fileService.createScriptFile(filename, 'JS', dataBuffer.toString()); this.currentFileName = this.currentFile.getIdentifier(); } createModel(file: File, dataBuffer) { this.fileType = 'cto'; let filename = (file && file.name) ? 'models/' + file.name : this.addModelFileName; - this.currentFile = this.clientService.createModelFile(dataBuffer.toString(), filename); + this.currentFile = this.fileService.createModelFile(dataBuffer.toString(), filename); this.currentFileName = this.currentFile.getName(); } @@ -142,14 +141,14 @@ export class AddFileComponent { createRules(dataBuffer) { this.fileType = 'acl'; let filename = 'permissions.acl'; - this.currentFile = this.clientService.createAclFile(filename, dataBuffer.toString()); + this.currentFile = this.fileService.createAclFile(filename, dataBuffer.toString()); this.currentFileName = filename; } createQuery(dataBuffer) { this.fileType = 'qry'; let filename = 'queries.qry'; - this.currentFile = this.clientService.createQueryFile(filename, dataBuffer.toString()); + this.currentFile = this.fileService.createQueryFile(filename, dataBuffer.toString()); this.currentFileName = filename; } @@ -166,23 +165,23 @@ export class AddFileComponent { `/** * New script file */`; - let existingScripts = this.clientService.getScripts(); + let existingScripts = this.fileService.getScripts(); let increment = 0; let scriptName = this.addScriptFileName + this.addScriptFileExtension; - while ( existingScripts.findIndex((file) => file.getIdentifier() === scriptName) !== -1 ) { + while (existingScripts.findIndex((file) => file.getIdentifier() === scriptName) !== -1) { scriptName = this.addScriptFileName + increment + this.addScriptFileExtension; increment++; } - this.currentFile = this.clientService.createScriptFile(scriptName, 'JS', code); + this.currentFile = this.fileService.createScriptFile(scriptName, 'JS', code); this.currentFileName = scriptName; } else if (this.fileType === 'cto') { - let existingModels = this.clientService.getModelFiles(); + let existingModels = this.fileService.getModelFiles(); let increment = 0; let newModelNamespace = this.addModelNamespace; - while ( existingModels.findIndex((file) => file.getNamespace() === newModelNamespace) !== -1 ) { + while (existingModels.findIndex((file) => file.getNamespace() === newModelNamespace) !== -1) { newModelNamespace = this.addModelNamespace + increment; increment++; } @@ -195,7 +194,7 @@ export class AddFileComponent { namespace ${newModelNamespace}`; let fileName = this.addModelPath + newModelNamespace + this.addModelFileExtension; - this.currentFile = this.clientService.createModelFile(code, fileName); + this.currentFile = this.fileService.createModelFile(code, fileName); this.currentFileName = fileName; } else if (this.fileType === 'qry') { let code = @@ -203,7 +202,7 @@ namespace ${newModelNamespace}`; * New query file */`; this.currentFileName = 'queries.qry'; - this.currentFile = this.clientService.createQueryFile(this.currentFileName, code); + this.currentFile = this.fileService.createQueryFile(this.currentFileName, code); } else { let code = `/** @@ -217,7 +216,7 @@ namespace ${newModelNamespace}`; action: ALLOW }`; this.currentFileName = 'permissions.acl'; - this.currentFile = this.clientService.createAclFile(this.currentFileName, code); + this.currentFile = this.fileService.createAclFile(this.currentFileName, code); } } } diff --git a/packages/composer-playground/src/app/editor/editor-file/editor-file.component.spec.ts b/packages/composer-playground/src/app/editor/editor-file/editor-file.component.spec.ts index ea59d985a0..7843fe264a 100644 --- a/packages/composer-playground/src/app/editor/editor-file/editor-file.component.spec.ts +++ b/packages/composer-playground/src/app/editor/editor-file/editor-file.component.spec.ts @@ -2,18 +2,19 @@ /* tslint:disable:no-unused-expression */ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ +/* tslint:disable:no-unused-variable */ +/* tslint:disable:no-unused-expression */ +/* tslint:disable:no-var-requires */ +/* tslint:disable:max-classes-per-file */ import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { Directive, Input } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { By } from '@angular/platform-browser'; -import { DebugElement } from '@angular/core'; - import { EditorFileComponent } from './editor-file.component'; - import { ClientService } from '../../services/client.service'; import * as sinon from 'sinon'; import * as chai from 'chai'; +import { FileService } from '../../services/file.service'; let should = chai.should(); @@ -36,24 +37,26 @@ class MockPerfectScrollBarDirective { @Directive({ selector: '[debounce]' }) + class MockDebounceDirective { @Input() delay; } describe('EditorFileComponent', () => { + let component: EditorFileComponent; let fixture: ComponentFixture; - let mockClientService = sinon.createStubInstance(ClientService); + let mockFileService = sinon.createStubInstance(FileService); beforeEach(() => { TestBed.configureTestingModule({ imports: [FormsModule], declarations: [EditorFileComponent, MockCodeMirrorDirective, MockPerfectScrollBarDirective, MockDebounceDirective], providers: [ - {provide: ClientService, useValue: mockClientService}] + {provide: ClientService, useValue: mockClientService}, + {provide: FileService, useValue: mockFileService}] }); - fixture = TestBed.createComponent(EditorFileComponent); component = fixture.componentInstance; }); @@ -68,7 +71,6 @@ describe('EditorFileComponent', () => { getCursor: sinon.stub().returns('myCursor') }; component['mdCodeConfig'].extraKeys['Ctrl-Q'](mockCm); - mockCm.getCursor.should.have.been.called; mockCm.foldCode.should.have.been.calledWith('myCursor'); }); @@ -79,52 +81,26 @@ describe('EditorFileComponent', () => { getCursor: sinon.stub().returns('myCursor') }; component['codeConfig'].extraKeys['Ctrl-Q'](mockCm); - mockCm.getCursor.should.have.been.called; mockCm.foldCode.should.have.been.calledWith('myCursor'); }); - describe('set editorFile', () => { - - beforeEach(() => { - mockClientService.validateFile.returns(null); - mockClientService.getModelFile.returns({getDefinitions: sinon.stub().returns({})}); - mockClientService.getScriptFile.returns({getContents: sinon.stub().returns({})}); - mockClientService.getAclFile.returns({getDefinitions: sinon.stub().returns({})}); - }); - - it('should set editor file', () => { - let mockLoadFile = sinon.stub(component, 'loadFile'); - component.editorFile = {editorFile: 'my file'}; - - mockLoadFile.should.have.been.called; - component['_editorFile'].should.deep.equal({editorFile: 'my file'}); - }); - - it('should not set editor file if null', () => { - let mockLoadFile = sinon.stub(component, 'loadFile'); - component.editorFile = null; - - mockLoadFile.should.not.have.been.called; - }); - - it('should validate model file content once set', () => { - component.editorFile = {model: true}; - mockClientService.validateFile.should.have.been.called; - }); - - it('should validate script file content once set', () => { - component.editorFile = {script: true}; - mockClientService.validateFile.should.have.been.called; - }); + it('should set _editorFile', () => { + let loadFileStub = sinon.stub(component, 'loadFile'); + component.editorFile = 'myFile'; + loadFileStub.should.have.been.called; + component['_editorFile'].should.equal('myFile'); + }); - it('should validate acl file content once set', () => { - component.editorFile = {acl: true}; - mockClientService.validateFile.should.have.been.called; - }); + it('should not set _editorFile', () => { + let loadFileStub = sinon.stub(component, 'loadFile'); + component.editorFile = null; + loadFileStub.should.not.have.been.called; + should.not.exist(component['_editorFile']); }); describe('set previewReadmeActive', () => { + it('should set the preview boolean to true', () => { component['_previewReadmeActive'] = false; component.previewReadmeActive = true; @@ -139,340 +115,248 @@ describe('EditorFileComponent', () => { }); describe('loadFile', () => { - it('should load a model file', () => { - mockClientService.getModelFile.returns({ - getDefinitions: sinon.stub().returns('my model') - }); + beforeEach(() => { component['_editorFile'] = { - model: true, - id: 'model' + id: 'myId', + isModel: sinon.stub().returns(false), + isScript: sinon.stub().returns(false), + isAcl: sinon.stub().returns(false), + isQuery: sinon.stub().returns(false), + isPackage: sinon.stub().returns(false), + isReadMe: sinon.stub().returns(false) }; + }); + it('should load a model file', () => { + component['_editorFile'].isModel.returns(true); + mockFileService.getFile.returns({ + getContent: sinon.stub().returns('my model') + }); + mockFileService.validateFile.returns(null); component.loadFile(); - - mockClientService.getModelFile.should.have.been.calledWith('model'); component['editorContent'].should.equal('my model'); component['editorType'].should.equal('code'); + should.not.exist(component['currentError']); }); it('should load a model file but not find it', () => { - mockClientService.getModelFile.returns(null); - - component['_editorFile'] = { - model: true, - id: 'model' - }; - + component['_editorFile'].isModel.returns(true); + mockFileService.getFile.returns(null); component.loadFile(); - should.not.exist(component['editorContent']); }); it('should load a script file', () => { - mockClientService.getScriptFile.returns({ - getContents: sinon.stub().returns('my script') + component['_editorFile'].isScript.returns(true); + mockFileService.getFile.returns({ + getContent: sinon.stub().returns('my script') }); - - component['_editorFile'] = { - script: true, - id: 'script' - }; - + mockFileService.validateFile.returns(null); component.loadFile(); - - mockClientService.getScriptFile.should.have.been.calledWith('script'); component['editorContent'].should.equal('my script'); component['editorType'].should.equal('code'); + should.not.exist(component['currentError']); }); it('should load a script file but not find it', () => { - mockClientService.getScriptFile.returns(null); - - component['_editorFile'] = { - script: true, - id: 'script' - }; - + component['_editorFile'].isScript.returns(true); + mockFileService.getFile.returns(null); + mockFileService.validateFile.returns(null); component.loadFile(); - should.not.exist(component['editorContent']); }); it('should load a acl file', () => { - mockClientService.getAclFile.returns({ - getDefinitions: sinon.stub().returns('my acl') + component['_editorFile'].isAcl.returns(true); + mockFileService.getFile.returns({ + getContent: sinon.stub().returns('my acl') }); - - component['_editorFile'] = { - acl: true, - }; - + mockFileService.validateFile.returns(null); component.loadFile(); - component['editorContent'].should.equal('my acl'); component['editorType'].should.equal('code'); + should.not.exist(component['currentError']); }); it('should load acl file but not find it', () => { - mockClientService.getAclFile.returns(null); - - component['_editorFile'] = { - acl: true, - }; - + component['_editorFile'].isAcl.returns(true); + mockFileService.getFile.returns(null); component.loadFile(); - should.not.exist(component['editorContent']); }); it('should load a package file', () => { - mockClientService.getMetaData.returns({ - getPackageJson: sinon.stub().returns({name: 'my network'}) - }); - - component['_editorFile'] = { - package: true, - }; - + component['_editorFile'].isPackage.returns(true); + mockFileService.getFile.returns({getContent: sinon.stub().returns(`{\n "name": "my network"\n}`)}); + mockFileService.validateFile.returns(null); component.loadFile(); - - component['editorContent'].should.deep.equal(`{\n "name": "my network"\n}`); + component['editorContent'].should.deep.equal('"{\\n \\"name\\": \\"my network\\"\\n}"'); component['editorType'].should.equal('code'); - }); - - it('should load package file but not find it', () => { - mockClientService.getMetaData.returns({ - getPackageJson: sinon.stub() - }); - - component['_editorFile'] = { - package: true, - }; - - component.loadFile(); - - should.not.exist(component['editorContent']); + should.not.exist(component['currentError']); }); it('should load a readme file', () => { - mockClientService.getMetaData.returns({ - getREADME: sinon.stub().returns('readme') + component['_editorFile'].isReadMe.returns(true); + mockFileService.getFile.returns({ + getContent: sinon.stub().returns(`readme`) }); - - component['_editorFile'] = { - readme: true, - }; - component['_previewReadmeActive'] = false; - component.loadFile(); - component['editorContent'].should.deep.equal(`readme`); component['previewContent'].should.deep.equal(`

readme

\n`); component['editorType'].should.equal('readme'); }); it('should load readme file but not find it', () => { - mockClientService.getMetaData.returns({ - getREADME: sinon.stub() - }); - - component['_editorFile'] = { - readme: true, - }; - + component['_editorFile'].isReadMe.returns(true); + mockFileService.getFile.returns(null); component['_previewReadmeActive'] = false; - component.loadFile(); - should.not.exist(component['editorContent']); }); it('should load a query file', () => { - mockClientService.getQueryFile.returns({ - getDefinitions: sinon.stub().returns('my query') + component['_editorFile'].isQuery.returns(true); + mockFileService.getFile.returns({ + getContent: sinon.stub().returns('my query') }); - - component['_editorFile'] = { - query: true, - }; - + mockFileService.validateFile.returns(null); component.loadFile(); - component['editorContent'].should.equal('my query'); component['editorType'].should.equal('code'); + should.not.exist(component['currentError']); }); it('should load a query file but not find it', () => { - mockClientService.getQueryFile.returns(null); - - component['_editorFile'] = { - query: true, - }; - + component['_editorFile'].isQuery.returns(true); + mockFileService.getFile.returns(null); component.loadFile(); - should.not.exist(component['editorContent']); }); it('should load no files', () => { - mockClientService.getScriptFile.returns(null); - - component['_editorFile'] = { - id: 'script' - }; - component.loadFile(); - should.not.exist(component['editorContent']); }); }); describe('setCurrentCode', () => { - it('should set model file', () => { + + let updatedFile = {getId: sinon.stub().returns('myId')}; + + beforeEach(() => { component['_editorFile'] = { - model: true, - id: 'model' + id: 'myId', + isModel: sinon.stub().returns(false), + isScript: sinon.stub().returns(false), + isAcl: sinon.stub().returns(false), + isQuery: sinon.stub().returns(false), + isPackage: sinon.stub().returns(false), + isReadMe: sinon.stub().returns(false) + }; + mockFileService.businessNetworkChanged$ = { + next: sinon.stub() }; - component['editorContent'] = 'my model'; + mockFileService.updateFile.returns(updatedFile); + mockFileService.validateFile.reset(); + mockFileService.updateBusinessNetwork.reset(); + }); + it('should set model file', () => { + component['_editorFile'].isModel.returns(true); + component['editorContent'] = 'my model'; component.setCurrentCode(); - - mockClientService.updateFile.should.have.been.calledWith('model', 'my model', 'model'); + mockFileService.updateFile.should.have.been.calledWith('myId', 'my model', 'model'); + mockFileService.validateFile.should.have.been.calledWith('myId', 'model'); + mockFileService.updateBusinessNetwork.should.have.been.calledWith('myId', updatedFile); should.not.exist(component['currentError']); }); it('should set script file', () => { - component['_editorFile'] = { - script: true, - id: 'script' - }; - + component['_editorFile'].isScript.returns(true); component['editorContent'] = 'my script'; - component.setCurrentCode(); - - mockClientService.updateFile.should.have.been.calledWith('script', 'my script', 'script'); + mockFileService.updateFile.should.have.been.calledWith('myId', 'my script', 'script'); + mockFileService.validateFile.should.have.been.calledWith('myId', 'script'); + mockFileService.updateBusinessNetwork.should.have.been.calledWith('myId', updatedFile); should.not.exist(component['currentError']); }); it('should set acl file', () => { - component['_editorFile'] = { - acl: true, - id: 'acl' - }; - + component['_editorFile'].isAcl.returns(true); component['editorContent'] = 'my acl'; - component.setCurrentCode(); - - mockClientService.updateFile.should.have.been.calledWith('acl', 'my acl', 'acl'); + mockFileService.updateFile.should.have.been.calledWith('myId', 'my acl', 'acl'); + mockFileService.validateFile.should.have.been.calledWith('myId', 'acl'); + mockFileService.updateBusinessNetwork.should.have.been.calledWith('myId', updatedFile); should.not.exist(component['currentError']); }); it('should set query file', () => { - component['_editorFile'] = { - query: true, - id: 'query' - }; - + component['_editorFile'].isQuery.returns(true); component['editorContent'] = 'my query'; - component.setCurrentCode(); - - mockClientService.updateFile.should.have.been.calledWith('query', 'my query', 'query'); + mockFileService.updateFile.should.have.been.calledWith('myId', 'my query', 'query'); + mockFileService.validateFile.should.have.been.calledWith('myId', 'query'); + mockFileService.updateBusinessNetwork.should.have.been.calledWith('myId', updatedFile); should.not.exist(component['currentError']); }); it('should set package file', () => { - component['_editorFile'] = { - package: true, - id: 'package' - }; - + component['_editorFile'].isPackage.returns(true); component['editorContent'] = '{"name": "my network"}'; - mockClientService.businessNetworkChanged$ = { next: sinon.stub() }; - component.setCurrentCode(); - - mockClientService.updateFile.should.have.been.calledWith('package', '{"name": "my network"}', 'package'); + mockFileService.updateFile.should.have.been.calledWith('myId', '{"name": "my network"}', 'package'); + mockFileService.validateFile.should.have.been.calledWith('myId', 'package'); + mockFileService.updateBusinessNetwork.should.have.been.calledWith('myId', updatedFile); should.not.exist(component['currentError']); }); it('should set the readme file', () => { - component['_editorFile'] = { - readme: true, - id: 'readme' - }; - + component['_editorFile'].isReadMe.returns(true); component['editorContent'] = 'my readme'; - component.setCurrentCode(); - - mockClientService.updateFile.should.have.been.calledWith('readme', 'my readme', 'readme'); + mockFileService.updateFile.should.have.been.calledWith('myId', 'my readme', 'readme'); }); it('should compile the readme file', () => { - component['_editorFile'] = { - readme: true, - id: 'readme' - }; - + component['_editorFile'].isReadMe.returns(true); component['_previewReadmeActive'] = true; - component['editorContent'] = 'my readme'; - component.setCurrentCode(); - component['previewContent'].should.equal(`

my readme

\n`); }); it('should throw error if unknown file type', (() => { - component['_editorFile'] = { - bob: true, - id: 'bob' - }; - component.setCurrentCode(); - component['currentError'].should.equal('Error: unknown file type'); - mockClientService.businessNetworkChanged$.next.should.have.been.calledWith(false); + mockFileService.businessNetworkChanged$.next.should.have.been.calledWith(false); })); it('should set current error on error', () => { - mockClientService.updateFile.returns('some error'); - component['_editorFile'] = { - acl: true, - id: 'acl' - }; - + mockFileService.updateFile.throws(new Error('some error')); + component['_editorFile'].isAcl.returns(true); component['editorContent'] = 'my acl'; component.setCurrentCode(); - mockClientService.updateFile.should.have.been.calledWith('acl', 'my acl', 'acl'); - component['currentError'].should.equal('some error'); + mockFileService.updateFile.should.have.been.calledWith('myId', 'my acl', 'acl'); + mockFileService.validateFile.should.not.have.been.called; + component['currentError'].should.equal('Error: some error'); }); - it('should set handle on error', () => { - mockClientService.setBusinessNetworkPackageJson.reset(); - component['_editorFile'] = { - package: true, - }; - - component['editorContent'] = '{"name": "my network"'; - - mockClientService.businessNetworkChanged$ = { - next: sinon.stub() - }; - + it('should not update business network on error', () => { + mockFileService.validateFile.returns('some error'); + component['_editorFile'].isAcl.returns(true); + component['editorContent'] = 'my acl'; component.setCurrentCode(); - + mockFileService.updateFile.should.have.been.calledWith('myId', 'my acl', 'acl'); + mockFileService.updateBusinessNetwork.should.not.have.been.called; component['currentError'].should.equal('some error'); }); }); @@ -487,7 +371,6 @@ describe('EditorFileComponent', () => { it('should update the code', () => { component.onCodeChanged(); - mockSetCurrentCode.should.have.been.called; component['previousCode'].should.equal('my code'); }); @@ -495,27 +378,21 @@ describe('EditorFileComponent', () => { it('should not update if changing file', () => { mockSetCurrentCode.reset(); component['changingCurrentFile'] = true; - component.onCodeChanged(); - mockSetCurrentCode.should.not.have.been.called; }); it('should not update if code hasn\'t changed', () => { mockSetCurrentCode.reset(); component['previousCode'] = 'my code'; - component.onCodeChanged(); - mockSetCurrentCode.should.not.have.been.called; }); it('should compile the readme on preview', () => { mockSetCurrentCode.reset(); component['_previewReadmeActive'] = true; - component.onCodeChanged(); - mockSetCurrentCode.should.have.been.called; }); }); diff --git a/packages/composer-playground/src/app/editor/editor-file/editor-file.component.ts b/packages/composer-playground/src/app/editor/editor-file/editor-file.component.ts index 6eb7cc542e..e75af81cd0 100644 --- a/packages/composer-playground/src/app/editor/editor-file/editor-file.component.ts +++ b/packages/composer-playground/src/app/editor/editor-file/editor-file.component.ts @@ -1,6 +1,8 @@ import { Component, Input } from '@angular/core'; +import { FileService } from '../../services/file.service'; import { ClientService } from '../../services/client.service'; +import { EditorComponent } from '../editor.component'; import * as marked from 'marked'; @@ -81,59 +83,60 @@ export class EditorFileComponent { this._previewReadmeActive = previewReadme; } - constructor(private clientService: ClientService) { + constructor(private fileService: FileService, private clientService: ClientService) { } loadFile() { this.changingCurrentFile = true; this.currentError = null; - if (this._editorFile.model) { - let modelFile = this.clientService.getModelFile(this._editorFile.id); + if (this._editorFile.isModel()) { + let modelFile = this.fileService.getFile(this._editorFile.id, 'model'); if (modelFile) { - this.editorContent = modelFile.getDefinitions(); + this.editorContent = modelFile.getContent(); this.editorType = 'code'; - this.currentError = this.clientService.validateFile(this._editorFile.id, this.editorContent, 'model'); + this.currentError = this.fileService.validateFile(this._editorFile.id, 'model'); } else { this.editorContent = null; } - } else if (this._editorFile.script) { - let script = this.clientService.getScriptFile(this._editorFile.id); + } else if (this._editorFile.isScript()) { + let script = this.fileService.getFile(this._editorFile.id, 'script'); if (script) { - this.editorContent = script.getContents(); + this.editorContent = script.getContent(); this.editorType = 'code'; - this.currentError = this.clientService.validateFile(this._editorFile.id, this.editorContent, 'script'); + this.currentError = this.fileService.validateFile(this._editorFile.id, 'script'); } else { this.editorContent = null; } - } else if (this._editorFile.acl) { - let aclFile = this.clientService.getAclFile(); + } else if (this._editorFile.isAcl()) { + let aclFile = this.fileService.getFile(this._editorFile.id, 'acl'); if (aclFile) { - this.editorContent = aclFile.getDefinitions(); + this.editorContent = aclFile.getContent(); this.editorType = 'code'; - this.currentError = this.clientService.validateFile(this._editorFile.id, this.editorContent, 'acl'); + this.currentError = this.fileService.validateFile(this._editorFile.id, 'acl'); } else { this.editorContent = null; } - } else if (this._editorFile.package) { - let packageJson = this.clientService.getMetaData().getPackageJson(); - this.editorContent = JSON.stringify(packageJson, null, 2); + } else if (this._editorFile.isPackage()) { + let packageJson = this.fileService.getFile(this._editorFile.id, 'package'); + this.editorContent = JSON.stringify(packageJson.getContent(), null, 2); this.editorType = 'code'; - } else if (this._editorFile.readme) { - let readme = this.clientService.getMetaData().getREADME(); + this.currentError = this.fileService.validateFile(this._editorFile.id, 'package'); + } else if (this._editorFile.isReadMe()) { + let readme = this.fileService.getFile(this._editorFile.id, 'readme'); if (readme) { - this.editorContent = readme; - this.previewContent = marked(readme); + this.editorContent = readme.getContent(); + this.previewContent = marked(readme.getContent()); this.editorType = 'readme'; } - } else if (this._editorFile.query) { - let queryFile = this.clientService.getQueryFile(); - if (queryFile) { - this.editorContent = queryFile.getDefinitions(); - this.editorType = 'code'; - this.currentError = this.clientService.validateFile(this._editorFile.id, this.editorContent, 'query'); - } else { - this.editorContent = null; - } + } else if (this._editorFile.isQuery()) { + let queryFile = this.fileService.getFile(this._editorFile.id, 'query'); + if (queryFile) { + this.editorContent = queryFile.getContent(); + this.editorType = 'code'; + this.currentError = this.fileService.validateFile(this._editorFile.id, 'query'); + } else { + this.editorContent = null; + } } else { this.editorContent = null; } @@ -143,29 +146,40 @@ export class EditorFileComponent { setCurrentCode() { let type: string; + this.currentError = null; try { - if (this._editorFile.model) { + if (this._editorFile.isModel()) { type = 'model'; - } else if (this._editorFile.script) { + } else if (this._editorFile.isScript()) { type = 'script'; - } else if (this._editorFile.acl) { + } else if (this._editorFile.isAcl()) { type = 'acl'; - } else if (this._editorFile.query) { + } else if (this._editorFile.isQuery()) { type = 'query'; - } else if (this._editorFile.package) { + } else if (this._editorFile.isPackage()) { type = 'package'; - } else if (this._editorFile.readme) { + } else if (this._editorFile.isReadMe()) { type = 'readme'; this.previewContent = marked(this.editorContent); } else { throw new Error('unknown file type'); } - this.currentError = this.clientService.updateFile(this._editorFile.id, this.editorContent, type); + let updatedFile = this.fileService.updateFile(this._editorFile.id, this.editorContent, type); + // read me isn't validated + if (!this._editorFile.isReadMe()) { + this.currentError = this.fileService.validateFile(updatedFile.getId(), type); + } + + if (!this.currentError) { + // update the stored business network + this.fileService.updateBusinessNetwork(this._editorFile.id, updatedFile); + } + + this.fileService.businessNetworkChanged$.next(true); } catch (e) { this.currentError = e.toString(); - - this.clientService.businessNetworkChanged$.next(false); + this.fileService.businessNetworkChanged$.next(false); } } diff --git a/packages/composer-playground/src/app/editor/editor.component.html b/packages/composer-playground/src/app/editor/editor.component.html index 3369097b8e..69bcbec1b6 100644 --- a/packages/composer-playground/src/app/editor/editor.component.html +++ b/packages/composer-playground/src/app/editor/editor.component.html @@ -8,11 +8,11 @@

Package Details

-

Model File

-

Script File

-

Access Control

-

About

-

Query File

+

Model File

+

Script File

+

Access Control

+

About

+

Query File

{{file.displayID}}
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 7c81684962..fc4db6406a 100644 --- a/packages/composer-playground/src/app/editor/editor.component.spec.ts +++ b/packages/composer-playground/src/app/editor/editor.component.spec.ts @@ -11,12 +11,13 @@ import { EditorComponent } from './editor.component'; import { AdminService } from '../services/admin.service'; import { ClientService } from '../services/client.service'; -import { EditorService } from './editor.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AlertService } from '../basic-modals/alert.service'; import { ModelFile, Script, AclFile, QueryFile } from 'composer-common'; +import { EditorFile } from '../services/editor-file'; import { ScrollToElementDirective } from '../directives/scroll/scroll-to-element.directive'; import { BehaviorSubject } from 'rxjs/Rx'; +import { FileService } from '../services/file.service'; import * as sinon from 'sinon'; import * as chai from 'chai'; @@ -61,6 +62,7 @@ describe('EditorComponent', () => { let mockAdminService; let mockAlertService; let mockClientService; + let mockFileService; let mockModal; let mockDrawer; let mockModelFile; @@ -73,15 +75,15 @@ describe('EditorComponent', () => { mockAdminService = sinon.createStubInstance(AdminService); mockAlertService = sinon.createStubInstance(AlertService); mockClientService = sinon.createStubInstance(ClientService); + mockFileService = sinon.createStubInstance(FileService); mockModal = sinon.createStubInstance(NgbModal); mockDrawer = sinon.createStubInstance(DrawerService); mockModelFile = sinon.createStubInstance(ModelFile); mockScriptFile = sinon.createStubInstance(Script); mockRuleFile = sinon.createStubInstance(AclFile); mockQueryFile = sinon.createStubInstance(QueryFile); - editorService = new EditorService(); - mockClientService.getQueryFile.returns(mockQueryFile); + mockFileService.getQueryFile.returns(mockQueryFile); mockAlertService.successStatus$ = {next: sinon.stub()}; mockAlertService.busyStatus$ = {next: sinon.stub()}; @@ -95,7 +97,7 @@ describe('EditorComponent', () => { {provide: ClientService, useValue: mockClientService}, {provide: NgbModal, useValue: mockModal}, {provide: AlertService, useValue: mockAlertService}, - {provide: EditorService, useValue: editorService}, + {provide: FileService, useValue: mockFileService}, {provide: DrawerService, useValue: mockDrawer}] }); @@ -108,7 +110,7 @@ describe('EditorComponent', () => { beforeEach(() => { mockClientService.ensureConnected.returns(Promise.resolve()); - mockClientService.businessNetworkChanged$ = { + mockFileService.businessNetworkChanged$ = { takeWhile: sinon.stub().returns({ subscribe: (callback) => { let noError = true; @@ -116,7 +118,7 @@ describe('EditorComponent', () => { } }) }; - mockClientService.namespaceChanged$ = { + mockFileService.namespaceChanged$ = { takeWhile: sinon.stub().returns({ subscribe: (callback) => { callback('new-name'); @@ -132,10 +134,11 @@ describe('EditorComponent', () => { it('should initialize the editor', fakeAsync(() => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetFile = sinon.stub(component, 'setCurrentFile'); let mockSetIntialFile = sinon.stub(component, 'setInitialFile'); + mockFileService.getEditorFiles.returns([]); + mockFileService.getCurrentFile.returns(null); component.ngOnInit(); tick(); @@ -144,17 +147,18 @@ describe('EditorComponent', () => { component['dirty'].should.equal(true); mockUpdatePackage.should.have.been.called; - mockUpdateFiles.should.have.been.called; mockSetFile.should.not.have.been.called; mockSetIntialFile.should.have.been.called; + mockFileService.loadFiles.should.have.been.called; })); it('should re-initialize the editor', fakeAsync(() => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetFile = sinon.stub(component, 'setCurrentFile'); let mockSetIntialFile = sinon.stub(component, 'setInitialFile'); - component['editorService'].setCurrentFile('file'); + + mockFileService.getEditorFiles.returns(['myFile']); + mockFileService.getCurrentFile.returns('myFile'); component.ngOnInit(); @@ -164,13 +168,13 @@ describe('EditorComponent', () => { component['dirty'].should.equal(true); mockUpdatePackage.should.have.been.called; - mockUpdateFiles.should.have.been.called; mockSetFile.should.have.been.called; mockSetIntialFile.should.not.have.been.called; + mockFileService.getEditorFiles.should.have.been.calledTwice; })); it('should set noError to false when notified', fakeAsync(() => { - mockClientService.businessNetworkChanged$ = { + mockFileService.businessNetworkChanged$ = { takeWhile: sinon.stub().returns({ subscribe: (callback) => { let noError = false; @@ -180,20 +184,16 @@ describe('EditorComponent', () => { }; mockEditorFilesValidate.returns(false); - let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + component.ngOnInit(); tick(); component['noError'].should.equal(false); - - mockUpdatePackage.should.have.been.called; - mockUpdateFiles.should.have.been.called; })); it('should set noError and dirty to be true when notified', fakeAsync(() => { - mockClientService.businessNetworkChanged$ = { + mockFileService.businessNetworkChanged$ = { takeWhile: sinon.stub().returns({ subscribe: (callback) => { let noError = true; @@ -202,103 +202,85 @@ describe('EditorComponent', () => { }) }; - let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); component.ngOnInit(); tick(); component['noError'].should.equal(true); component['dirty'].should.equal(true); - - mockUpdatePackage.should.have.been.called; - mockUpdateFiles.should.have.been.called; })); - it('should set current file to readme', fakeAsync(() => { - let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); - let mockCurrentFile = sinon.stub(component, 'setCurrentFile'); - - component['files'] = [{readme: true}, {model: true}]; - component.ngOnInit(); - - tick(); - - mockCurrentFile.should.have.been.calledWith({readme: true}); + it('should handle namespace change', fakeAsync(() => { + component['currentFile'] = 'myFile'; + let updateFilesStub = sinon.stub(component, 'updateFiles'); + let findFileStub = sinon.stub(component, 'findFileIndex').returns(0); + let currentFileStub = sinon.stub(component, 'setCurrentFile'); - mockUpdatePackage.should.have.been.called; - mockUpdateFiles.should.have.been.called; - })); + component['files'] = ['myFile']; - it('should set current file to first one if no readme', fakeAsync(() => { - let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); - let mockCurrentFile = sinon.stub(component, 'setCurrentFile'); + mockFileService.namespaceChanged$ = { + takeWhile: sinon.stub().returns({ + subscribe: (callback) => { + let newName = 'bob'; + callback(newName); + } + }) + }; - component['files'] = [{model: true}, {script: true}]; component.ngOnInit(); tick(); - mockCurrentFile.should.have.been.calledWith({model: true}); - - mockUpdatePackage.should.have.been.called; - mockUpdateFiles.should.have.been.called; + updateFilesStub.should.have.been.called; + findFileStub.should.have.been.calledWith(true, 'bob'); + currentFileStub.should.have.been.calledWith('myFile'); })); - it('should set current file from editor service if present', fakeAsync(() => { - let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); - - let file = {id: 'testFile', displayID: 'script.js'}; - component['editorService'].setCurrentFile(file); + it('should handle error', fakeAsync(() => { + mockClientService.ensureConnected.returns(Promise.reject('some error')); component.ngOnInit(); + tick(); - let setFile = component['currentFile']; - component['currentFile'].should.deep.equal(file); + mockAlertService.errorStatus$.next.should.have.been.calledWith('some error'); })); - it('should not do anything through the newFileName callback if no files loaded', fakeAsync(() => { + it('should set current file to readme', fakeAsync(() => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockCurrentFile = sinon.stub(component, 'setCurrentFile'); - let fileSpy = sinon.spy(component['files'], 'findIndex'); - component['files'] = []; + let readMeFile = new EditorFile('1', '1', 'this is the readme', 'readme'); + let modelFile = new EditorFile('2', '2', 'this is the model', 'model'); + + mockFileService.getEditorFiles.returns([readMeFile, modelFile]); + mockFileService.getCurrentFile.returns(null); component.ngOnInit(); + tick(); - fileSpy.should.not.have.been.called; + mockCurrentFile.should.have.been.calledWith(readMeFile); + + mockUpdatePackage.should.have.been.called; })); - it('should set a new file based on the passed file name through the newFileName callback if files loaded', fakeAsync(() => { + it('should set current file to first one if no readme', fakeAsync(() => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockCurrentFile = sinon.stub(component, 'setCurrentFile'); - component['files'] = [{id: 'random'}, {id: 'new-name'}, {id: 'namespace'}]; - component['currentFile'] = component['files'][0]; - - let fileSpy = sinon.spy(component['files'], 'findIndex'); - - component.ngOnInit(); - tick(); - - fileSpy.should.have.been.called; - })); - - it('should handle error', fakeAsync(() => { - mockClientService.ensureConnected.returns(Promise.reject('some error')); + let modelFile = new EditorFile('2', '2', 'this is the model', 'model'); + let scriptFile = new EditorFile('1', '1', 'this is the script', 'script'); + mockFileService.getEditorFiles.returns([modelFile, scriptFile]); + mockFileService.getCurrentFile.returns(null); component.ngOnInit(); tick(); - mockAlertService.errorStatus$.next.should.have.been.calledWith('some error'); + mockCurrentFile.should.have.been.calledWith(modelFile); + + mockUpdatePackage.should.have.been.called; })); }); @@ -319,7 +301,7 @@ describe('EditorComponent', () => { getVersion: sinon.stub().returns('my version'), }; - mockClientService.getMetaData = sinon.stub().returns(mockMetaData); + mockFileService.getMetaData = sinon.stub().returns(mockMetaData); component.updatePackageInfo(); component['deployedPackageVersion'].should.equal('my version'); @@ -328,85 +310,110 @@ describe('EditorComponent', () => { }); describe('setCurrentFile', () => { + + let editorFilesValidate; + + beforeEach(() => { + editorFilesValidate = sinon.stub(component, 'editorFilesValidate').returns(true); + }); + it('should set current file', () => { - component['currentFile'] = {displayID: 'oldFile', id: 'oldID'}; - let file = {displayID: 'newFile', id: 'newID'}; + component['currentFile'] = new EditorFile('oldID', 'oldFile', 'myContent', 'model'); + let file = new EditorFile('newID', 'newFile', 'myContent', 'model'); component.setCurrentFile(file); component['currentFile'].should.deep.equal(file); + + component['noError'].should.equal(true); }); it('should set current file', () => { - component['currentFile'] = {displayID: 'oldFile', id: 'oldID'}; + component['currentFile'] = new EditorFile('oldID', 'oldFile', 'myContent', 'model'); + component['editingPackage'] = true; + + let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); + + let file = new EditorFile('newID', 'newFile', 'myContent', 'model'); + component.setCurrentFile(file); + component['currentFile'].should.deep.equal(file); + + mockUpdatePackage.should.have.been.called; + component['editingPackage'].should.equal(false); + component['noError'].should.equal(true); + }); + + it('should set current file with an error', () => { + editorFilesValidate.returns(false); + component['currentFile'] = new EditorFile('oldID', 'oldFile', 'myContent', 'model'); component['editingPackage'] = true; let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let file = {displayID: 'myFile', id: 'newID'}; + let file = new EditorFile('newID', 'newFile', 'myContent', 'model'); component.setCurrentFile(file); component['currentFile'].should.deep.equal(file); mockUpdatePackage.should.have.been.called; component['editingPackage'].should.equal(false); + component['noError'].should.equal(false); }); it('should always set current file, if same file selected and is readme file', () => { - component['currentFile'] = {displayID: 'readme', readme: true}; - let serviceSpy = sinon.spy(editorService, 'setCurrentFile'); - let file = {displayID: 'readme', readme: true}; + component['currentFile'] = new EditorFile('readme', 'readme.md', 'myContent', 'readme'); + let file = new EditorFile('readme', 'readme.md', 'myContent', 'readme'); component.setCurrentFile(file); - serviceSpy.should.have.been.called; + mockFileService.setCurrentFile.should.have.been.called; + component['noError'].should.equal(true); }); it('should always set current file, if same file selected and is acl file', () => { - component['currentFile'] = {displayID: 'acl', acl: true}; - let serviceSpy = sinon.spy(editorService, 'setCurrentFile'); - let file = {displayID: 'acl', acl: true}; + component['currentFile'] = new EditorFile('acl', 'acl', 'myContent', 'acl'); + let file = new EditorFile('acl', 'acl', 'myContent', 'acl'); component.setCurrentFile(file); - serviceSpy.should.have.been.called; + mockFileService.setCurrentFile.should.have.been.called; + component['noError'].should.equal(true); }); it('should not set current file, if same file selected', () => { - component['currentFile'] = {displayID: 'myFile'}; + component['currentFile'] = new EditorFile('model', 'model', 'myContent', 'model'); let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let serviceSpy = sinon.spy(editorService, 'setCurrentFile'); - let file = {displayID: 'myFile'}; + let file = new EditorFile('model', 'model', 'myContent', 'model'); component.setCurrentFile(file); - serviceSpy.should.not.have.been.called; + mockFileService.setCurrentFile.should.not.have.been.called; mockUpdatePackage.should.not.have.been.called; }); it('should mark a file as deletable if a script type', () => { - let file = {script: true, displayID: 'myFile'}; + let file = new EditorFile('script', 'script', 'myContent', 'script'); component.setCurrentFile(file); component['deletableFile'].should.equal(true); }); it('should mark a file as deletable if a model type', () => { - let file = {model: true, displayID: 'myFile'}; + let file = new EditorFile('model', 'model', 'myContent', 'model'); component.setCurrentFile(file); component['deletableFile'].should.equal(true); }); it('should mark a file as deletable if a query type', () => { - let file = {query: true, displayID: 'myFile'}; + let file = new EditorFile('query', 'query', 'myContent', 'query'); component.setCurrentFile(file); component['deletableFile'].should.equal(true); }); it('should not mark a file as deletable if a acl type', () => { - let file = {acl: true, displayID: 'myFile'}; + let file = new EditorFile('acl', 'acl', 'myContent', 'acl'); component.setCurrentFile(file); component['deletableFile'].should.equal(false); }); it('should not mark a file as deletable if a readme type', () => { - let file = {readme: true, displayID: 'myFile'}; + let file = new EditorFile('readme', 'readme', 'myContent', 'readme'); component.setCurrentFile(file); component['deletableFile'].should.equal(false); }); @@ -414,88 +421,16 @@ describe('EditorComponent', () => { describe('updateFiles', () => { it('should update the files, and not include system model files', () => { - mockClientService.getModelFiles.returns([ - { - getNamespace: sinon.stub().returns('model 2'), - getName: sinon.stub().returns('models/model2.cto'), - isSystemModelFile: sinon.stub().returns(false) - }, - { - getNamespace: sinon.stub().returns('model 1'), - getName: sinon.stub().returns('models/model1.cto'), - isSystemModelFile: sinon.stub().returns(false) - }, - { - getNamespace: sinon.stub().returns('system 1'), - getName: sinon.stub().returns('models/system1.cto'), - isSystemModelFile: sinon.stub().returns(true) - }, - ]); - - mockClientService.getScripts.returns([ - {getIdentifier: sinon.stub().returns('script 2')}, - {getIdentifier: sinon.stub().returns('script 1')} - ]); - - mockClientService.getAclFile.returns({getIdentifier: sinon.stub().returns('acl')}); - - mockClientService.getMetaData.returns({ - getREADME: sinon.stub().returns('readme') - }); - - mockQueryFile.getIdentifier.returns('query 1'); - + mockFileService.getEditorFiles.returns(['myFile']); component.updateFiles(); - component['files'].length.should.equal(7); - - component['files'][0].should.deep.equal({ - readme: true, - id: 'readme', - displayID: 'README.md', - }); - - component['files'][1].should.deep.equal({ - model: true, - id: 'model 1', - displayID: 'models/model1.cto', - }); - - component['files'][2].should.deep.equal({ - model: true, - id: 'model 2', - displayID: 'models/model2.cto', - }); - - component['files'][3].should.deep.equal({ - script: true, - id: 'script 1', - displayID: 'script 1' - }); - - component['files'][4].should.deep.equal({ - script: true, - id: 'script 2', - displayID: 'script 2' - }); - - component['files'][5].should.deep.equal({ - acl: true, - id: 'acl', - displayID: 'acl', - }); - - component['files'][6].should.deep.equal({ - query: true, - id: 'query 1', - displayID: 'query 1', - }); + component['files'].should.deep.equal(['myFile']); }); }); describe('addModelFile', () => { it('should add a model file', () => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); + let mockEditorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); let mockModelFile0 = sinon.createStubInstance(ModelFile); mockModelFile0.getNamespace.returns('namespace0'); @@ -515,87 +450,94 @@ describe('EditorComponent', () => { mockModelFile.getNamespace.returns('namespace'); mockModelFile.id = 'namespace'; - let modelManagerMock = { - addModelFile: sinon.stub().returns(mockModelFile) - }; + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'model'); - mockClientService.getBusinessNetwork.returns({ - getModelManager: sinon.stub().returns(modelManagerMock) - }); + mockFileService.addFile.returns(file); component.addModelFile(); - modelManagerMock.addModelFile.should.have.been.calledWith(`/** - * New model file - */ + mockFileService.getEditorFiles.should.have.been.called; - namespace namespace3`); - mockUpdateFiles.should.have.been.called; - - mockSetCurrentFile.should.have.been.calledWith({id: 'namespace'}); + mockSetCurrentFile.should.have.been.calledWith(file); component['dirty'].should.equal(true); + mockEditorFilesValidateStub.should.have.been.called; }); it('should add a model file with contents', () => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + let mockEditorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - component['addModelNamespace'] = 'namespace'; component['files'] = [{id: 'random'}, {id: 'namespace0'}]; mockModelFile.getNamespace.returns('namespace0'); - let modelManagerMock = { - addModelFile: sinon.stub().returns(mockModelFile) - }; - mockClientService.getBusinessNetwork.returns({ - getModelManager: sinon.stub().returns(modelManagerMock) - }); + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'model'); - component.addModelFile('my code'); + mockFileService.addFile.returns(file); - modelManagerMock.addModelFile.should.have.been.calledWith('my code'); - mockUpdateFiles.should.have.been.called; + component.addModelFile({namespace: 'namespace', fileName: 'myFile', definitions: 'myCode'}); + + mockFileService.addFile.should.have.been.calledWith('namespace', 'myFile', 'myCode', 'model'); - mockSetCurrentFile.should.have.been.calledWith({id: 'namespace0'}); + mockFileService.getEditorFiles.should.have.been.called; + mockEditorFilesValidateStub.should.have.been.called; + + mockSetCurrentFile.should.have.been.calledWith(file); + component['dirty'].should.equal(true); + }); + + it('should add a model file with contents that doesn\'t validate', () => { + let mockEditorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); + let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); + + component['files'] = [{id: 'random'}, {id: 'namespace0'}]; + + mockModelFile.getNamespace.returns('namespace0'); + + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'model'); + + mockFileService.addFile.returns(file); + + mockFileService.validateFile.returns('error'); + + component.addModelFile({namespace: 'namespace', fileName: 'myFile', definitions: 'myCode'}); + + mockFileService.addFile.should.have.been.calledWith('namespace', 'myFile', 'myCode', 'model'); + + mockFileService.getEditorFiles.should.have.been.called; + mockEditorFilesValidateStub.should.have.been.called; + + mockFileService.updateBusinessNetworkFile.should.not.have.been.called; + + mockSetCurrentFile.should.have.been.calledWith(file); component['dirty'].should.equal(true); }); }); describe('addScriptFile', () => { it('should create and add a script file', () => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + let mockEditorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); mockScriptFile.getIdentifier.returns('script'); mockScriptFile.id = 'script'; component['files'] = [mockScriptFile]; - let scriptManagerMock = { - createScript: sinon.stub().returns(mockScriptFile), - addScript: sinon.stub(), - getScripts: sinon.stub().returns([]), - }; + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'script'); - mockClientService.getBusinessNetwork.returns({ - getScriptManager: sinon.stub().returns(scriptManagerMock) - }); + mockFileService.addFile.returns(file); component.addScriptFile(); - scriptManagerMock.createScript.should.have.been.calledWith('lib/script.js', 'JS', `/** - * New script file - */`); - - scriptManagerMock.addScript.should.have.been.called; - mockUpdateFiles.should.have.been.called; + mockFileService.getEditorFiles.should.have.been.called; + mockEditorFilesValidateStub.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({id: 'script'}); + mockSetCurrentFile.should.have.been.calledWith(file); component['dirty'].should.equal(true); }); it('should create and add a script file with an incremented name', () => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + let mockEditorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); let mockScript0 = sinon.createStubInstance(Script); @@ -618,65 +560,110 @@ describe('EditorComponent', () => { mockScriptFile.getIdentifier.returns('script'); - let scriptManagerMock = { - createScript: sinon.stub().returns(mockScriptFile), - addScript: sinon.stub(), - getScripts: sinon.stub().returns([mockScript0, mockScript1, mockScript2, mockScript3]), - }; + mockFileService.getFile.onFirstCall().returns('myFile'); + mockFileService.getFile.onSecondCall().returns(null); - mockClientService.getBusinessNetwork.returns({ - getScriptManager: sinon.stub().returns(scriptManagerMock) - }); + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'script'); + + mockFileService.addFile.returns(file); component.addScriptFile(); - scriptManagerMock.createScript.should.have.been.calledWith('lib/script2.js', 'JS', `/** - * New script file - */`); + mockFileService.addFile.should.have.been.calledWith('lib/script2.js', 'lib/script2.js', `/** + * New script file + */`, 'script'); - scriptManagerMock.addScript.should.have.been.called; - mockUpdateFiles.should.have.been.called; + mockFileService.getEditorFiles.should.have.been.called; + mockEditorFilesValidateStub.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({id: 'script'}); + mockSetCurrentFile.should.have.been.calledWith(file); component['dirty'].should.equal(true); }); it('should add a script file with content', () => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + let mockEditorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); component['addScriptFileName'] = 'script'; component['files'] = [{id: 'random'}, {id: 'script'}]; - mockScriptFile.getIdentifier.returns('script'); + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'script'); - let scriptManagerMock = { - createScript: sinon.stub().returns(mockScriptFile), - addScript: sinon.stub(), - getScripts: sinon.stub().returns([]), - }; + mockFileService.addFile.returns(file); - mockClientService.getBusinessNetwork.returns({ - getScriptManager: sinon.stub().returns(scriptManagerMock) - }); + component.addScriptFile(mockScriptFile); + + mockFileService.getEditorFiles.should.have.been.called; + mockEditorFilesValidateStub.should.have.been.called; + + mockSetCurrentFile.should.have.been.calledWith(file); + component['dirty'].should.equal(true); + }); + + it('should add a script file with content with increment file name', () => { + let mockEditorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); + let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); + + mockScriptFile.getIdentifier.returns('bob'); + mockScriptFile.contents = `/** + * New script file + */`; + + component['addScriptFileName'] = 'script'; + component['files'] = [{id: 'random'}, {id: 'script'}]; + + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'script'); + + mockFileService.getFile.onFirstCall().returns('myFile'); + mockFileService.getFile.onSecondCall().returns(null); + + mockFileService.addFile.returns(file); component.addScriptFile(mockScriptFile); - scriptManagerMock.createScript.should.not.have.been.called; + mockFileService.addFile.should.have.been.calledWith('2bob', '2bob', `/** + * New script file + */`, 'script'); - scriptManagerMock.addScript.should.have.been.called; - mockUpdateFiles.should.have.been.called; + mockFileService.getEditorFiles.should.have.been.called; + mockEditorFilesValidateStub.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({id: 'script'}); + mockSetCurrentFile.should.have.been.calledWith(file); component['dirty'].should.equal(true); }); + + it('should add a script file with content and not validate', () => { + let mockEditorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); + let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); + + component['addScriptFileName'] = 'script'; + component['files'] = [{id: 'random'}, {id: 'script'}]; + + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'script'); + + mockFileService.addFile.returns(file); + + mockFileService.validateFile.returns('error'); + + component.addScriptFile(mockScriptFile); + + mockFileService.getEditorFiles.should.have.been.called; + mockEditorFilesValidateStub.should.have.been.called; + + mockSetCurrentFile.should.have.been.calledWith(file); + component['dirty'].should.equal(true); + mockFileService.updateBusinessNetworkFile.should.not.have.been.called; + }); }); describe('addReadme', () => { it('should not open confirm modal if no readme present', fakeAsync(() => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - component['files'] = [{id: 'random'}, {id: 'script'}]; + + let files = [new EditorFile('myId', 'myDisplay', 'myContent', 'script')]; + component['files'] = files; + + mockFileService.getEditorFiles.returns(files); let b = new Blob(['/**README File*/'], {type: 'text/plain'}); let mockReadmeFile = new File([b], 'readme.md'); @@ -688,10 +675,12 @@ describe('EditorComponent', () => { })); it('should create readme if no existing readme present', fakeAsync(() => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - component['files'] = [{id: 'zero-index'}, {id: 'script'}]; + let files = [new EditorFile('myId', 'myDisplay', 'myContent', 'script')]; + component['files'] = files; + + mockFileService.getEditorFiles.returns(files); let b = new Blob(['/**README File*/'], {type: 'text/plain'}); let mockReadmeFile = new File([b], 'readme.md'); @@ -700,16 +689,17 @@ describe('EditorComponent', () => { tick(); - mockClientService.setBusinessNetworkReadme.should.have.been.calledWith(mockReadmeFile); - mockUpdateFiles.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({id: 'zero-index'}); + mockFileService.setBusinessNetworkReadme.should.have.been.calledWith(mockReadmeFile); + mockSetCurrentFile.should.have.been.calledWith(files[0]); component['dirty'].should.be.equal(true); })); it('should open confirm modal if readme present and handle error', fakeAsync(() => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - component['files'] = [{readme: true}, {id: 'script'}]; + let files = [new EditorFile('myId', 'myDisplay', 'myContent', 'readme')]; + component['files'] = files; + + mockFileService.getEditorFiles.returns(files); let b = new Blob(['/**README File*/'], {type: 'text/plain'}); let mockReadmeFile = new File([b], 'readme.md'); @@ -724,15 +714,16 @@ describe('EditorComponent', () => { mockModal.open.should.have.been.called; mockAlertService.errorStatus$.next.should.have.been.calledWith('some error'); - mockClientService.setBusinessNetworkReadme.should.not.have.been.called; - mockUpdateFiles.should.not.have.been.called; + mockFileService.setBusinessNetworkReadme.should.not.have.been.called; mockSetCurrentFile.should.not.have.been.called; })); it('should handle confirm modal cancel', fakeAsync(() => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - component['files'] = [{readme: true}, {id: 'script'}]; + let files = [new EditorFile('myId', 'myDisplay', 'myContent', 'readme')]; + component['files'] = files; + + mockFileService.getEditorFiles.returns(files); let b = new Blob(['/**README File*/'], {type: 'text/plain'}); let mockReadmeFile = new File([b], 'readme.md'); @@ -747,16 +738,16 @@ describe('EditorComponent', () => { mockModal.open.should.have.been.called; mockAlertService.errorStatus$.next.should.not.have.been.called; - mockClientService.setBusinessNetworkReadme.should.not.have.been.called; - mockUpdateFiles.should.not.have.been.called; + mockFileService.setBusinessNetworkReadme.should.not.have.been.called; mockSetCurrentFile.should.not.have.been.called; })); it('should create readme on modal confirm', fakeAsync(() => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - component['files'] = [{readme: true}, {id: 'script'}]; + let files = [new EditorFile('myId', 'myDisplay', 'myContent', 'readme')]; + component['files'] = files; + mockFileService.getEditorFiles.returns(files); let b = new Blob(['/**README File*/'], {type: 'text/plain'}); let mockReadmeFile = new File([b], 'readme.md'); @@ -770,9 +761,8 @@ describe('EditorComponent', () => { mockModal.open.should.have.been.called; mockAlertService.errorStatus$.next.should.not.have.been.called; - mockClientService.setBusinessNetworkReadme.should.have.been.called; - mockUpdateFiles.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({readme: true}); + mockFileService.setBusinessNetworkReadme.should.have.been.called; + mockSetCurrentFile.should.have.been.calledWith(files[0]); })); }); @@ -780,7 +770,7 @@ describe('EditorComponent', () => { describe('addRuleFile', () => { it('should not open confirm modal if no ACL file present', fakeAsync(() => { let mockProcessRules = sinon.stub(component, 'processRuleFileAddition'); - component['files'] = [{id: 'random'}, {id: 'script'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'script')]; component.addRuleFile(mockRuleFile); tick(); @@ -790,7 +780,7 @@ describe('EditorComponent', () => { it('should call processRuleFileAddition if no existing rules present', fakeAsync(() => { let mockProcessRules = sinon.stub(component, 'processRuleFileAddition'); - component['files'] = [{id: 'zero-index'}, {id: 'script'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'script')]; component.addRuleFile(mockRuleFile); tick(); @@ -801,7 +791,7 @@ describe('EditorComponent', () => { it('should open confirm modal if rule file present and handle error', fakeAsync(() => { let mockProcessRules = sinon.stub(component, 'processRuleFileAddition'); - component['files'] = [{acl: true}, {id: 'permissions.acl'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'acl')]; mockModal.open = sinon.stub().returns({ componentInstance: {}, @@ -818,7 +808,7 @@ describe('EditorComponent', () => { it('should handle confirm modal cancel', fakeAsync(() => { let mockProcessRules = sinon.stub(component, 'processRuleFileAddition'); - component['files'] = [{acl: true}, {id: 'permissions.acl'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'acl')]; mockModal.open = sinon.stub().returns({ componentInstance: {}, @@ -835,7 +825,7 @@ describe('EditorComponent', () => { it('should call processRuleFileAddition on modal confirm', fakeAsync(() => { let mockProcessRules = sinon.stub(component, 'processRuleFileAddition'); - component['files'] = [{acl: true}, {id: 'permissions.acl'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'acl')]; mockModal.open = sinon.stub().returns({ componentInstance: {}, @@ -854,7 +844,7 @@ describe('EditorComponent', () => { describe('addQueryFile', () => { it('should not open confirm modal if no query file present', fakeAsync(() => { let mockProcessQuery = sinon.stub(component, 'processQueryFileAddition'); - component['files'] = [{id: 'random'}, {id: 'script'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'script')]; component.addQueryFile(mockQueryFile); tick(); @@ -864,7 +854,7 @@ describe('EditorComponent', () => { it('should call processQueryFileAddition if no existing rules present', fakeAsync(() => { let mockProcessQuery = sinon.stub(component, 'processQueryFileAddition'); - component['files'] = [{id: 'zero-index'}, {id: 'script'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'script')]; component.addQueryFile(mockQueryFile); tick(); @@ -875,7 +865,7 @@ describe('EditorComponent', () => { it('should open confirm modal if query file present and handle error', fakeAsync(() => { let mockProcessQuery = sinon.stub(component, 'processQueryFileAddition'); - component['files'] = [{query: true}, {id: 'queries.qry'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'query')]; mockModal.open = sinon.stub().returns({ componentInstance: {}, @@ -892,7 +882,7 @@ describe('EditorComponent', () => { it('should handle confirm modal cancel', fakeAsync(() => { let mockProcessQuery = sinon.stub(component, 'processQueryFileAddition'); - component['files'] = [{query: true}, {id: 'queries.qry'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'query')]; mockModal.open = sinon.stub().returns({ componentInstance: {}, @@ -909,7 +899,7 @@ describe('EditorComponent', () => { it('should call processQueryFileAddition on modal confirm', fakeAsync(() => { let mockProcessQuery = sinon.stub(component, 'processQueryFileAddition'); - component['files'] = [{query: true}, {id: 'queries.qry'}]; + component['files'] = [new EditorFile('myId', 'myDisplay', 'myContent', 'query')]; mockModal.open = sinon.stub().returns({ componentInstance: {}, @@ -928,95 +918,90 @@ describe('EditorComponent', () => { describe('processRuleFileAddition', () => { it('should set the aclFile as that passed in', () => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + let editorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - let mockFindIndex = sinon.stub(component, 'findFileIndex'); - mockFindIndex.returns(7); + mockFileService.getEditorFiles.returns(['myFile']); - let aclManagerMock = { - setAclFile: sinon.stub() - }; + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'acl'); - mockClientService.getBusinessNetwork.returns({ - getAclManager: sinon.stub().returns(aclManagerMock) - }); + mockFileService.addFile.returns(file); + mockFileService.validateFile.returns(null); component.processRuleFileAddition(mockRuleFile); - aclManagerMock.setAclFile.should.have.been.calledWith(mockRuleFile); + mockFileService.updateBusinessNetwork.should.have.been.calledWith('myId', file); + component['dirty'].should.equal(true); + editorFilesValidateStub.should.have.been.called; + component['files'].should.deep.equal(['myFile']); }); - it('should call updateFiles, setCurrentFile and set editor dirty', () => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + it('should not update business network if not valid', () => { + let editorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); + mockFileService.getEditorFiles.returns(['myFile']); let mockFindIndex = sinon.stub(component, 'findFileIndex'); - mockFindIndex.returns(0); - component['files'] = [{acl: true}]; + mockFindIndex.returns(7); - let aclManagerMock = { - setAclFile: sinon.stub() - }; + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'acl'); - mockClientService.getBusinessNetwork.returns({ - getAclManager: sinon.stub().returns(aclManagerMock) - }); + mockFileService.addFile.returns(file); + mockFileService.validateFile.returns('error'); component.processRuleFileAddition(mockRuleFile); - mockUpdateFiles.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({acl: true}); - component['dirty'].should.be.equal(true); + mockFileService.updateBusinessNetwork.should.not.have.been.called; + component['dirty'].should.equal(true); + editorFilesValidateStub.should.have.been.called; + component['files'].should.deep.equal(['myFile']); }); }); describe('processQueryFileAddition', () => { it('should set the queryFile as that passed in', () => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + let editorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - let mockFindIndex = sinon.stub(component, 'findFileIndex'); - mockFindIndex.returns(7); + mockFileService.getEditorFiles.returns(['myFile']); - let queryManagerMock = { - setQueryFile: sinon.stub() - }; + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'query'); - mockClientService.getBusinessNetwork.returns({ - getQueryManager: sinon.stub().returns(queryManagerMock) - }); + mockFileService.addFile.returns(file); + mockFileService.validateFile.returns(null); component.processQueryFileAddition(mockQueryFile); - queryManagerMock.setQueryFile.should.have.been.calledWith(mockQueryFile); + mockFileService.updateBusinessNetwork.should.have.been.calledWith('myId', file); + component['dirty'].should.equal(true); + editorFilesValidateStub.should.have.been.called; + component['files'].should.deep.equal(['myFile']); }); - it('should call updateFiles, setCurrentFile and set editor dirty', () => { - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + it('should not update business network if not valid', () => { + let editorFilesValidateStub = sinon.stub(component, 'editorFilesValidate'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); + mockFileService.getEditorFiles.returns(['myFile']); let mockFindIndex = sinon.stub(component, 'findFileIndex'); - mockFindIndex.returns(0); - component['files'] = [{query: true}]; + mockFindIndex.returns(7); - let queryManagerMock = { - setQueryFile: sinon.stub() - }; + let file = new EditorFile('myId', 'myDisplayID', 'myContent', 'query'); - mockClientService.getBusinessNetwork.returns({ - getQueryManager: sinon.stub().returns(queryManagerMock) - }); + mockFileService.addFile.returns(file); + mockFileService.validateFile.returns('error'); component.processQueryFileAddition(mockQueryFile); - mockUpdateFiles.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({query: true}); - component['dirty'].should.be.equal(true); + mockFileService.updateBusinessNetwork.should.not.have.been.called; + component['dirty'].should.equal(true); + editorFilesValidateStub.should.have.been.called; + component['files'].should.deep.equal(['myFile']); }); }); describe('openImportModal', () => { it('should open the import modal', fakeAsync(() => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); + + mockFileService.loadFiles.returns([]); let finishedImport = new BehaviorSubject(true); @@ -1034,15 +1019,16 @@ describe('EditorComponent', () => { tick(); mockUpdatePackage.should.have.been.called; - mockUpdateFiles.should.have.been.called; + mockFileService.loadFiles.should.have.been.called; })); it('should open the import modal and set file to readme', fakeAsync(() => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - component['files'] = [{readme: true}, {model: true}]; + let file = new EditorFile('myId', 'myDisplay', 'myContent', 'readme'); + + mockFileService.loadFiles.returns([file]); let finishedImport = new BehaviorSubject(true); @@ -1060,17 +1046,18 @@ describe('EditorComponent', () => { tick(); mockUpdatePackage.should.have.been.called; - mockUpdateFiles.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({readme: true}); + mockFileService.loadFiles.should.have.been.called; + mockSetCurrentFile.should.have.been.calledWith(file); mockAlertService.successStatus$.next.should.have.been.called; })); it('should open the import modal and set file to first one if no readme', fakeAsync(() => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); - component['files'] = [{model: true}, {script: true}]; + let file = new EditorFile('myId', 'myDisplay', 'myContent', 'model'); + + mockFileService.loadFiles.returns([file, new EditorFile('myId', 'myDisplay', 'myContent', 'script')]); let finishedImport = new BehaviorSubject(true); @@ -1088,14 +1075,13 @@ describe('EditorComponent', () => { tick(); mockUpdatePackage.should.have.been.called; - mockUpdateFiles.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({model: true}); + mockFileService.loadFiles.should.have.been.called; + mockSetCurrentFile.should.have.been.calledWith(file); mockAlertService.successStatus$.next.should.have.been.called; })); it('should open the import modal and handle error', fakeAsync(() => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let finishedImport = new BehaviorSubject(true); @@ -1114,14 +1100,13 @@ describe('EditorComponent', () => { tick(); mockUpdatePackage.should.not.have.been.called; - mockUpdateFiles.should.not.have.been.called; + mockFileService.loadFiles.should.not.have.been.called; drawerItem.close.should.have.been.called; mockAlertService.errorStatus$.next.should.have.been.calledWith('some error'); })); it('should open the import modal and handle cancel', fakeAsync(() => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let finishedImport = new BehaviorSubject(true); @@ -1140,7 +1125,7 @@ describe('EditorComponent', () => { tick(); mockUpdatePackage.should.not.have.been.called; - mockUpdateFiles.should.not.have.been.called; + mockFileService.loadFiles.should.not.have.been.called; drawerItem.close.should.have.been.called; mockAlertService.errorStatus$.next.should.not.have.been.called; })); @@ -1159,11 +1144,11 @@ describe('EditorComponent', () => { let testFile = new Blob(['test'], {type: 'application/octet-stream'}); let testFilename: string = 'my_business_name.bna'; - mockClientService.getBusinessNetwork.returns({ + mockFileService.getBusinessNetwork.returns({ toArchive: sinon.stub().returns(Promise.resolve('my_data')) }); - mockClientService.getBusinessNetworkName.returns('my_business_name'); + mockFileService.getBusinessNetworkName.returns('my_business_name'); component.exportBNA(); @@ -1184,11 +1169,11 @@ describe('EditorComponent', () => { let mockFile = sinon.stub(window, 'Blob'); mockFile.returns(new Blob(['test'], {type: 'application/octet-stream'})); - mockClientService.getBusinessNetwork.returns({ + mockFileService.getBusinessNetwork.returns({ toArchive: sinon.stub().returns(Promise.resolve('my_data')) }); - mockClientService.getBusinessNetworkName.returns('my_business_name'); + mockFileService.getBusinessNetworkName.returns('my_business_name'); component.exportBNA(); @@ -1215,7 +1200,7 @@ describe('EditorComponent', () => { result: Promise.resolve(mockModelFile) }); - mockClientService.businessNetworkChanged$ = { + mockFileService.businessNetworkChanged$ = { next: sinon.stub() }; @@ -1248,7 +1233,7 @@ describe('EditorComponent', () => { tick(); mockAddModel.should.have.been.called; - mockClientService.businessNetworkChanged$.next.should.have.been.calledWith(true); + mockFileService.businessNetworkChanged$.next.should.have.been.calledWith(true); })); it('should open AddFileComponent modal and call addScriptFile if script returned', fakeAsync(() => { @@ -1264,7 +1249,7 @@ describe('EditorComponent', () => { tick(); mockAddScript.should.have.been.called; - mockClientService.businessNetworkChanged$.next.should.have.been.calledWith(true); + mockFileService.businessNetworkChanged$.next.should.have.been.calledWith(true); })); it('should open AddFileComponent modal and call addreadme if README returned', fakeAsync(() => { @@ -1280,7 +1265,7 @@ describe('EditorComponent', () => { tick(); mockAddReadme.should.have.been.called; - mockClientService.businessNetworkChanged$.next.should.have.been.calledWith(true); + mockFileService.businessNetworkChanged$.next.should.have.been.calledWith(true); })); it('should open AddFileComponent modal and call addRuleFile if acl file returned', fakeAsync(() => { @@ -1296,7 +1281,7 @@ describe('EditorComponent', () => { tick(); mockAddRule.should.have.been.called; - mockClientService.businessNetworkChanged$.next.should.have.been.calledWith(true); + mockFileService.businessNetworkChanged$.next.should.have.been.calledWith(true); })); it('should open AddFileComponent modal and call addQueryFile if query file returned', fakeAsync(() => { @@ -1312,7 +1297,7 @@ describe('EditorComponent', () => { tick(); mockAddQuery.should.have.been.called; - mockClientService.businessNetworkChanged$.next.should.have.been.calledWith(true); + mockFileService.businessNetworkChanged$.next.should.have.been.calledWith(true); })); it('should do nothing if no result', fakeAsync(() => { @@ -1332,7 +1317,7 @@ describe('EditorComponent', () => { mockAddScript.should.not.have.been.called; mockAddModel.should.not.have.been.called; - mockClientService.businessNetworkChanged$.next.should.not.have.been.called; + mockFileService.businessNetworkChanged$.next.should.not.have.been.called; })); it('should open add file modal and handle error', fakeAsync(() => { @@ -1348,7 +1333,7 @@ describe('EditorComponent', () => { tick(); mockAddModel.should.not.have.been.called; - mockClientService.businessNetworkChanged$.next.should.not.have.been.called; + mockFileService.businessNetworkChanged$.next.should.not.have.been.called; mockAlertService.errorStatus$.next.should.have.been.calledWith('some error'); })); @@ -1363,7 +1348,7 @@ describe('EditorComponent', () => { tick(); mockAddModel.should.not.have.been.called; - mockClientService.businessNetworkChanged$.next.should.not.have.been.called; + mockFileService.businessNetworkChanged$.next.should.not.have.been.called; mockAlertService.errorStatus$.next.should.not.have.been.called; })); @@ -1463,7 +1448,7 @@ describe('EditorComponent', () => { }); it('should toggle editing', () => { - component['currentFile'] = {model : true}; + component['currentFile'] = new EditorFile('1', '1', 'this is the model', 'model'); component['editActive'] = false; component.toggleEditActive(); @@ -1476,8 +1461,10 @@ describe('EditorComponent', () => { component['editingPackage'] = false; component['deployedPackageVersion'] = '1.0.0'; + mockFileService.getMetaData.returns({name: 'package'}); + // Specify README file - let file = {readme: true, id: 'readme', displayID: 'README.md'}; + let file = new EditorFile('readme', 'README.md', 'this is the readme', 'readme'); component.setCurrentFile(file); fixture.detectChanges(); @@ -1495,7 +1482,6 @@ describe('EditorComponent', () => { element = fixture.debugElement.query(By.css('.business-network-details')).nativeElement; element.innerHTML.should.not.contain('id="editFileButton"'); element.textContent.should.contain('Editing package.json'); - }); }); @@ -1514,7 +1500,7 @@ describe('EditorComponent', () => { describe('fileType', () => { it('should identify model file via parameters', () => { - let testItem = {model: true, displayID: 'test_name'}; + let testItem = new EditorFile('1', '1', 'this is the model', 'model'); let result = component['fileType'](testItem); @@ -1522,7 +1508,7 @@ describe('EditorComponent', () => { }); it('should identify script file via parameters', () => { - let testItem = {script: true, displayID: 'test_name'}; + let testItem = new EditorFile('1', '1', 'this is the script', 'script'); let result = component['fileType'](testItem); @@ -1530,7 +1516,7 @@ describe('EditorComponent', () => { }); it('should identify ACL file via parameters', () => { - let testItem = {acl: true, displayID: 'test_name'}; + let testItem = new EditorFile('1', '1', 'this is the acl', 'acl'); let result = component['fileType'](testItem); @@ -1538,7 +1524,7 @@ describe('EditorComponent', () => { }); it('should identify Query file via parameters', () => { - let testItem = {query: true, displayID: 'test_name'}; + let testItem = new EditorFile('1', '1', 'this is the query', 'query'); let result = component['fileType'](testItem); @@ -1546,7 +1532,7 @@ describe('EditorComponent', () => { }); it('should identify unknown file via parameters as README', () => { - let testItem = {displayID: 'test_name'}; + let testItem = new EditorFile('1', '1', 'this is the octopus', 'octopus'); let result = component['fileType'](testItem); @@ -1557,63 +1543,63 @@ describe('EditorComponent', () => { describe('editorFilesValidate', () => { beforeEach(() => { - mockClientService.validateFile.returns(null); - mockClientService.getModelFile.returns({getDefinitions: sinon.stub().returns({})}); - mockClientService.getScriptFile.returns({getContents: sinon.stub().returns({})}); - mockClientService.getAclFile.returns({getDefinitions: sinon.stub().returns({})}); - mockClientService.getQueryFile.returns({getDefinitions: sinon.stub().returns({})}); + mockFileService.validateFile.returns(null); + mockFileService.getModelFile.returns({getDefinitions: sinon.stub().returns({})}); + mockFileService.getScriptFile.returns({getContents: sinon.stub().returns({})}); + mockFileService.getAclFile.returns({getDefinitions: sinon.stub().returns({})}); + mockFileService.getQueryFile.returns({getDefinitions: sinon.stub().returns({})}); }); it('should validate model files', () => { let fileArray = []; - fileArray.push({model: true, displayID: 'test_name'}); + fileArray.push(new EditorFile('myId', 'myDisplayID', 'myContent', 'model')); component['files'] = fileArray; let result = component['editorFilesValidate'](); - mockClientService.getModelFile.should.have.been.called; + mockFileService.validateFile.should.have.been.calledWith('myId', 'model'); result.should.equal(true); }); it('should validate script files', () => { let fileArray = []; - fileArray.push({script: true, displayID: 'test_name'}); + fileArray.push(new EditorFile('myId', 'myDisplayID', 'myContent', 'script')); component['files'] = fileArray; let result = component['editorFilesValidate'](); - mockClientService.getScriptFile.should.have.been.called; + mockFileService.validateFile.should.have.been.calledWith('myId', 'script'); result.should.equal(true); }); it('should validate acl files', () => { let fileArray = []; - fileArray.push({acl: true, displayID: 'test_name'}); + fileArray.push(new EditorFile('myId', 'myDisplayID', 'myContent', 'acl')); component['files'] = fileArray; let result = component['editorFilesValidate'](); - mockClientService.getAclFile.should.have.been.called; + mockFileService.validateFile.should.have.been.calledWith('myId', 'acl'); result.should.equal(true); }); it('should validate query files', () => { let fileArray = []; - fileArray.push({query: true, displayID: 'test_name'}); + fileArray.push(new EditorFile('myId', 'myDisplayID', 'myContent', 'query')); component['files'] = fileArray; let result = component['editorFilesValidate'](); - mockClientService.getQueryFile.should.have.been.called; + mockFileService.validateFile.should.have.been.calledWith('myId', 'query'); result.should.equal(true); }); it('should fail validation for invalid model files', () => { let fileArray = []; - fileArray.push({model: true, displayID: 'test_name'}); + fileArray.push(new EditorFile('myId', 'myDisplayID', 'myContent', 'model')); component['files'] = fileArray; - mockClientService.validateFile.returns('error'); + mockFileService.validateFile.returns('error'); let result = component['editorFilesValidate'](); result.should.equal(false); @@ -1621,10 +1607,10 @@ describe('EditorComponent', () => { it('should fail validation for invalid acl files', () => { let fileArray = []; - fileArray.push({acl: true, displayID: 'test_name'}); + fileArray.push(new EditorFile('myId', 'myDisplayID', 'myContent', 'acl')); component['files'] = fileArray; - mockClientService.validateFile.returns('error'); + mockFileService.validateFile.returns('error'); let result = component['editorFilesValidate'](); result.should.equal(false); @@ -1632,10 +1618,10 @@ describe('EditorComponent', () => { it('should fail validation for invalid script files', () => { let fileArray = []; - fileArray.push({script: true, displayID: 'test_name'}); + fileArray.push(new EditorFile('myId', 'myDisplayID', 'myContent', 'script')); component['files'] = fileArray; - mockClientService.validateFile.returns('error'); + mockFileService.validateFile.returns('error'); let result = component['editorFilesValidate'](); result.should.equal(false); @@ -1643,10 +1629,10 @@ describe('EditorComponent', () => { it('should fail validation for invalid query files', () => { let fileArray = []; - fileArray.push({query: true, displayID: 'test_name'}); + fileArray.push(new EditorFile('myId', 'myDisplayID', 'myContent', 'query')); component['files'] = fileArray; - mockClientService.validateFile.returns('error'); + mockFileService.validateFile.returns('error'); let result = component['editorFilesValidate'](); result.should.equal(false); @@ -1654,11 +1640,11 @@ describe('EditorComponent', () => { it('should fail validation for multiple invalid files', () => { let fileArray = []; - fileArray.push({script: true, displayID: 'test_name'}); - fileArray.push({acl: true, displayID: 'test_name'}); + fileArray.push(new EditorFile('myIdScript', 'myDisplayIDScript', 'myContent', 'script')); + fileArray.push(new EditorFile('myIdAcl', 'myDisplayIDAcl', 'myContent', 'acl')); component['files'] = fileArray; - mockClientService.validateFile.returns('error'); + mockFileService.validateFile.returns('error'); let result = component['editorFilesValidate'](); result.should.equal(false); @@ -1696,24 +1682,24 @@ describe('EditorComponent', () => { deleteQueryFile: sinon.stub() }; - mockClientService.getBusinessNetwork.returns({ + mockFileService.getBusinessNetwork.returns({ getScriptManager: sinon.stub().returns(scriptManagerMock), getModelManager: sinon.stub().returns(modelManagerMock), getQueryManager: sinon.stub().returns(queryManagerMock) }); - mockClientService.businessNetworkChanged$ = { + mockFileService.businessNetworkChanged$ = { next: sinon.stub() }; // Create file array of length 6 let fileArray = []; - fileArray.push({acl: true, id: 'acl file', displayID: 'acl0'}); - fileArray.push({script: true, id: 'script 0', displayID: 'script0'}); - fileArray.push({script: true, id: 'script 1', displayID: 'script1'}); - fileArray.push({model: true, id: 'model 1', displayID: 'model1'}); - fileArray.push({script: true, id: 'script 2', displayID: 'script2'}); - fileArray.push({query: true, id: 'query file', displayID: 'query0'}); + fileArray.push(new EditorFile('acl', 'myDisplayAcl', 'myContent', 'acl')); + fileArray.push(new EditorFile('script0', 'myDisplayIDScript0', 'myContent', 'script')); + fileArray.push(new EditorFile('script1', 'myDisplayIDScript1', 'myContent', 'script')); + fileArray.push(new EditorFile('model', 'myDisplayIDModel', 'myContent', 'model')); + fileArray.push(new EditorFile('script2', 'myDisplayIDScript2', 'myContent', 'script')); + fileArray.push(new EditorFile('query', 'myDisplayID', 'myContent', 'query')); component['files'] = fileArray; }); @@ -1743,7 +1729,7 @@ describe('EditorComponent', () => { tick(); mockAlertService.errorStatus$.next.should.have.been.called; - mockClientService.businessNetworkChanged$.next.should.not.have.been.called; + mockFileService.businessNetworkChanged$.next.should.not.have.been.called; })); it('should open delete-confirm modal and handle cancel', fakeAsync(() => { @@ -1772,34 +1758,26 @@ describe('EditorComponent', () => { tick(); mockAlertService.successStatus$.next.should.not.have.been.called; - mockClientService.businessNetworkChanged$.next.should.not.have.been.called; + mockFileService.businessNetworkChanged$.next.should.not.have.been.called; })); it('should delete the correct script file', fakeAsync(() => { - component['currentFile'] = component['files'][2]; let mockSetIntialFile = sinon.stub(component, 'setInitialFile'); + mockFileService.getEditorFiles.returns(['myFile']); + component.openDeleteFileModal(); tick(); - // Check initial file set - mockSetIntialFile.should.have.been.called; - // Check services called - mockClientService.businessNetworkChanged$.next.should.have.been.called; + mockFileService.businessNetworkChanged$.next.should.have.been.called; mockAlertService.successStatus$.next.should.have.been.called; - // check remaining files - let currentFiles = component['files']; - // should have only deleted one - currentFiles.length.should.equal(5); - // should have deleted the correct one - let index = currentFiles.findIndex((x) => { - x.displayID === 'script1'; - }); - index.should.equal(-1); + // Check initial file set + mockSetIntialFile.should.have.been.called; + component['files'].should.deep.equal(['myFile']); })); it('should delete the correct model file', fakeAsync(() => { @@ -1807,6 +1785,8 @@ describe('EditorComponent', () => { component['currentFile'] = component['files'][3]; let mockSetIntialFile = sinon.stub(component, 'setInitialFile'); + mockFileService.getEditorFiles.returns(['myFile']); + component.openDeleteFileModal(); tick(); @@ -1814,24 +1794,17 @@ describe('EditorComponent', () => { mockSetIntialFile.should.have.been.called; // Check services called - mockClientService.businessNetworkChanged$.next.should.have.been.called; + mockFileService.businessNetworkChanged$.next.should.have.been.calledWith(true); mockAlertService.successStatus$.next.should.have.been.called; - // check remaining files - let currentFiles = component['files']; - // should have only deleted one - currentFiles.length.should.equal(5); - // should have deleted the correct one - let index = currentFiles.findIndex((x) => { - x.displayID === 'model1'; - }); - index.should.equal(-1); + component['files'].should.deep.equal(['myFile']); })); it('should delete the query file', fakeAsync(() => { component['currentFile'] = component['files'][5]; let mockSetIntialFile = sinon.stub(component, 'setInitialFile'); + mockFileService.getEditorFiles.returns(['myFile']); component.openDeleteFileModal(); tick(); @@ -1840,18 +1813,10 @@ describe('EditorComponent', () => { mockSetIntialFile.should.have.been.called; // Check services called - mockClientService.businessNetworkChanged$.next.should.have.been.called; + mockFileService.businessNetworkChanged$.next.should.have.been.calledWith(true); mockAlertService.successStatus$.next.should.have.been.called; - // Check remaining files - let currentFiles = component['files']; - // Should have only deleted one - currentFiles.length.should.equal(5); - // Should have deleted the correct one - let index = currentFiles.findIndex((x) => { - x.displayID === 'query0'; - }); - index.should.equal(-1); + component['files'].should.deep.equal(['myFile']); })); it('should only enable deletion of model or script files', fakeAsync(() => { @@ -1863,7 +1828,7 @@ describe('EditorComponent', () => { tick(); // Check services called - mockClientService.businessNetworkChanged$.next.should.not.have.been.called; + mockFileService.businessNetworkChanged$.next.should.not.have.been.called; mockAlertService.errorStatus$.next.should.have.been.called; // check no files removed @@ -1879,12 +1844,12 @@ describe('EditorComponent', () => { tick(); let currentFile = component['currentFile']; - currentFile.displayID.should.equal('acl0'); + currentFile.displayID.should.equal('myDisplayIDScript1'); })); it('should disable the deploy button if remaining files are invalid', fakeAsync(() => { - validateMock.returns(false); + let mockSetIntialFile = sinon.stub(component, 'setInitialFile'); component['currentFile'] = component['files'][3]; @@ -1892,18 +1857,8 @@ describe('EditorComponent', () => { tick(); // Check services called - mockClientService.businessNetworkChanged$.next.should.have.been.calledWith(false); + mockFileService.businessNetworkChanged$.next.should.have.been.calledWith(false); mockAlertService.successStatus$.next.should.have.been.called; - - // check we still deleted the file - let currentFiles = component['files']; - // should have only deleted one - currentFiles.length.should.equal(5); - // should have deleted the correct one - let index = currentFiles.findIndex((x) => { - x.displayID === 'model1'; - }); - index.should.equal(-1); })); }); @@ -1931,7 +1886,7 @@ describe('EditorComponent', () => { it('should prevent edit of acl file', () => { // Attempt edit of ACL component['inputFileNameArray'] = ['', 'permissions', '.acl']; - component['currentFile'] = {acl: true}; + component['currentFile'] = new EditorFile('1', '1', 'this is the acl', 'acl'); component['editFileName'](); component['fileNameError'].should.be.equal('Error: Unable to process rename on current file type'); @@ -1940,7 +1895,7 @@ describe('EditorComponent', () => { it('should prevent edit of readme file', () => { // Attempt edit of README component['inputFileNameArray'] = ['', 'README', '.md']; - component['currentFile'] = {readme: true}; + component['currentFile'] = new EditorFile('1', '1', 'this is the readme', 'readme'); component['editFileName'](); component['fileNameError'].should.be.equal('Error: Unable to process rename on current file type'); @@ -1963,6 +1918,7 @@ describe('EditorComponent', () => { // Attempt edit of script component['inputFileNameArray'] = ['', 'myScriptFile', '.js']; component['currentFile'] = {script: true, id: 'myScriptFile.js'}; + component['currentFile'] = new EditorFile('myScriptFile.js', 'myScriptFile.js', 'this is the script', 'script'); component['files'] = [{id: 'muchRandom'}, {id: 'myScriptFile.js'}, @@ -1974,7 +1930,7 @@ describe('EditorComponent', () => { it('should not rename model file if name unchanged', () => { // Attempt edit of model component['inputFileNameArray'] = ['', 'myModelFile', '.cto']; - component['currentFile'] = {model: true, displayID: 'myModelFile.cto'}; + component['currentFile'] = new EditorFile('myModelFile.cto', 'myModelFile.cto', 'this is the model', 'model'); component['files'] = [{displayID: 'muchRandom'}, {displayID: 'myModelFile.cto'}, @@ -1984,69 +1940,62 @@ describe('EditorComponent', () => { }); it('should enable script file rename by replacing script', () => { - // Should call: - // - this.clientService.replaceFile(this.currentFile.id, inputFileName, contents, 'script'); - // - this.updateFiles(); - // - this.setCurrentFile(this.files[index]); - // Should set: - // - this.dirty = true; - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); let mockFindIndex = sinon.stub(component, 'findFileIndex'); mockFindIndex.onCall(0).returns(-1); mockFindIndex.onCall(1).returns(2); - mockClientService.getScriptFile.returns({ + mockFileService.getScriptFile.returns({ getContents: sinon.stub().returns('my script content') }); component['inputFileNameArray'] = ['', 'myNewScriptFile', '.js']; - component['currentFile'] = {script: true, id: 'myCurrentScriptFile.js'}; + component['currentFile'] = new EditorFile('myCurrentScriptFile.js', 'myCurrentScriptFile.js', 'my script content', 'script'); component['files'] = [{id: 'muchRandom'}, {id: 'myCurrentScriptFile.js'}, {id: 'otherScriptFile.js'}, {id: 'oldNameID'}]; + let file = new EditorFile('myId', 'myDisplay', 'myContent', 'script'); + + mockFileService.replaceFile.returns(file); + // Call Method component['editFileName'](); - mockClientService.replaceFile.should.have.been.calledWith('myCurrentScriptFile.js', 'myNewScriptFile.js', 'my script content', 'script'); - mockUpdateFiles.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({id: 'otherScriptFile.js'}); + mockFileService.replaceFile.should.have.been.calledWith('myCurrentScriptFile.js', 'myNewScriptFile.js', 'my script content', 'script'); + mockFileService.getEditorFiles.should.have.been.called; + mockSetCurrentFile.should.have.been.calledWith(file); component['dirty'].should.be.equal(true); }); it('should enable model file rename by editing filename', () => { - // Should call: - // - this.clientService.replaceFile(this.currentFile.id, inputFileName, contents, 'script'); - // - this.updateFiles(); - // - this.setCurrentFile(this.files[index]); - // Should set: - // - this.dirty = true; - let mockUpdateFiles = sinon.stub(component, 'updateFiles'); let mockSetCurrentFile = sinon.stub(component, 'setCurrentFile'); let mockFindIndex = sinon.stub(component, 'findFileIndex'); mockFindIndex.onCall(0).returns(-1); mockFindIndex.onCall(1).returns(2); - mockClientService.getModelFile.returns({ + mockFileService.getModelFile.returns({ getDefinitions: sinon.stub().returns('My ModelFile content') }); component['inputFileNameArray'] = ['', 'myNewModelFile', '.cto']; - component['currentFile'] = {model: true, id: 'myCurrentModelFile.cto'}; + component['currentFile'] = new EditorFile('myCurrentFile.cto', 'myCurrentFile.cto', 'My ModelFile content', 'model'); component['files'] = [{id: 'muchRandom'}, {displayID: 'myCurrentModelFile.cto'}, {displayID: 'otherModelFile.cto'}, {id: 'oldNameID'}]; + let file = new EditorFile('myId', 'myDisplay', 'myContent', 'model'); + mockFileService.replaceFile.returns(file); + // Call Method component['editFileName'](); - mockClientService.replaceFile.should.have.been.calledWith('myCurrentModelFile.cto', 'myNewModelFile.cto', 'My ModelFile content', 'model'); - mockUpdateFiles.should.have.been.called; - mockSetCurrentFile.should.have.been.calledWith({displayID: 'otherModelFile.cto'}); + mockFileService.replaceFile.should.have.been.calledWith('myCurrentFile.cto', 'myNewModelFile.cto', 'My ModelFile content', 'model'); + mockFileService.getEditorFiles.should.have.been.called; + mockSetCurrentFile.should.have.been.calledWith(file); component['dirty'].should.be.equal(true); }); @@ -2118,57 +2067,48 @@ describe('EditorComponent', () => { describe('preventNameEdit', () => { it('should prevent name edit of acl', () => { - let resource = { - acl: true - }; + let testFile = new EditorFile('1', '1', 'this is the acl', 'acl'); - let response = component.preventNameEdit(resource); + let response = component.preventNameEdit(testFile); response.should.be.true; }); it('should prevent name edit of query', () => { - let resource = { - query: true - }; + let testFile = new EditorFile('1', '1', 'this is the query', 'query'); - let response = component.preventNameEdit(resource); + let response = component.preventNameEdit(testFile); response.should.be.true; }); it('should permit name edit of unknown', () => { - let resource = { - wombat: true - }; + let testFile = new EditorFile('1', '1', 'this is the octopus', 'octopus'); - let response = component.preventNameEdit(resource); + let response = component.preventNameEdit(testFile); response.should.be.false; }); it('should permit name edit of model', () => { - let resource = { - model: true - }; + let testFile = new EditorFile('1', '1', 'this is the model', 'model'); - let response = component.preventNameEdit(resource); + let response = component.preventNameEdit(testFile); response.should.be.false; }); it('should permit name edit of script', () => { - let resource = { - script: true - }; + let testFile = new EditorFile('1', '1', 'this is the script', 'script'); - let response = component.preventNameEdit(resource); + let response = component.preventNameEdit(testFile); response.should.be.false; }); }); describe('setReadmePreview', () => { + it('should set the read me', () => { component.setReadmePreview(true); component['previewReadme'].should.equal(true); diff --git a/packages/composer-playground/src/app/editor/editor.component.ts b/packages/composer-playground/src/app/editor/editor.component.ts index f8e8c22e7d..017c43cc5a 100644 --- a/packages/composer-playground/src/app/editor/editor.component.ts +++ b/packages/composer-playground/src/app/editor/editor.component.ts @@ -10,14 +10,14 @@ import { DrawerService } from '../common/drawer/drawer.service'; import { AdminService } from '../services/admin.service'; import { ClientService } from '../services/client.service'; import { AlertService } from '../basic-modals/alert.service'; -import { EditorService } from './editor.service'; +import { FileService } from '../services/file.service'; +import { EditorFile } from '../services/editor-file'; import { ModelFile, Script, ScriptManager, ModelManager, - AclManager, AclFile, QueryFile, QueryManager @@ -43,6 +43,7 @@ export class EditorComponent implements OnInit, OnDestroy { private addModelNamespace: string = 'models/org.acme.model'; private addScriptFileName: string = 'lib/script'; private addScriptFileExtension: string = '.js'; + private addModelFileExtension: string = '.cto'; private noError: boolean = true; private dirty: boolean = false; @@ -66,15 +67,14 @@ export class EditorComponent implements OnInit, OnDestroy { private clientService: ClientService, private modalService: NgbModal, private alertService: AlertService, - private editorService: EditorService, - private drawerService: DrawerService) { - + private drawerService: DrawerService, + private fileService: FileService) { } ngOnInit(): Promise { return this.clientService.ensureConnected() .then(() => { - this.clientService.businessNetworkChanged$.takeWhile(() => this.alive) + this.fileService.businessNetworkChanged$.takeWhile(() => this.alive) .subscribe((noError) => { if (this.editorFilesValidate() && noError) { this.noError = noError; @@ -84,7 +84,7 @@ export class EditorComponent implements OnInit, OnDestroy { } }); - this.clientService.namespaceChanged$.takeWhile(() => this.alive) + this.fileService.namespaceChanged$.takeWhile(() => this.alive) .subscribe((newName) => { if (this.currentFile !== null) { this.updateFiles(); @@ -93,11 +93,16 @@ export class EditorComponent implements OnInit, OnDestroy { } }); + if (this.fileService.getEditorFiles().length === 0) { + this.files = this.fileService.loadFiles(); + } else { + this.files = this.fileService.getEditorFiles(); + } + this.updatePackageInfo(); - this.updateFiles(); - if (this.editorService.getCurrentFile() !== null) { - this.setCurrentFile(this.editorService.getCurrentFile()); + if (this.fileService.getCurrentFile() !== null) { + this.setCurrentFile(this.fileService.getCurrentFile()); } else { this.setInitialFile(); } @@ -112,7 +117,7 @@ export class EditorComponent implements OnInit, OnDestroy { } updatePackageInfo() { - let metaData = this.clientService.getMetaData(); + let metaData = this.fileService.getMetaData(); this.deployedPackageVersion = metaData.getVersion(); // Set Version this.inputPackageVersion = metaData.getVersion(); } @@ -120,7 +125,7 @@ export class EditorComponent implements OnInit, OnDestroy { setInitialFile() { if (this.files.length) { let initialFile = this.files.find((file) => { - return file.readme; + return file.isReadMe(); }); if (!initialFile) { initialFile = this.files[0]; @@ -131,14 +136,14 @@ export class EditorComponent implements OnInit, OnDestroy { setCurrentFile(file) { this.listItem = 'editorFileList' + this.findFileIndex(true, file.id); - let always = (this.currentFile === null || file.readme || file.acl || file.query); + let always = (this.currentFile === null || file.isPackage() || file.isReadMe() || file.isAcl() || file.isQuery()); let conditional = (always || this.currentFile.id !== file.id || this.currentFile.displayID !== file.displayID); if (always || conditional) { if (this.editingPackage) { this.updatePackageInfo(); this.editingPackage = false; } - if (file.script || file.model || file.query) { + if (file.isScript() || file.isModel() || file.isQuery()) { this.deletableFile = true; } else { this.deletableFile = false; @@ -146,7 +151,7 @@ export class EditorComponent implements OnInit, OnDestroy { // Reset editActive this.editActive = false; // Set selected file - this.editorService.setCurrentFile(file); + this.fileService.setCurrentFile(file); this.currentFile = file; // Update inputFileName @@ -155,6 +160,8 @@ export class EditorComponent implements OnInit, OnDestroy { // re-validate, since we do not persist bad files- they revert when navigated away if (this.editorFilesValidate()) { this.noError = true; + } else { + this.noError = false; } // remove fileError flag @@ -173,141 +180,100 @@ export class EditorComponent implements OnInit, OnDestroy { } updateFiles() { - let newFiles = []; - // deal with model files - let modelFiles = this.clientService.getModelFiles(); - let newModelFiles = []; - modelFiles.forEach((modelFile) => { - // ignore system model files - if (!modelFile.isSystemModelFile()) { - newModelFiles.push({ - model: true, - id: modelFile.getNamespace(), - displayID: modelFile.getName(), - }); - } - }); - newModelFiles.sort((a, b) => { - return a.displayID.localeCompare(b.displayID); - }); - newFiles.push.apply(newFiles, newModelFiles); - - // deal with script files - let scriptFiles = this.clientService.getScripts(); - let newScriptFiles = []; - scriptFiles.forEach((scriptFile) => { - newScriptFiles.push({ - script: true, - id: scriptFile.getIdentifier(), - displayID: scriptFile.getIdentifier() - }); - }); - newScriptFiles.sort((a, b) => { - return a.displayID.localeCompare(b.displayID); - }); - newFiles.push.apply(newFiles, newScriptFiles); - - // deal with acl file - let aclFile = this.clientService.getAclFile(); - if (aclFile) { - newFiles.push({ - acl: true, - id: aclFile.getIdentifier(), - displayID: aclFile.getIdentifier() - }); - } - - // deal with query - let queryFile = this.clientService.getQueryFile(); - if (queryFile) { - newFiles.push({ - query: true, - id: queryFile.getIdentifier(), - displayID: queryFile.getIdentifier() - }); - } - - // deal with readme - let readme = this.clientService.getMetaData().getREADME(); - if (readme) { - // add it first so it appears at the top of the list - newFiles.unshift({ - readme: true, - id: 'readme', - displayID: 'README.md' - }); - } - this.files = newFiles; + this.files = this.fileService.getEditorFiles(); } addModelFile(contents = null) { - let businessNetworkDefinition = this.clientService.getBusinessNetwork(); - let modelManager = businessNetworkDefinition.getModelManager(); let code; + let modelName; + let newModelNamespace; if (!contents) { - let newModelNamespace = this.addModelNamespace; + newModelNamespace = this.addModelNamespace; let increment = 0; while (this.findFileIndex(true, newModelNamespace) !== -1) { newModelNamespace = this.addModelNamespace + increment; increment++; } + modelName = newModelNamespace + this.addModelFileExtension; code = `/** - * New model file - */ + * New model file + */ - namespace ${newModelNamespace}`; + namespace ${newModelNamespace}`; } else { - code = contents; + newModelNamespace = contents.namespace; + modelName = contents.fileName; + code = contents.definitions; } - let newFile = modelManager.addModelFile(code); - this.updateFiles(); - let index = this.findFileIndex(true, newFile.getNamespace()); - this.setCurrentFile(this.files[index]); - this.dirty = true; + let newFile = this.fileService.addFile(newModelNamespace, modelName, code, 'model'); + this.setCurrentFile(newFile); + try { + let error = this.fileService.validateFile(newFile.getId(), newFile.getType()); + if (!error) { + this.fileService.updateBusinessNetwork(newFile.getId(), newFile); + } + } finally { + this.files = this.fileService.getEditorFiles(); + this.dirty = true; + this.editorFilesValidate(); + } } - addScriptFile(scriptFile = null) { - let businessNetworkDefinition = this.clientService.getBusinessNetwork(); - let scriptManager = businessNetworkDefinition.getScriptManager(); - let existingScripts = scriptManager.getScripts(); + addScriptFile(content = null) { + let scriptName; let code; - let script; - if (!scriptFile) { - let increment = 0; - let scriptName = this.addScriptFileName + this.addScriptFileExtension; - while (existingScripts.findIndex((file) => file.getIdentifier() === scriptName) !== -1) { + if (!content) { + scriptName = this.addScriptFileName + this.addScriptFileExtension; + let increment = 1; + while (typeof this.fileService.getFile(scriptName, 'script') !== 'undefined') { scriptName = this.addScriptFileName + increment + this.addScriptFileExtension; increment++; } code = `/** - * New script file - */`; - script = scriptManager.createScript(scriptName, 'JS', code); + * New script file + */`; + } else { - script = scriptFile; + scriptName = content.getIdentifier(); + let increment = 1; + while (typeof this.fileService.getFile(scriptName, 'script') !== 'undefined') { + let fileName = content.getIdentifier(); + let breakPoint = fileName.lastIndexOf('.'); + scriptName = fileName.substring(0, breakPoint) + increment + fileName.substring(breakPoint, fileName.length); + increment++; + } + code = content.contents; } - scriptManager.addScript(script); - this.updateFiles(); - let index = this.findFileIndex(true, script.getIdentifier()); - this.setCurrentFile(this.files[index]); - this.dirty = true; + let newFile = this.fileService.addFile(scriptName, scriptName, code, 'script'); + this.setCurrentFile(newFile); + try { + let error = this.fileService.validateFile(newFile.getId(), newFile.getType()); + if (!error) { + this.fileService.updateBusinessNetwork(newFile.getId(), newFile); + } + } finally { + this.files = this.fileService.getEditorFiles(); + this.dirty = true; + this.editorFilesValidate(); + } } addQueryFile(query) { - if (this.files.findIndex((file) => file.query === true) !== -1) { + if (this.files.findIndex((file) => file.isQuery() === true) !== -1) { const confirmModalRef = this.modalService.open(ReplaceComponent); confirmModalRef.componentInstance.mainMessage = 'Your current Query file will be replaced with the new one that you are uploading.'; confirmModalRef.componentInstance.supplementaryMessage = 'Please ensure that you have saved a copy of your Query file to disc.'; confirmModalRef.componentInstance.resource = 'file'; confirmModalRef.result.then((result) => { + this.fileService.deleteFile(null, 'query'); this.processQueryFileAddition(query); }, (reason) => { if (reason && reason !== 1) { @@ -320,24 +286,29 @@ export class EditorComponent implements OnInit, OnDestroy { } processQueryFileAddition(query) { - let businessNetworkDefinition = this.clientService.getBusinessNetwork(); - let queryManager: QueryManager = businessNetworkDefinition.getQueryManager(); - queryManager.setQueryFile(query); - this.updateFiles(); - let index = this.findFileIndex(true, query.getIdentifier()); - this.setCurrentFile(this.files[index]); - this.dirty = true; + let newFile = this.fileService.addFile(query.getIdentifier(), query.getIdentifier(), query.getDefinitions(), 'query'); + this.setCurrentFile(newFile); + try { + let error = this.fileService.validateFile(newFile.getId(), newFile.getType()); + if (!error) { + this.fileService.updateBusinessNetwork(newFile.getId(), newFile); + } + } finally { + this.files = this.fileService.getEditorFiles(); + this.dirty = true; + this.editorFilesValidate(); + } } addReadme(readme) { - if (this.files[0].readme) { + if (this.files[0].isReadMe()) { const confirmModalRef = this.modalService.open(ReplaceComponent); confirmModalRef.componentInstance.mainMessage = 'Your current README file will be replaced with the new one that you are uploading.'; confirmModalRef.componentInstance.supplementaryMessage = 'Please ensure that you have saved a copy of your README file to disc.'; confirmModalRef.componentInstance.resource = 'file'; confirmModalRef.result.then((result) => { - this.clientService.setBusinessNetworkReadme(readme); - this.updateFiles(); + this.fileService.setBusinessNetworkReadme(readme); + this.files = this.fileService.getEditorFiles(); this.setCurrentFile(this.files[0]); this.dirty = true; }, (reason) => { @@ -346,20 +317,21 @@ export class EditorComponent implements OnInit, OnDestroy { } }); } else { - this.clientService.setBusinessNetworkReadme(readme); - this.updateFiles(); + this.fileService.setBusinessNetworkReadme(readme); + this.files = this.fileService.getEditorFiles(); this.setCurrentFile(this.files[0]); this.dirty = true; } } addRuleFile(rules) { - if (this.files.findIndex((file) => file.acl === true) !== -1) { + if (this.files.findIndex((file) => file.isAcl() === true) !== -1) { const confirmModalRef = this.modalService.open(ReplaceComponent); confirmModalRef.componentInstance.mainMessage = 'Your current ACL file will be replaced with the new one that you are uploading.'; confirmModalRef.componentInstance.supplementaryMessage = 'Please ensure that you have saved a copy of your ACL file to disc.'; confirmModalRef.componentInstance.resource = 'file'; confirmModalRef.result.then((result) => { + this.fileService.deleteFile(null, 'acl'); this.processRuleFileAddition(rules); }, (reason) => { if (reason && reason !== 1) { @@ -373,24 +345,29 @@ export class EditorComponent implements OnInit, OnDestroy { } processRuleFileAddition(rules) { - let businessNetworkDefinition = this.clientService.getBusinessNetwork(); - let aclManager: AclManager = businessNetworkDefinition.getAclManager(); - aclManager.setAclFile(rules); - this.updateFiles(); - let index = this.findFileIndex(true, rules.getIdentifier()); - this.setCurrentFile(this.files[index]); - this.dirty = true; + let newFile = this.fileService.addFile(rules.getIdentifier(), rules.getIdentifier(), rules.getDefinitions(), 'acl'); + this.setCurrentFile(newFile); + try { + let error = this.fileService.validateFile(newFile.getId(), newFile.getType()); + if (!error) { + this.fileService.updateBusinessNetwork(newFile.getId(), newFile); + } + } finally { + this.files = this.fileService.getEditorFiles(); + this.dirty = true; + this.editorFilesValidate(); + } } openImportModal() { const importModalRef = this.drawerService.open(UpdateComponent); importModalRef.componentInstance.finishedSampleImport.subscribe((result) => { if (result.deployed) { + this.files = this.fileService.loadFiles(); this.updatePackageInfo(); - this.updateFiles(); if (this.files.length) { let currentFile = this.files.find((file) => { - return file.readme; + return file.isReadMe(); }); if (!currentFile) { currentFile = this.files[0]; @@ -412,10 +389,10 @@ export class EditorComponent implements OnInit, OnDestroy { } exportBNA() { - return this.clientService.getBusinessNetwork().toArchive().then((exportedData) => { + return this.fileService.getBusinessNetwork().toArchive().then((exportedData) => { let file = new Blob([exportedData], {type: 'application/octet-stream'}); - saveAs(file, this.clientService.getBusinessNetworkName() + '.bna'); + saveAs(file, this.fileService.getBusinessNetworkName() + '.bna'); }); } @@ -439,7 +416,7 @@ export class EditorComponent implements OnInit, OnDestroy { } else { this.addReadme(result); } - this.clientService.businessNetworkChanged$.next(true); + this.fileService.businessNetworkChanged$.next(true); } catch (error) { this.alertService.errorStatus$.next(error); } @@ -455,7 +432,7 @@ export class EditorComponent implements OnInit, OnDestroy { // Gets the definition for the currently deployed business network this.alertService.busyStatus$.next({ title: 'Updating updated business network', - text: 'updating ' + this.clientService.getBusinessNetworkName() + text: 'updating ' + this.fileService.getBusinessNetworkName() }); return Promise.resolve() .then(() => { @@ -463,12 +440,12 @@ export class EditorComponent implements OnInit, OnDestroy { return; } this.deploying = true; - return this.adminService.update(this.clientService.getBusinessNetwork()); + return this.adminService.update(this.fileService.getBusinessNetwork()); }) .then(() => { this.dirty = false; this.deploying = false; - return this.clientService.refresh(this.clientService.getBusinessNetworkName()); + return this.clientService.refresh(this.fileService.getBusinessNetworkName()); }) .then(() => { this.updatePackageInfo(); @@ -506,11 +483,8 @@ export class EditorComponent implements OnInit, OnDestroy { toggleEditActive() { this.editActive = !this.editActive; if (this.editActive && this.fileType(this.currentFile) === 'Readme') { - this.setCurrentFile({ - package: true, - id: 'package', - displayID: 'package.json' - }); + let mockPackageFile = new EditorFile('package', 'package.json', JSON.stringify(this.fileService.getMetaData().packageJson), 'package'); + this.setCurrentFile(mockPackageFile); this.hideEdit(); } } @@ -525,25 +499,29 @@ export class EditorComponent implements OnInit, OnDestroy { let inputFileName = this.inputFileNameArray[0] + this.inputFileNameArray[1] + this.inputFileNameArray[2]; if ((this.findFileIndex(false, inputFileName) !== -1) && (this.currentFile.displayID !== inputFileName)) { this.fileNameError = 'Error: Filename already exists'; - } else if (this.currentFile.script) { + } else if (this.currentFile.isScript()) { if (this.currentFile.id !== inputFileName) { // Replace Script - let contents = this.clientService.getScriptFile(this.currentFile.id).getContents(); - this.clientService.replaceFile(this.currentFile.id, inputFileName, contents, 'script'); - this.updateFiles(); - let index = this.findFileIndex(true, inputFileName); - this.setCurrentFile(this.files[index]); + let contents = this.fileService.getScriptFile(this.currentFile.id).getContents(); + this.fileService.replaceBusinessNetworkFile(this.currentFile.id, inputFileName, contents, 'script'); + let newFile = this.fileService.replaceFile(this.currentFile.id, inputFileName, contents, 'script'); // file service uses its own saved contents so can rename an invalid file + this.files = this.fileService.getEditorFiles(); + this.setCurrentFile(newFile); this.dirty = true; + } else { + this.editActive = false; } - } else if (this.currentFile.model) { + } else if (this.currentFile.isModel()) { if (this.currentFile.displayID !== inputFileName) { // Update Model filename - let contents = this.clientService.getModelFile(this.currentFile.id).getDefinitions(); - this.clientService.replaceFile(this.currentFile.id, inputFileName, contents, 'model'); - this.updateFiles(); - let index = this.findFileIndex(false, inputFileName); - this.setCurrentFile(this.files[index]); + let contents = this.fileService.getModelFile(this.currentFile.id).getDefinitions(); + this.fileService.replaceBusinessNetworkFile(this.currentFile.id, inputFileName, contents, 'model'); + let newFile = this.fileService.replaceFile(this.currentFile.id, inputFileName, contents, 'model'); // file service uses its own saved contents so it can use an invalid file, needs the last known good contents though so can get namespace if its can't from own contents + this.files = this.fileService.getEditorFiles(); + this.setCurrentFile(newFile); this.dirty = true; + } else { + this.editActive = false; } } else { this.fileNameError = 'Error: Unable to process rename on current file type'; @@ -573,34 +551,36 @@ export class EditorComponent implements OnInit, OnDestroy { if (result) { this.alertService.busyStatus$.next({ title: 'Deleting file within business network', - text: 'deleting ' + this.clientService.getBusinessNetworkName() + text: 'deleting ' + this.fileService.getBusinessNetworkName() }); - if (deleteFile.script) { - let scriptManager: ScriptManager = this.clientService.getBusinessNetwork().getScriptManager(); + if (deleteFile.isScript()) { + let scriptManager: ScriptManager = this.fileService.getBusinessNetwork().getScriptManager(); scriptManager.deleteScript(deleteFile.id); - } else if (deleteFile.model) { - let modelManager: ModelManager = this.clientService.getBusinessNetwork().getModelManager(); + this.fileService.deleteFile(deleteFile.id, 'script'); + } else if (deleteFile.isModel()) { + let modelManager: ModelManager = this.fileService.getBusinessNetwork().getModelManager(); modelManager.deleteModelFile(deleteFile.id); - } else if (deleteFile.query) { - let queryManager: QueryManager = this.clientService.getBusinessNetwork().getQueryManager(); + this.fileService.deleteFile(deleteFile.id, 'model'); + } else if (deleteFile.isQuery()) { + let queryManager: QueryManager = this.fileService.getBusinessNetwork().getQueryManager(); queryManager.deleteQueryFile(); + this.fileService.deleteFile(deleteFile.id, 'query'); } else { throw new Error('Unable to process delete on selected file type'); } // remove file from list view - let index = this.findFileIndex(false, deleteFile.displayID); - this.files.splice(index, 1); + this.files = this.fileService.getEditorFiles(); // Make sure we set a file to remove the deleted file from the view this.setInitialFile(); // validate the remaining (acl/cto files and conditionally enable deploy if (this.editorFilesValidate()) { - this.clientService.businessNetworkChanged$.next(true); + this.fileService.businessNetworkChanged$.next(true); } else { - this.clientService.businessNetworkChanged$.next(false); + this.fileService.businessNetworkChanged$.next(false); } // Send alert @@ -623,22 +603,22 @@ export class EditorComponent implements OnInit, OnDestroy { }); } - fileType(resource: any): string { - if (resource.model) { + fileType(resource: EditorFile): string { + if (resource.isModel()) { return 'Model'; - } else if (resource.script) { + } else if (resource.isScript()) { return 'Script'; - } else if (resource.acl) { + } else if (resource.isAcl()) { return 'ACL'; - } else if (resource.query) { + } else if (resource.isQuery()) { return 'Query'; } else { return 'Readme'; } } - preventNameEdit(resource: any): boolean { - if (resource.acl || resource.query) { + preventNameEdit(resource: EditorFile): boolean { + if (resource.isAcl() || resource.isQuery()) { return true; } else { return false; @@ -655,35 +635,31 @@ export class EditorComponent implements OnInit, OnDestroy { editorFilesValidate(): boolean { let allValid: boolean = true; - for (let file of this.files) { - if (file.model) { - let modelFile = this.clientService.getModelFile(file.id); - if (this.clientService.validateFile(file.id, modelFile.getDefinitions(), 'model') !== null) { + if (file.isModel()) { + if (this.fileService.validateFile(file.id, 'model') !== null) { allValid = false; file.invalid = true; } else { file.invalid = false; } - } else if (file.acl) { - let aclFile = this.clientService.getAclFile(); - if (this.clientService.validateFile(file.id, aclFile.getDefinitions(), 'acl') !== null) { + + } else if (file.isAcl()) { + if (this.fileService.validateFile(file.id, 'acl') !== null) { allValid = false; file.invalid = true; } else { file.invalid = false; } - } else if (file.script) { - let script = this.clientService.getScriptFile(file.id); - if (this.clientService.validateFile(file.id, script.getContents(), 'script') !== null) { + } else if (file.isScript()) { + if (this.fileService.validateFile(file.id, 'script') !== null) { allValid = false; file.invalid = true; } else { file.invalid = false; } - } else if (file.query) { - let query = this.clientService.getQueryFile(); - if (this.clientService.validateFile(file.id, query.getDefinitions(), 'query') !== null) { + } else if (file.isQuery()) { + if (this.fileService.validateFile(file.id, 'query') !== null) { allValid = false; file.invalid = true; } else { @@ -691,6 +667,10 @@ export class EditorComponent implements OnInit, OnDestroy { } } } + + if (this.fileService.validateFile('package', 'package') !== null) { + allValid = false; + } return allValid; } } diff --git a/packages/composer-playground/src/app/editor/editor.module.ts b/packages/composer-playground/src/app/editor/editor.module.ts index 6a145ea05f..42ff21de1f 100644 --- a/packages/composer-playground/src/app/editor/editor.module.ts +++ b/packages/composer-playground/src/app/editor/editor.module.ts @@ -18,8 +18,7 @@ import { FooterModule } from '../footer/footer.module'; @NgModule({ imports: [CommonModule, FormsModule, NgbModule, PerfectScrollbarModule, CodemirrorModule, DirectivesModule, FileImporterModule, ImportModule, EditorRoutingModule, FooterModule], entryComponents: [AddFileComponent], - declarations: [EditorComponent, EditorFileComponent, AddFileComponent], - providers: [EditorService] + declarations: [EditorComponent, EditorFileComponent, AddFileComponent] }) export class EditorModule { diff --git a/packages/composer-playground/src/app/editor/editor.service.spec.ts b/packages/composer-playground/src/app/editor/editor.service.spec.ts deleted file mode 100644 index 8e16b7d4b5..0000000000 --- a/packages/composer-playground/src/app/editor/editor.service.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { TestBed, inject } from '@angular/core/testing'; -import { EditorService } from './editor.service'; -import * as chai from 'chai'; -let assert = chai.assert; - -describe('Editor Service', () => { - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [ - EditorService - ] - }); - }); - it('should set and get the current file for the editor', inject([EditorService], (service: EditorService) => { - let TEST = {name: 'foo', val: 'bar'}; - assert.isNull(service.getCurrentFile()); - service.setCurrentFile(TEST); - assert.equal(service.getCurrentFile(), TEST); - })); -}); diff --git a/packages/composer-playground/src/app/editor/editor.service.ts b/packages/composer-playground/src/app/editor/editor.service.ts deleted file mode 100644 index 13166875e0..0000000000 --- a/packages/composer-playground/src/app/editor/editor.service.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Injectable } from '@angular/core'; - -@Injectable() -export class EditorService { - - private currentFile: any = null; - - getCurrentFile(): any { - return this.currentFile; - } - - setCurrentFile(cf: any) { - this.currentFile = cf; - } -} 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 49f95f3a71..e698251620 100644 --- a/packages/composer-playground/src/app/identity/identity.component.spec.ts +++ b/packages/composer-playground/src/app/identity/identity.component.spec.ts @@ -70,7 +70,7 @@ describe(`IdentityComponent`, () => { })); mockClientService.getBusinessNetworkConnection.returns(mockBusinessNetworkConnection); - mockClientService.getMetaData.returns({ + mockClientService.getBusinessNetwork.returns({ getName: sinon.stub().returns('name') }); @@ -111,7 +111,7 @@ describe(`IdentityComponent`, () => { describe('load all identities', () => { it('should load the identities', fakeAsync(() => { - mockClientService.getMetaData.returns({getName: sinon.stub().returns('myNetwork')}); + mockClientService.getBusinessNetwork.returns({getName: sinon.stub().returns('myNetwork')}); let myIdentityMock = sinon.stub(component, 'loadMyIdentities'); mockIdentityCardService.getCurrentIdentityCard.returns({ diff --git a/packages/composer-playground/src/app/identity/identity.component.ts b/packages/composer-playground/src/app/identity/identity.component.ts index 2e6519e8a9..7e21730386 100644 --- a/packages/composer-playground/src/app/identity/identity.component.ts +++ b/packages/composer-playground/src/app/identity/identity.component.ts @@ -42,7 +42,7 @@ export class IdentityComponent implements OnInit { this.loadMyIdentities(); return this.clientService.ensureConnected() .then(() => { - this.businessNetworkName = this.clientService.getMetaData().getName(); + this.businessNetworkName = this.clientService.getBusinessNetwork().getName(); return this.clientService.getBusinessNetworkConnection().getIdentityRegistry(); }).then((registry) => { return registry.getAll(); diff --git a/packages/composer-playground/src/app/import/update.component.spec.ts b/packages/composer-playground/src/app/import/update.component.spec.ts index 32db4c9b11..ed1ba445d4 100644 --- a/packages/composer-playground/src/app/import/update.component.spec.ts +++ b/packages/composer-playground/src/app/import/update.component.spec.ts @@ -16,7 +16,13 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AlertService } from '../basic-modals/alert.service'; import { UpdateComponent } from './update.component'; import { ActiveDrawer } from '../common/drawer'; -import { ModelManager, BusinessNetworkDefinition, AssetDeclaration, ParticipantDeclaration, TransactionDeclaration } from 'composer-common'; +import { + ModelManager, + BusinessNetworkDefinition, + AssetDeclaration, + ParticipantDeclaration, + TransactionDeclaration +} from 'composer-common'; import * as sinon from 'sinon'; import * as chai from 'chai'; @@ -161,7 +167,7 @@ describe('UpdateComponent', () => { describe('onShow', () => { it('should get the list of sample networks', fakeAsync(() => { - mockClientService.getBusinessNetworkName.returns('my-network'); + mockClientService.getBusinessNetwork.returns({getName: sinon.stub().returns('my-network')}); let selectNetworkStub = sinon.stub(component, 'selectNetwork'); let addEmptyNetworkOption = sinon.stub(component, 'addEmptyNetworkOption').returns([{name: 'empty'}, {name: 'modelOne'}, {name: 'modelTwo'}]); mockBusinessNetworkService.getSampleList.returns(Promise.resolve([{name: 'modelTwo'}, {name: 'modelOne'}])); @@ -178,6 +184,7 @@ describe('UpdateComponent', () => { })); it('should handle error', fakeAsync(() => { + mockClientService.getBusinessNetwork.returns({getName: sinon.stub().returns('my-network')}); mockBusinessNetworkService.getSampleList.returns(Promise.reject({message: 'some error'})); component.onShow(); @@ -222,22 +229,22 @@ describe('UpdateComponent', () => { describe('selectNetwork', () => { it('should select the network', fakeAsync(() => { - let mockUpdateBusinessNetworkNameAndDesc = sinon.stub(component, 'updateBusinessNetworkNameAndDesc'); - mockModelManager.getParticipantDeclarations.returns([mockParticipantDeclaration]); - mockModelManager.getTransactionDeclarations.returns([mockTransactionDeclaration]); - mockModelManager.getAssetDeclarations.returns([mockAssetDeclaration]); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - mockBusinessNetworkService.getChosenSample.returns(Promise.resolve(mockBusinessNetworkDefinition)); - component.selectNetwork('bob'); - - tick(); - - component['chosenNetwork']; - component['currentBusinessNetwork'].should.deep.equal(mockBusinessNetworkDefinition); - component['currentBusinessNetwork']['participants'].should.deep.equal([mockParticipantDeclaration]); - component['currentBusinessNetwork']['transactions'].should.deep.equal([mockTransactionDeclaration]); - component['currentBusinessNetwork']['assets'].should.deep.equal([mockAssetDeclaration]); - mockUpdateBusinessNetworkNameAndDesc.should.have.been.calledWith('bob'); + let mockUpdateBusinessNetworkNameAndDesc = sinon.stub(component, 'updateBusinessNetworkNameAndDesc'); + mockModelManager.getParticipantDeclarations.returns([mockParticipantDeclaration]); + mockModelManager.getTransactionDeclarations.returns([mockTransactionDeclaration]); + mockModelManager.getAssetDeclarations.returns([mockAssetDeclaration]); + mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); + mockBusinessNetworkService.getChosenSample.returns(Promise.resolve(mockBusinessNetworkDefinition)); + component.selectNetwork('bob'); + + tick(); + + component['chosenNetwork']; + component['currentBusinessNetwork'].should.deep.equal(mockBusinessNetworkDefinition); + component['currentBusinessNetwork']['participants'].should.deep.equal([mockParticipantDeclaration]); + component['currentBusinessNetwork']['transactions'].should.deep.equal([mockTransactionDeclaration]); + component['currentBusinessNetwork']['assets'].should.deep.equal([mockAssetDeclaration]); + mockUpdateBusinessNetworkNameAndDesc.should.have.been.calledWith('bob'); })); it('should select the empty network', () => { diff --git a/packages/composer-playground/src/app/import/update.component.ts b/packages/composer-playground/src/app/import/update.component.ts index 28f9c26031..8b24e16049 100644 --- a/packages/composer-playground/src/app/import/update.component.ts +++ b/packages/composer-playground/src/app/import/update.component.ts @@ -24,7 +24,7 @@ export class UpdateComponent extends ImportComponent { } onShow(): Promise { - this.networkName = this.clientService.getBusinessNetworkName(); + this.networkName = this.clientService.getBusinessNetwork().getName(); return super.onShow(); } 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 05fa055937..c67f43739a 100644 --- a/packages/composer-playground/src/app/services/client.service.spec.ts +++ b/packages/composer-playground/src/app/services/client.service.spec.ts @@ -13,8 +13,17 @@ let expect = chai.expect; import { AdminService } from './admin.service'; import { AlertService } from '../basic-modals/alert.service'; -import { BusinessNetworkDefinition, ModelFile, Script, AclFile, QueryFile, ConnectionProfileStore, Util } from 'composer-common'; +import { + BusinessNetworkDefinition, + ModelFile, + Script, + AclFile, + QueryFile, + ConnectionProfileStore, + Util +} from 'composer-common'; import { BusinessNetworkConnection } from 'composer-client'; +import { FileService } from './file.service'; import { IdentityService } from './identity.service'; import { IdentityCardService } from './identity-card.service'; import { LocalStorageService } from 'angular-2-local-storage'; @@ -75,23 +84,6 @@ describe('ClientService', () => { sandbox.restore(); }); - describe('createAclFile', () => { - let mockBusinessNetwork; - - beforeEach(() => { - mockBusinessNetwork = sinon.createStubInstance(BusinessNetworkDefinition); - }); - - it('should create an ACL file', fakeAsync(inject([ClientService], (service: ClientService) => { - service['currentBusinessNetwork'] = mockBusinessNetwork; - let allRule = 'rule SystemACL {description: "System ACL to permit all access" participant: "org.hyperledger.composer.system.Participant" operation: ALL resource: "org.hyperledger.composer.system.**" action: ALLOW}'; - let aclFile = service.createAclFile('permissions', allRule); - aclFile.should.be.instanceOf(AclFile); - mockBusinessNetwork.getModelManager.should.have.been.called; - }))); - - }); - describe('createBusinessNetwork', () => { it('should pass through and call createNewBusinessDefinition from common', inject([ClientService], (service: ClientService) => { let name = 'myname'; @@ -140,743 +132,6 @@ describe('ClientService', () => { })); }); - describe('getModelFile', () => { - it('should get the model file', inject([ClientService], (service: ClientService) => { - let modelManagerMock = { - getModelFile: sinon.stub().returns(modelFileMock) - }; - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - let result = service.getModelFile('testId'); - - result.should.deep.equal(modelFileMock); - modelManagerMock.getModelFile.should.have.been.calledWith('testId'); - })); - }); - - describe('getModelFiles', () => { - it('should get model files', inject([ClientService], (service: ClientService) => { - let modelManagerMock = { - getModelFiles: sinon.stub().returns([modelFileMock, modelFileMock]) - }; - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - let result = service.getModelFiles(); - - result.length.should.equal(2); - result[0].should.deep.equal(modelFileMock); - result[1].should.deep.equal(modelFileMock); - })); - }); - - describe('updateFile', () => { - let mockBusinessNetwork; - let businessNetworkChangedSpy; - let modelManagerMock; - let namespaceChangedSpy; - let mockNamespaceCollide; - - beforeEach(inject([ClientService], (service: ClientService) => { - mockBusinessNetwork = sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - mockNamespaceCollide = sinon.stub(service, 'modelNamespaceCollides').returns(false); - businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); - namespaceChangedSpy = sinon.spy(service.namespaceChanged$, 'next'); - - modelManagerMock = { - addModelFile: sinon.stub(), - updateModelFile: sinon.stub(), - deleteModelFile: sinon.stub(), - getModelFile: sinon.stub().returns(modelFileMock), - }; - })); - - it('should update a model file if id matches namespace', inject([ClientService], (service: ClientService) => { - modelFileMock.getNamespace.returns('model-ns'); - modelFileMock.getName.returns('model.cto'); - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - - let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); - - let result = service.updateFile('model-ns', 'my-model-content', 'model'); - - modelManagerMock.updateModelFile.should.have.been.calledWith(modelFileMock); - modelManagerMock.addModelFile.should.not.have.been.called; - should.not.exist(result); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should replace a model file if id does not match namespace', inject([ClientService], (service: ClientService) => { - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - modelFileMock.getNamespace.returns('model-ns'); - modelFileMock.getName.returns('model.cto'); - - let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); - - let result = service.updateFile('diff-model-ns', 'my-model-content', 'model'); - - modelManagerMock.addModelFile.should.have.been.calledWith(modelFileMock); - should.not.exist(result); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should notify if model file namespace changes', inject([ClientService], (service: ClientService) => { - - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - modelFileMock.getNamespace.returns('new-model-ns'); - modelManagerMock.getModelFile.returns(modelFileMock); - - let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); - - service.updateFile('model-ns', 'my-model-content', 'model'); - - namespaceChangedSpy.should.have.been.calledWith('new-model-ns'); - })); - - it('should update a script file', inject([ClientService], (service: ClientService) => { - let scriptManagerMock = { - createScript: sinon.stub().returns(scriptFileMock), - addScript: sinon.stub() - }; - - businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); - - let result = service.updateFile('script', 'my-script', 'script'); - - scriptManagerMock.createScript.should.have.been.calledWith('script', 'JS', 'my-script'); - scriptManagerMock.addScript.should.have.been.calledWith(scriptFileMock); - should.not.exist(result); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should update a acl file', inject([ClientService], (service: ClientService) => { - let aclManagerMock = { - setAclFile: sinon.stub() - }; - - businessNetworkDefMock.getAclManager.returns(aclManagerMock); - - let mockCreateAclFile = sinon.stub(service, 'createAclFile').returns(aclFileMock); - - let result = service.updateFile('acl', 'my-acl', 'acl'); - - aclManagerMock.setAclFile.should.have.been.calledWith(aclFileMock); - should.not.exist(result); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should update a query file', inject([ClientService], (service: ClientService) => { - let queryManagerMock = { - setQueryFile: sinon.stub() - }; - - businessNetworkDefMock.getQueryManager.returns(queryManagerMock); - - let mockCreateQueryFile = sinon.stub(service, 'createQueryFile').returns(queryFileMock); - - // call function - let result = service.updateFile('query', 'my-query', 'query'); - - queryManagerMock.setQueryFile.should.have.been.calledWith(queryFileMock); - should.not.exist(result); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should update a package.json file', inject([ClientService], (service: ClientService) => { - let mockSetPackage = sinon.stub(service, 'setBusinessNetworkPackageJson'); - let packageJson = JSON.stringify({ name: 'my name' }); - - // call function - let result = service.updateFile('package', packageJson, 'package'); - - mockSetPackage.should.have.been.calledWith(JSON.parse(packageJson)); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should fail to update a package.json file due to JSON error', inject([ClientService], (service: ClientService) => { - let mockSetPackage = sinon.stub(service, 'setBusinessNetworkPackageJson'); - let packageJson = JSON.stringify({ name: 'my name' }); - - // call function - let result = service.updateFile('package', packageJson.substring(0, 10), 'package'); - - result.should.equal('SyntaxError: Unexpected end of JSON input'); - businessNetworkChangedSpy.should.have.been.calledWith(false); - })); - - it('should update a readme file', inject([ClientService], (service: ClientService) => { - let mockSetReadme = sinon.stub(service, 'setBusinessNetworkReadme'); - - // call function - let result = service.updateFile('readme.md', 'read this', 'readme'); - - mockSetReadme.should.have.been.calledWith('read this'); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should not update a model file if invalid with a matching namespace', inject([ClientService], (service: ClientService) => { - - modelManagerMock = { - addModelFile: sinon.stub().throws('invalid'), - updateModelFile: sinon.stub().throws('invalid'), - deleteModelFile: sinon.stub(), - getModelFile: sinon.stub().returns(modelFileMock) - }; - - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - modelFileMock.getNamespace.returns('model-ns'); - - let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); - - let result = service.updateFile('model-ns', 'my-model-content', 'model'); - - result.should.equal('invalid'); - businessNetworkChangedSpy.should.have.been.calledWith(false); - })); - - it('should not replace a model file if id does not match namespace and file is invalid', inject([ClientService], (service: ClientService) => { - - modelManagerMock = { - addModelFile: sinon.stub().throws('invalid'), - updateModelFile: sinon.stub().throws('invalid'), - deleteModelFile: sinon.stub(), - getModelFile: sinon.stub().returns(modelFileMock) - }; - - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - - modelFileMock.getNamespace.returns('new-model'); - let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); - - let result = service.updateFile('model', 'my-model', 'model'); - - result.should.equal('invalid'); - businessNetworkChangedSpy.should.have.been.calledWith(false); - })); - - it('should not update an invalid script file', inject([ClientService], (service: ClientService) => { - let scriptManagerMock = { - createScript: sinon.stub().throws('invalid'), - addScript: sinon.stub() - }; - - businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); - - let result = service.updateFile('script', 'my-script', 'script'); - - result.should.equal('invalid'); - businessNetworkChangedSpy.should.have.been.calledWith(false); - })); - - it('should not update an invalid acl file', inject([ClientService], (service: ClientService) => { - let aclManagerMock = { - setAclFile: sinon.stub().throws('invalid') - }; - - businessNetworkDefMock.getAclManager.returns(aclManagerMock); - let mockCreateAclFile = sinon.stub(service, 'createAclFile').returns(aclFileMock); - - let result = service.updateFile('acl', 'my-acl', 'acl'); - - businessNetworkChangedSpy.should.have.been.calledWith(false); - result.should.equal('invalid'); - })); - - it('should not update a model file if namespace collision detected', inject([ClientService], (service: ClientService) => { - modelManagerMock = { - addModelFile: sinon.stub(), - updateModelFile: sinon.stub(), - deleteModelFile: sinon.stub(), - getModelFile: sinon.stub().returns(modelFileMock) - }; - - mockNamespaceCollide.returns(true); - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - - modelFileMock.getNamespace.returns('new-model'); - let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); - - let result = service.updateFile('model', 'my-model', 'model'); - - result.should.equal('Error: The namespace collides with existing model namespace new-model'); - modelManagerMock.updateModelFile.should.not.have.been.called; - businessNetworkChangedSpy.should.have.been.calledWith(false); - })); - - it('should return error message if type is invalid', inject([ClientService], (service: ClientService) => { - let result = service.updateFile('bad.file', 'content of wombat type', 'wombat'); - result.should.equal('Error: Attempted update of unknown file of type: wombat'); - })); - }); - - describe('validateFile', () => { - let mockBusinessNetwork; - - beforeEach(inject([ClientService], (service: ClientService) => { - mockBusinessNetwork = sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - })); - - it('should validate a model file', inject([ClientService], (service: ClientService) => { - let modelManagerMock = { - validateModelFile: sinon.stub() - }; - - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - - modelFileMock.getNamespace.returns('model'); - let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); - - let result = service.validateFile('model', 'my-model', 'model'); - - modelManagerMock.validateModelFile.should.have.been.calledWith(modelFileMock); - should.not.exist(result); - })); - - it('should validate a script file', inject([ClientService], (service: ClientService) => { - let scriptManagerMock = { - createScript: sinon.stub().returns(scriptFileMock), - addScript: sinon.stub() - }; - - businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); - - let result = service.validateFile('script', 'my-script', 'script'); - - scriptManagerMock.createScript.should.have.been.calledWith('script', 'JS', 'my-script'); - scriptManagerMock.addScript.should.not.have.been.called; - should.not.exist(result); - })); - - it('should validate an acl file', inject([ClientService], (service: ClientService) => { - let modelManagerMock = { - validateModelFile: sinon.stub() - }; - - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - - aclFileMock = { - validate: sinon.stub() - }; - - sinon.stub(service, 'createAclFile').returns(aclFileMock); - - let result = service.validateFile('acl', 'my-acl', 'acl'); - - aclFileMock.validate.should.have.been.called; - should.not.exist(result); - })); - - it('should validate a query file', inject([ClientService], (service: ClientService) => { - let queryManagerMock = { - validateQueryFile: sinon.stub() - }; - - businessNetworkDefMock.getQueryManager.returns(queryFileMock); - - queryFileMock = { - validate: sinon.stub() - }; - - sinon.stub(service, 'createQueryFile').returns(queryFileMock); - - let result = service.validateFile('query', 'my-query', 'query'); - - queryFileMock.validate.should.have.been.called; - should.not.exist(result); - })); - - it('should return error message if a model file is invalid', inject([ClientService], (service: ClientService) => { - let modelManagerMock = { - validateModelFile: sinon.stub().throws('invalid') - }; - - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - - modelFileMock.getNamespace.returns('model'); - let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); - - let result = service.validateFile('model', 'my-model', 'model'); - - modelManagerMock.validateModelFile.should.have.been.calledWith(modelFileMock); - result.should.equal('invalid'); - - })); - - it('should return error message if a script file is invalid', inject([ClientService], (service: ClientService) => { - let scriptManagerMock = { - createScript: sinon.stub().throws('invalid'), - addScript: sinon.stub() - }; - - businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); - - let result = service.validateFile('script', 'my-script', 'script'); - - scriptManagerMock.createScript.should.have.been.calledWith('script', 'JS', 'my-script'); - scriptManagerMock.addScript.should.not.have.been.called; - result.should.equal('invalid'); - })); - - it('should return error message if an acl file is invalid', inject([ClientService], (service: ClientService) => { - let modelManagerMock = { - validateModelFile: sinon.stub() - }; - - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - - aclFileMock = { - validate: sinon.stub().throws('invalid') - }; - - sinon.stub(service, 'createAclFile').returns(aclFileMock); - - let result = service.validateFile('acl', 'my-acl', 'acl'); - - aclFileMock.validate.should.have.been.called; - result.should.equal('invalid'); - })); - - it('should return error message if an query file is invalid', inject([ClientService], (service: ClientService) => { - let queryManagerMock = { - validateQueryFile: sinon.stub() - }; - - businessNetworkDefMock.getQueryManager.returns(queryManagerMock); - - queryFileMock = { - validate: sinon.stub().throws('invalid') - }; - - sinon.stub(service, 'createQueryFile').returns(queryFileMock); - - let result = service.validateFile('query', 'my-query', 'query'); - - queryFileMock.validate.should.have.been.called; - result.should.equal('invalid'); - })); - - it('should return error message if type is invalid', inject([ClientService], (service: ClientService) => { - let result = service.validateFile('bad.file', 'content of wombat type', 'wombat'); - result.should.equal('Error: Attempted validation of unknown file of type: wombat'); - })); - }); - - describe('replaceFile', () => { - // replaceFile(oldId: string, newId: string, content: any, type: string) - it('should handle error case by notifying and returning error message in string', inject([ClientService], (service: ClientService) => { - sinon.stub(service, 'getBusinessNetwork').throws(new Error('Forced Error')); - let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); - - let response = service['replaceFile']('oldId', 'newId', 'content', 'model'); - - businessNetworkChangedSpy.should.have.been.calledWith(false); - response.should.equal('Error: Forced Error'); - - })); - - it('should replace a model file by model manager update', inject([ClientService], (service: ClientService) => { - - let modelManagerMock = { - updateModelFile: sinon.stub() - }; - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); - - let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); - - // Call the method (model) - let response = service['replaceFile']('oldId', 'newId', 'content', 'model'); - - // Check correct items were called with correct parameters - modelManagerMock.updateModelFile.should.have.been.calledWith(modelFileMock, 'newId'); - businessNetworkChangedSpy.should.have.been.calledWith(true); - should.not.exist(response); - })); - - it('should replace a script file by deletion and addition', inject([ClientService], (service: ClientService) => { - - let scriptManagerMock = { - createScript: sinon.stub().returns(scriptFileMock), - addScript: sinon.stub(), - deleteScript: sinon.stub() - }; - businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); - let businessNetworkMock = sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); - - // Call the method (script) - let response = service['replaceFile']('oldId', 'newId', 'content', 'script'); - - // Check correct items were called with correct parameters - scriptManagerMock.addScript.should.have.been.calledWith(scriptFileMock); - scriptManagerMock.deleteScript.should.have.been.calledWith('oldId'); - businessNetworkChangedSpy.should.have.been.calledWith(true); - should.not.exist(response); - })); - - it('should return error message if type is invalid', inject([ClientService], (service: ClientService) => { - let result = service.replaceFile('oldId', 'newId', 'content', 'wombat'); - result.should.equal('Error: Attempted replace of ununsupported file type: wombat'); - })); - }); - - describe('modelNamespaceCollides', () => { - - let modelManagerMock; - let mockCreateBusinessNetwork; - let mockFile0 = sinon.createStubInstance(ModelFile); - mockFile0.getNamespace.returns('name0'); - let mockFile1 = sinon.createStubInstance(ModelFile); - mockFile1.getNamespace.returns('name1'); - let mockFile2 = sinon.createStubInstance(ModelFile); - mockFile2.getNamespace.returns('name2'); - let mockFile3 = sinon.createStubInstance(ModelFile); - mockFile3.getNamespace.returns('name3'); - let mockFile4 = sinon.createStubInstance(ModelFile); - mockFile4.getNamespace.returns('name4'); - - beforeEach(inject([ClientService], (service: ClientService) => { - modelManagerMock = { - getModelFiles: sinon.stub().returns([mockFile0, mockFile1, mockFile2, mockFile3, mockFile4]) - }; - - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - mockCreateBusinessNetwork = sinon.stub(service, 'createBusinessNetwork').returns(businessNetworkDefMock); - service['currentBusinessNetwork'] = businessNetworkDefMock; - })); - - it('should return true if namespace collision detected', inject([ClientService], (service: ClientService) => { - - let result = service.modelNamespaceCollides('name1', 'something-different'); - result.should.be.equal(true); - - })); - - it('should return false if no namespace collision detected with new name', inject([ClientService], (service: ClientService) => { - - let result = service.modelNamespaceCollides('not-in-list', 'something-different'); - result.should.be.equal(false); - - })); - - it('should handle no previousNamespace being passed', inject([ClientService], (service: ClientService) => { - - let result = service.modelNamespaceCollides('new-namespace', null); - result.should.be.equal(false); - - })); - - it('should handle no model files existing in BND', inject([ClientService], (service: ClientService) => { - modelManagerMock = { - getModelFiles: sinon.stub().returns([]) - }; - - let result = service.modelNamespaceCollides('not-in-list', 'something-different'); - result.should.be.equal(false); - })); - - }); - - describe('getScriptFile', () => { - it('should get the script file', inject([ClientService], (service: ClientService) => { - let scriptManagerMock = { - getScript: sinon.stub().returns(scriptFileMock) - }; - businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); - let businessNetworkMock = sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - let result = service.getScriptFile('testId'); - - result.should.deep.equal(scriptFileMock); - scriptManagerMock.getScript.should.have.been.calledWith('testId'); - })); - }); - - describe('getScriptFiles', () => { - it('should get script files', inject([ClientService], (service: ClientService) => { - let scriptManagerMock = { - getScripts: sinon.stub().returns([scriptFileMock, scriptFileMock]) - }; - businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); - let businessNetworkMock = sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - let result = service.getScripts(); - - result.length.should.equal(2); - result[0].should.deep.equal(scriptFileMock); - result[1].should.deep.equal(scriptFileMock); - })); - }); - - describe('getAclFile', () => { - it('should get the acl file', inject([ClientService], (service: ClientService) => { - let aclManagerMock = { - getAclFile: sinon.stub().returns(aclFileMock) - }; - businessNetworkDefMock.getAclManager.returns(aclManagerMock); - let businessNetworkMock = sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - let result = service.getAclFile(); - - result.should.deep.equal(aclFileMock); - aclManagerMock.getAclFile.should.have.been.called; - })); - }); - - describe('getQueryFile', () => { - it('should get the query file', inject([ClientService], (service: ClientService) => { - let queryManagerMock = { - getQueryFile: sinon.stub().returns(queryFileMock) - }; - - businessNetworkDefMock.getQueryManager.returns(queryManagerMock); - let businessNetworkMock = sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - let result = service.getQueryFile(); - - result.should.deep.equal(queryFileMock); - queryManagerMock.getQueryFile.should.have.been.called; - })); - }); - - describe('getMetaData', () => { - it('should get the metadata', inject([ClientService], (service: ClientService) => { - businessNetworkDefMock.getMetadata.returns({metadata: 'my metadata'}); - let businessNetworkMock = sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - let result = service.getMetaData(); - - result.should.deep.equal({metadata: 'my metadata'}); - businessNetworkDefMock.getMetadata.should.have.been.called; - })); - }); - - describe('setBusinessNetwork...', () => { - beforeEach(inject([ClientService], (service: ClientService) => { - let modelManagerMock = { - getModelFiles: sinon.stub().returns([modelFileMock, modelFileMock]), - addModelFiles: sinon.stub() - }; - - let aclManagerMock = { - setAclFile: sinon.stub(), - getAclFile: sinon.stub().returns(aclFileMock) - }; - - let scriptManagerMock = { - getScripts: sinon.stub().returns([scriptFileMock, scriptFileMock]), - addScript: sinon.stub() - }; - - let queryManagerMock = { - setQueryFile: sinon.stub(), - getQueryFile: sinon.stub().returns(queryFileMock) - }; - - businessNetworkDefMock.getModelManager.returns(modelManagerMock); - businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); - businessNetworkDefMock.getAclManager.returns(aclManagerMock); - businessNetworkDefMock.getQueryManager.returns(queryManagerMock); - - sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - })); - - it('should set business network readme', inject([ClientService], (service: ClientService) => { - let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); - - businessNetworkDefMock.getMetadata.returns({ - setReadme: sinon.stub() - }); - - service.setBusinessNetworkReadme('my readme'); - - businessNetworkDefMock.setReadme.should.have.been.calledWith('my readme'); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should set business network version', inject([ClientService], (service: ClientService) => { - let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); - - businessNetworkDefMock.getMetadata.returns({ - getName: sinon.stub().returns('my name'), - getPackageJson: sinon.stub().returns({version: '0.0'}), - setPackageJson: sinon.stub() - }); - - service.setBusinessNetworkVersion('new_version'); - - businessNetworkDefMock.setPackageJson.should.have.been.calledWith({version: 'new_version'}); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should set business network packageJson', inject([ClientService], (service: ClientService) => { - let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); - - businessNetworkDefMock.getMetadata.returns({ - getName: sinon.stub().returns('my name') - }); - - let packageJson = {name: 'my name', version: 'my version', description: 'my description'}; - - service.setBusinessNetworkPackageJson(packageJson); - - businessNetworkDefMock.setPackageJson.should.have.been.calledWith(packageJson); - businessNetworkChangedSpy.should.have.been.calledWith(true); - })); - - it('should prevent setting the business network packageJson to change the BND name', inject([ClientService], (service: ClientService) => { - let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); - - businessNetworkDefMock.getMetadata.returns({ - getName: sinon.stub().returns('my name') - }); - - let packageJson = {name: 'my different name', version: 'my version', description: 'my description'}; - - try { - service.setBusinessNetworkPackageJson(packageJson); - throw new Error('should not get here'); - } catch (error) { - businessNetworkDefMock.setPackageJson.should.not.have.been.called; - businessNetworkChangedSpy.should.not.have.been.called; - error.toString().should.equal('Error: Unsupported attempt to update Business Network Name.'); - } - })); - }); - - describe('getBusinessNetworkName', () => { - it('should get the name', inject([ClientService], (service: ClientService) => { - sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - businessNetworkDefMock.getMetadata.returns({ - getName: sinon.stub().returns('my name') - }); - - let result = service.getBusinessNetworkName(); - - result.should.equal('my name'); - })); - }); - - describe('getBusinessNetworkDescription', () => { - it('should get the description', inject([ClientService], (service: ClientService) => { - sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - - businessNetworkDefMock.getMetadata.returns({ - getDescription: sinon.stub().returns('my description') - }); - - let result = service.getBusinessNetworkDescription(); - - result.should.equal('my description'); - })); - }); - describe('ensureConnected', () => { beforeEach(() => { identityServiceMock.getCurrentConnectionProfile.returns({name: 'myProfile'}); @@ -903,7 +158,9 @@ describe('ClientService', () => { 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').returns('myNetwork'); + businessNetworkDefMock.getName.returns('myNetwork'); + + sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); service.ensureConnected(null, false); @@ -932,7 +189,7 @@ describe('ClientService', () => { let refreshMock = sinon.stub(service, 'refresh').returns(Promise.resolve()); let setBusinessNetworkMock = sinon.stub(service, 'setSavedBusinessNetworkName'); - let businessNetworkNameMock = sinon.stub(service, 'getBusinessNetworkName'); + businessNetworkDefMock.getName.returns('myNetwork'); service.ensureConnected('myNetwork', false); @@ -952,7 +209,7 @@ describe('ClientService', () => { setBusinessNetworkMock.should.have.been.called; - businessNetworkNameMock.should.not.have.been.called; + businessNetworkDefMock.getName.should.not.have.been.called; service['isConnected'].should.equal(true); should.not.exist(service['connectingPromise']); @@ -965,7 +222,7 @@ describe('ClientService', () => { let setBusinessNetworkMock = sinon.stub(service, 'setSavedBusinessNetworkName'); let getBusinessNetworkMock = sinon.stub(service, 'getSavedBusinessNetworkName').returns('myNetwork'); - let businessNetworkNameMock = sinon.stub(service, 'getBusinessNetworkName').throws(); + sinon.stub(service, 'getBusinessNetwork').throws(); service.ensureConnected(null, false); @@ -1154,15 +411,6 @@ describe('ClientService', () => { }))); }); - describe('createNewBusinessNetwork', () => { - it('should alert on failure', inject([ClientService], (service: ClientService) => { - // Set up mocks - let mockCreateBusinessNetwork = sinon.stub(service, 'createBusinessNetwork').throws('forced error'); - let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); - sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); - })); - }); - describe('disconnect', () => { it('should disconnect', inject([ClientService], (service: ClientService) => { let businessNetworkMock = sinon.stub(service, 'getBusinessNetworkConnection').returns(businessNetworkConMock); @@ -1184,7 +432,8 @@ describe('ClientService', () => { describe('setSavedBusinessNetworkName', () => { it('should save the business network name', inject([ClientService], (service: ClientService) => { - let businessNetworkMock = sinon.stub(service, 'getBusinessNetworkName').returns('myNetwork'); + businessNetworkDefMock.getName.returns('myNetwork'); + sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); service['setSavedBusinessNetworkName']('bob'); mockLocalStorage.set.should.have.been.calledWith('currentBusinessNetwork:bob', 'myNetwork'); @@ -1258,22 +507,6 @@ describe('ClientService', () => { }); - describe('createQueryFile', () => { - let mockBusinessNetwork; - - beforeEach(() => { - mockBusinessNetwork = sinon.createStubInstance(BusinessNetworkDefinition); - }); - - it('should create a Query file', fakeAsync(inject([ClientService], (service: ClientService) => { - service['currentBusinessNetwork'] = mockBusinessNetwork; - let queryFile = service.createQueryFile('query', ''); - queryFile.should.be.instanceOf(QueryFile); - mockBusinessNetwork.getModelManager.should.have.been.called; - }))); - - }); - describe('resolveTransactionRelationship', () => { let mockRegistry; diff --git a/packages/composer-playground/src/app/services/client.service.ts b/packages/composer-playground/src/app/services/client.service.ts index bc08980cf4..f0800d854b 100644 --- a/packages/composer-playground/src/app/services/client.service.ts +++ b/packages/composer-playground/src/app/services/client.service.ts @@ -1,21 +1,23 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject, Subject } from 'rxjs/Rx'; import { LocalStorageService } from 'angular-2-local-storage'; import { AdminService } from './admin.service'; import { IdentityService } from './identity.service'; -import { IdentityCardService } from './identity-card.service'; import { AlertService } from '../basic-modals/alert.service'; import { ConnectionProfileStoreService } from './connectionProfileStores/connectionprofilestore.service'; import { BusinessNetworkConnection } from 'composer-client'; -import { BusinessNetworkDefinition, Util, ModelFile, Script, AclFile, QueryFile, TransactionDeclaration } from 'composer-common'; +import { + BusinessNetworkDefinition, + ModelFile, + Script, + AclFile, + QueryFile, + TransactionDeclaration +} from 'composer-common'; @Injectable() export class ClientService { - public businessNetworkChanged$: Subject = new BehaviorSubject(null); - public namespaceChanged$: Subject = new BehaviorSubject(null); - private businessNetworkConnection: BusinessNetworkConnection = null; private isConnected: boolean = false; private connectingPromise: Promise = null; @@ -24,32 +26,11 @@ export class ClientService { constructor(private adminService: AdminService, private identityService: IdentityService, - private identityCardService: IdentityCardService, private alertService: AlertService, private localStorageService: LocalStorageService, private connectionProfileStoreService: ConnectionProfileStoreService) { } - // horrible hack for tests - createModelFile(content, fileName) { - return new ModelFile(this.getBusinessNetwork().getModelManager(), content, fileName); - } - - // horrible hack for tests - createAclFile(id, content) { - return new AclFile(id, this.getBusinessNetwork().getModelManager(), content); - } - - // horrible hack for tests - createScriptFile(id, type, content) { - return this.getBusinessNetwork().getScriptManager().createScript(id, type, content); - } - - // horrible hack for tests - createQueryFile(id, content) { - return new QueryFile(id, this.getBusinessNetwork().getModelManager(), content); - } - // horrible hack for tests createBusinessNetwork(identifier, description, packageJson, readme) { return new BusinessNetworkDefinition(identifier, description, packageJson, readme); @@ -72,172 +53,6 @@ export class ClientService { return this.currentBusinessNetwork; } - getBusinessNetworkName() { - return this.getBusinessNetwork().getMetadata().getName(); - } - - getBusinessNetworkDescription() { - return this.getBusinessNetwork().getMetadata().getDescription(); - } - - getModelFile(id: string): ModelFile { - return this.getBusinessNetwork().getModelManager().getModelFile(id); - } - - getModelFiles(): ModelFile[] { - return this.getBusinessNetwork().getModelManager().getModelFiles(); - } - - getScriptFile(id: string): Script { - return this.getBusinessNetwork().getScriptManager().getScript(id); - } - - getScripts(): Script[] { - return this.getBusinessNetwork().getScriptManager().getScripts(); - } - - getAclFile(): AclFile { - return this.getBusinessNetwork().getAclManager().getAclFile(); - } - - getQueryFile(): QueryFile { - return this.getBusinessNetwork().getQueryManager().getQueryFile(); - } - - getMetaData() { - return this.getBusinessNetwork().getMetadata(); - } - - validateFile(id: string, content: any, type: string): string { - try { - switch (type) { - case 'model': - let modelFile = this.createModelFile(content, null); - this.getBusinessNetwork().getModelManager().validateModelFile(modelFile); - break; - case 'script': - this.createScriptFile(id, 'JS', content); - break; - case 'acl': - let aclFile = this.createAclFile(id, content); - aclFile.validate(); - break; - case 'query': - let queryFile = this.createQueryFile(id, content); - queryFile.validate(); - break; - default: - throw new Error('Attempted validation of unknown file of type: ' + type); - } - return null; - } catch (e) { - return e.toString(); - } - } - - updateFile(id: string, content: any, type: string): string { - try { - switch (type) { - case 'model': - let modelManager = this.getBusinessNetwork().getModelManager(); - let original: ModelFile = modelManager.getModelFile(id); - let modelFile = this.createModelFile(content, original.getName()); - if (this.modelNamespaceCollides(modelFile.getNamespace(), id)) { - throw new Error(`The namespace collides with existing model namespace ${modelFile.getNamespace()}`); - } - if (id !== modelFile.getNamespace()) { - // Then we are changing namespace and must delete old reference - modelManager.addModelFile(modelFile); - modelManager.deleteModelFile(id); - this.namespaceChanged$.next(modelFile.getNamespace()); - } else { - modelManager.updateModelFile(modelFile); - } - break; - case 'script': - let script = this.createScriptFile(id, 'JS', content); - this.getBusinessNetwork().getScriptManager().addScript(script); - break; - case 'acl': - let aclFile = this.createAclFile(id, content); - this.getBusinessNetwork().getAclManager().setAclFile(aclFile); - break; - case 'query': - let query = this.createQueryFile(id, content); - this.getBusinessNetwork().getQueryManager().setQueryFile(query); - break; - case 'package': - this.setBusinessNetworkPackageJson(JSON.parse(content)); - break; - case 'readme': - this.setBusinessNetworkReadme(content); - break; - default: - throw new Error('Attempted update of unknown file of type: ' + type); - } - this.businessNetworkChanged$.next(true); - return null; - } catch (e) { - this.businessNetworkChanged$.next(false); - return e.toString(); - } - } - - replaceFile(oldId: string, newId: string, content: any, type: string): string { - try { - switch (type) { - case 'model': - let modelFile = this.createModelFile(content, newId); - this.getBusinessNetwork().getModelManager().updateModelFile(modelFile, newId); - this.businessNetworkChanged$.next(true); - break; - case 'script': - let script = this.createScriptFile(newId, 'JS', content); - this.getBusinessNetwork().getScriptManager().addScript(script); - this.getBusinessNetwork().getScriptManager().deleteScript(oldId); - this.businessNetworkChanged$.next(true); - break; - default: - throw new Error('Attempted replace of ununsupported file type: ' + type); - } - return null; - } catch (e) { - this.businessNetworkChanged$.next(false); - return e.toString(); - } - } - - modelNamespaceCollides(newNamespace, previousNamespace): boolean { - let allModelFiles = this.currentBusinessNetwork.getModelManager().getModelFiles(); - if ((newNamespace !== previousNamespace) && (allModelFiles.findIndex((model) => model.getNamespace() === newNamespace) !== -1)) { - return true; - } else { - return false; - } - } - - setBusinessNetworkReadme(readme) { - this.getBusinessNetwork().setReadme(readme); - this.businessNetworkChanged$.next(true); - } - - setBusinessNetworkVersion(version: string) { - let packageJson = this.getBusinessNetwork().getMetadata().getPackageJson(); - packageJson.version = version; - this.getBusinessNetwork().setPackageJson(packageJson); - this.businessNetworkChanged$.next(true); - } - - setBusinessNetworkPackageJson(packageJson) { - // prevent BND name change - if (packageJson.name !== this.getBusinessNetworkName()) { - throw new Error('Unsupported attempt to update Business Network Name.'); - } else { - this.getBusinessNetwork().setPackageJson(packageJson); - this.businessNetworkChanged$.next(true); - } - } - ensureConnected(name: string = null, force: boolean = false): Promise { if (this.isConnected && !force) { return Promise.resolve(); @@ -257,7 +72,10 @@ export class ClientService { if (!name) { try { - businessNetworkName = this.getBusinessNetworkName(); + let businessNetwork = this.getBusinessNetwork(); + if (businessNetwork) { + businessNetworkName = this.getBusinessNetwork().getName(); + } } catch (error) { console.log('business network name not set yet so using from local storage'); } finally { @@ -360,6 +178,6 @@ export class ClientService { setSavedBusinessNetworkName(identity: string): void { let key = `currentBusinessNetwork:${identity}`; - this.localStorageService.set(key, this.getBusinessNetworkName()); + this.localStorageService.set(key, this.getBusinessNetwork().getName()); } } diff --git a/packages/composer-playground/src/app/services/editor-file.spec.ts b/packages/composer-playground/src/app/services/editor-file.spec.ts new file mode 100644 index 0000000000..1ddb45f4e2 --- /dev/null +++ b/packages/composer-playground/src/app/services/editor-file.spec.ts @@ -0,0 +1,425 @@ +/* tslint:disable:no-unused-variable */ +/* tslint:disable:no-unused-expression */ +/* tslint:disable:no-var-requires */ +/* tslint:disable:max-classes-per-file */ +/* tslint:disable:no-unused-variable */ +/* tslint:disable:no-unused-expression */ +/* tslint:disable:no-var-requires */ +/* tslint:disable:max-classes-per-file */ +import { ModelManager, ModelFile } from 'composer-common'; +import { EditorFile } from './editor-file'; + +import * as sinon from 'sinon'; + +describe('EditorFile', () => { + let file; + + beforeEach(() => { + file = new EditorFile('fileID', 'fileDisplayID', 'fileContent', 'fileType'); + }); + + describe('EditorFile', () => { + + it('should create a file', () => { + file['id'].should.equal('fileID'); + file['displayID'].should.equal('fileDisplayID'); + file['content'].should.equal('fileContent'); + file['type'].should.equal('fileType'); + }); + + it('should return the ID of a file', () => { + file.getId().should.equal('fileID'); + }); + + it('should return the content of a file', () => { + file.getContent().should.equal('fileContent'); + }); + + it('should return the type of a file', () => { + file.getType().should.equal('fileType'); + }); + + it('should set the ID of a file', () => { + file.setId('newFileId'); + file.getId().should.equal('newFileId'); + }); + + it('should set the content of a file', () => { + file.setContent('newFileContent'); + file.getContent().should.equal('newFileContent'); + }); + + it('should set deisplay id', () => { + file.setDisplayID('newDisplayId'); + file['displayID'].should.equal('newDisplayId'); + }); + + it('should set the type of a file', () => { + file.setType('newFileType'); + file.getType().should.equal('newFileType'); + }); + + it('should get if model type', () => { + file['type'] = 'model'; + file.isModel().should.equal(true); + }); + + it('should get if not model type', () => { + file['type'] = 'bob'; + file.isModel().should.equal(false); + }); + + it('should get if script type', () => { + file['type'] = 'script'; + file.isScript().should.equal(true); + }); + + it('should get if not script type', () => { + file['type'] = 'bob'; + file.isScript().should.equal(false); + }); + + it('should get if acl type', () => { + file['type'] = 'acl'; + file.isAcl().should.equal(true); + }); + + it('should get if not acl type', () => { + file['type'] = 'bob'; + file.isAcl().should.equal(false); + }); + + it('should get if query type', () => { + file['type'] = 'query'; + file.isQuery().should.equal(true); + }); + + it('should get if not query type', () => { + file['type'] = 'bob'; + file.isQuery().should.equal(false); + }); + + it('should get if readme type', () => { + file['type'] = 'readme'; + file.isReadMe().should.equal(true); + }); + + it('should get if not readme type', () => { + file['type'] = 'bob'; + file.isReadMe().should.equal(false); + }); + + it('should get if package type', () => { + file['type'] = 'package'; + file.isPackage().should.equal(true); + }); + + it('should get if not package type', () => { + file['type'] = 'bob'; + file.isPackage().should.equal(false); + }); + }); + + describe('getModelNamespace', () => { + + it('should get the namespace of the model file', () => { + file['content'] = `/** + * Sample business network definition. + */ +namespace org.acme.sample +`; + file.getModelNamespace().should.equal('org.acme.sample'); + }); + }); + + describe('validate', () => { + + it('should validate a model file', () => { + const model1 = ` + namespace org.acme.ext + asset MyAsset2 identified by assetId { + o String assetId + }`; + const model2 = ` + namespace org.acme + import org.acme.ext.MyAsset2 + asset MyAsset identified by assetId { + o String assetId + }`; + let modelManager = new ModelManager(); + let modelFile1 = new ModelFile(modelManager, model1); + modelManager.addModelFiles([modelFile1], [modelFile1.getName()]); + file['type'] = 'model'; + file['content'] = model2; + (() => file.validate(modelManager)).should.not.throw(); + }); + + it('should throw error with invalid model file', () => { + const model1 = ` + namespace org.acme.ext + asset MyAsset2 identified by assetId { + o String assetId + }`; + const model2 = ` + namespace org.acme + import org.acme.ext.MyAsset2 + ast MyAsset identified by assetId { + o String assetId + }`; + let modelManager = new ModelManager(); + let modelFile1 = new ModelFile(modelManager, model1); + modelManager.addModelFiles([modelFile1], [modelFile1.getName()]); + file['type'] = 'model'; + file['content'] = model2; + (() => file.validate(modelManager)).should.throw(); + }); + + it('should validate script file', () => { + const model = `/** + * Sample business network definition. + */ +namespace org.acme.sample +asset SampleAsset identified by assetId { + o String assetId + --> SampleParticipant owner + o String value +} +participant SampleParticipant identified by participantId { + o String participantId + o String firstName + o String lastName +} +transaction SampleTransaction { + --> SampleAsset asset + o String newValue +} +event SampleEvent { + --> SampleAsset asset + o String oldValue + o String newValue +}`; + const script = `function sampleTransaction(tx) { + // Save the old value of the asset. + var oldValue = tx.asset.value; + // Update the asset with the new value. + tx.asset.value = tx.newValue; + // Get the asset registry for the asset. + return getAssetRegistry('org.acme.sample.SampleAsset') + .then(function (assetRegistry) { + // Update the asset in the asset registry. + return assetRegistry.update(tx.asset); + }) + .then(function () { + // Emit an event for the modified asset. + var event = getFactory().newEvent('org.acme.sample', 'SampleEvent'); + event.asset = tx.asset; + event.oldValue = oldValue; + event.newValue = tx.newValue; + emit(event); + }); + }`; + file['content'] = script; + file['type'] = 'script'; + let modelManager = new ModelManager(); + let modelFile = new ModelFile(modelManager, model); + modelManager.addModelFile(modelFile); + (() => file.validate(modelManager)).should.not.throw(); + }); + + it('should throw error on invalid script file', () => { + const model = `/** + * Sample business network definition. + */ +namespace org.acme.sample +asset SampleAsset identified by assetId { + o String assetId + --> SampleParticipant owner + o String value +} +participant SampleParticipant identified by participantId { + o String participantId + o String firstName + o String lastName +} +transaction SampleTransaction { + --> SampleAsset asset + o String newValue +}`; + const script = `funn sampleTransaction(tx) { + // Save the old value of the asset. + var oldValue = tx.asset.value; + // Update the asset with the new value. + tx.asset.value = tx.newValue; + // Get the asset registry for the asset. + return getRegistry('org.acme.sample.SampleAsset') + .then(function (assetRegistry) { + // Update the asset in the asset registry. + return assetRegistry.update(tx.asset); + }) + .then(function () { + // Emit an event for the modified asset. + var event = getFactory().newEvent('org.acme.sample', 'SampleEvent'); + event.asset = tx.asset; + event.oldValue = oldValue; + event.newValue = tx.newValue; + emit(event); + }); + }`; + file['content'] = script; + file['type'] = 'script'; + let modelManager = new ModelManager(); + let modelFile = new ModelFile(modelManager, model); + modelManager.addModelFile(modelFile); + (() => file.validate(modelManager)).should.throw(); + }); + + it('should validate a query file', () => { + const model = `/** + * Commodity trading network + */ +namespace org.acme.trading +asset Commodity identified by tradingSymbol { + o String tradingSymbol + o String description + o String mainExchange + o Double quantity + --> Trader owner +} +participant Trader identified by tradeId { + o String tradeId + o String firstName + o String lastName +}`; + const query = ` +query selectCommodities { + description: "Select all commodities" + statement: + SELECT org.acme.trading.Commodity +}`; + file['content'] = query; + file['type'] = 'query'; + let modelManager = new ModelManager(); + let modelFile = new ModelFile(modelManager, model); + modelManager.addModelFile(modelFile); + (() => file.validate(modelManager)).should.not.throw(); + }); + + it('should throw error on invalid query file', () => { + const model = `/** + * Commodity trading network + */ +namespace org.acme.trading +asset Commodity identified by tradingSymbol { + o String tradingSymbol + o String description + o String mainExchange + o Double quantity + --> Trader owner +} +participant Trader identified by tradeId { + o String tradeId + o String firstName + o String lastName +}`; + const query = ` +query selectCommodities { + description: "Select all commodities" + statement: + SELBOBECT org.acme.trading.Commodity +}`; + file['content'] = query; + file['type'] = 'query'; + let modelManager = new ModelManager(); + let modelFile = new ModelFile(modelManager, model); + modelManager.addModelFile(modelFile); + (() => file.validate(modelManager)).should.throw(); + }); + + it('should validate acl file', () => { + const model = `/** + * Sample business network definition. + */ +namespace org.acme.sample +asset SampleAsset identified by assetId { + o String assetId + --> SampleParticipant owner + o String value +} +participant SampleParticipant identified by participantId { + o String participantId + o String firstName + o String lastName +} +transaction SampleTransaction { + --> SampleAsset asset + o String newValue +}`; + const acl = `rule SystemACL { + description: "System ACL to permit all access" + participant: "org.hyperledger.composer.system.Participant" + operation: ALL + resource: "org.hyperledger.composer.system.**" + action: ALLOW + }`; + file['content'] = acl; + file['type'] = 'acl'; + let modelManager = new ModelManager(); + let modelFile = new ModelFile(modelManager, model); + modelManager.addModelFile(modelFile); + (() => file.validate(modelManager)).should.not.throw(); + }); + + it('should throw error on invalid acl file', () => { + const model = `/** + * Sample business network definition. + */ +namespace org.acme.sample +asset SampleAsset identified by assetId { + o String assetId + --> SampleParticipant owner + o String value +} +participant SampleParticipant identified by participantId { + o String participantId + o String firstName + o String lastName +} +transaction SampleTransaction { + --> SampleAsset asset + o String newValue +}`; + const acl = `rule SystemACL { + description: "System ACL to permit all access" + participant: "org.hyperledger.composer.system.Participant" + operation: ALL + resource: "org.hyperledger.composer.system.**" + action: BOB + }`; + + file['content'] = acl; + file['type'] = 'acl'; + let modelManager = new ModelManager(); + let modelFile = new ModelFile(modelManager, model); + modelManager.addModelFile(modelFile); + (() => file.validate(modelManager)).should.throw(); + }); + + it('should not do anything if not a type we deal with', () => { + let validateModelSpy = sinon.spy(file, 'validateModelFile'); + let validateScriptSpy = sinon.spy(file, 'validateScriptFile'); + let validateAclSpy = sinon.spy(file, 'validateAclFile'); + let validateQuerySpy = sinon.spy(file, 'validateQueryFile'); + + let modelManager = new ModelManager(); + + file['type'] = 'banana'; + + file.validate(); + + validateAclSpy.should.not.have.been.called; + validateModelSpy.should.not.have.been.called; + validateScriptSpy.should.not.have.been.called; + validateQuerySpy.should.not.have.been.called; + }); + }); +}); diff --git a/packages/composer-playground/src/app/services/editor-file.ts b/packages/composer-playground/src/app/services/editor-file.ts new file mode 100644 index 0000000000..c2487d7ef0 --- /dev/null +++ b/packages/composer-playground/src/app/services/editor-file.ts @@ -0,0 +1,111 @@ +import { ModelManager, ModelFile, Script, AclFile, QueryFile } from 'composer-common'; + +export class EditorFile { + private id; + private displayID; + private content; + private type; + + constructor(id: string, displayID: string, content: string, type: string) { + this.id = id; + this.displayID = displayID; + this.content = content; + this.type = type; + } + + isModel() { + return this.type === 'model'; + } + + isScript() { + return this.type === 'script'; + } + + isAcl() { + return this.type === 'acl'; + } + + isQuery() { + return this.type === 'query'; + } + + isReadMe() { + return this.type === 'readme'; + } + + isPackage() { + return this.type === 'package'; + } + + getId() { + return this.id; + } + + getContent() { + return this.content; + } + + getType() { + return this.type; + } + + getModelNamespace() { + let modelManager = new ModelManager(); + let modelFile = new ModelFile(modelManager, this.content, null); + return modelFile.getNamespace(); + } + + setId(id) { + this.id = id; + } + + setDisplayID(id) { + this.displayID = id; + } + + setContent(content) { + this.content = content; + } + + setType(type) { + this.type = type; + } + + validate(modelManager: ModelManager) { + switch (this.type) { + case 'model': + this.validateModelFile(modelManager); + break; + case 'script': + this.validateScriptFile(modelManager); + break; + case 'query': + this.validateQueryFile(modelManager); + break; + case 'acl': + this.validateAclFile(modelManager); + break; + default: + break; + } + } + + private validateModelFile(modelManager: ModelManager) { + let modelFile = new ModelFile(modelManager, this.content, null); + modelFile.validate(); + } + + private validateScriptFile(modelManager: ModelManager) { + let mockscriptFile = new Script(modelManager, null, 'JS', this.content); + } + + private validateAclFile(modelManager: ModelManager) { + let aclFile = new AclFile(null, modelManager, this.content); + aclFile.validate(); + } + + private validateQueryFile(modelManager: ModelManager) { + let queryFile = new QueryFile(null, modelManager, this.content); + queryFile.validate(); + } +} diff --git a/packages/composer-playground/src/app/services/file.service.spec.ts b/packages/composer-playground/src/app/services/file.service.spec.ts new file mode 100644 index 0000000000..56a6e568d7 --- /dev/null +++ b/packages/composer-playground/src/app/services/file.service.spec.ts @@ -0,0 +1,2375 @@ +/* tslint:disable:no-unused-variable */ +/* tslint:disable:no-unused-expression */ +/* tslint:disable:no-var-requires */ +/* tslint:disable:max-classes-per-file */ +/* tslint:disable:no-unused-variable */ +/* tslint:disable:no-unused-expression */ +/* tslint:disable:no-var-requires */ +/* tslint:disable:max-classes-per-file */ +import { TestBed, inject, fakeAsync } from '@angular/core/testing'; + +import * as sinon from 'sinon'; +import * as chai from 'chai'; + +let should = chai.should(); +let assert = chai.assert; + +import { EditorFile } from './editor-file'; +import { FileService } from './file.service'; +import { ModelFile, BusinessNetworkDefinition, Script, AclFile, QueryFile } from 'composer-common'; +import { ClientService } from './client.service'; + +describe('FileService', () => { + + let sandbox; + + let mockClientService; + let businessNetworkDefMock; + let modelFileMock; + let aclFileMock; + let scriptFileMock; + let queryFileMock; + + beforeEach(() => { + + modelFileMock = sinon.createStubInstance(ModelFile); + scriptFileMock = sinon.createStubInstance(Script); + aclFileMock = sinon.createStubInstance(AclFile); + queryFileMock = sinon.createStubInstance(QueryFile); + businessNetworkDefMock = sinon.createStubInstance(BusinessNetworkDefinition); + mockClientService = sinon.createStubInstance(ClientService); + sandbox = sinon.sandbox.create(); + + TestBed.configureTestingModule({ + providers: [FileService, + {provide: ClientService, useValue: mockClientService}] + }); + }); + + describe('getFile', () => { + + it('should return model files when provided with the model file type', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let file2 = new EditorFile('2', '2', 'this is the 2 model', 'model'); + let testModels = new Map(); + + testModels.set('1', file); + testModels.set('2', file2); + + fileService['modelFiles'] = testModels; + + let testFile = fileService.getFile('1', 'model'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the model'); + testFile.getType().should.equal('model'); + + }))); + + it('should return script files when provided with the script file type', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the script', 'script'); + let file2 = new EditorFile('2', '2', 'this is the 2 script', 'script'); + let testScripts = new Map(); + + testScripts.set('1', file); + testScripts.set('2', file2); + + fileService['scriptFiles'] = testScripts; + + let testFile = fileService.getFile('1', 'script'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the script'); + testFile.getType().should.equal('script'); + + }))); + + it('should return the query file when provided with the query file type', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the query', 'query'); + + fileService['queryFile'] = file; + + let testFile = fileService.getFile('1', 'query'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the query'); + testFile.getType().should.equal('query'); + + }))); + + it('should return the acl file when provided with the acl file type', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the acl', 'acl'); + + fileService['aclFile'] = file; + + let testFile = fileService.getFile('1', 'acl'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the acl'); + testFile.getType().should.equal('acl'); + + }))); + + it('should return the readme file when provided with the readme file type', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + + fileService['readMe'] = file; + + let testFile = fileService.getFile('1', 'readme'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the readme'); + testFile.getType().should.equal('readme'); + + }))); + + it('should return the packageJson file when provided with the package file type', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the packageJson', 'package'); + + fileService['packageJson'] = file; + + let testFile = fileService.getFile('1', 'package'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the packageJson'); + testFile.getType().should.equal('package'); + + }))); + + it('should throw an error if none of the above cases are matched', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let type = 'octopus'; + + (() => { + fileService.getFile(id, type); + }).should.throw(/Type passed must be one of readme, acl, query, script, model or packageJson/); + + }))); + }); + + describe('getReadMe', () => { + it('should return the readme file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + + fileService['readMe'] = file; + + let testReadMeFile = fileService.getEditorReadMe(); + + testReadMeFile.getId().should.equal('1'); + testReadMeFile.getContent().should.equal('this is the readme'); + testReadMeFile.getType().should.equal('readme'); + }))); + }); + + describe('getEditorModelFiles', () => { + it('should return all of the model files', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let file2 = new EditorFile('2', '2', 'this is the model 2', 'model'); + let testModels = new Map(); + + testModels.set('1', file); + testModels.set('2', file2); + + fileService['modelFiles'] = testModels; + + let testModelsArray = fileService.getEditorModelFiles(); + + testModelsArray[0].getId().should.equal('1'); + testModelsArray[0].getContent().should.equal('this is the model'); + testModelsArray[0].getType().should.equal('model'); + + testModelsArray[1].getId().should.equal('2'); + testModelsArray[1].getContent().should.equal('this is the model 2'); + testModelsArray[1].getType().should.equal('model'); + }))); + }); + + describe('getScriptFiles', () => { + it('should return all of the script files', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the script', 'script'); + let file2 = new EditorFile('2', '2', 'this is the script 2', 'script'); + let testScripts = new Map(); + + testScripts.set('1', file); + testScripts.set('2', file2); + + fileService['scriptFiles'] = testScripts; + + let testScriptsArray = fileService.getEditorScriptFiles(); + + testScriptsArray[0].getId().should.equal('1'); + testScriptsArray[0].getContent().should.equal('this is the script'); + testScriptsArray[0].getType().should.equal('script'); + + testScriptsArray[1].getId().should.equal('2'); + testScriptsArray[1].getContent().should.equal('this is the script 2'); + testScriptsArray[1].getType().should.equal('script'); + }))); + }); + + describe('getAclFile', () => { + it('should return the acl file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the acl', 'acl'); + + fileService['aclFile'] = file; + + let testAclFile = fileService.getEditorAclFile(); + + testAclFile.getId().should.equal('1'); + testAclFile.getContent().should.equal('this is the acl'); + testAclFile.getType().should.equal('acl'); + }))); + }); + + describe('getQueryFile', () => { + it('should return the query file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the query', 'query'); + + fileService['queryFile'] = file; + + let testQueryFile = fileService.getEditorQueryFile(); + + testQueryFile.getId().should.equal('1'); + testQueryFile.getContent().should.equal('this is the query'); + testQueryFile.getType().should.equal('query'); + }))); + }); + + describe('getPackageFile', () => { + it('should return the package file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the package', 'package'); + + fileService['packageJson'] = file; + + let testPackageFile = fileService.getEditorPackageFile(); + + testPackageFile.getId().should.equal('1'); + testPackageFile.getContent().should.equal('this is the package'); + testPackageFile.getType().should.equal('package'); + }))); + }); + + describe('getEditorFiles', () => { + + it('should return an empty array if no files stored in the file service', fakeAsync(inject([FileService], (fileService: FileService) => { + let testArray = fileService.getEditorFiles(); + + }))); + + it('should return the readme if only readme stored in the file service', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + + fileService['readMe'] = file; + + let testArray = fileService.getEditorFiles(); + + testArray[0].getId().should.equal('1'); + testArray[0].getContent().should.equal('this is the readme'); + testArray[0].getType().should.equal('readme'); + }))); + + it('should return readme + model files if they are only items stored in the file service', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + let file2 = new EditorFile('1', '1', 'this is the model', 'model'); + + let testModels = new Map(); + testModels.set('1', file2); + + fileService['readMe'] = file; + fileService['modelFiles'] = testModels; + + let testArray = fileService.getEditorFiles(); + + testArray[0].getId().should.equal('1'); + testArray[0].getContent().should.equal('this is the readme'); + testArray[0].getType().should.equal('readme'); + + testArray[1].getId().should.equal('1'); + testArray[1].getContent().should.equal('this is the model'); + testArray[1].getType().should.equal('model'); + }))); + + it('should return readme + model + script files if they are only items stored in the file service', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + let file2 = new EditorFile('1', '1', 'this is the model', 'model'); + let file3 = new EditorFile('1', '1', 'this is the script', 'script'); + + let testModels = new Map(); + let testScripts = new Map(); + + testModels.set('1', file2); + testScripts.set('1', file3); + + fileService['readMe'] = file; + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + + let testArray = fileService.getEditorFiles(); + + testArray[0].getId().should.equal('1'); + testArray[0].getContent().should.equal('this is the readme'); + testArray[0].getType().should.equal('readme'); + + testArray[1].getId().should.equal('1'); + testArray[1].getContent().should.equal('this is the model'); + testArray[1].getType().should.equal('model'); + + testArray[2].getId().should.equal('1'); + testArray[2].getContent().should.equal('this is the script'); + testArray[2].getType().should.equal('script'); + }))); + + it('should return readme + model + script + acl files if they are only items stored in the file service', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + let file2 = new EditorFile('1', '1', 'this is the model', 'model'); + let file3 = new EditorFile('1', '1', 'this is the script', 'script'); + let file4 = new EditorFile('1', '1', 'this is the acl', 'acl'); + + let testModels = new Map(); + let testScripts = new Map(); + + testModels.set('1', file2); + testScripts.set('1', file3); + + fileService['readMe'] = file; + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = file4; + + let testArray = fileService.getEditorFiles(); + + testArray[0].getId().should.equal('1'); + testArray[0].getContent().should.equal('this is the readme'); + testArray[0].getType().should.equal('readme'); + + testArray[1].getId().should.equal('1'); + testArray[1].getContent().should.equal('this is the model'); + testArray[1].getType().should.equal('model'); + + testArray[2].getId().should.equal('1'); + testArray[2].getContent().should.equal('this is the script'); + testArray[2].getType().should.equal('script'); + + testArray[3].getId().should.equal('1'); + testArray[3].getContent().should.equal('this is the acl'); + testArray[3].getType().should.equal('acl'); + }))); + + it('should return readme + model + script + acl + query files if they are only items stored in the file service', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + let file2 = new EditorFile('1', '1', 'this is the model', 'model'); + let file3 = new EditorFile('1', '1', 'this is the script', 'script'); + let file4 = new EditorFile('1', '1', 'this is the acl', 'acl'); + let file5 = new EditorFile('1', '1', 'this is the query', 'query'); + + let testModels = new Map(); + let testScripts = new Map(); + + testModels.set('1', file2); + testScripts.set('1', file3); + + fileService['readMe'] = file; + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = file4; + fileService['queryFile'] = file5; + + let testArray = fileService.getEditorFiles(); + + testArray[0].getId().should.equal('1'); + testArray[0].getContent().should.equal('this is the readme'); + testArray[0].getType().should.equal('readme'); + + testArray[1].getId().should.equal('1'); + testArray[1].getContent().should.equal('this is the model'); + testArray[1].getType().should.equal('model'); + + testArray[2].getId().should.equal('1'); + testArray[2].getContent().should.equal('this is the script'); + testArray[2].getType().should.equal('script'); + + testArray[3].getId().should.equal('1'); + testArray[3].getContent().should.equal('this is the acl'); + testArray[3].getType().should.equal('acl'); + + testArray[4].getId().should.equal('1'); + testArray[4].getContent().should.equal('this is the query'); + testArray[4].getType().should.equal('query'); + }))); + + it('should return readme + model + script + acl + query + pacakage files if they are only items stored in the file service', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + let file2 = new EditorFile('1', '1', 'this is the model', 'model'); + let file3 = new EditorFile('1', '1', 'this is the script', 'script'); + let file4 = new EditorFile('1', '1', 'this is the acl', 'acl'); + let file5 = new EditorFile('1', '1', 'this is the query', 'query'); + let file6 = new EditorFile('1', '1', 'this is the package', 'package'); + + let testModels = new Map(); + let testScripts = new Map(); + + testModels.set('1', file2); + testScripts.set('1', file3); + + fileService['readMe'] = file; + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = file4; + fileService['queryFile'] = file5; + fileService['packageJson'] = file6; + + fileService['includePackageJson'] = true; + let includePackageJson = true; + + let testArray = fileService.getEditorFiles(includePackageJson); + + testArray[0].getId().should.equal('1'); + testArray[0].getContent().should.equal('this is the readme'); + testArray[0].getType().should.equal('readme'); + + testArray[1].getId().should.equal('1'); + testArray[1].getContent().should.equal('this is the model'); + testArray[1].getType().should.equal('model'); + + testArray[2].getId().should.equal('1'); + testArray[2].getContent().should.equal('this is the script'); + testArray[2].getType().should.equal('script'); + + testArray[3].getId().should.equal('1'); + testArray[3].getContent().should.equal('this is the acl'); + testArray[3].getType().should.equal('acl'); + + testArray[4].getId().should.equal('1'); + testArray[4].getContent().should.equal('this is the query'); + testArray[4].getType().should.equal('query'); + + testArray[5].getId().should.equal('1'); + testArray[5].getContent().should.equal('this is the package'); + testArray[5].getType().should.equal('package'); + }))); + }); + + describe('addFile', () => { + it('should add a new model file if one with the same ID does not exist', fakeAsync(inject([FileService], (fileService: FileService) => { + + let id = '1'; + let displayID = '1'; + let content = 'this is the model'; + let type = 'model'; + + fileService.addFile(id, displayID, content, type); + + let testModels = fileService.getEditorModelFiles(); + + testModels[0].getId().should.equal('1'); + testModels[0].getContent().should.equal('this is the model'); + testModels[0].getType().should.equal('model'); + }))); + + it('should throw an error when trying to add a model file with existing ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let testModels = new Map(); + + testModels.set('1', file); + + fileService['modelFiles'] = testModels; + + let id = '1'; + let displayID = '1'; + let content = 'this is the model'; + let type = 'model'; + + (() => { + fileService.addFile(id, displayID, content, type); + }).should.throw(/FileService already contains model file with ID: 1/); + }))); + + it('should add a new script file if one with the same ID does not exist', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let displayID = '1'; + let content = 'this is the script'; + let type = 'script'; + + fileService.addFile(id, displayID, content, type); + + let testScripts = fileService.getEditorScriptFiles(); + + testScripts[0].getId().should.equal('1'); + testScripts[0].getContent().should.equal('this is the script'); + testScripts[0].getType().should.equal('script'); + }))); + + it('should throw an error when trying to add a script file with existing ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the script', 'script'); + let testScripts = new Map(); + + testScripts.set('1', file); + + fileService['scriptFiles'] = testScripts; + + let id = '1'; + let displayID = '1'; + let content = 'this is the script'; + let type = 'script'; + + (() => { + fileService.addFile(id, displayID, content, type); + }).should.throw(/FileService already contains script file with ID: 1/); + }))); + + it('should add a new query file if one with the same ID does not exist', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let displayID = '1'; + let content = 'this is the query'; + let type = 'query'; + + fileService.addFile(id, displayID, content, type); + + let testQuery = fileService.getEditorQueryFile(); + + testQuery.getId().should.equal('1'); + testQuery.getContent().should.equal('this is the query'); + testQuery.getType().should.equal('query'); + }))); + + it('should throw an error when trying to add a query file with existing ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the query', 'query'); + + fileService['queryFile'] = file; + + let id = '1'; + let displayID = '1'; + let content = 'this is the query'; + let type = 'query'; + + (() => { + fileService.addFile(id, displayID, content, type); + }).should.throw(/FileService already contains a query file/); + }))); + + it('should add a new acl file if one with the same ID does not exist', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let displayID = '1'; + let content = 'this is the acl'; + let type = 'acl'; + + fileService.addFile(id, displayID, content, type); + + let testAcl = fileService.getEditorAclFile(); + + testAcl.getId().should.equal('1'); + testAcl.getContent().should.equal('this is the acl'); + testAcl.getType().should.equal('acl'); + }))); + + it('should throw an error when trying to add an acl file with existing ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the acl', 'acl'); + + fileService['aclFile'] = file; + + let id = '1'; + let displayID = '1'; + let content = 'this is the acl'; + let type = 'acl'; + + (() => { + fileService.addFile(id, displayID, content, type); + }).should.throw(/FileService already contains an acl file/); + }))); + + it('should add a new readme if one with the same ID does not exist', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let displayID = '1'; + let content = 'this is the readme'; + let type = 'readme'; + + fileService.addFile(id, displayID, content, type); + + let testReadMe = fileService.getEditorReadMe(); + + testReadMe.getId().should.equal('1'); + testReadMe.getContent().should.equal('this is the readme'); + testReadMe.getType().should.equal('readme'); + }))); + + it('should throw an error when trying to add a readme file with existing ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + + fileService['readMe'] = file; + + let id = '1'; + let displayID = '1'; + let content = 'this is the readme'; + let type = 'readme'; + + (() => { + fileService.addFile(id, displayID, content, type); + }).should.throw(/FileService already contains a readme file/); + }))); + + it('should add a new package if one with the same ID does not exist', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let displayID = '1'; + let content = 'this is the package'; + let type = 'package'; + + fileService.addFile(id, displayID, content, type); + + let testPackage = fileService.getEditorPackageFile(); + + testPackage.getId().should.equal('1'); + testPackage.getContent().should.equal('this is the package'); + testPackage.getType().should.equal('package'); + }))); + + it('should throw an error when trying to add a package file with existing ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the package', 'package'); + + fileService['packageJson'] = file; + + let id = '1'; + let displayID = '1'; + let content = 'this is the package'; + let type = 'package'; + + (() => { + fileService.addFile(id, displayID, content, type); + }).should.throw(/FileService already contains a package.json file/); + }))); + + it('should default to throwing an error', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let displayID = '1'; + let content = 'this is the octopus'; + let type = 'octopus'; + + (() => { + fileService.addFile(id, displayID, content, type); + }).should.throw(/Attempted addition of unknown file type: octopus/); + }))); + }); + + describe('updateFile', () => { + it('should update the correct model file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let testModels = new Map(); + + let id = '1'; + let content = 'this is the NEW model'; + let type = 'model'; + + testModels.set('1', file); + + sinon.stub(fileService, 'getModelFile'); + + fileService['modelFiles'] = testModels; + + fileService.updateFile(id, content, type); + + let testFile = fileService.getFile('1', 'model'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the NEW model'); + testFile.getType().should.equal('model'); + }))); + + it('should update the correct model file with a namespace change', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let testModels = new Map(); + + let id = '1'; + let content = 'this is the NEW model'; + let type = 'model'; + + testModels.set('1', file); + + sinon.stub(fileService, 'getModelFile').returns({getName: sinon.stub().returns('myName')}); + sinon.stub(fileService, 'modelNamespaceCollides').returns(false); + + sinon.stub(fileService, 'createModelFile').returns({ + getNamespace: sinon.stub().returns('myNamespace'), + getName: sinon.stub().returns('myName'), + getDefinitions: sinon.stub().returns('myDefs') + }); + + let addFileStub = sinon.stub(fileService, 'addFile').returns('myFile'); + let deleteFileStub = sinon.stub(fileService, 'deleteFile'); + + fileService['modelFiles'] = testModels; + + let result = fileService.updateFile(id, content, type); + + result.should.equal('myFile'); + + let testFile = fileService.getFile('1', 'model'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the NEW model'); + testFile.getType().should.equal('model'); + + deleteFileStub.should.have.been.called; + }))); + + it('should return if namespace collides', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let testModels = new Map(); + + let id = '1'; + let content = 'this is the NEW model'; + let type = 'model'; + + testModels.set('1', file); + + let addFileStub = sinon.stub(fileService, 'addFile').returns('myFile'); + + sinon.stub(fileService, 'getModelFile').returns({getName: sinon.stub().returns('myName')}); + sinon.stub(fileService, 'modelNamespaceCollides').returns(true); + + sinon.stub(fileService, 'createModelFile').returns({ + getNamespace: sinon.stub().returns('myNamespace'), + getName: sinon.stub().returns('myName'), + getDefinitions: sinon.stub().returns('myDefs') + }); + + fileService['modelFiles'] = testModels; + + fileService.updateFile(id, content, type); + + addFileStub.should.not.have.been.called; + }))); + + it('should throw an error if there is no model file with the given ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let content = 'this is the NEW model'; + let type = 'model'; + + (() => { + fileService.updateFile(id, content, type); + }).should.throw(/File does not exist of type model and id 1/); + }))); + + it('should update the correct script file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the script', 'script'); + let testScripts = new Map(); + + let id = '1'; + let content = 'this is the NEW script'; + let type = 'script'; + + testScripts.set('1', file); + + fileService['scriptFiles'] = testScripts; + + fileService.updateFile(id, content, type); + + let testFile = fileService.getFile('1', 'script'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the NEW script'); + testFile.getType().should.equal('script'); + }))); + + it('should update the correct model file with a not namespace', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let testModels = new Map(); + + let id = '1'; + let content = 'this is the NEW model'; + let type = 'model'; + + testModels.set('1', file); + + sinon.stub(fileService, 'getModelFile').returns({getName: sinon.stub().returns('myName')}); + sinon.stub(fileService, 'createModelFile').returns({ + getNamespace: sinon.stub().returns('1'), + getName: sinon.stub().returns('myName'), + getDefinitions: sinon.stub().returns('myDefs') + }); + + sinon.stub(fileService, 'modelNamespaceCollides').returns(false); + + let addFileStub = sinon.stub(fileService, 'addFile'); + + fileService['modelFiles'] = testModels; + + fileService.updateFile(id, content, type); + + let testFile = fileService.getFile('1', 'model'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the NEW model'); + testFile.getType().should.equal('model'); + + addFileStub.should.not.have.been.called; + }))); + + it('should throw an error if there is no script file with the given ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let content = 'this is the NEW script'; + let type = 'script'; + + (() => { + fileService.updateFile(id, content, type); + }).should.throw(/File does not exist of type script and id 1/); + }))); + + it('should update the correct query file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the query', 'query'); + + let id = '1'; + let content = 'this is the NEW query'; + let type = 'query'; + + fileService['queryFile'] = file; + + fileService.updateFile(id, content, type); + + let testFile = fileService.getFile('1', 'query'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the NEW query'); + testFile.getType().should.equal('query'); + }))); + + it('should throw an error if there is no query file with the given ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let content = 'this is the NEW query'; + let type = 'query'; + + (() => { + fileService.updateFile(id, content, type); + }).should.throw(/Query file does not exist in file service/); + }))); + + it('should update the correct acl file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the acl', 'acl'); + + let id = '1'; + let content = 'this is the NEW acl'; + let type = 'acl'; + + fileService['aclFile'] = file; + + fileService.updateFile(id, content, type); + + let testFile = fileService.getFile('1', 'acl'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the NEW acl'); + testFile.getType().should.equal('acl'); + }))); + + it('should throw an error if there is no acl file with the given ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let content = 'this is the NEW acl'; + let type = 'acl'; + + (() => { + fileService.updateFile(id, content, type); + }).should.throw(/Acl file does not exist in file service/); + }))); + + it('should update the correct readme file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + + let id = '1'; + let content = 'this is the NEW readme'; + let type = 'readme'; + + fileService['readMe'] = file; + + fileService.updateFile(id, content, type); + + let testFile = fileService.getFile('1', 'readme'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.equal('this is the NEW readme'); + testFile.getType().should.equal('readme'); + }))); + + it('should throw an error if there is no readme file with the given ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let content = 'this is the NEW readme'; + let type = 'readme'; + + (() => { + fileService.updateFile(id, content, type); + }).should.throw(/ReadMe file does not exist in file service/); + }))); + + it('should update the correct packageJson file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', '{"name" : "this is the NEW packageJson"}', 'package'); + + let id = '1'; + let content = '{"name" : "this is the NEW packageJson"}'; + let type = 'package'; + + fileService['packageJson'] = file; + + fileService.updateFile(id, content, type); + + let testFile = fileService.getFile('1', 'package'); + + testFile.getId().should.equal('1'); + testFile.getContent().should.deep.equal({name: 'this is the NEW packageJson'}); + testFile.getType().should.equal('package'); + }))); + + it('should throw an error if there is no packageJson file with the given ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let content = 'this is the NEW packageJson'; + let type = 'package'; + + (() => { + fileService.updateFile(id, content, type); + }).should.throw(/PackageJson file does not exist in file service/); + }))); + + it('should default to throwing an error', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let content = 'this is the octopus'; + let type = 'octopus'; + + (() => { + fileService.updateFile(id, content, type); + }).should.throw(/Attempted update of unknown file type: octopus/); + + }))); + }); + + describe('deleteFile', () => { + it('should delete the correct model file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let testModels = new Map(); + + testModels.set('1', file); + + fileService['modelFiles'] = testModels; + + let id = '1'; + let type = 'model'; + + fileService.deleteFile(id, type); + + should.not.exist(testModels.get('1')); + }))); + + it('should delete the correct script file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the script', 'script'); + let testScripts = new Map(); + + testScripts.set('1', file); + + fileService['scriptFiles'] = testScripts; + + let id = '1'; + let type = 'script'; + + fileService.deleteFile(id, type); + + should.not.exist(testScripts.get('1')); + }))); + + it('should delete the correct query file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the query', 'query'); + + let id = '1'; + let type = 'query'; + + fileService['queryFile'] = file; + + fileService.deleteFile(id, type); + + let testQuery = fileService.getEditorQueryFile(); + should.not.exist(testQuery); + }))); + + it('should delete the correct acl file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the acl', 'acl'); + + let id = '1'; + let type = 'acl'; + + fileService['aclFile'] = file; + + fileService.deleteFile(id, type); + + let testAcl = fileService.getEditorAclFile(); + should.not.exist(testAcl); + }))); + + it('should delete the correct readme file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + + let id = '1'; + let type = 'readme'; + + fileService['readMe'] = file; + + fileService.deleteFile(id, type); + + let testReadMe = fileService.getEditorReadMe(); + should.not.exist(testReadMe); + }))); + + it('should delete the correct package file', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the package', 'package'); + + let id = '1'; + let type = 'package'; + + fileService['packageJson'] = file; + + fileService.deleteFile(id, type); + + let testPackage = fileService.getEditorPackageFile(); + should.not.exist(testPackage); + }))); + + it('should default to throwing an error', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let type = 'octopus'; + + (() => { + fileService.deleteFile(id, type); + }).should.throw(/Attempted deletion of file unknown type: octopus/); + + }))); + }); + + describe('deleteAllFiles', () => { + it('should delete all files', fakeAsync(inject([FileService], (fileService: FileService) => { + let file = new EditorFile('1', '1', 'this is the readme', 'readme'); + let file2 = new EditorFile('1', '1', 'this is the model', 'model'); + let file3 = new EditorFile('1', '1', 'this is the script', 'script'); + let file4 = new EditorFile('1', '1', 'this is the acl', 'acl'); + let file5 = new EditorFile('1', '1', 'this is the query', 'query'); + + let testModels = new Map(); + let testScripts = new Map(); + + testModels.set('1', file2); + testScripts.set('1', file3); + + fileService['readMe'] = file; + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = file4; + fileService['queryFile'] = file5; + + fileService.deleteAllFiles(); + + let testReadMe = fileService.getEditorReadMe(); + let testAcl = fileService.getEditorAclFile(); + let testQuery = fileService.getEditorQueryFile(); + + should.not.exist(testReadMe); + should.not.exist(testModels.get('1')); + should.not.exist(testScripts.get('1')); + should.not.exist(testAcl); + should.not.exist(testQuery); + + }))); + }); + + describe('replaceFile', () => { + it('should throw an error if there in no model file with the given "file to replace" ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let oldId = '1'; + let newId = '2'; + let content = 'this is the replacement model file'; + let type = 'model'; + + (() => { + fileService.replaceFile(oldId, newId, content, type); + }).should.throw(/There is no existing file of type model with the id 1/); + }))); + + it('should throw an error if there is an existing model file with the given replacement file ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let oldId = '2'; + let newId = '3'; + let content = 'this is the replacement model file'; + let type = 'model'; + + let file = new EditorFile('2', '2', 'this is the model', 'model'); + let file2 = new EditorFile('3', '3', 'this is the other model', 'model'); + let testModels = new Map(); + testModels.set('2', file); + testModels.set('3', file2); + + fileService['modelFiles'] = testModels; + + (() => { + fileService.replaceFile(oldId, newId, content, type); + }).should.throw(/There is an existing file of type model with the id 2/); + }))); + + it('should throw an error if there in no script file with the given "file to replace" ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let oldId = '1'; + let newId = '2'; + let content = 'this is the replacement script file'; + let type = 'script'; + + (() => { + fileService.replaceFile(oldId, newId, content, type); + }).should.throw(/There is no existing file of type script with the id 1/); + }))); + + it('should throw an error if there is an existing script file with the given replacement file ID', fakeAsync(inject([FileService], (fileService: FileService) => { + let oldId = '2'; + let newId = '3'; + let content = 'this is the replacement script file'; + let type = 'script'; + + let file = new EditorFile('2', '2', 'this is the script', 'script'); + let file2 = new EditorFile('3', '3', 'this is the other script', 'script'); + let testScripts = new Map(); + testScripts.set('2', file); + testScripts.set('3', file2); + + fileService['scriptFiles'] = testScripts; + + (() => { + fileService.replaceFile(oldId, newId, content, type); + }).should.throw(/There is an existing file of type script with the id 2/); + }))); + + it('should default to throw an error', fakeAsync(inject([FileService], (fileService: FileService) => { + let oldId = '1'; + let newId = '2'; + let content = 'this is the replacement octopus'; + let type = 'octopus'; + + (() => { + fileService.replaceFile(oldId, newId, content, type); + }).should.throw(/Attempted replace of ununsupported file type: octopus/); + }))); + + it('should correctly replace a model file', fakeAsync(inject([FileService], (fileService: FileService) => { + let oldId = '1'; + let newId = '2'; + let content = 'this is the replacement model file'; + let type = 'model'; + + modelFileMock.getNamespace.returns('model-ns'); + modelFileMock.getName.returns('model-name'); + sinon.stub(fileService, 'createModelFile').returns(modelFileMock); + + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let testModels = new Map(); + testModels.set('1', file); + + fileService['modelFiles'] = testModels; + + let replacedFile = fileService.replaceFile(oldId, newId, content, type); + }))); + + it('should correctly replace a model file', fakeAsync(inject([FileService], (fileService: FileService) => { + let oldId = '1'; + let newId = '2'; + let content = 'this is the replacement model file'; + let type = 'model'; + + modelFileMock.getNamespace.returns('model-ns'); + modelFileMock.getName.returns('model-name'); + let createmodelMock = sinon.stub(fileService, 'createModelFile'); + createmodelMock.onCall(0).throws(); + createmodelMock.onCall(1).returns(modelFileMock); + + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let testModels = new Map(); + testModels.set('1', file); + + fileService['modelFiles'] = testModels; + + let replacedFile = fileService.replaceFile(oldId, newId, content, type); + }))); + + it('should correctly replace a model file', fakeAsync(inject([FileService], (fileService: FileService) => { + let oldId = '1'; + let newId = '2'; + let content = 'this is the replacement model file'; + let type = 'model'; + + modelFileMock.getNamespace.returns('model-ns'); + modelFileMock.getName.returns('model-name'); + let createmodelMock = sinon.stub(fileService, 'createModelFile'); + createmodelMock.onCall(0).throws(); + createmodelMock.onCall(1).throws(); + createmodelMock.onCall(2).returns(modelFileMock); + + let file = new EditorFile('1', '1', 'this is the model', 'model'); + let testModels = new Map(); + testModels.set('1', file); + + fileService['modelFiles'] = testModels; + + (() => { + fileService.replaceFile(oldId, newId, content, type); + }).should.throw(); + }))); + + it('should correctly replace a script file', fakeAsync(inject([FileService], (fileService: FileService) => { + let oldId = '1'; + let newId = '2'; + let content = 'this is the replacement script file'; + let type = 'script'; + + let file = new EditorFile('1', '1', 'this is the script', 'script'); + let testScripts = new Map(); + testScripts.set('1', file); + + fileService['scriptFiles'] = testScripts; + + let replacedFile = fileService.replaceFile(oldId, newId, content, type); + + replacedFile.getId().should.equal('2'); + replacedFile.getContent().should.equal('this is the script'); + replacedFile.getType().should.equal('script'); + }))); + }); + + describe('validateFile', () => { + it('should validate a given model file', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let type = 'model'; + + let testModels = new Map(); + let testScripts = new Map(); + + businessNetworkDefMock.getModelManager.returns({getModelFile: sinon.stub()}); + + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + let mockModelFile = sinon.createStubInstance(EditorFile); + mockModelFile.validate.returns(null); + + // cases to throw if validation slips in to incorrect case. + + let mockScriptFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.throws('should not be called'); + + let mockAclFile = sinon.createStubInstance(EditorFile); + mockAclFile.validate.throws('should not be called'); + + let mockQueryFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.throws('should not be called'); + + testModels.set('1', mockModelFile); + testScripts.set('1', mockScriptFile); + + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = mockAclFile; + fileService['queryFile'] = mockQueryFile; + + should.not.exist(fileService.validateFile(id, type)); + }))); + + it('should throw an error if namespace collides given model file', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let type = 'model'; + + let testModels = new Map(); + let testScripts = new Map(); + + businessNetworkDefMock.getModelManager.returns({getModelFile: sinon.stub().returns({getName: sinon.stub().returns('myName')})}); + + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + sinon.stub(fileService, 'createModelFile').returns({getNamespace: sinon.stub().returns('myNamespace')}); + + let mockNamspaceCollides = sinon.stub(fileService, 'modelNamespaceCollides').returns(true); + + let mockModelFile = sinon.createStubInstance(EditorFile); + mockModelFile.validate.returns(null); + + // cases to throw if validation slips in to incorrect case. + + let mockScriptFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.throws('should not be called'); + + let mockAclFile = sinon.createStubInstance(EditorFile); + mockAclFile.validate.throws('should not be called'); + + let mockQueryFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.throws('should not be called'); + + testModels.set('1', mockModelFile); + testScripts.set('1', mockScriptFile); + + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = mockAclFile; + fileService['queryFile'] = mockQueryFile; + + let result = fileService.validateFile(id, type); + result.toString().should.equal('Error: The namespace collides with existing model namespace myNamespace'); + }))); + + it('should validate if already exists', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let type = 'model'; + + let testModels = new Map(); + let testScripts = new Map(); + + businessNetworkDefMock.getModelManager.returns({getModelFile: sinon.stub().returns({getName: sinon.stub().returns('myName')})}); + + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + sinon.stub(fileService, 'createModelFile').returns({getNamespace: sinon.stub().returns('myNamespace')}); + + let mockNamspaceCollides = sinon.stub(fileService, 'modelNamespaceCollides').returns(false); + + let mockModelFile = sinon.createStubInstance(EditorFile); + mockModelFile.validate.returns(null); + + // cases to throw if validation slips in to incorrect case. + + let mockScriptFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.throws('should not be called'); + + let mockAclFile = sinon.createStubInstance(EditorFile); + mockAclFile.validate.throws('should not be called'); + + let mockQueryFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.throws('should not be called'); + + testModels.set('1', mockModelFile); + testScripts.set('1', mockScriptFile); + + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = mockAclFile; + fileService['queryFile'] = mockQueryFile; + + should.not.exist(fileService.validateFile(id, type)); + }))); + + it('should validate a given script file', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let type = 'script'; + + let testScripts = new Map(); + let testModels = new Map(); + + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + let mockScriptFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.returns(null); + + // cases to throw if validation slips in to incorrect case. + + let mockModelFile = sinon.createStubInstance(EditorFile); + mockModelFile.validate.throws('should not be called'); + + let mockAclFile = sinon.createStubInstance(EditorFile); + mockAclFile.validate.throws('should not be called'); + + let mockQueryFile = sinon.createStubInstance(EditorFile); + mockQueryFile.validate.throws('should not be called'); + + testScripts.set('1', mockScriptFile); + testModels.set('1', mockModelFile); + + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = mockAclFile; + fileService['queryFile'] = mockQueryFile; + + should.not.exist(fileService.validateFile(id, type)); + }))); + + it('should validate a given acl file', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let type = 'acl'; + + let testScripts = new Map(); + let testModels = new Map(); + + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + let mockAclFile = sinon.createStubInstance(EditorFile); + mockAclFile.validate.returns(null); + + // cases to throw if validation slips in to incorrect case. + + let mockScriptFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.throws('should not be called'); + + let mockModelFile = sinon.createStubInstance(EditorFile); + mockModelFile.validate.throws('should not be called'); + + let mockQueryFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.throws('should not be called'); + + testModels.set('1', mockModelFile); + testScripts.set('1', mockScriptFile); + + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = mockAclFile; + fileService['queryFile'] = mockQueryFile; + + should.not.exist(fileService.validateFile(id, type)); + }))); + + it('should validate a given query file', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let type = 'query'; + + let testScripts = new Map(); + let testModels = new Map(); + + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + let mockQueryFile = sinon.createStubInstance(EditorFile); + mockQueryFile.validate.returns(null); + + // cases to throw if validation slips in to incorrect case. + + let mockScriptFile = sinon.createStubInstance(EditorFile); + mockScriptFile.validate.throws('should not be called'); + + let mockAclFile = sinon.createStubInstance(EditorFile); + mockAclFile.validate.throws('should not be called'); + + let mockModelFile = sinon.createStubInstance(EditorFile); + mockModelFile.validate.throws('should not be called'); + + testModels.set('1', mockModelFile); + testScripts.set('1', mockScriptFile); + + fileService['modelFiles'] = testModels; + fileService['scriptFiles'] = testScripts; + fileService['aclFile'] = mockAclFile; + fileService['queryFile'] = mockQueryFile; + + should.not.exist(fileService.validateFile(id, type)); + }))); + + it('should validate package file', inject([FileService], (fileService: FileService) => { + mockClientService.getBusinessNetwork.returns({getName: sinon.stub().returns('myName')}); + + let editorFile = new EditorFile('myId', 'myDisplay', 'myContent', 'package'); + + editorFile.setContent({name: 'myName'}); + + fileService['packageJson'] = editorFile; + + should.not.exist(fileService.validateFile('package', 'package')); + })); + + it('should not validate package file', inject([FileService], (fileService: FileService) => { + mockClientService.getBusinessNetwork.returns({getName: sinon.stub().returns('myOtherName')}); + + let editorFile = new EditorFile('myId', 'myDisplay', 'myContent', 'package'); + + editorFile.setContent({name: 'myName'}); + + fileService['packageJson'] = editorFile; + + let result = fileService.validateFile('package', 'package'); + result.toString().should.contain('Unsupported attempt to update Business Network Name.'); + })); + + it('should throw an error when no match with provided file type', fakeAsync(inject([FileService], (fileService: FileService) => { + let id = '1'; + let type = 'octopus'; + + let result = fileService.validateFile(id, type); + result.toString().should.contain('Attempted validation of unknown file of type: octopus'); + }))); + }); + + describe('set and get current file', () => { + it('should set and get the current file for the editor', inject([FileService], (fileService: FileService) => { + let TEST = {name: 'foo', val: 'bar'}; + assert.isNull(fileService.getCurrentFile()); + fileService.setCurrentFile(TEST); + assert.equal(fileService.getCurrentFile(), TEST); + })); + }); + + describe('loadFiles', () => { + let deleteAllFileStub; + let addFileStub; + let getFilesStub; + + let aclStub; + let queryStub; + let metaStub; + + beforeEach(inject([FileService], (fileService: FileService) => { + deleteAllFileStub = sinon.stub(fileService, 'deleteAllFiles'); + addFileStub = sinon.stub(fileService, 'addFile'); + getFilesStub = sinon.stub(fileService, 'getEditorFiles').returns(['myFiles']); + + sinon.stub(fileService, 'getModelFiles').returns([{ + getNamespace: sinon.stub().returns('myNameSpace'), + getName: sinon.stub().returns('myName'), + getDefinitions: sinon.stub().returns('myDefs') + }]); + + sinon.stub(fileService, 'getScripts').returns([{ + getIdentifier: sinon.stub().returns('myId'), + getContents: sinon.stub().returns('contents') + }]); + + aclStub = sinon.stub(fileService, 'getAclFile').returns({ + getIdentifier: sinon.stub().returns('myId'), + getDefinitions: sinon.stub().returns('myDefs') + }); + + queryStub = sinon.stub(fileService, 'getQueryFile').returns({ + getIdentifier: sinon.stub().returns('myId'), + getDefinitions: sinon.stub().returns('myDefs') + }); + + metaStub = sinon.stub(fileService, 'getMetaData').returns({ + getPackageJson: sinon.stub().returns('myJson'), + getREADME: sinon.stub().returns('myReadMe') + }); + })); + + it('should load all the files', inject([FileService], (fileService: FileService) => { + + let result = fileService.loadFiles(); + + result.should.deep.equal(['myFiles']); + + deleteAllFileStub.should.have.been.called; + + addFileStub.firstCall.should.have.been.calledWith('myNameSpace', 'myName', 'myDefs', 'model'); + addFileStub.secondCall.should.have.been.calledWith('myId', 'myId', 'contents', 'script'); + addFileStub.thirdCall.should.have.been.calledWith('myId', 'myId', 'myDefs', 'acl'); + addFileStub.getCall(3).should.have.been.calledWith('myId', 'myId', 'myDefs', 'query'); + addFileStub.getCall(4).should.have.been.calledWith('readme', 'README.md', 'myReadMe', 'readme'); + addFileStub.getCall(5).should.have.been.calledWith('package', 'package.json', 'myJson', 'package'); + + getFilesStub.should.have.been.called; + + should.not.exist(fileService['currentFile']); + })); + + it('should load files without acl, query, package and readme', inject([FileService], (fileService: FileService) => { + aclStub.returns(null); + + queryStub.returns(null); + + metaStub.returns({getREADME: sinon.stub(), getPackageJson: sinon.stub()}); + + let result = fileService.loadFiles(); + + result.should.deep.equal(['myFiles']); + + deleteAllFileStub.should.have.been.called; + + addFileStub.firstCall.should.have.been.calledWith('myNameSpace', 'myName', 'myDefs', 'model'); + addFileStub.secondCall.should.have.been.calledWith('myId', 'myId', 'contents', 'script'); + + addFileStub.callCount.should.equal(2); + + getFilesStub.should.have.been.called; + + should.not.exist(fileService['currentFile']); + })); + + it('should load all the files and unset currentFile', inject([FileService], (fileService: FileService) => { + + let file = { + getType: sinon.stub().returns('model'), + getContent: sinon.stub().returns('myContent'), + getId: sinon.stub().returns('myId') + }; + + let file2 = { + getType: sinon.stub().returns('script'), + getContent: sinon.stub().returns('myContent'), + getId: sinon.stub().returns('myId') + }; + + fileService['currentFile'] = file; + + getFilesStub.returns([file2]); + + let result = fileService.loadFiles(); + + result[0].should.deep.equal(file2); + + deleteAllFileStub.should.have.been.called; + + addFileStub.firstCall.should.have.been.calledWith('myNameSpace', 'myName', 'myDefs', 'model'); + addFileStub.secondCall.should.have.been.calledWith('myId', 'myId', 'contents', 'script'); + addFileStub.thirdCall.should.have.been.calledWith('myId', 'myId', 'myDefs', 'acl'); + addFileStub.getCall(3).should.have.been.calledWith('myId', 'myId', 'myDefs', 'query'); + addFileStub.getCall(4).should.have.been.calledWith('readme', 'README.md', 'myReadMe', 'readme'); + + getFilesStub.should.have.been.called; + + should.not.exist(fileService['currentFile']); + })); + + it('should load all the files and keep currentFile', inject([FileService], (fileService: FileService) => { + + let file = { + getType: sinon.stub().returns('model'), + getContent: sinon.stub().returns('myContent'), + getId: sinon.stub().returns('myId') + }; + + fileService['currentFile'] = file; + + getFilesStub.returns([file]); + + let result = fileService.loadFiles(); + + result.should.deep.equal([file]); + + deleteAllFileStub.should.have.been.called; + + addFileStub.firstCall.should.have.been.calledWith('myNameSpace', 'myName', 'myDefs', 'model'); + addFileStub.secondCall.should.have.been.calledWith('myId', 'myId', 'contents', 'script'); + addFileStub.thirdCall.should.have.been.calledWith('myId', 'myId', 'myDefs', 'acl'); + addFileStub.getCall(3).should.have.been.calledWith('myId', 'myId', 'myDefs', 'query'); + addFileStub.getCall(4).should.have.been.calledWith('readme', 'README.md', 'myReadMe', 'readme'); + + getFilesStub.should.have.been.called; + + fileService['currentFile'].should.deep.equal(file); + })); + }); + + describe('updateBusinessNetwork', () => { + let updateBusinessNetworkFileStub; + + beforeEach(inject([FileService], (fileService: FileService) => { + updateBusinessNetworkFileStub = sinon.stub(fileService, 'updateBusinessNetworkFile'); + })); + + it('should update a model file', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'model'); + + sinon.stub(fileService, 'getModelFile').returns('myFile'); + + fileService.updateBusinessNetwork('oldId', editorFile); + + updateBusinessNetworkFileStub.should.have.been.calledWith('oldId', 'myContent', 'model'); + })); + + it('should add a model file', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'model'); + + sinon.stub(fileService, 'getModelFile'); + + let mockModelManager = { + addModelFile: sinon.stub() + }; + + businessNetworkDefMock.getModelManager.returns(mockModelManager); + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + fileService.updateBusinessNetwork('oldId', editorFile); + + mockModelManager.addModelFile.should.have.been.calledWith('myContent'); + })); + + it('should update a script file', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'script'); + + sinon.stub(fileService, 'getScriptFile').returns('myFile'); + + fileService.updateBusinessNetwork('oldId', editorFile); + + updateBusinessNetworkFileStub.should.have.been.calledWith('oldId', 'myContent', 'script'); + })); + + it('should add a script file', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'script'); + + sinon.stub(fileService, 'getScriptFile'); + sinon.stub(fileService, 'createScriptFile').returns('myScriptFile'); + + let mockScriptManager = { + addScript: sinon.stub() + }; + + businessNetworkDefMock.getScriptManager.returns(mockScriptManager); + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + fileService.updateBusinessNetwork('oldId', editorFile); + + mockScriptManager.addScript.should.have.been.calledWith('myScriptFile'); + })); + + it('should update a acl file', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'acl'); + + sinon.stub(fileService, 'getAclFile').returns('myFile'); + + fileService.updateBusinessNetwork('oldId', editorFile); + + updateBusinessNetworkFileStub.should.have.been.calledWith('oldId', 'myContent', 'acl'); + })); + + it('should add a acl file', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'acl'); + + sinon.stub(fileService, 'getAclFile'); + sinon.stub(fileService, 'createAclFile').returns('myAclFile'); + + let mockAclManager = { + setAclFile: sinon.stub() + }; + + businessNetworkDefMock.getAclManager.returns(mockAclManager); + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + fileService.updateBusinessNetwork('oldId', editorFile); + + mockAclManager.setAclFile.should.have.been.calledWith('myAclFile'); + })); + + it('should update a query file', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'query'); + + sinon.stub(fileService, 'getQueryFile').returns('myFile'); + + fileService.updateBusinessNetwork('oldId', editorFile); + + updateBusinessNetworkFileStub.should.have.been.calledWith('oldId', 'myContent', 'query'); + })); + + it('should add a query file', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'query'); + + sinon.stub(fileService, 'getQueryFile'); + sinon.stub(fileService, 'createQueryFile').returns('myQueryFile'); + + let mockQueryManager = { + setQueryFile: sinon.stub() + }; + + businessNetworkDefMock.getQueryManager.returns(mockQueryManager); + + fileService['currentBusinessNetwork'] = businessNetworkDefMock; + + fileService.updateBusinessNetwork('oldId', editorFile); + + mockQueryManager.setQueryFile.should.have.been.calledWith('myQueryFile'); + })); + + it('should update read me', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'readme'); + + fileService.updateBusinessNetwork('oldId', editorFile); + + updateBusinessNetworkFileStub.should.have.been.calledWith('oldId', 'myContent', 'readme'); + })); + + it('should update package json', inject([FileService], (fileService: FileService) => { + let editorFile = new EditorFile('myId', 'myDisplayId', 'myContent', 'package'); + + fileService.updateBusinessNetwork('oldId', editorFile); + + updateBusinessNetworkFileStub.should.have.been.calledWith('oldId', 'myContent', 'package'); + })); + }); + + describe('createAclFile', () => { + let mockBusinessNetwork; + + beforeEach(() => { + mockBusinessNetwork = sinon.createStubInstance(BusinessNetworkDefinition); + }); + + it('should create an ACL file', fakeAsync(inject([FileService], (service: FileService) => { + service['currentBusinessNetwork'] = mockBusinessNetwork; + let allRule = 'rule SystemACL {description: "System ACL to permit all access" participant: "org.hyperledger.composer.system.Participant" operation: ALL resource: "org.hyperledger.composer.system.**" action: ALLOW}'; + let aclFile = service.createAclFile('permissions', allRule); + aclFile.should.be.instanceOf(AclFile); + mockBusinessNetwork.getModelManager.should.have.been.called; + }))); + }); + + describe('getModelFile', () => { + it('should get the model file', inject([FileService], (service: FileService) => { + service['currentBusinessNetwork'] = businessNetworkDefMock; + let modelManagerMock = { + getModelFile: sinon.stub().returns(modelFileMock) + }; + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + + let result = service.getModelFile('testId'); + + result.should.deep.equal(modelFileMock); + modelManagerMock.getModelFile.should.have.been.calledWith('testId'); + })); + }); + + describe('updateBusinessNetworkFile', () => { + let businessNetworkChangedSpy; + let modelManagerMock; + let namespaceChangedSpy; + let mockNamespaceCollide; + + beforeEach(inject([FileService], (service: FileService) => { + service['currentBusinessNetwork'] = businessNetworkDefMock; + mockNamespaceCollide = sinon.stub(service, 'modelNamespaceCollides').returns(false); + businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); + namespaceChangedSpy = sinon.spy(service.namespaceChanged$, 'next'); + + modelManagerMock = { + addModelFile: sinon.stub(), + updateModelFile: sinon.stub(), + deleteModelFile: sinon.stub(), + getModelFile: sinon.stub().returns(modelFileMock), + }; + })); + + it('should update a model file if id matches namespace', inject([FileService], (service: FileService) => { + modelFileMock.getNamespace.returns('model-ns'); + modelFileMock.getName.returns('model.cto'); + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + + let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); + + let result = service.updateBusinessNetworkFile('model-ns', 'my-model-content', 'model'); + + modelManagerMock.updateModelFile.should.have.been.calledWith(modelFileMock); + modelManagerMock.addModelFile.should.not.have.been.called; + should.not.exist(result); + })); + + it('should replace a model file if id does not match namespace', inject([FileService], (service: FileService) => { + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + modelFileMock.getNamespace.returns('model-ns'); + modelFileMock.getName.returns('model.cto'); + + let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); + + let result = service.updateBusinessNetworkFile('diff-model-ns', 'my-model-content', 'model'); + + modelManagerMock.addModelFile.should.have.been.calledWith(modelFileMock); + should.not.exist(result); + })); + + it('should notify if model file namespace changes', inject([FileService], (service: FileService) => { + + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + modelFileMock.getNamespace.returns('new-model-ns'); + modelManagerMock.getModelFile.returns(modelFileMock); + + let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); + + service.updateBusinessNetworkFile('model-ns', 'my-model-content', 'model'); + + namespaceChangedSpy.should.have.been.calledWith('new-model-ns'); + })); + + it('should update a script file', inject([FileService], (service: FileService) => { + let scriptManagerMock = { + createScript: sinon.stub().returns(scriptFileMock), + addScript: sinon.stub() + }; + + businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); + + let result = service.updateBusinessNetworkFile('script', 'my-script', 'script'); + + scriptManagerMock.createScript.should.have.been.calledWith('script', 'JS', 'my-script'); + scriptManagerMock.addScript.should.have.been.calledWith(scriptFileMock); + should.not.exist(result); + })); + + it('should update a acl file', inject([FileService], (service: FileService) => { + let aclManagerMock = { + setAclFile: sinon.stub() + }; + + businessNetworkDefMock.getAclManager.returns(aclManagerMock); + + let mockCreateAclFile = sinon.stub(service, 'createAclFile').returns(aclFileMock); + + let result = service.updateBusinessNetworkFile('acl', 'my-acl', 'acl'); + + aclManagerMock.setAclFile.should.have.been.calledWith(aclFileMock); + should.not.exist(result); + })); + + it('should update a query file', inject([FileService], (service: FileService) => { + let queryManagerMock = { + setQueryFile: sinon.stub() + }; + + businessNetworkDefMock.getQueryManager.returns(queryManagerMock); + + let mockCreateQueryFile = sinon.stub(service, 'createQueryFile').returns(queryFileMock); + + // call function + let result = service.updateBusinessNetworkFile('query', 'my-query', 'query'); + + queryManagerMock.setQueryFile.should.have.been.calledWith(queryFileMock); + should.not.exist(result); + })); + + it('should update a package.json file', inject([FileService], (service: FileService) => { + let mockSetPackage = sinon.stub(service, 'setBusinessNetworkPackageJson'); + let packageJson = JSON.stringify({name: 'my name'}); + + // call function + let result = service.updateBusinessNetworkFile('package', packageJson, 'package'); + + mockSetPackage.should.have.been.calledWith(packageJson); + })); + + it('should update a readme file', inject([FileService], (service: FileService) => { + let mockSetReadme = sinon.stub(service, 'setBusinessNetworkReadme'); + + // call function + let result = service.updateBusinessNetworkFile('readme.md', 'read this', 'readme'); + + mockSetReadme.should.have.been.calledWith('read this'); + })); + + it('should not replace a model file if id does not match namespace and file is invalid', inject([FileService], (service: FileService) => { + + let error = new Error('invalid'); + modelManagerMock = { + addModelFile: sinon.stub().throws(error), + updateModelFile: sinon.stub().throws(error), + deleteModelFile: sinon.stub(), + getModelFile: sinon.stub().returns(modelFileMock) + }; + + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + + modelFileMock.getNamespace.returns('new-model'); + let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); + + try { + service.updateBusinessNetworkFile('model', 'my-model', 'model'); + } catch (e) { + e.should.deep.equal(error); + } + })); + + it('should not update an invalid script file', inject([FileService], (service: FileService) => { + let error = new Error('invalid'); + let scriptManagerMock = { + createScript: sinon.stub().throws(error), + addScript: sinon.stub() + }; + + businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); + + try { + service.updateBusinessNetworkFile('script', 'my-script', 'script'); + } catch (e) { + e.should.deep.equal(error); + } + })); + + it('should not update an invalid acl file', inject([FileService], (service: FileService) => { + let error = new Error('invalid'); + + let aclManagerMock = { + setAclFile: sinon.stub().throws(error) + }; + + businessNetworkDefMock.getAclManager.returns(aclManagerMock); + let mockCreateAclFile = sinon.stub(service, 'createAclFile').returns(aclFileMock); + + try { + service.updateBusinessNetworkFile('acl', 'my-acl', 'acl'); + } catch (e) { + e.should.deep.equal(error); + } + })); + + it('should not update a model file if namespace collision detected', inject([FileService], (service: FileService) => { + let error = new Error('The namespace collides with existing model namespace new-model'); + + modelManagerMock = { + addModelFile: sinon.stub(), + updateModelFile: sinon.stub(), + deleteModelFile: sinon.stub(), + getModelFile: sinon.stub().returns(modelFileMock) + }; + + mockNamespaceCollide.returns(true); + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + + modelFileMock.getNamespace.returns('new-model'); + let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); + + try { + service.updateBusinessNetworkFile('model', 'my-model', 'model'); + } catch (e) { + e.toString().should.equal(error.toString()); + } + + modelManagerMock.updateModelFile.should.not.have.been.called; + })); + + it('should return error message if type is invalid', inject([FileService], (service: FileService) => { + let error = new Error('Attempted update of unknown file of type: wombat'); + + try { + service.updateBusinessNetworkFile('bad.file', 'content of wombat type', 'wombat'); + } catch (e) { + e.toString().should.equal(error.toString()); + } + })); + }); + + describe('replaceBusinessNetworkFile', () => { + it('should handle error case by notifying and returning error message in string', inject([FileService], (service: FileService) => { + service['currentBusinessNetwork'] = businessNetworkDefMock; + service['currentBusinessNetwork'].getModelManager.throws(new Error('Forced Error')); + let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); + + let response = service.replaceBusinessNetworkFile('oldId', 'newId', 'content', 'model'); + + businessNetworkChangedSpy.should.have.been.calledWith(false); + response.should.equal('Error: Forced Error'); + + })); + + it('should replace a model file by model manager update', inject([FileService], (service: FileService) => { + + let modelManagerMock = { + updateModelFile: sinon.stub() + }; + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + service['currentBusinessNetwork'] = businessNetworkDefMock; + + let mockCreateModelFile = sinon.stub(service, 'createModelFile').returns(modelFileMock); + + let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); + + // Call the method (model) + let response = service.replaceBusinessNetworkFile('oldId', 'newId', 'content', 'model'); + + // Check correct items were called with correct parameters + modelManagerMock.updateModelFile.should.have.been.calledWith(modelFileMock, 'newId'); + businessNetworkChangedSpy.should.have.been.calledWith(true); + should.not.exist(response); + })); + + it('should replace a script file by deletion and addition', inject([FileService], (service: FileService) => { + + let scriptManagerMock = { + createScript: sinon.stub().returns(scriptFileMock), + addScript: sinon.stub(), + deleteScript: sinon.stub() + }; + businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); + service['currentBusinessNetwork'] = businessNetworkDefMock; + + let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); + + // Call the method (script) + let response = service.replaceBusinessNetworkFile('oldId', 'newId', 'content', 'script'); + + // Check correct items were called with correct parameters + scriptManagerMock.addScript.should.have.been.calledWith(scriptFileMock); + scriptManagerMock.deleteScript.should.have.been.calledWith('oldId'); + businessNetworkChangedSpy.should.have.been.calledWith(true); + should.not.exist(response); + })); + + it('should return error message if type is invalid', inject([FileService], (service: FileService) => { + let result = service.replaceBusinessNetworkFile('oldId', 'newId', 'content', 'wombat'); + result.should.equal('Error: Attempted replace of ununsupported file type: wombat'); + })); + }); + + describe('modelNamespaceCollides', () => { + + let modelManagerMock; + let mockCreateBusinessNetwork; + let mockFile0 = sinon.createStubInstance(ModelFile); + mockFile0.getNamespace.returns('name0'); + let mockFile1 = sinon.createStubInstance(ModelFile); + mockFile1.getNamespace.returns('name1'); + let mockFile2 = sinon.createStubInstance(ModelFile); + mockFile2.getNamespace.returns('name2'); + let mockFile3 = sinon.createStubInstance(ModelFile); + mockFile3.getNamespace.returns('name3'); + let mockFile4 = sinon.createStubInstance(ModelFile); + mockFile4.getNamespace.returns('name4'); + + beforeEach(inject([FileService], (service: FileService) => { + modelManagerMock = { + getModelFiles: sinon.stub().returns([mockFile0, mockFile1, mockFile2, mockFile3, mockFile4]) + }; + + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + service['currentBusinessNetwork'] = businessNetworkDefMock; + })); + + it('should return true if namespace collision detected', inject([FileService], (service: FileService) => { + + let result = service.modelNamespaceCollides('name1', 'something-different'); + result.should.be.equal(true); + + })); + + it('should return false if no namespace collision detected with new name', inject([FileService], (service: FileService) => { + + let result = service.modelNamespaceCollides('not-in-list', 'something-different'); + result.should.be.equal(false); + + })); + + it('should handle no previousNamespace being passed', inject([FileService], (service: FileService) => { + + let result = service.modelNamespaceCollides('new-namespace', null); + result.should.be.equal(false); + + })); + + it('should handle no model files existing in BND', inject([FileService], (service: FileService) => { + modelManagerMock = { + getModelFiles: sinon.stub().returns([]) + }; + + let result = service.modelNamespaceCollides('not-in-list', 'something-different'); + result.should.be.equal(false); + })); + + }); + + describe('getScriptFile', () => { + it('should get the script file', inject([FileService], (service: FileService) => { + let scriptManagerMock = { + getScript: sinon.stub().returns(scriptFileMock) + }; + businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); + service['currentBusinessNetwork'] = businessNetworkDefMock; + + let result = service.getScriptFile('testId'); + + result.should.deep.equal(scriptFileMock); + scriptManagerMock.getScript.should.have.been.calledWith('testId'); + })); + }); + + describe('getAclFile', () => { + it('should get the acl file', inject([FileService], (service: FileService) => { + let aclManagerMock = { + getAclFile: sinon.stub().returns(aclFileMock) + }; + businessNetworkDefMock.getAclManager.returns(aclManagerMock); + service['currentBusinessNetwork'] = businessNetworkDefMock; + + let result = service.getAclFile(); + + result.should.deep.equal(aclFileMock); + aclManagerMock.getAclFile.should.have.been.called; + })); + }); + + describe('getQueryFile', () => { + it('should get the query file', inject([FileService], (service: FileService) => { + let queryManagerMock = { + getQueryFile: sinon.stub().returns(queryFileMock) + }; + + businessNetworkDefMock.getQueryManager.returns(queryManagerMock); + service['currentBusinessNetwork'] = businessNetworkDefMock; + + let result = service.getQueryFile(); + + result.should.deep.equal(queryFileMock); + queryManagerMock.getQueryFile.should.have.been.called; + })); + }); + + describe('createQueryFile', () => { + let mockBusinessNetwork; + + beforeEach(() => { + mockBusinessNetwork = sinon.createStubInstance(BusinessNetworkDefinition); + }); + + it('should create a Query file', fakeAsync(inject([FileService], (service: FileService) => { + service['currentBusinessNetwork'] = mockBusinessNetwork; + let queryFile = service.createQueryFile('query', ''); + queryFile.should.be.instanceOf(QueryFile); + mockBusinessNetwork.getModelManager.should.have.been.called; + }))); + + }); + + describe('setBusinessNetwork...', () => { + beforeEach(inject([FileService], (service: FileService) => { + let modelManagerMock = { + getModelFiles: sinon.stub().returns([modelFileMock, modelFileMock]), + addModelFiles: sinon.stub() + }; + + let aclManagerMock = { + setAclFile: sinon.stub(), + getAclFile: sinon.stub().returns(aclFileMock) + }; + + let scriptManagerMock = { + getScripts: sinon.stub().returns([scriptFileMock, scriptFileMock]), + addScript: sinon.stub() + }; + + let queryManagerMock = { + setQueryFile: sinon.stub(), + getQueryFile: sinon.stub().returns(queryFileMock) + }; + + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); + businessNetworkDefMock.getAclManager.returns(aclManagerMock); + businessNetworkDefMock.getQueryManager.returns(queryManagerMock); + + service['currentBusinessNetwork'] = businessNetworkDefMock; + })); + + it('should set business network readme', inject([FileService], (service: FileService) => { + let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); + + businessNetworkDefMock.getMetadata.returns({ + setReadme: sinon.stub() + }); + + service.setBusinessNetworkReadme('my readme'); + + businessNetworkDefMock.setReadme.should.have.been.calledWith('my readme'); + })); + + it('should set business network version', inject([FileService], (service: FileService) => { + let businessNetworkChangedSpy = sinon.spy(service.businessNetworkChanged$, 'next'); + + businessNetworkDefMock.getMetadata.returns({ + getName: sinon.stub().returns('my name'), + getPackageJson: sinon.stub().returns({version: '0.0'}), + setPackageJson: sinon.stub() + }); + + service.setBusinessNetworkVersion('new_version'); + + businessNetworkDefMock.setPackageJson.should.have.been.calledWith({version: 'new_version'}); + businessNetworkChangedSpy.should.have.been.calledWith(true); + })); + + it('should set business network packageJson', inject([FileService], (service: FileService) => { + businessNetworkDefMock.getMetadata.returns({ + getName: sinon.stub().returns('my name') + }); + + let packageJson = {name: 'my name', version: 'my version', description: 'my description'}; + + service.setBusinessNetworkPackageJson(packageJson); + + businessNetworkDefMock.setPackageJson.should.have.been.calledWith(packageJson); + })); + }); + + describe('getModelFiles', () => { + it('should get model files', inject([FileService], (service: FileService) => { + let modelManagerMock = { + getModelFiles: sinon.stub().returns([modelFileMock, modelFileMock]) + }; + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); + + let result = service.getModelFiles(); + + result.length.should.equal(2); + result[0].should.deep.equal(modelFileMock); + result[1].should.deep.equal(modelFileMock); + })); + + it('should not get sys model files', inject([FileService], (service: FileService) => { + let sysModel = sinon.createStubInstance(ModelFile); + + sysModel.systemModelFile = true; + + let modelManagerMock = { + getModelFiles: sinon.stub().returns([modelFileMock, modelFileMock, sysModel]) + }; + businessNetworkDefMock.getModelManager.returns(modelManagerMock); + sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); + + let result = service.getModelFiles(false); + + result.length.should.equal(2); + result[0].should.deep.equal(modelFileMock); + result[1].should.deep.equal(modelFileMock); + })); + }); + + describe('getScripts', () => { + it('should get script files', inject([FileService], (service: FileService) => { + let scriptManagerMock = { + getScripts: sinon.stub().returns([scriptFileMock, scriptFileMock]) + }; + businessNetworkDefMock.getScriptManager.returns(scriptManagerMock); + service['currentBusinessNetwork'] = businessNetworkDefMock; + + let result = service.getScripts(); + + result.length.should.equal(2); + result[0].should.deep.equal(scriptFileMock); + result[1].should.deep.equal(scriptFileMock); + })); + }); + + describe('getMetaData', () => { + it('should get the metadata', inject([FileService], (service: FileService) => { + + businessNetworkDefMock.getMetadata.returns({metadata: 'my metadata'}); + service['currentBusinessNetwork'] = businessNetworkDefMock; + + let result = service.getMetaData(); + + result.should.deep.equal({metadata: 'my metadata'}); + businessNetworkDefMock.getMetadata.should.have.been.called; + })); + }); + + describe('getBusinessNetworkName', () => { + it('should get the name', inject([FileService], (service: FileService) => { + sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); + + businessNetworkDefMock.getMetadata.returns({ + getName: sinon.stub().returns('my name') + }); + + let result = service.getBusinessNetworkName(); + + result.should.equal('my name'); + })); + }); + + describe('getBusinessNetworkDescription', () => { + it('should get the description', inject([FileService], (service: FileService) => { + sinon.stub(service, 'getBusinessNetwork').returns(businessNetworkDefMock); + + businessNetworkDefMock.getMetadata.returns({ + getDescription: sinon.stub().returns('my description') + }); + + let result = service.getBusinessNetworkDescription(); + + result.should.equal('my description'); + })); + }); + + describe('getBusinessNetwork', () => { + it('should get the businessNetwork', inject([FileService], (service: FileService) => { + service['currentBusinessNetwork'] = businessNetworkDefMock; + + let result = service.getBusinessNetwork(); + result.should.deep.equal(businessNetworkDefMock); + })); + }); +}); diff --git a/packages/composer-playground/src/app/services/file.service.ts b/packages/composer-playground/src/app/services/file.service.ts new file mode 100644 index 0000000000..7b4be799ef --- /dev/null +++ b/packages/composer-playground/src/app/services/file.service.ts @@ -0,0 +1,625 @@ +import { Injectable } from '@angular/core'; +import { EditorFile } from './editor-file'; +import { ModelFile, AclFile, QueryFile, Script, BusinessNetworkDefinition } from 'composer-common'; +import { ClientService } from './client.service'; +import { BehaviorSubject, Subject } from 'rxjs/Rx'; + +import { sortBy } from 'lodash'; + +@Injectable() +export class FileService { + public namespaceChanged$: Subject = new BehaviorSubject(null); + public businessNetworkChanged$: Subject = new BehaviorSubject(null); + + private readMe: EditorFile = null; + private packageJson: EditorFile = null; + private modelFiles: Map = new Map(); + private scriptFiles: Map = new Map(); + private aclFile: EditorFile = null; + private queryFile: EditorFile = null; + + private currentFile: any = null; + + private currentBusinessNetwork; + + constructor(private clientService: ClientService) { + } + + // horrible hack for tests + createModelFile(content, fileName) { + return new ModelFile(this.currentBusinessNetwork.getModelManager(), content, fileName); + } + + getModelFile(id: string): ModelFile { + return this.currentBusinessNetwork.getModelManager().getModelFile(id); + } + + // horrible hack for tests + createAclFile(id, content) { + return new AclFile(id, this.currentBusinessNetwork.getModelManager(), content); + } + + // horrible hack for tests + createScriptFile(id, type, content) { + return this.currentBusinessNetwork.getScriptManager().createScript(id, type, content); + } + + getScriptFile(id: string): Script { + return this.currentBusinessNetwork.getScriptManager().getScript(id); + } + + // horrible hack for tests + createQueryFile(id, content) { + return new QueryFile(id, this.currentBusinessNetwork.getModelManager(), content); + } + + loadFiles() { + this.deleteAllFiles(); + + this.currentBusinessNetwork = this.clientService.getBusinessNetwork(); + + let modelFiles = this.getModelFiles(false); + modelFiles.forEach((modelFile) => { + this.addFile(modelFile.getNamespace(), modelFile.getName(), modelFile.getDefinitions(), 'model'); + }); + + let scriptFiles = this.getScripts(); + scriptFiles.forEach((scriptFile) => { + this.addFile(scriptFile.getIdentifier(), scriptFile.getIdentifier(), scriptFile.getContents(), 'script'); + }); + + let aclFile = this.getAclFile(); + if (aclFile) { + this.addFile(aclFile.getIdentifier(), aclFile.getIdentifier(), aclFile.getDefinitions(), 'acl'); + } + + let queryFile = this.getQueryFile(); + if (queryFile) { + this.addFile(queryFile.getIdentifier(), queryFile.getIdentifier(), queryFile.getDefinitions(), 'query'); + } + + // deal with readme + let readme = this.getMetaData().getREADME(); + if (readme) { + this.addFile('readme', 'README.md', readme, 'readme'); + } + + let packageJson = this.getMetaData().getPackageJson(); + if (packageJson) { + this.addFile('package', 'package.json', packageJson, 'package'); + } + + let allFiles = this.getEditorFiles(); + + // check if we have changed the business network or just switch tabs + if (this.currentFile) { + let foundFile = allFiles.find((file) => { + return file.getType() === this.currentFile.getType() && file.getContent() === this.currentFile.getContent() && file.getId() === this.currentFile.getId(); + }); + + if (!foundFile) { + this.currentFile = null; + } + } + + return allFiles; + } + + getFile(id: string, type: string): EditorFile { + let file: EditorFile; + switch (type) { + // Deal with the addition of a model file. + case 'model': + file = this.modelFiles.get(id); + break; + // Deal with the addition of a script file. + case 'script': + file = this.scriptFiles.get(id); + break; + // Deal with the addition of a query file. + case 'query': + file = this.queryFile; + break; + // Deal with the addition of an acl file. + case 'acl': + file = this.aclFile; + break; + // Deal with the addition of a readme file. + case 'readme': + file = this.readMe; + break; + case 'package': + file = this.packageJson; + break; + default: + throw new Error('Type passed must be one of readme, acl, query, script, model or packageJson'); + } + return file; + } + + getEditorReadMe(): EditorFile { + return this.readMe; + } + + getEditorModelFiles(): Array { + let files = []; + this.modelFiles.forEach((editorFile: EditorFile, id: string) => { + files.push(editorFile); + }); + + files = sortBy(files, ['displayID']); + + return files; + } + + getEditorScriptFiles(): Array { + let files = []; + this.scriptFiles.forEach((editorFile: EditorFile, id: string) => { + files.push(editorFile); + }); + + return files; + } + + getEditorAclFile(): EditorFile { + return this.aclFile; + } + + getEditorQueryFile(): EditorFile { + return this.queryFile; + } + + getEditorPackageFile(): EditorFile { + return this.packageJson; + } + + getEditorFiles(includePackageJson = false): Array { + let files = []; + if (this.getEditorReadMe() !== null) { + files.push(this.getEditorReadMe()); + } + + files = files.concat(this.getEditorModelFiles()); + files = files.concat(this.getEditorScriptFiles()); + if (this.getEditorAclFile() !== null) { + files.push(this.getEditorAclFile()); + } + if (this.getEditorQueryFile() !== null) { + files.push(this.getEditorQueryFile()); + } + if (includePackageJson && this.getEditorPackageFile() !== null) { + files.push(this.getEditorPackageFile()); + } + + return files; + } + + // Handle the addition of a new file. + addFile(id: string, displayID: string, content: string, type: string): EditorFile { + let file = new EditorFile(id, displayID, content, type); + switch (type) { + // Deal with the addition of a model file. + case 'model': + if (this.modelFiles.has(id)) { + throw new Error('FileService already contains model file with ID: ' + id); + } else { + this.modelFiles.set(id, file); + } + break; + // Deal with the addition of a script file. + case 'script': + if (this.scriptFiles.has(id)) { + throw new Error('FileService already contains script file with ID: ' + id); + } else { + this.scriptFiles.set(id, file); + } + break; + // Deal with the addition of a query file. + case 'query': + if (this.getEditorQueryFile() !== null) { + throw new Error('FileService already contains a query file'); + } else { + this.queryFile = file; + } + break; + // Deal with the addition of an acl file. + case 'acl': + if (this.getEditorAclFile() !== null) { + throw new Error('FileService already contains an acl file'); + } else { + this.aclFile = file; + } + break; + // Deal with the addition of a readme file. + case 'readme': + if (this.getEditorReadMe() !== null) { + throw new Error('FileService already contains a readme file'); + } else { + this.readMe = file; + } + break; + case 'package': + if (this.getEditorPackageFile() !== null) { + throw new Error('FileService already contains a package.json file'); + } else { + this.packageJson = file; + } + break; + default: + throw new Error('Attempted addition of unknown file type: ' + type); + } + + return file; + } + + // Handle the update of a file. + updateFile(id: string, content: string, type: string): EditorFile { + let updatedFile: EditorFile; + switch (type) { + // Deal with the update of a model file. + case 'model': + if (!this.modelFiles.has(id)) { + throw new Error('File does not exist of type ' + type + ' and id ' + id); + } + + // update the content first incase something goes wrong later + let updatedModelFile = this.modelFiles.get(id); + updatedModelFile.setContent(content); + updatedFile = updatedModelFile; + + let original: ModelFile = this.getModelFile(id); + if (original) { + let modelFile = this.createModelFile(content, original.getName()); + + if (this.modelNamespaceCollides(modelFile.getNamespace(), id)) { + // don't want it to replace the files as want the error to happen + return updatedFile; + } + if (id !== modelFile.getNamespace()) { + // Then we are changing namespace and must delete old reference + this.deleteFile(id, 'model'); + updatedFile = this.addFile(modelFile.getNamespace(), modelFile.getName(), modelFile.getDefinitions(), 'model'); + } + } + break; + // Deal with the update of a script file. + case 'script': + if (!this.scriptFiles.has(id)) { + throw new Error('File does not exist of type ' + type + ' and id ' + id); + } + let updatedScriptFile = this.scriptFiles.get(id); + updatedScriptFile.setContent(content); + updatedFile = updatedScriptFile; + break; + // Deal with the update of a query file. + case 'query': + if (this.queryFile === null) { + throw new Error('Query file does not exist in file service'); + } + this.queryFile.setContent(content); + updatedFile = this.queryFile; + break; + // Deal with the update of an acl file. + case 'acl': + if (this.aclFile === null) { + throw new Error('Acl file does not exist in file service'); + } + this.aclFile.setContent(content); + updatedFile = this.aclFile; + break; + // Deal with the update of a readme file. + case 'readme': + if (this.readMe === null) { + throw new Error('ReadMe file does not exist in file service'); + } + this.readMe.setContent(content); + updatedFile = this.readMe; + break; + case 'package': + if (this.packageJson === null) { + throw new Error('PackageJson file does not exist in file service'); + } + this.packageJson.setContent(JSON.parse(content)); + updatedFile = this.packageJson; + break; + default: + throw new Error('Attempted update of unknown file type: ' + type); + } + + return updatedFile; + } + + // Handle the deletion of a file. + deleteFile(id: string, type: string) { + switch (type) { + // Deal with the deletion of a model file. + case 'model': + this.modelFiles.delete(id); + break; + // Deal with the deletion of a script file. + case 'script': + this.scriptFiles.delete(id); + break; + // Deal with the deletion of a query file. + case 'query': + this.queryFile = null; + break; + // Deal with the deletion of an acl file. + case 'acl': + this.aclFile = null; + break; + // Deal with the deletion of a readme file. + case 'readme': + this.readMe = null; + break; + // Deal with the deletion of a package file. + case 'package': + this.packageJson = null; + break; + default: + throw new Error('Attempted deletion of file unknown type: ' + type); + } + } + + deleteAllFiles() { + this.modelFiles.clear(); + this.scriptFiles.clear(); + this.queryFile = null; + this.aclFile = null; + this.readMe = null; + this.packageJson = null; + + this.currentFile = null; + } + + replaceFile(oldId: string, newId: string, content: string, type: string): EditorFile { + switch (type) { + case 'model': + if (!this.modelFiles.has(oldId)) { + throw new Error('There is no existing file of type ' + type + ' with the id ' + oldId); + } + if (this.modelFiles.has(newId)) { + throw new Error('There is an existing file of type ' + type + ' with the id ' + oldId); + } + let modelFile; + try { + modelFile = this.createModelFile(this.getFile(oldId, 'model').getContent(), newId); + this.deleteFile(oldId, 'model'); + this.addFile(modelFile.getNamespace(), modelFile.getName(), modelFile.getDefinitions(), 'model'); + return this.getFile(modelFile.getNamespace(), 'model'); + } catch (err) { + try { + modelFile = this.createModelFile(content, newId); // current contents must be invalid so use old ones so we can have namespace + let actualContent = this.getFile(oldId, 'model').getContent(); + this.deleteFile(oldId, 'model'); + this.addFile(modelFile.getNamespace(), modelFile.getName(), actualContent, 'model'); + return this.getFile(modelFile.getNamespace(), 'model'); + } catch (err) { + throw new Error(err); + } + } + case 'script': + if (!this.scriptFiles.has(oldId)) { + throw new Error('There is no existing file of type ' + type + ' with the id ' + oldId); + } + if (this.scriptFiles.has(newId)) { + throw new Error('There is an existing file of type ' + type + ' with the id ' + oldId); + } + this.addFile(newId, newId, this.getFile(oldId, 'script').getContent(), 'script'); + this.deleteFile(oldId, 'script'); + return this.getFile(newId, 'script'); + default: + throw new Error('Attempted replace of ununsupported file type: ' + type); + } + } + + // Validate a file. + validateFile(id: string, type: string): string { + try { + switch (type) { + case 'model': + let modelFile = this.modelFiles.get(id); + let modelManager = this.currentBusinessNetwork.getModelManager(); + modelFile.validate(modelManager); + let original: ModelFile = modelManager.getModelFile(id); + if (original) { + let newModelFile = this.createModelFile(modelFile.getContent(), original.getName()); + if (this.modelNamespaceCollides(newModelFile.getNamespace(), id)) { + throw new Error(`The namespace collides with existing model namespace ${newModelFile.getNamespace()}`); + } + } + break; + case 'script': + let scriptFile = this.scriptFiles.get(id); + scriptFile.validate(this.currentBusinessNetwork.getModelManager()); + break; + case 'acl': + this.aclFile.validate(this.currentBusinessNetwork.getModelManager()); + break; + case 'query': + this.queryFile.validate(this.currentBusinessNetwork.getModelManager()); + break; + case 'package': + if (this.packageJson.getContent().name !== this.clientService.getBusinessNetwork().getName()) { + throw new Error('Unsupported attempt to update Business Network Name.'); + } + break; + default: + throw new Error('Attempted validation of unknown file of type: ' + type); + } + return null; + } catch (e) { + return e; + } + } + + updateBusinessNetwork(oldId: string, editorFile: EditorFile) { + if (editorFile.isModel()) { + if (this.getModelFile(oldId)) { + this.updateBusinessNetworkFile(oldId, editorFile.getContent(), editorFile.getType()); + } else { + this.currentBusinessNetwork.getModelManager().addModelFile(editorFile.getContent()); + } + } else if (editorFile.isAcl()) { + if (this.getAclFile()) { + this.updateBusinessNetworkFile(oldId, editorFile.getContent(), editorFile.getType()); + } else { + let aclFile = this.createAclFile(oldId, editorFile.getContent()); + this.currentBusinessNetwork.getAclManager().setAclFile(aclFile); + } + + } else if (editorFile.isScript()) { + if (this.getScriptFile(oldId)) { + this.updateBusinessNetworkFile(oldId, editorFile.getContent(), editorFile.getType()); + } else { + let script = this.createScriptFile(oldId, 'JS', editorFile.getContent()); + this.currentBusinessNetwork.getScriptManager().addScript(script); + } + } else if (editorFile.isQuery()) { + if (this.getQueryFile()) { + this.updateBusinessNetworkFile(oldId, editorFile.getContent(), editorFile.getType()); + } else { + let query = this.createQueryFile(oldId, editorFile.getContent()); + this.currentBusinessNetwork.getQueryManager().setQueryFile(query); + } + } else if (editorFile.isReadMe()) { + this.updateBusinessNetworkFile(oldId, editorFile.getContent(), editorFile.getType()); + } else if (editorFile.isPackage()) { + this.updateBusinessNetworkFile(oldId, editorFile.getContent(), editorFile.getType()); + } + } + + getCurrentFile(): any { + return this.currentFile; + } + + setCurrentFile(cf: any) { + this.currentFile = cf; + } + + updateBusinessNetworkFile(id: string, content: any, type: string) { + switch (type) { + case 'model': + let modelManager = this.currentBusinessNetwork.getModelManager(); + let original: ModelFile = modelManager.getModelFile(id); + let modelFile = this.createModelFile(content, original.getName()); + if (this.modelNamespaceCollides(modelFile.getNamespace(), id)) { + throw new Error(`The namespace collides with existing model namespace ${modelFile.getNamespace()}`); + } + if (id !== modelFile.getNamespace()) { + // Then we are changing namespace and must delete old reference + modelManager.addModelFile(modelFile); + modelManager.deleteModelFile(id); + this.namespaceChanged$.next(modelFile.getNamespace()); + } else { + modelManager.updateModelFile(modelFile); + } + break; + case 'script': + let script = this.createScriptFile(id, 'JS', content); + this.currentBusinessNetwork.getScriptManager().addScript(script); + break; + case 'acl': + let aclFile = this.createAclFile(id, content); + this.currentBusinessNetwork.getAclManager().setAclFile(aclFile); + break; + case 'query': + let query = this.createQueryFile(id, content); + this.currentBusinessNetwork.getQueryManager().setQueryFile(query); + break; + case 'package': + this.setBusinessNetworkPackageJson(content); + break; + case 'readme': + this.setBusinessNetworkReadme(content); + break; + default: + throw new Error('Attempted update of unknown file of type: ' + type); + } + return null; + } + + modelNamespaceCollides(newNamespace, previousNamespace): boolean { + let allModelFiles = this.currentBusinessNetwork.getModelManager().getModelFiles(); + if ((newNamespace !== previousNamespace) && (allModelFiles.findIndex((model) => model.getNamespace() === newNamespace) !== -1)) { + return true; + } else { + return false; + } + } + + replaceBusinessNetworkFile(oldId: string, newId: string, content: any, type: string): string { + try { + switch (type) { + case 'model': + let modelFile = this.createModelFile(content, newId); + this.currentBusinessNetwork.getModelManager().updateModelFile(modelFile, newId); + this.businessNetworkChanged$.next(true); + break; + case 'script': + let script = this.createScriptFile(newId, 'JS', content); + this.currentBusinessNetwork.getScriptManager().addScript(script); + this.currentBusinessNetwork.getScriptManager().deleteScript(oldId); + this.businessNetworkChanged$.next(true); + break; + default: + throw new Error('Attempted replace of ununsupported file type: ' + type); + } + return null; + } catch (e) { + this.businessNetworkChanged$.next(false); + return e.toString(); + } + } + + setBusinessNetworkPackageJson(packageJson) { + this.currentBusinessNetwork.setPackageJson(packageJson); + } + + setBusinessNetworkReadme(readme) { + this.currentBusinessNetwork.setReadme(readme); + } + + setBusinessNetworkVersion(version: string) { + let packageJson = this.currentBusinessNetwork.getMetadata().getPackageJson(); + packageJson.version = version; + this.currentBusinessNetwork.setPackageJson(packageJson); + this.businessNetworkChanged$.next(true); + } + + getBusinessNetwork(): BusinessNetworkDefinition { + return this.currentBusinessNetwork; + } + + getBusinessNetworkName() { + return this.getBusinessNetwork().getMetadata().getName(); + } + + getBusinessNetworkDescription() { + return this.getBusinessNetwork().getMetadata().getDescription(); + } + + getModelFiles(getSystemModels = true): ModelFile[] { + let models = this.getBusinessNetwork().getModelManager().getModelFiles(); + models = models.filter((obj) => { + return getSystemModels || !obj.systemModelFile; + }); + return models; + } + + getScripts(): Script[] { + return this.currentBusinessNetwork.getScriptManager().getScripts(); + } + + getAclFile(): AclFile { + return this.currentBusinessNetwork.getAclManager().getAclFile(); + } + + getQueryFile(): QueryFile { + return this.currentBusinessNetwork.getQueryManager().getQueryFile(); + } + + getMetaData() { + return this.currentBusinessNetwork.getMetadata(); + } +} 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 3c9d269bea..c9d4e86142 100644 --- a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts +++ b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts @@ -9,11 +9,7 @@ import { IdentityCardService } from './identity-card.service'; import { AdminService } from './admin.service'; import { ClientService } from './client.service'; import { BusinessNetworkDefinition, AclFile, Serializer, Factory, ModelManager } from 'composer-common'; - -import * as sinon from 'sinon'; -import * as chai from 'chai'; - -let should = chai.should(); +import { FileService } from './file.service'; import { HttpModule, @@ -23,6 +19,11 @@ import { } from '@angular/http'; import { MockBackend } from '@angular/http/testing'; +import * as sinon from 'sinon'; +import * as chai from 'chai'; + +let should = chai.should(); + describe('SampleBusinessNetworkService', () => { let adminMock; @@ -32,6 +33,7 @@ describe('SampleBusinessNetworkService', () => { let businessNetworkMock; let sandbox; let identityCardMock; + let mockFileService; beforeEach(() => { sandbox = sinon.sandbox.create(); @@ -41,6 +43,7 @@ describe('SampleBusinessNetworkService', () => { aclFileMock = sinon.createStubInstance(AclFile); alertMock = sinon.createStubInstance(AlertService); businessNetworkMock = sinon.createStubInstance(BusinessNetworkDefinition); + mockFileService = sinon.createStubInstance(FileService); const modelManager = new ModelManager(); const factory = new Factory(modelManager); @@ -56,6 +59,7 @@ describe('SampleBusinessNetworkService', () => { {provide: AlertService, useValue: alertMock}, {provide: AdminService, useValue: adminMock}, {provide: ClientService, useValue: clientMock}, + {provide: FileService, useValue: mockFileService}, {provide: AclFile, useValue: aclFileMock}, {provide: XHRBackend, useClass: MockBackend}, {provide: IdentityCardService, useValue: identityCardMock}] @@ -189,12 +193,6 @@ describe('SampleBusinessNetworkService', () => { const bootstrapTransactions = service.generateBootstrapTransactions(businessNetworkMock, 'doggoship1', {certificate: 'myCert'}); sanitize(bootstrapTransactions); - console.log('ACTUAL', bootstrapTransactions[1]); - console.log('EXPECTED', { - $class: 'org.hyperledger.composer.system.BindIdentity', - participant: 'resource:org.hyperledger.composer.system.NetworkAdmin#doggoship1', - certficate: 'myCert' - }); bootstrapTransactions.should.deep.equal([ { $class: 'org.hyperledger.composer.system.AddParticipant', @@ -514,8 +512,8 @@ describe('SampleBusinessNetworkService', () => { adminMock.update.returns(Promise.resolve()); adminMock.connect.returns(Promise.resolve()); clientMock.refresh.returns(Promise.resolve()); - clientMock.getBusinessNetworkName.returns('myNetwork'); - clientMock.getBusinessNetworkDescription.returns('myDescription'); + mockFileService.getBusinessNetworkName.returns('myNetwork'); + mockFileService.getBusinessNetworkDescription.returns('myDescription'); let buildStub = sinon.stub(service, 'buildNetwork').returns({getName: sinon.stub().returns('newname')}); @@ -539,8 +537,8 @@ describe('SampleBusinessNetworkService', () => { }))); it('should handle error', fakeAsync(inject([SampleBusinessNetworkService], (service: SampleBusinessNetworkService) => { - clientMock.getBusinessNetworkName.returns('myNetwork'); - clientMock.getBusinessNetworkDescription.returns('myDescription'); + mockFileService.getBusinessNetworkName.returns('myNetwork'); + mockFileService.getBusinessNetworkDescription.returns('myDescription'); let buildStub = sinon.stub(service, 'buildNetwork').returns({getName: sinon.stub()}); diff --git a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts index c1fed77f8b..833dc10167 100644 --- a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts +++ b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts @@ -3,9 +3,10 @@ import { Http, RequestOptions, URLSearchParams } from '@angular/http'; import { AdminService } from './admin.service'; import { ClientService } from './client.service'; +import { FileService } from './file.service'; import { AlertService } from '../basic-modals/alert.service'; -import { BusinessNetworkDefinition, IdCard } from 'composer-common'; +import { BusinessNetworkDefinition } from 'composer-common'; import { IdentityCardService } from './identity-card.service'; @Injectable() @@ -15,6 +16,7 @@ export class SampleBusinessNetworkService { private clientService: ClientService, private alertService: AlertService, private identityCardService: IdentityCardService, + private fileService: FileService, private http: Http) { } @@ -196,8 +198,8 @@ export class SampleBusinessNetworkService { } public updateBusinessNetwork(businessNetworkDefinition: BusinessNetworkDefinition): Promise { - let currentBusinessNetworkName = this.clientService.getBusinessNetworkName(); - let currentBusinessNetworkDescription = this.clientService.getBusinessNetworkDescription(); + let currentBusinessNetworkName = this.fileService.getBusinessNetworkName(); + let currentBusinessNetworkDescription = this.fileService.getBusinessNetworkDescription(); let packageJson = businessNetworkDefinition.getMetadata().getPackageJson(); packageJson.name = currentBusinessNetworkName; diff --git a/packages/composer-playground/src/app/services/services.module.ts b/packages/composer-playground/src/app/services/services.module.ts index cc3c277f2d..cdfff3317e 100644 --- a/packages/composer-playground/src/app/services/services.module.ts +++ b/packages/composer-playground/src/app/services/services.module.ts @@ -6,6 +6,7 @@ import { AdminService } from './admin.service'; import { ClientService } from './client.service'; import { ConnectionProfileService } from './connectionprofile.service'; import { ConnectionProfileStoreService } from './connectionProfileStores/connectionprofilestore.service'; +import { FileService } from './file.service'; import { IdentityService } from './identity.service'; import { IdentityCardService } from './identity-card.service'; import { IdentityCardStorageService } from './identity-card-storage.service'; @@ -29,6 +30,7 @@ let identityCardStorageServiceConfig = { ClientService, ConnectionProfileService, ConnectionProfileStoreService, + FileService, IdentityService, IdentityCardService, IdentityCardStorageService,