360 lines
11 KiB
Go
360 lines
11 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
package chaincode_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
pb "github.com/hyperledger/fabric-protos-go/peer"
|
|
lb "github.com/hyperledger/fabric-protos-go/peer/lifecycle"
|
|
"github.com/hyperledger/fabric/bccsp/sw"
|
|
"github.com/hyperledger/fabric/internal/peer/lifecycle/chaincode"
|
|
"github.com/hyperledger/fabric/internal/peer/lifecycle/chaincode/mock"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"github.com/onsi/gomega/gbytes"
|
|
)
|
|
|
|
var _ = Describe("QueryCommitted", func() {
|
|
Describe("CommittedQuerier", func() {
|
|
var (
|
|
mockProposalResponse *pb.ProposalResponse
|
|
mockEndorserClient *mock.EndorserClient
|
|
mockSigner *mock.Signer
|
|
input *chaincode.CommittedQueryInput
|
|
committedQuerier *chaincode.CommittedQuerier
|
|
)
|
|
|
|
BeforeEach(func() {
|
|
mockResult := &lb.QueryChaincodeDefinitionsResult{
|
|
ChaincodeDefinitions: []*lb.QueryChaincodeDefinitionsResult_ChaincodeDefinition{
|
|
{
|
|
Name: "woohoo",
|
|
Sequence: 93,
|
|
Version: "a-version",
|
|
EndorsementPlugin: "e-plugin",
|
|
ValidationPlugin: "v-plugin",
|
|
},
|
|
{
|
|
Name: "yahoo",
|
|
Sequence: 20,
|
|
Version: "another-version",
|
|
EndorsementPlugin: "e-plugin",
|
|
ValidationPlugin: "v-plugin",
|
|
},
|
|
},
|
|
}
|
|
|
|
mockResultBytes, err := proto.Marshal(mockResult)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(mockResultBytes).NotTo(BeNil())
|
|
mockProposalResponse = &pb.ProposalResponse{
|
|
Response: &pb.Response{
|
|
Status: 200,
|
|
Payload: mockResultBytes,
|
|
},
|
|
}
|
|
|
|
mockEndorserClient = &mock.EndorserClient{}
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
|
|
mockSigner = &mock.Signer{}
|
|
buffer := gbytes.NewBuffer()
|
|
|
|
input = &chaincode.CommittedQueryInput{
|
|
ChannelID: "test-channel",
|
|
}
|
|
|
|
committedQuerier = &chaincode.CommittedQuerier{
|
|
Input: input,
|
|
EndorserClient: mockEndorserClient,
|
|
Signer: mockSigner,
|
|
Writer: buffer,
|
|
}
|
|
})
|
|
|
|
It("queries committed chaincodes and writes the output as human readable plain-text", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(committedQuerier.Writer).Should(gbytes.Say("Committed chaincode definitions on channel 'test-channel':\n"))
|
|
Eventually(committedQuerier.Writer).Should(gbytes.Say("Name: woohoo, Version: a-version, Sequence: 93, Endorsement Plugin: e-plugin, Validation Plugin: v-plugin\n"))
|
|
Eventually(committedQuerier.Writer).Should(gbytes.Say("Name: yahoo, Version: another-version, Sequence: 20, Endorsement Plugin: e-plugin, Validation Plugin: v-plugin\n"))
|
|
})
|
|
|
|
Context("when JSON-formatted output is requested", func() {
|
|
BeforeEach(func() {
|
|
committedQuerier.Input.OutputFormat = "json"
|
|
})
|
|
|
|
It("queries committed chaincodes and writes the output as JSON", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
expectedOutput := &lb.QueryChaincodeDefinitionsResult{
|
|
ChaincodeDefinitions: []*lb.QueryChaincodeDefinitionsResult_ChaincodeDefinition{
|
|
{
|
|
Name: "woohoo",
|
|
Sequence: 93,
|
|
Version: "a-version",
|
|
EndorsementPlugin: "e-plugin",
|
|
ValidationPlugin: "v-plugin",
|
|
},
|
|
{
|
|
Name: "yahoo",
|
|
Sequence: 20,
|
|
Version: "another-version",
|
|
EndorsementPlugin: "e-plugin",
|
|
ValidationPlugin: "v-plugin",
|
|
},
|
|
},
|
|
}
|
|
json, err := json.MarshalIndent(expectedOutput, "", "\t")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(committedQuerier.Writer).Should(gbytes.Say(fmt.Sprintf(`\Q%s\E`, string(json))))
|
|
})
|
|
})
|
|
|
|
Context("when a single chaincode definition is requested", func() {
|
|
BeforeEach(func() {
|
|
input.Name = "test-cc"
|
|
|
|
mockResult := &lb.QueryChaincodeDefinitionResult{
|
|
Sequence: 93,
|
|
Version: "a-version",
|
|
EndorsementPlugin: "e-plugin",
|
|
ValidationPlugin: "v-plugin",
|
|
Approvals: map[string]bool{
|
|
"whatkindoforgisthis": true,
|
|
"nowaydoiapprove": false,
|
|
},
|
|
}
|
|
|
|
mockResultBytes, err := proto.Marshal(mockResult)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(mockResultBytes).NotTo(BeNil())
|
|
mockProposalResponse = &pb.ProposalResponse{
|
|
Response: &pb.Response{
|
|
Status: 200,
|
|
Payload: mockResultBytes,
|
|
},
|
|
}
|
|
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
})
|
|
|
|
It("queries the committed chaincode and writes the output as human readable plain-text", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(committedQuerier.Writer).Should(gbytes.Say("Committed chaincode definition for chaincode 'test-cc' on channel 'test-channel'"))
|
|
Eventually(committedQuerier.Writer).Should(gbytes.Say(`\QVersion: a-version, Sequence: 93, Endorsement Plugin: e-plugin, Validation Plugin: v-plugin, Approvals: [nowaydoiapprove: false, whatkindoforgisthis: true]\E`))
|
|
})
|
|
|
|
Context("when the payload contains bytes that aren't a QueryChaincodeDefinitionResult", func() {
|
|
BeforeEach(func() {
|
|
mockProposalResponse.Response = &pb.Response{
|
|
Payload: []byte("badpayloadbadpayload"),
|
|
Status: 200,
|
|
}
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError(ContainSubstring("failed to unmarshal proposal response's response payload")))
|
|
})
|
|
})
|
|
|
|
Context("when JSON-formatted output is requested", func() {
|
|
BeforeEach(func() {
|
|
committedQuerier.Input.OutputFormat = "json"
|
|
})
|
|
|
|
It("queries the committed chaincodes and writes the output as JSON", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).NotTo(HaveOccurred())
|
|
expectedOutput := &lb.QueryChaincodeDefinitionResult{
|
|
Sequence: 93,
|
|
Version: "a-version",
|
|
EndorsementPlugin: "e-plugin",
|
|
ValidationPlugin: "v-plugin",
|
|
Approvals: map[string]bool{
|
|
"whatkindoforgisthis": true,
|
|
"nowaydoiapprove": false,
|
|
},
|
|
}
|
|
json, err := json.MarshalIndent(expectedOutput, "", "\t")
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Eventually(committedQuerier.Writer).Should(gbytes.Say(fmt.Sprintf(`\Q%s\E`, string(json))))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("when the channel is not provided", func() {
|
|
BeforeEach(func() {
|
|
committedQuerier.Input.ChannelID = ""
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError("channel name must be specified"))
|
|
})
|
|
})
|
|
|
|
Context("when the signer cannot be serialized", func() {
|
|
BeforeEach(func() {
|
|
mockSigner.SerializeReturns(nil, errors.New("cafe"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError("failed to create proposal: failed to serialize identity: cafe"))
|
|
})
|
|
})
|
|
|
|
Context("when the signer fails to sign the proposal", func() {
|
|
BeforeEach(func() {
|
|
mockSigner.SignReturns(nil, errors.New("tea"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError("failed to create signed proposal: tea"))
|
|
})
|
|
})
|
|
|
|
Context("when the endorser fails to endorse the proposal", func() {
|
|
BeforeEach(func() {
|
|
mockEndorserClient.ProcessProposalReturns(nil, errors.New("latte"))
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError("failed to endorse proposal: latte"))
|
|
})
|
|
})
|
|
|
|
Context("when the endorser returns a nil proposal response", func() {
|
|
BeforeEach(func() {
|
|
mockProposalResponse = nil
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError("received nil proposal response"))
|
|
})
|
|
})
|
|
|
|
Context("when the endorser returns a proposal response with a nil response", func() {
|
|
BeforeEach(func() {
|
|
mockProposalResponse.Response = nil
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError("received proposal response with nil response"))
|
|
})
|
|
})
|
|
|
|
Context("when the endorser returns a non-success status", func() {
|
|
BeforeEach(func() {
|
|
mockProposalResponse.Response = &pb.Response{
|
|
Status: 500,
|
|
Message: "capuccino",
|
|
}
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError("query failed with status: 500 - capuccino"))
|
|
})
|
|
})
|
|
|
|
Context("when the payload contains bytes that aren't a QueryChaincodeDefinitionsResult", func() {
|
|
BeforeEach(func() {
|
|
mockProposalResponse.Response = &pb.Response{
|
|
Payload: []byte("badpayloadbadpayload"),
|
|
Status: 200,
|
|
}
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError(ContainSubstring("failed to unmarshal proposal response's response payload")))
|
|
})
|
|
})
|
|
|
|
Context("when the payload contains bytes that aren't a QueryChaincodeDefinitionsResult and JSON-output is requested", func() {
|
|
BeforeEach(func() {
|
|
mockProposalResponse.Response = &pb.Response{
|
|
Payload: []byte("badpayloadbadpayload"),
|
|
Status: 200,
|
|
}
|
|
mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
|
|
committedQuerier.Input.OutputFormat = "json"
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := committedQuerier.Query()
|
|
Expect(err).To(MatchError(ContainSubstring("failed to unmarshal proposal response's response payload as type *lifecycle.QueryChaincodeDefinitionsResult")))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("QueryCommittedCmd", func() {
|
|
var queryCommittedCmd *cobra.Command
|
|
|
|
BeforeEach(func() {
|
|
cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
|
|
Expect(err).To(BeNil())
|
|
queryCommittedCmd = chaincode.QueryCommittedCmd(nil, cryptoProvider)
|
|
queryCommittedCmd.SilenceErrors = true
|
|
queryCommittedCmd.SilenceUsage = true
|
|
queryCommittedCmd.SetArgs([]string{
|
|
"--name=testcc",
|
|
"--channelID=testchannel",
|
|
"--peerAddresses=querycommittedpeer1",
|
|
"--tlsRootCertFiles=tls1",
|
|
})
|
|
})
|
|
|
|
AfterEach(func() {
|
|
chaincode.ResetFlags()
|
|
})
|
|
|
|
It("attempts to connect to the endorser", func() {
|
|
err := queryCommittedCmd.Execute()
|
|
Expect(err).To(MatchError(ContainSubstring("failed to retrieve endorser client")))
|
|
})
|
|
|
|
Context("when more than one peer address is provided", func() {
|
|
BeforeEach(func() {
|
|
queryCommittedCmd.SetArgs([]string{
|
|
"--name=testcc",
|
|
"--channelID=testchannel",
|
|
"--peerAddresses=querycommittedpeer1",
|
|
"--tlsRootCertFiles=tls1",
|
|
"--peerAddresses=querycommittedpeer2",
|
|
"--tlsRootCertFiles=tls2",
|
|
})
|
|
})
|
|
|
|
It("returns an error", func() {
|
|
err := queryCommittedCmd.Execute()
|
|
Expect(err).To(MatchError(ContainSubstring("failed to validate peer connection parameters")))
|
|
})
|
|
})
|
|
})
|
|
})
|