Added unit tests for query-asset chaincode functions

Signed-off-by: Sijo Cherian <sijo@ibm.com>
This commit is contained in:
Sijo Cherian 2020-08-10 22:51:54 -04:00
parent 94faa34930
commit 9ee10c28bc
4 changed files with 662 additions and 22 deletions

View file

@ -38,7 +38,11 @@ You can use the test network script to deploy the private data smart contract to
./network.sh deployCC -ccn private -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-go/collections_config.json ./network.sh deployCC -ccn private -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-go/collections_config.json
``` ```
Note that we are using the `-ccep` flag to deploy the private data smart contract with a chaincode endorsement policy of `"OR('Org1MSP.peer','Org2MSP.peer')"`. This allows Org1 and Org2 to create an asset without receiving an endorsement from the other organization. The command also uses the `-cccg` flag to provide the path to the collection configuration file. The above command deploys the go chaincode with short name `private`, and specifies the private data collection configuration from file `collections_config.json` using `-cccg` flag.
Note that we are using the `-ccep` flag to deploy the private data smart contract with a chaincode endorsement policy of `"OR('Org1MSP.peer','Org2MSP.peer')"`. This allows Org1 and Org2 to create an asset without receiving an endorsement from the other organization.
Now you are ready to call the deployed smart contract.
Note that this sample workflow steps below, can also be executed via the application at `asset-transfer-private-data/application-javascript` folder, in fewer steps. To execute the workflow via CLI, read on.
## Register identities ## Register identities
@ -147,7 +151,7 @@ The query will return the value of the asset:
### Buyer from Org2 agrees to buy the asset ### Buyer from Org2 agrees to buy the asset
The buyer identity from Org2 is interested in buying the asset. Set the following environment variables to operate as the buyer: The buyer identity from Org2 is interested in buying the asset. In a new terminal, set the following environment variables to operate as the buyer:
``` ```
export CORE_PEER_LOCALMSPID="Org2MSP" export CORE_PEER_LOCALMSPID="Org2MSP"
@ -156,20 +160,17 @@ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.e
export CORE_PEER_ADDRESS=localhost:9051 export CORE_PEER_ADDRESS=localhost:9051
``` ```
Now that we are operating as a member of Org2, we can demonstrate that the asset appraisal is not stored on the Org2 peer: Now that we are operating as a member of Org2, we can demonstrate that the asset appraisal is not stored in Org2MSPPrivateCollection, on the Org2 peer:
``` ```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}' peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'
``` ```
The buyer only finds that asset1 does exist in the Org1 collection: The empty response shows that, the asset1 private details, does not exist in buyer private collection.
```
Error: endorsement failure during invoke. response: status:500 message:"appraisal value for asset1 does not exist in private data collection"
```
Nor is a member of Org2 able to read the Org1 private data collection: Nor can a member of Org2, able to read the Org1 private data collection:
``` ```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}' peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
``` ```
By setting `"memberOnlyRead": true` in the collection configuration file, we specify that only members of Org1 can read data from the collection. A member who tries to read the collection would only get the following response. By setting `"memberOnlyRead": true` in the collection configuration file, we specify that only members of Org1 can read data from the collection. A Org2 member who tries to read the collection would only get the following response.
``` ```
Error: endorsement failure during query. response: status:500 message:"failed to read from asset details GET_STATE failed: transaction ID: 10d39a7d0b340455a19ca4198146702d68d884d41a0e60936f1599c1ddb9c99d: tx creator does not have read access permission on privatedata in chaincodeName:private collectionName: Org1MSPPrivateCollection" Error: endorsement failure during query. response: status:500 message:"failed to read from asset details GET_STATE failed: transaction ID: 10d39a7d0b340455a19ca4198146702d68d884d41a0e60936f1599c1ddb9c99d: tx creator does not have read access permission on privatedata in chaincodeName:private collectionName: Org1MSPPrivateCollection"
``` ```
@ -189,9 +190,9 @@ The invoke will return the following value:
{"assetID":"asset1","appraisedValue":100} {"assetID":"asset1","appraisedValue":100}
``` ```
## Transfer the asset to Org2 ## Org1 member transfers the asset to Org2
Now that buyer has agreed to buy the asset for appraised value, the owner from Org1 can transfer the asset to Org2. Set the following environment variables to operate as Org1: Now that buyer has agreed to buy the asset for appraised value, the owner from Org1 can transfer the asset to Org2. In the first terminal (with the following environment variables to operate as Org1):
``` ```
export CORE_PEER_LOCALMSPID="Org1MSP" export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp
@ -199,7 +200,12 @@ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.e
export CORE_PEER_ADDRESS=localhost:7051 export CORE_PEER_ADDRESS=localhost:7051
``` ```
To transfer the asset, the owner needs to pass the MSP ID of new asset owner. The transfer function will read the client ID of the interested buyer from the transfer agreement. Now that buyer has agreed to buy the asset for appraised value, the owner from Org1 can read the data added by `AgreeToTransfer` to see buyer identity.
```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadTransferAgreement","Args":["asset1"]}'
```
The owner from Org1 can now transfer the asset to Org2. To transfer the asset, the owner needs to pass the MSP ID of new asset owner Org. The transfer function will read the client ID of the interested buyer user from the transfer agreement.
``` ```
export ASSET_OWNER=$(echo -n "{\"assetID\":\"asset1\",\"buyerMSP\":\"Org2MSP\"}" | base64 | tr -d \\n) export ASSET_OWNER=$(echo -n "{\"assetID\":\"asset1\",\"buyerMSP\":\"Org2MSP\"}" | base64 | tr -d \\n)
``` ```
@ -209,7 +215,7 @@ The owner of the asset needs to initiate the transfer.
``` ```
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"TransferAsset","Args":[]}' --transient "{\"asset_owner\":\"$ASSET_OWNER\"}" --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"TransferAsset","Args":[]}' --transient "{\"asset_owner\":\"$ASSET_OWNER\"}" --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
``` ```
You can query `asset1` to see the results of the transfer. You can ReadAsset `asset1` to see the results of the transfer.
``` ```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}' peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'
``` ```
@ -229,10 +235,7 @@ You can also confirm that transfer removed the private details from the Org1 col
``` ```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}' peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
``` ```
Your query will return the following result: Your query will return empty result, since the asset private data is removed from the Org1 private data collection.
```
Error: endorsement failure during query. response: status:500 message:"appraisal value for asset1 does not exist in private data collection"
```
## Clean up ## Clean up

View file

@ -0,0 +1,185 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package chaincode_test
import (
"encoding/json"
"fmt"
"testing"
"github.com/hyperledger/fabric-protos-go/ledger/queryresult"
"github.com/hyperledger/fabric-samples/asset-transfer-private-data/chaincode-go/chaincode"
"github.com/hyperledger/fabric-samples/asset-transfer-private-data/chaincode-go/chaincode/mocks"
"github.com/stretchr/testify/require"
)
/*
For details on generating the mocks, see comments in the file asset_transfer_test.go
*/
func TestReadAsset(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
assetBytes, err := assetTransferCC.ReadAsset(transactionContext, "id1")
require.NoError(t, err)
require.Nil(t, assetBytes)
chaincodeStub.GetPrivateDataReturns(nil, fmt.Errorf("unable to retrieve asset"))
assetBytes, err = assetTransferCC.ReadAsset(transactionContext, "id1")
require.EqualError(t, err, "failed to read asset: unable to retrieve asset")
testAsset := &chaincode.Asset{
ID: "id1",
Type: "testfulasset",
Color: "gray",
Size: 7,
Owner: myOrg1Clientid,
}
setReturnPrivateDataInStub(t, chaincodeStub, testAsset)
assetRead, err := assetTransferCC.ReadAsset(transactionContext, "id1")
require.NoError(t, err)
require.Equal(t, testAsset, assetRead)
}
func TestReadAssetPrivateDetails(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
assetBytes, err := assetTransferCC.ReadAssetPrivateDetails(transactionContext, myOrg1PrivCollection, "id1")
require.NoError(t, err)
require.Nil(t, assetBytes)
//read from the collection with no access
chaincodeStub.GetPrivateDataReturns(nil, fmt.Errorf("collection not found"))
assetBytes, err = assetTransferCC.ReadAssetPrivateDetails(transactionContext, myOrg2PrivCollection, "id1")
require.EqualError(t, err, "failed to read asset details: collection not found")
returnPrivData := &chaincode.AssetPrivateDetails{
ID: "id1",
AppraisedValue: 5,
}
setReturnAssetPrivateDetailsInStub(t, chaincodeStub, returnPrivData)
assetRead, err := assetTransferCC.ReadAssetPrivateDetails(transactionContext, myOrg1PrivCollection, "id1")
require.NoError(t, err)
require.Equal(t, returnPrivData, assetRead)
}
func TestReadTransferAgreement(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
//TransferAgreement does not exist
assetBytes, err := assetTransferCC.ReadTransferAgreement(transactionContext, "id1")
require.NoError(t, err)
require.Nil(t, assetBytes)
chaincodeStub.GetPrivateDataReturns([]byte(myOrg2Clientid), nil)
expectedData := &chaincode.TransferAgreement{
ID: "id1",
BuyerID: myOrg2Clientid,
}
dataRead, err := assetTransferCC.ReadTransferAgreement(transactionContext, "id1")
require.NoError(t, err)
require.Equal(t, expectedData, dataRead)
}
func TestQueryAssetByOwner(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
asset := &chaincode.Asset{Type: "valuableasset", ID: "asset1", Owner: "user1"}
asset1Bytes, err := json.Marshal(asset)
require.NoError(t, err)
iterator := &mocks.StateQueryIterator{}
iterator.HasNextReturnsOnCall(0, true)
iterator.HasNextReturnsOnCall(1, false)
iterator.NextReturns(&queryresult.KV{Value: asset1Bytes}, nil)
chaincodeStub.GetPrivateDataQueryResultReturns(iterator, nil)
assetTransferCC := &chaincode.SmartContract{}
assets, err := assetTransferCC.QueryAssetByOwner(transactionContext, "valuableasset", "user1")
require.NoError(t, err)
require.Equal(t, []*chaincode.Asset{asset}, assets)
iterator.HasNextReturns(true)
iterator.NextReturns(nil, fmt.Errorf("failed retrieving next item"))
assets, err = assetTransferCC.QueryAssetByOwner(transactionContext, "valuableasset", "user1")
require.EqualError(t, err, "failed retrieving next item")
require.Nil(t, assets)
}
func TestQueryAssets(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
//Iterator with no records
iterator := &mocks.StateQueryIterator{}
iterator.HasNextReturns(false)
chaincodeStub.GetPrivateDataQueryResultReturns(iterator, nil)
assetTransferCC := &chaincode.SmartContract{}
assets, err := assetTransferCC.QueryAssets(transactionContext, "querystr")
require.NoError(t, err)
require.Equal(t, []*chaincode.Asset{}, assets)
iterator = &mocks.StateQueryIterator{}
chaincodeStub.GetPrivateDataQueryResultReturns(iterator, nil)
iterator.HasNextReturns(true)
iterator.NextReturns(nil, fmt.Errorf("failed retrieving next item"))
assets, err = assetTransferCC.QueryAssets(transactionContext, "querystr")
require.EqualError(t, err, "failed retrieving next item")
require.Nil(t, assets)
asset := &chaincode.Asset{Type: "valuableasset", ID: "asset1", Owner: "user1"}
asset1Bytes, err := json.Marshal(asset)
require.NoError(t, err)
iterator = &mocks.StateQueryIterator{}
chaincodeStub.GetPrivateDataQueryResultReturns(iterator, nil)
iterator.HasNextReturnsOnCall(0, true)
iterator.HasNextReturnsOnCall(1, false)
iterator.NextReturns(&queryresult.KV{Value: asset1Bytes}, nil)
assets, err = assetTransferCC.QueryAssets(transactionContext, "querystr")
require.NoError(t, err)
require.Equal(t, []*chaincode.Asset{asset}, assets)
}
func TestGetAssetByRange(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
//Iterator with no records
iterator := &mocks.StateQueryIterator{}
iterator.HasNextReturns(false)
chaincodeStub.GetPrivateDataByRangeReturns(iterator, nil)
assetTransferCC := &chaincode.SmartContract{}
assets, err := assetTransferCC.GetAssetByRange(transactionContext, "st", "end")
require.NoError(t, err)
require.Equal(t, []*chaincode.Asset{}, assets)
iterator = &mocks.StateQueryIterator{}
chaincodeStub.GetPrivateDataByRangeReturns(iterator, nil)
iterator.HasNextReturns(true)
iterator.NextReturns(nil, fmt.Errorf("failed retrieving next item"))
assets, err = assetTransferCC.GetAssetByRange(transactionContext, "st", "end")
require.EqualError(t, err, "failed retrieving next item")
require.Nil(t, assets)
asset := &chaincode.Asset{Type: "valuableasset", ID: "asset1", Owner: "user1"}
asset1Bytes, err := json.Marshal(asset)
require.NoError(t, err)
iterator = &mocks.StateQueryIterator{}
chaincodeStub.GetPrivateDataByRangeReturns(iterator, nil)
iterator.HasNextReturnsOnCall(0, true)
iterator.HasNextReturnsOnCall(1, false)
iterator.NextReturns(&queryresult.KV{Value: asset1Bytes}, nil)
assets, err = assetTransferCC.GetAssetByRange(transactionContext, "st", "end")
require.NoError(t, err)
require.Equal(t, []*chaincode.Asset{asset}, assets)
}

View file

@ -0,0 +1,444 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package chaincode_test
import (
"encoding/json"
"os"
"testing"
"github.com/hyperledger/fabric-chaincode-go/pkg/cid"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
"github.com/hyperledger/fabric-samples/asset-transfer-private-data/chaincode-go/chaincode"
"github.com/hyperledger/fabric-samples/asset-transfer-private-data/chaincode-go/chaincode/mocks"
"github.com/stretchr/testify/require"
)
/*
These unit tests use mocks to simulate chaincode-api & fabric interactions
The mocks are generated using counterfeiter directives in the comments (starting with "go:generate counterfeiter")
All files in mocks/* are generated by running following, in the directory with your directive:
`go generate`
*/
//go:generate counterfeiter -o mocks/transaction.go -fake-name TransactionContext . transactionContext
type transactionContext interface {
contractapi.TransactionContextInterface
}
//go:generate counterfeiter -o mocks/chaincodestub.go -fake-name ChaincodeStub . chaincodeStub
type chaincodeStub interface {
shim.ChaincodeStubInterface
}
//go:generate counterfeiter -o mocks/statequeryiterator.go -fake-name StateQueryIterator . stateQueryIterator
type stateQueryIterator interface {
shim.StateQueryIteratorInterface
}
//go:generate counterfeiter -o mocks/clientIdentity.go -fake-name ClientIdentity . clientIdentity
type clientIdentity interface {
cid.ClientIdentity
}
const assetCollectionName = "assetCollection"
const transferAgreementObjectType = "transferAgreement"
const myOrg1Msp = "Org1Testmsp"
const myOrg1Clientid = "myOrg1Userid"
const myOrg1PrivCollection = "Org1TestmspPrivateCollection"
const myOrg2Msp = "Org2Testmsp"
const myOrg2Clientid = "myOrg2Userid"
const myOrg2PrivCollection = "Org2TestmspPrivateCollection"
type assetTransientInput struct {
Type string `json:"objectType"`
ID string `json:"assetID"`
Color string `json:"color"`
Size int `json:"size"`
AppraisedValue int `json:"appraisedValue"`
}
type assetTransferTransientInput struct {
ID string `json:"assetID"`
BuyerMSP string `json:"buyerMSP"`
}
func TestCreateAssetBadInput(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
// No transient map
err := assetTransferCC.CreateAsset(transactionContext)
require.EqualError(t, err, "asset not found in the transient map input")
// transient map with incomplete asset data
assetPropMap := map[string][]byte{
"asset_properties": []byte("ill formatted property"),
}
chaincodeStub.GetTransientReturns(assetPropMap, nil)
err = assetTransferCC.CreateAsset(transactionContext)
require.Error(t, err, "Expected error: transient map with incomplete asset data")
require.Contains(t, err.Error(), "failed to unmarshal JSON")
testAsset := &assetTransientInput{
Type: "testfulasset",
}
setReturnAssetPropsInTransientMap(t, chaincodeStub, testAsset)
err = assetTransferCC.CreateAsset(transactionContext)
require.EqualError(t, err, "assetID field must be a non-empty string")
testAsset = &assetTransientInput{
ID: "id1",
Color: "gray",
}
setReturnAssetPropsInTransientMap(t, chaincodeStub, testAsset)
err = assetTransferCC.CreateAsset(transactionContext)
require.EqualError(t, err, "objectType field must be a non-empty string")
// case when asset exists, GetPrivateData returns a valid data from ledger
testAsset = &assetTransientInput{
ID: "id1",
Type: "testfulasset",
Color: "gray",
Size: 7,
AppraisedValue: 500,
}
setReturnAssetPropsInTransientMap(t, chaincodeStub, testAsset)
chaincodeStub.GetPrivateDataReturns([]byte{}, nil)
err = assetTransferCC.CreateAsset(transactionContext)
require.EqualError(t, err, "this asset already exists: id1")
}
func TestCreateAssetSuccessful(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
testAsset := &assetTransientInput{
ID: "id1",
Type: "testfulasset",
Color: "gray",
Size: 7,
AppraisedValue: 500,
}
setReturnAssetPropsInTransientMap(t, chaincodeStub, testAsset)
err := assetTransferCC.CreateAsset(transactionContext)
require.NoError(t, err)
//Validate PutPrivateData calls
calledCollection, calledId, _ := chaincodeStub.PutPrivateDataArgsForCall(0)
require.Equal(t, assetCollectionName, calledCollection)
require.Equal(t, "id1", calledId)
expectedPrivateDetails := &chaincode.AssetPrivateDetails{
ID: "id1",
AppraisedValue: 500,
}
assetBytes, err := json.Marshal(expectedPrivateDetails)
calledCollection, calledId, calledAssetBytes := chaincodeStub.PutPrivateDataArgsForCall(1)
require.Equal(t, myOrg1PrivCollection, calledCollection)
require.Equal(t, "id1", calledId)
require.Equal(t, assetBytes, calledAssetBytes)
}
func TestAgreeToTransferBadInput(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
assetPrivDetail := &chaincode.AssetPrivateDetails{
ID: "id1",
//no AppraisedValue
}
setReturnAssetPrivateDetailsInTransientMap(t, chaincodeStub, assetPrivDetail)
origAsset := chaincode.Asset{
ID: "id1",
Type: "testfulasset",
Color: "gray",
Size: 7,
Owner: myOrg1Clientid,
}
setReturnPrivateDataInStub(t, chaincodeStub, &origAsset)
err := assetTransferCC.AgreeToTransfer(transactionContext)
require.EqualError(t, err, "appraisedValue field must be a positive integer")
assetPrivDetail = &chaincode.AssetPrivateDetails{
//no ID
AppraisedValue: 500,
}
setReturnAssetPrivateDetailsInTransientMap(t, chaincodeStub, assetPrivDetail)
err = assetTransferCC.AgreeToTransfer(transactionContext)
require.EqualError(t, err, "assetID field must be a non-empty string")
assetPrivDetail = &chaincode.AssetPrivateDetails{
ID: "id1",
AppraisedValue: 500,
}
setReturnAssetPrivateDetailsInTransientMap(t, chaincodeStub, assetPrivDetail)
//asset does not exist
setReturnPrivateDataInStub(t, chaincodeStub, nil)
err = assetTransferCC.AgreeToTransfer(transactionContext)
require.EqualError(t, err, "id1 does not exist")
}
func TestAgreeToTransferSuccessful(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
assetPrivDetail := &chaincode.AssetPrivateDetails{
ID: "id1",
AppraisedValue: 500,
}
setReturnAssetPrivateDetailsInTransientMap(t, chaincodeStub, assetPrivDetail)
origAsset := chaincode.Asset{
ID: "id1",
Type: "testfulasset",
Color: "gray",
Size: 7,
Owner: myOrg1Clientid,
}
setReturnPrivateDataInStub(t, chaincodeStub, &origAsset)
chaincodeStub.CreateCompositeKeyReturns(transferAgreementObjectType+"id1", nil)
err := assetTransferCC.AgreeToTransfer(transactionContext)
require.NoError(t, err)
expectedDataBytes, err := json.Marshal(assetPrivDetail)
calledCollection, calledId, calledWithDataBytes := chaincodeStub.PutPrivateDataArgsForCall(0)
require.Equal(t, myOrg1PrivCollection, calledCollection)
require.Equal(t, "id1", calledId)
require.Equal(t, expectedDataBytes, calledWithDataBytes)
calledCollection, calledId, calledWithDataBytes = chaincodeStub.PutPrivateDataArgsForCall(1)
require.Equal(t, assetCollectionName, calledCollection)
require.Equal(t, transferAgreementObjectType+"id1", calledId)
require.Equal(t, []byte(myOrg1Clientid), calledWithDataBytes)
}
func TestTransferAssetBadInput(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
assetNewOwner := &assetTransferTransientInput{
ID: "id1",
BuyerMSP: "",
}
setReturnAssetOwnerInTransientMap(t, chaincodeStub, assetNewOwner)
setReturnPrivateDataInStub(t, chaincodeStub, &chaincode.Asset{})
err := assetTransferCC.TransferAsset(transactionContext)
require.EqualError(t, err, "buyerMSP field must be a non-empty string")
assetNewOwner = &assetTransferTransientInput{
ID: "id1",
BuyerMSP: myOrg2Msp,
}
setReturnAssetOwnerInTransientMap(t, chaincodeStub, assetNewOwner)
//asset does not exist
setReturnPrivateDataInStub(t, chaincodeStub, nil)
err = assetTransferCC.TransferAsset(transactionContext)
require.EqualError(t, err, "id1 does not exist")
}
func TestTransferAssetSuccessful(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
assetNewOwner := &assetTransferTransientInput{
ID: "id1",
BuyerMSP: myOrg2Msp,
}
setReturnAssetOwnerInTransientMap(t, chaincodeStub, assetNewOwner)
origAsset := chaincode.Asset{
ID: "id1",
Type: "testfulasset",
Color: "gray",
Size: 7,
Owner: myOrg1Clientid,
}
setReturnPrivateDataInStub(t, chaincodeStub, &origAsset)
//to ensure we pass data hash verification
chaincodeStub.GetPrivateDataHashReturns([]byte("datahash"), nil)
//to ensure that ReadTransferAgreement call returns org2 client ID
chaincodeStub.GetPrivateDataReturnsOnCall(1, []byte(myOrg2Clientid), nil)
chaincodeStub.CreateCompositeKeyReturns(transferAgreementObjectType+"id1", nil)
err := assetTransferCC.TransferAsset(transactionContext)
require.NoError(t, err)
//Validate PutPrivateData calls
expectedNewAsset := origAsset
expectedNewAsset.Owner = myOrg2Clientid
expectedNewAssetBytes, err := json.Marshal(expectedNewAsset)
require.NoError(t, err)
calledCollection, calledId, calledWithAssetBytes := chaincodeStub.PutPrivateDataArgsForCall(0)
require.Equal(t, assetCollectionName, calledCollection)
require.Equal(t, "id1", calledId)
require.Equal(t, expectedNewAssetBytes, calledWithAssetBytes)
calledCollection, calledId = chaincodeStub.DelPrivateDataArgsForCall(0)
require.Equal(t, myOrg1PrivCollection, calledCollection)
require.Equal(t, "id1", calledId)
calledCollection, calledId = chaincodeStub.DelPrivateDataArgsForCall(1)
require.Equal(t, assetCollectionName, calledCollection)
require.Equal(t, transferAgreementObjectType+"id1", calledId)
}
func TestTransferAssetByNonOwner(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
assetNewOwner := &assetTransferTransientInput{
ID: "id1",
BuyerMSP: myOrg1Msp,
}
setReturnAssetOwnerInTransientMap(t, chaincodeStub, assetNewOwner)
//Try to transfer asset owned by Org2
org2Asset := chaincode.Asset{
ID: "id1",
Type: "testfulasset",
Color: "gray",
Size: 7,
Owner: myOrg2Clientid,
}
setReturnPrivateDataInStub(t, chaincodeStub, &org2Asset)
err := assetTransferCC.TransferAsset(transactionContext)
require.EqualError(t, err, "failed transfer verification: error: submitting client identity does not own asset")
}
func TestTransferAssetWithoutAnAgreement(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
assetNewOwner := &assetTransferTransientInput{
ID: "id1",
BuyerMSP: myOrg1Msp,
}
setReturnAssetOwnerInTransientMap(t, chaincodeStub, assetNewOwner)
orgAsset := chaincode.Asset{
ID: "id1",
Type: "testfulasset",
Color: "gray",
Size: 7,
Owner: myOrg1Clientid,
}
setReturnPrivateDataInStub(t, chaincodeStub, &orgAsset)
//to ensure we pass data hash verification
chaincodeStub.GetPrivateDataHashReturns([]byte("datahash"), nil)
chaincodeStub.CreateCompositeKeyReturns(transferAgreementObjectType+"id1", nil)
//ReadTransferAgreement call returns no buyer client ID
chaincodeStub.GetPrivateDataReturnsOnCall(1, []byte{}, nil)
err := assetTransferCC.TransferAsset(transactionContext)
require.EqualError(t, err, "BuyerID not found in TransferAgreement for id1")
}
func TestTransferAssetNonMatchingAppraisalValue(t *testing.T) {
transactionContext, chaincodeStub := prepMocksAsOrg1()
assetTransferCC := chaincode.SmartContract{}
assetNewOwner := &assetTransferTransientInput{
ID: "id1",
BuyerMSP: myOrg2Msp,
}
setReturnAssetOwnerInTransientMap(t, chaincodeStub, assetNewOwner)
orgAsset := chaincode.Asset{
ID: "id1",
Type: "testfulasset",
Color: "gray",
Size: 7,
Owner: myOrg1Clientid,
}
setReturnPrivateDataInStub(t, chaincodeStub, &orgAsset)
chaincodeStub.CreateCompositeKeyReturns(transferAgreementObjectType+"id1", nil)
//data hash different in each collection
chaincodeStub.GetPrivateDataHashReturnsOnCall(0, []byte("datahash1"), nil)
chaincodeStub.GetPrivateDataHashReturnsOnCall(1, []byte("datahash2"), nil)
err := assetTransferCC.TransferAsset(transactionContext)
require.Error(t, err, "Expected failed hash verification")
require.Contains(t, err.Error(), "failed transfer verification: hash for appraised value")
}
func prepMocksAsOrg1() (*mocks.TransactionContext, *mocks.ChaincodeStub) {
return prepMocks(myOrg1Msp, myOrg1Clientid)
}
func prepMocksAsOrg2() (*mocks.TransactionContext, *mocks.ChaincodeStub) {
return prepMocks(myOrg2Msp, myOrg2Clientid)
}
func prepMocks(orgMSP, clientId string) (*mocks.TransactionContext, *mocks.ChaincodeStub) {
chaincodeStub := &mocks.ChaincodeStub{}
transactionContext := &mocks.TransactionContext{}
transactionContext.GetStubReturns(chaincodeStub)
clientIdentity := &mocks.ClientIdentity{}
clientIdentity.GetMSPIDReturns(orgMSP, nil)
clientIdentity.GetIDReturns(clientId, nil)
//set matching msp ID using peer shim env variable
os.Setenv("CORE_PEER_LOCALMSPID", orgMSP)
transactionContext.GetClientIdentityReturns(clientIdentity)
return transactionContext, chaincodeStub
}
func setReturnAssetPrivateDetailsInTransientMap(t *testing.T, chaincodeStub *mocks.ChaincodeStub, assetPrivDetail *chaincode.AssetPrivateDetails) []byte {
assetOwnerBytes := []byte{}
if assetPrivDetail != nil {
var err error
assetOwnerBytes, err = json.Marshal(assetPrivDetail)
require.NoError(t, err)
}
assetPropMap := map[string][]byte{
"asset_value": assetOwnerBytes,
}
chaincodeStub.GetTransientReturns(assetPropMap, nil)
return assetOwnerBytes
}
func setReturnAssetOwnerInTransientMap(t *testing.T, chaincodeStub *mocks.ChaincodeStub, assetOwner *assetTransferTransientInput) []byte {
assetOwnerBytes := []byte{}
if assetOwner != nil {
var err error
assetOwnerBytes, err = json.Marshal(assetOwner)
require.NoError(t, err)
}
assetPropMap := map[string][]byte{
"asset_owner": assetOwnerBytes,
}
chaincodeStub.GetTransientReturns(assetPropMap, nil)
return assetOwnerBytes
}
func setReturnAssetPropsInTransientMap(t *testing.T, chaincodeStub *mocks.ChaincodeStub, testAsset *assetTransientInput) []byte {
assetBytes := []byte{}
if testAsset != nil {
var err error
assetBytes, err = json.Marshal(testAsset)
require.NoError(t, err)
}
assetPropMap := map[string][]byte{
"asset_properties": assetBytes,
}
chaincodeStub.GetTransientReturns(assetPropMap, nil)
return assetBytes
}
func setReturnPrivateDataInStub(t *testing.T, chaincodeStub *mocks.ChaincodeStub, testAsset *chaincode.Asset) []byte {
if testAsset == nil {
chaincodeStub.GetPrivateDataReturns(nil, nil)
return nil
} else {
var err error
assetBytes, err := json.Marshal(testAsset)
require.NoError(t, err)
chaincodeStub.GetPrivateDataReturns(assetBytes, nil)
return assetBytes
}
}
func setReturnAssetPrivateDetailsInStub(t *testing.T, chaincodeStub *mocks.ChaincodeStub, testAsset *chaincode.AssetPrivateDetails) []byte {
if testAsset == nil {
chaincodeStub.GetPrivateDataReturns(nil, nil)
return nil
} else {
var err error
assetBytes, err := json.Marshal(testAsset)
require.NoError(t, err)
chaincodeStub.GetPrivateDataReturns(assetBytes, nil)
return assetBytes
}
}

View file

@ -116,14 +116,14 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface)
} }
// Make submitting client the owner // Make submitting client the owner
asset := &Asset{ asset := Asset{
Type: assetInput.Type, Type: assetInput.Type,
ID: assetInput.ID, ID: assetInput.ID,
Color: assetInput.Color, Color: assetInput.Color,
Size: assetInput.Size, Size: assetInput.Size,
Owner: clientID, Owner: clientID,
} }
assetJSONasBytes, err := json.Marshal(asset) assetJSONasBytes, err := json.Marshal(&asset)
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal asset into JSON: %v", err) return fmt.Errorf("failed to marshal asset into JSON: %v", err)
} }
@ -138,12 +138,12 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface)
} }
// Save asset details to collection visible to owning organization // Save asset details to collection visible to owning organization
assetPrivateDetails := &AssetPrivateDetails{ assetPrivateDetails := AssetPrivateDetails{
ID: assetInput.ID, ID: assetInput.ID,
AppraisedValue: assetInput.AppraisedValue, AppraisedValue: assetInput.AppraisedValue,
} }
assetPrivateDetailsAsBytes, err := json.Marshal(assetPrivateDetails) // marshal asset details to JSON assetPrivateDetailsAsBytes, err := json.Marshal(&assetPrivateDetails) // marshal asset details to JSON
if err != nil { if err != nil {
return fmt.Errorf("failed to marshal into JSON: %v", err) return fmt.Errorf("failed to marshal into JSON: %v", err)
} }
@ -202,6 +202,14 @@ func (s *SmartContract) AgreeToTransfer(ctx contractapi.TransactionContextInterf
return fmt.Errorf("appraisedValue field must be a positive integer") return fmt.Errorf("appraisedValue field must be a positive integer")
} }
// Read asset from the private data collection
asset, err := s.ReadAsset(ctx, valueJSON.ID)
if err != nil {
return fmt.Errorf("error reading asset: %v", err)
}
if asset == nil {
return fmt.Errorf("%v does not exist", valueJSON.ID)
}
// Verify that the client is submitting request to peer in their organization // Verify that the client is submitting request to peer in their organization
err = verifyClientOrgMatchesPeerOrg(ctx) err = verifyClientOrgMatchesPeerOrg(ctx)
if err != nil { if err != nil {
@ -486,7 +494,7 @@ func (s *SmartContract) DeleteTranferAgreement(ctx contractapi.TransactionContex
} }
if len(assetDeleteInput.ID) == 0 { if len(assetDeleteInput.ID) == 0 {
return fmt.Errorf("ID field must be a non-empty string") return fmt.Errorf("transient input ID field must be a non-empty string")
} }
// Verify that the client is submitting request to peer in their organization // Verify that the client is submitting request to peer in their organization