Skip to content

Commit

Permalink
Added "happy path end-to-end test"
Browse files Browse the repository at this point in the history
This is the main happy path test that connects all the individual
v1.0 APIs together to ensure the return values and parameters are
properly constructed and designed to allow applications to use
the SDK to implement complete flows.

The change also includes corresponding README.md updates for
instructions to set up the target environment and drive the tests.

fixes in response to comments:
- ESLint warnings

Change-Id: Id523f479434de3588ba9ba345c500c5d0519e7f9
Signed-off-by: Jim Zhang <jzhang@us.ibm.com>
  • Loading branch information
jimthematrix committed Nov 1, 2016
1 parent 9731107 commit 21473c4
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 9 deletions.
88 changes: 84 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,90 @@ Clone the project and launch the following commands in the project root folder t
* `gulp doc` to generate API docs
* `npm test` to run the headless tests that do not require any additional set up

The following tests require setting up a local blockchain network as the target.
* Test user management with a member services. Start a member service instance using the _hyperledger/fabric-membersrvc_ docker image. Then run `node test/unit/ca-tests.js`
* Test transaction proposals. Start a peer instance using the _hyperledger/fabric-peer_ docker image. Then run `node test/unit/endorser-tests.js`
* Test sending endorsed transactions for consensus. Start a peer instance using the _hyperledger/fabric-orderer_ docker image. Then run `node test/unit/orderer-tests.js`
The following tests require setting up a local blockchain network as the target. Because v1.0 is still in active development, you still need the vagrant environment to build the necessary Docker images needed to run the network. Follow the steps below to set it up.
* `cd fabric/devenv`
* Open the file `Vagrantfile` and insert the following statement below the existing `config.vm.network` statements:
* ` config.vm.network :forwarded_port, guest: 5151, host: 5151 # orderer service`
* run `vagrant up` to launch the vagrant VM
* Once inside vagrant, `cd $GOPATH/src/github.com/hyperledger/fabric`
* run `make images` to build the docker images
* create a docker-compose.yaml file in home directory (/home/vagrant) and copy the following content into the file
```yaml
vp:
image: hyperledger/fabric-peer
environment:
- CORE_PEER_ADDRESSAUTODETECT=true
- CORE_VM_ENDPOINT=unix:///host/var/run/docker.sock
- CORE_LOGGING_LEVEL=DEBUG
- CORE_PEER_NETWORKID=${CORE_PEER_NETWORKID}
- CORE_NEXT=true
- CORE_PEER_ENDORSER_ENABLED=true
- CORE_SECURITY_ENABLED=true
- CORE_PEER_PKI_ECA_PADDR=membersrvc:7054
- CORE_PEER_PKI_TCA_PADDR=membersrvc:7054
- CORE_PEER_PKI_TLSCA_PADDR=membersrvc:7054
- CORE_PEER_PKI_TLS_ROOTCERT_FILE=./bddtests/tlsca.cert
command: peer node start
volumes:
- /var/run/:/host/var/run/

membersrvc:
image: hyperledger/fabric-membersrvc
command: membersrvc
ports:
- 7054:7054

orderer:
image: hyperledger/fabric-orderer
environment:
- ORDERER_GENERAL_LEDGERTYPE=ram
- ORDERER_GENERAL_BATCHTIMEOUT=10s
- ORDERER_GENERAL_BATCHSIZE=10
- ORDERER_GENERAL_MAXWINDOWSIZE=1000
- ORDERER_GENERAL_LISTENADDRESS=0.0.0.0
- ORDERER_GENERAL_LISTENPORT=5005
- ORDERER_RAMLEDGER_HISTORY_SIZE=100
- ORDERER_GENERAL_ORDERERTYPE=solo
working_dir: /opt/gopath/src/github.com/hyperledger/fabric/orderer
command: orderer
ports:
- 5151:5005

vp0:
extends:
service: vp
environment:
- CORE_PEER_ID=vp0
- CORE_SECURITY_ENROLLID=test_vp0
- CORE_SECURITY_ENROLLSECRET=MwYpmSRjupbT
- CORE_PEER_PROFILE_ENABLED=true
links:
- membersrvc
- orderer0
ports:
- 7051:7051
- 7053:7053
```
* run `docker-compose up` to launch the network
* Back in your native host (MacOS, or Windows, or Ubuntu, etc), run the following tests:
* Test user management with a member services, run `node test/unit/ca-tests.js`
* Test happy path from end to end, run `node test/unit/end-2-end.js`
* Test transaction proposals, run `node test/unit/endorser-tests.js`
* Test sending endorsed transactions for consensus, run `node test/unit/orderer-tests.js`

### Contributor Check-list
The following check-list is for code contributors to make sure their changesets are compliant to the coding standards and avoid time wasted in rejected changesets:

Check the coding styles, run the following command and make sure no ESLint violations are present:
* `gulp`

Run the full unit test bucket:
* `node test/unit/headless-tests.js`
* `node test/unit/ca-tests.js`
* `node test/unit/end-2-end.js`
* `node test/unit/endorser-tests.js`
* `node test/unit/orderer-tests.js`
* `node test/unit/orderer-member-tests.js`

### HFC objects and reference documentation
For a high-level design specificiation for Fabric SDKs of all languages, visit [this google doc](https://docs.google.com/document/d/1R5RtIBMW9fZpli37E5Li5_Q9ve3BnQ4q3gWmGZj6Sv4/edit?usp=sharing) (Work-In-Progress).
Expand Down
4 changes: 2 additions & 2 deletions lib/Member.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,8 +445,8 @@ var Member = class {
let peer = new Peer(request.endorserUrl);
return peer.sendProposal(proposal)
.then(
function(status) {
resolve(status);
function(response) {
resolve(response);
}
);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Peer.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ var Peer = class extends Remote {
} else {
if (response) {
logger.info('Received proposal response: code - %s', JSON.stringify(response.response.status));
resolve(response.response.status);
resolve(response);
} else {
logger.error('GRPC client failed to get a proper response from the peer.');
reject(new Error('GRPC client failed to get a proper response from the peer.'));
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
},
"devDependencies": {
"bunyan": "^1.8.1",
"fs-extra": "^0.30.0",
"gulp": "^3.9.1",
"gulp-eslint": "^3.0.1",
"gulp-jsdoc3": "^0.3.0",
Expand Down
187 changes: 187 additions & 0 deletions test/fixtures/example_cc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
Copyright IBM Corp. 2016 All Rights Reserved.
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.
*/

package main

// A copy of fabric/examples/chaincode/go/chaincode_example02/chaincode_example02.go

import (
"errors"
"fmt"
"strconv"

"github.com/hyperledger/fabric/core/chaincode/shim"
)

// SimpleChaincode example simple Chaincode implementation
type SimpleChaincode struct {
}

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) ([]byte, error) {
_, args := stub.GetFunctionAndParameters()
var A, B string // Entities
var Aval, Bval int // Asset holdings
var err error

if len(args) != 4 {
return nil, errors.New("Incorrect number of arguments. Expecting 4")
}

// Initialize the chaincode
A = args[0]
Aval, err = strconv.Atoi(args[1])
if err != nil {
return nil, errors.New("Expecting integer value for asset holding")
}
B = args[2]
Bval, err = strconv.Atoi(args[3])
if err != nil {
return nil, errors.New("Expecting integer value for asset holding")
}
fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

// Write the state to the ledger
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {
return nil, err
}

err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
if err != nil {
return nil, err
}

return nil, nil
}

// Transaction makes payment of X units from A to B
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) ([]byte, error) {
function, args := stub.GetFunctionAndParameters()
if function == "delete" {
// Deletes an entity from its state
return t.delete(stub, args)
}

var A, B string // Entities
var Aval, Bval int // Asset holdings
var X int // Transaction value
var err error

if len(args) != 3 {
return nil, errors.New("Incorrect number of arguments. Expecting 3")
}

A = args[0]
B = args[1]

// Get the state from the ledger
// TODO: will be nice to have a GetAllState call to ledger
Avalbytes, err := stub.GetState(A)
if err != nil {
return nil, errors.New("Failed to get state")
}
if Avalbytes == nil {
return nil, errors.New("Entity not found")
}
Aval, _ = strconv.Atoi(string(Avalbytes))

Bvalbytes, err := stub.GetState(B)
if err != nil {
return nil, errors.New("Failed to get state")
}
if Bvalbytes == nil {
return nil, errors.New("Entity not found")
}
Bval, _ = strconv.Atoi(string(Bvalbytes))

// Perform the execution
X, err = strconv.Atoi(args[2])
if err != nil {
return nil, errors.New("Invalid transaction amount, expecting a integer value")
}
Aval = Aval - X
Bval = Bval + X
fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

// Write the state back to the ledger
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {
return nil, err
}

err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
if err != nil {
return nil, err
}

return nil, nil
}

// Deletes an entity from state
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
if len(args) != 1 {
return nil, errors.New("Incorrect number of arguments. Expecting 1")
}

A := args[0]

// Delete the key from the state in ledger
err := stub.DelState(A)
if err != nil {
return nil, errors.New("Failed to delete state")
}

return nil, nil
}

// Query callback representing the query of a chaincode
func (t *SimpleChaincode) Query(stub shim.ChaincodeStubInterface) ([]byte, error) {
function, args := stub.GetFunctionAndParameters()
if function != "query" {
return nil, errors.New("Invalid query function name. Expecting \"query\"")
}
var A string // Entities
var err error

if len(args) != 1 {
return nil, errors.New("Incorrect number of arguments. Expecting name of the person to query")
}

A = args[0]

// Get the state from the ledger
Avalbytes, err := stub.GetState(A)
if err != nil {
jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
return nil, errors.New(jsonResp)
}

if Avalbytes == nil {
jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
return nil, errors.New(jsonResp)
}

jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
fmt.Printf("Query Response:%s\n", jsonResp)
return Avalbytes, nil
}

func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
83 changes: 83 additions & 0 deletions test/unit/end-to-end.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// This is an end-to-end test that focuses on exercising all parts of the fabric APIs
// in a happy-path scenario
var tape = require('tape');
var _test = require('tape-promise');
var test = _test(tape);

var path = require('path');

var hfc = require('../..');
var util = require('util');
var grpc = require('grpc');
var testUtil = require('./util.js');

var _fabricProto = grpc.load(path.join(__dirname,'../../lib/protos/fabric_next.proto')).protos;

var chain = hfc.newChain('testChain');
var webUser;

testUtil.setupChaincodeDeploy();

chain.setKeyValueStore(hfc.newKeyValueStore({
path: '/tmp/kvs-hfc-e2e'
}));

chain.setMemberServicesUrl('grpc://localhost:7054');
chain.setOrderer('grpc://localhost:5151');

test('End-to-end flow of chaincode deploy, transaction invocation, and query', function(t) {
chain.enroll('admin', 'Xurw3yU9zI0l')
.then(
function(admin) {
t.pass('Successfully enrolled user \'admin\'');
webUser = admin;

// send proposal to endorser
var request = {
endorserUrl: 'grpc://localhost:7051',
chaincodePath: testUtil.CHAINCODE_PATH,
fcn: 'init',
args: ['a', '100', 'b', '200']
};

return admin.sendDeploymentProposal(request);
},
function(err) {
t.fail('Failed to enroll user \'admin\'. ' + err);
t.end();
}
).then(
function(response) {
if (response && response.response && response.response.status === 200) {
t.pass(util.format('Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s', response.response.status, response.response.message, response.response.payload, response.endorsement.signature));

var tx = new _fabricProto.Transaction2();
tx.setEndorsedActions([{
actionBytes: response.actionBytes,
endorsements: response.response.endorsement
}]);

return webUser.sendTransaction(tx.toBuffer());

} else {
t.fail('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...');
t.end();
}
},
function(err) {
t.fail('Failed to send deployment proposal due to error: ' + err.stack ? err.stack : err);
t.end();
}
).then(
function(data) {
t.pass(util.format('Response from orderer: %j', data));

t.end();
}
).catch(
function(err) {
t.fail('Failed to send deployment proposal. ' + err.stack ? err.stack : err);
t.end();
}
);
});
Loading

0 comments on commit 21473c4

Please sign in to comment.