From 76f382908b20da3224d60f561713bdf133b48f24 Mon Sep 17 00:00:00 2001 From: Simon Stone Date: Thu, 6 Jul 2017 09:39:46 +0100 Subject: [PATCH 01/88] Add WebSQL adapter to handle Safari (#1484) --- packages/composer-runtime-pouchdb/lib/pouchdbdataservice.js | 3 ++- packages/composer-runtime-pouchdb/package.json | 1 + packages/composer-runtime-web/lib/webdataservice.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/composer-runtime-pouchdb/lib/pouchdbdataservice.js b/packages/composer-runtime-pouchdb/lib/pouchdbdataservice.js index c987369750..ee1dfac452 100644 --- a/packages/composer-runtime-pouchdb/lib/pouchdbdataservice.js +++ b/packages/composer-runtime-pouchdb/lib/pouchdbdataservice.js @@ -23,8 +23,9 @@ const PouchDBUtils = require('./pouchdbutils'); const LOG = Logger.getLog('PouchDBDataService'); -// Install the PouchDB plugins. +// Install the PouchDB plugins. The order of the adapters is important! PouchDB.plugin(require('pouchdb-adapter-idb')); +PouchDB.plugin(require('pouchdb-adapter-websql')); PouchDB.plugin(require('pouchdb-adapter-memory')); PouchDB.plugin(require('pouchdb-find')); diff --git a/packages/composer-runtime-pouchdb/package.json b/packages/composer-runtime-pouchdb/package.json index ab8fb35cf8..65bacd390d 100644 --- a/packages/composer-runtime-pouchdb/package.json +++ b/packages/composer-runtime-pouchdb/package.json @@ -58,6 +58,7 @@ "istanbul-lib-instrument": "^1.7.2", "pouchdb-adapter-idb": "^6.2.0", "pouchdb-adapter-memory": "^6.2.0", + "pouchdb-adapter-websql": "^6.2.0", "pouchdb-collate": "^6.2.0", "pouchdb-core": "^6.2.0", "pouchdb-find": "^6.2.0", diff --git a/packages/composer-runtime-web/lib/webdataservice.js b/packages/composer-runtime-web/lib/webdataservice.js index 94524f2cf5..158e17d100 100644 --- a/packages/composer-runtime-web/lib/webdataservice.js +++ b/packages/composer-runtime-web/lib/webdataservice.js @@ -31,7 +31,7 @@ class WebDataService extends PouchDBDataService { * @param {boolean} [autocommit] Should this data service auto commit? */ constructor(uuid, autocommit) { - super(uuid, autocommit, { adapter: 'idb' }); + super(uuid, autocommit); const method = 'constructor'; LOG.entry(method, uuid, autocommit); LOG.exit(method); From ea1da4bd8a60281ef02ccfb8a80c8078ad6bb56d Mon Sep 17 00:00:00 2001 From: EdProsser Date: Thu, 6 Jul 2017 10:54:14 +0100 Subject: [PATCH 02/88] Command changes ed (#1483) * Woooh changes.... * ToC changes * ToC changes * identity issue * TOC Update * Beta->RC * network deploy changes --- .../jekylldocs/_includes/sidebars/managing.md | 1 + .../jekylldocs/business-network/bnd-create.md | 2 + .../jekylldocs/business-network/bnd-deploy.md | 10 ++-- .../business-network/bnd-publish.md | 2 + .../business-network-index.md | 2 + .../businessnetworkdefinition.md | 2 + .../business-network/publishing-events.md | 2 + .../jekylldocs/business-network/query.md | 3 +- .../jekylldocs/business-network/testing.md | 2 + .../installing/development-tools.md | 4 +- .../installing/using-playground-locally.md | 10 ++-- .../jekylldocs/introduction/introduction.md | 4 +- .../jekylldocs/introduction/key-concepts.md | 7 +-- .../introduction/solution-architecture.md | 7 +-- .../jekylldocs/managing/updating-composer.md | 25 +++++++++- .../jekylldocs/problems/diagnostics.md | 2 + .../jekylldocs/reference/JSDOC-README.md | 34 ++++++++++++++ .../jekylldocs/reference/commands.md | 4 ++ .../reference/composer.identity.issue.md | 2 + .../reference/composer.network.deploy.md | 46 ++++++++++++++++--- .../reference/composer.network.logLevel.md | 27 +++++++++++ .../jekylldocs/reference/connectionprofile.md | 7 +-- .../jekylldocs/reference/glossary.md | 2 +- .../jekylldocs/reference/reference-index.md | 2 +- .../jekylldocs/support/index.md | 2 + 25 files changed, 179 insertions(+), 32 deletions(-) create mode 100644 packages/composer-website/jekylldocs/reference/JSDOC-README.md create mode 100644 packages/composer-website/jekylldocs/reference/composer.network.logLevel.md diff --git a/packages/composer-website/jekylldocs/_includes/sidebars/managing.md b/packages/composer-website/jekylldocs/_includes/sidebars/managing.md index d7f3ebf1ba..f7bafc398f 100644 --- a/packages/composer-website/jekylldocs/_includes/sidebars/managing.md +++ b/packages/composer-website/jekylldocs/_includes/sidebars/managing.md @@ -11,6 +11,7 @@ - [Revoking an identity from a participant](../managing/identity-revoke.html) - [Enabling OAuth using GitHub](../managing/github-oauth.html) - [Enabling access control using the current participant](../managing/current-participant.html) + - [Updating {{site.data.conrefs.composer_full}}](../managing/updating-composer.html) - [Diagnosing Problems](../problems/diagnostics.html) - [Reference](../reference/reference-index.html) - [Support](../support/index.html) diff --git a/packages/composer-website/jekylldocs/business-network/bnd-create.md b/packages/composer-website/jekylldocs/business-network/bnd-create.md index 67143680a1..e5283b29b2 100644 --- a/packages/composer-website/jekylldocs/business-network/bnd-create.md +++ b/packages/composer-website/jekylldocs/business-network/bnd-create.md @@ -2,6 +2,8 @@ layout: default title: Task - Create a Business Network Definition category: tasks +section: business-network +index-order: 2 sidebar: sidebars/businessnetworks.md excerpt: How to create a business network definition --- diff --git a/packages/composer-website/jekylldocs/business-network/bnd-deploy.md b/packages/composer-website/jekylldocs/business-network/bnd-deploy.md index d6c4dcb515..51e43ee2ff 100644 --- a/packages/composer-website/jekylldocs/business-network/bnd-deploy.md +++ b/packages/composer-website/jekylldocs/business-network/bnd-deploy.md @@ -2,6 +2,8 @@ layout: default title: Task - Deploying and Updating Business Networks category: tasks +section: business-network +index-order: 3 sidebar: sidebars/businessnetworks.md excerpt: How to deploy or update Business Networks --- @@ -20,17 +22,17 @@ For example: To update the definition of an already deployed business network use the `composer network update` CLI command. -## Deploying business networks to {{site.data.conrefs.hlf_full}} v1.0 Beta 1 +## Deploying business networks to {{site.data.conrefs.hlf_full}} v1.0 RC 1 -In {{site.data.conrefs.hlf_full}} v1.0 Beta 1 peers now enforce the concepts of admins and members. Admin user's identities and crypto material must be available to the peer at deployment. To make that identity and its crypto material available, your must import it to your local `keyValStore` directory before deploying the business network. To import the identity, use the [`composer identity import` command](../reference/composer.identity.import.html). When importing an identity, you do not assign it a secret, however the `composer network deploy` command requires a secret. If you are using an imported identity, you can enter any value for the secret. +In {{site.data.conrefs.hlf_full}} v1.0 RC 1 peers now enforce the concepts of admins and members. Admin user's identities and crypto material must be available to the peer at deployment. To make that identity and its crypto material available, your must import it to your local `keyValStore` directory before deploying the business network. To import the identity, use the [`composer identity import` command](../reference/composer.identity.import.html). When importing an identity, you do not assign it a secret, however the `composer network deploy` command requires a secret. If you are using an imported identity, you can enter any value for the secret. When connecting to the peer you must use an identity (certificate) where the Common Name (CN) contains the text `admin`, for example, `PeerAdmin`, `myadmin`, `Admin` or `AdminPeer` are all valid Common Names. Peers in different organizations may have different admin users. Only an admin user of peer's organization will be able to deploy a business network to their peers. -Due to many breaking API changes between {{site.data.conrefs.hlf_full}} alpha 1 and {{site.data.conrefs.hlf_full}} v1.0 Beta 1, {{site.data.conrefs.composer_full}} only supports the beta 1 level and cannot support older versions of {{site.data.conrefs.hlf_full}} v1.0 (e.g. alpha 1). +Due to many breaking API changes between {{site.data.conrefs.hlf_full}} alpha 1 and {{site.data.conrefs.hlf_full}} v1.0 RC 1, {{site.data.conrefs.composer_full}} only supports the RC 1 level and cannot support older versions of {{site.data.conrefs.hlf_full}} v1.0 (e.g. alpha 1). ### Deploying business networks using Playground locally -When deploying a business network to {{site.data.conrefs.hlf_full}} v1.0 Beta 1 using the Playground locally, you must follow the process above to connect using the peer admin identity. However, in order to create identities and interact with your business network in the Playground, you must use the certificate authority admin identity. +When deploying a business network to {{site.data.conrefs.hlf_full}} v1.0 RC 1 using the Playground locally, you must follow the process above to connect using the peer admin identity. However, in order to create identities and interact with your business network in the Playground, you must use the certificate authority admin identity. ## References diff --git a/packages/composer-website/jekylldocs/business-network/bnd-publish.md b/packages/composer-website/jekylldocs/business-network/bnd-publish.md index 9a04d33df8..43f40ebca1 100644 --- a/packages/composer-website/jekylldocs/business-network/bnd-publish.md +++ b/packages/composer-website/jekylldocs/business-network/bnd-publish.md @@ -2,6 +2,8 @@ layout: default title: Task - Publish Models or Business Network Definitions category: tasks +section: business-network +index-order: 6 sidebar: sidebars/businessnetworks.md excerpt: How to publish a model or business network definition for use by applications --- diff --git a/packages/composer-website/jekylldocs/business-network/business-network-index.md b/packages/composer-website/jekylldocs/business-network/business-network-index.md index 29c1ed2678..c220d4ef2d 100644 --- a/packages/composer-website/jekylldocs/business-network/business-network-index.md +++ b/packages/composer-website/jekylldocs/business-network/business-network-index.md @@ -2,6 +2,8 @@ layout: default title: Hyperledger Composer - Developing Business Networks category: concepts +section: business-network +index-order: 0 sidebar: sidebars/businessnetworks.md excerpt: Overview of Developing Business Networks --- diff --git a/packages/composer-website/jekylldocs/business-network/businessnetworkdefinition.md b/packages/composer-website/jekylldocs/business-network/businessnetworkdefinition.md index 0575117e3e..c3b0009f05 100644 --- a/packages/composer-website/jekylldocs/business-network/businessnetworkdefinition.md +++ b/packages/composer-website/jekylldocs/business-network/businessnetworkdefinition.md @@ -2,6 +2,8 @@ layout: default title: Hyperledger Composer - Business Network Definition category: concepts +section: business-network +index-order: 1 sidebar: sidebars/businessnetworks.md excerpt: Overview of the Business Network Definition --- diff --git a/packages/composer-website/jekylldocs/business-network/publishing-events.md b/packages/composer-website/jekylldocs/business-network/publishing-events.md index 30c4482d49..caf84b3013 100644 --- a/packages/composer-website/jekylldocs/business-network/publishing-events.md +++ b/packages/composer-website/jekylldocs/business-network/publishing-events.md @@ -2,6 +2,8 @@ layout: default title: Emitting Events category: tasks +section: business-network +index-order: 4 sidebar: sidebars/businessnetworks.md excerpt: Emitting Events from Transaction Processor Functions --- diff --git a/packages/composer-website/jekylldocs/business-network/query.md b/packages/composer-website/jekylldocs/business-network/query.md index 16e1b1854c..3fe3c2894d 100644 --- a/packages/composer-website/jekylldocs/business-network/query.md +++ b/packages/composer-website/jekylldocs/business-network/query.md @@ -3,6 +3,7 @@ layout: default title: Querying Business Network Data category: tasks section: business-network +index-order: 7 sidebar: sidebars/businessnetworks.md excerpt: Queries are used to return data about the blockchain world-state; for example, you could write a query to return all drivers over a defined age parameter, or all drivers with a specific name. --- @@ -15,7 +16,7 @@ Queries are used to return data about the blockchain world-state; for example, y Queries are an optional component of a business network definition, written in a single query file (`queries.qry`). -Note: Queries are supported by the {{site.data.conrefs.hlf_full}} v1.0, embedded and web runtimes. The query support for the embedded and web runtimes currently has limitations and is unstable. When using the {{site.data.conrefs.hlf_full}} v1.0-beta runtime {{site.data.conrefs.hlf_full}} must be configured to use CouchDB persistence. Queries are **not** supported with the {{site.data.conrefs.hlf_full}} v0.6 runtime. +Note: Queries are supported by the {{site.data.conrefs.hlf_full}} v1.0, embedded and web runtimes. The query support for the embedded and web runtimes currently has limitations and is unstable. When using the {{site.data.conrefs.hlf_full}} v1.0-RC runtime {{site.data.conrefs.hlf_full}} must be configured to use CouchDB persistence. Queries are **not** supported with the {{site.data.conrefs.hlf_full}} v0.6 runtime. ## Writing Queries diff --git a/packages/composer-website/jekylldocs/business-network/testing.md b/packages/composer-website/jekylldocs/business-network/testing.md index 12a4257090..193c1a2b82 100644 --- a/packages/composer-website/jekylldocs/business-network/testing.md +++ b/packages/composer-website/jekylldocs/business-network/testing.md @@ -2,6 +2,8 @@ layout: default title: Task - Testing Business Networks category: tasks +section: business-network +index-order: 5 sidebar: sidebars/businessnetworks.md excerpt: How to test business networks --- diff --git a/packages/composer-website/jekylldocs/installing/development-tools.md b/packages/composer-website/jekylldocs/installing/development-tools.md index ba2753afba..40daeb6c5b 100644 --- a/packages/composer-website/jekylldocs/installing/development-tools.md +++ b/packages/composer-website/jekylldocs/installing/development-tools.md @@ -12,7 +12,7 @@ index-order: 3 Follow the instructions below to get the required {{site.data.conrefs.composer_full}} development tools and stand up a {{site.data.conrefs.hlf_full}}. -There are two version of {{site.data.conrefs.hlf_full}}: v0.6 and v1.0-beta. The default is for v1.0-beta and we suggest this is the one you use. +There are two version of {{site.data.conrefs.hlf_full}}: v0.6 and v1.0-RC. The default is for v1.0-RC and we suggest this is the one you use. ## Before you begin @@ -107,7 +107,7 @@ docker rmi $(docker images dev-* -q) export FABRIC_VERSION=hlfv0.6 - {{site.data.conrefs.hlf_full}} v1.0-beta is the default, but to 'unset' the v0.6, or to be explicit in using v1 use this command + {{site.data.conrefs.hlf_full}} v1.0-RC is the default, but to 'unset' the v0.6, or to be explicit in using v1 use this command export FABRIC_VERSION=hlfv1 diff --git a/packages/composer-website/jekylldocs/installing/using-playground-locally.md b/packages/composer-website/jekylldocs/installing/using-playground-locally.md index cad98245a6..590e030b98 100644 --- a/packages/composer-website/jekylldocs/installing/using-playground-locally.md +++ b/packages/composer-website/jekylldocs/installing/using-playground-locally.md @@ -10,7 +10,7 @@ index-order: 2 # Installing and running {{site.data.conrefs.composer_full}} Playground locally -This tutorial will take you through how to install and run the {{site.data.conrefs.composer_full}} Playground on your local machine. It also creates an instance of {{site.data.conrefs.hlf_full}} v1.0 Beta 1. +This tutorial will take you through how to install and run the {{site.data.conrefs.composer_full}} Playground on your local machine. It also creates an instance of {{site.data.conrefs.hlf_full}} v1.0 RC 1. {{site.data.conrefs.composer_full}} Playground can also be used in a "browser only" mode, without a running instance of {{site.data.conrefs.hlf_full}}. When used in this mode, all the functionality of {{site.data.conrefs.composer_full}} Playground is available, but all of the data (business networks, assets, participants, and transactions) is persisted into browser local storage. @@ -39,17 +39,17 @@ docker images -aq | xargs docker rmi -f 2. Access your local {{site.data.conrefs.composer_full}} Playground by clicking this link: http://localhost:8080. -## Deploying business networks to {{site.data.conrefs.hlf_full}} Beta +## Deploying business networks to {{site.data.conrefs.hlf_full}} RC -In {{site.data.conrefs.hlf_full}} Beta peers now enforce the concepts of admins and members. Admin user's identities and crypto material must be available to the peer at deployment. To make that identity and its crypto material available, your must import it to your local `keyValStore` directory before deploying the business network. To import the identity, use the [`composer identity import` command](../reference/composer.identity.import.html). When importing an identity, you do not assign it a secret, however the `composer network deploy` command requires a secret. If you are using an imported identity, you can enter any value for the secret. +In {{site.data.conrefs.hlf_full}} RC peers now enforce the concepts of admins and members. Admin user's identities and crypto material must be available to the peer at deployment. To make that identity and its crypto material available, your must import it to your local `keyValStore` directory before deploying the business network. To import the identity, use the [`composer identity import` command](../reference/composer.identity.import.html). When importing an identity, you do not assign it a secret, however the `composer network deploy` command requires a secret. If you are using an imported identity, you can enter any value for the secret. When connecting to the peer you must use an identity (certificate) where the Common Name (CN) contains the text `admin`, for example, `PeerAdmin`, `myadmin`, `Admin` or `AdminPeer` are all valid Common Names. Peers in different organizations may have different admin users. Only an admin user of peer's organization will be able to deploy a business network to their peers. -Due to many breaking API changes between {{site.data.conrefs.hlf_full}} v1.0 alpha 1 and {{site.data.conrefs.hlf_full}} v1.0 beta, {{site.data.conrefs.composer_full}} only supports the beta level and cannot support older versions of {{site.data.conrefs.hlf_full}} v1.0 (e.g. alpha 1). +Due to many breaking API changes between {{site.data.conrefs.hlf_full}} v1.0 alpha 1 and {{site.data.conrefs.hlf_full}} v1.0 RC, {{site.data.conrefs.composer_full}} only supports the RC level and cannot support older versions of {{site.data.conrefs.hlf_full}} v1.0 (e.g. alpha 1). ### Deploying business networks using Playground locally -When deploying a business network to {{site.data.conrefs.hlf_full}} Beta using the Playground locally, you must follow the process above to connect using the peer admin identity. However, in order to create identities and interact with your business network in the Playground, you must use the certificate authority admin identity. +When deploying a business network to {{site.data.conrefs.hlf_full}} RC using the Playground locally, you must follow the process above to connect using the peer admin identity. However, in order to create identities and interact with your business network in the Playground, you must use the certificate authority admin identity. --- diff --git a/packages/composer-website/jekylldocs/introduction/introduction.md b/packages/composer-website/jekylldocs/introduction/introduction.md index 19a952eaa0..1d5fae8f3d 100644 --- a/packages/composer-website/jekylldocs/introduction/introduction.md +++ b/packages/composer-website/jekylldocs/introduction/introduction.md @@ -1,7 +1,9 @@ --- layout: default -title: Hyperledger Composer - Overview +title: Introduction category: overview +section: introduction +index-order: 0 sidebar: sidebars/introduction.md excerpt: Hyperledger Composer overview --- diff --git a/packages/composer-website/jekylldocs/introduction/key-concepts.md b/packages/composer-website/jekylldocs/introduction/key-concepts.md index 1399141825..2e04897846 100644 --- a/packages/composer-website/jekylldocs/introduction/key-concepts.md +++ b/packages/composer-website/jekylldocs/introduction/key-concepts.md @@ -1,11 +1,12 @@ --- layout: default -title: Key Concepts in Hyperledger Composer +title: Key Concepts sidebar: sidebars/introduction.md -excerpt: +section: introduction +index-order: 1 --- -# Features and Concepts in {{site.data.conrefs.composer_full}} +# Key Concepts in {{site.data.conrefs.composer_full}} {{site.data.conrefs.composer_full}} is a programming model containing a modeling language, and a set of APIs to quickly define and deploy business networks and applications that allow **participants** to send **transactions** that exchange **assets**. diff --git a/packages/composer-website/jekylldocs/introduction/solution-architecture.md b/packages/composer-website/jekylldocs/introduction/solution-architecture.md index 5d155c023e..4a15228418 100644 --- a/packages/composer-website/jekylldocs/introduction/solution-architecture.md +++ b/packages/composer-website/jekylldocs/introduction/solution-architecture.md @@ -1,8 +1,9 @@ --- layout: default -title: Solution Architecture +title: Typical Solution Architecture sidebar: sidebars/introduction.md -excerpt: +section: introduction +index-order: 2.5 --- # Typical {{site.data.conrefs.composer_full}} Solution Architecture @@ -29,7 +30,7 @@ excerpt: {{site.data.conrefs.composer_full}} has been designed to support different pluggable runtimes, and currently has four runtime implementations: * {{site.data.conrefs.hlf_full}} version 0.6. State is stored on the distributed ledger. -* {{site.data.conrefs.hlf_full}} version 1.0 beta 1. State is stored on the distributed ledger. +* {{site.data.conrefs.hlf_full}} version 1.0 RC 1. State is stored on the distributed ledger. * Web, which executes within a web page, and is used by Playground. State is stored in browser local storage. * Embedded, which executes within a Node.js process, and is used primarily for unit testing business logic. State is stored in an in-memory key-value store. diff --git a/packages/composer-website/jekylldocs/managing/updating-composer.md b/packages/composer-website/jekylldocs/managing/updating-composer.md index baf08b8440..2f2450945d 100644 --- a/packages/composer-website/jekylldocs/managing/updating-composer.md +++ b/packages/composer-website/jekylldocs/managing/updating-composer.md @@ -4,10 +4,31 @@ title: Updating Hyperledger Composer category: tasks section: managing sidebar: sidebars/managing.md -excerpt: +excerpt: To [update Hyperledger Composer](./updating-composer.html) to a new version, the Hyperledger Composer components must be uninstalled and reinstalled using npm. index-order: 7 --- # Updating {{site.data.conrefs.composer_full}} -After deploying {{site.data.conrefs.composer_full}} you may wish to upgrade to a new version for additional functionality. +After deploying {{site.data.conrefs.composer_full}} you may wish to upgrade to a new version. To update your installed version of {{site.data.conrefs.composer_full}} you must uninstall the client, admin, and runtime CLI components and reinstall them by using npm. + +## Procedure + +1. Uninstall the {{site.data.conrefs.composer_full}} components by using the following commands: + + npm uninstall -g composer-cli + npm uninstall -g composer-rest-server + npm uninstall -g generator-fabric-composer + +2. Install the latest version of the {{site.data.conrefs.composer_full}} components by using the following commands: + + npm install -g composer-cli + npm install -g composer-rest-server + npm install -g generator-fabric-composer + + +## What next? + +- [Defining a business network](../business-network/bnd-create.html) +- [Modeling language](../reference/cto_language.html) +- [Managing your solution](./managingindex.html) diff --git a/packages/composer-website/jekylldocs/problems/diagnostics.md b/packages/composer-website/jekylldocs/problems/diagnostics.md index 3080179b23..dc3d631f65 100644 --- a/packages/composer-website/jekylldocs/problems/diagnostics.md +++ b/packages/composer-website/jekylldocs/problems/diagnostics.md @@ -4,6 +4,8 @@ title: Task - Diagnosing Problems category: tasks sidebar: sidebars/problems.md excerpt: Diagnosing Problems +section: diagnostics +index-order: 0 --- # Diagnosing Problems diff --git a/packages/composer-website/jekylldocs/reference/JSDOC-README.md b/packages/composer-website/jekylldocs/reference/JSDOC-README.md new file mode 100644 index 0000000000..76132da2bc --- /dev/null +++ b/packages/composer-website/jekylldocs/reference/JSDOC-README.md @@ -0,0 +1,34 @@ +--- +index-order: 9 +title: API Documentation +section: reference +layout: default +exception: API +excerpt: The Client, Admin, and Runtime components of Hyperledger Composer contain [JavaScript APIs](../jsdoc/index.html) for application integration. +--- + +# Hyperledger Composer API +Hyperledger Composer is an application development framework for building Blockchain applications based on Hyperledger. This is the JavaScript documentation for the Hyperledger Composer Client, Admin, and Runtime JavaScript APIs. + + +## Overview +The major components of Hyperledger Composer are: + +1. The Hyperledger Composer language for describing the structure of resources (assets, participants +and transactions) that participate in a blockchain backed business network. +2. JavaScript APIs to query, create, update and delete resources and submit transactions + from client applications. Hyperledger Composer resources are stored on the Blockchain. +3. JavaScript transaction processor functions that runs on Hyperledger Fabric when transactions are +submitted for processing. These functions may update the state of resources +stored on the Blockchain via server-side Hyperledger Composer APIs. + +## Resources + +- [Documentation](https://hyperledger.github.io/composer/) +- [npm modules](https://www.npmjs.com/search?q=hyperledger-composer) +- [GitHub repositories](https://github.com/hyperledger/composer) + +## Contributing + +To read more about the community and guidelines for submitting pull requests, +please read the [Contributing document](https://github.com/hyperledger/composer/blob/master/CONTRIBUTING.md). diff --git a/packages/composer-website/jekylldocs/reference/commands.md b/packages/composer-website/jekylldocs/reference/commands.md index 46031a7f24..c916b4c4e5 100644 --- a/packages/composer-website/jekylldocs/reference/commands.md +++ b/packages/composer-website/jekylldocs/reference/commands.md @@ -44,6 +44,10 @@ Permanently disable a business network definition: [composer network undeploy](. List the contents of a deployed Business Network: [composer network list](./composer.network.list.md) +`composer network logLevel` + +Return or update the log level for the composer runtime: [`composer network logLevel`](./composer.network.logLevel.md) + `composer network ping` Test the connection to a deployed a Business Network: [composer network ping](./composer.network.ping.md) diff --git a/packages/composer-website/jekylldocs/reference/composer.identity.issue.md b/packages/composer-website/jekylldocs/reference/composer.identity.issue.md index ea3758c2d5..9deebb694e 100644 --- a/packages/composer-website/jekylldocs/reference/composer.identity.issue.md +++ b/packages/composer-website/jekylldocs/reference/composer.identity.issue.md @@ -29,6 +29,8 @@ Options: --newUserId, -u The user ID for the new identity [string] [required] --participantId, -a The particpant to issue the new identity to [string] [required] --issuer, -x If the new identity should be able to issue other new identities [boolean] [required] + --option, -o Options that are specific specific to a connection. Multiple options are specified by repeating this option [string] + --optionFile, -O A file containing options that are specific to connection [string] ``` ## Options diff --git a/packages/composer-website/jekylldocs/reference/composer.network.deploy.md b/packages/composer-website/jekylldocs/reference/composer.network.deploy.md index 15ea40633d..9e17dbbe64 100644 --- a/packages/composer-website/jekylldocs/reference/composer.network.deploy.md +++ b/packages/composer-website/jekylldocs/reference/composer.network.deploy.md @@ -18,12 +18,15 @@ composer network deploy -a -i -s -p -l -i -s +``` + +### Options +``` +--help Show help [boolean] +-v, --version Show version number [boolean] +--businessNetworkName, -n The business network name [string] [required] +--connectionProfileName, -p The connection profile name [string] [required] +--newlevel, -l the new logging level (INFO/WARNING/ERROR/DEBUG) [string] +--enrollId, -i The enrollment ID of the user [string] [required] +--enrollSecret, -s The enrollment secret of the user [string] +``` diff --git a/packages/composer-website/jekylldocs/reference/connectionprofile.md b/packages/composer-website/jekylldocs/reference/connectionprofile.md index 8613af6f5f..ad6727e166 100644 --- a/packages/composer-website/jekylldocs/reference/connectionprofile.md +++ b/packages/composer-website/jekylldocs/reference/connectionprofile.md @@ -4,7 +4,7 @@ title: Connection Profiles section: reference index-order: 6 sidebar: sidebars/reference.md -excerpt: In order to connect your business network to a fabric, you must [**define a connection profile**](./connectionprofile.html). Connection profiles contain the information necessary to connect to a fabric. This topic contains example connection profiles for Hyperledger Fabric v0.6 and v1.0-beta. +excerpt: In order to connect your business network to a fabric, you must [**define a connection profile**](./connectionprofile.html). Connection profiles contain the information necessary to connect to a fabric. This topic contains example connection profiles for Hyperledger Fabric v0.6 and v1.0-RC. --- # Connection Profiles @@ -36,7 +36,7 @@ A Connection Profile is used by {{site.data.conrefs.composer_full}} to connect t "peerURL": , "eventHubURL": } - If you are creating a connection profile for {{site.data.conrefs.hlf_full}} v1.0 beta, use the following format: + If you are creating a connection profile for {{site.data.conrefs.hlf_full}} v1.0 RC, use the following format: { "type": "hlfv1", @@ -106,7 +106,7 @@ A Connection Profile is used by {{site.data.conrefs.composer_full}} to connect t "maxRecvSize": 15 } - - `type` defines the version of {{site.data.conrefs.hlf_full}} that you will connect to. To connect to {{site.data.conrefs.hlf_full}} v1.0-beta is must be `hlfv1`. + - `type` defines the version of {{site.data.conrefs.hlf_full}} that you will connect to. To connect to {{site.data.conrefs.hlf_full}} v1.0-RC is must be `hlfv1`. - `orderers` is an array of objects which describe the orderes to communicate with. Within `orderers`, you must define the `url` of each orderer. If you are connecting via TLS, all `url` properties in your connection profile must begin with `grpcs://` and must also contain the correct TLS certificate in the `cert` property. - `peers` is an array of objects describing the peers to communicate with. Each `peer` must have a defined `requestURL` and a defined `eventURL`. If you are connecting using TLS, each `peer` must also have the correct TLS certificate in the `cert` property. @@ -117,5 +117,6 @@ A Connection Profile is used by {{site.data.conrefs.composer_full}} to connect t - `mspid` is the Membership Service Provider ID of your organization. It is associated with the enrollment id that you will use to interact with the business network. - `timeout` is an optional property which controls the timeout for each request made to peers and orderers. Please note, some commands may make several sequential requests and the timeout will be applied individually to each request. + - `globalcert` defines the TLS certificate which is used for all peers and orderers if no `cert` property is specified. If a `cert` property is specified, it overrides the `globalcert` property only for the peer or orderer it is specified for. - `maxSendSize` is an optional property which defines the size limit of outbound grpc messages being send to orderers and peers. The value is defined in megabytes. If this is not set, grpc sets a default. Setting this property to `-1` results in no size restriction. - `maxRecvSize` is an optional property which defines the size limit of inbound grpc messages being received from orderers and peers. The value is defined in megabytes. If this is not set, grpc sets a default. Setting this property to `-1` results in no size restriction. diff --git a/packages/composer-website/jekylldocs/reference/glossary.md b/packages/composer-website/jekylldocs/reference/glossary.md index a10066aa46..31bdf31c03 100644 --- a/packages/composer-website/jekylldocs/reference/glossary.md +++ b/packages/composer-website/jekylldocs/reference/glossary.md @@ -2,7 +2,7 @@ layout: default title: Hyperledger Composer Glossary of Terms section: reference -index-order: 9 +index-order: 10 sidebar: sidebars/reference.md excerpt: The glossary contains [**definitions of all Hyperledger Composer terms**](./glossary.html) for developing a solution with Hyperledger Composer. --- diff --git a/packages/composer-website/jekylldocs/reference/reference-index.md b/packages/composer-website/jekylldocs/reference/reference-index.md index 1e53b577a3..7e5d87b316 100644 --- a/packages/composer-website/jekylldocs/reference/reference-index.md +++ b/packages/composer-website/jekylldocs/reference/reference-index.md @@ -15,7 +15,7 @@ The {{site.data.conrefs.composer_full}} reference material contains a number of {% assign sorted = site.pages | sort: 'index-order' %} {% for page in sorted %} -{% if page.section == 'reference' and page.title != "Reference Index" %} +{% if page.section == 'reference' and page.title != "Reference Index" or page.exception == 'API' %} ### {{ page.title }} {{ page.excerpt }} {% endif %} diff --git a/packages/composer-website/jekylldocs/support/index.md b/packages/composer-website/jekylldocs/support/index.md index d4865b3b68..4a0a8d7b48 100644 --- a/packages/composer-website/jekylldocs/support/index.md +++ b/packages/composer-website/jekylldocs/support/index.md @@ -4,6 +4,8 @@ title: Support category: start sidebar: sidebars/support.md excerpt: Get support with Hyperledger Composer +section: support +index-order: 0 --- #Community From e682b18c22c4a1594d52249e1f443467a19ab39b Mon Sep 17 00:00:00 2001 From: fabric-composer-app Date: Thu, 6 Jul 2017 13:06:20 +0000 Subject: [PATCH 03/88] Automatic version bump to 0.9.2 --- lerna.json | 2 +- package.json | 2 +- packages/composer-admin/package.json | 8 ++++---- packages/composer-cli/package.json | 10 +++++----- packages/composer-client/package.json | 8 ++++---- packages/composer-common/package.json | 2 +- .../composer-connector-embedded/package.json | 8 ++++---- packages/composer-connector-hlf/package.json | 6 +++--- packages/composer-connector-hlfv1/package.json | 6 +++--- packages/composer-connector-proxy/package.json | 4 ++-- .../composer-connector-server/package.json | 10 +++++----- packages/composer-connector-web/package.json | 8 ++++---- packages/composer-cucumber-steps/package.json | 10 +++++----- packages/composer-playground-api/package.json | 6 +++--- packages/composer-playground/package.json | 18 +++++++++--------- packages/composer-rest-server/package.json | 12 ++++++------ .../composer-runtime-embedded/package.json | 8 ++++---- packages/composer-runtime-hlf/package.json | 4 ++-- packages/composer-runtime-hlfv1/package.json | 4 ++-- packages/composer-runtime-pouchdb/package.json | 6 +++--- packages/composer-runtime-web/package.json | 8 ++++---- packages/composer-runtime/package.json | 4 ++-- packages/composer-systests/package.json | 16 ++++++++-------- packages/composer-website/package.json | 10 +++++----- .../package.json | 10 +++++----- .../loopback-connector-composer/package.json | 10 +++++----- 26 files changed, 100 insertions(+), 100 deletions(-) diff --git a/lerna.json b/lerna.json index 21e6fe4b37..05e6b4521e 100644 --- a/lerna.json +++ b/lerna.json @@ -3,6 +3,6 @@ "packages": [ "packages/*" ], - "version": "0.9.1", + "version": "0.9.2", "hoist": true } diff --git a/package.json b/package.json index 3166a6aa55..945a0fac38 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ }, "name": "composer", "description": "You must install [Lerna](https://lernajs.io) to build this multi-package repository.", - "version": "0.9.1", + "version": "0.9.2", "main": "index.js", "private": true, "scripts": { diff --git a/packages/composer-admin/package.json b/packages/composer-admin/package.json index 5ba99c8722..aded8ffafe 100644 --- a/packages/composer-admin/package.json +++ b/packages/composer-admin/package.json @@ -1,6 +1,6 @@ { "name": "composer-admin", - "version": "0.9.1", + "version": "0.9.2", "description": "Hyperledger Composer Admin, code that manages business networks deployed to Hyperledger Fabric", "engines": { "node": ">=6", @@ -42,9 +42,9 @@ "sinon-as-promised": "^4.0.2" }, "dependencies": { - "composer-common": "^0.9.1", - "composer-connector-hlf": "^0.9.1", - "composer-connector-hlfv1": "^0.9.1" + "composer-common": "^0.9.2", + "composer-connector-hlf": "^0.9.2", + "composer-connector-hlfv1": "^0.9.2" }, "license-check-config": { "src": [ diff --git a/packages/composer-cli/package.json b/packages/composer-cli/package.json index 13d9d5f707..60efd18aa8 100644 --- a/packages/composer-cli/package.json +++ b/packages/composer-cli/package.json @@ -1,6 +1,6 @@ { "name": "composer-cli", - "version": "0.9.1", + "version": "0.9.2", "description": "Hyperledger Composer command line interfaces (CLIs)", "engines": { "node": ">=6", @@ -42,10 +42,10 @@ "dependencies": { "chalk": "^1.1.3", "cli-table": "^0.3.1", - "composer-admin": "^0.9.1", - "composer-client": "^0.9.1", - "composer-common": "^0.9.1", - "composer-rest-server": "^0.9.1", + "composer-admin": "^0.9.2", + "composer-client": "^0.9.2", + "composer-common": "^0.9.2", + "composer-rest-server": "^0.9.2", "homedir": "^0.6.0", "npm-paths": "^0.1.3", "nunjucks": "^3.0.0", diff --git a/packages/composer-client/package.json b/packages/composer-client/package.json index 338df94ca5..ec87a53e1c 100644 --- a/packages/composer-client/package.json +++ b/packages/composer-client/package.json @@ -1,6 +1,6 @@ { "name": "composer-client", - "version": "0.9.1", + "version": "0.9.2", "description": "The node.js client library for Hyperledger Composer, a development framework for Hyperledger Fabric", "engines": { "node": ">=6", @@ -42,9 +42,9 @@ "logError": true }, "dependencies": { - "composer-common": "^0.9.1", - "composer-connector-hlf": "^0.9.1", - "composer-connector-hlfv1": "^0.9.1", + "composer-common": "^0.9.2", + "composer-connector-hlf": "^0.9.2", + "composer-connector-hlfv1": "^0.9.2", "uuid": "^3.0.1" }, "devDependencies": { diff --git a/packages/composer-common/package.json b/packages/composer-common/package.json index 8b8cb131ea..c264334181 100644 --- a/packages/composer-common/package.json +++ b/packages/composer-common/package.json @@ -1,6 +1,6 @@ { "name": "composer-common", - "version": "0.9.1", + "version": "0.9.2", "description": "Hyperledger Composer Common, code that is common across client, admin and runtime.", "engines": { "node": ">=6", diff --git a/packages/composer-connector-embedded/package.json b/packages/composer-connector-embedded/package.json index 65c85d85c3..83acfc466e 100644 --- a/packages/composer-connector-embedded/package.json +++ b/packages/composer-connector-embedded/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-embedded", - "version": "0.9.1", + "version": "0.9.2", "description": "The embedded client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -52,9 +52,9 @@ "watchify": "^3.7.0" }, "dependencies": { - "composer-common": "^0.9.1", - "composer-runtime": "^0.9.1", - "composer-runtime-embedded": "^0.9.1" + "composer-common": "^0.9.2", + "composer-runtime": "^0.9.2", + "composer-runtime-embedded": "^0.9.2" }, "nyc": { "exclude": [ diff --git a/packages/composer-connector-hlf/package.json b/packages/composer-connector-hlf/package.json index 4a6002650f..ce7f76d1e2 100644 --- a/packages/composer-connector-hlf/package.json +++ b/packages/composer-connector-hlf/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-hlf", - "version": "0.9.1", + "version": "0.9.2", "description": "The Hyperledger Fabric Client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -40,8 +40,8 @@ "logError": true }, "dependencies": { - "composer-common": "^0.9.1", - "composer-runtime-hlf": "^0.9.1", + "composer-common": "^0.9.2", + "composer-runtime-hlf": "^0.9.2", "fs-extra": "^1.0.0", "hfc": "^0.6.5", "semver": "^5.3.0", diff --git a/packages/composer-connector-hlfv1/package.json b/packages/composer-connector-hlfv1/package.json index 2f80770e8f..cc494612ed 100644 --- a/packages/composer-connector-hlfv1/package.json +++ b/packages/composer-connector-hlfv1/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-hlfv1", - "version": "0.9.1", + "version": "0.9.2", "description": "The Hyperledger Fabric v1.x Client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -40,8 +40,8 @@ "logError": true }, "dependencies": { - "composer-common": "^0.9.1", - "composer-runtime-hlfv1": "^0.9.1", + "composer-common": "^0.9.2", + "composer-runtime-hlfv1": "^0.9.2", "fabric-ca-client": "1.0.0-rc1", "fabric-client": "1.0.0-rc1", "fs-extra": "^1.0.0", diff --git a/packages/composer-connector-proxy/package.json b/packages/composer-connector-proxy/package.json index b26a0043ef..050cd45120 100644 --- a/packages/composer-connector-proxy/package.json +++ b/packages/composer-connector-proxy/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-proxy", - "version": "0.9.1", + "version": "0.9.2", "description": "The proxying client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -54,7 +54,7 @@ "watchify": "^3.7.0" }, "dependencies": { - "composer-common": "^0.9.1", + "composer-common": "^0.9.2", "socket.io-client": "^1.7.3" }, "nyc": { diff --git a/packages/composer-connector-server/package.json b/packages/composer-connector-server/package.json index 38f65cffdc..8a18522ed6 100644 --- a/packages/composer-connector-server/package.json +++ b/packages/composer-connector-server/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-server", - "version": "0.9.1", + "version": "0.9.2", "description": "The remote connector server for Hyperledger Composer", "engines": { "node": ">=6", @@ -44,10 +44,10 @@ "logError": true }, "dependencies": { - "composer-common": "^0.9.1", - "composer-connector-embedded": "^0.9.1", - "composer-connector-hlf": "^0.9.1", - "composer-connector-hlfv1": "^0.9.1", + "composer-common": "^0.9.2", + "composer-connector-embedded": "^0.9.2", + "composer-connector-hlf": "^0.9.2", + "composer-connector-hlfv1": "^0.9.2", "serializerr": "^1.0.3", "socket.io": "^1.7.3", "uuid": "^3.0.1", diff --git a/packages/composer-connector-web/package.json b/packages/composer-connector-web/package.json index 32a16924fe..0b90569753 100644 --- a/packages/composer-connector-web/package.json +++ b/packages/composer-connector-web/package.json @@ -1,6 +1,6 @@ { "name": "composer-connector-web", - "version": "0.9.1", + "version": "0.9.2", "description": "The web client connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -62,9 +62,9 @@ "watchify": "^3.7.0" }, "dependencies": { - "composer-common": "^0.9.1", - "composer-runtime": "^0.9.1", - "composer-runtime-web": "^0.9.1", + "composer-common": "^0.9.2", + "composer-runtime": "^0.9.2", + "composer-runtime-web": "^0.9.2", "uuid": "^3.0.1" } } diff --git a/packages/composer-cucumber-steps/package.json b/packages/composer-cucumber-steps/package.json index 1fc80317e2..96f1bc642b 100644 --- a/packages/composer-cucumber-steps/package.json +++ b/packages/composer-cucumber-steps/package.json @@ -1,6 +1,6 @@ { "name": "composer-cucumber-steps", - "version": "0.9.1", + "version": "0.9.2", "description": "A library of Cucumber steps for testing Hyperledger Composer", "main": "index.js", "scripts": { @@ -70,10 +70,10 @@ "dependencies": { "browserfs": "^1.1.0", "chai": "^3.5.0", - "composer-admin": "^0.9.1", - "composer-client": "^0.9.1", - "composer-common": "^0.9.1", - "composer-connector-embedded": "^0.9.1", + "composer-admin": "^0.9.2", + "composer-client": "^0.9.2", + "composer-common": "^0.9.2", + "composer-connector-embedded": "^0.9.2", "thenify-all": "^1.6.0" } } diff --git a/packages/composer-playground-api/package.json b/packages/composer-playground-api/package.json index d47b8a4166..29d07b98d8 100644 --- a/packages/composer-playground-api/package.json +++ b/packages/composer-playground-api/package.json @@ -1,6 +1,6 @@ { "name": "composer-playground-api", - "version": "0.9.1", + "version": "0.9.2", "description": "The REST API for the Hyperledger Composer Playground", "engines": { "node": ">=6", @@ -58,8 +58,8 @@ "dependencies": { "async": "^2.5.0", "body-parser": "^1.17.0", - "composer-common": "^0.9.1", - "composer-connector-server": "^0.9.1", + "composer-common": "^0.9.2", + "composer-connector-server": "^0.9.2", "dotenv": "^4.0.0", "express": "^4.15.2", "http-status": "^1.0.1", diff --git a/packages/composer-playground/package.json b/packages/composer-playground/package.json index 072de84733..aaf84bb42f 100644 --- a/packages/composer-playground/package.json +++ b/packages/composer-playground/package.json @@ -1,6 +1,6 @@ { "name": "composer-playground", - "version": "0.9.1", + "version": "0.9.2", "description": "A test harness/UI for the web runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -78,8 +78,8 @@ "dependencies": { "@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.24", "cheerio": "^0.22.0", - "composer-common": "^0.9.1", - "composer-playground-api": "^0.9.1", + "composer-common": "^0.9.2", + "composer-playground-api": "^0.9.2", "express": "^4.15.2", "fast-json-patch": "^1.1.8", "file-saver": "^1.3.3", @@ -133,12 +133,12 @@ "chai": "^3.5.0", "codelyzer": "^2.0.1", "codemirror": "5.26.0", - "composer-admin": "^0.9.1", - "composer-client": "^0.9.1", - "composer-connector-proxy": "^0.9.1", - "composer-connector-web": "^0.9.1", - "composer-runtime": "^0.9.1", - "composer-runtime-web": "^0.9.1", + "composer-admin": "^0.9.2", + "composer-client": "^0.9.2", + "composer-connector-proxy": "^0.9.2", + "composer-connector-web": "^0.9.2", + "composer-runtime": "^0.9.2", + "composer-runtime-web": "^0.9.2", "copy-webpack-plugin": "^4.0.1", "core-js": "^2.4.1", "css-loader": "^0.26.1", diff --git a/packages/composer-rest-server/package.json b/packages/composer-rest-server/package.json index 3329e89bbe..119ac0cad7 100644 --- a/packages/composer-rest-server/package.json +++ b/packages/composer-rest-server/package.json @@ -1,6 +1,6 @@ { "name": "composer-rest-server", - "version": "0.9.1", + "version": "0.9.2", "description": "Hyperledger Composer REST server that uses the Hyperledger Composer LoopBack connector", "engines": { "node": ">=6", @@ -36,7 +36,7 @@ "chalk": "^1.1.3", "clear": "0.0.1", "clui": "^0.3.1", - "composer-common": "^0.9.1", + "composer-common": "^0.9.2", "compression": "^1.0.3", "connect-ensure-login": "^0.1.1", "cookie-parser": "^1.4.3", @@ -52,7 +52,7 @@ "loopback-boot": "^2.23.0", "loopback-component-explorer": "^4.1.1", "loopback-component-passport": "^3.2.0", - "loopback-connector-composer": "^0.9.1", + "loopback-connector-composer": "^0.9.2", "passport-local": "^1.0.0", "serve-favicon": "^2.0.1", "strong-error-handler": "^1.0.1", @@ -64,9 +64,9 @@ "chai-as-promised": "^6.0.0", "chai-http": "^3.0.0", "clone": "^2.1.1", - "composer-admin": "^0.9.1", - "composer-client": "^0.9.1", - "composer-connector-embedded": "^0.9.1", + "composer-admin": "^0.9.2", + "composer-client": "^0.9.2", + "composer-connector-embedded": "^0.9.2", "eslint": "^3.17.1", "jsdoc": "^3.4.3", "license-check": "^1.1.5", diff --git a/packages/composer-runtime-embedded/package.json b/packages/composer-runtime-embedded/package.json index 0cc6ae21b4..62b316a4ae 100644 --- a/packages/composer-runtime-embedded/package.json +++ b/packages/composer-runtime-embedded/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime-embedded", - "version": "0.9.1", + "version": "0.9.2", "description": "The embedded runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -52,9 +52,9 @@ "logError": true }, "dependencies": { - "composer-common": "^0.9.1", - "composer-runtime": "^0.9.1", - "composer-runtime-pouchdb": "^0.9.1", + "composer-common": "^0.9.2", + "composer-runtime": "^0.9.2", + "composer-runtime-pouchdb": "^0.9.2", "debug": "^2.6.2", "istanbul-lib-instrument": "^1.7.2", "request": "^2.81.0", diff --git a/packages/composer-runtime-hlf/package.json b/packages/composer-runtime-hlf/package.json index 922c63309b..8c061b9b5e 100644 --- a/packages/composer-runtime-hlf/package.json +++ b/packages/composer-runtime-hlf/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime-hlf", - "version": "0.9.1", + "version": "0.9.2", "description": "The Hyperledger Fabric runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -28,7 +28,7 @@ "babelify": "^7.3.0", "browserify": "^13.3.0", "browserify-replace": "^0.9.0", - "composer-runtime": "^0.9.1", + "composer-runtime": "^0.9.2", "exorcist": "^0.4.0", "fs-extra": "^1.0.0", "uglify-js": "2.7.5" diff --git a/packages/composer-runtime-hlfv1/package.json b/packages/composer-runtime-hlfv1/package.json index 624209e647..68e8cb2223 100644 --- a/packages/composer-runtime-hlfv1/package.json +++ b/packages/composer-runtime-hlfv1/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime-hlfv1", - "version": "0.9.1", + "version": "0.9.2", "description": "The Hyperledger Fabric v1.x runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -28,7 +28,7 @@ "babelify": "^7.3.0", "browserify": "^13.3.0", "browserify-replace": "^0.9.0", - "composer-runtime": "^0.9.1", + "composer-runtime": "^0.9.2", "exorcist": "^0.4.0", "fs-extra": "^1.0.0", "uglify-js": "2.7.5" diff --git a/packages/composer-runtime-pouchdb/package.json b/packages/composer-runtime-pouchdb/package.json index 65bacd390d..76e96e0587 100644 --- a/packages/composer-runtime-pouchdb/package.json +++ b/packages/composer-runtime-pouchdb/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime-pouchdb", - "version": "0.9.1", + "version": "0.9.2", "description": "Common PouchDB based runtime container code for Hyperledger Composer", "engines": { "node": ">=6", @@ -52,8 +52,8 @@ "logError": true }, "dependencies": { - "composer-common": "^0.9.1", - "composer-runtime": "^0.9.1", + "composer-common": "^0.9.2", + "composer-runtime": "^0.9.2", "debug": "^2.6.2", "istanbul-lib-instrument": "^1.7.2", "pouchdb-adapter-idb": "^6.2.0", diff --git a/packages/composer-runtime-web/package.json b/packages/composer-runtime-web/package.json index b7e4e5ff21..c5c73bfdbf 100644 --- a/packages/composer-runtime-web/package.json +++ b/packages/composer-runtime-web/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime-web", - "version": "0.9.1", + "version": "0.9.2", "description": "The web runtime container for Hyperledger Composer", "engines": { "node": ">=6", @@ -62,9 +62,9 @@ "logError": true }, "dependencies": { - "composer-common": "^0.9.1", - "composer-runtime": "^0.9.1", - "composer-runtime-pouchdb": "^0.9.1", + "composer-common": "^0.9.2", + "composer-runtime": "^0.9.2", + "composer-runtime-pouchdb": "^0.9.2", "uuid": "^3.0.1", "xhr": "^2.4.0" } diff --git a/packages/composer-runtime/package.json b/packages/composer-runtime/package.json index 7ca776a5bb..767bb80026 100644 --- a/packages/composer-runtime/package.json +++ b/packages/composer-runtime/package.json @@ -1,6 +1,6 @@ { "name": "composer-runtime", - "version": "0.9.1", + "version": "0.9.2", "description": "The runtime execution environment for Hyperledger Composer", "engines": { "node": ">=6", @@ -62,7 +62,7 @@ "logError": true }, "dependencies": { - "composer-common": "^0.9.1", + "composer-common": "^0.9.2", "debug": "^2.6.2", "fast-json-patch": "^1.1.8", "jsonata": "^1.2.2", diff --git a/packages/composer-systests/package.json b/packages/composer-systests/package.json index 3f63745953..5eae127d61 100644 --- a/packages/composer-systests/package.json +++ b/packages/composer-systests/package.json @@ -1,6 +1,6 @@ { "name": "composer-systests", - "version": "0.9.1", + "version": "0.9.2", "private": true, "description": "System tests and automation for Hyperledger Composer", "engines": { @@ -42,13 +42,13 @@ "chai": "^3.5.0", "chai-as-promised": "^6.0.0", "chai-subset": "^1.3.0", - "composer-admin": "^0.9.1", - "composer-client": "^0.9.1", - "composer-common": "^0.9.1", - "composer-connector-embedded": "^0.9.1", - "composer-connector-proxy": "^0.9.1", - "composer-connector-server": "^0.9.1", - "composer-connector-web": "^0.9.1", + "composer-admin": "^0.9.2", + "composer-client": "^0.9.2", + "composer-common": "^0.9.2", + "composer-connector-embedded": "^0.9.2", + "composer-connector-proxy": "^0.9.2", + "composer-connector-server": "^0.9.2", + "composer-connector-web": "^0.9.2", "eslint": "^3.17.1", "homedir": "^0.6.0", "karma": "^1.3.0", diff --git a/packages/composer-website/package.json b/packages/composer-website/package.json index a5b03913d7..7db5815bb9 100644 --- a/packages/composer-website/package.json +++ b/packages/composer-website/package.json @@ -1,6 +1,6 @@ { "name": "composer-website", - "version": "0.9.1", + "version": "0.9.2", "private": true, "description": "Hyperledger Composer is a blockchain development framework for Hyperledger Fabric: a library of assets/functions for creating blockchain-based applications.", "engines": { @@ -30,10 +30,10 @@ "author": "Hyperledger Composer", "license": "Apache-2.0", "devDependencies": { - "composer-admin": "^0.9.1", - "composer-client": "^0.9.1", - "composer-common": "^0.9.1", - "composer-runtime": "^0.9.1", + "composer-admin": "^0.9.2", + "composer-client": "^0.9.2", + "composer-common": "^0.9.2", + "composer-runtime": "^0.9.2", "jsdoc": "^3.4.3", "node-plantuml": "^0.5.0", "sanitize-html": "^1.14.1" diff --git a/packages/generator-hyperledger-composer/package.json b/packages/generator-hyperledger-composer/package.json index 2a6b070dc2..584fa4b753 100755 --- a/packages/generator-hyperledger-composer/package.json +++ b/packages/generator-hyperledger-composer/package.json @@ -1,6 +1,6 @@ { "name": "generator-hyperledger-composer", - "version": "0.9.1", + "version": "0.9.2", "description": "Generates projects from Hyperledger Composer business network definitions", "engines": { "node": ">=6", @@ -15,8 +15,8 @@ "liveNetworkTest": "mocha -t 0 test/angular-network.js" }, "dependencies": { - "composer-client": "^0.9.1", - "composer-common": "^0.9.1", + "composer-client": "^0.9.2", + "composer-common": "^0.9.2", "shelljs": "^0.7.7", "underscore.string": "^3.3.4", "yeoman-generator": "^0.24.1" @@ -29,8 +29,8 @@ "license": "Apache-2.0", "devDependencies": { "@angular/cli": "^1.0.0-rc.0", - "composer-admin": "^0.9.1", - "composer-connector-embedded": "^0.9.1", + "composer-admin": "^0.9.2", + "composer-connector-embedded": "^0.9.2", "mocha": "^3.4.2", "typings": "^2.1.0", "yeoman-assert": "^3.0.0", diff --git a/packages/loopback-connector-composer/package.json b/packages/loopback-connector-composer/package.json index 294672440a..aa09d11610 100644 --- a/packages/loopback-connector-composer/package.json +++ b/packages/loopback-connector-composer/package.json @@ -1,6 +1,6 @@ { "name": "loopback-connector-composer", - "version": "0.9.1", + "version": "0.9.2", "description": "A Loopback connector for Hyperledger Composer", "engines": { "node": ">=6", @@ -26,8 +26,8 @@ "author": "Hyperledger Composer", "license": "Apache-2.0", "dependencies": { - "composer-client": "^0.9.1", - "composer-common": "^0.9.1", + "composer-client": "^0.9.2", + "composer-common": "^0.9.2", "loopback": "^3.4.0", "loopback-connector": "^4.0.0", "node-cache": "^4.1.1" @@ -35,8 +35,8 @@ "devDependencies": { "chai": "^3.5.0", "chai-as-promised": "^6.0.0", - "composer-admin": "^0.9.1", - "composer-connector-embedded": "^0.9.1", + "composer-admin": "^0.9.2", + "composer-connector-embedded": "^0.9.2", "eslint": "^3.17.1", "jsdoc": "^3.4.3", "license-check": "^1.1.5", From ab17b3992fdccafcd0e362380bee93c9e937afbf Mon Sep 17 00:00:00 2001 From: Tobias Hunter Date: Thu, 6 Jul 2017 16:48:15 +0100 Subject: [PATCH 04/88] homepage fixes (#1496) --- .../jekylldocs/_includes/indexfooter.html | 6 +++--- .../jekylldocs/assets/css/new-style.min.css | 2 +- .../jekylldocs/assets/css/new-style.scss | 12 ++++-------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/composer-website/jekylldocs/_includes/indexfooter.html b/packages/composer-website/jekylldocs/_includes/indexfooter.html index a89bcd5e94..dedebfa4cd 100644 --- a/packages/composer-website/jekylldocs/_includes/indexfooter.html +++ b/packages/composer-website/jekylldocs/_includes/indexfooter.html @@ -5,10 +5,10 @@ diff --git a/packages/composer-website/jekylldocs/assets/css/new-style.min.css b/packages/composer-website/jekylldocs/assets/css/new-style.min.css index 2a9d7aa49e..fe9e25d0e1 100644 --- a/packages/composer-website/jekylldocs/assets/css/new-style.min.css +++ b/packages/composer-website/jekylldocs/assets/css/new-style.min.css @@ -1 +1 @@ -@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600|Source+Code+Pro");html{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;height:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;padding-bottom:0}h1{font-family:"Open Sans";font-weight:300;font-size:2.50rem;color:#19273C}h2{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;font-size:1.35rem;line-height:130%;color:#19273C}p{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;color:#19273C;font-weight:400;font-size:0.9rem}svg{font-family:"Source Code Pro"}button{position:relative;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;user-select:none;background-image:none;border:1px solid transparent;border-radius:5px;margin:0;box-shadow:none;line-height:40px;cursor:pointer;background-color:#FDFDFD;flex-basis:auto;width:auto;padding-right:30px;padding-left:30px;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:0.9rem;font-weight:400;transition:0.4s cubic-bezier(0.075, 0.82, 0.165, 1);min-width:140px}button:focus{outline:none}button+button{margin-left:0.8em}button h1{display:inline;margin-right:.5em}button[disabled],button[disabled]:hover{opacity:.65;cursor:not-allowed}button.primary{background-color:#3F55AF;border-color:#3F55AF;color:#FDFDFD}button.primary:hover{background-color:#2D3D7D}button.primary.active,button.primary:active,button.primary:focus{background-color:#2D3D7D}button.primary[disabled],button.primary[disabled]:hover{background-color:#B8C1C1;border-color:#B8C1C1}button.secondary{background-color:#FDFDFD;color:#3F55AF;border-color:#E3ECEC}button.secondary:hover{background-color:#5068C2;border-color:#5068C2;color:#FDFDFD}button.secondary.active,button.secondary:active,button.secondary:focus{background-color:#5068C2;border-color:#5068C2;color:#FDFDFD}button.secondary[disabled],button.secondary[disabled]:hover{border-color:#B8C1C1;color:#B8C1C1}button.expand{background-color:#FDFDFD;border:1px solid #F1F3F7;border-radius:25px;box-shadow:0 0 20px rgba(0,0,0,0.1);padding:0;font-size:0.75em;line-height:25px;color:#8C9696}button.action{border:none;border-bottom:2px solid transparent;background-color:transparent;color:#19273C;min-width:0;padding:0;border-radius:0px}button.action:hover{color:#3F55AF;background-color:transparent;border-bottom:2px solid #3F55AF}button.action:hover svg.ibm-icon{fill:#3F55AF}button.action:focus,button.action.active,button.action:active{color:#3F55AF;background-color:transparent;border-bottom:2px solid #3F55AF}button.action:focus svg.ibm-icon,button.action.active svg.ibm-icon,button.action:active svg.ibm-icon{fill:#3F55AF}button.action[disabled],button.action[disabled]:hover{background-color:transparent;color:#F1F3F7;border-bottom:2px solid #F1F3F7}button.action[disabled] svg.ibm-icon,button.action[disabled]:hover svg.ibm-icon{fill:#F1F3F7}button.icon{background-color:transparent;min-width:0;padding:0}button.icon:hover{color:#3F55AF;background-color:transparent}button.icon:hover svg.ibm-icon{fill:#3F55AF}button.basic{background-color:#FDFDFD;border:1px solid #F1F3F7;border-radius:0.25em}.overlay{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGUlEQVQImWPwS9olhQsz+CXtkmLABQabJABXUBO1fQxIrwAAAABJRU5ErkJggg==) repeat,linear-gradient(137deg, #3F55AF 0%, #5376D4 40%, #5477D5 42%, #597FDE 52%, #B4E4FF 100%);overflow:hidden;top:0;left:0;bottom:0;right:0;z-index:-100}.hero_container{width:70vw;margin:auto;margin-top:17vh;display:flex;position:relative;flex-direction:row;z-index:10;box-sizing:border-box}.hero_container .leftcontent{flex-basis:40%;display:flex;flex-direction:column;padding-right:2rem;padding-bottom:8rem}.hero_container .hero_illustration{flex-basis:55%;padding-bottom:5rem;box-sizing:border-box}.hero_container .hero_illustration img{max-height:30rem}.hero_container button{box-shadow:0 1px 7px 0 rgba(0,0,0,0.2)}.hero_container h1{color:#FDFDFD;line-height:50px}.hero_container .social-container{display:flex}.hero_container .social-container ul{display:flex;flex-direction:row;margin:0;padding:0;height:100%}.hero_container .social-container li{margin-right:1.5rem;flex-direction:column;justify-content:center;height:100%;display:flex}.hero_container .social-container .icon2{font-size:1.2rem;height:100%;width:100%;transition:0.4s cubic-bezier(0.075, 0.82, 0.165, 1);color:#FDFDFD}.hero_container .social-container .icon2:hover{color:#2D3D7D}.buttoncontainer{margin-top:3.5rem;margin-bottom:1rem;display:flex}.readmore{margin-bottom:2rem;color:#FDFDFD}.readmore a{color:#FDFDFD;text-decoration:underline}.homepage-callout{display:flex;background-color:#FFD640;box-shadow:2px 2px 10px 0 rgba(0,0,0,0.2)}.homepage-callout .callout-copy{margin:auto;margin-top:1rem;margin-bottom:0.5rem}.homepage-callout .callout-copy a{text-decoration:none;border-bottom:1px solid #19273C;color:#19273C;transition:all 0.2s ease-in-out}.homepage-callout .callout-copy a:hover{border-bottom:1px solid #19273C;padding-bottom:3px}.navbar{display:flex;flex-basis:auto;width:100vw;height:4rem;margin:auto;background-color:none;color:#19273C}.navbar-fixed-top{border:none}.indexnav{display:flex;flex-direction:row;align-items:center;width:100%;height:3rem;z-index:11;padding-top:0px;position:absolute}.navcontent{font-size:0.9rem;display:flex;width:70vw;margin:auto;padding-top:2rem}.navlogo{flex-grow:1;flex-basis:100%;flex-shrink:1}.navlogo .brand{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;color:#FDFDFD;font-weight:400;font-size:0.9rem;cursor:pointer;text-decoration:none}.navlogo .brand a{text-decoration:none}.menuitems{display:flex;justify-content:flex-end;flex-basis:auto;flex-shrink:1;height:100%}.menuitems ul{display:flex;flex-direction:row;margin:0;padding:0;height:100%}.menuitems ul li{margin-left:1rem;margin-right:1rem;flex-direction:column;justify-content:center;height:100%;display:flex}.menuitems ul li a{color:#FDFDFD;text-decoration:none}.menuitems ul li a:hover{border-bottom:1px solid #FDFDFD;padding-bottom:3px}.footer-bg{display:flex;background-color:#3F55AF;width:100vw;height:auto}.footer-container{display:flex;width:70vw;margin:auto;padding-top:3rem;margin-bottom:4rem}.footer-container .footer-left{display:flex;flex-grow:1;flex-shrink:1}.footer-container .footer-left p{color:#FDFDFD}.footer-container .footer-left .icon2{font-size:20px;height:100%;width:100%;transition:0.4s cubic-bezier(0.075, 0.82, 0.165, 1);color:#19273C}.footer-container .footer-left .icon2:hover{color:#3F55AF}.footer-container .footer-right{display:flex;justify-content:flex-end;flex-basis:auto;flex-shrink:1;height:100%}.footer-container .footer-right ul{display:flex;flex-grow:2;flex-direction:row;margin:0;padding:0;height:100%}.footer-container .footer-right li{margin-left:2rem;flex-direction:column;justify-content:center;height:100%;display:flex}.footer-container .footer-right li a{color:#FDFDFD;text-decoration:none;font-size:0.9rem}.footer-container .footer-right li a:hover{border-bottom:1px solid #FDFDFD}.footer-container .footer-right li p{opacity:0.6;color:#FDFDFD}.trio{margin-top:9rem;margin-bottom:7rem;margin-left:auto;margin-right:auto;width:70vw;display:flex;flex-direction:row;justify-content:space-around;flex-wrap:wrap}.trio p{text-align:center}.trio h2{text-align:center;padding-bottom:1rem}.trio .trio-left{justify-content:center;flex-grow:0;flex-basis:20%;flex-shrink:1;flex-wrap:wrap}.trio .trio-left img{display:flex;height:60px;width:auto;margin:auto}.trio .trio-center{justify-content:center;flex-grow:0;flex-basis:20%;flex-shrink:1;flex-wrap:wrap}.trio .trio-center img{display:flex;height:60px;width:auto;margin:auto}.trio .trio-right{justify-content:center;flex-grow:0;flex-basis:20%;flex-shrink:1;flex-wrap:wrap}.trio .trio-right img{display:flex;height:60px;width:auto;margin:auto}.contact-bg{background-color:#F1F3F7;position:relative;padding-bottom:4rem;padding-top:4rem;height:auto}.contact-bg .contact-us{display:flex;flex-direction:row;justify-content:center;margin-left:auto;margin-right:auto;width:70vw}.contact-bg .contact-us .contact-illustration{flex-basis:40%;flex-grow:0}.contact-bg .contact-us .contact-illustration img{display:flex;height:17rem;width:auto}.contact-bg .contact-us .contact-content{flex-basis:50%}.contact-bg .contact-us .contact-content .social-icons{display:flex;flex-wrap:wrap;flex-direction:row}.contact-bg .contact-us .contact-content .social-icons .icon3{margin-top:0.5rem;margin-right:1rem;color:#3F55AF;font-size:1.5rem;width:auto}.contact-bg .contact-us .contact-content a{color:#19273C}.contact-bg .contact-us .contact-content a:hover{text-decoration:none}.contact-bg .contact-us .contact-content .community{color:#3F55AF;border-bottom:1px solid #3F55AF;padding-bottom:3px}.contact-bg .contact-us .contact-content .community:hover{color:#2D3D7D;border-bottom:1px solid #2D3D7D;padding-bottom:3px;text-decoration:none}.contact-bg .contact-us .contact-content .social-icons>div{display:flex;flex-basis:calc(40% - 30px);justify-content:center;flex-direction:column;margin-right:2rem}.contact-bg .contact-us .contact-content .social-icons>div>div{display:flex;flex-direction:row}.contact-bg .contact-us .contact-content button{margin-top:1rem;margin-bottom:1rem}.contact-bg .contact-us .contact-content p{margin-top:1rem}@media only screen and (max-width: 1200px){.hero_container{flex-direction:column-reverse;-webkit-flex-direction:row}.hero_container .hero_illustration{padding-bottom:2rem}.contact-bg .contact-us .contact-content .social-icons>div{flex-basis:calc(50% - 30px);flex-direction:column}.contact-bg .contact-us .contact-illustration{margin-right:2rem}}@media only screen and (max-width: 900px){.hero_container{-webkit-flex-direction:row}.homepage-callout .callout-copy{padding-left:2rem;padding-right:2rem}.trio{flex-direction:column;max-width:30rem;margin-top:4rem;margin-bottom:4rem}.trio .trio-left{margin-bottom:3rem}.trio .trio-center{margin-bottom:3rem}.trio .trio-right{margin-bottom:3rem}.contact-bg .contact-us{flex-direction:column}.contact-bg .contact-us .contact-illustration{padding-bottom:2rem}}@media only screen and (max-width: 750px){.hero_container{-webkit-flex-direction:row}.contact-bg .contact-us .contact-content .social-icons>div{flex-basis:calc(50% - 30px);flex-direction:column}.contact-bg .contact-us .contact-content .social-icons>div>div{flex-direction:row}}@media only screen and (max-width: 500px){.indexnav{overflow:hidden;height:auto}.indexnav .navcontent{flex-direction:column;-webkit-flex-direction:row}.indexnav .navcontent .navlogo{margin-bottom:0.5rem}.indexnav .navcontent .menuitems{justify-content:flex-start}.indexnav .navcontent .menuitems ul{margin-top:0.5rem}.indexnav .navcontent .menuitems ul li{margin-left:0;margin-right:2rem}.hero_container{-webkit-flex-direction:row}.trio{-webkit-flex-direction:row}.contact-bg .contact-us{-webkit-flex-direction:row}.contact-bg .contact-us .contact-content .social-icons{flex-direction:column;-webkit-flex-direction:row}.contact-bg .contact-us .contact-content .social-icons>div{flex-basis:calc(50% - 30px);flex-direction:column;-webkit-flex-direction:row}.contact-bg .contact-us .contact-content .social-icons>div>div{flex-direction:row;-webkit-flex-direction:row}.contact-bg .contact-us .contact-illustration img{height:auto}.hero_container .leftcontent .textcontent .buttoncontainer{display:flex}.footer-bg .footer-container{flex-wrap:wrap;flex-direction:column;-webkit-flex-direction:row}.footer-bg .footer-container .footer-right ul{flex-direction:column;-webkit-flex-direction:row}.footer-bg .footer-container .footer-right li{margin-left:0}.footer-bg .footer-container .footer-right li a{padding-top:0.6rem}.footer-bg .footer-container .footer-right li a:hover{border-bottom:none}} +@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600|Source+Code+Pro");html{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;height:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;padding-bottom:0}h1{font-family:"Open Sans";font-weight:300;font-size:2.50rem;color:#19273C}h2{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;font-size:1.35rem;line-height:130%;color:#19273C}p{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;color:#19273C;font-weight:400;font-size:0.9rem}svg{font-family:"Source Code Pro"}button{position:relative;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;user-select:none;background-image:none;border:1px solid transparent;border-radius:5px;margin:0;box-shadow:none;line-height:40px;cursor:pointer;background-color:#FDFDFD;flex-basis:auto;width:auto;padding-right:30px;padding-left:30px;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:0.9rem;font-weight:400;transition:0.4s cubic-bezier(0.075, 0.82, 0.165, 1);min-width:140px}button:focus{outline:none}button+button{margin-left:0.8em}button h1{display:inline;margin-right:.5em}button[disabled],button[disabled]:hover{opacity:.65;cursor:not-allowed}button.primary{background-color:#3F55AF;border-color:#3F55AF;color:#FDFDFD}button.primary:hover{background-color:#2D3D7D}button.primary.active,button.primary:active,button.primary:focus{background-color:#2D3D7D}button.primary[disabled],button.primary[disabled]:hover{background-color:#B8C1C1;border-color:#B8C1C1}button.secondary{background-color:#FDFDFD;color:#3F55AF;border-color:#E3ECEC}button.secondary:hover{background-color:#5068C2;border-color:#5068C2;color:#FDFDFD}button.secondary.active,button.secondary:active,button.secondary:focus{background-color:#5068C2;border-color:#5068C2;color:#FDFDFD}button.secondary[disabled],button.secondary[disabled]:hover{border-color:#B8C1C1;color:#B8C1C1}button.expand{background-color:#FDFDFD;border:1px solid #F1F3F7;border-radius:25px;box-shadow:0 0 20px rgba(0,0,0,0.1);padding:0;font-size:0.75em;line-height:25px;color:#8C9696}button.action{border:none;border-bottom:2px solid transparent;background-color:transparent;color:#19273C;min-width:0;padding:0;border-radius:0px}button.action:hover{color:#3F55AF;background-color:transparent;border-bottom:2px solid #3F55AF}button.action:hover svg.ibm-icon{fill:#3F55AF}button.action:focus,button.action.active,button.action:active{color:#3F55AF;background-color:transparent;border-bottom:2px solid #3F55AF}button.action:focus svg.ibm-icon,button.action.active svg.ibm-icon,button.action:active svg.ibm-icon{fill:#3F55AF}button.action[disabled],button.action[disabled]:hover{background-color:transparent;color:#F1F3F7;border-bottom:2px solid #F1F3F7}button.action[disabled] svg.ibm-icon,button.action[disabled]:hover svg.ibm-icon{fill:#F1F3F7}button.icon{background-color:transparent;min-width:0;padding:0}button.icon:hover{color:#3F55AF;background-color:transparent}button.icon:hover svg.ibm-icon{fill:#3F55AF}button.basic{background-color:#FDFDFD;border:1px solid #F1F3F7;border-radius:0.25em}.overlay{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAGUlEQVQImWPwS9olhQsz+CXtkmLABQabJABXUBO1fQxIrwAAAABJRU5ErkJggg==) repeat,linear-gradient(137deg, #3F55AF 0%, #5376D4 40%, #5477D5 42%, #597FDE 52%, #B4E4FF 100%);overflow:hidden;top:0;left:0;bottom:0;right:0;z-index:-100}.hero_container{width:70vw;margin:auto;margin-top:17vh;display:flex;position:relative;flex-direction:row;z-index:10;box-sizing:border-box}.hero_container .leftcontent{flex-basis:40%;display:flex;flex-direction:column;padding-right:2rem;padding-bottom:8rem}.hero_container .hero_illustration{flex-basis:55%;padding-bottom:5rem;box-sizing:border-box}.hero_container .hero_illustration img{max-height:30rem}.hero_container button{box-shadow:0 1px 7px 0 rgba(0,0,0,0.2)}.hero_container h1{color:#FDFDFD;line-height:50px}.hero_container .social-container{display:flex}.hero_container .social-container ul{display:flex;flex-direction:row;margin:0;padding:0;height:100%}.hero_container .social-container li{margin-right:1.5rem;flex-direction:column;justify-content:center;height:100%;display:flex}.hero_container .social-container .icon2{font-size:1.2rem;height:100%;width:100%;transition:0.4s cubic-bezier(0.075, 0.82, 0.165, 1);color:#FDFDFD}.hero_container .social-container .icon2:hover{color:#2D3D7D}.buttoncontainer{margin-top:3.5rem;margin-bottom:1rem;display:flex}.readmore{margin-bottom:2rem;color:#FDFDFD}.readmore a{color:#FDFDFD;text-decoration:underline}.homepage-callout{display:flex;background-color:#FFD640;box-shadow:2px 2px 10px 0 rgba(0,0,0,0.2)}.homepage-callout .callout-copy{margin:auto;margin-top:1rem;margin-bottom:0.5rem}.homepage-callout .callout-copy a{text-decoration:none;border-bottom:1px solid #19273C;color:#19273C;transition:all 0.2s ease-in-out}.homepage-callout .callout-copy a:hover{border-bottom:1px solid #19273C;padding-bottom:3px}.navbar{display:flex;flex-basis:auto;width:100vw;height:4rem;margin:auto;background-color:none;color:#19273C}.navbar-fixed-top{border:none}.indexnav{display:flex;flex-direction:row;align-items:center;width:100%;height:3rem;z-index:11;padding-top:0px;position:absolute}.navcontent{font-size:0.9rem;display:flex;width:70vw;margin:auto;padding-top:2rem}.navlogo{flex-grow:1;flex-basis:100%;flex-shrink:1}.navlogo .brand{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;color:#FDFDFD;font-weight:400;font-size:0.9rem;cursor:pointer;text-decoration:none}.navlogo .brand a{text-decoration:none}.menuitems{display:flex;justify-content:flex-end;flex-basis:auto;flex-shrink:1;height:100%}.menuitems ul{display:flex;flex-direction:row;margin:0;padding:0;height:100%}.menuitems ul li{margin-left:1rem;margin-right:1rem;flex-direction:column;justify-content:center;height:100%;display:flex}.menuitems ul li a{color:#FDFDFD;text-decoration:none}.menuitems ul li a:hover{border-bottom:1px solid #FDFDFD;padding-bottom:3px}.footer-bg{display:flex;background-color:#3F55AF;width:100vw;height:auto}.footer-container{display:flex;width:70vw;margin:auto;padding-top:3rem;margin-bottom:4rem}.footer-container .footer-left{display:flex;flex-grow:1;flex-shrink:1}.footer-container .footer-left p{color:#FDFDFD}.footer-container .footer-left .icon2{font-size:20px;height:100%;width:100%;transition:0.4s cubic-bezier(0.075, 0.82, 0.165, 1);color:#19273C}.footer-container .footer-left .icon2:hover{color:#3F55AF}.footer-container .footer-right{display:flex;justify-content:flex-end;flex-basis:auto;flex-shrink:1;height:100%}.footer-container .footer-right ul{display:flex;flex-grow:2;flex-direction:row;margin:0;padding:0;height:100%}.footer-container .footer-right .license{opacity:0.6}.footer-container .footer-right li{margin-left:2rem;flex-direction:column;justify-content:center;height:100%;display:flex}.footer-container .footer-right li a{color:#FDFDFD;text-decoration:none;font-size:0.9rem}.footer-container .footer-right li a:hover{border-bottom:1px solid #FDFDFD}.footer-container .footer-right li p{color:#FDFDFD}.trio{margin-top:9rem;margin-bottom:7rem;margin-left:auto;margin-right:auto;width:70vw;display:flex;flex-direction:row;justify-content:space-around;flex-wrap:wrap}.trio p{text-align:center}.trio h2{text-align:center;padding-bottom:1rem}.trio .trio-left{justify-content:center;flex-grow:0;flex-basis:20%;flex-shrink:1;flex-wrap:wrap}.trio .trio-left img{display:flex;height:60px;width:auto;margin:auto}.trio .trio-center{justify-content:center;flex-grow:0;flex-basis:20%;flex-shrink:1;flex-wrap:wrap}.trio .trio-center img{display:flex;height:60px;width:auto;margin:auto}.trio .trio-right{justify-content:center;flex-grow:0;flex-basis:20%;flex-shrink:1;flex-wrap:wrap}.trio .trio-right img{display:flex;height:60px;width:auto;margin:auto}.contact-bg{background-color:#F1F3F7;position:relative;padding-bottom:4rem;padding-top:4rem;height:auto}.contact-bg .contact-us{display:flex;flex-direction:row;justify-content:center;margin-left:auto;margin-right:auto;width:70vw}.contact-bg .contact-us .contact-illustration{flex-basis:40%;flex-grow:0}.contact-bg .contact-us .contact-illustration img{display:flex;height:17rem;width:auto}.contact-bg .contact-us .contact-content{flex-basis:50%}.contact-bg .contact-us .contact-content .social-icons{display:flex;flex-wrap:wrap;flex-direction:row}.contact-bg .contact-us .contact-content .social-icons .icon3{margin-top:0.5rem;margin-right:1rem;color:#3F55AF;font-size:1.5rem;width:auto}.contact-bg .contact-us .contact-content a{color:#19273C}.contact-bg .contact-us .contact-content a:hover{text-decoration:none}.contact-bg .contact-us .contact-content .community{color:#3F55AF;padding-bottom:3px}.contact-bg .contact-us .contact-content .community:hover{color:#2D3D7D;padding-bottom:3px;text-decoration:none}.contact-bg .contact-us .contact-content .social-icons>div{display:flex;flex-basis:calc(40% - 30px);justify-content:center;flex-direction:column;margin-right:2rem}.contact-bg .contact-us .contact-content .social-icons>div>div{display:flex;flex-direction:row}.contact-bg .contact-us .contact-content button{margin-top:1rem;margin-bottom:1rem}.contact-bg .contact-us .contact-content p{margin-top:1rem}@media only screen and (max-width: 1200px){.hero_container{flex-direction:column-reverse;-webkit-flex-direction:row}.hero_container .hero_illustration{padding-bottom:2rem}.contact-bg .contact-us .contact-content .social-icons>div{flex-basis:calc(50% - 30px);flex-direction:column}.contact-bg .contact-us .contact-illustration{margin-right:2rem}}@media only screen and (max-width: 900px){.hero_container{-webkit-flex-direction:row}.homepage-callout .callout-copy{padding-left:2rem;padding-right:2rem}.trio{flex-direction:column;max-width:30rem;margin-top:4rem;margin-bottom:4rem}.trio .trio-left{margin-bottom:3rem}.trio .trio-center{margin-bottom:3rem}.trio .trio-right{margin-bottom:3rem}.contact-bg .contact-us{flex-direction:column}.contact-bg .contact-us .contact-illustration{padding-bottom:2rem}}@media only screen and (max-width: 750px){.hero_container{-webkit-flex-direction:row}.contact-bg .contact-us .contact-content .social-icons>div{flex-basis:calc(50% - 30px);flex-direction:column}.contact-bg .contact-us .contact-content .social-icons>div>div{flex-direction:row}}@media only screen and (max-width: 500px){.indexnav{overflow:hidden;height:auto}.indexnav .navcontent{flex-direction:column;-webkit-flex-direction:row}.indexnav .navcontent .navlogo{margin-bottom:0.5rem}.indexnav .navcontent .menuitems{justify-content:flex-start}.indexnav .navcontent .menuitems ul{margin-top:0.5rem}.indexnav .navcontent .menuitems ul li{margin-left:0;margin-right:2rem}.hero_container{-webkit-flex-direction:row}.trio{-webkit-flex-direction:row}.contact-bg .contact-us{-webkit-flex-direction:row}.contact-bg .contact-us .contact-content .social-icons{flex-direction:column;-webkit-flex-direction:row}.contact-bg .contact-us .contact-content .social-icons>div{flex-basis:calc(50% - 30px);flex-direction:column;-webkit-flex-direction:row}.contact-bg .contact-us .contact-content .social-icons>div>div{flex-direction:row;-webkit-flex-direction:row}.contact-bg .contact-us .contact-illustration img{height:auto}.hero_container .leftcontent .textcontent .buttoncontainer{display:flex}.footer-bg .footer-container{flex-wrap:wrap;flex-direction:column;-webkit-flex-direction:row}.footer-bg .footer-container .footer-right ul{flex-direction:column;-webkit-flex-direction:row}.footer-bg .footer-container .footer-right li{margin-left:0}.footer-bg .footer-container .footer-right li a{padding-top:0.6rem}.footer-bg .footer-container .footer-right li a:hover{border-bottom:none}} diff --git a/packages/composer-website/jekylldocs/assets/css/new-style.scss b/packages/composer-website/jekylldocs/assets/css/new-style.scss index 1b8b42f683..ad183f90c5 100644 --- a/packages/composer-website/jekylldocs/assets/css/new-style.scss +++ b/packages/composer-website/jekylldocs/assets/css/new-style.scss @@ -459,7 +459,6 @@ li { .footer-left { display: flex; - flex-grow: 1; flex-shrink: 1; @@ -480,7 +479,6 @@ li { .footer-right { display: flex; - justify-content: flex-end; flex-basis: auto; flex-shrink: 1; @@ -493,13 +491,15 @@ li { padding: 0; height: 100%; } + .license { + opacity: 0.6; + } li { margin-left: 2rem; flex-direction: column; justify-content: center; height: 100%; display: flex; - a { color: $white; text-decoration: none; @@ -508,12 +508,10 @@ li { border-bottom: 1px solid $white; } } - p { - opacity: 0.6; + // opacity: 0.6; color: $white; } - } } @@ -644,11 +642,9 @@ li { } .community { color: $first-highlight; - border-bottom: 1px solid $first-highlight; padding-bottom: 3px; &:hover { color: $sixth-highlight; - border-bottom: 1px solid $sixth-highlight; padding-bottom: 3px; text-decoration: none; } From ba75648f93e9eb5b6d800b197afc6d1578778969 Mon Sep 17 00:00:00 2001 From: Liam Grace <14gracel@users.noreply.github.com> Date: Thu, 6 Jul 2017 18:01:21 +0100 Subject: [PATCH 05/88] Added footer to playground (#1478) * Fix alignment of transaction in view-transaction * Added footer to playground * Fixed Height issue and page loading issues --- .../composer-playground/src/app/app.module.ts | 1 + .../connection-profile.component.html | 53 ++++++++------- .../connection-profile.component.scss | 3 +- .../connection-profile.component.spec.ts | 13 +++- .../connection-profile.module.ts | 3 +- .../src/app/editor/editor.component.html | 5 +- .../src/app/editor/editor.component.scss | 2 + .../src/app/editor/editor.component.spec.ts | 12 +++- .../src/app/editor/editor.module.ts | 3 +- .../src/app/footer/footer.component.html | 24 +++++++ .../src/app/footer/footer.component.scss | 42 ++++++++++++ .../src/app/footer/footer.component.spec.ts | 67 +++++++++++++++++++ .../src/app/footer/footer.component.ts | 32 +++++++++ .../src/app/footer/footer.module.ts | 17 +++++ .../src/app/footer/index.ts | 1 + .../src/app/identity/identity.component.html | 60 +++++++++-------- .../src/app/identity/identity.component.scss | 2 + .../app/identity/identity.component.spec.ts | 12 +++- .../src/app/identity/identity.module.ts | 3 +- .../src/app/services/about.service.ts | 52 +++++++------- .../src/app/test/test.component.html | 6 +- .../src/app/test/test.component.scss | 2 + .../src/app/test/test.component.spec.ts | 15 ++++- .../src/app/test/test.module.ts | 3 +- .../view-transaction.component.html | 15 ++--- .../view-transaction.component.scss | 39 ++++++----- .../view-transaction.component.ts | 2 +- .../src/assets/styles/base/_codemirror.scss | 1 - 28 files changed, 372 insertions(+), 118 deletions(-) create mode 100644 packages/composer-playground/src/app/footer/footer.component.html create mode 100644 packages/composer-playground/src/app/footer/footer.component.scss create mode 100644 packages/composer-playground/src/app/footer/footer.component.spec.ts create mode 100644 packages/composer-playground/src/app/footer/footer.component.ts create mode 100644 packages/composer-playground/src/app/footer/footer.module.ts create mode 100644 packages/composer-playground/src/app/footer/index.ts diff --git a/packages/composer-playground/src/app/app.module.ts b/packages/composer-playground/src/app/app.module.ts index b71815ab1d..6f39cbf215 100644 --- a/packages/composer-playground/src/app/app.module.ts +++ b/packages/composer-playground/src/app/app.module.ts @@ -22,6 +22,7 @@ import { BasicModalsModule } from './basic-modals/basic-models.module'; import { WelcomeComponent } from './welcome'; import { NoContentComponent } from './no-content'; import { VersionCheckComponent } from './version-check'; +import { FooterComponent } from './footer'; import { ServicesModule } from './services/services.module'; let actionBasedIcons = require.context('../assets/svg/action-based', false, /.*\.svg$/); diff --git a/packages/composer-playground/src/app/connection-profile/connection-profile.component.html b/packages/composer-playground/src/app/connection-profile/connection-profile.component.html index e80fdacfc5..85b0bedd9f 100644 --- a/packages/composer-playground/src/app/connection-profile/connection-profile.component.html +++ b/packages/composer-playground/src/app/connection-profile/connection-profile.component.html @@ -25,31 +25,34 @@
-
-
-
- -
-
-

Connection Profiles are not available in Web Playground

-

- It is not possible to add Connection Profiles in the Web Playground version of Hyperledger Composer. - If you like what you see, you can install a a local version of Hyperledger Composer. - The local version offers the same UI, or a CLI experience, and can be used to deploy Business Network Definitions to running Hyperledger Fabric instances. -

-

And of course it's still 100% open source

-

Learn more in the docs

-
-
- -
-
+
+
+
+
+ +
+
+

Connection Profiles are not available in Web Playground

+

+ It is not possible to add Connection Profiles in the Web Playground version of Hyperledger Composer. + If you like what you see, you can install a a local version of Hyperledger Composer. + The local version offers the same UI, or a CLI experience, and can be used to deploy Business Network Definitions to running Hyperledger Fabric instances. +

+

And of course it's still 100% open source

+

Learn more in the docs

+
+
+ +
+
-
+
-
- -
+
+ +
+
+
diff --git a/packages/composer-playground/src/app/connection-profile/connection-profile.component.scss b/packages/composer-playground/src/app/connection-profile/connection-profile.component.scss index b8c80462ac..372ec88c0a 100644 --- a/packages/composer-playground/src/app/connection-profile/connection-profile.component.scss +++ b/packages/composer-playground/src/app/connection-profile/connection-profile.component.scss @@ -36,7 +36,8 @@ connection-profile { overflow-y: auto; background-color: $fourth-highlight; padding: $space-large $space-large $space-medium $space-large; - overflow-y:auto; + display: flex; + flex-direction: column; .playground-profile-warning { background-color: $white; diff --git a/packages/composer-playground/src/app/connection-profile/connection-profile.component.spec.ts b/packages/composer-playground/src/app/connection-profile/connection-profile.component.spec.ts index 1cec4900d1..83bb05350c 100644 --- a/packages/composer-playground/src/app/connection-profile/connection-profile.component.spec.ts +++ b/packages/composer-playground/src/app/connection-profile/connection-profile.component.spec.ts @@ -3,7 +3,7 @@ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { Directive, Input } from '@angular/core'; +import { Directive, Input, Component } from '@angular/core'; import { ConnectionProfileComponent } from './connection-profile.component'; import { ConnectionProfileService } from '../services/connectionprofile.service'; @@ -15,10 +15,17 @@ import { AlertService } from '../basic-modals/alert.service'; let should = chai.should(); +@Component({ + selector: 'app-footer', + template: '' +}) +class MockFooterComponent { + +} + @Directive({ selector: 'connection-profile-data' }) - class MockConnectionProfileDataDirective { @Input() public profileUpdated; @@ -36,7 +43,7 @@ describe('ConnectionProfileComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ConnectionProfileComponent, MockConnectionProfileDataDirective], + declarations: [ConnectionProfileComponent, MockConnectionProfileDataDirective, MockFooterComponent], providers: [ {provide: ConnectionProfileService, useValue: mockConnectionProfileService}, {provide: AlertService, useValue: mockAlertService}, diff --git a/packages/composer-playground/src/app/connection-profile/connection-profile.module.ts b/packages/composer-playground/src/app/connection-profile/connection-profile.module.ts index 5be630d645..b71317fa5f 100644 --- a/packages/composer-playground/src/app/connection-profile/connection-profile.module.ts +++ b/packages/composer-playground/src/app/connection-profile/connection-profile.module.ts @@ -12,9 +12,10 @@ import { DeleteConnectionProfileComponent } from './delete-connection-profile/de import { ViewCertificateComponent } from './view-certificate/view-certificate.component'; import { FileImporterModule } from '../file-importer/file-importer.module'; import { SwitchIdentityComponent } from '../test/switch-identity/switch-identity.component'; +import { FooterModule } from '../footer/footer.module'; @NgModule({ - imports: [CommonModule, FormsModule, NgbModule, ReactiveFormsModule, FileImporterModule, ConnectionProfileRoutingModule], + imports: [CommonModule, FormsModule, NgbModule, ReactiveFormsModule, FileImporterModule, ConnectionProfileRoutingModule, FooterModule], entryComponents: [AddCertificateComponent, AddConnectionProfileComponent, DeleteConnectionProfileComponent, ViewCertificateComponent, SwitchIdentityComponent], declarations: [ConnectionProfileComponent, ConnectionProfileDataComponent, AddCertificateComponent, AddConnectionProfileComponent, DeleteConnectionProfileComponent, ViewCertificateComponent, SwitchIdentityComponent], providers: [] diff --git a/packages/composer-playground/src/app/editor/editor.component.html b/packages/composer-playground/src/app/editor/editor.component.html index 1175277ec1..04d639f5ca 100644 --- a/packages/composer-playground/src/app/editor/editor.component.html +++ b/packages/composer-playground/src/app/editor/editor.component.html @@ -112,5 +112,8 @@

+
+ + + diff --git a/packages/composer-playground/src/app/editor/editor.component.scss b/packages/composer-playground/src/app/editor/editor.component.scss index 98ca30dda7..7ea3a9e5d2 100644 --- a/packages/composer-playground/src/app/editor/editor.component.scss +++ b/packages/composer-playground/src/app/editor/editor.component.scss @@ -43,6 +43,8 @@ app-editor { } .main-view { + display: flex; + flex-direction: column; .business-network-details { display: flex; height:50px; diff --git a/packages/composer-playground/src/app/editor/editor.component.spec.ts b/packages/composer-playground/src/app/editor/editor.component.spec.ts index cea95b2bc6..2c52c6fa54 100644 --- a/packages/composer-playground/src/app/editor/editor.component.spec.ts +++ b/packages/composer-playground/src/app/editor/editor.component.spec.ts @@ -3,7 +3,7 @@ /* 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 { Directive, Input, Component } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; @@ -42,6 +42,14 @@ class MockEditorFileDirective { class MockPerfectScrollBarDirective { } +@Component({ + selector: 'app-footer', + template: '' +}) +class MockFooterComponent { + +} + describe('EditorComponent', () => { let component: EditorComponent; let fixture: ComponentFixture; @@ -73,7 +81,7 @@ describe('EditorComponent', () => { TestBed.configureTestingModule({ imports: [FormsModule], - declarations: [EditorComponent, MockEditorFileDirective, MockPerfectScrollBarDirective, ScrollToElementDirective], + declarations: [EditorComponent, MockEditorFileDirective, MockPerfectScrollBarDirective, ScrollToElementDirective, MockFooterComponent], providers: [ {provide: AdminService, useValue: mockAdminService}, {provide: ClientService, useValue: mockClientService}, diff --git a/packages/composer-playground/src/app/editor/editor.module.ts b/packages/composer-playground/src/app/editor/editor.module.ts index 591d3ede9e..0c1a499db4 100644 --- a/packages/composer-playground/src/app/editor/editor.module.ts +++ b/packages/composer-playground/src/app/editor/editor.module.ts @@ -13,9 +13,10 @@ import { AddFileComponent } from './add-file/add-file.component'; import { ImportComponent } from './import/import.component'; import { FileImporterModule } from '../file-importer/file-importer.module'; import { DirectivesModule } from '../directives/directives.module'; +import { FooterModule } from '../footer/footer.module'; @NgModule({ - imports: [CommonModule, FormsModule, NgbModule, PerfectScrollbarModule, CodemirrorModule, DirectivesModule, FileImporterModule, EditorRoutingModule], + imports: [CommonModule, FormsModule, NgbModule, PerfectScrollbarModule, CodemirrorModule, DirectivesModule, FileImporterModule, EditorRoutingModule, FooterModule], entryComponents: [AddFileComponent, ImportComponent], declarations: [EditorComponent, EditorFileComponent, AddFileComponent, ImportComponent], providers: [EditorService] diff --git a/packages/composer-playground/src/app/footer/footer.component.html b/packages/composer-playground/src/app/footer/footer.component.html new file mode 100644 index 0000000000..b92341cae3 --- /dev/null +++ b/packages/composer-playground/src/app/footer/footer.component.html @@ -0,0 +1,24 @@ + diff --git a/packages/composer-playground/src/app/footer/footer.component.scss b/packages/composer-playground/src/app/footer/footer.component.scss new file mode 100644 index 0000000000..00140b9c70 --- /dev/null +++ b/packages/composer-playground/src/app/footer/footer.component.scss @@ -0,0 +1,42 @@ +@import '../../assets/styles/base/_colors.scss'; +@import '../../assets/styles/base/_variables.scss'; + +app-footer { + position: relative; + display: flex; + width: 100%; + bottom: -($space-large); + + .footer-main{ + position: relative; + bottom: 0; + flex: 1; + padding: 22px 10px 22px 10px; + border-top: solid 1px #D0DADA; + color: $secondary-text ; + font-size: 12px; + display: flex; + height: 62px; + + .footer-left, .footer-right{ + flex:1; + display:flex; + + .footer-item{ + flex-shrink: 1; + padding-right:30px; + a { + color: $secondary-text; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + } + .footer-right { + justify-content: flex-end; + } + } +} diff --git a/packages/composer-playground/src/app/footer/footer.component.spec.ts b/packages/composer-playground/src/app/footer/footer.component.spec.ts new file mode 100644 index 0000000000..abc386cd20 --- /dev/null +++ b/packages/composer-playground/src/app/footer/footer.component.spec.ts @@ -0,0 +1,67 @@ +import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; +import { BehaviorSubject, Subject } from 'rxjs/Rx'; + +import { FooterComponent } from './footer.component'; +import { AboutService } from '../services/about.service'; +import { AlertService } from '../basic-modals/alert.service'; + +import * as sinon from 'sinon'; +import * as sap from 'sinon-as-promised'; + +import * as chai from 'chai'; + +class MockAlertService { + public errorStatus$: Subject = new BehaviorSubject(null); + public busyStatus$: Subject = new BehaviorSubject(null); +} + +describe('FooterComponent', () => { + + let component: FooterComponent; + let fixture: ComponentFixture; + let de: DebugElement; + let el: HTMLElement; + let mockAboutService; + + beforeEach(() => { + + mockAboutService = sinon.createStubInstance(AboutService); + + TestBed.configureTestingModule({ + declarations: [FooterComponent], + providers: [ + {provide: AboutService, useValue: mockAboutService}, + {provide: AlertService, useClass: MockAlertService} + ] + }); + + fixture = TestBed.createComponent(FooterComponent); + component = fixture.componentInstance; + + // query for the title

by CSS element selector + de = fixture.debugElement.query(By.css('a')); + el = de.nativeElement; + }); + + describe('ngOninit', () => { + it('should call getVersions from the AboutService', fakeAsync(() => { + mockAboutService.getVersions.resolves({playground: {version: 'v1'}}); + component.ngOnInit(); + tick(); + component['playgroundVersion'].should.equal('v1'); + })); + + it('should send the error to the AlertService', fakeAsync(() => { + mockAboutService.getVersions.returns(Promise.reject('detailed reject message')); + component.ngOnInit(); + tick(); + component['alertService'].errorStatus$.subscribe( + (message) => { + message.should.equal('detailed reject message'); + } + ); + })); + }); +}); diff --git a/packages/composer-playground/src/app/footer/footer.component.ts b/packages/composer-playground/src/app/footer/footer.component.ts new file mode 100644 index 0000000000..ff1f07deb7 --- /dev/null +++ b/packages/composer-playground/src/app/footer/footer.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { AboutService } from './../services/about.service'; +import { AlertService } from './../basic-modals/alert.service'; + +@Component({ + selector: 'app-footer', + templateUrl: './footer.component.html', + styleUrls: [ + './footer.component.scss'.toString() + ] +}) + +export class FooterComponent implements OnInit { + + private playgroundVersion: string = ''; + + constructor(private aboutService: AboutService, + private alertService: AlertService) { + } + + ngOnInit() { + return this.aboutService.getVersions() + .then((versions) => { + this.playgroundVersion = versions.playground.version; + }) + .catch((err) => { + this.alertService.errorStatus$.next(err); + }); + } +} diff --git a/packages/composer-playground/src/app/footer/footer.module.ts b/packages/composer-playground/src/app/footer/footer.module.ts new file mode 100644 index 0000000000..fc7cc14cd1 --- /dev/null +++ b/packages/composer-playground/src/app/footer/footer.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + +import { FooterComponent } from './footer.component'; + +@NgModule({ + imports: [CommonModule], + entryComponents: [], + declarations: [FooterComponent], + providers: [], + exports: [FooterComponent] +}) + +export class FooterModule { +} diff --git a/packages/composer-playground/src/app/footer/index.ts b/packages/composer-playground/src/app/footer/index.ts new file mode 100644 index 0000000000..a50d57382e --- /dev/null +++ b/packages/composer-playground/src/app/footer/index.ts @@ -0,0 +1 @@ +export * from './footer.component'; diff --git a/packages/composer-playground/src/app/identity/identity.component.html b/packages/composer-playground/src/app/identity/identity.component.html index 8d5157c841..e8672d73ac 100644 --- a/packages/composer-playground/src/app/identity/identity.component.html +++ b/packages/composer-playground/src/app/identity/identity.component.html @@ -1,36 +1,40 @@
-
-

My Wallet

+
+
+

My Wallet

- + - -
- -
-
ID Name
-
Status
-
-
-
- {{id}} +
-
- In use - -
-
- In my wallet + +
+
ID Name
+
Status
-
- - +
+
+ {{id}} +
+
+ In use + +
+
+ In my wallet +
+
+ + +
+ +
diff --git a/packages/composer-playground/src/app/identity/identity.component.scss b/packages/composer-playground/src/app/identity/identity.component.scss index 96f9c6fc44..f6a106587d 100644 --- a/packages/composer-playground/src/app/identity/identity.component.scss +++ b/packages/composer-playground/src/app/identity/identity.component.scss @@ -7,6 +7,8 @@ identity { .main-view { overflow-y: auto; + display: flex; + flex-direction: column; .identity-title { display: flex; diff --git a/packages/composer-playground/src/app/identity/identity.component.spec.ts b/packages/composer-playground/src/app/identity/identity.component.spec.ts index 29e1ef71e9..f73091d6a1 100644 --- a/packages/composer-playground/src/app/identity/identity.component.spec.ts +++ b/packages/composer-playground/src/app/identity/identity.component.spec.ts @@ -2,6 +2,7 @@ /* tslint:disable:no-unused-expression */ /* tslint:disable:no-var-requires */ /* tslint:disable:max-classes-per-file */ +import { Component } from '@angular/core'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; @@ -20,6 +21,14 @@ import { WalletService } from '../services/wallet.service'; let should = chai.should(); +@Component({ + selector: 'app-footer', + template: '' +}) +class MockFooterComponent { + +} + describe(`IdentityComponent`, () => { let component: IdentityComponent; @@ -50,7 +59,8 @@ describe(`IdentityComponent`, () => { TestBed.configureTestingModule({ imports: [FormsModule], declarations: [ - IdentityComponent + IdentityComponent, + MockFooterComponent ], providers: [ {provide: NgbModal, useValue: mockModal}, diff --git a/packages/composer-playground/src/app/identity/identity.module.ts b/packages/composer-playground/src/app/identity/identity.module.ts index 5f0c082667..aceaa61848 100644 --- a/packages/composer-playground/src/app/identity/identity.module.ts +++ b/packages/composer-playground/src/app/identity/identity.module.ts @@ -11,9 +11,10 @@ import { AddIdentityComponent } from './add-identity/add-identity.component'; import { IdentityIssuedComponent } from './identity-issued/identity-issued.component'; import { IssueIdentityComponent } from './issue-identity/issue-identity.component'; import { IdentityComponent } from './identity.component'; +import { FooterModule } from '../footer/footer.module'; @NgModule({ - imports: [CommonModule, FormsModule, NgbModule, FileImporterModule, IdentityRoutingModule], + imports: [CommonModule, FormsModule, NgbModule, FileImporterModule, IdentityRoutingModule, FooterModule], entryComponents: [AddIdentityComponent, IdentityIssuedComponent, IssueIdentityComponent], declarations: [AddIdentityComponent, IdentityIssuedComponent, IssueIdentityComponent, IdentityComponent], providers: [] diff --git a/packages/composer-playground/src/app/services/about.service.ts b/packages/composer-playground/src/app/services/about.service.ts index 3870f8c30b..082e189cad 100644 --- a/packages/composer-playground/src/app/services/about.service.ts +++ b/packages/composer-playground/src/app/services/about.service.ts @@ -1,36 +1,40 @@ import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; +import { BehaviorSubject, Observable } from 'rxjs/Rx'; @Injectable() export class AboutService { + versions = null; constructor(private http: Http) { } getVersions(): Promise { - return this.getModules() - .then((modules) => { - return { - playground: { - name: 'playground', - version: modules.version - }, - common: { - name: 'composer-common', - version: modules.dependencies['composer-common'].version - }, - client: { - name: 'composer-client', - version: modules.dependencies['composer-client'].version, - }, - admin: { - name: 'composer-admin', - version: modules.dependencies['composer-admin'].version - } - }; - }) - .catch((e) => { - console.log(e); - }); + if (!this.versions) { + return this.getModules() + .then((modules) => { + this.versions = { + playground: { + name: 'playground', + version: modules.version + }, + common: { + name: 'composer-common', + version: modules.dependencies['composer-common'].version + }, + client: { + name: 'composer-client', + version: modules.dependencies['composer-client'].version, + }, + admin: { + name: 'composer-admin', + version: modules.dependencies['composer-admin'].version + } + }; + return this.versions; + }); + } else { + return Promise.resolve(this.versions); + } } private getModules(): Promise { diff --git a/packages/composer-playground/src/app/test/test.component.html b/packages/composer-playground/src/app/test/test.component.html index 0c9124d868..e980ed5230 100644 --- a/packages/composer-playground/src/app/test/test.component.html +++ b/packages/composer-playground/src/app/test/test.component.html @@ -45,5 +45,9 @@

All Transactions

- +
+
+ +
+ diff --git a/packages/composer-playground/src/app/test/test.component.scss b/packages/composer-playground/src/app/test/test.component.scss index 8e33402c5d..a0ba1a2993 100644 --- a/packages/composer-playground/src/app/test/test.component.scss +++ b/packages/composer-playground/src/app/test/test.component.scss @@ -43,5 +43,7 @@ app-test { .main-view { overflow-y: auto; + display: flex; + flex-direction: column; } } diff --git a/packages/composer-playground/src/app/test/test.component.spec.ts b/packages/composer-playground/src/app/test/test.component.spec.ts index aeccb6e3e3..12c681b997 100644 --- a/packages/composer-playground/src/app/test/test.component.spec.ts +++ b/packages/composer-playground/src/app/test/test.component.spec.ts @@ -3,8 +3,9 @@ /* 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 { Directive, Input, Component } from '@angular/core'; import { TestComponent } from './test.component'; +import { FooterComponent } from '../footer/footer.component'; import { ClientService } from '../services/client.service'; import { InitializationService } from '../services/initialization.service'; import { TransactionService } from '../services/transaction.service'; @@ -19,6 +20,14 @@ import { BusinessNetworkConnection } from 'composer-client'; let should = chai.should(); +@Component({ + selector: 'app-footer', + template: '' +}) +class MockFooterComponent { + +} + @Directive({ selector: 'registry' }) @@ -59,14 +68,14 @@ describe('TestComponent', () => { mockClientService.getBusinessNetworkConnection.returns(mockBusinessNetworkConnection); TestBed.configureTestingModule({ - declarations: [TestComponent, MockRegistryDirective], + declarations: [TestComponent, MockRegistryDirective, MockFooterComponent], providers: [ {provide: NgbModal, useValue: mockModal}, {provide: InitializationService, useValue: mockInitializationService}, {provide: AlertService, useValue: mockAlertService}, {provide: ClientService, useValue: mockClientService}, {provide: TransactionService, useValue: mockTransactionService} - ] + ], }); fixture = TestBed.createComponent(TestComponent); diff --git a/packages/composer-playground/src/app/test/test.module.ts b/packages/composer-playground/src/app/test/test.module.ts index 2729f80759..c59c7309d0 100644 --- a/packages/composer-playground/src/app/test/test.module.ts +++ b/packages/composer-playground/src/app/test/test.module.ts @@ -11,9 +11,10 @@ import { RegistryComponent } from './registry/registry.component'; import { TransactionComponent } from './transaction/transaction.component'; import { ViewTransactionComponent } from './view-transaction/view-transaction.component'; import { DirectivesModule } from '../directives/directives.module'; +import { FooterModule } from '../footer/footer.module'; @NgModule({ - imports: [CommonModule, FormsModule, NgbModule, CodemirrorModule, DirectivesModule, TestRoutingModule], + imports: [CommonModule, FormsModule, NgbModule, CodemirrorModule, DirectivesModule, TestRoutingModule, FooterModule], entryComponents: [ResourceComponent, TransactionComponent, ViewTransactionComponent], declarations: [RegistryComponent, ResourceComponent, TransactionComponent, TestComponent, ViewTransactionComponent], providers: [] diff --git a/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.html b/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.html index 313d46df21..cb852927e4 100644 --- a/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.html +++ b/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.html @@ -1,4 +1,4 @@ -
+
-
diff --git a/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.scss b/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.scss index c00b0f360b..63f74eea4c 100644 --- a/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.scss +++ b/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.scss @@ -1,7 +1,21 @@ @import '../../../assets/styles/base/_colors.scss'; @import '../../../assets/styles/base/_variables.scss'; @import '../../../assets/styles/base/_typography.scss'; -transaction-modal { +@import '../transaction/transaction.component'; + +.view-transaction-modal { + + @extend transaction-modal; + + .resource-component { + padding-right: $space-small; + margin-top: $space-medium; + } + + .resource-component.events { + overflow-y: scroll; + } + .modal-sub-header { display: flex; align-items: center; @@ -52,6 +66,10 @@ transaction-modal { width: 100%; background-color: $third-highlight; padding: $space-small $space-medium; + min-height: 0; + max-height: 50px; + min-width: 630px; + max-width: 650px; .resource-title { .title { width: 85%; @@ -63,30 +81,21 @@ transaction-modal { &::after { content: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIxN3B4IiBoZWlnaHQ9IjE0cHgiIHZpZXdCb3g9IjAgMCAxNyAxNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4gICAgICAgIDx0aXRsZT5Db2xsYXBzZS1FeHBhbmQ8L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4gICAgICAgIDxnIGlkPSJGYWJyaWMtQ29tcG9zZXItUGxheWdyb3VuZC1EZWZpbmUtMi1Db3B5LTEyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEwNzYuMDAwMDAwLCAtNDg2LjAwMDAwMCkiPiAgICAgICAgICAgIDxnIGlkPSJHcm91cC0zLUNvcHkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMwMC4wMDAwMDAsIDQ2NC4wMDAwMDApIj4gICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLUNvcHktMiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzg0LjUwMDAwMCwgMjkuMDAwMDAwKSByb3RhdGUoOTAuMDAwMDAwKSB0cmFuc2xhdGUoLTc4NC41MDAwMDAsIC0yOS4wMDAwMDApIHRyYW5zbGF0ZSg3NzcuNTAwMDAwLCAyMS41MDAwMDApIj4gICAgICAgICAgICAgICAgICAgIDxnIGlkPSJDb2xsYXBzZS1FeHBhbmQiPiAgICAgICAgICAgICAgICAgICAgICAgIDxwb2x5Z29uIGlkPSJGaWxsLTMiIGZpbGw9IiMxOTI3M0MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMuNDYxNTM4LCA3LjUzODQ2Mikgc2NhbGUoLTEsIDEpIHRyYW5zbGF0ZSgtMy40NjE1MzgsIC03LjUzODQ2MikgIiBwb2ludHM9IjUuNTM4NDYxNTQgMiAwIDcuNTM4NDYxNTQgNS41Mzg0NjE1NCAxMy4wNzY5MjMxIDYuOTIzMDc2OTIgMTEuNjkyMzA3NyAyLjc2OTIzMDc3IDcuNTM4NDYxNTQgNi45MjMwNzY5MiAzLjM4NDYxNTM4Ij48L3BvbHlnb24+ICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTEzLDAgTDEzLDE1IiBpZD0iTGluZSIgc3Ryb2tlPSIjMTkyNzNDIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJzcXVhcmUiPjwvcGF0aD4gICAgICAgICAgICAgICAgICAgIDwvZz4gICAgICAgICAgICAgICAgPC9nPiAgICAgICAgICAgIDwvZz4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==); cursor: pointer; - position: fixed; - right: 8%; + position: absolute; + right: 6%; + bottom: 85%; width: 0; height: 0; } } .resource-content { - position: relative; - width: 100%; - background-color: $third-highlight; - padding: $space-small $space-medium; - text-align: left; - border-top:1px solid #e3ecec; .CodeMirror { height: 200px; min-height: 0; max-width: none; - .CodeMirror-scroll { - max-width: 0; - } } - .ps.ps--active-x>.ps__scrollbar-x-rail, - .ps.ps--active-y>.ps__scrollbar-y-rail{ - opacity: 0.6; + .CodeMirror, .CodeMirror-scroll { + min-height: 0; } } } diff --git a/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.ts b/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.ts index b4acc4be9f..fb1836aac9 100644 --- a/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.ts +++ b/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.ts @@ -35,7 +35,7 @@ export class ViewTransactionComponent implements OnInit, OnDestroy { private codeConfig = { lineNumbers: true, lineWrapping: true, - readOnly: false, + readOnly: true, mode: 'javascript', autofocus: true, extraKeys: { diff --git a/packages/composer-playground/src/assets/styles/base/_codemirror.scss b/packages/composer-playground/src/assets/styles/base/_codemirror.scss index 8f4f4ca2ae..b16ce05964 100644 --- a/packages/composer-playground/src/assets/styles/base/_codemirror.scss +++ b/packages/composer-playground/src/assets/styles/base/_codemirror.scss @@ -74,5 +74,4 @@ flex:1; color: #8C9696; padding-left:1rem; - border-bottom: solid 1px #D0DADA; } From 8424d76ac56270cc569f57db8974db644bf2d5cb Mon Sep 17 00:00:00 2001 From: James Taylor Date: Thu, 6 Jul 2017 20:07:53 +0100 Subject: [PATCH 06/88] Improve wallet service test coverage (#1506) Contributes to #848 Signed-off-by: James Taylor --- .../src/app/services/wallet.service.spec.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/composer-playground/src/app/services/wallet.service.spec.ts b/packages/composer-playground/src/app/services/wallet.service.spec.ts index 78ab309942..19e31dc8e3 100644 --- a/packages/composer-playground/src/app/services/wallet.service.spec.ts +++ b/packages/composer-playground/src/app/services/wallet.service.spec.ts @@ -6,6 +6,7 @@ import { TestBed, inject, fakeAsync, tick } from '@angular/core/testing'; import { WalletService } from './wallet.service'; import * as sinon from 'sinon'; import { FileWallet } from 'composer-common'; +import { Logger } from 'composer-common'; describe('WalletService', () => { @@ -23,6 +24,37 @@ describe('WalletService', () => { }); }); + describe('getWallet', () => { + beforeEach(() => { + // webpack can't handle dymanically creating a logger + Logger.setFunctionalLogger({ + log: sinon.stub() + }); + }); + + it('should get a wallet', fakeAsync(inject([WalletService], (service: WalletService) => { + service['fileWallets'] = mockFileWallets; + mockFileWallets.has.returns(true); + + service.getWallet('identity1'); + + tick(); + + mockFileWallets.set.should.not.have.been.called; + mockFileWallets.get.should.have.been.calledWith('identity1'); + }))); + + it('should create a new wallet if it doesn\'t already exist', fakeAsync(inject([WalletService], (service: WalletService) => { + service['fileWallets'] = mockFileWallets; + + service.getWallet('secrectIdentity'); + + tick(); + + mockFileWallets.set.should.have.been.calledWith('secrectIdentity'); + }))); + }); + describe('removeFromWallet', () => { it('should remove an identity from the wallet', fakeAsync(inject([WalletService], (service: WalletService) => { let mockGetWallet = sinon.stub(service, 'getWallet').returns(mockFileWallet); From 9a0351f940e5412c5dad3caeb61708f18897fece Mon Sep 17 00:00:00 2001 From: Matthew B White Date: Thu, 6 Jul 2017 21:42:37 +0100 Subject: [PATCH 07/88] Tests updated to get the code coverage; and defects fixed that it found (#1502) --- packages/composer-common/messages/en.json | 2 + .../test/model/relationship.js | 13 +++ .../test/model/validatedconcept.js | 106 ++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 packages/composer-common/test/model/validatedconcept.js diff --git a/packages/composer-common/messages/en.json b/packages/composer-common/messages/en.json index 534800e1e7..7046f0fbff 100644 --- a/packages/composer-common/messages/en.json +++ b/packages/composer-common/messages/en.json @@ -39,6 +39,8 @@ "factory-newinstance-missingidentifier": "Missing identifier for Type {type} in namespace {namespace}", "factory-newinstance-invalididentifier": "Invalid or missing identifier for Type {type} in namespace {namespace}", "factory-newinstance-abstracttype": "Cannot instantiate Abstract Type {type} in namespace {namespace}", + "factory-newrelationship-notregisteredwithmm" : "Cannot create relationship as namespace {namespace} is not known", + "factory-newinstance-typenotdeclaredinns" : "Cannot instantiate Type {type} in namespace {namespace}", "instancegenerator-newinstance-noconcreteclass": "No concrete extending type for {type}", diff --git a/packages/composer-common/test/model/relationship.js b/packages/composer-common/test/model/relationship.js index 8bbaa5ad19..b2144fcf1e 100644 --- a/packages/composer-common/test/model/relationship.js +++ b/packages/composer-common/test/model/relationship.js @@ -124,5 +124,18 @@ describe('Relationship', function () { rel.getType().should.equal('Person'); rel.getIdentifier().should.equal('123'); }); + + it('check invalid name space gets error', function() { + (function () { + Relationship.fromURI(modelManager, '123', 'org.acme.empty', 'Person' ); + }).should.throw(/Cannot create relationship as namespace org.acme.empty is not known/); + }); + + it('check that relationships can be created from a URI', function() { + (function () { + Relationship.fromURI(modelManager, 'resource:org.acme.l1.Unkown#123' ); + }).should.throw(/Cannot instantiate Type Unkown in namespace org.acme.l1/); + }); + }); }); diff --git a/packages/composer-common/test/model/validatedconcept.js b/packages/composer-common/test/model/validatedconcept.js new file mode 100644 index 0000000000..4fb6715100 --- /dev/null +++ b/packages/composer-common/test/model/validatedconcept.js @@ -0,0 +1,106 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const ModelManager = require('../../lib/modelmanager'); +const ValidatedConcept = require('../../lib/model/validatedconcept'); +const ResourceValidator = require('../../lib/serializer/resourcevalidator'); +const sinon = require('sinon'); +const chai = require('chai'); +chai.should(); +chai.use(require('chai-things')); + +describe('Validatedoncept', function () { + + const levelOneModel = `namespace org.acme.l1 + concept Person { + o String name + o String[] arrayName + } + asset Car identified by vin { + o String vin + o Person owner + } + `; + + let modelManager = null; + let mockResourceValidator; + + before(function () { + modelManager = new ModelManager(); + }); + + beforeEach(function () { + modelManager.addModelFile(levelOneModel); + mockResourceValidator = sinon.createStubInstance(ResourceValidator); + mockResourceValidator.visit.returns(null); + + }); + + afterEach(function () { + modelManager.clearModelFiles(); + }); + + describe('#getClassDeclaration', function() { + it('should throw with no ModelFile', function () { + const resource = new ValidatedConcept(modelManager, 'org.acme.l1', 'Person' ,mockResourceValidator); + const stub = sinon.stub(modelManager, 'getModelFile', function(){return null;}); + (function () { + resource.getClassDeclaration(); + }).should.throw(/No model for namespace org.acme.l1 is registered with the ModelManager/); + stub.restore(); + }); + }); + + describe('#setPropertyValue', () => { + it (' should accept valid property - value', function (){ + const resource = new ValidatedConcept(modelManager, 'org.acme.l1', 'Person' ,mockResourceValidator); + resource.setPropertyValue('name','Fred Bloggs'); + }); + it (' should throw error for invalid property name', function (){ + const resource = new ValidatedConcept(modelManager, 'org.acme.l1', 'Person' ,mockResourceValidator); + ( () => { + resource.setPropertyValue('namenamename','Fred Bloggs'); + }).should.throw(/Trying to set field namenamename which is not declared in the model/); + }); + it (' should throw error for array', function (){ + const resource = new ValidatedConcept(modelManager, 'org.acme.l1', 'Person' ,mockResourceValidator); + ( () => { + resource.addArrayValue('name',['Fred','Bloggs']); + }).should.throw(/Trying to add array item name which is not declared as an array in the model/); + }); + it (' correct path for adding an array', function (){ + const resource = new ValidatedConcept(modelManager, 'org.acme.l1', 'Person' ,mockResourceValidator); + resource.addArrayValue('arrayName',['Fred','Bloggs']); + }); + it (' should throw error for invalid property name', function (){ + const resource = new ValidatedConcept(modelManager, 'org.acme.l1', 'Person' ,mockResourceValidator); + (()=>{ + resource.addArrayValue('invalid','Fred'); + }).should.throw(/Trying to set field invalid which is not declared in the model/); + }); + it (' validate', function (){ + const resource = new ValidatedConcept(modelManager, 'org.acme.l1', 'Person' ,mockResourceValidator); + resource.validate(); + }); + it (' add two elements separately to an array property', function (){ + const resource = new ValidatedConcept(modelManager, 'org.acme.l1', 'Person' ,mockResourceValidator); + resource.addArrayValue('arrayName','Fred'); + resource.addArrayValue('arrayName','Bloggs'); + }); + + }); + +}); From f993330fae5b7976ae8f44271842ad1d7f738617 Mon Sep 17 00:00:00 2001 From: Simon Stone Date: Fri, 7 Jul 2017 09:21:45 +0100 Subject: [PATCH 08/88] Complete PersistentModel API tests (resolves #930) (#1511) --- .../lib/businessnetworkconnector.js | 118 ++-- .../test/assets.js | 392 ++++++++++- .../test/businessnetworkconnector.js | 314 +++++---- .../loopback-connector-composer/test/index.js | 6 + .../test/participants.js | 633 ++++++++++++++++++ 5 files changed, 1239 insertions(+), 224 deletions(-) create mode 100644 packages/loopback-connector-composer/test/participants.js diff --git a/packages/loopback-connector-composer/lib/businessnetworkconnector.js b/packages/loopback-connector-composer/lib/businessnetworkconnector.js index b4b6c7e22f..099adb2275 100644 --- a/packages/loopback-connector-composer/lib/businessnetworkconnector.js +++ b/packages/loopback-connector-composer/lib/businessnetworkconnector.js @@ -257,49 +257,30 @@ class BusinessNetworkConnector extends Connector { let filterKeys = Object.keys(filter); if(filterKeys.indexOf('where') >= 0) { - debug('where', JSON.stringify(filter.where)); - let whereKeys = Object.keys(filter.where); - debug('where keys', whereKeys); + const keys = Object.keys(filter.where); + if (keys.length === 0) { + throw new Error('The destroyAll operation without a where clause is not supported'); + } let identifierField = this.getClassIdentifier(composerModelName); - debug('identifierField', identifierField); + if(!filter.where[identifierField]) { + throw new Error('The specified filter does not match the identifier in the model'); + } // Check we have the right identifier for the object type - if(whereKeys.indexOf(identifierField) >= 0) { - let objectId = filter.where[identifierField]; - if(doResolve) { - return registry.resolve(objectId) - .then((result) => { - debug('Got Result:', result); - return [ result ]; - }) - .catch((error) => { - // check the error - it might be ok just an error indicating that the object doesn't exist - debug('all: error ', error); - if(error.toString().indexOf('does not exist') >= 0) { - return {}; - } else { - throw error; - } - }); - - } else { - return registry.get(objectId) - .then((result) => { - debug('Got Result:', result); - return [ this.serializer.toJSON(result) ]; - }) - .catch((error) => { - // check the error - it might be ok just an error indicating that the object doesn't exist - debug('all: error ', error); - if(error.toString().indexOf('does not exist') >= 0) { - return {}; - } else { - throw error; - } - }); - } + let objectId = filter.where[identifierField]; + if(doResolve) { + return registry.resolve(objectId) + .then((result) => { + debug('Got Result:', result); + return [ result ]; + }); + } else { - throw new Error('The specified filter does not match the identifier in the model'); + return registry.get(objectId) + .then((result) => { + debug('Got Result:', result); + return [ this.serializer.toJSON(result) ]; + }); } } else if(doResolve) { debug('no where filter, about to resolve on all'); @@ -326,6 +307,10 @@ class BusinessNetworkConnector extends Connector { callback(null, result); }) .catch((error) => { + if (error.message.match(/does not exist/)) { + callback(null, []); + return; + } callback(error); }); } @@ -469,13 +454,21 @@ class BusinessNetworkConnector extends Connector { data.$class = composerModelName; } - let resource; + let registry; return this.ensureConnected(options) .then((businessNetworkConnection) => { - resource = this.serializer.fromJSON(data); return this.getRegistryForModel(businessNetworkConnection, composerModelName); }) - .then((registry) => { + .then((registry_) => { + registry = registry_; + return registry.get(objectId); + }) + .then((resource) => { + const object = this.serializer.toJSON(resource); + Object.keys(data).forEach((key) => { + object[key] = data[key]; + }); + resource = this.serializer.fromJSON(object); return registry.update(resource); }) .then(() => { @@ -667,13 +660,14 @@ class BusinessNetworkConnector extends Connector { * Update an instance of an object in Composer. For assets, this method * updates the asset to the default asset registry. * @param {string} lbModelName the fully qualified model name. + * @param {string} where The filter to identify the asset or participant to be removed. * @param {Object} data the data for the asset or transaction. * @param {Object} options the options provided by Loopback. * @param {function} callback the callback to call when complete. * @returns {Promise} A promise that is resolved when complete. */ - update(lbModelName, data, options, callback) { - debug('update', lbModelName, data, options); + update(lbModelName, where, data, options, callback) { + debug('update', lbModelName, where, data, options); let composerModelName = this.getComposerModelName(lbModelName); // If the $class property has not been provided, add it now. @@ -681,17 +675,28 @@ class BusinessNetworkConnector extends Connector { data.$class = composerModelName; } + let idField; return this.ensureConnected(options) .then((businessNetworkConnection) => { + const keys = Object.keys(where); + if (keys.length === 0) { + throw new Error('The update operation without a where clause is not supported'); + } + idField = keys[0]; + if(!this.isValidId(composerModelName, idField)) { + throw new Error('The specified filter does not match the identifier in the model'); + } + return this.getRegistryForModel(businessNetworkConnection, composerModelName); + }) + .then((registry) => { + // Convert the JSON data into a resource. let serializer = this.businessNetworkDefinition.getSerializer(); let resource = serializer.fromJSON(data); - - // The create action is based on the type of the resource. - return this.getRegistryForModel(businessNetworkConnection, composerModelName) - .then((registry) => { - return registry.update(resource); - }); + if (resource.getIdentifier() !== where[idField]) { + throw new Error('The specified resource does not match the identifier in the filter'); + } + return registry.update(resource); }) .then(() => { @@ -721,12 +726,15 @@ class BusinessNetworkConnector extends Connector { let idField, registry; return this.ensureConnected(options) .then((businessNetworkConnection) => { - idField = Object.keys(where)[0]; - if(this.isValidId(composerModelName, idField)) { - return this.getRegistryForModel(businessNetworkConnection, composerModelName); - } else { - callback(new Error('The specified filter does not match the identifier in the model')); + const keys = Object.keys(where); + if (keys.length === 0) { + throw new Error('The destroyAll operation without a where clause is not supported'); } + idField = keys[0]; + if(!this.isValidId(composerModelName, idField)) { + throw new Error('The specified filter does not match the identifier in the model'); + } + return this.getRegistryForModel(businessNetworkConnection, composerModelName); }) .then((registry_) => { registry = registry_; diff --git a/packages/loopback-connector-composer/test/assets.js b/packages/loopback-connector-composer/test/assets.js index 15839e7d52..bd0a52ce98 100644 --- a/packages/loopback-connector-composer/test/assets.js +++ b/packages/loopback-connector-composer/test/assets.js @@ -22,6 +22,7 @@ const connector = require('..'); const fs = require('fs'); const loopback = require('loopback'); const path = require('path'); +const Util = require('composer-common').Util; const chai = require('chai'); const should = chai.should(); @@ -213,6 +214,20 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); }); }); + beforeEach(() => { + return Util.invokeChainCode(businessNetworkConnection.securityContext, 'resetBusinessNetwork', []) + .then(() => { + return businessNetworkConnection.getAssetRegistry('org.acme.bond.BondAsset'); + }) + .then((assetRegistry_) => { + assetRegistry = assetRegistry_; + return assetRegistry.addAll([ + serializer.fromJSON(assetData[0]), + serializer.fromJSON(assetData[1]) + ]); + }); + }); + describe(`#count namespaces[${namespaces}]`, () => { it('should count all of the assets', () => { @@ -248,7 +263,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); .then((asset) => { let json = serializer.toJSON(asset); json.should.deep.equal(assetData[2]); - return assetRegistry.remove('ISIN_3'); }); }); @@ -261,7 +275,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); let json = serializer.toJSON(asset); delete json.$class; json.should.deep.equal(assetData[3]); - return assetRegistry.remove('ISIN_4'); }); }); @@ -285,7 +298,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); .then((asset) => { let json = serializer.toJSON(asset); json.should.deep.equal(assetData[2]); - return assetRegistry.remove('ISIN_3'); }) .then(() => { return assetRegistry.get('ISIN_4'); @@ -294,7 +306,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); let json = serializer.toJSON(asset); delete json.$class; json.should.deep.equal(assetData[3]); - return assetRegistry.remove('ISIN_4'); }); }); @@ -302,10 +313,46 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); describe(`#destroyAll namespaces[${namespaces}]`, () => { + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'BondAsset'].destroyAll() + .should.be.rejectedWith(/is not supported/); + + }); + + it('should remove a single specified asset', () => { + return app.models[prefix + 'BondAsset'].destroyAll({ ISINCode: 'ISIN_1' }) + .then(() => { + return assetRegistry.exists('ISIN_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + it('should return an error if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].destroyAll({ ISINCode: 'ISIN_999' }) + .should.be.rejected; + }); + }); describe(`#destroyById namespaces[${namespaces}]`, () => { + it('should delete the specified asset', () => { + return app.models[prefix + 'BondAsset'].destroyById('ISIN_1') + .then(() => { + return assetRegistry.exists('ISIN_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + it('should return an error if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].destroyById('ISIN_999') + .should.be.rejected; + }); + }); describe(`#exists namespaces[${namespaces}]`, () => { @@ -410,7 +457,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); .then((asset) => { let json = serializer.toJSON(asset); json.should.deep.equal(assetData[2]); - return assetRegistry.remove('ISIN_3'); }); }); @@ -422,7 +468,6 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); .then((asset) => { let json = serializer.toJSON(asset); json.should.deep.equal(assetData[2]); - return assetRegistry.remove('ISIN_3'); }); }); @@ -430,38 +475,373 @@ const bfs_fs = BrowserFS.BFSRequire('fs'); describe(`#replaceById namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should update the specified asset', () => { + return app.models[prefix + 'BondAsset'].replaceById('ISIN_1', updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should return an error if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].replaceById('ISIN_999', updatedAsset) + .should.be.rejected; + }); + }); describe(`#replaceOrCreate namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should update the specified asset', () => { + return app.models[prefix + 'BondAsset'].replaceOrCreate(updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should create a new asset if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].replaceOrCreate(assetData[2]) + .then(() => { + return assetRegistry.get('ISIN_3'); + }) + .then((asset) => { + let json = serializer.toJSON(asset); + json.should.deep.equal(assetData[2]); + }); + }); + }); describe(`#updateAll namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'BondAsset'].updateAll(updatedAsset) + .should.be.rejectedWith(/is not supported/); + + }); + + it('should remove a single specified asset', () => { + return app.models[prefix + 'BondAsset'].updateAll({ ISINCode: 'ISIN_1' }, updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should return an error if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].updateAll({ ISINCode: 'ISIN_999' }, updatedAsset) + .should.be.rejected; + }); + }); describe(`#upsert namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should update the specified asset', () => { + return app.models[prefix + 'BondAsset'].upsert(updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should create a new asset if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].upsert(assetData[2]) + .then(() => { + return assetRegistry.get('ISIN_3'); + }) + .then((asset) => { + let json = serializer.toJSON(asset); + json.should.deep.equal(assetData[2]); + }); + }); + }); describe(`#upsertWithWhere namespaces[${namespaces}]`, () => { + const updatedAsset = { + $class: 'org.acme.bond.BondAsset', + ISINCode: 'ISIN_1', + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }; + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'BondAsset'].upsertWithWhere({}, updatedAsset) + .should.be.rejectedWith(/is not supported/); + + }); + + it('should update the specified asset', () => { + return app.models[prefix + 'BondAsset'].upsertWithWhere({ ISINCode: 'ISIN_1' }, updatedAsset) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + + it('should create a new asset if the specified asset does not exist', () => { + return app.models[prefix + 'BondAsset'].upsertWithWhere({ ISINCode: 'ISIN_3' }, assetData[2]) + .then(() => { + return assetRegistry.get('ISIN_3'); + }) + .then((asset) => { + let json = serializer.toJSON(asset); + json.should.deep.equal(assetData[2]); + }); + }); + }); describe(`#destroy namespaces[${namespaces}]`, () => { + it('should delete the specified asset', () => { + return app.models[prefix + 'BondAsset'].findById('ISIN_1') + .then((asset) => { + return asset.destroy(); + }) + .then(() => { + return assetRegistry.exists('ISIN_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + }); describe(`#replaceAttributes namespaces[${namespaces}]`, () => { + it('should replace attributes in the specified asset', () => { + return app.models[prefix + 'BondAsset'].findById('ISIN_1') + .then((asset) => { + return asset.replaceAttributes({ + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }); + }) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + }); describe(`#updateAttribute namespaces[${namespaces}]`, () => { + it('should replace attribute in the specified asset', () => { + return app.models[prefix + 'BondAsset'].findById('ISIN_1') + .then((asset) => { + return asset.updateAttribute('bond', { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + }); + }) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + }); describe(`#updateAttributes namespaces[${namespaces}]`, () => { + it('should replace attributes in the specified asset', () => { + return app.models[prefix + 'BondAsset'].findById('ISIN_1') + .then((asset) => { + return asset.updateAttributes({ + bond: { + $class: 'org.acme.bond.Bond', + dayCountFraction: 'EOM', + exchangeId: [ + 'NYSE' + ], + faceAmount: 1000, + instrumentId: [ + 'AliceNewCorp' + ], + issuer: 'resource:org.acme.bond.Issuer#1', + maturity: '2018-02-27T21:03:52.000Z', + parValue: 1000, + paymentFrequency: { + $class: 'org.acme.bond.PaymentFrequency', + period: 'MONTH', + periodMultiplier: 6 + } + } + }); + }) + .then(() => { + return assetRegistry.get('ISIN_1'); + }) + .then((asset) => { + asset.bond.instrumentId.should.deep.equal([ 'AliceNewCorp' ]); + }); + }); + }); }); diff --git a/packages/loopback-connector-composer/test/businessnetworkconnector.js b/packages/loopback-connector-composer/test/businessnetworkconnector.js index e74c994379..cf1997881c 100644 --- a/packages/loopback-connector-composer/test/businessnetworkconnector.js +++ b/packages/loopback-connector-composer/test/businessnetworkconnector.js @@ -14,21 +14,18 @@ 'use strict'; -const AssetDeclaration = require('composer-common/lib/introspect/assetdeclaration'); const AssetRegistry = require('composer-client/lib/assetregistry'); const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; const BusinessNetworkConnector = require('../lib/businessnetworkconnector'); const BusinessNetworkConnectionWrapper = require('../lib/businessnetworkconnectionwrapper'); const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition; +const Factory = require('composer-common').Factory; const Introspector = require('composer-common').Introspector; const LoopbackVisitor = require('composer-common').LoopbackVisitor; const ModelManager = require('composer-common').ModelManager; const NodeCache = require('node-cache'); -const ParticipantDeclaration = require('composer-common/lib/introspect/participantdeclaration'); const ParticipantRegistry = require('composer-client/lib/participantregistry'); -const Resource = require('composer-common/lib/model/resource'); const Serializer = require('composer-common').Serializer; -const TransactionDeclaration = require('composer-common/lib/introspect/transactiondeclaration'); const TransactionRegistry = require('composer-client/lib/transactionregistry'); const TypeNotFoundException = require('composer-common/lib/typenotfoundexception'); @@ -63,6 +60,7 @@ describe('BusinessNetworkConnector', () => { let sandbox; let testConnector; let modelManager; + let factory; let introspector; beforeEach(() => { @@ -90,6 +88,7 @@ describe('BusinessNetworkConnector', () => { modelManager = new ModelManager(); modelManager.addModelFile(MODEL_FILE); introspector = new Introspector(modelManager); + factory = new Factory(modelManager); sandbox = sinon.sandbox.create(); @@ -551,7 +550,7 @@ describe('BusinessNetworkConnector', () => { .then((result) => { sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); - result.should.deep.equal({}); + result.should.deep.equal([]); }); }); @@ -569,7 +568,7 @@ describe('BusinessNetworkConnector', () => { .then((result) => { sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); - result.should.deep.equal({}); + result.should.deep.equal([]); }); }); @@ -881,7 +880,7 @@ describe('BusinessNetworkConnector', () => { describe('#updateAttributes', () => { let mockAssetRegistry; - let mockResourceToUpdate; + let resource; beforeEach(() => { sinon.spy(testConnector, 'getRegistryForModel'); @@ -893,12 +892,14 @@ describe('BusinessNetworkConnector', () => { testConnector.connected = true; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockResourceToUpdate = sinon.createStubInstance(Resource); + resource = factory.newResource('org.acme.base', 'BaseAsset', 'theId'); + mockAssetRegistry.get.withArgs('theId').resolves(resource); + mockSerializer.toJSON.withArgs(resource).returns({ theValue: 'theId', prop1: 'woohoo' }); }); it('should update the attributes for the given object id on the blockchain with no $class attribute', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.resolves(); testConnector.updateAttributes('org.acme.base.BaseAsset', 'theId', { 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -908,18 +909,18 @@ describe('BusinessNetworkConnector', () => { }); }) .then((result) => { - sinon.assert.calledWith(mockSerializer.fromJSON, { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated' }); + sinon.assert.calledWith(mockSerializer.fromJSON, { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated', prop1: 'woohoo' }); sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(testConnector.getRegistryForModel); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResourceToUpdate); + sinon.assert.calledWith(mockAssetRegistry.update, resource); }); }); it('should update the attributes for the given object id on the blockchain with a $class attribute', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.resolves(); testConnector.updateAttributes('org.acme.base.BaseAsset', 'theId', { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -929,18 +930,18 @@ describe('BusinessNetworkConnector', () => { }); }) .then((result) => { - sinon.assert.calledWith(mockSerializer.fromJSON, { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated' }); + sinon.assert.calledWith(mockSerializer.fromJSON, { '$class': 'org.acme.base.BaseAsset', 'theValue' : 'updated', prop1: 'woohoo' }); sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(testConnector.getRegistryForModel); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResourceToUpdate); + sinon.assert.calledWith(mockAssetRegistry.update, resource); }); }); it('should handle the error when an invalid model is specified', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.resolves(); testConnector.updateAttributes('org.acme.base.WrongBaseAsset', 'theId', { 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -959,7 +960,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an update error from the composer api', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.rejects(new Error('Update error from Composer')); testConnector.updateAttributes('org.acme.base.BaseAsset', 'theId', { 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -979,7 +980,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an update error from the composer api for an asset that does not exist', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(resource); mockAssetRegistry.update.rejects(new Error('does not exist')); testConnector.updateAttributes('org.acme.base.BaseAsset', 'theId', { 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1004,7 +1005,7 @@ describe('BusinessNetworkConnector', () => { describe('#replaceById', () => { let mockAssetRegistry; - let mockResourceToUpdate; + let asset; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1016,12 +1017,12 @@ describe('BusinessNetworkConnector', () => { testConnector.connected = true; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockResourceToUpdate = sinon.createStubInstance(Resource); + asset = factory.newResource('org.acme.base', 'BaseAsset', 'myId'); }); it('should update the attributes for the given object id on the blockchain with no $class attribute', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.resolves(); testConnector.replaceById('org.acme.base.BaseAsset', '1', { 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1036,13 +1037,13 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(testConnector.getRegistryForModel); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResourceToUpdate); + sinon.assert.calledWith(mockAssetRegistry.update, asset); }); }); it('should update the attributes for the given object id on the blockchain with a $class attribute', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.resolves(); testConnector.replaceById('org.acme.base.BaseAsset', '1', { '$class': 'org.acme.base.BaseAsset', 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1057,13 +1058,13 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(testConnector.getRegistryForModel); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResourceToUpdate); + sinon.assert.calledWith(mockAssetRegistry.update, asset); }); }); it('should handle the error when an invalid model is specified', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.resolves(); testConnector.replaceById('org.acme.base.WrongBaseAsset', '1', { 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1082,7 +1083,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an update error from the composer api', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.rejects(new Error('Update error from Composer')); testConnector.replaceById('org.acme.base.BaseAsset', '1', { 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1102,7 +1103,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an update error from the composer api for an asset that does not exist', () => { return new Promise((resolve, reject) => { - mockSerializer.fromJSON.returns(mockResourceToUpdate); + mockSerializer.fromJSON.returns(asset); mockAssetRegistry.update.rejects(new Error('does not exist')); testConnector.replaceById('org.acme.base.BaseAsset', '1', { 'assetId': '1', 'theValue' : 'updated' }, { test: 'options' }, (error) => { if(error) { @@ -1129,10 +1130,9 @@ describe('BusinessNetworkConnector', () => { let mockAssetRegistry; let mockParticipantRegistry; - let mockAssetDeclaration; - let mockParticipantDeclaration; - let mockTransactionDeclaration; - let mockResource; + let asset; + let participant; + let transaction; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1140,22 +1140,17 @@ describe('BusinessNetworkConnector', () => { testConnector.modelManager = modelManager; testConnector.introspector = introspector; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); + mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); - mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); - mockParticipantDeclaration = sinon.createStubInstance(ParticipantDeclaration); - mockTransactionDeclaration = sinon.createStubInstance(TransactionDeclaration); - mockResource = sinon.createStubInstance(Resource); + mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); mockBusinessNetworkDefinition.getSerializer.returns(mockSerializer); - mockSerializer.fromJSON.onFirstCall().returns(mockResource); - + asset = factory.newResource('org.acme.base', 'BaseAsset', 'myId'); + participant = factory.newResource('org.acme.base', 'BaseParticipant', 'myId'); + transaction = factory.newResource('org.acme.base', 'BaseTransaction', 'myId'); }); it('should use the model name as the class name if not specified', () => { - - mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); - + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseAsset', { @@ -1178,9 +1173,8 @@ describe('BusinessNetworkConnector', () => { }); it('should throw if the type is not an asset or a transaction', () => { - mockBusinessNetworkConnection.getAssetRegistry.onFirstCall().resolves(mockAssetRegistry); - mockResource.getClassDeclaration.onFirstCall().returns({}); - + let concept = factory.newConcept('org.acme.base', 'BaseConcept'); + mockSerializer.fromJSON.onFirstCall().returns(concept); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseConcept', { some : 'data' @@ -1194,10 +1188,7 @@ describe('BusinessNetworkConnector', () => { }); it('should add an asset to the default asset registry', () => { - mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); - + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseAsset', { $class : 'org.acme.base.BaseAsset', @@ -1215,16 +1206,14 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(mockBusinessNetworkConnection.getAssetRegistry); sinon.assert.calledWith(mockBusinessNetworkConnection.getAssetRegistry, 'org.acme.base.BaseAsset'); sinon.assert.calledOnce(mockAssetRegistry.add); - sinon.assert.calledWith(mockAssetRegistry.add, mockResource); + sinon.assert.calledWith(mockAssetRegistry.add, asset); should.equal(identifier, undefined); }); }); it('should handle an error adding an asset to the default asset registry', () => { - mockAssetRegistry.add.onFirstCall().throws(new Error('expected error')); - mockBusinessNetworkConnection.getAssetRegistry.onFirstCall().resolves(mockAssetRegistry); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); + mockAssetRegistry.add.onFirstCall().rejects(new Error('expected error')); + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseAsset', { @@ -1240,9 +1229,7 @@ describe('BusinessNetworkConnector', () => { }); it('should add a participant to the default participant registry', () => { - mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); - mockParticipantDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseParticipant'); - mockResource.getClassDeclaration.onFirstCall().returns(mockParticipantDeclaration); + mockSerializer.fromJSON.onFirstCall().returns(participant); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseParticipant', { @@ -1261,16 +1248,14 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(mockBusinessNetworkConnection.getParticipantRegistry); sinon.assert.calledWith(mockBusinessNetworkConnection.getParticipantRegistry, 'org.acme.base.BaseParticipant'); sinon.assert.calledOnce(mockParticipantRegistry.add); - sinon.assert.calledWith(mockParticipantRegistry.add, mockResource); + sinon.assert.calledWith(mockParticipantRegistry.add, participant); should.equal(identifier, undefined); }); }); it('should handle an error adding a participant to the default participant registry', () => { - mockParticipantRegistry.add.onFirstCall().throws(new Error('expected error')); - mockBusinessNetworkConnection.getParticipantRegistry.onFirstCall().resolves(mockParticipantRegistry); - mockParticipantDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseParticipant'); - mockResource.getClassDeclaration.onFirstCall().returns(mockParticipantDeclaration); + mockParticipantRegistry.add.onFirstCall().rejects(new Error('expected error')); + mockSerializer.fromJSON.onFirstCall().returns(participant); return new Promise((resolve, reject) => { testConnector.create('org.acme.base.BaseParticipant', { @@ -1286,9 +1271,7 @@ describe('BusinessNetworkConnector', () => { }); it('should submit a transaction', () => { - mockTransactionDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.Transaction'); - mockResource.getIdentifier.returns('f7cf42d6-492f-4b7e-8b6a-2150ac5bcc5f'); - mockResource.getClassDeclaration.onFirstCall().returns(mockTransactionDeclaration); + mockSerializer.fromJSON.onFirstCall().returns(transaction); return new Promise((resolve, reject) => { testConnector.create('org.acme.Transaction', { @@ -1305,15 +1288,14 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(testConnector.ensureConnected); sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); sinon.assert.calledOnce(mockBusinessNetworkConnection.submitTransaction); - sinon.assert.calledWith(mockBusinessNetworkConnection.submitTransaction, mockResource); - identifier.should.equal('f7cf42d6-492f-4b7e-8b6a-2150ac5bcc5f'); + sinon.assert.calledWith(mockBusinessNetworkConnection.submitTransaction, transaction); + identifier.should.equal('myId'); }); }); it('should handle an error submitting a transaction', () => { mockBusinessNetworkConnection.submitTransaction.onFirstCall().rejects(new Error('expected error')); - mockTransactionDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.Transaction'); - mockResource.getClassDeclaration.onFirstCall().returns(mockTransactionDeclaration); + mockSerializer.fromJSON.onFirstCall().returns(transaction); return new Promise((resolve, reject) => { testConnector.create('org.acme.Transaction', { @@ -1332,6 +1314,9 @@ describe('BusinessNetworkConnector', () => { describe('#retrieve', () => { let mockAssetRegistry; + let mockParticipantRegistry; + let asset; + let participant; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1342,17 +1327,15 @@ describe('BusinessNetworkConnector', () => { testConnector.serializer = mockSerializer; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); + mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); + mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); + asset = factory.newResource('org.acme.base', 'BaseAsset', 'theId'); + participant = factory.newResource('org.acme.base', 'BaseParticipant', 'theId'); }); it('should retrieve an asset', () => { - mockAssetRegistry.get.resolves({assetId : 'myId', stringValue : 'a big car'}); + mockAssetRegistry.get.resolves(asset); mockSerializer.toJSON.onFirstCall().returns({assetId : 'myId', stringValue : 'a big car'}); - mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - let mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); - mockModelManager.getType.returns(mockAssetDeclaration); return new Promise((resolve, reject) => { @@ -1376,15 +1359,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle asset errors', () => { - let mockAssetRegistry = sinon.createStubInstance(AssetRegistry); - mockAssetRegistry.get.onFirstCall().throws(new Error('expected error')); - mockBusinessNetworkConnection.getAssetRegistry.onFirstCall().resolves(mockAssetRegistry); - - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - let mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); - mockModelManager.getType.returns(mockAssetDeclaration); - + mockAssetRegistry.get.onFirstCall().rejects(new Error('expected error')); return new Promise((resolve, reject) => { testConnector.retrieve('org.acme.base.BaseAsset', { @@ -1399,15 +1374,8 @@ describe('BusinessNetworkConnector', () => { }); it('should retrieve a participant', () => { - let mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); - mockParticipantRegistry.get.resolves({participantId : 'myId', stringValue : 'a big car'}); + mockParticipantRegistry.get.resolves(participant); mockSerializer.toJSON.onFirstCall().returns({participantId : 'myId', stringValue : 'a big car'}); - mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); - - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - let mockParticipantDeclaration = sinon.createStubInstance(ParticipantDeclaration); - mockModelManager.getType.returns(mockParticipantDeclaration); return new Promise((resolve, reject) => { testConnector.retrieve('org.acme.base.BaseParticipant', 'myId', { test: 'options' }, (error, result) => { @@ -1430,14 +1398,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle participant errors', () => { - let mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); - mockParticipantRegistry.get.onFirstCall().throws(new Error('expected error')); - mockBusinessNetworkConnection.getParticipantRegistry.onFirstCall().resolves(mockParticipantRegistry); - - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - let mockParticipantDeclaration = sinon.createStubInstance(ParticipantDeclaration); - mockModelManager.getType.returns(mockParticipantDeclaration); + mockParticipantRegistry.get.onFirstCall().rejects(new Error('expected error')); return new Promise((resolve, reject) => { testConnector.retrieve('org.acme.base.BaseParticipant', { @@ -1452,10 +1413,6 @@ describe('BusinessNetworkConnector', () => { }); it('should throw error on unsupported type', () => { - let mockModelManager = sinon.createStubInstance(ModelManager); - mockBusinessNetworkDefinition.getModelManager.returns(mockModelManager); - mockModelManager.getType.returns({}); - return new Promise((resolve, reject) => { testConnector.retrieve('org.acme.base.BaseConcept', 'myId', { test: 'options' }, (error, result) => { if (error) { @@ -1471,9 +1428,9 @@ describe('BusinessNetworkConnector', () => { let mockAssetRegistry; let mockParticipantRegistry; - let mockResource; - let mockAssetDeclaration; - let mockParticipantDeclaration; + let asset; + let participant; + beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); testConnector.businessNetworkDefinition = mockBusinessNetworkDefinition; @@ -1484,21 +1441,16 @@ describe('BusinessNetworkConnector', () => { mockBusinessNetworkDefinition.getSerializer.returns(mockSerializer); mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockParticipantRegistry = sinon.createStubInstance(ParticipantRegistry); - mockAssetDeclaration = sinon.createStubInstance(AssetDeclaration); - mockParticipantDeclaration = sinon.createStubInstance(ParticipantDeclaration); - mockResource = sinon.createStubInstance(Resource); - mockSerializer.fromJSON.onFirstCall().returns(mockResource); + asset = factory.newResource('org.acme.base', 'BaseAsset', 'myId'); + participant = factory.newResource('org.acme.base', 'BaseParticipant', 'myId'); mockBusinessNetworkConnection.getParticipantRegistry.resolves(mockParticipantRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); }); it('should update an asset', ()=> { - + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); - - testConnector.update('org.acme.base.BaseAsset', { + testConnector.update('org.acme.base.BaseAsset', { theValue: 'myId' }, { $class : 'org.acme.base.BaseAsset', some : 'data' }, { test: 'options' }, (error) => { @@ -1514,16 +1466,14 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(mockBusinessNetworkConnection.getAssetRegistry); sinon.assert.calledWith(mockBusinessNetworkConnection.getAssetRegistry, 'org.acme.base.BaseAsset'); sinon.assert.calledOnce(mockAssetRegistry.update); - sinon.assert.calledWith(mockAssetRegistry.update, mockResource); + sinon.assert.calledWith(mockAssetRegistry.update, asset); }); }); it('should update a participant', ()=> { - mockParticipantDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseParticipant'); - mockResource.getClassDeclaration.onFirstCall().returns(mockParticipantDeclaration); - + mockSerializer.fromJSON.onFirstCall().returns(participant); return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseParticipant', { + testConnector.update('org.acme.base.BaseParticipant', { theValue: 'myId' }, { $class : 'org.acme.base.BaseParticipant', some : 'data' }, { test: 'options' }, (error) => { @@ -1539,13 +1489,13 @@ describe('BusinessNetworkConnector', () => { sinon.assert.calledOnce(mockBusinessNetworkConnection.getParticipantRegistry); sinon.assert.calledWith(mockBusinessNetworkConnection.getParticipantRegistry, 'org.acme.base.BaseParticipant'); sinon.assert.calledOnce(mockParticipantRegistry.update); - sinon.assert.calledWith(mockParticipantRegistry.update, mockResource); + sinon.assert.calledWith(mockParticipantRegistry.update, participant); }); }); - it('should handle error if unsupported class', () => { + it('should handle error if no where clause', () => { return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseConcept', { + testConnector.update('org.acme.base.BaseConcept', { }, { assetId : 'myId', stringValue : 'a bigger car' }, { test: 'options' }, (error, result) => { @@ -1554,16 +1504,43 @@ describe('BusinessNetworkConnector', () => { } resolve(result); }); - }).should.be.rejectedWith(/No registry for specified model name/); + }).should.be.rejectedWith(/is not supported/); + }); + + it('should handle error if unsupported ID field', () => { + return new Promise((resolve, reject) => { + testConnector.update('org.acme.base.BaseConcept', { doge: 'myId' }, { + assetId : 'myId', + stringValue : 'a bigger car' + }, { test: 'options' }, (error, result) => { + if (error) { + return reject(error); + } + resolve(result); + }); + }).should.be.rejectedWith(/does not match the identifier/); + }); + + it('should handle error if mismatched ID field', () => { + mockSerializer.fromJSON.onFirstCall().returns(asset); + return new Promise((resolve, reject) => { + testConnector.update('org.acme.base.BaseConcept', { theValue: 'doge' }, { + theValue : 'lolz' + }, { test: 'options' }, (error, result) => { + if (error) { + return reject(error); + } + resolve(result); + }); + }).should.be.rejectedWith(/does not match the identifier/); }); it('should handle asset errors', () => { - mockAssetRegistry.update.onFirstCall().throws(new Error('expected error')); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); + mockAssetRegistry.update.onFirstCall().rejects(new Error('expected error')); + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseAsset', { + testConnector.update('org.acme.base.BaseAsset', { theValue: 'myId' }, { assetId : 'myId', stringValue : 'value' }, { test: 'options' }, (error) => { @@ -1576,13 +1553,11 @@ describe('BusinessNetworkConnector', () => { }); it('should handle participant errors', () => { - mockParticipantRegistry.update.onFirstCall().throws(new Error('expected error')); - mockSerializer.fromJSON.onFirstCall().returns(mockResource); - mockParticipantDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseParticipant'); - mockResource.getClassDeclaration.onFirstCall().returns(mockParticipantDeclaration); + mockParticipantRegistry.update.onFirstCall().rejects(new Error('expected error')); + mockSerializer.fromJSON.onFirstCall().returns(participant); return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseParticipant', { + testConnector.update('org.acme.base.BaseParticipant', { theValue: 'myId' }, { participantId : 'myId', stringValue : 'value' }, { test: 'options' }, (error) => { @@ -1595,12 +1570,11 @@ describe('BusinessNetworkConnector', () => { }); it('should handle asset errors for assets that do not exist', () => { - mockAssetRegistry.update.onFirstCall().throws(new Error('does not exist')); - mockAssetDeclaration.getFullyQualifiedName.onFirstCall().returns('org.acme.base.BaseAsset'); - mockResource.getClassDeclaration.onFirstCall().returns(mockAssetDeclaration); + mockAssetRegistry.update.onFirstCall().rejects(new Error('does not exist')); + mockSerializer.fromJSON.onFirstCall().returns(asset); return new Promise((resolve, reject) => { - testConnector.update('org.acme.base.BaseAsset', { + testConnector.update('org.acme.base.BaseAsset', { theValue: 'myId' }, { assetId : 'myId', stringValue : 'value' }, { test: 'options' }, (error) => { @@ -1622,7 +1596,7 @@ describe('BusinessNetworkConnector', () => { describe('#destroy', () => { let mockAssetRegistry; - let mockResourceToDelete; + let resourceToDelete; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1633,11 +1607,11 @@ describe('BusinessNetworkConnector', () => { testConnector.connected = true; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockResourceToDelete = sinon.createStubInstance(Resource); + resourceToDelete = factory.newResource('org.acme.base', 'BaseAsset', 'foo'); }); it('should delete the object for the given id from the blockchain', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.resolves(); return new Promise((resolve, reject) => { testConnector.destroy('org.acme.base.BaseAsset','foo' , { test: 'options' }, (error) => { @@ -1676,7 +1650,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle an error when calling composer remove for the given id', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.rejects(new Error('removal error')); return new Promise((resolve, reject) => { testConnector.destroy('org.acme.base.BaseAsset','foo', { test: 'options' }, (error) => { @@ -1696,7 +1670,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle an error when calling composer remove for an asset that does not exist', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.rejects(new Error('does not exist')); return new Promise((resolve, reject) => { testConnector.destroy('org.acme.base.BaseAsset', 'foo' , { test: 'options' }, (error) => { @@ -1722,7 +1696,7 @@ describe('BusinessNetworkConnector', () => { describe('#destroyAll', () => { let mockAssetRegistry; - let mockResourceToDelete; + let resourceToDelete; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); @@ -1733,11 +1707,11 @@ describe('BusinessNetworkConnector', () => { testConnector.connected = true; mockAssetRegistry = sinon.createStubInstance(AssetRegistry); mockBusinessNetworkConnection.getAssetRegistry.resolves(mockAssetRegistry); - mockResourceToDelete = sinon.createStubInstance(Resource); + resourceToDelete = factory.newResource('org.acme.base', 'BaseAsset', 'foo'); }); it('should delete the object for the given id from the blockchain', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.resolves(); return new Promise((resolve, reject) => { testConnector.destroyAll('org.acme.base.BaseAsset', { 'theValue' : 'foo' }, { test: 'options' }, (error) => { @@ -1755,6 +1729,23 @@ describe('BusinessNetworkConnector', () => { }); }); + it('should handle an error when an empty where clause is specified', () => { + mockAssetRegistry.get.rejects(new Error('get error')); + return new Promise((resolve, reject) => { + testConnector.destroyAll('org.acme.base.BaseAsset', {}, { test: 'options' }, (error) => { + if(error) { + return reject(error); + } + resolve(); + }); + }) + .should.be.rejectedWith(/is not supported/) + .then(() => { + sinon.assert.calledOnce(testConnector.ensureConnected); + sinon.assert.calledWith(testConnector.ensureConnected, { test: 'options' }); + }); + }); + it('should handle an error when an invalid Object identifier is specified', () => { mockAssetRegistry.get.rejects(new Error('get error')); return new Promise((resolve, reject) => { @@ -1791,7 +1782,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle an error when calling composer remove for the given id', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.rejects(new Error('removal error')); return new Promise((resolve, reject) => { testConnector.destroyAll('org.acme.base.BaseAsset', { 'theValue' : 'foo' }, { test: 'options' }, (error) => { @@ -1811,7 +1802,7 @@ describe('BusinessNetworkConnector', () => { }); it('should handle an error when calling composer remove for an asset that does not exist', () => { - mockAssetRegistry.get.resolves(mockResourceToDelete); + mockAssetRegistry.get.resolves(resourceToDelete); mockAssetRegistry.remove.rejects(new Error('does not exist')); return new Promise((resolve, reject) => { testConnector.destroyAll('org.acme.base.BaseAsset', { 'theValue' : 'foo' }, { test: 'options' }, (error) => { @@ -1922,25 +1913,23 @@ describe('BusinessNetworkConnector', () => { describe('#getAllTransactions', () => { let mockTransactionRegistry; - let mockTransaction1, mockTransaction2; + let transaction1, transaction2; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); testConnector.connected = true; mockTransactionRegistry = sinon.createStubInstance(TransactionRegistry); mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); - mockTransaction1 = sinon.createStubInstance(Resource); - mockTransaction1.transactionId = 'tx1'; - mockTransaction2 = sinon.createStubInstance(Resource); - mockTransaction2.transactionId = 'tx2'; + transaction1 = factory.newResource('org.acme.base', 'BaseTransaction', 'tx1'); + transaction2 = factory.newResource('org.acme.base', 'BaseTransaction', 'tx2'); testConnector.serializer = mockSerializer; }); it('should get all of the transactions in the transaction registry', () => { mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); - mockTransactionRegistry.getAll.resolves([mockTransaction1, mockTransaction2]); - mockSerializer.toJSON.withArgs(mockTransaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); - mockSerializer.toJSON.withArgs(mockTransaction2).returns({ transactionId: 'tx2', $class: 'sometx' }); + mockTransactionRegistry.getAll.resolves([transaction1, transaction2]); + mockSerializer.toJSON.withArgs(transaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); + mockSerializer.toJSON.withArgs(transaction2).returns({ transactionId: 'tx2', $class: 'sometx' }); const cb = sinon.stub(); return testConnector.getAllTransactions({ test: 'options' }, cb) .then(() => { @@ -1979,22 +1968,21 @@ describe('BusinessNetworkConnector', () => { describe('#getTransactionByID', () => { let mockTransactionRegistry; - let mockTransaction1; + let transaction; beforeEach(() => { sinon.stub(testConnector, 'ensureConnected').resolves(mockBusinessNetworkConnection); testConnector.connected = true; mockTransactionRegistry = sinon.createStubInstance(TransactionRegistry); mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); - mockTransaction1 = sinon.createStubInstance(Resource); - mockTransaction1.transactionId = 'tx1'; + transaction = factory.newResource('org.acme.base', 'BaseTransaction', 'tx1'); testConnector.serializer = mockSerializer; }); it('should get the specified transaction in the transaction registry', () => { mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); - mockTransactionRegistry.get.withArgs('tx1').resolves(mockTransaction1); - mockSerializer.toJSON.withArgs(mockTransaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); + mockTransactionRegistry.get.withArgs('tx1').resolves(transaction); + mockSerializer.toJSON.withArgs(transaction).returns({ transactionId: 'tx1', $class: 'sometx' }); const cb = sinon.stub(); return testConnector.getTransactionByID('tx1', { test: 'options' }, cb) .then(() => { @@ -2013,7 +2001,7 @@ describe('BusinessNetworkConnector', () => { it('should handle an error getting the specified transaction in the transaction registry', () => { mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); mockTransactionRegistry.get.withArgs('tx1').rejects(new Error('such error')); - mockSerializer.toJSON.withArgs(mockTransaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); + mockSerializer.toJSON.withArgs(transaction).returns({ transactionId: 'tx1', $class: 'sometx' }); const cb = sinon.stub(); return testConnector.getTransactionByID('tx1', { test: 'options' }, cb) .then(() => { @@ -2029,7 +2017,7 @@ describe('BusinessNetworkConnector', () => { it('should return a 404 error getting the specified transaction in the transaction registry', () => { mockBusinessNetworkConnection.getTransactionRegistry.resolves(mockTransactionRegistry); mockTransactionRegistry.get.withArgs('tx1').rejects(new Error('the thing does not exist')); - mockSerializer.toJSON.withArgs(mockTransaction1).returns({ transactionId: 'tx1', $class: 'sometx' }); + mockSerializer.toJSON.withArgs(transaction).returns({ transactionId: 'tx1', $class: 'sometx' }); const cb = sinon.stub(); return testConnector.getTransactionByID('tx1', { test: 'options' }, cb) .then(() => { diff --git a/packages/loopback-connector-composer/test/index.js b/packages/loopback-connector-composer/test/index.js index 65c569ecad..f0cbd51988 100644 --- a/packages/loopback-connector-composer/test/index.js +++ b/packages/loopback-connector-composer/test/index.js @@ -74,6 +74,7 @@ describe('loopback-connector-composer', () => { sinon.assert.calledOnce(connectorModule.createConnector); sinon.assert.calledWith(connectorModule.createConnector, dataSource.settings); sinon.assert.calledOnce(mockBusinessNetworkConnector.connect); + mockBusinessNetworkConnector.connecting = true; }); }); @@ -94,6 +95,7 @@ describe('loopback-connector-composer', () => { sinon.assert.calledOnce(connectorModule.createConnector); sinon.assert.calledWith(connectorModule.createConnector, {}); sinon.assert.calledOnce(mockBusinessNetworkConnector.connect); + mockBusinessNetworkConnector.connecting = true; }); }); @@ -102,6 +104,10 @@ describe('loopback-connector-composer', () => { settings : {} }; connectorModule.initialize(dataSource); + dataSource.connector.should.be.an.instanceOf(BusinessNetworkConnector); + sinon.assert.calledOnce(connectorModule.createConnector); + sinon.assert.calledWith(connectorModule.createConnector, {}); + mockBusinessNetworkConnector.connecting = false; }); }); }); diff --git a/packages/loopback-connector-composer/test/participants.js b/packages/loopback-connector-composer/test/participants.js new file mode 100644 index 0000000000..72d46bb54b --- /dev/null +++ b/packages/loopback-connector-composer/test/participants.js @@ -0,0 +1,633 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const AdminConnection = require('composer-admin').AdminConnection; +const BrowserFS = require('browserfs/dist/node/index'); +const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; +const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition; +const connector = require('..'); +const fs = require('fs'); +const loopback = require('loopback'); +const path = require('path'); +const Util = require('composer-common').Util; + +const chai = require('chai'); +const should = chai.should(); +chai.use(require('chai-as-promised')); + +const bfs_fs = BrowserFS.BFSRequire('fs'); + +['always', 'never'].forEach((namespaces) => { + + const prefix = namespaces === 'always' ? 'org_acme_bond_' : ''; + + const participantData = [{ + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alice' + }, { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_2', + name: 'Bob' + }, { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_3', + name: 'Charlie' + }, { + // $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_4', + name: 'Daria' + }]; + + describe(`Participant persisted model unit tests namespaces[${namespaces}]`, () => { + + let app; + let dataSource; + let businessNetworkConnection; + let participantRegistry; + let serializer; + + before(() => { + BrowserFS.initialize(new BrowserFS.FileSystem.InMemory()); + const adminConnection = new AdminConnection({ fs: bfs_fs }); + return adminConnection.createProfile('defaultProfile', { + type : 'embedded' + }) + .then(() => { + return adminConnection.connect('defaultProfile', 'admin', 'Xurw3yU9zI0l'); + }) + .then(() => { + const banana = fs.readFileSync(path.resolve(__dirname, 'bond-network.bna')); + return BusinessNetworkDefinition.fromArchive(banana); + }) + .then((businessNetworkDefinition) => { + serializer = businessNetworkDefinition.getSerializer(); + return adminConnection.deploy(businessNetworkDefinition); + }) + .then(() => { + app = loopback(); + const connectorSettings = { + name: 'composer', + connector: connector, + connectionProfileName: 'defaultProfile', + businessNetworkIdentifier: 'bond-network', + participantId: 'admin', + participantPwd: 'adminpw', + namespaces: namespaces, + fs: bfs_fs + }; + dataSource = app.loopback.createDataSource('composer', connectorSettings); + return new Promise((resolve, reject) => { + console.log('Discovering types from business network definition ...'); + dataSource.discoverModelDefinitions({}, (error, modelDefinitions) => { + if (error) { + return reject(error); + } + resolve(modelDefinitions); + }); + }); + }) + .then((modelDefinitions) => { + console.log('Discovered types from business network definition'); + console.log('Generating schemas for all types in business network definition ...'); + return modelDefinitions.reduce((promise, modelDefinition) => { + return promise.then((schemas) => { + return new Promise((resolve, reject) => { + dataSource.discoverSchemas(modelDefinition.name, { visited: {}, associations: true }, (error, modelSchema) => { + if (error) { + return reject(error); + } + schemas.push(modelSchema); + resolve(schemas); + }); + }); + }); + }, Promise.resolve([])); + }) + .then((modelSchemas) => { + console.log('Generated schemas for all types in business network definition'); + console.log('Adding schemas for all types to Loopback ...'); + modelSchemas.forEach((modelSchema) => { + let model = app.loopback.createModel(modelSchema); + app.model(model, { + dataSource: dataSource, + public: true + }); + }); + businessNetworkConnection = new BusinessNetworkConnection({ fs: bfs_fs }); + return businessNetworkConnection.connect('defaultProfile', 'bond-network', 'admin', 'Xurw3yU9zI0l'); + }) + .then(() => { + return businessNetworkConnection.getParticipantRegistry('org.acme.bond.Issuer'); + }) + .then((participantRegistry_) => { + participantRegistry = participantRegistry_; + return participantRegistry.addAll([ + serializer.fromJSON(participantData[0]), + serializer.fromJSON(participantData[1]) + ]); + }); + }); + + beforeEach(() => { + return Util.invokeChainCode(businessNetworkConnection.securityContext, 'resetBusinessNetwork', []) + .then(() => { + return businessNetworkConnection.getParticipantRegistry('org.acme.bond.Issuer'); + }) + .then((participantRegistry_) => { + participantRegistry = participantRegistry_; + return participantRegistry.addAll([ + serializer.fromJSON(participantData[0]), + serializer.fromJSON(participantData[1]) + ]); + }); + }); + + describe(`#count namespaces[${namespaces}]`, () => { + + it('should count all of the participants', () => { + return app.models[prefix + 'Issuer'].count() + .then((count) => { + count.should.equal(2); + }); + }); + + it('should count an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].count({ memberId: 'MEMBER_1' }) + .then((count) => { + count.should.equal(1); + }); + }); + + it('should count an non-existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].count({ memberId: 'MEMBER_999' }) + .then((count) => { + count.should.equal(0); + }); + }); + + }); + + describe(`#create namespaces[${namespaces}]`, () => { + + it('should create the specified participant', () => { + return app.models[prefix + 'Issuer'].create(participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + it('should create the specified participant without a $class property', () => { + return app.models[prefix + 'Issuer'].create(participantData[3]) + .then(() => { + return participantRegistry.get('MEMBER_4'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + delete json.$class; + json.should.deep.equal(participantData[3]); + }); + }); + + it('should return an error if the specified participant already exists', () => { + return app.models[prefix + 'Issuer'].create(participantData[0]) + .should.be.rejected; + }); + + it('should create the specified array of participants', () => { + return new Promise((resolve, reject) => { + return app.models[prefix + 'Issuer'].create([ participantData[2], participantData[3] ], (err) => { + if (err) { + return reject(err); + } + resolve(); + }); + }) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }) + .then(() => { + return participantRegistry.get('MEMBER_4'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + delete json.$class; + json.should.deep.equal(participantData[3]); + }); + }); + + }); + + describe(`#destroyAll namespaces[${namespaces}]`, () => { + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'Issuer'].destroyAll() + .should.be.rejectedWith(/is not supported/); + + }); + + it('should remove a single specified participant', () => { + return app.models[prefix + 'Issuer'].destroyAll({ memberId: 'MEMBER_1' }) + .then(() => { + return participantRegistry.exists('MEMBER_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + it('should return an error if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].destroyAll({ memberId: 'MEMBER_999' }) + .should.be.rejected; + }); + + }); + + describe(`#destroyById namespaces[${namespaces}]`, () => { + + it('should delete the specified participant', () => { + return app.models[prefix + 'Issuer'].destroyById('MEMBER_1') + .then(() => { + return participantRegistry.exists('MEMBER_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + it('should return an error if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].destroyById('MEMBER_999') + .should.be.rejected; + }); + + }); + + describe(`#exists namespaces[${namespaces}]`, () => { + + it('should check the existence of an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].exists('MEMBER_1') + .then((exists) => { + exists.should.be.true; + }); + }); + + it('should check the existence of an non-existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].exists('MEMBER_999') + .then((exists) => { + exists.should.be.false; + }); + }); + + }); + + describe(`#find namespaces[${namespaces}]`, () => { + + it('should find all existing participants', () => { + return app.models[prefix + 'Issuer'].find() + .then((participants) => { + JSON.parse(JSON.stringify(participants)).should.deep.equal([participantData[0], participantData[1]]); + }); + }); + + it('should find an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].find({ where: { memberId: 'MEMBER_1' } }) + .then((participants) => { + JSON.parse(JSON.stringify(participants)).should.deep.equal([participantData[0]]); + }); + }); + + }); + + describe(`#findById namespaces[${namespaces}]`, () => { + + it('should find an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + }); + }); + + it('should not find an non-existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_999') + .then((participant) => { + should.equal(participant, null); + }); + }); + + }); + + describe(`#findOne namespaces[${namespaces}]`, () => { + + it('should find the first of all existing participants', () => { + return app.models[prefix + 'Issuer'].findOne() + .then((participant) => { + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + }); + }); + + it('should find an existing participant using the participant ID', () => { + return app.models[prefix + 'Issuer'].findOne({ where: { memberId: 'MEMBER_1' } }) + .then((participant) => { + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + }); + }); + + }); + + describe(`#findOrCreate namespaces[${namespaces}]`, () => { + + it('should find an existing participant using the input participant', () => { + return app.models[prefix + 'Issuer'].findOrCreate(participantData[0]) + .then((parts) => { + const participant = parts[0]; + const created = parts[1]; + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + created.should.be.false; + }); + }); + + it('should find an existing participant using a where clause', () => { + return app.models[prefix + 'Issuer'].findOrCreate({ where: { memberId: 'MEMBER_1' } }, participantData[0]) + .then((parts) => { + const participant = parts[0]; + const created = parts[1]; + JSON.parse(JSON.stringify(participant)).should.deep.equal(participantData[0]); + created.should.be.false; + }); + }); + + it('should not find and create the specified participant using the input participant', () => { + return app.models[prefix + 'Issuer'].findOrCreate(participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + it('should not find and create the specified participant using a where clause', () => { + return app.models[prefix + 'Issuer'].findOrCreate({ where: { memberId: 'MEMBER_3' } }, participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + }); + + describe(`#replaceById namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should update the specified participant', () => { + return app.models[prefix + 'Issuer'].replaceById('MEMBER_1', updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should return an error if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].replaceById('MEMBER_999', updatedParticipant) + .should.be.rejected; + }); + + }); + + describe(`#replaceOrCreate namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should update the specified participant', () => { + return app.models[prefix + 'Issuer'].replaceOrCreate(updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should create a new participant if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].replaceOrCreate(participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + }); + + describe(`#updateAll namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'Issuer'].updateAll(updatedParticipant) + .should.be.rejectedWith(/is not supported/); + + }); + + it('should remove a single specified participant', () => { + return app.models[prefix + 'Issuer'].updateAll({ memberId: 'MEMBER_1' }, updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should return an error if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].updateAll({ memberId: 'MEMBER_999' }, updatedParticipant) + .should.be.rejected; + }); + + }); + + describe(`#upsert namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should update the specified participant', () => { + return app.models[prefix + 'Issuer'].upsert(updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should create a new participant if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].upsert(participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + }); + + describe(`#upsertWithWhere namespaces[${namespaces}]`, () => { + + const updatedParticipant = { + $class: 'org.acme.bond.Issuer', + memberId: 'MEMBER_1', + name: 'Alexa' + }; + + it('should throw without a where clause as it is unsupported', () => { + return app.models[prefix + 'Issuer'].upsertWithWhere({}, updatedParticipant) + .should.be.rejectedWith(/is not supported/); + + }); + + it('should update the specified participant', () => { + return app.models[prefix + 'Issuer'].upsertWithWhere({ memberId: 'MEMBER_1' }, updatedParticipant) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + it('should create a new participant if the specified participant does not exist', () => { + return app.models[prefix + 'Issuer'].upsertWithWhere({ memberId: 'MEMBER_3' }, participantData[2]) + .then(() => { + return participantRegistry.get('MEMBER_3'); + }) + .then((participant) => { + let json = serializer.toJSON(participant); + json.should.deep.equal(participantData[2]); + }); + }); + + }); + + describe(`#destroy namespaces[${namespaces}]`, () => { + + it('should delete the specified participant', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + return participant.destroy(); + }) + .then(() => { + return participantRegistry.exists('MEMBER_1'); + }) + .then((exists) => { + exists.should.be.false; + }); + }); + + }); + + describe(`#replaceAttributes namespaces[${namespaces}]`, () => { + + it('should replace attributes in the specified participant', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + return participant.replaceAttributes({ + name: 'Alexa' + }); + }) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + }); + + describe(`#updateAttribute namespaces[${namespaces}]`, () => { + + it('should replace attribute in the specified participant', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + return participant.updateAttribute('name', 'Alexa'); + }) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + }); + + describe(`#updateAttributes namespaces[${namespaces}]`, () => { + + it('should replace attributes in the specified participant', () => { + return app.models[prefix + 'Issuer'].findById('MEMBER_1') + .then((participant) => { + return participant.updateAttributes({ + name: 'Alexa' + }); + }) + .then(() => { + return participantRegistry.get('MEMBER_1'); + }) + .then((participant) => { + participant.name.should.equal('Alexa'); + }); + }); + + }); + + }); + +}); From c5fdad2d8350b6dd81f1609631beb1cd966fcd2e Mon Sep 17 00:00:00 2001 From: Liam Grace <14gracel@users.noreply.github.com> Date: Fri, 7 Jul 2017 10:12:24 +0100 Subject: [PATCH 09/88] composer-common/lib/introspect unit tests (#1507) * Added assetdeclaration unit test * composer-common/lib/introspect unit tests --- .../lib/introspect/relationshipdeclaration.js | 3 - .../parser/assetdeclaration.systypename.cto | 5 + .../classdeclaration.isrelationshiptarget.cto | 3 + .../parser/classdeclaration.noidentifier.cto | 4 + ...claration.participantwithparents.child.cto | 6 +- .../parser/eventdeclaration.systypename.cto | 5 + .../participantdeclaration.systypename.cto | 5 + .../parser/participantdeclaration.valid.cto | 5 + .../relationshipdeclaration.missingtype.cto | 6 ++ .../test/introspect/assetdeclaration.js | 11 +++ .../test/introspect/basemodelexception.js | 5 + .../test/introspect/classdeclaration.js | 48 +++++++++ .../test/introspect/eventdeclaration.js | 43 ++++++++ .../test/introspect/modelfile.js | 35 +++++++ .../test/introspect/numbervalidator.js | 12 +++ .../test/introspect/participantdeclaration.js | 97 +++++++++++++++++++ .../introspect/relationshipdeclaration.js | 58 ++++++++++- .../test/introspect/transactiondeclaration.js | 65 +++++++++++++ 18 files changed, 408 insertions(+), 8 deletions(-) create mode 100644 packages/composer-common/test/data/parser/assetdeclaration.systypename.cto create mode 100644 packages/composer-common/test/data/parser/classdeclaration.isrelationshiptarget.cto create mode 100644 packages/composer-common/test/data/parser/classdeclaration.noidentifier.cto create mode 100644 packages/composer-common/test/data/parser/eventdeclaration.systypename.cto create mode 100644 packages/composer-common/test/data/parser/participantdeclaration.systypename.cto create mode 100644 packages/composer-common/test/data/parser/participantdeclaration.valid.cto create mode 100644 packages/composer-common/test/data/parser/relationshipdeclaration.missingtype.cto create mode 100644 packages/composer-common/test/introspect/participantdeclaration.js create mode 100644 packages/composer-common/test/introspect/transactiondeclaration.js diff --git a/packages/composer-common/lib/introspect/relationshipdeclaration.js b/packages/composer-common/lib/introspect/relationshipdeclaration.js index da092b8743..eeeac24e60 100644 --- a/packages/composer-common/lib/introspect/relationshipdeclaration.js +++ b/packages/composer-common/lib/introspect/relationshipdeclaration.js @@ -77,11 +77,8 @@ class RelationshipDeclaration extends Property { throw new IllegalModelException('Relationship ' + this.getName() + ' points to a missing type ' + this.getFullyQualifiedTypeName(), classDecl.getModelFile(), this.ast.location); } - if ((namespace === ModelUtil.getSystemNamespace()) && classDecl.isEvent()) { // Transaction relationship in event, continue - } else if((namespace === ModelUtil.getSystemNamespace()) && classDeclaration.isSystemRelationshipTarget() === false) { - throw new IllegalModelException('Relationship ' + this.getName() + ' must be to an asset, participant or transaction, but is to ' + this.getFullyQualifiedTypeName(), classDecl.getModelFile(), this.ast.location); } else if(classDeclaration.isRelationshipTarget() === false) { throw new IllegalModelException('Relationship ' + this.getName() + ' must be to an asset or participant, but is to ' + this.getFullyQualifiedTypeName(), classDecl.getModelFile(), this.ast.location); } diff --git a/packages/composer-common/test/data/parser/assetdeclaration.systypename.cto b/packages/composer-common/test/data/parser/assetdeclaration.systypename.cto new file mode 100644 index 0000000000..e84793b08b --- /dev/null +++ b/packages/composer-common/test/data/parser/assetdeclaration.systypename.cto @@ -0,0 +1,5 @@ +namespace com.testing + +asset Asset identified by id { + o String id +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/classdeclaration.isrelationshiptarget.cto b/packages/composer-common/test/data/parser/classdeclaration.isrelationshiptarget.cto new file mode 100644 index 0000000000..13f26ac52b --- /dev/null +++ b/packages/composer-common/test/data/parser/classdeclaration.isrelationshiptarget.cto @@ -0,0 +1,3 @@ +namespace com.testing +concept Test { +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/classdeclaration.noidentifier.cto b/packages/composer-common/test/data/parser/classdeclaration.noidentifier.cto new file mode 100644 index 0000000000..15f988d565 --- /dev/null +++ b/packages/composer-common/test/data/parser/classdeclaration.noidentifier.cto @@ -0,0 +1,4 @@ +namespace com.testing + +transaction Transaction { +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/classdeclaration.participantwithparents.child.cto b/packages/composer-common/test/data/parser/classdeclaration.participantwithparents.child.cto index 2559bb7579..dd533027ce 100644 --- a/packages/composer-common/test/data/parser/classdeclaration.participantwithparents.child.cto +++ b/packages/composer-common/test/data/parser/classdeclaration.participantwithparents.child.cto @@ -4,4 +4,8 @@ import com.testing.parent.Super participant Sub extends Super { } -participant Sub2 extends Super { } \ No newline at end of file +participant Sub2 extends Super { } + +concept Test { + +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/eventdeclaration.systypename.cto b/packages/composer-common/test/data/parser/eventdeclaration.systypename.cto new file mode 100644 index 0000000000..0bf429402c --- /dev/null +++ b/packages/composer-common/test/data/parser/eventdeclaration.systypename.cto @@ -0,0 +1,5 @@ +namespace com.testing + +event Event identified by eventId{ + o String eventId +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/participantdeclaration.systypename.cto b/packages/composer-common/test/data/parser/participantdeclaration.systypename.cto new file mode 100644 index 0000000000..a02505dd5d --- /dev/null +++ b/packages/composer-common/test/data/parser/participantdeclaration.systypename.cto @@ -0,0 +1,5 @@ +namespace com.testing + +participant Participant identified by participantId { + o String participantId +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/participantdeclaration.valid.cto b/packages/composer-common/test/data/parser/participantdeclaration.valid.cto new file mode 100644 index 0000000000..08e43751e0 --- /dev/null +++ b/packages/composer-common/test/data/parser/participantdeclaration.valid.cto @@ -0,0 +1,5 @@ +namespace com.testing + +participant P identified by pId { + o String pId +} \ No newline at end of file diff --git a/packages/composer-common/test/data/parser/relationshipdeclaration.missingtype.cto b/packages/composer-common/test/data/parser/relationshipdeclaration.missingtype.cto new file mode 100644 index 0000000000..9f59b4937f --- /dev/null +++ b/packages/composer-common/test/data/parser/relationshipdeclaration.missingtype.cto @@ -0,0 +1,6 @@ +namespace com.testing + +asset A identified by AId { + o String AId + --> Testing test +} \ No newline at end of file diff --git a/packages/composer-common/test/introspect/assetdeclaration.js b/packages/composer-common/test/introspect/assetdeclaration.js index 92c0c961bc..1fe5cb41cc 100644 --- a/packages/composer-common/test/introspect/assetdeclaration.js +++ b/packages/composer-common/test/introspect/assetdeclaration.js @@ -14,6 +14,7 @@ 'use strict'; +const IllegalModelException = require('../../lib/introspect/illegalmodelexception'); const AssetDeclaration = require('../../lib/introspect/assetdeclaration'); const ClassDeclaration = require('../../lib/introspect/classdeclaration'); const ModelFile = require('../../lib/introspect/modelfile'); @@ -129,6 +130,16 @@ describe('AssetDeclaration', () => { asset.validate(); }).should.throw(/more than one field named/); }); + + it('should throw an an IllegalModelException if its not a System Type and is called Asset', () => { + let asset = loadLastAssetDeclaration('test/data/parser/assetdeclaration.systypename.cto'); + try { + asset.validate(); + } catch (err) { + err.should.be.an.instanceOf(IllegalModelException); + err.message.should.match(/Asset is a reserved type name./); + } + }); }); describe('#getSuperType', () => { diff --git a/packages/composer-common/test/introspect/basemodelexception.js b/packages/composer-common/test/introspect/basemodelexception.js index a58db2a41a..d2269686e9 100644 --- a/packages/composer-common/test/introspect/basemodelexception.js +++ b/packages/composer-common/test/introspect/basemodelexception.js @@ -43,6 +43,11 @@ describe('BaseModelException', function () { exc.stack.should.be.a('string'); }); + it('should use messahe over fulMessage', () => { + let exc = new BaseModelException('message', {start: 1, end: 2}); + exc.message.should.equal('message'); + }); + it('should handle a lack of support for stack traces', function () { let captureStackTrace = Error.captureStackTrace; Error.captureStackTrace = null; diff --git a/packages/composer-common/test/introspect/classdeclaration.js b/packages/composer-common/test/introspect/classdeclaration.js index 3602620920..69e2f102a3 100644 --- a/packages/composer-common/test/introspect/classdeclaration.js +++ b/packages/composer-common/test/introspect/classdeclaration.js @@ -14,6 +14,7 @@ 'use strict'; +const IllegalModelException = require('../../lib/introspect/illegalmodelexception'); const ClassDeclaration = require('../../lib/introspect/classdeclaration'); const AssetDeclaration = require('../../lib/introspect/assetdeclaration'); const EnumDeclaration = require('../../lib/introspect/enumdeclaration'); @@ -139,6 +140,17 @@ describe('ClassDeclaration', () => { }).should.throw(/Duplicate class/); }); + it('should throw when not abstract, not enum and not concept without an identifier', () => { + let asset = loadLastDeclaration('test/data/parser/classdeclaration.noidentifier.cto', TransactionDeclaration); + asset.superType = null; + try { + asset.validate(); + } catch (err) { + err.should.be.an.instanceOf(IllegalModelException); + should.exist(err.message); + err.message.should.match(/Class Transaction is not declared as abstract. It must define an identifying field./); + } + }); }); describe('#accept', () => { @@ -309,4 +321,40 @@ describe('ClassDeclaration', () => { }); }); + describe('#isEvent', () => { + const modelFileNames = [ + 'test/data/parser/classdeclaration.participantwithparents.parent.cto', + 'test/data/parser/classdeclaration.participantwithparents.child.cto' + ]; + let modelManager; + + beforeEach(() => { + modelManager = new ModelManager(); + const modelFiles = loadModelFiles(modelFileNames, modelManager); + modelManager.addModelFiles(modelFiles); + }); + it('should return false', () => { + const testClass = modelManager.getType('com.testing.child.Sub'); + testClass.isEvent().should.be.false; + + }); + }); + + describe('#isRelationshipTarget', () => { + const modelFileNames = [ + 'test/data/parser/classdeclaration.isrelationshiptarget.cto', + ]; + let modelManager; + + beforeEach(() => { + modelManager = new ModelManager(); + const modelFiles = loadModelFiles(modelFileNames, modelManager); + modelManager.addModelFiles(modelFiles); + }); + it('should return false', () => { + const testClass = modelManager.getType('com.testing.Test'); + testClass.isRelationshipTarget().should.be.false; + + }); + }); }); diff --git a/packages/composer-common/test/introspect/eventdeclaration.js b/packages/composer-common/test/introspect/eventdeclaration.js index 0509e0c22f..eb555aa9ee 100644 --- a/packages/composer-common/test/introspect/eventdeclaration.js +++ b/packages/composer-common/test/introspect/eventdeclaration.js @@ -24,10 +24,44 @@ const sinon = require('sinon'); describe('EventDeclaration', () => { + let mockModelManager; + let mockSystemEvent; + + /** + * Load an arbitrary number of model files. + * @param {String[]} modelFileNames array of model file names. + * @param {ModelManager} modelManager the model manager to which the created model files will be registered. + * @return {ModelFile[]} array of loaded model files, matching the supplied arguments. + */ + const loadModelFiles = (modelFileNames, modelManager) => { + const modelFiles = []; + for (let modelFileName of modelFileNames) { + const modelDefinitions = fs.readFileSync(modelFileName, 'utf8'); + const modelFile = new ModelFile(modelManager, modelDefinitions); + modelFiles.push(modelFile); + } + modelManager.addModelFiles(modelFiles, modelFileNames); + return modelFiles; + }; + + const loadModelFile = (modelFileName) => { + return loadModelFiles([modelFileName], mockModelManager)[0]; + }; + + const loadLastDeclaration = (modelFileName, type) => { + const modelFile = loadModelFile(modelFileName); + const declarations = modelFile.getDeclarations(type); + return declarations[declarations.length - 1]; + }; + let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); + mockModelManager = sinon.createStubInstance(ModelManager); + mockSystemEvent = sinon.createStubInstance(EventDeclaration); + mockSystemEvent.getFullyQualifiedName.returns('org.hyperledger.composer.system.Event'); + mockModelManager.getSystemTypes.returns([mockSystemEvent]); }); afterEach(() => { @@ -50,6 +84,15 @@ describe('EventDeclaration', () => { }); }); + describe('#validate', () => { + it('should throw if event is not a system type but named event', () => { + let event = loadLastDeclaration('test/data/parser/eventdeclaration.systypename.cto', EventDeclaration); + event.superType = null; + (() => { + event.validate(); + }).should.throw(/Event is a reserved type name./); + }); + }); describe('#parse', () => { diff --git a/packages/composer-common/test/introspect/modelfile.js b/packages/composer-common/test/introspect/modelfile.js index 3a47a76496..e19d538aed 100644 --- a/packages/composer-common/test/introspect/modelfile.js +++ b/packages/composer-common/test/introspect/modelfile.js @@ -466,6 +466,17 @@ describe('ModelFile', () => { mf.getType('String').should.equal('String'); }); + it('should return false if imported, non primative\'s modelFile doesn\'t exist', () => { + const ast = { + namespace: 'org.acme', + body: [ ] + }; + sandbox.stub(parser, 'parse').returns(ast); + let mf = new ModelFile(mockModelManager, 'fake'); + mf.isImportedType = () => { return true; }; + mf.resolveImport = () => { return 'org.acme'; }; + should.not.exist(mf.getType('TNTAsset')); + }); }); describe('#getAssetDeclaration', () => { @@ -542,4 +553,28 @@ describe('ModelFile', () => { }); + describe('#getFullyQualifiedTypeName', () => { + it('should return null if not prmative, imported or local type', () => { + const ast = { + namespace: 'org.acme', + body: [ ] + }; + sandbox.stub(parser, 'parse').returns(ast); + let mf = new ModelFile(mockModelManager, 'fake'); + mf.isImportedType = () => { return false; }; + mf.isLocalType = () => { return false; }; + should.not.exist(mf.getFullyQualifiedTypeName('TNTAsset')); + }); + + it('should return the type name if its a primative type', () => { + const ast = { + namespace: 'org.acme', + body: [ ] + }; + sandbox.stub(parser, 'parse').returns(ast); + let modelFile = new ModelFile(mockModelManager, 'something'); + + modelFile.getFullyQualifiedTypeName('String').should.equal('String'); + }); + }); }); diff --git a/packages/composer-common/test/introspect/numbervalidator.js b/packages/composer-common/test/introspect/numbervalidator.js index 8f00224895..4fd1425372 100644 --- a/packages/composer-common/test/introspect/numbervalidator.js +++ b/packages/composer-common/test/introspect/numbervalidator.js @@ -114,5 +114,17 @@ describe('NumberValidator', () => { v.validate('id', 101); }).should.throw(/org.acme.myField: Value is outside upper bound 101/); }); + + it('should do nothing if no value is given', () => { + let v = new NumberValidator(mockField, VALID_UPPER_AND_LOWER_BOUND_AST); + v.validate(); + }); + }); + + describe('#toString', () => { + it('should return the correct string', () => { + let v = new NumberValidator(mockField, VALID_UPPER_AND_LOWER_BOUND_AST); + v.toString().should.equal(`NumberValidator lower: ${VALID_UPPER_AND_LOWER_BOUND_AST.lower} upper: ${VALID_UPPER_AND_LOWER_BOUND_AST.upper}`); + }); }); }); diff --git a/packages/composer-common/test/introspect/participantdeclaration.js b/packages/composer-common/test/introspect/participantdeclaration.js new file mode 100644 index 0000000000..158a040626 --- /dev/null +++ b/packages/composer-common/test/introspect/participantdeclaration.js @@ -0,0 +1,97 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const ParticipantDeclaration = require('../../lib/introspect/participantdeclaration'); +const ClassDeclaration = require('../../lib/introspect/classdeclaration'); +const ModelFile = require('../../lib/introspect/modelfile'); +const ModelManager = require('../../lib/modelmanager'); +const fs = require('fs'); + +require('chai').should(); +const sinon = require('sinon'); + +describe('ParticipantDeclaration', () => { + + let mockModelManager; + let mockClassDeclaration; + let mockSystemParticipant; + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + mockModelManager = sinon.createStubInstance(ModelManager); + mockSystemParticipant = sinon.createStubInstance(ParticipantDeclaration); + mockSystemParticipant.getFullyQualifiedName.returns('org.hyperledger.composer.system.Participant'); + mockModelManager.getSystemTypes.returns([mockSystemParticipant]); + mockClassDeclaration = sinon.createStubInstance(ClassDeclaration); + mockModelManager.getType.returns(mockClassDeclaration); + mockClassDeclaration.getProperties.returns([]); + }); + + afterEach(() => { + sandbox.restore(); + }); + + let loadParticipantDeclaration = (modelFileName) => { + let modelDefinitions = fs.readFileSync(modelFileName, 'utf8'); + let modelFile = new ModelFile(mockModelManager, modelDefinitions); + let assets = modelFile.getParticipantDeclarations(); + assets.should.have.lengthOf(1); + + return assets[0]; + }; + + describe('#constructor', () => { + + it('should throw if modelFile not specified', () => { + (() => { + new ParticipantDeclaration(null, {}); + }).should.throw(/required/); + }); + + it('should throw if ast not specified', () => { + let mockModelFile = sinon.createStubInstance(ModelFile); + (() => { + new ParticipantDeclaration(mockModelFile, null); + }).should.throw(/required/); + }); + + }); + + describe('#isRelationshipTarget', () => { + it('should return true', () => { + let p = loadParticipantDeclaration('test/data/parser/participantdeclaration.valid.cto'); + p.isRelationshipTarget().should.be.true; + }); + }); + + describe('#getSystemType', () => { + it('should return Participant', () => { + let p = loadParticipantDeclaration('test/data/parser/participantdeclaration.valid.cto'); + p.getSystemType().should.equal('Participant'); + }); + }); + + describe('#validate', () => { + it('should throw error if system type and name Participant', () => { + let p = loadParticipantDeclaration('test/data/parser/participantdeclaration.systypename.cto'); + (() => { + p.validate(); + }).should.throw(/Participant is a reserved type name./); + }); + }); + +}); diff --git a/packages/composer-common/test/introspect/relationshipdeclaration.js b/packages/composer-common/test/introspect/relationshipdeclaration.js index df4c490cec..4c7e5b9ecd 100644 --- a/packages/composer-common/test/introspect/relationshipdeclaration.js +++ b/packages/composer-common/test/introspect/relationshipdeclaration.js @@ -16,6 +16,7 @@ const ModelManager = require('../../lib/modelmanager'); const sinon = require('sinon'); +const ClassDeclaration = require('../../lib/introspect/classdeclaration'); const RelationshipDeclaration = require('../../lib/introspect/relationshipdeclaration'); // const ModelUtil = require('../../lib/modelutil'); @@ -26,6 +27,7 @@ chai.use(require('chai-things')); describe('RelationshipDeclaration', function () { let modelManager; + let mockClassDeclaration; const levelOneModel = `namespace org.acme.l1 participant Person identified by ssn { @@ -38,11 +40,11 @@ describe('RelationshipDeclaration', function () { `; - before(function () { + beforeEach(function () { modelManager = new ModelManager(); - }); + mockClassDeclaration = sinon.createStubInstance(ClassDeclaration); + mockClassDeclaration.getModelFile.returns(mockClassDeclaration); - beforeEach(function () { }); afterEach(function () { @@ -60,11 +62,59 @@ describe('RelationshipDeclaration', function () { (function () { field.validate(vehicleDeclaration); }).should.throw(/Relationship must have a type/); + }); + it('should throw if relationship points to a missing type', () => { + const model = ` + namespace org.acme.l1 + participant Person identified by ssn { + o String ssn + } + `; + const model2 = ` + namespace org.acme.l2 + import org.acme.l1.* + + asset Car identified by vin { + o String vin + -->Person owner + } + `; + + modelManager.addModelFile(model); + modelManager.addModelFile(model2); + const vehicleDeclaration = modelManager.getType('org.acme.l2.Car'); + const field = vehicleDeclaration.getProperty('owner'); + (field instanceof RelationshipDeclaration).should.be.true; + modelManager.getType = () => { return null; }; + (function () { + field.validate(vehicleDeclaration); + }).should.throw(/Relationship owner points to a missing type org.acme.l1./); }); + it('should throw if relationship is not a relationship target', () => { + const model = ` + namespace org.acme.l1 + participant Person identified by ssn { + o String ssn + } + + asset Car identified by vin { + o String vin + -->Person owner + } + `; + modelManager.addModelFile(model); + const vehicleDeclaration = modelManager.getType('org.acme.l1.Car'); + const field = vehicleDeclaration.getProperty('owner'); + (field instanceof RelationshipDeclaration).should.be.true; + mockClassDeclaration.isRelationshipTarget.returns(false); + field.getParent().getModelFile().getType = () => {return mockClassDeclaration;}; - + (function () { + field.validate(vehicleDeclaration); + }).should.throw(/Relationship owner must be to an asset or participant/); + }); }); }); diff --git a/packages/composer-common/test/introspect/transactiondeclaration.js b/packages/composer-common/test/introspect/transactiondeclaration.js new file mode 100644 index 0000000000..e384e121f5 --- /dev/null +++ b/packages/composer-common/test/introspect/transactiondeclaration.js @@ -0,0 +1,65 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const TransactionDeclaration = require('../../lib/introspect/transactiondeclaration'); +const ClassDeclaration = require('../../lib/introspect/classdeclaration'); +const ModelFile = require('../../lib/introspect/modelfile'); +const ModelManager = require('../../lib/modelmanager'); + +require('chai').should(); +const sinon = require('sinon'); + +describe('TransactionDeclaration', () => { + + let mockModelManager; + let mockSystemTransaction; + let mockClassDeclaration; + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + mockModelManager = sinon.createStubInstance(ModelManager); + mockSystemTransaction = sinon.createStubInstance(TransactionDeclaration); + mockSystemTransaction.getFullyQualifiedName.returns('org.hyperledger.composer.system.Transaction'); + mockModelManager.getSystemTypes.returns([mockSystemTransaction]); + mockClassDeclaration = sinon.createStubInstance(ClassDeclaration); + mockModelManager.getType.returns(mockClassDeclaration); + mockClassDeclaration.getProperties.returns([]); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('#validate', () => { + it('should throw error name is Transaction', () => { + const model = ` + namespace com.test + + transaction Transaction identified by transactionId { + o String transactionId + }`; + + const modelFile = new ModelFile(mockModelManager, model); + const p = modelFile.getTransactionDeclarations()[0]; + + (() => { + p.validate(); + }).should.throw(/Transaction is a reserved type name./); + }); + }); + +}); From a57392815c0c1cc218e53e75564e70dfd333ad2a Mon Sep 17 00:00:00 2001 From: Liam Grace <14gracel@users.noreply.github.com> Date: Fri, 7 Jul 2017 13:47:08 +0100 Subject: [PATCH 10/88] Event validation (#1508) * fix event bug * Fixed problem where invalid events are emitted --- packages/composer-common/lib/serializer.js | 9 ++++++--- .../lib/serializer/resourcevalidator.js | 19 ++++++++++++++++++- .../basic-sample-network/models/sample.cto | 1 + .../composer-cucumber-steps/lib/composer.js | 1 - 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/composer-common/lib/serializer.js b/packages/composer-common/lib/serializer.js index 918aea9177..0de7e22ff5 100644 --- a/packages/composer-common/lib/serializer.js +++ b/packages/composer-common/lib/serializer.js @@ -82,9 +82,12 @@ class Serializer { const classDeclaration = this.modelManager.getType( resource.getFullyQualifiedType() ); // validate the resource against the model - options = options || { validate: true }; - if(options.validate === true) { - const validator = new ResourceValidator(); + options = options || {}; + if(options.validate === undefined) { + options.validate = true; + } + if(options.validate) { + const validator = new ResourceValidator(options); classDeclaration.accept(validator, parameters); } diff --git a/packages/composer-common/lib/serializer/resourcevalidator.js b/packages/composer-common/lib/serializer/resourcevalidator.js index a49350e669..65be0f974a 100644 --- a/packages/composer-common/lib/serializer/resourcevalidator.js +++ b/packages/composer-common/lib/serializer/resourcevalidator.js @@ -46,6 +46,19 @@ const Globalize = require('../globalize'); * @memberof module:composer-common */ class ResourceValidator { + + /** + * ResourceValidator constructor + * @param {Object} options - the optional serialization options. + * @param {boolean} options.validate - validate the structure of the Resource + * with its model prior to serialization (default to true) + * @param {boolean} options.convertResourcesToRelationships - Convert resources that + * are specified for relationship fields into relationships, false by default. + * @param {boolean} options.permitResourcesForRelationships - Permit resources in the + */ + constructor(options) { + this.options = options || {}; + } /** * Visitor design pattern. * @@ -370,7 +383,11 @@ class ResourceValidator { * @private */ checkRelationship(parameters, relationshipDeclaration, obj) { - if(!(obj instanceof Relationship)) { + if(obj instanceof Relationship) { + // All good.. + } else if (obj instanceof Resource && (this.options.convertResourcesToRelationships || this.options.permitResourcesForRelationships)) { + // All good.. Again + } else { ResourceValidator.reportNotRelationshipViolation(parameters.rootResourceIdentifier, relationshipDeclaration, obj); } diff --git a/packages/composer-cucumber-steps/features/basic-sample-network/models/sample.cto b/packages/composer-cucumber-steps/features/basic-sample-network/models/sample.cto index 25d8dca45a..d2f62c72a7 100644 --- a/packages/composer-cucumber-steps/features/basic-sample-network/models/sample.cto +++ b/packages/composer-cucumber-steps/features/basic-sample-network/models/sample.cto @@ -23,4 +23,5 @@ transaction SampleTransaction { event SampleEvent { o String oldValue o String newValue + --> SampleAsset asset } diff --git a/packages/composer-cucumber-steps/lib/composer.js b/packages/composer-cucumber-steps/lib/composer.js index 0f3235d613..77346a8ed2 100644 --- a/packages/composer-cucumber-steps/lib/composer.js +++ b/packages/composer-cucumber-steps/lib/composer.js @@ -475,7 +475,6 @@ class Composer { this.compareResources(actualResource, expectedResource); return true; } catch (error) { - console.log(error); return false; } }); From 1149319f988cd48555159ae84bc9e05f4e394320 Mon Sep 17 00:00:00 2001 From: "Mark S. Lewis" Date: Fri, 7 Jul 2017 14:24:45 +0100 Subject: [PATCH 11/88] PR for #796 (#1434) * Rafactor of composer-common/factory - Re-work unit tests to simplify and improve test coverage. - Refactor Factory to remove code duplication. * Add includeOptionalFields option for Factory.newXX functions * Create sample array fields with only one element in InstanceGenerator Rather than creating three elements for every generated array field value: - For sample data, create a single element array. - For empty data, create an empty array. * Generate concrete type for references to abstract types. * Refactor InstanceGenerator to remove duplicate ID generation code. * Add 'Optional Properties' checkbox to create resource and new transaction dialogs --- packages/composer-common/api.txt | 8 +- packages/composer-common/changelog.txt | 2 + .../fromcto/loopback/loopbackvisitor.js | 2 +- packages/composer-common/lib/factory.js | 94 ++++++++------ .../lib/introspect/classdeclaration.js | 14 +- .../lib/introspect/property.js | 4 +- .../lib/introspect/relationshipdeclaration.js | 2 +- packages/composer-common/lib/model/typed.js | 3 +- packages/composer-common/lib/modelutil.js | 2 +- packages/composer-common/lib/serializer.js | 6 +- .../lib/serializer/instancegenerator.js | 87 ++++++------- .../lib/serializer/jsonpopulator.js | 8 +- .../lib/serializer/valuegenerator.js | 18 +++ packages/composer-common/test/factory.js | 122 +++++++++++------- .../test/serializer/instancegenerator.js | 102 ++++++++------- .../test/serializer/valuegenerator.js | 105 ++++++++------- .../app/test/resource/resource.component.html | 6 + .../app/test/resource/resource.component.ts | 6 +- .../transaction/transaction.component.html | 6 + .../test/transaction/transaction.component.ts | 6 +- .../generators/angular/index.js | 2 +- 21 files changed, 362 insertions(+), 243 deletions(-) diff --git a/packages/composer-common/api.txt b/packages/composer-common/api.txt index 27c132ecd8..11e9c4b445 100644 --- a/packages/composer-common/api.txt +++ b/packages/composer-common/api.txt @@ -26,11 +26,11 @@ class BusinessNetworkMetadata { } class Factory { + void constructor(ModelManager) - + Resource newResource(string,string,string,Object,boolean,string) throws TypeNotFoundException - + Resource newConcept(string,string,Object,boolean,string) throws TypeNotFoundException + + Resource newResource(string,string,string,Object,boolean,string,boolean) throws TypeNotFoundException + + Resource newConcept(string,string,Object,boolean,string,boolean) throws TypeNotFoundException + Relationship newRelationship(string,string,string) throws TypeNotFoundException - + Resource newTransaction(string,string,string,Object,string) - + Resource newEvent(string,string,string,Object,string) + + Resource newTransaction(string,string,string,Object,string,boolean) + + Resource newEvent(string,string,string,Object,string,boolean) } class FileWallet extends Wallet { + string getHomeDirectory() diff --git a/packages/composer-common/changelog.txt b/packages/composer-common/changelog.txt index f9173170aa..b3f04ca16c 100644 --- a/packages/composer-common/changelog.txt +++ b/packages/composer-common/changelog.txt @@ -11,6 +11,8 @@ # # Note that the latest public API is documented using JSDocs and is available in api.txt. # +Version 0.9.2 {60327250ee4f059647020f8aee5ed67b} 2017-07-06 +- Added includeOptionalFields option to Factory.newXXX functions Version 0.9.0 {d0b9ae5179276e20604874624fe9529d} 2017-06-23 - Removed useless toJSON methods everywhere diff --git a/packages/composer-common/lib/codegen/fromcto/loopback/loopbackvisitor.js b/packages/composer-common/lib/codegen/fromcto/loopback/loopbackvisitor.js index 3aa656e46e..d43866b056 100644 --- a/packages/composer-common/lib/codegen/fromcto/loopback/loopbackvisitor.js +++ b/packages/composer-common/lib/codegen/fromcto/loopback/loopbackvisitor.js @@ -326,7 +326,7 @@ class LoopbackVisitor { // Add information from the class declaration into the composer section. if (jsonSchema.options && jsonSchema.options.composer) { - jsonSchema.options.composer.namespace = classDeclaration.getModelFile().getNamespace(); + jsonSchema.options.composer.namespace = classDeclaration.getNamespace(); jsonSchema.options.composer.name = classDeclaration.getName(); jsonSchema.options.composer.fqn = classDeclaration.getFullyQualifiedName(); } diff --git a/packages/composer-common/lib/factory.js b/packages/composer-common/lib/factory.js index a7f4e3c85d..c74ee31ba4 100644 --- a/packages/composer-common/lib/factory.js +++ b/packages/composer-common/lib/factory.js @@ -35,7 +35,6 @@ const EventDeclaration = require('./introspect/eventdeclaration'); const uuid = require('uuid'); - /** * Use the Factory to create instances of Resource: transactions, participants * and assets. @@ -68,6 +67,8 @@ class Factory { * @param {string} [options.generate] - Pass one of:
*
sample
return a resource instance with generated sample data.
*
empty
return a resource instance with empty property values.
+ * @param {boolean} [options.includeOptionalFields] - if options.generate + * is specified, whether optional fields should be generated. * @return {Resource} the new instance * @throws {TypeNotFoundException} if the type is not registered with the ModelManager */ @@ -112,29 +113,7 @@ class Factory { newObj = new ValidatedResource(this.modelManager, ns, type, id, new ResourceValidator()); } newObj.assignFieldDefaults(); - - if(options.generate) { - let generator; - let includeOptionalFields; - if ((/^empty$/i).test(options.generate)) { - generator = ValueGeneratorFactory.empty(); - includeOptionalFields = false; - } else { - generator = ValueGeneratorFactory.sample(); - includeOptionalFields = true; - } - - const visitor = new InstanceGenerator(); - const parameters = { - stack: new TypedStack(newObj), - modelManager: this.modelManager, - factory: this, - valueGenerator: generator, - includeOptionalFields: includeOptionalFields - }; - - classDecl.accept(visitor, parameters); - } + this.initializeNewObject(newObj, classDecl, options); // if we have an identifier, we set it now let idField = classDecl.getIdentifierFieldName(); @@ -153,6 +132,8 @@ class Factory { * @param {string} [options.generate] - Pass one of:
*
sample
return a resource instance with generated sample data.
*
empty
return a resource instance with empty property values.
+ * @param {boolean} [options.includeOptionalFields] - if options.generate + * is specified, whether optional fields should be generated. * @return {Resource} the new instance * @throws {TypeNotFoundException} if the type is not registered with the ModelManager */ @@ -181,18 +162,7 @@ class Factory { newObj = new ValidatedConcept(this.modelManager,ns,type, new ResourceValidator()); } newObj.assignFieldDefaults(); - - if(options.generate) { - const visitor = new InstanceGenerator(); - const generator = (/^empty$/i).test(options.generate) ? ValueGeneratorFactory.empty() : ValueGeneratorFactory.sample(); - const parameters = { - stack: new TypedStack(newObj), - modelManager: this.modelManager, - factory: this, - valueGenerator: generator - }; - classDecl.accept(visitor, parameters); - } + this.initializeNewObject(newObj, classDecl, options); debug('Factory.newResource created concept %s', classDecl.getFullyQualifiedName() ); return newObj; @@ -230,6 +200,8 @@ class Factory { * @param {string} [options.generate] - Pass one of:
*
sample
return a resource instance with generated sample data.
*
empty
return a resource instance with empty property values.
+ * @param {boolean} [options.includeOptionalFields] - if options.generate + * is specified, whether optional fields should be generated. * @return {Resource} A resource for the new transaction. */ newTransaction(ns, type, id, options) { @@ -263,6 +235,8 @@ class Factory { * @param {string} [options.generate] - Pass one of:
*
sample
return a resource instance with generated sample data.
*
empty
return a resource instance with empty property values.
+ * @param {boolean} [options.includeOptionalFields] - if options.generate + * is specified, whether optional fields should be generated. * @return {Resource} A resource for the new event. */ newEvent(ns, type, id, options) { @@ -285,6 +259,54 @@ class Factory { return event; } + /** + * PRIVATE IMPLEMENTATION. DO NOT CALL FROM OUTSIDE THIS CLASS. + * + * Initialize the state of a newly created resource + * @private + * @param {Typed} newObject - resource to initialize. + * @param {ClassDeclaration} classDeclaration - class declaration for the resource. + * @param {Object} clientOptions - field generation options supplied by the caller. + */ + initializeNewObject(newObject, classDeclaration, clientOptions) { + const generateParams = this.parseGenerateOptions(clientOptions); + if (generateParams) { + generateParams.stack = new TypedStack(newObject); + const visitor = new InstanceGenerator(); + classDeclaration.accept(visitor, generateParams); + } + } + + /** + * PRIVATE IMPLEMENTATION. DO NOT CALL FROM OUTSIDE THIS CLASS. + * + * Parse the client-supplied field generation options and return a corresponding set of InstanceGenerator + * options that can be used to initialize a resource. + * @private + * @param {Object} clientOptions - field generation options supplied by the caller. + * @return {Object} InstanceGenerator options. + */ + parseGenerateOptions(clientOptions) { + if (!clientOptions.generate) { + return null; + } + + const generateParams = { }; + generateParams.modelManager = this.modelManager; + generateParams.factory = this; + + if ((/^empty$/i).test(clientOptions.generate)) { + generateParams.valueGenerator = ValueGeneratorFactory.empty(); + } else { + // Allow any other value for backwards compatibility with previous (truthy) behavior + generateParams.valueGenerator = ValueGeneratorFactory.sample(); + } + + generateParams.includeOptionalFields = clientOptions.includeOptionalFields ? true : false; + + return generateParams; + } + } module.exports = Factory; diff --git a/packages/composer-common/lib/introspect/classdeclaration.js b/packages/composer-common/lib/introspect/classdeclaration.js index 1c4d66ddc0..fa80673402 100644 --- a/packages/composer-common/lib/introspect/classdeclaration.js +++ b/packages/composer-common/lib/introspect/classdeclaration.js @@ -231,7 +231,7 @@ class ClassDeclaration { // we now validate the field, however to ensure that // imports are resolved correctly we validate in the context // of the declared type of the field for non-primitives in a different namespace - if(field.isPrimitive() || this.isEnum() || field.getNamespace() === this.getModelFile().getNamespace() ) { + if(field.isPrimitive() || this.isEnum() || field.getNamespace() === this.getNamespace() ) { field.validate(this); } else { @@ -304,7 +304,7 @@ class ClassDeclaration { * @return {boolean} true if the class may be pointed to by a relationship */ isSystemType() { - return ModelUtil.getSystemNamespace() === this.modelFile.getNamespace(); + return ModelUtil.getSystemNamespace() === this.getNamespace(); } /** @@ -317,6 +317,14 @@ class ClassDeclaration { return this.name; } + /** + * Return the namespace of this class. + * @return {String} namespace - a namespace. + */ + getNamespace() { + return this.modelFile.getNamespace(); + } + /** * Returns the fully qualified name of this class. * The name will include the namespace if present. @@ -324,7 +332,7 @@ class ClassDeclaration { * @return {string} the fully-qualified name of this class */ getFullyQualifiedName() { - return this.modelFile.getNamespace() + '.' + this.name; + return this.getNamespace() + '.' + this.name; } /** diff --git a/packages/composer-common/lib/introspect/property.js b/packages/composer-common/lib/introspect/property.js index c5154be285..8c81c11398 100644 --- a/packages/composer-common/lib/introspect/property.js +++ b/packages/composer-common/lib/introspect/property.js @@ -156,7 +156,7 @@ class Property { * @return {string} the fully qualified name of this property */ getFullyQualifiedName() { - return this.getNamespace() + '.' + this.getParent().getName() + '.' + this.getName(); + return this.getParent().getFullyQualifiedName() + '.' + this.getName(); } /** @@ -164,7 +164,7 @@ class Property { * @return {string} the namespace of the parent of this property */ getNamespace() { - return this.getParent().getModelFile().getNamespace(); + return this.getParent().getNamespace(); } /** diff --git a/packages/composer-common/lib/introspect/relationshipdeclaration.js b/packages/composer-common/lib/introspect/relationshipdeclaration.js index eeeac24e60..28131b004c 100644 --- a/packages/composer-common/lib/introspect/relationshipdeclaration.js +++ b/packages/composer-common/lib/introspect/relationshipdeclaration.js @@ -57,7 +57,7 @@ class RelationshipDeclaration extends Property { if(ModelUtil.isPrimitiveType(this.getType())) { throw new IllegalModelException('Relationship ' + this.getName() + ' cannot be to the primitive type ' + this.getType(), classDecl.getModelFile(), this.ast.location ); } else { - let namespace = this.getParent().getModelFile().getNamespace(); + let namespace = this.getParent().getNamespace(); // we first try to get the type from our own model file // because during validate we have not yet been added to the model manager diff --git a/packages/composer-common/lib/model/typed.js b/packages/composer-common/lib/model/typed.js index fe74073c84..18e398fe87 100644 --- a/packages/composer-common/lib/model/typed.js +++ b/packages/composer-common/lib/model/typed.js @@ -15,6 +15,7 @@ 'use strict'; const Field = require('../introspect/field'); +const ModelUtil = require('../modelutil'); /** * Object is an instance with a namespace and a type. @@ -76,7 +77,7 @@ class Typed { * @return {string} The fully-qualified type name of this object */ getFullyQualifiedType() { - return this.$namespace + '.' + this.$type; + return ModelUtil.getFullyQualifiedName(this.$namespace, this.$type); } /** diff --git a/packages/composer-common/lib/modelutil.js b/packages/composer-common/lib/modelutil.js index c2bea80c83..e365aa1ce6 100644 --- a/packages/composer-common/lib/modelutil.js +++ b/packages/composer-common/lib/modelutil.js @@ -188,7 +188,7 @@ class ModelUtil { */ static getFullyQualifiedName(namespace, type) { if (namespace) { - return namespace + '.' + type; + return `${namespace}.${type}`; } else { return type; } diff --git a/packages/composer-common/lib/serializer.js b/packages/composer-common/lib/serializer.js index 0de7e22ff5..a0488b0bb8 100644 --- a/packages/composer-common/lib/serializer.js +++ b/packages/composer-common/lib/serializer.js @@ -139,15 +139,15 @@ class Serializer { // create a new instance, using the identifier field name as the ID. let resource; if (classDeclaration instanceof TransactionDeclaration) { - resource = this.factory.newTransaction( classDeclaration.getModelFile().getNamespace(), + resource = this.factory.newTransaction( classDeclaration.getNamespace(), classDeclaration.getName(), jsonObject[classDeclaration.getIdentifierFieldName()] ); } else if (classDeclaration instanceof EventDeclaration) { - resource = this.factory.newEvent( classDeclaration.getModelFile().getNamespace(), + resource = this.factory.newEvent( classDeclaration.getNamespace(), classDeclaration.getName(), jsonObject[classDeclaration.getIdentifierFieldName()] ); } else { - resource = this.factory.newResource( classDeclaration.getModelFile().getNamespace(), + resource = this.factory.newResource( classDeclaration.getNamespace(), classDeclaration.getName(), jsonObject[classDeclaration.getIdentifierFieldName()] ); } diff --git a/packages/composer-common/lib/serializer/instancegenerator.js b/packages/composer-common/lib/serializer/instancegenerator.js index 0d05c75f11..359c68edaf 100644 --- a/packages/composer-common/lib/serializer/instancegenerator.js +++ b/packages/composer-common/lib/serializer/instancegenerator.js @@ -21,7 +21,6 @@ const leftPad = require('left-pad'); const ModelUtil = require('../modelutil'); const RelationshipDeclaration = require('../introspect/relationshipdeclaration'); const Util = require('../util'); -const ValueGeneratorFactory = require('./valuegenerator'); const Globalize = require('../globalize'); /** @@ -84,11 +83,8 @@ class InstanceGenerator { */ visitField(field, parameters) { if (field.isArray()) { - let result = []; - for (let i = 0; i < 3; i++) { - result.push(this.getFieldValue(field, parameters)); - } - return result; + const valueSupplier = () => this.getFieldValue(field, parameters); + return parameters.valueGenerator.getArray(valueSupplier); } else { return this.getFieldValue(field, parameters); } @@ -102,57 +98,53 @@ class InstanceGenerator { */ getFieldValue(field, parameters) { let type = field.getFullyQualifiedTypeName(); - let valueGenerator = parameters.valueGenerator || ValueGeneratorFactory.sample(); + if (ModelUtil.isPrimitiveType(type)) { switch(type) { case 'DateTime': - return valueGenerator.getDateTime(); + return parameters.valueGenerator.getDateTime(); case 'Integer': - return valueGenerator.getInteger(); + return parameters.valueGenerator.getInteger(); case 'Long': - return valueGenerator.getLong(); + return parameters.valueGenerator.getLong(); case 'Double': - return valueGenerator.getDouble(); + return parameters.valueGenerator.getDouble(); case 'Boolean': - return valueGenerator.getBoolean(); + return parameters.valueGenerator.getBoolean(); default: - return valueGenerator.getString(); + return parameters.valueGenerator.getString(); } } + let classDeclaration = parameters.modelManager.getType(type); + if (classDeclaration instanceof EnumDeclaration) { let enumValues = classDeclaration.getOwnProperties(); - return valueGenerator.getEnum(enumValues).getName(); + return parameters.valueGenerator.getEnum(enumValues).getName(); } - if (classDeclaration.isAbstract()) { - const newClassDecl = this.findConcreteSubclass(classDeclaration); - classDeclaration = newClassDecl; - type = newClassDecl.getName(); - } + classDeclaration = this.findConcreteSubclass(classDeclaration); if (classDeclaration.isConcept()) { - let concept = parameters.factory.newConcept(classDeclaration.getModelFile().getNamespace(), classDeclaration.getName()); + let concept = parameters.factory.newConcept(classDeclaration.getNamespace(), classDeclaration.getName()); parameters.stack.push(concept); return classDeclaration.accept(this, parameters); } else { - let identifierFieldName = classDeclaration.getIdentifierFieldName(); - let idx = Math.round(Math.random() * 9999).toString(); - idx = leftPad(idx, 4, '0'); - let id = `${identifierFieldName}:${idx}`; - let resource = parameters.factory.newResource(classDeclaration.getModelFile().getNamespace(), classDeclaration.getName(), id); + const id = this.generateRandomId(classDeclaration); + let resource = parameters.factory.newResource(classDeclaration.getNamespace(), classDeclaration.getName(), id); parameters.stack.push(resource); return classDeclaration.accept(this, parameters); } } /** - * Find a type that extends the provided abstract type and return it. + * Find a concrete type that extends the provided type. If the supplied type argument is + * not abstract then it will be returned. * TODO: work out whether this has to be a leaf node or whether the closest type can be used * It depends really since the closest type will satisfy the model but whether it satisfies * any transaction code which attempts to use the generated resource is another matter. * @param {ClassDeclaration} declaration the class declaration. - * @return {ClassDeclaration} the closest extending concrete class definition - null if none are found. + * @return {ClassDeclaration} the closest extending concrete class definition. * @throws {Error} if no concrete subclasses exist. */ findConcreteSubclass(declaration) { @@ -176,33 +168,38 @@ class InstanceGenerator { /** * Visitor design pattern * @param {RelationshipDeclaration} relationshipDeclaration - the object being visited - * @param {Object} parameters - the parameter - * @return {Object} the result of visiting or null + * @param {Object} parameters - the parameter + * @return {Relationship} the result of visiting * @private */ visitRelationshipDeclaration(relationshipDeclaration, parameters) { let classDeclaration = parameters.modelManager.getType(relationshipDeclaration.getFullyQualifiedTypeName()); - let identifierFieldName = classDeclaration.getIdentifierFieldName(); - let factory = parameters.factory; + classDeclaration = this.findConcreteSubclass(classDeclaration); + const factory = parameters.factory; + const valueSupplier = () => { + const id = this.generateRandomId(classDeclaration); + return factory.newRelationship(classDeclaration.getNamespace(), classDeclaration.getName(), id); + }; if (relationshipDeclaration.isArray()) { - let result = []; - for (let i = 0; i < 3; i++) { - let idx = Math.round(Math.random() * 9999).toString(); - idx = leftPad(idx, 4, '0'); - let id = `${identifierFieldName}:${idx}`; - let relationship = factory.newRelationship(classDeclaration.getModelFile().getNamespace(), classDeclaration.getName(), id); - result.push(relationship); - } - return result; + return parameters.valueGenerator.getArray(valueSupplier); } else { - let idx = Math.round(Math.random() * 9999).toString(); - idx = leftPad(idx, 4, '0'); - let id = `${identifierFieldName}:${idx}`; - let relationship = factory.newRelationship(classDeclaration.getModelFile().getNamespace(), classDeclaration.getName(), id); - return relationship; + return valueSupplier(); } } + /** + * Generate a random ID for a given type. + * @private + * @param {ClassDeclaration} classDeclaration - class declaration for a type. + * @return {String} an ID. + */ + generateRandomId(classDeclaration) { + const prefix = classDeclaration.getIdentifierFieldName(); + let index = Math.round(Math.random() * 9999).toString(); + index = leftPad(index, 4, '0'); + return `${prefix}:${index}`; + } + } module.exports = InstanceGenerator; diff --git a/packages/composer-common/lib/serializer/jsonpopulator.js b/packages/composer-common/lib/serializer/jsonpopulator.js index e19896aaa1..e8e6c4f6a1 100644 --- a/packages/composer-common/lib/serializer/jsonpopulator.js +++ b/packages/composer-common/lib/serializer/jsonpopulator.js @@ -139,12 +139,12 @@ class JSONPopulator { // if this is identifiable, then we create a resource if(!classDeclaration.isConcept()) { - subResource = parameters.factory.newResource(classDeclaration.getModelFile().getNamespace(), + subResource = parameters.factory.newResource(classDeclaration.getNamespace(), classDeclaration.getName(), jsonItem[classDeclaration.getIdentifierFieldName()] ); } else { // otherwise we create a concept - subResource = parameters.factory.newConcept(classDeclaration.getModelFile().getNamespace(), + subResource = parameters.factory.newConcept(classDeclaration.getNamespace(), classDeclaration.getName() ); } @@ -232,7 +232,7 @@ class JSONPopulator { const classDeclaration = parameters.modelManager.getType(jsonItem.$class); // create a new instance, using the identifier field name as the ID. - let subResource = parameters.factory.newResource(classDeclaration.getModelFile().getNamespace(), + let subResource = parameters.factory.newResource(classDeclaration.getNamespace(), classDeclaration.getName(), jsonItem[classDeclaration.getIdentifierFieldName()] ); parameters.jsonStack.push(jsonItem); parameters.resourceStack.push(subResource); @@ -257,7 +257,7 @@ class JSONPopulator { const classDeclaration = parameters.modelManager.getType(jsonObj.$class); // create a new instance, using the identifier field name as the ID. - let subResource = parameters.factory.newResource(classDeclaration.getModelFile().getNamespace(), + let subResource = parameters.factory.newResource(classDeclaration.getNamespace(), classDeclaration.getName(), jsonObj[classDeclaration.getIdentifierFieldName()] ); parameters.jsonStack.push(jsonObj); parameters.resourceStack.push(subResource); diff --git a/packages/composer-common/lib/serializer/valuegenerator.js b/packages/composer-common/lib/serializer/valuegenerator.js index fc0f91dc2f..c28c8c37cc 100644 --- a/packages/composer-common/lib/serializer/valuegenerator.js +++ b/packages/composer-common/lib/serializer/valuegenerator.js @@ -107,6 +107,15 @@ class EmptyValueGenerator { getEnum(enumValues) { return enumValues[0]; } + + /** + * Get an array using the supplied callback to obtain array values. + * @param {Function} valueSupplier - callback to obtain values. + * @return {Array} an array + */ + getArray(valueSupplier) { + return [ ]; + } } /** @@ -170,6 +179,15 @@ class SampleValueGenerator extends EmptyValueGenerator { getEnum(enumValues) { return enumValues[Math.floor(Math.random() * enumValues.length)]; } + + /** + * Get an array using the supplied callback to obtain array values. + * @param {Function} valueSupplier - callback to obtain values. + * @return {Array} an array + */ + getArray(valueSupplier) { + return [ valueSupplier() ]; + } } module.exports = ValueGeneratorFactory; diff --git a/packages/composer-common/test/factory.js b/packages/composer-common/test/factory.js index a464797139..eff55cf261 100644 --- a/packages/composer-common/test/factory.js +++ b/packages/composer-common/test/factory.js @@ -22,7 +22,7 @@ const uuid = require('uuid'); const should = require('chai').should(); const sinon = require('sinon'); -describe('Factory', () => { +describe('Factory', function() { const namespace = 'org.acme.test'; const assetName = 'MyAsset'; @@ -40,6 +40,9 @@ describe('Factory', () => { concept MyConcept { o String newValue } + abstract asset AbstractAsset identified by assetId { + o String assetId + } asset MyAsset identified by assetId { o String assetId o String newValue @@ -63,88 +66,107 @@ describe('Factory', () => { sandbox.restore(); }); - describe('#newResource', () => { - it('should throw creating a new instance without an ID', () => { + describe('#newResource', function() { + it('should throw creating a new instance without an ID', function() { (() => { factory.newResource(namespace, assetName, null); }).should.throw(/Invalid or missing identifier/); }); - it('should throw creating a new instance with an ID that is just whitespace', () => { + it('should throw creating a new instance with an ID that is just whitespace', function() { (() => { factory.newResource(namespace, assetName, ' '); }).should.throw(/Missing identifier/); }); - it('should create a new instance with a specified ID', () => { - let resource = factory.newResource(namespace, assetName, 'MY_ID_1'); + it('should throw creating an abstract asset', function() { + (() => { + factory.newResource(namespace, 'AbstractAsset', 'MY_ID_1'); + }).should.throw(/AbstractAsset/); + }); + + it('should throw creating a concept', function() { + (() => { + factory.newResource(namespace, 'MyConcept', 'MY_ID_1'); + }).should.throw(/MyConcept/); + }); + + it('should create a new instance with a specified ID', function() { + const resource = factory.newResource(namespace, assetName, 'MY_ID_1'); resource.assetId.should.equal('MY_ID_1'); - should.equal(resource.newValue, undefined); - should.equal(resource.optionalValue, undefined); + }); + + it('should create a new validating instance by default', function() { + const resource = factory.newResource(namespace, assetName, 'MY_ID_1'); should.not.equal(resource.validate, undefined); }); - it('should create a new non-validating instance with a specified ID', () => { - let resource = factory.newResource(namespace, assetName, 'MY_ID_1', { disableValidation: true }); - resource.assetId.should.equal('MY_ID_1'); - should.equal(resource.newValue, undefined); - should.equal(resource.optionalValue, undefined); + it('should create a new non-validating instance', function() { + const resource = factory.newResource(namespace, assetName, 'MY_ID_1', { disableValidation: true }); should.equal(resource.validate, undefined); }); - it('should create a new instance with a specified ID and generated empty data', () => { - let resource = factory.newResource(namespace, assetName, 'MY_ID_1', { generate: 'empty' }); - resource.assetId.should.equal('MY_ID_1'); - resource.newValue.should.be.a('String'); - resource.newValue.length.should.equal(0); - should.equal(resource.optionalValue, undefined); - should.not.equal(resource.validate, undefined); + it('should not define fields if \'generate\' option is not set', function() { + const resource = factory.newResource(namespace, assetName, 'MY_ID_1'); + should.equal(resource.newValue, undefined); }); - const validateSampleData = (resource) => { - resource.assetId.should.equal('MY_ID_1'); + it('should generate empty field values', function() { + const resource = factory.newResource(namespace, assetName, 'MY_ID_1', { generate: 'empty' }); + assertEmptyFieldValues(resource); + }); + + const assertEmptyFieldValues = function(resource) { + should.not.equal(resource.newValue, undefined); resource.newValue.should.be.a('String'); - resource.newValue.length.should.not.equal(0); - resource.optionalValue.should.be.a('String'); - resource.optionalValue.length.should.not.equal(0); - should.not.equal(resource.validate, undefined); + resource.newValue.length.should.equal(0); }; - it('should create a new instance with a specified ID and generated sample data', () => { + it('should generate sample field values', function() { const resource = factory.newResource(namespace, assetName, 'MY_ID_1', { generate: 'sample' }); - validateSampleData(resource); + assertSampleFieldValues(resource); }); - it('should generate sample data if \'generate\' option is a boolean', () => { + it('should generate sample field values if \'generate\' option is a boolean', function() { const resource = factory.newResource(namespace, assetName, 'MY_ID_1', { generate: true }); - validateSampleData(resource); + assertSampleFieldValues(resource); }); - }); + const assertSampleFieldValues = function(resource) { + should.not.equal(resource.newValue, undefined); + resource.newValue.should.be.a('String'); + resource.newValue.length.should.not.equal(0); + }; - describe('#newResource', () => { + it('should not define optional fields with generated empty data if includeOptionalFields not specified', function() { + const resource = factory.newResource(namespace, assetName, 'MY_ID_1', { generate: 'empty' }); + assertOptionalNotDefined(resource); + }); - it('should create a new instance with a specified ID', () => { - let resource = factory.newResource(namespace, assetName, 'MY_ID_1'); - resource.assetId.should.equal('MY_ID_1'); - should.equal(resource.newValue, undefined); - should.not.equal(resource.validate, undefined); + it('should not define optional fields with generated sample data if includeOptionalFields not specified', function() { + const resource = factory.newResource(namespace, assetName, 'MY_ID_1', { generate: 'sample' }); + assertOptionalNotDefined(resource); }); - it('should create a new non-validating instance with a specified ID', () => { - let resource = factory.newResource(namespace, assetName, 'MY_ID_1', { disableValidation: true }); - resource.assetId.should.equal('MY_ID_1'); - should.equal(resource.newValue, undefined); - should.equal(resource.validate, undefined); + const assertOptionalNotDefined = function(resource) { + should.equal(resource.optionalValue, undefined); + }; + + it ('should define optional fields with generated empty data if includeOptionalFields is specified', function() { + const resource = factory.newResource(namespace, assetName, 'MY_ID_1', { generate: 'empty', includeOptionalFields: true }); + assertOptionalIsDefined(resource); }); - it('should create a new instance with a specified ID and generated data', () => { - let resource = factory.newResource(namespace, assetName, 'MY_ID_1', { generate: true }); - resource.assetId.should.equal('MY_ID_1'); - resource.newValue.should.be.a('string'); - should.not.equal(resource.validate, undefined); + it ('should define optional fields with generated sample data if includeOptionalFields is specified', function() { + const resource = factory.newResource(namespace, assetName, 'MY_ID_1', { generate: 'sample', includeOptionalFields: true }); + assertOptionalIsDefined(resource); }); + const assertOptionalIsDefined = function(resource) { + should.not.equal(resource.optionalValue, undefined); + resource.optionalValue.should.be.a('String'); + }; + }); describe('#newConcept', () => { @@ -167,6 +189,12 @@ describe('Factory', () => { }).should.throw(/Cannot instantiate Abstract Type AbstractConcept in namespace org.acme.test/); }); + it('should throw creating a non-concept', function() { + (() => { + factory.newConcept(namespace, assetName); + }).should.throw(new RegExp(assetName)); + }); + it('should create a new concept', () => { let resource = factory.newConcept(namespace, 'MyConcept'); should.equal(resource.newValue, undefined); diff --git a/packages/composer-common/test/serializer/instancegenerator.js b/packages/composer-common/test/serializer/instancegenerator.js index 04216f8b8a..454020a1c3 100644 --- a/packages/composer-common/test/serializer/instancegenerator.js +++ b/packages/composer-common/test/serializer/instancegenerator.js @@ -18,30 +18,38 @@ const Factory = require('../../lib/factory'); const InstanceGenerator = require('../../lib/serializer/instancegenerator'); const ModelManager = require('../../lib/modelmanager'); const TypedStack = require('../../lib/serializer/typedstack'); +const ValueGenerator = require('../../lib/serializer/valuegenerator'); const chai = require('chai'); const should = chai.should(); describe('InstanceGenerator', () => { - let factory; let modelManager; let parameters; let visitor; + const useEmptyGenerator = () => { + parameters.valueGenerator = ValueGenerator.empty(); + }; + + const useSampleGenerator = () => { + parameters.valueGenerator = ValueGenerator.sample(); + }; + beforeEach(() => { modelManager = new ModelManager(); factory = new Factory(modelManager); parameters = { modelManager: modelManager, - factory: factory + factory: factory, }; + useSampleGenerator(); visitor = new InstanceGenerator(); }); - let test = (modelFile, additionalParams) => { + const test = (modelFile) => { modelManager.addModelFile(modelFile); - Object.assign(parameters, additionalParams); let resource = factory.newResource('org.acme.test', 'MyAsset', 'asset1'); parameters.stack = new TypedStack(resource); let classDeclaration = resource.getClassDeclaration(); @@ -65,16 +73,25 @@ describe('InstanceGenerator', () => { resource.theValue.should.be.a('string'); }); - it('should generate a default value for a string array property', () => { + it('should generate empty array value for array property with empty generator ', () => { + useEmptyGenerator(); + let resource = test(`namespace org.acme.test + asset MyAsset identified by assetId { + o String assetId + o String[] theValues + }`); + resource.theValues.should.be.a('Array').that.is.empty; + }); + + it('should generate one default value for a string array property with sample generator ', () => { + useSampleGenerator(); let resource = test(`namespace org.acme.test asset MyAsset identified by assetId { o String assetId o String[] theValues }`); - resource.theValues.should.have.lengthOf(3); - resource.theValues[0].should.be.a('string'); - resource.theValues[1].should.be.a('string'); - resource.theValues[2].should.be.a('string'); + resource.theValues.should.be.a('Array').and.have.lengthOf(1); + resource.theValues[0].should.be.a('String'); }); it('should generate a default value for a date/time property', () => { @@ -86,16 +103,14 @@ describe('InstanceGenerator', () => { resource.theValue.should.be.an.instanceOf(Date); }); - it('should generate a default value for a date/time array property', () => { + it('should generate one default value for a date/time array property', () => { let resource = test(`namespace org.acme.test asset MyAsset identified by assetId { o String assetId o DateTime[] theValues }`); - resource.theValues.should.have.lengthOf(3); - resource.theValues[0].should.be.an.instanceOf(Date); - resource.theValues[1].should.be.an.instanceOf(Date); - resource.theValues[2].should.be.an.instanceOf(Date); + resource.theValues.should.be.a('Array').and.have.lengthOf(1); + resource.theValues[0].should.be.a('Date'); }); it('should generate a default value for an integer property', () => { @@ -113,10 +128,8 @@ describe('InstanceGenerator', () => { o String assetId o Integer[] theValues }`); - resource.theValues.should.have.lengthOf(3); - resource.theValues[0].should.be.a('number'); - resource.theValues[1].should.be.a('number'); - resource.theValues[2].should.be.a('number'); + resource.theValues.should.be.a('Array').and.have.lengthOf(1); + resource.theValues[0].should.be.a('Number'); }); it('should generate a default value for a long property', () => { @@ -134,10 +147,8 @@ describe('InstanceGenerator', () => { o String assetId o Long[] theValues }`); - resource.theValues.should.have.lengthOf(3); - resource.theValues[0].should.be.a('number'); - resource.theValues[1].should.be.a('number'); - resource.theValues[2].should.be.a('number'); + resource.theValues.should.be.a('Array').and.have.lengthOf(1); + resource.theValues[0].should.be.a('Number'); }); it('should generate a default value for a double property', () => { @@ -155,10 +166,8 @@ describe('InstanceGenerator', () => { o String assetId o Double[] theValues }`); - resource.theValues.should.have.lengthOf(3); - resource.theValues[0].should.be.a('number'); - resource.theValues[1].should.be.a('number'); - resource.theValues[2].should.be.a('number'); + resource.theValues.should.be.a('Array').and.have.lengthOf(1); + resource.theValues[0].should.be.a('Number'); }); it('should generate a default value for a boolean property', () => { @@ -176,10 +185,8 @@ describe('InstanceGenerator', () => { o String assetId o Boolean[] theValues }`); - resource.theValues.should.have.lengthOf(3); - resource.theValues[0].should.be.a('boolean'); - resource.theValues[1].should.be.a('boolean'); - resource.theValues[2].should.be.a('boolean'); + resource.theValues.should.be.a('Array').and.have.lengthOf(1); + resource.theValues[0].should.be.a('Boolean'); }); it('should generate a default value for an enum property', () => { @@ -207,10 +214,8 @@ describe('InstanceGenerator', () => { o String assetId o MyEnum[] theValues }`); - resource.theValues.should.have.lengthOf(3); + resource.theValues.should.be.a('Array').and.have.lengthOf(1); resource.theValues[0].should.be.oneOf(['ENUM_VAL1', 'ENUM_VAL2', 'ENUM_VAL3']); - resource.theValues[1].should.be.oneOf(['ENUM_VAL1', 'ENUM_VAL2', 'ENUM_VAL3']); - resource.theValues[2].should.be.oneOf(['ENUM_VAL1', 'ENUM_VAL2', 'ENUM_VAL3']); }); it('should generate a default value for a relationship property', () => { @@ -228,10 +233,8 @@ describe('InstanceGenerator', () => { o String assetId --> MyAsset[] theValues }`); - resource.theValues.should.have.lengthOf(3); + resource.theValues.should.be.a('Array').and.have.lengthOf(1); resource.theValues[0].getIdentifier().should.match(/^assetId:\d{4}$/); - resource.theValues[1].getIdentifier().should.match(/^assetId:\d{4}$/); - resource.theValues[2].getIdentifier().should.match(/^assetId:\d{4}$/); }); it('should generate a default value for a resource property', () => { @@ -258,13 +261,9 @@ describe('InstanceGenerator', () => { o String assetId o MyInnerAsset[] theValues }`); - resource.theValues.should.have.lengthOf(3); + resource.theValues.should.be.a('Array').and.have.lengthOf(1); resource.theValues[0].getIdentifier().should.match(/^innerAssetId:\d{4}$/); resource.theValues[0].theValue.should.be.a('string'); - resource.theValues[1].getIdentifier().should.match(/^innerAssetId:\d{4}$/); - resource.theValues[1].theValue.should.be.a('string'); - resource.theValues[2].getIdentifier().should.match(/^innerAssetId:\d{4}$/); - resource.theValues[2].theValue.should.be.a('string'); }); it('should generate a default value for base class properties', () => { @@ -290,7 +289,7 @@ describe('InstanceGenerator', () => { o String id o BaseConcept aConcept }`); - resource.aConcept.$type.should.match(/^MyConcept$/); + resource.aConcept.getType().should.equal('MyConcept'); }); it('should throw an error when trying to generate a resource from a model that uses an Abstract type with no concrete Implementing type', () => { @@ -309,23 +308,34 @@ describe('InstanceGenerator', () => { }); it('should not generate default value for optional property if not requested', () => { - let resource = test(`namespace org.acme.test + parameters.includeOptionalFields = false; + const resource = test(`namespace org.acme.test asset MyAsset identified by assetId { o String assetId o String theValue optional - }`, { includeOptionalFields: false }); + }`); should.equal(resource.theValue, undefined); }); it('should generate default value for optional property if requested', () => { - let resource = test(`namespace org.acme.test + parameters.includeOptionalFields = true; + const resource = test(`namespace org.acme.test asset MyAsset identified by assetId { o String assetId o String theValue optional - }`, { includeOptionalFields: true }); + }`); resource.theValue.should.be.a('String'); }); + it('should generate concrete subclass for abstract reference', function() { + let resource = test(`namespace org.acme.test + asset MyAsset identified by id { + o String id + --> Asset theValue + }`); + resource.theValue.getType().should.equal('MyAsset'); + }); + }); }); diff --git a/packages/composer-common/test/serializer/valuegenerator.js b/packages/composer-common/test/serializer/valuegenerator.js index 8f0bae26dc..653d17af5a 100644 --- a/packages/composer-common/test/serializer/valuegenerator.js +++ b/packages/composer-common/test/serializer/valuegenerator.js @@ -19,66 +19,79 @@ const ValueGeneratorFactory = require('../../lib/serializer/valuegenerator'); const chai = require('chai'); const expect = chai.expect; -describe('ValueGenerator', () => { - - /** Array of names of the static factory methods for obtaining ValueGenerator objects. */ - let generatorNames; - - /** - * Check that invoking the supplied function name on all of the ValueGenerator implementations returns the expected type. - * @param {string} functionName name of the function to invoke. - * @param {string} type expected return type. - */ - let assertFunctionReturnsType = (functionName, type) => { - generatorNames.forEach((generatorName) => { - const generator = ValueGeneratorFactory[generatorName](); - const returnValue = generator[functionName](); - expect(returnValue, generatorName + '.' + functionName + '() should return a ' + type) - .to.be.a(type); - }); - }; +describe('ValueGenerator', function() { - before(() => { - generatorNames = Object.getOwnPropertyNames(ValueGeneratorFactory) + describe('Consistent return types', function() { + /** Array of names of the static factory methods for obtaining ValueGenerator objects. */ + const generatorNames = Object.getOwnPropertyNames(ValueGeneratorFactory) .filter((p) => { return typeof ValueGeneratorFactory[p] === 'function'; }); - }); - it('getDateTime should return a Date', () => { - assertFunctionReturnsType('getDateTime', 'Date'); - }); + /** + * Check that invoking the supplied function name on all of the ValueGenerator implementations returns the expected type. + * @param {string} functionName name of the function to invoke. + * @param {string} type expected return type. + */ + const assertFunctionReturnsType = (functionName, type) => { + generatorNames.forEach((generatorName) => { + const generator = ValueGeneratorFactory[generatorName](); + const returnValue = generator[functionName](); + expect(returnValue, generatorName + '.' + functionName + '() should return a ' + type) + .to.be.a(type); + }); + }; - it('getInteger should return a number', () => { - assertFunctionReturnsType('getInteger', 'number'); - }); + it('getDateTime should return a Date', function() { + assertFunctionReturnsType('getDateTime', 'Date'); + }); - it('getLong should return a number', () => { - assertFunctionReturnsType('getLong', 'number'); - }); + it('getInteger should return a number', function() { + assertFunctionReturnsType('getInteger', 'number'); + }); - it('getDouble should return a number', () => { - assertFunctionReturnsType('getDouble', 'number'); - }); + it('getLong should return a number', function() { + assertFunctionReturnsType('getLong', 'number'); + }); - it('getBoolean should return a boolean', () => { - assertFunctionReturnsType('getBoolean', 'boolean'); - }); + it('getDouble should return a number', function() { + assertFunctionReturnsType('getDouble', 'number'); + }); + + it('getBoolean should return a boolean', function() { + assertFunctionReturnsType('getBoolean', 'boolean'); + }); - it('getString should return a string', () => { - assertFunctionReturnsType('getString', 'string'); + it('getString should return a string', function() { + assertFunctionReturnsType('getString', 'string'); + }); }); - it('EmptyValueGenerator.getEnum should return the first value', () => { - const inputs = ['One', 'Two', 'Three']; - const output = ValueGeneratorFactory.empty().getEnum(inputs); - expect(output).to.equal(inputs[0]); + describe('EmptyValueGenerator', function() { + it('getEnum should return the first value', function() { + const inputs = ['One', 'Two', 'Three']; + const output = ValueGeneratorFactory.empty().getEnum(inputs); + expect(output).to.equal(inputs[0]); + }); + + it('getArray should return empty array', function() { + const output = ValueGeneratorFactory.empty().getArray(() => ''); + expect(output).to.be.a('Array').that.is.empty; + }); }); - it('SampleValueGenerator.getEnum should return one of the input values', () => { - const inputs = ['One', 'Two', 'Three']; - const output = ValueGeneratorFactory.sample().getEnum(inputs); - expect(inputs).to.include(output); + describe('SampleValueGenerator', function() { + it('getEnum should return one of the input values', function() { + const inputs = ['One', 'Two', 'Three']; + const output = ValueGeneratorFactory.sample().getEnum(inputs); + expect(inputs).to.include(output); + }); + + it('getArray should return array with one element obtained from callback', function() { + const value = 'TEST_VALUE'; + const output = ValueGeneratorFactory.sample().getArray(() => value); + expect(output).to.be.a('Array').and.deep.equal([value]); + }); }); }); diff --git a/packages/composer-playground/src/app/test/resource/resource.component.html b/packages/composer-playground/src/app/test/resource/resource.component.html index 01658c4c0d..bbed7e8998 100644 --- a/packages/composer-playground/src/app/test/resource/resource.component.html +++ b/packages/composer-playground/src/app/test/resource/resource.component.html @@ -18,6 +18,12 @@

In registry: {{registryID}}

(ngModelChange)="onDefinitionChanged()" width="100%" height="100%" ngDefaultControl> +
+ +

{{definitionError}}

diff --git a/packages/composer-playground/src/app/test/resource/resource.component.ts b/packages/composer-playground/src/app/test/resource/resource.component.ts index 9940d430f4..e35da6e176 100644 --- a/packages/composer-playground/src/app/test/resource/resource.component.ts +++ b/packages/composer-playground/src/app/test/resource/resource.component.ts @@ -37,6 +37,7 @@ export class ResourceComponent implements OnInit { private resourceDeclaration: ClassDeclaration = null; private actionInProgress: boolean = false; private definitionError: string = null; + private includeOptionalFields: boolean = false; private codeConfig = { lineNumbers: true, @@ -118,7 +119,10 @@ export class ResourceComponent implements OnInit { idx = leftPad(idx, 4, '0'); let id = `${this.resourceDeclaration.getIdentifierFieldName()}:${idx}`; try { - const generateParameters = {generate: withSampleData ? 'sample' : 'empty'}; + const generateParameters = { + generate: withSampleData ? 'sample' : 'empty', + includeOptionalFields: this.includeOptionalFields + }; let resource = factory.newResource( this.resourceDeclaration.getModelFile().getNamespace(), this.resourceDeclaration.getName(), diff --git a/packages/composer-playground/src/app/test/transaction/transaction.component.html b/packages/composer-playground/src/app/test/transaction/transaction.component.html index 3b6f129d0d..4d71d417ef 100644 --- a/packages/composer-playground/src/app/test/transaction/transaction.component.html +++ b/packages/composer-playground/src/app/test/transaction/transaction.component.html @@ -33,6 +33,12 @@

Submit Transaction

(ngModelChange)="onDefinitionChanged()" width="100%" height="100%" ngDefaultControl> +
+ +

{{definitionError}}

diff --git a/packages/composer-playground/src/app/test/transaction/transaction.component.ts b/packages/composer-playground/src/app/test/transaction/transaction.component.ts index 436bb6b1c9..68b56e0616 100644 --- a/packages/composer-playground/src/app/test/transaction/transaction.component.ts +++ b/packages/composer-playground/src/app/test/transaction/transaction.component.ts @@ -30,6 +30,7 @@ export class TransactionComponent implements OnInit { private selectedTransactionName: string = null; private hiddenTransactionItems = new Map(); private submittedTransaction = null; + private includeOptionalFields: boolean = false; private resourceDefinition: string = null; private submitInProgress: boolean = false; @@ -121,7 +122,10 @@ export class TransactionComponent implements OnInit { private generateTransactionDeclaration(withSampleData?: boolean): void { let businessNetworkDefinition = this.clientService.getBusinessNetwork(); let factory = businessNetworkDefinition.getFactory(); - const generateParameters = {generate: withSampleData ? 'sample' : 'empty'}; + const generateParameters = { + generate: withSampleData ? 'sample' : 'empty', + includeOptionalFields: this.includeOptionalFields + }; let resource = factory.newTransaction( this.selectedTransaction.getModelFile().getNamespace(), this.selectedTransaction.getName(), diff --git a/packages/generator-hyperledger-composer/generators/angular/index.js b/packages/generator-hyperledger-composer/generators/angular/index.js index d3b3ee8f4a..33a7fe8cc7 100755 --- a/packages/generator-hyperledger-composer/generators/angular/index.js +++ b/packages/generator-hyperledger-composer/generators/angular/index.js @@ -445,7 +445,7 @@ module.exports = yeoman.Base.extend({ assetList.push({ 'name': asset.name, - 'namespace': asset.getModelFile().getNamespace(), + 'namespace': asset.getNamespace(), 'properties': tempList, 'identifier': asset.getIdentifierFieldName() }); From e9be2ab079322c99c4cf2f34ef38715a398b7eb0 Mon Sep 17 00:00:00 2001 From: Liam Grace <14gracel@users.noreply.github.com> Date: Mon, 10 Jul 2017 15:44:45 +0100 Subject: [PATCH 12/88] Added svg transformation when showing event (#1512) --- .../test/view-transaction/view-transaction.component.html | 2 +- .../test/view-transaction/view-transaction.component.scss | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.html b/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.html index cb852927e4..4b93531afd 100644 --- a/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.html +++ b/packages/composer-playground/src/app/test/view-transaction/view-transaction.component.html @@ -25,7 +25,7 @@

Transaction Data

-