github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/integration/ledger/marbles_test.go (about) 1 /* 2 Copyright hechain All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package ledger 8 9 import ( 10 "bytes" 11 "encoding/json" 12 "fmt" 13 "path/filepath" 14 "syscall" 15 16 "github.com/hechain20/hechain/integration/nwo" 17 "github.com/hechain20/hechain/integration/nwo/commands" 18 "github.com/hechain20/hechain/integration/nwo/runner" 19 . "github.com/onsi/ginkgo" 20 . "github.com/onsi/gomega" 21 "github.com/onsi/gomega/gexec" 22 "github.com/tedsuo/ifrit" 23 ) 24 25 var _ = Describe("all shim APIs for non-private data", func() { 26 var ( 27 setup *setup 28 helper *marblesTestHelper 29 chaincode nwo.Chaincode 30 ) 31 32 BeforeEach(func() { 33 setup = initThreeOrgsSetup() 34 helper = &marblesTestHelper{ 35 networkHelper: &networkHelper{ 36 Network: setup.network, 37 orderer: setup.orderer, 38 peers: setup.peers, 39 testDir: setup.testDir, 40 channelID: setup.channelID, 41 }, 42 } 43 44 chaincode = nwo.Chaincode{ 45 Name: "marbles", 46 Version: "0.0", 47 Path: "github.com/hechain20/hechain/integration/chaincode/marbles/cmdwithindexspecs", 48 Lang: "golang", 49 PackageFile: filepath.Join(setup.testDir, "marbles.tar.gz"), 50 Label: "marbles", 51 SignaturePolicy: `OR ('Org1MSP.member','Org2MSP.member', 'Org3MSP.member')`, 52 Sequence: "1", 53 } 54 }) 55 56 AfterEach(func() { 57 setup.cleanup() 58 }) 59 60 // assertMarbleAPIs invoke marble APIs to add/transfer/delete marbles and get marbles without rich queries. 61 // These APIs are applicable to both levelDB and CouchDB. 62 assertMarbleAPIs := func(ccName string, peer *nwo.Peer) { 63 height := helper.getLedgerHeight(peer) 64 65 By("adding six marbles, marble-0 to marble-5") 66 for i := 0; i <= 5; i++ { 67 helper.invokeMarblesChaincode(ccName, peer, "initMarble", fmt.Sprintf("marble-%d", i), "blue", "35", "tom") 68 helper.waitUntilAllPeersEqualLedgerHeight(height + i + 1) 69 } 70 71 By("getting marbles by range") 72 expectedQueryResult := newMarbleQueryResult(1, 4, "blue", 35, "tom") 73 helper.assertQueryMarbles(ccName, peer, expectedQueryResult, "getMarblesByRange", "marble-1", "marble-5") 74 75 By("transferring marble-0 to jerry") 76 helper.invokeMarblesChaincode(ccName, peer, "transferMarble", "marble-0", "jerry") 77 78 By("verifying new owner of marble-0 after transfer") 79 expectedResult := newMarble("marble-0", "blue", 35, "jerry") 80 helper.assertMarbleExists(ccName, peer, expectedResult, "marble-0") 81 82 By("deleting marble-0") 83 helper.invokeMarblesChaincode(ccName, peer, "delete", "marble-0") 84 85 By("verifying deletion of marble-0") 86 helper.assertMarbleDoesNotExist(peer, ccName, "marble-0") 87 88 By("transferring marbles by color") 89 helper.invokeMarblesChaincode(ccName, peer, "transferMarblesBasedOnColor", "blue", "jerry") 90 91 By("verifying new owner after transfer by color") 92 for i := 1; i <= 5; i++ { 93 name := fmt.Sprintf("marble-%d", i) 94 expectedResult = newMarble(name, "blue", 35, "jerry") 95 helper.assertMarbleExists(ccName, peer, expectedResult, name) 96 } 97 98 By("getting history for marble-0") 99 expectedHistoryResult := []*marbleHistoryResult{ 100 {IsDelete: "true"}, 101 {IsDelete: "false", Value: newMarble("marble-0", "blue", 35, "jerry")}, 102 {IsDelete: "false", Value: newMarble("marble-0", "blue", 35, "tom")}, 103 } 104 helper.assertGetHistoryForMarble(ccName, peer, expectedHistoryResult, "marble-0") 105 } 106 107 // assertMarbleAPIs verifies marbles APIs using rich queries and pagination that are only applicable to CouchDB. 108 assertMarbleAPIsRichQueries := func(ccName string, peer *nwo.Peer) { 109 By("querying marbles by owner") 110 expectedQueryResult := newMarbleQueryResult(1, 5, "blue", 35, "jerry") 111 helper.assertQueryMarbles(ccName, peer, expectedQueryResult, "queryMarblesByOwner", "jerry") 112 113 By("quering marbles by search criteria") 114 helper.assertQueryMarbles(ccName, peer, expectedQueryResult, "queryMarbles", `{"selector":{"color":"blue"}}`) 115 116 By("quering marbles by range with pagination size 3, 1st call") 117 bookmark := "" 118 expectedQueryResult = newMarbleQueryResult(1, 3, "blue", 35, "jerry") 119 bookmark = helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult, 120 "getMarblesByRangeWithPagination", "marble-1", "marble-6", "3", bookmark) 121 122 By("quering marbles by range with pagination size 3, 2nd call") 123 expectedQueryResult = newMarbleQueryResult(4, 5, "blue", 35, "jerry") 124 bookmark = helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult, 125 "getMarblesByRangeWithPagination", "marble-1", "marble-6", "3", bookmark) 126 127 By("quering marbles by range with pagination size 3, 3rd call should return no marble") 128 expectedQueryResult = make([]*marbleQueryResult, 0) 129 helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult, 130 "getMarblesByRangeWithPagination", "marble-1", "marble-6", "3", bookmark) 131 132 By("quering marbles by search criteria with pagination size 10, 1st call") 133 bookmark = "" 134 expectedQueryResult = newMarbleQueryResult(1, 5, "blue", 35, "jerry") 135 bookmark = helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult, 136 "queryMarblesWithPagination", `{"selector":{"owner":"jerry"}}`, "10", bookmark) 137 138 By("quering marbles by search criteria with pagination size 10, 2nd call should return no marble") 139 expectedQueryResult = make([]*marbleQueryResult, 0) 140 helper.assertQueryMarblesWithPagination(ccName, peer, expectedQueryResult, 141 "queryMarblesWithPagination", `{"selector":{"owner":"jerry"}}`, "10", bookmark) 142 } 143 144 When("levelDB is used as stateDB", func() { 145 It("calls marbles APIs", func() { 146 peer := setup.network.Peer("Org2", "peer0") 147 148 By("deploying new lifecycle chaincode") 149 nwo.EnableCapabilities(setup.network, setup.channelID, "Application", "V2_0", setup.orderer, setup.peers...) 150 helper.deployChaincode(chaincode) 151 152 By("verifying marbles chaincode APIs") 153 assertMarbleAPIs(chaincode.Name, peer) 154 }) 155 }) 156 157 When("CouchDB is used as stateDB", func() { 158 var couchProcess ifrit.Process 159 160 BeforeEach(func() { 161 By("stopping peers") 162 setup.stopPeers() 163 164 By("configuring a peer with couchdb") 165 // configure only one of the peers (Org2, peer0) to use couchdb. 166 // Note that we do not support a channel with mixed DBs. 167 // However, for testing, it would be fine to use couchdb for one 168 // peer and sending all the couchdb related test queries to this peer 169 couchDB := &runner.CouchDB{} 170 couchProcess = ifrit.Invoke(couchDB) 171 Eventually(couchProcess.Ready(), runner.DefaultStartTimeout).Should(BeClosed()) 172 Consistently(couchProcess.Wait()).ShouldNot(Receive()) 173 couchAddr := couchDB.Address() 174 peer := setup.network.Peer("Org2", "peer0") 175 core := setup.network.ReadPeerConfig(peer) 176 core.Ledger.State.StateDatabase = "CouchDB" 177 core.Ledger.State.CouchDBConfig.CouchDBAddress = couchAddr 178 setup.network.WritePeerConfig(peer, core) 179 180 By("restarting peers with couchDB") 181 setup.startPeers() 182 }) 183 184 AfterEach(func() { 185 couchProcess.Signal(syscall.SIGTERM) 186 Eventually(couchProcess.Wait(), setup.network.EventuallyTimeout).Should(Receive()) 187 }) 188 189 It("calls marbles APIs", func() { 190 peer := setup.network.Peer("Org2", "peer0") 191 192 By("deploying new lifecycle chaincode") 193 nwo.EnableCapabilities(setup.network, setup.channelID, "Application", "V2_0", setup.orderer, setup.peers...) 194 helper.deployChaincode(chaincode) 195 196 By("verifying marbles chaincode APIs") 197 assertMarbleAPIs(chaincode.Name, peer) 198 199 By("verifying marbles rich queries") 200 assertMarbleAPIsRichQueries(chaincode.Name, peer) 201 }) 202 }) 203 }) 204 205 // marble is the struct to unmarshal the response bytes returned from getMarble API 206 type marble struct { 207 ObjectType string `json:"docType"` // docType is "marble" 208 Name string `json:"name"` 209 Color string `json:"color"` 210 Size int `json:"size"` 211 Owner string `json:"owner"` 212 } 213 214 // marbleQueryResult is the struct to unmarshal the response bytes returned from marbles query APIs 215 type marbleQueryResult struct { 216 Key string `json:"Key"` 217 Record *marble `json:"Record"` 218 } 219 220 type metadata struct { 221 RecordsCount string `json:"RecordsCount"` 222 Bookmark string `json:"Bookmark"` 223 } 224 225 // marbleQueryResult is the struct to unmarshal the metadata bytes returned from marbles pagination APIs 226 type paginationMetadata struct { 227 ResponseMetadata *metadata `json:"ResponseMetadata"` 228 } 229 230 // marbleHistoryResult is the struct to unmarshal the response bytes returned from marbles history API 231 type marbleHistoryResult struct { 232 TxId string `json:"TxId"` 233 Value *marble `json:"Value"` 234 Timestamp string `json:"Timestamp"` 235 IsDelete string `json:"IsDelete"` 236 } 237 238 // newMarble creates a marble object for the given parameters 239 func newMarble(name, color string, size int, owner string) *marble { 240 return &marble{"marble", name, color, size, owner} 241 } 242 243 // newMarbleQueryResult creates a slice of marbleQueryResult for the marbles based on startIndex and endIndex. 244 // Both startIndex and endIndex are inclusive 245 func newMarbleQueryResult(startIndex, endIndex int, color string, size int, owner string) []*marbleQueryResult { 246 expectedResult := make([]*marbleQueryResult, 0) 247 for i := startIndex; i <= endIndex; i++ { 248 name := fmt.Sprintf("marble-%d", i) 249 item := marbleQueryResult{Key: name, Record: newMarble(name, color, size, owner)} 250 expectedResult = append(expectedResult, &item) 251 } 252 return expectedResult 253 } 254 255 // marblesTestHelper implements helper methods to call marbles chaincode APIs and verify results 256 type marblesTestHelper struct { 257 *networkHelper 258 } 259 260 // invokeMarblesChaincode invokes marbles APIs such as initMarble, transfer and delete. 261 func (th *marblesTestHelper) invokeMarblesChaincode(chaincodeName string, peer *nwo.Peer, funcAndArgs ...string) { 262 command := commands.ChaincodeInvoke{ 263 ChannelID: th.channelID, 264 Orderer: th.OrdererAddress(th.orderer, nwo.ListenPort), 265 Name: chaincodeName, 266 Ctor: prepareChaincodeInvokeArgs(funcAndArgs...), 267 PeerAddresses: []string{ 268 th.PeerAddress(peer, nwo.ListenPort), 269 }, 270 WaitForEvent: true, 271 } 272 th.invokeChaincode(peer, command) 273 nwo.WaitUntilEqualLedgerHeight(th.Network, th.channelID, nwo.GetLedgerHeight(th.Network, peer, th.channelID), th.peers...) 274 } 275 276 // assertMarbleExists asserts that the marble exists and matches the expected result 277 func (th *marblesTestHelper) assertMarbleExists(chaincodeName string, peer *nwo.Peer, expectedResult *marble, marbleName string) { 278 command := commands.ChaincodeQuery{ 279 ChannelID: th.channelID, 280 Name: chaincodeName, 281 Ctor: fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName), 282 } 283 sess, err := th.PeerUserSession(peer, "User1", command) 284 Expect(err).NotTo(HaveOccurred()) 285 Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0)) 286 result := &marble{} 287 err = json.Unmarshal(sess.Out.Contents(), result) 288 Expect(err).NotTo(HaveOccurred()) 289 Expect(result).To(Equal(expectedResult)) 290 } 291 292 // assertMarbleDoesNotExist asserts that the marble does not exist 293 func (th *marblesTestHelper) assertMarbleDoesNotExist(peer *nwo.Peer, chaincodeName, marbleName string) { 294 command := commands.ChaincodeQuery{ 295 ChannelID: th.channelID, 296 Name: chaincodeName, 297 Ctor: fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName), 298 } 299 th.queryChaincode(peer, command, "Marble does not exist", false) 300 } 301 302 // assertQueryMarbles queries the chaincode and verifies the result based on the function and arguments, 303 // including range queries and rich queries. 304 func (th *marblesTestHelper) assertQueryMarbles(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleQueryResult, funcAndArgs ...string) { 305 command := commands.ChaincodeQuery{ 306 ChannelID: th.channelID, 307 Name: chaincodeName, 308 Ctor: prepareChaincodeInvokeArgs(funcAndArgs...), 309 } 310 sess, err := th.PeerUserSession(peer, "User1", command) 311 Expect(err).NotTo(HaveOccurred()) 312 Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0)) 313 results := make([]*marbleQueryResult, 0) 314 err = json.Unmarshal(sess.Out.Contents(), &results) 315 Expect(err).NotTo(HaveOccurred()) 316 Expect(results).To(Equal(expectedResult)) 317 } 318 319 // assertQueryMarbles queries the chaincode with pagination and verifies the result based on the function and arguments, 320 // including range queries and rich queries. 321 func (th *marblesTestHelper) assertQueryMarblesWithPagination(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleQueryResult, funcAndArgs ...string) string { 322 command := commands.ChaincodeQuery{ 323 ChannelID: th.channelID, 324 Name: chaincodeName, 325 Ctor: prepareChaincodeInvokeArgs(funcAndArgs...), 326 } 327 sess, err := th.PeerUserSession(peer, "User1", command) 328 Expect(err).NotTo(HaveOccurred()) 329 Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0)) 330 331 // response bytes contains 2 json arrays: [{"Key":...}][{"ResponseMetadata": ...}] 332 responseBytes := sess.Out.Contents() 333 index := bytes.LastIndex(responseBytes, []byte("][")) 334 335 // unmarshal and verify response result 336 results := make([]*marbleQueryResult, 0) 337 err = json.Unmarshal(responseBytes[:index+1], &results) 338 Expect(err).NotTo(HaveOccurred()) 339 Expect(results).To(Equal(expectedResult)) 340 341 // unmarshal ResponseMetadata and return bookmark to the caller for next call 342 respMetadata := make([]*paginationMetadata, 0) 343 err = json.Unmarshal(responseBytes[index+1:], &respMetadata) 344 Expect(err).NotTo(HaveOccurred()) 345 Expect(respMetadata).To(HaveLen(1)) 346 347 return respMetadata[0].ResponseMetadata.Bookmark 348 } 349 350 // assertGetHistoryForMarble queries the history for a specific marble and verifies the result 351 func (th *marblesTestHelper) assertGetHistoryForMarble(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleHistoryResult, marbleName string) { 352 command := commands.ChaincodeQuery{ 353 ChannelID: th.channelID, 354 Name: chaincodeName, 355 Ctor: fmt.Sprintf(`{"Args":["getHistoryForMarble","%s"]}`, marbleName), 356 } 357 sess, err := th.PeerUserSession(peer, "User1", command) 358 Expect(err).NotTo(HaveOccurred()) 359 Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0)) 360 361 // unmarshal bytes and verify history 362 results := make([]*marbleHistoryResult, 0) 363 err = json.Unmarshal(sess.Out.Contents(), &results) 364 Expect(err).NotTo(HaveOccurred()) 365 Expect(results).To(HaveLen(len(expectedResult))) 366 for i, result := range results { 367 Expect(result.IsDelete).To(Equal(expectedResult[i].IsDelete)) 368 if result.IsDelete == "true" { 369 Expect(result.Value).To(BeNil()) 370 continue 371 } 372 Expect(result.Value).To(Equal(expectedResult[i].Value)) 373 } 374 }