Skip to content

Commit 2275367

Browse files
committed
[FAB-16525] Integration tests for marbles APIs
This CR includes - FAB-16525 IT test additions to exercise all APIs of marbles chaincode - FAB-16304 Integration tests for GetHistoryForKey Change-Id: Ib7512c9503b76e69a9c88b92acbfe4251b7d507a Signed-off-by: Wenjian Qiao <wenjianq@gmail.com>
1 parent b5c1e3c commit 2275367

File tree

2 files changed

+398
-3
lines changed

2 files changed

+398
-3
lines changed

integration/ledger/marbles_test.go

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
/*
2+
Copyright IBM Corp 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/hyperledger/fabric/integration/nwo"
17+
"github.com/hyperledger/fabric/integration/nwo/commands"
18+
"github.com/hyperledger/fabric/integration/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 _ bool = 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/hyperledger/fabric/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.waitUntilEqualLedgerHeight(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+
&marbleHistoryResult{IsDelete: "true"},
101+
&marbleHistoryResult{IsDelete: "false", Value: newMarble("marble-0", "blue", 35, "jerry")},
102+
&marbleHistoryResult{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 (
159+
couchProcess ifrit.Process
160+
)
161+
162+
BeforeEach(func() {
163+
By("stopping peers")
164+
setup.stopPeers()
165+
166+
By("configuring a peer with couchdb")
167+
// configure only one of the peers (org2, peer0) to use couchdb.
168+
// Note that we do not support a channel with mixed DBs.
169+
// However, for testing, it would be fine to use couchdb for one
170+
// peer and sending all the couchdb related test queries to this peer
171+
couchDB := &runner.CouchDB{}
172+
couchProcess = ifrit.Invoke(couchDB)
173+
Eventually(couchProcess.Ready(), runner.DefaultStartTimeout).Should(BeClosed())
174+
Consistently(couchProcess.Wait()).ShouldNot(Receive())
175+
couchAddr := couchDB.Address()
176+
peer := setup.network.Peer("org2", "peer0")
177+
core := setup.network.ReadPeerConfig(peer)
178+
core.Ledger.State.StateDatabase = "CouchDB"
179+
core.Ledger.State.CouchDBConfig.CouchDBAddress = couchAddr
180+
setup.network.WritePeerConfig(peer, core)
181+
182+
By("restarting peers with couchDB")
183+
setup.startPeers()
184+
})
185+
186+
AfterEach(func() {
187+
couchProcess.Signal(syscall.SIGTERM)
188+
Eventually(couchProcess.Wait(), setup.network.EventuallyTimeout).Should(Receive())
189+
})
190+
191+
It("calls marbles APIs", func() {
192+
peer := setup.network.Peer("org2", "peer0")
193+
194+
By("deploying new lifecycle chaincode")
195+
nwo.EnableCapabilities(setup.network, setup.channelID, "Application", "V2_0", setup.orderer, setup.peers...)
196+
helper.deployChaincode(chaincode)
197+
198+
By("verifying marbles chaincode APIs")
199+
assertMarbleAPIs(chaincode.Name, peer)
200+
201+
By("verifying marbles rich queries")
202+
assertMarbleAPIsRichQueries(chaincode.Name, peer)
203+
})
204+
})
205+
})
206+
207+
type chaincode struct {
208+
nwo.Chaincode
209+
isLegacy bool
210+
}
211+
212+
// marble is the struct to unmarshal the response bytes returned from getMarble API
213+
type marble struct {
214+
ObjectType string `json:"docType"` //docType is "marble"
215+
Name string `json:"name"`
216+
Color string `json:"color"`
217+
Size int `json:"size"`
218+
Owner string `json:"owner"`
219+
}
220+
221+
// marbleQueryResult is the struct to unmarshal the response bytes returned from marbles query APIs
222+
type marbleQueryResult struct {
223+
Key string `json:"Key"`
224+
Record *marble `json:"Record"`
225+
}
226+
227+
type metadata struct {
228+
RecordsCount string `json:"RecordsCount"`
229+
Bookmark string `json:"Bookmark"`
230+
}
231+
232+
// marbleQueryResult is the struct to unmarshal the metadata bytes returned from marbles pagination APIs
233+
type paginationMetadata struct {
234+
ResponseMetadata *metadata `json:"ResponseMetadata"`
235+
}
236+
237+
// marbleHistoryResult is the struct to unmarshal the response bytes returned from marbles history API
238+
type marbleHistoryResult struct {
239+
TxId string `json:"TxId"`
240+
Value *marble `json:"Value"`
241+
Timestamp string `json:"Timestamp"`
242+
IsDelete string `json:"IsDelete"`
243+
}
244+
245+
// newMarble creates a marble object for the given parameters
246+
func newMarble(name, color string, size int, owner string) *marble {
247+
return &marble{"marble", name, color, size, owner}
248+
}
249+
250+
// newMarbleQueryResult creates a slice of marbleQueryResult for the marbles based on startIndex and endIndex.
251+
// Both startIndex and endIndex are inclusive
252+
func newMarbleQueryResult(startIndex, endIndex int, color string, size int, owner string) []*marbleQueryResult {
253+
expectedResult := make([]*marbleQueryResult, 0)
254+
for i := startIndex; i <= endIndex; i++ {
255+
name := fmt.Sprintf("marble-%d", i)
256+
item := marbleQueryResult{Key: name, Record: newMarble(name, color, size, owner)}
257+
expectedResult = append(expectedResult, &item)
258+
}
259+
return expectedResult
260+
}
261+
262+
// marblesTestHelper implements helper methods to call marbles chaincode APIs and verify results
263+
type marblesTestHelper struct {
264+
*networkHelper
265+
}
266+
267+
// invokeMarblesChaincode invokes marbles APIs such as initMarble, transfer and delete.
268+
func (th *marblesTestHelper) invokeMarblesChaincode(chaincodeName string, peer *nwo.Peer, funcAndArgs ...string) {
269+
command := commands.ChaincodeInvoke{
270+
ChannelID: th.channelID,
271+
Orderer: th.OrdererAddress(th.orderer, nwo.ListenPort),
272+
Name: chaincodeName,
273+
Ctor: prepareChaincodeInvokeArgs(funcAndArgs...),
274+
PeerAddresses: []string{
275+
th.PeerAddress(peer, nwo.ListenPort),
276+
},
277+
WaitForEvent: true,
278+
}
279+
th.invokeChaincode(peer, command)
280+
nwo.WaitUntilEqualLedgerHeight(th.Network, th.channelID, nwo.GetLedgerHeight(th.Network, peer, th.channelID), th.peers...)
281+
}
282+
283+
// assertMarbleExists asserts that the marble exists and matches the expected result
284+
func (th *marblesTestHelper) assertMarbleExists(chaincodeName string, peer *nwo.Peer, expectedResult *marble, marbleName string) {
285+
command := commands.ChaincodeQuery{
286+
ChannelID: th.channelID,
287+
Name: chaincodeName,
288+
Ctor: fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName),
289+
}
290+
sess, err := th.PeerUserSession(peer, "User1", command)
291+
Expect(err).NotTo(HaveOccurred())
292+
Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0))
293+
result := &marble{}
294+
err = json.Unmarshal(sess.Out.Contents(), result)
295+
Expect(err).NotTo(HaveOccurred())
296+
Expect(result).To(Equal(expectedResult))
297+
298+
}
299+
300+
// assertMarbleDoesNotExist asserts that the marble does not exist
301+
func (th *marblesTestHelper) assertMarbleDoesNotExist(peer *nwo.Peer, chaincodeName, marbleName string) {
302+
command := commands.ChaincodeQuery{
303+
ChannelID: th.channelID,
304+
Name: chaincodeName,
305+
Ctor: fmt.Sprintf(`{"Args":["readMarble","%s"]}`, marbleName),
306+
}
307+
th.queryChaincode(peer, command, "Marble does not exist", false)
308+
}
309+
310+
// assertQueryMarbles queries the chaincode and verifies the result based on the function and arguments,
311+
// including range queries and rich queries.
312+
func (th *marblesTestHelper) assertQueryMarbles(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleQueryResult, funcAndArgs ...string) {
313+
command := commands.ChaincodeQuery{
314+
ChannelID: th.channelID,
315+
Name: chaincodeName,
316+
Ctor: prepareChaincodeInvokeArgs(funcAndArgs...),
317+
}
318+
sess, err := th.PeerUserSession(peer, "User1", command)
319+
Expect(err).NotTo(HaveOccurred())
320+
Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0))
321+
results := make([]*marbleQueryResult, 0)
322+
err = json.Unmarshal(sess.Out.Contents(), &results)
323+
Expect(err).NotTo(HaveOccurred())
324+
Expect(results).To(Equal(expectedResult))
325+
}
326+
327+
// assertQueryMarbles queries the chaincode with pagination and verifies the result based on the function and arguments,
328+
// including range queries and rich queries.
329+
func (th *marblesTestHelper) assertQueryMarblesWithPagination(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleQueryResult, funcAndArgs ...string) string {
330+
command := commands.ChaincodeQuery{
331+
ChannelID: th.channelID,
332+
Name: chaincodeName,
333+
Ctor: prepareChaincodeInvokeArgs(funcAndArgs...),
334+
}
335+
sess, err := th.PeerUserSession(peer, "User1", command)
336+
Expect(err).NotTo(HaveOccurred())
337+
Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0))
338+
339+
// response bytes contains 2 json arrays: [{"Key":...}][{"ResponseMetadata": ...}]
340+
responseBytes := sess.Out.Contents()
341+
index := bytes.LastIndex(responseBytes, []byte("]["))
342+
343+
// unmarshal and verify response result
344+
results := make([]*marbleQueryResult, 0)
345+
err = json.Unmarshal(responseBytes[:index+1], &results)
346+
Expect(err).NotTo(HaveOccurred())
347+
Expect(results).To(Equal(expectedResult))
348+
349+
// unmarshal ResponseMetadata and return bookmark to the caller for next call
350+
respMetadata := make([]*paginationMetadata, 0)
351+
err = json.Unmarshal(responseBytes[index+1:], &respMetadata)
352+
Expect(err).NotTo(HaveOccurred())
353+
Expect(respMetadata).To(HaveLen(1))
354+
355+
return respMetadata[0].ResponseMetadata.Bookmark
356+
}
357+
358+
// assertGetHistoryForMarble queries the history for a specific marble and verifies the result
359+
func (th *marblesTestHelper) assertGetHistoryForMarble(chaincodeName string, peer *nwo.Peer, expectedResult []*marbleHistoryResult, marbleName string) {
360+
command := commands.ChaincodeQuery{
361+
ChannelID: th.channelID,
362+
Name: chaincodeName,
363+
Ctor: fmt.Sprintf(`{"Args":["getHistoryForMarble","%s"]}`, marbleName),
364+
}
365+
sess, err := th.PeerUserSession(peer, "User1", command)
366+
Expect(err).NotTo(HaveOccurred())
367+
Eventually(sess, th.EventuallyTimeout).Should(gexec.Exit(0))
368+
369+
// unmarshal bytes and verify history
370+
results := make([]*marbleHistoryResult, 0)
371+
err = json.Unmarshal(sess.Out.Contents(), &results)
372+
Expect(err).NotTo(HaveOccurred())
373+
Expect(results).To(HaveLen(len(expectedResult)))
374+
for i, result := range results {
375+
Expect(result.IsDelete).To(Equal(expectedResult[i].IsDelete))
376+
if result.IsDelete == "true" {
377+
Expect(result.Value).To(BeNil())
378+
continue
379+
}
380+
Expect(result.Value).To(Equal(expectedResult[i].Value))
381+
}
382+
}

0 commit comments

Comments
 (0)