github.com/anjalikarhana/fabric@v2.1.1+incompatible/internal/peer/lifecycle/chaincode/approveformyorg_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package chaincode_test
     8  
     9  import (
    10  	"context"
    11  	"crypto/tls"
    12  	"time"
    13  
    14  	pb "github.com/hyperledger/fabric-protos-go/peer"
    15  	"github.com/hyperledger/fabric/bccsp/sw"
    16  	"github.com/hyperledger/fabric/internal/peer/lifecycle/chaincode"
    17  	"github.com/hyperledger/fabric/internal/peer/lifecycle/chaincode/mock"
    18  	"github.com/pkg/errors"
    19  	"github.com/spf13/cobra"
    20  	"google.golang.org/grpc"
    21  
    22  	. "github.com/onsi/ginkgo"
    23  	. "github.com/onsi/gomega"
    24  )
    25  
    26  var _ = Describe("ApproverForMyOrg", func() {
    27  	Describe("Approve", func() {
    28  		var (
    29  			mockProposalResponse *pb.ProposalResponse
    30  			mockEndorserClient   *mock.EndorserClient
    31  			mockEndorserClients  []chaincode.EndorserClient
    32  			mockDeliverClient    *mock.PeerDeliverClient
    33  			mockSigner           *mock.Signer
    34  			certificate          tls.Certificate
    35  			mockBroadcastClient  *mock.BroadcastClient
    36  			input                *chaincode.ApproveForMyOrgInput
    37  			approver             *chaincode.ApproverForMyOrg
    38  		)
    39  
    40  		BeforeEach(func() {
    41  			mockEndorserClient = &mock.EndorserClient{}
    42  
    43  			mockProposalResponse = &pb.ProposalResponse{
    44  				Response: &pb.Response{
    45  					Status: 200,
    46  				},
    47  				Endorsement: &pb.Endorsement{},
    48  			}
    49  			mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
    50  			mockEndorserClients = []chaincode.EndorserClient{mockEndorserClient}
    51  
    52  			mockDeliverClient = &mock.PeerDeliverClient{}
    53  			input = &chaincode.ApproveForMyOrgInput{
    54  				ChannelID: "testchannel",
    55  				Name:      "testcc",
    56  				Version:   "1.0",
    57  				PackageID: "testpackageid",
    58  				Sequence:  1,
    59  			}
    60  
    61  			mockSigner = &mock.Signer{}
    62  
    63  			certificate = tls.Certificate{}
    64  			mockBroadcastClient = &mock.BroadcastClient{}
    65  
    66  			approver = &chaincode.ApproverForMyOrg{
    67  				Certificate:     certificate,
    68  				BroadcastClient: mockBroadcastClient,
    69  				DeliverClients:  []pb.DeliverClient{mockDeliverClient},
    70  				EndorserClients: mockEndorserClients,
    71  				Input:           input,
    72  				Signer:          mockSigner,
    73  			}
    74  		})
    75  
    76  		It("approves a chaincode definition for an organization", func() {
    77  			err := approver.Approve()
    78  			Expect(err).NotTo(HaveOccurred())
    79  		})
    80  
    81  		Context("when the channel name is not provided", func() {
    82  			BeforeEach(func() {
    83  				approver.Input.ChannelID = ""
    84  			})
    85  
    86  			It("returns an error", func() {
    87  				err := approver.Approve()
    88  				Expect(err).To(MatchError("The required parameter 'channelID' is empty. Rerun the command with -C flag"))
    89  			})
    90  		})
    91  
    92  		Context("when the chaincode name is not provided", func() {
    93  			BeforeEach(func() {
    94  				approver.Input.Name = ""
    95  			})
    96  
    97  			It("returns an error", func() {
    98  				err := approver.Approve()
    99  				Expect(err).To(MatchError("The required parameter 'name' is empty. Rerun the command with -n flag"))
   100  			})
   101  		})
   102  
   103  		Context("when the chaincode version is not provided", func() {
   104  			BeforeEach(func() {
   105  				approver.Input.Version = ""
   106  			})
   107  
   108  			It("returns an error", func() {
   109  				err := approver.Approve()
   110  				Expect(err).To(MatchError("The required parameter 'version' is empty. Rerun the command with -v flag"))
   111  			})
   112  		})
   113  
   114  		Context("when the sequence is not provided", func() {
   115  			BeforeEach(func() {
   116  				approver.Input.Sequence = 0
   117  			})
   118  
   119  			It("returns an error", func() {
   120  				err := approver.Approve()
   121  				Expect(err).To(MatchError("The required parameter 'sequence' is empty. Rerun the command with --sequence flag"))
   122  			})
   123  		})
   124  
   125  		Context("when the signer cannot be serialized", func() {
   126  			BeforeEach(func() {
   127  				mockSigner.SerializeReturns(nil, errors.New("cafe"))
   128  			})
   129  
   130  			It("returns an error", func() {
   131  				err := approver.Approve()
   132  				Expect(err).To(MatchError("failed to create proposal: failed to serialize identity: cafe"))
   133  			})
   134  		})
   135  
   136  		Context("when the signer fails to sign the proposal", func() {
   137  			BeforeEach(func() {
   138  				mockSigner.SignReturns(nil, errors.New("tea"))
   139  			})
   140  
   141  			It("returns an error", func() {
   142  				err := approver.Approve()
   143  				Expect(err).To(MatchError("failed to create signed proposal: tea"))
   144  			})
   145  		})
   146  
   147  		Context("when the endorser fails to endorse the proposal", func() {
   148  			BeforeEach(func() {
   149  				mockEndorserClient.ProcessProposalReturns(nil, errors.New("latte"))
   150  			})
   151  
   152  			It("returns an error", func() {
   153  				err := approver.Approve()
   154  				Expect(err).To(MatchError("failed to endorse proposal: latte"))
   155  			})
   156  		})
   157  
   158  		Context("when no endorser clients are set", func() {
   159  			BeforeEach(func() {
   160  				approver.EndorserClients = nil
   161  			})
   162  
   163  			It("doesn't receive any responses and returns an error", func() {
   164  				err := approver.Approve()
   165  				Expect(err).To(MatchError("no proposal responses received"))
   166  			})
   167  		})
   168  
   169  		Context("when the endorser returns a nil proposal response", func() {
   170  			BeforeEach(func() {
   171  				mockProposalResponse = nil
   172  				mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
   173  			})
   174  
   175  			It("returns an error", func() {
   176  				err := approver.Approve()
   177  				Expect(err).To(MatchError("received nil proposal response"))
   178  			})
   179  		})
   180  
   181  		Context("when the endorser returns a proposal response with a nil response", func() {
   182  			BeforeEach(func() {
   183  				mockProposalResponse.Response = nil
   184  				mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
   185  			})
   186  
   187  			It("returns an error", func() {
   188  				err := approver.Approve()
   189  				Expect(err).To(MatchError("received proposal response with nil response"))
   190  			})
   191  		})
   192  
   193  		Context("when the endorser returns a non-success status", func() {
   194  			BeforeEach(func() {
   195  				mockProposalResponse.Response = &pb.Response{
   196  					Status:  500,
   197  					Message: "capuccino",
   198  				}
   199  				mockEndorserClient.ProcessProposalReturns(mockProposalResponse, nil)
   200  			})
   201  
   202  			It("returns an error", func() {
   203  				err := approver.Approve()
   204  				Expect(err).To(MatchError("proposal failed with status: 500 - capuccino"))
   205  			})
   206  		})
   207  
   208  		Context("when the signer fails to sign the transaction", func() {
   209  			BeforeEach(func() {
   210  				mockSigner.SignReturnsOnCall(1, nil, errors.New("peaberry"))
   211  			})
   212  
   213  			It("returns an error", func() {
   214  				err := approver.Approve()
   215  				Expect(err).To(MatchError("failed to create signed transaction: peaberry"))
   216  				Expect(mockSigner.SignCallCount()).To(Equal(2))
   217  			})
   218  		})
   219  
   220  		Context("when the broadcast client fails to send the envelope", func() {
   221  			BeforeEach(func() {
   222  				mockBroadcastClient.SendReturns(errors.New("arabica"))
   223  			})
   224  
   225  			It("returns an error", func() {
   226  				err := approver.Approve()
   227  				Expect(err).To(MatchError("failed to send transaction: arabica"))
   228  			})
   229  		})
   230  
   231  		Context("when the wait for event flag is enabled and the transaction is committed", func() {
   232  			BeforeEach(func() {
   233  				input.WaitForEvent = true
   234  				input.WaitForEventTimeout = 3 * time.Second
   235  				input.TxID = "testtx"
   236  				input.PeerAddresses = []string{"approvepeer0"}
   237  				mockDeliverClient.DeliverFilteredStub = func(ctx context.Context, opts ...grpc.CallOption) (pb.Deliver_DeliverFilteredClient, error) {
   238  					mockDF := &mock.Deliver{}
   239  					resp := &pb.DeliverResponse{
   240  						Type: &pb.DeliverResponse_FilteredBlock{
   241  							FilteredBlock: createFilteredBlock(input.ChannelID, "testtx"),
   242  						},
   243  					}
   244  					mockDF.RecvReturns(resp, nil)
   245  					return mockDF, nil
   246  				}
   247  			})
   248  
   249  			It("waits for the event containing the txid", func() {
   250  				err := approver.Approve()
   251  				Expect(err).NotTo(HaveOccurred())
   252  			})
   253  		})
   254  
   255  		Context("when the wait for event flag is enabled and the client can't connect", func() {
   256  			BeforeEach(func() {
   257  				input.WaitForEvent = true
   258  				input.WaitForEventTimeout = 3 * time.Second
   259  				input.TxID = "testtx"
   260  				input.PeerAddresses = []string{"approvepeer0"}
   261  				mockDeliverClient.DeliverFilteredReturns(nil, errors.New("robusta"))
   262  			})
   263  
   264  			It("returns an error", func() {
   265  				err := approver.Approve()
   266  				Expect(err).To(MatchError("failed to connect to deliver on all peers: error connecting to deliver filtered at approvepeer0: robusta"))
   267  			})
   268  		})
   269  
   270  		Context("when the wait for event flag is enabled and the transaction isn't returned before the timeout", func() {
   271  			BeforeEach(func() {
   272  				input.WaitForEvent = true
   273  				input.WaitForEventTimeout = 10 * time.Millisecond
   274  				input.TxID = "testtx"
   275  				input.PeerAddresses = []string{"approvepeer0"}
   276  				delayChan := make(chan struct{})
   277  				mockDeliverClient.DeliverFilteredStub = func(ctx context.Context, opts ...grpc.CallOption) (pb.Deliver_DeliverFilteredClient, error) {
   278  					mockDF := &mock.Deliver{}
   279  					mockDF.RecvStub = func() (*pb.DeliverResponse, error) {
   280  						<-delayChan
   281  						resp := &pb.DeliverResponse{
   282  							Type: &pb.DeliverResponse_FilteredBlock{
   283  								FilteredBlock: createFilteredBlock(input.ChannelID, "testtx"),
   284  							},
   285  						}
   286  						return resp, nil
   287  					}
   288  					return mockDF, nil
   289  				}
   290  			})
   291  
   292  			It("returns an error", func() {
   293  				err := approver.Approve()
   294  				Expect(err).To(MatchError("timed out waiting for txid on all peers"))
   295  			})
   296  		})
   297  	})
   298  
   299  	Describe("ApproveForMyOrgCmd", func() {
   300  		var (
   301  			approveForMyOrgCmd *cobra.Command
   302  		)
   303  
   304  		BeforeEach(func() {
   305  			cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
   306  			Expect(err).To(BeNil())
   307  			approveForMyOrgCmd = chaincode.ApproveForMyOrgCmd(nil, cryptoProvider)
   308  			approveForMyOrgCmd.SetArgs([]string{
   309  				"--channelID=testchannel",
   310  				"--name=testcc",
   311  				"--version=testversion",
   312  				"--package-id=testpackageid",
   313  				"--sequence=1",
   314  				"--peerAddresses=querypeer1",
   315  				"--tlsRootCertFiles=tls1",
   316  				"--signature-policy=AND ('Org1MSP.member','Org2MSP.member')",
   317  			})
   318  		})
   319  
   320  		AfterEach(func() {
   321  			chaincode.ResetFlags()
   322  		})
   323  
   324  		It("sets up the approver for my org and attempts to approve the chaincode definition", func() {
   325  			err := approveForMyOrgCmd.Execute()
   326  			Expect(err).To(MatchError(ContainSubstring("failed to retrieve endorser client")))
   327  		})
   328  
   329  		Context("when the channel config policy is specified", func() {
   330  			BeforeEach(func() {
   331  				cryptoProvider, err := sw.NewDefaultSecurityLevelWithKeystore(sw.NewDummyKeyStore())
   332  				Expect(err).To(BeNil())
   333  				approveForMyOrgCmd = chaincode.ApproveForMyOrgCmd(nil, cryptoProvider)
   334  				approveForMyOrgCmd.SetArgs([]string{
   335  					"--channel-config-policy=/Channel/Application/Readers",
   336  					"--channelID=testchannel",
   337  					"--name=testcc",
   338  					"--version=testversion",
   339  					"--package-id=testpackageid",
   340  					"--sequence=1",
   341  					"--peerAddresses=querypeer1",
   342  					"--tlsRootCertFiles=tls1",
   343  				})
   344  			})
   345  
   346  			It("sets up the approver for my org and attempts to approve the chaincode definition", func() {
   347  				err := approveForMyOrgCmd.Execute()
   348  				Expect(err).To(MatchError(ContainSubstring("failed to retrieve endorser client")))
   349  			})
   350  		})
   351  
   352  		Context("when a signature policy and channel config policy are both specified", func() {
   353  			BeforeEach(func() {
   354  				approveForMyOrgCmd.SetArgs([]string{
   355  					"--signature-policy=a_policy",
   356  					"--channel-config-policy=anotha_policy",
   357  					"--channelID=testchannel",
   358  					"--name=testcc",
   359  					"--version=testversion",
   360  					"--package-id=testpackageid",
   361  					"--sequence=1",
   362  					"--peerAddresses=querypeer1",
   363  					"--tlsRootCertFiles=tls1",
   364  				})
   365  			})
   366  
   367  			It("returns an error", func() {
   368  				err := approveForMyOrgCmd.Execute()
   369  				Expect(err).To(MatchError("cannot specify both \"--signature-policy\" and \"--channel-config-policy\""))
   370  			})
   371  		})
   372  
   373  		Context("when the signature policy is invalid", func() {
   374  			BeforeEach(func() {
   375  				approveForMyOrgCmd.SetArgs([]string{
   376  					"--signature-policy=notapolicy",
   377  					"--channelID=testchannel",
   378  					"--name=testcc",
   379  					"--version=testversion",
   380  					"--package-id=testpackageid",
   381  					"--sequence=1",
   382  					"--peerAddresses=querypeer1",
   383  					"--tlsRootCertFiles=tls1",
   384  				})
   385  			})
   386  
   387  			It("returns an error", func() {
   388  				err := approveForMyOrgCmd.Execute()
   389  				Expect(err).To(MatchError("invalid signature policy: notapolicy"))
   390  			})
   391  		})
   392  
   393  		Context("when the collections config is invalid", func() {
   394  			BeforeEach(func() {
   395  				approveForMyOrgCmd.SetArgs([]string{
   396  					"--collections-config=idontexist.json",
   397  					"--channelID=testchannel",
   398  					"--name=testcc",
   399  					"--version=testversion",
   400  					"--package-id=testpackageid",
   401  					"--sequence=1",
   402  					"--peerAddresses=querypeer1",
   403  					"--tlsRootCertFiles=tls1",
   404  				})
   405  			})
   406  
   407  			It("returns an error", func() {
   408  				err := approveForMyOrgCmd.Execute()
   409  				Expect(err).To(MatchError("invalid collection configuration in file idontexist.json: could not read file 'idontexist.json': open idontexist.json: no such file or directory"))
   410  			})
   411  		})
   412  	})
   413  })
   414  
   415  func createFilteredBlock(channelID string, txIDs ...string) *pb.FilteredBlock {
   416  	var filteredTransactions []*pb.FilteredTransaction
   417  	for _, txID := range txIDs {
   418  		ft := &pb.FilteredTransaction{
   419  			Txid:             txID,
   420  			TxValidationCode: pb.TxValidationCode_VALID,
   421  		}
   422  		filteredTransactions = append(filteredTransactions, ft)
   423  	}
   424  	fb := &pb.FilteredBlock{
   425  		Number:               0,
   426  		ChannelId:            channelID,
   427  		FilteredTransactions: filteredTransactions,
   428  	}
   429  	return fb
   430  }