package chaincode_test import ( "encoding/json" "fmt" "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 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) } //todo // ReadAssetPrivateDetails // AgreeToTransfer func TestTransferAsset(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 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 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 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 { assetBytes := []byte{} if testAsset != nil { var err error assetBytes, err = json.Marshal(testAsset) require.NoError(t, err) } chaincodeStub.GetPrivateDataReturns(assetBytes, nil) return assetBytes } /* func TestReadAsset(t *testing.T) { chaincodeStub := &mocks.ChaincodeStub{} transactionContext := &mocks.TransactionContext{} transactionContext.GetStubReturns(chaincodeStub) expectedAsset := &chaincode.Asset{ID: "asset1"} bytes, err := json.Marshal(expectedAsset) require.NoError(t, err) chaincodeStub.GetStateReturns(bytes, nil) assetTransferCC := chaincode.SmartContract{} asset, err := assetTransferCC.ReadAsset(transactionContext, "") require.NoError(t, err) require.Equal(t, expectedAsset, asset) chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset")) _, err = assetTransferCC.ReadAsset(transactionContext, "") require.EqualError(t, err, "failed to read from world state: unable to retrieve asset") chaincodeStub.GetStateReturns(nil, nil) asset, err = assetTransferCC.ReadAsset(transactionContext, "asset1") require.EqualError(t, err, "the asset asset1 does not exist") require.Nil(t, asset) } func TestUpdateAsset(t *testing.T) { chaincodeStub := &mocks.ChaincodeStub{} transactionContext := &mocks.TransactionContext{} transactionContext.GetStubReturns(chaincodeStub) expectedAsset := &chaincode.Asset{ID: "asset1"} bytes, err := json.Marshal(expectedAsset) require.NoError(t, err) chaincodeStub.GetStateReturns(bytes, nil) assetTransferCC := chaincode.SmartContract{} err = assetTransferCC.UpdateAsset(transactionContext, "", "", 0, "", 0) require.NoError(t, err) chaincodeStub.GetStateReturns(nil, nil) err = assetTransferCC.UpdateAsset(transactionContext, "asset1", "", 0, "", 0) require.EqualError(t, err, "the asset asset1 does not exist") chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset")) err = assetTransferCC.UpdateAsset(transactionContext, "asset1", "", 0, "", 0) require.EqualError(t, err, "failed to read from world state: unable to retrieve asset") } func TestDeleteAsset(t *testing.T) { chaincodeStub := &mocks.ChaincodeStub{} transactionContext := &mocks.TransactionContext{} transactionContext.GetStubReturns(chaincodeStub) asset := &chaincode.Asset{ID: "asset1"} bytes, err := json.Marshal(asset) require.NoError(t, err) chaincodeStub.GetStateReturns(bytes, nil) chaincodeStub.DelStateReturns(nil) assetTransferCC := chaincode.SmartContract{} err = assetTransferCC.DeleteAsset(transactionContext, "") require.NoError(t, err) chaincodeStub.GetStateReturns(nil, nil) err = assetTransferCC.DeleteAsset(transactionContext, "asset1") require.EqualError(t, err, "the asset asset1 does not exist") chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset")) err = assetTransferCC.DeleteAsset(transactionContext, "") require.EqualError(t, err, "failed to read from world state: unable to retrieve asset") } func TestTransferAsset(t *testing.T) { chaincodeStub := &mocks.ChaincodeStub{} transactionContext := &mocks.TransactionContext{} transactionContext.GetStubReturns(chaincodeStub) asset := &chaincode.Asset{ID: "asset1"} bytes, err := json.Marshal(asset) require.NoError(t, err) chaincodeStub.GetStateReturns(bytes, nil) assetTransferCC := chaincode.SmartContract{} err = assetTransferCC.TransferAsset(transactionContext, "", "") require.NoError(t, err) chaincodeStub.GetStateReturns(nil, fmt.Errorf("unable to retrieve asset")) err = assetTransferCC.TransferAsset(transactionContext, "", "") require.EqualError(t, err, "failed to read from world state: unable to retrieve asset") } func TestGetAllAssets(t *testing.T) { asset := &chaincode.Asset{ID: "asset1"} bytes, err := json.Marshal(asset) require.NoError(t, err) iterator := &mocks.StateQueryIterator{} iterator.HasNextReturnsOnCall(0, true) iterator.HasNextReturnsOnCall(1, false) iterator.NextReturns(&queryresult.KV{Value: bytes}, nil) chaincodeStub := &mocks.ChaincodeStub{} transactionContext := &mocks.TransactionContext{} transactionContext.GetStubReturns(chaincodeStub) chaincodeStub.GetStateByRangeReturns(iterator, nil) assetTransferCC := &chaincode.SmartContract{} assets, err := assetTransferCC.GetAllAssets(transactionContext) 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.GetAllAssets(transactionContext) require.EqualError(t, err, "failed retrieving next item") require.Nil(t, assets) chaincodeStub.GetStateByRangeReturns(nil, fmt.Errorf("failed retrieving all assets")) assets, err = assetTransferCC.GetAllAssets(transactionContext) require.EqualError(t, err, "failed retrieving all assets") require.Nil(t, assets) }*/