diff --git a/.eslintrc b/.eslintrc index 8a8e1ca..9873a66 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,6 +8,10 @@ "localStorage": true }, "extends": "eslint-config-airbnb", + "plugins": ["eslint-plugin-jest"], + "env": { + "jest/globals": true + }, "rules": { "import/extensions": 0, "import/no-extraneous-dependencies": 0, @@ -24,6 +28,11 @@ "class-methods-use-this": 0, "no-param-reassign": 0, "no-loop-func": 0, - "global-require": 0 + "global-require": 0, + "jest/no-disabled-tests": "warn", + "jest/no-focused-tests": "error", + "jest/no-identical-title": "error", + "jest/prefer-to-have-length": "warn", + "jest/valid-expect": "error" } } diff --git a/README.md b/README.md index 2f8be44..d2b4e10 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,14 @@ and aims to help people use and manage fabric networks easily. ## Releases -- [package](https://github.com/blockchain-desktop/hyperledger-fabric-desktop/releases) +- [released app](https://github.com/blockchain-desktop/hyperledger-fabric-desktop/releases) - [source code](https://github.com/blockchain-desktop/hyperledger-fabric-desktop) ## Documentation, Getting Started and Develop Guideline -It is highly recommended to read [index.md](doc/doc-En/index-En.md) first. +It is highly recommended to read [index.md](doc/doc-En/index-En.md) first. + +You can also watch the [introduction video](https://wiki.hyperledger.org/download/attachments/2392116/20190228_Fabric_Desktop.mp4?version=1&modificationDate=1551429922000&api=v2). ## Contributing @@ -76,7 +78,10 @@ available at http://creativecommons.org/licenses/by/4.0/. * [使用者:功能介绍](doc/doc-Ch/tutorial-Ch.md) * [开发者:参与开发](doc/doc-Ch/coding-guidelines-Ch.md) * [开发者:贡献代码](doc/doc-Ch/CONTRIBUTING-Ch.md) -* [项目架构](./doc/architect.md) +* [项目架构](./doc/architect.md) + +介绍视频 +* [超级账本meetup录像](https://pan.baidu.com/s/1VU_bt4Oo_OJtk5DhCZ3L4A) ## 如何参与? diff --git a/fabric/v1.1/basic-network/teardown.sh b/fabric/v1.1/basic-network/teardown.sh index 7036793..f115207 100755 --- a/fabric/v1.1/basic-network/teardown.sh +++ b/fabric/v1.1/basic-network/teardown.sh @@ -14,6 +14,6 @@ docker-compose -f docker-compose.yml kill && docker-compose -f docker-compose.ym rm -f ~/.hfc-key-store/* # remove chaincode docker images -docker rmi $(docker images dev-* -q) +# docker rmi $(docker images dev-* -q) # Your system is now clean diff --git a/package.json b/package.json index bfec443..ae5c084 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,8 @@ "eslint-plugin-import": "^2", "eslint-plugin-jsx-a11y": "^5", "eslint-plugin-react": "^7", + "@types/jest": "^23.3.13", + "eslint-plugin-jest": "^22.3.0", "jest": "^23.6.0" } } diff --git a/src/util/fabric.js b/src/util/fabric.js index d3002d9..e74fe2d 100644 --- a/src/util/fabric.js +++ b/src/util/fabric.js @@ -18,10 +18,11 @@ class FabricClient { this.fabric_client = fabricClient; } - _gitConfig() { + // 抽出空挡,插入配置文件,以便集成测试 + _getConfig(configDb) { const self = this; return new Promise((resolve, reject) => { - db.find({}, (err, resultList) => { + configDb.find({}, (err, resultList) => { if (err) { logger.info('the operation of find documents failed!'); reject('error'); @@ -117,6 +118,7 @@ class FabricClient { } this.channels[channelName] = channel; } else { + logger.info(`channel(${channelName}) exists, get it from memory.`); channel = this.channels[channelName]; } return channel; @@ -197,9 +199,9 @@ class FabricClient { const self = this; return this._enrollUser(this).then((user) => { if (user && user.isEnrolled()) { - logger.info('Successfully loaded user1 from persistence'); + logger.info(`Successfully loaded user(${user.getName()}) from persistence`); } else { - logger.error('Failed to get user1.... run registerUser.js'); + logger.error('Failed to get user run registerUser.js'); return Promise.reject(new Error('Failed to get user1.... run registerUser.js')); } @@ -773,12 +775,11 @@ class FabricClient { let __fabricClient; -// FabricClient单例模式。后续考虑优化为多套身份,多个client -export default function getFabricClientSingleton() { +export function getFabricClientSingletonHelper(dbConfig) { if (!__fabricClient) { - logger.info('strat create fabric client'); + logger.info('instantiating fabric client.'); __fabricClient = new FabricClient(); - return __fabricClient._gitConfig() + return __fabricClient._getConfig(dbConfig) .then(__fabricClient._config) .then(__fabricClient._enrollUser) .then(() => Promise.resolve(__fabricClient)); @@ -786,6 +787,14 @@ export default function getFabricClientSingleton() { return Promise.resolve(__fabricClient); } +// TODO: 考虑是否去除export default,全部使用export。 +// 由此保证import无需再区分 import something from 'lib' 与 import {something} from 'lib' + +// FabricClient单例模式。后续考虑优化为多套身份,多个client +export default function getFabricClientSingleton() { + return getFabricClientSingletonHelper(db); +} + export function deleteFabricClientSingleton() { __fabricClient = null; } diff --git a/test/resources/key/users/Org1MSP b/test/resources/key/users/Org1MSP new file mode 100644 index 0000000..b6081c7 --- /dev/null +++ b/test/resources/key/users/Org1MSP @@ -0,0 +1 @@ +{"name":"Org1MSP","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"cd96d5260ad4757551ed4a5a991e62130f8008a0bf996e4e4b84cd097a747fec","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICGDCCAb+gAwIBAgIQFSxnLAGsu04zrFkAEwzn6zAKBggqhkjOPQQDAjBzMQsw\nCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy\nYW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEcMBoGA1UEAxMTY2Eu\nb3JnMS5leGFtcGxlLmNvbTAeFw0xNzA4MzEwOTE0MzJaFw0yNzA4MjkwOTE0MzJa\nMFsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T\nYW4gRnJhbmNpc2NvMR8wHQYDVQQDDBZBZG1pbkBvcmcxLmV4YW1wbGUuY29tMFkw\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV1dfmKxsFKWo7o6DNBIaIVebCCPAM9C/\nsLBt4pJRre9pWE987DjXZoZ3glc4+DoPMtTmBRqbPVwYcUvpbYY8p6NNMEswDgYD\nVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwKwYDVR0jBCQwIoAgQjmqDc122u64\nugzacBhR0UUE0xqtGy3d26xqVzZeSXwwCgYIKoZIzj0EAwIDRwAwRAIgXMy26AEU\n/GUMPfCMs/nQjQME1ZxBHAYZtKEuRR361JsCIEg9BOZdIoioRivJC+ZUzvJUnkXu\no2HkWiuxLsibGxtE\n-----END CERTIFICATE-----\n"}}} \ No newline at end of file diff --git a/test/resources/key/users/cd96d5260ad4757551ed4a5a991e62130f8008a0bf996e4e4b84cd097a747fec-priv b/test/resources/key/users/cd96d5260ad4757551ed4a5a991e62130f8008a0bf996e4e4b84cd097a747fec-priv new file mode 100644 index 0000000..fef217d --- /dev/null +++ b/test/resources/key/users/cd96d5260ad4757551ed4a5a991e62130f8008a0bf996e4e4b84cd097a747fec-priv @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgRgQr347ij6cjwX7m +KjzbbD8Tlwdfu6FaubjWJWLGyqahRANCAARXV1+YrGwUpajujoM0EhohV5sII8Az +0L+wsG3iklGt72lYT3zsONdmhneCVzj4Og8y1OYFGps9XBhxS+lthjyn +-----END PRIVATE KEY----- diff --git a/test/resources/key/users/cd96d5260ad4757551ed4a5a991e62130f8008a0bf996e4e4b84cd097a747fec-pub b/test/resources/key/users/cd96d5260ad4757551ed4a5a991e62130f8008a0bf996e4e4b84cd097a747fec-pub new file mode 100644 index 0000000..d1ebfea --- /dev/null +++ b/test/resources/key/users/cd96d5260ad4757551ed4a5a991e62130f8008a0bf996e4e4b84cd097a747fec-pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEV1dfmKxsFKWo7o6DNBIaIVebCCPA +M9C/sLBt4pJRre9pWE987DjXZoZ3glc4+DoPMtTmBRqbPVwYcUvpbYY8pw== +-----END PUBLIC KEY----- diff --git a/test/resources/persistence/.gitkeep b/test/resources/persistence/.gitkeep new file mode 100644 index 0000000..a10d4fe --- /dev/null +++ b/test/resources/persistence/.gitkeep @@ -0,0 +1,3 @@ +# Ignore everything in this directory +* +# Except this file !.gitkeep \ No newline at end of file diff --git a/test/resources/persistence/block.db b/test/resources/persistence/block.db new file mode 100644 index 0000000..e69de29 diff --git a/test/resources/persistence/chaincode.db b/test/resources/persistence/chaincode.db new file mode 100644 index 0000000..e69de29 diff --git a/test/resources/persistence/config.db b/test/resources/persistence/config.db new file mode 100644 index 0000000..bdbfb7a --- /dev/null +++ b/test/resources/persistence/config.db @@ -0,0 +1 @@ +{"id":0,"isSign":1,"_id":"0RYjfGm5ZQNGBcxV","peerGrpcUrl":"grpc://localhost:7051","peerEventUrl":"grpc://localhost:7053","ordererUrl":"grpc://localhost:7050","mspid":"Org1MSP","tlsPeerPath":"","tlsOrdererPath":"","path":"test/resources/key/users/"} diff --git a/test/resources/persistence/invoke.db b/test/resources/persistence/invoke.db new file mode 100644 index 0000000..e69de29 diff --git a/test/util/fabric.test.js b/test/util/fabric.test.js new file mode 100644 index 0000000..5119ab2 --- /dev/null +++ b/test/util/fabric.test.js @@ -0,0 +1,127 @@ +// Copyright 2019 The hyperledger-fabric-desktop Authors. All rights reserved. +import { getFabricClientSingletonHelper } from '../../src/util/fabric'; + +const { execSync } = require('child_process'); +const logger = require('electron-log'); +const Datastore = require('nedb'); +const path = require('path'); + +// Steps +// 1. start fabric network +// 2. call client functions +// 3. stop fabric network, clean up + +function initFabricNetwork() { + logger.info('Shuting down old Fabric Network.'); + let buf = execSync('cd fabric/v1.1/basic-network && ./teardown.sh'); + logger.debug(buf.toString()); + + logger.info('Initiating Fabric Network.'); + buf = execSync('cd fabric/v1.1/fabcar && ./startFabric.sh'); + logger.debug(buf.toString()); +} + +function clearFabricNetwork() { + // FIXME: 关闭网络需处理异步问题。 直接关闭会导致异步测试未结束,而网络被关闭。 + // 当前解决方案,在initFabricNetwork()中关闭网络 + + // logger.info('clearing Fabric Network.'); + // const buf = execSync('cd fabric/v1.1/basic-network && ./teardown.sh'); + // logger.debug(buf.toString()); +} + +beforeAll(() => { + jest.setTimeout(10000); + initFabricNetwork(); +}); + +afterAll(clearFabricNetwork); + +// 注意, config.db文件中的"path"字段,对应fabric-node-sdk的用户私钥仓库路径,需根据测试环境配置, +// 目前只处理fabric v1.1-basic-network的例子,处理fabric v1.3需同时考虑sdk版本升级,以及启动脚本的调整等 +const configDbForTest = new Datastore({ + filename: path.join(__dirname, '../resources/persistence/config.db'), + autoload: true, +}); + +describe('Fabric Client Basic', () => { + it('instantiate client.', () => + getFabricClientSingletonHelper(configDbForTest) + .then((client) => { + expect(client) + .not + .toBeNull(); + })); + + it('query chaincode', () => { + const clientPromise = getFabricClientSingletonHelper(configDbForTest); + return clientPromise.then((client) => { + logger.info('OK. Got client. client.channel: ', client.channels); + client.queryCc('fabcar', 'queryAllCars', null, 'mychannel') + .then((result) => { + logger.info('query result: ', result); + expect(result) + .not + .toBeNull(); + }) + .catch((err) => { + logger.error(err); + throw new Error(); + }); + }); + }); +}); + +describe('Fabric Client Advanced', () => { + describe('invoke chaincode', () => { + it('invoke for one peer', () => getFabricClientSingletonHelper(configDbForTest) + .then(client => client.invokeCc('fabcar', + 'changeCarOwner', + ['CAR0', 'newPerson'], + 'mychannel', + [], + [], + []) + .then((result) => { + logger.info('invoke result: ', result); + expect(result) + .not + .toBeNull(); + return Promise.resolve(); + })) + .catch((err) => { + logger.info('catch invoke reject'); + logger.info(err); + throw err; + })); + + it('invoke for multiple peers', () => { + // TODO: to be implemented + }); + }); + + it('install chaincode', () => { + + }); + + it('instantiate chaincode', () => { + + }); + + it('upgrade chaincode', () => { + + }); + + it('query block info', () => { + + }); + + it('create channel', () => { + + }); + + it('join peer to channel', () => { + + }); +}); +