diff --git a/.golangci.yml b/.golangci.yml index 1e52fc3d9..b6f06ac84 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,6 +19,7 @@ linters: - govet - ineffassign - misspell + - testifylint - typecheck - unused diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 000000000..1c9454373 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,13 @@ +with-expecter: true +exported: true +filename: "{{.InterfaceName | lower}}_mock_test.go" +mockname: "Mock{{.InterfaceName | firstUpper}}" +unroll-variadic: false +packages: + google.golang.org/grpc: + config: + dir: pkg/client + outpkg: client + interfaces: + ClientConnInterface: + ClientStream: diff --git a/Makefile b/Makefile index 24c00a595..96fe72f9a 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,10 @@ scenario_dir := $(base_dir)/scenario go_bin_dir := $(shell go env GOPATH)/bin +mockery_version := 2.46.2 +kernel_name := $(shell uname -s) +machine_hardware := $(shell uname -m) + export SOFTHSM2_CONF ?= $(base_dir)/softhsm2.conf TMPDIR ?= /tmp @@ -130,10 +134,18 @@ scan-java-osv-scanner: go install github.com/google/osv-scanner/cmd/osv-scanner@latest osv-scanner scan --lockfile='$(java_dir)/pom.xml' +.PHONY: install-mockery +install-mockery: + curl --fail --location \ + 'https://github.com/vektra/mockery/releases/download/v$(mockery_version)/mockery_$(mockery_version)_$(kernel_name)_$(machine_hardware).tar.gz' \ + | tar -C '$(go_bin_dir)' -xzf - mockery + +$(go_bin_dir)/mockery: + $(MAKE) install-mockery + .PHONY: generate -generate: - go install go.uber.org/mock/mockgen@latest - go generate '$(go_dir)/...' +generate: $(go_bin_dir)/mockery clean-generated + cd '$(base_dir)' && mockery .PHONY: vendor-chaincode vendor-chaincode: diff --git a/go.mod b/go.mod index e1dc34340..ae4125ec4 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,13 @@ go 1.22.0 require ( github.com/cucumber/godog v0.14.1 - github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 + github.com/google/go-cmp v0.6.0 + github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 github.com/miekg/pkcs11 v1.1.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 - go.uber.org/mock v0.4.0 - golang.org/x/crypto v0.27.0 - google.golang.org/grpc v1.67.0 + golang.org/x/crypto v0.28.0 + google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.34.2 ) @@ -23,9 +23,10 @@ require ( github.com/hashicorp/go-memdb v1.3.4 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect golang.org/x/net v0.28.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f89ff07ec..7182b770b 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3 h1:Xpd6fzG/KjAOHJsq7EQXY2l+qi/y8muxBaY7R6QWABk= -github.com/hyperledger/fabric-protos-go-apiv2 v0.3.3/go.mod h1:2pq0ui6ZWA0cC8J+eCErgnMDCS1kPOEYVY+06ZAK0qE= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4 h1:YJrd+gMaeY0/vsN0aS0QkEKTivGoUnSRIXxGJ7KI+Pc= +github.com/hyperledger/fabric-protos-go-apiv2 v0.3.4/go.mod h1:bau/6AJhvEcu9GKKYHlDXAxXKzYNfhP6xu2GXuxEcFk= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -46,26 +46,26 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/java/pom.xml b/java/pom.xml index 4eb670dcb..c8bdf588d 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -40,7 +40,7 @@ 8 1.78.1 ${skipTests} - 7.5.0 + 7.6.0 @@ -48,29 +48,29 @@ io.cucumber cucumber-bom - 7.19.0 + 7.20.0 pom import org.junit junit-bom - 5.11.0 + 5.11.2 pom import io.grpc grpc-bom - 1.68.0 + 1.67.1 pom import - + com.google.protobuf protobuf-bom - 3.25.5 + 4.28.2 pom import @@ -97,7 +97,7 @@ org.mockito mockito-core - 5.13.0 + 5.14.1 test @@ -146,7 +146,7 @@ org.hyperledger.fabric fabric-protos - 0.3.3 + 0.3.4 @@ -530,7 +530,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.5 + 3.2.6 sign-artifacts diff --git a/node/package.json b/node/package.json index ca5f18cae..539890521 100644 --- a/node/package.json +++ b/node/package.json @@ -57,6 +57,6 @@ "ts-jest": "^29.2.4", "typedoc": "^0.26.6", "typescript": "~5.5.4", - "typescript-eslint": "~8.5.0" + "typescript-eslint": "^8.7.0" } } diff --git a/pkg/client/blockevents_test.go b/pkg/client/blockevents_test.go index dd28af3af..2fd7f08d1 100644 --- a/pkg/client/blockevents_test.go +++ b/pkg/client/blockevents_test.go @@ -5,369 +5,17 @@ package client import ( "context" - "errors" "testing" - "github.com/hyperledger/fabric-gateway/pkg/internal/test" "github.com/hyperledger/fabric-protos-go-apiv2/common" - "github.com/hyperledger/fabric-protos-go-apiv2/msp" - "github.com/hyperledger/fabric-protos-go-apiv2/orderer" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" ) -func AssertValidBlockEventRequestHeader(t *testing.T, payload *common.Payload, expectedChannel string) { - channelHeader := &common.ChannelHeader{} - test.AssertUnmarshal(t, payload.GetHeader().GetChannelHeader(), channelHeader) - - require.Equal(t, expectedChannel, channelHeader.GetChannelId(), "channel name") - - signatureHeader := &common.SignatureHeader{} - test.AssertUnmarshal(t, payload.GetHeader().GetSignatureHeader(), signatureHeader) - - expectedCreator := &msp.SerializedIdentity{ - Mspid: TestCredentials.Identity().MspID(), - IdBytes: TestCredentials.Identity().Credentials(), - } - actualCreator := &msp.SerializedIdentity{} - test.AssertUnmarshal(t, signatureHeader.GetCreator(), actualCreator) - test.AssertProtoEqual(t, expectedCreator, actualCreator) -} - func TestBlockEvents(t *testing.T) { - t.Run("Returns connect error", func(t *testing.T) { - expected := NewStatusError(t, codes.Aborted, "BLOCK_EVENTS_ERROR") - mockClient := NewMockDeliverClient(gomock.NewController(t)) - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(nil, expected) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - _, err := network.BlockEvents(ctx) - - require.Equal(t, status.Code(expected), status.Code(err), "status code") - require.ErrorIs(t, err, expected, "error type: %T", err) - require.ErrorContains(t, err, expected.Error(), "message") - }) - - t.Run("Sends valid request with default start position", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - _, err := network.BlockEvents(ctx) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_NextCommit{ - NextCommit: &orderer.SeekNextCommit{}, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Sends valid request with specified start block number", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - _, err := network.BlockEvents(ctx, WithStartBlock(418)) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 418, - }, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Uses specified start block instead of unset checkpoint", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - - _, err := network.BlockEvents(ctx, WithStartBlock(418), WithCheckpoint(checkpointer)) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 418, - }, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Uses checkpoint block instead of specified start block", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - checkpointer.CheckpointBlock(uint64(500)) - - _, err := network.BlockEvents(ctx, WithStartBlock(418), WithCheckpoint(checkpointer)) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 501, - }, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Uses checkpoint block zero with set transaction ID instead of specified start block", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - blockNumber := uint64(0) - checkpointer.CheckpointTransaction(blockNumber, "transctionId") - - _, err := network.BlockEvents(ctx, WithStartBlock(418), WithCheckpoint(checkpointer)) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: blockNumber, - }, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Uses default start position with unset checkpoint and no start block", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - - _, err := network.BlockEvents(ctx, WithCheckpoint(checkpointer)) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_NextCommit{ - NextCommit: &orderer.SeekNextCommit{}, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Closes event channel on receive error", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - receive, err := network.BlockEvents(ctx, WithStartBlock(418)) - require.NoError(t, err) - - actual, ok := <-receive - - require.False(t, ok, "Expected event listening to be cancelled, got %v", actual) - }) - t.Run("Receives events", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil) - - blocks := []*common.Block{ + expected := []*common.Block{ { Header: &common.BlockHeader{ Number: 1, @@ -389,46 +37,32 @@ func TestBlockEvents(t *testing.T) { }, }, } - responseIndex := 0 - mockEvents.EXPECT().Recv(). - DoAndReturn(func() (*peer.DeliverResponse, error) { - if responseIndex >= len(blocks) { - return nil, errors.New("fake") - } - response := &peer.DeliverResponse{ - Type: &peer.DeliverResponse_Block{ - Block: blocks[responseIndex], - }, - } - responseIndex++ - return response, nil - }). - AnyTimes() + + var responses []*peer.DeliverResponse + for _, block := range expected { + responses = append(responses, &peer.DeliverResponse{ + Type: &peer.DeliverResponse_Block{ + Block: block, + }, + }) + } + + tester := NewBlockEventsTest(t) + tester.SetResponses(responses...) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - receive, err := network.BlockEvents(ctx) + events, err := tester.Events(ctx) require.NoError(t, err) - for _, event := range blocks { - actual := <-receive - test.AssertProtoEqual(t, event, actual) + for _, event := range expected { + actual := <-events + AssertProtoEqual(t, event, actual) } }) t.Run("Closes event channel on non-block message", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil) - block := &common.Block{ Header: &common.BlockHeader{ Number: 1, @@ -456,102 +90,25 @@ func TestBlockEvents(t *testing.T) { }, }, } - responseIndex := 0 - mockEvents.EXPECT().Recv(). - DoAndReturn(func() (*peer.DeliverResponse, error) { - if responseIndex >= len(responses) { - return nil, errors.New("fake") - } - response := responses[responseIndex] - responseIndex++ - return response, nil - }). - AnyTimes() + + tester := NewBlockEventsTest(t) + tester.SetResponses(responses...) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - receive, err := network.BlockEvents(ctx) + events, err := tester.Events(ctx) require.NoError(t, err) - expected := []*common.Block{ + // proto.Message must be used to match the type of events channel; otherwise nil comparisons will fail + expected := []proto.Message{ block, nil, nil, } for _, event := range expected { - actual := <-receive - test.AssertProtoEqual(t, event, actual) + actual := <-events + AssertProtoEqual(t, event, actual) } }) - - t.Run("Uses specified gRPC call options", func(t *testing.T) { - var actual []grpc.CallOption - expected := grpc.WaitForReady(true) - - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, opts ...grpc.CallOption) { - actual = opts - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil). - AnyTimes() - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - request, err := network.NewBlockEventsRequest() - require.NoError(t, err, "NewBlockEventsRequest") - - _, err = request.Events(ctx, expected) - require.NoError(t, err, "Events") - - require.Contains(t, actual, expected, "CallOptions") - }) - - t.Run("Sends request with TLS client certificate hash", func(t *testing.T) { - expected := []byte("TLS_CLIENT_CERTIFICATE_HASH") - - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient), WithTLSClientCertificateHash(expected)) - _, err := network.BlockEvents(ctx) - require.NoError(t, err) - - channelHeader := &common.ChannelHeader{} - test.AssertUnmarshal(t, payload.GetHeader().GetChannelHeader(), channelHeader) - - require.Equal(t, expected, channelHeader.GetTlsCertHash()) - }) } diff --git a/pkg/client/blockeventscommon_test.go b/pkg/client/blockeventscommon_test.go new file mode 100644 index 000000000..f03e67d32 --- /dev/null +++ b/pkg/client/blockeventscommon_test.go @@ -0,0 +1,423 @@ +// Copyright IBM Corp. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package client + +import ( + "context" + "errors" + "io" + "testing" + + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-protos-go-apiv2/orderer" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +func TestCommonBlockEvents(t *testing.T) { + for eventType, newTester := range map[string]func(*testing.T) blockEventsTester{ + "Block": func(t *testing.T) blockEventsTester { + return NewBlockEventsTest(t) + }, + "FilteredBlock": func(t *testing.T) blockEventsTester { + return NewFilteredBlockEventsTest(t) + }, + "BlockAndPrivateData": func(t *testing.T) blockEventsTester { + return NewBlockAndPrivateDataEventsTest(t) + }, + } { + t.Run(eventType, func(t *testing.T) { + t.Run("Returns connect error", func(t *testing.T) { + expected := NewStatusError(t, codes.Aborted, "BLOCK_EVENTS_ERROR") + + tester := newTester(t) + tester.SetConnectError(expected) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, err := tester.Events(ctx) + + require.Equal(t, status.Code(expected), status.Code(err), "status code") + require.ErrorIs(t, err, expected, "error type: %T", err) + require.ErrorContains(t, err, expected.Error(), "message") + }) + }) + + for testName, testCase := range map[string]struct { + options []BlockEventsOption + expected *orderer.SeekInfo + }{ + "Sends valid request with default start position": { + options: nil, + expected: &orderer.SeekInfo{ + Start: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_NextCommit{ + NextCommit: &orderer.SeekNextCommit{}, + }, + }, + Stop: seekLargestBlockNumber(), + }, + }, + "Sends valid request with specified start block number": { + options: []BlockEventsOption{ + WithStartBlock(418), + }, + expected: &orderer.SeekInfo{ + Start: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 418, + }, + }, + }, + Stop: seekLargestBlockNumber(), + }, + }, + "Uses specified start block instead of unset checkpoint": { + options: []BlockEventsOption{ + WithStartBlock(418), + WithCheckpoint(new(InMemoryCheckpointer)), + }, + expected: &orderer.SeekInfo{ + Start: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 418, + }, + }, + }, + Stop: seekLargestBlockNumber(), + }, + }, + "Uses checkpoint block instead of specified start block": { + options: func() []BlockEventsOption { + checkpointer := new(InMemoryCheckpointer) + checkpointer.CheckpointBlock(500) + return []BlockEventsOption{ + WithStartBlock(418), + WithCheckpoint(checkpointer), + } + }(), + expected: &orderer.SeekInfo{ + Start: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 501, + }, + }, + }, + Stop: seekLargestBlockNumber(), + }, + }, + "Uses checkpoint block zero with set transaction ID instead of specified start block": { + options: func() []BlockEventsOption { + checkpointer := new(InMemoryCheckpointer) + checkpointer.CheckpointTransaction(0, "transctionId") + return []BlockEventsOption{ + WithStartBlock(418), + WithCheckpoint(checkpointer), + } + }(), + expected: &orderer.SeekInfo{ + Start: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 0, + }, + }, + }, + Stop: seekLargestBlockNumber(), + }, + }, + "Uses default start position with unset checkpoint and no start block": { + options: []BlockEventsOption{ + WithCheckpoint(new(InMemoryCheckpointer)), + }, + expected: &orderer.SeekInfo{ + Start: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_NextCommit{ + NextCommit: &orderer.SeekNextCommit{}, + }, + }, + Stop: seekLargestBlockNumber(), + }, + }, + } { + t.Run(testName, func(t *testing.T) { + networkName := "NETWORK" + + tester := newTester(t) + tester.SetNetworkName(networkName) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, err := tester.Events(ctx, testCase.options...) + require.NoError(t, err) + + payload := &common.Payload{} + AssertUnmarshal(t, (<-tester.Requests()).GetPayload(), payload) + AssertValidBlockEventRequestHeader(t, payload, networkName) + actual := &orderer.SeekInfo{} + AssertUnmarshal(t, payload.GetData(), actual) + + AssertProtoEqual(t, testCase.expected, actual) + }) + } + + t.Run("Closes event channel on receive error", func(t *testing.T) { + tester := newTester(t) + tester.SetReceiveError(errors.New("fake")) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + events, err := tester.Events(ctx, WithStartBlock(418)) + require.NoError(t, err) + + actual, ok := <-events + + require.False(t, ok, "Expected event listening to be cancelled, got %v", actual) + }) + + t.Run("Uses specified gRPC call options", func(t *testing.T) { + expected := grpc.WaitForReady(true) + + tester := newTester(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, err := tester.EventsWithCallOptions(ctx, expected) + require.NoError(t, err) + + require.Contains(t, (<-tester.CallOptions()), expected, "CallOptions") + }) + + t.Run("Sends request with TLS client certificate hash", func(t *testing.T) { + expected := []byte("TLS_CLIENT_CERTIFICATE_HASH") + + tester := newTester(t) + tester.SetGatewayOptions(WithTLSClientCertificateHash(expected)) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + _, err := tester.Events(ctx) + require.NoError(t, err) + + payload := &common.Payload{} + AssertUnmarshal(t, (<-tester.Requests()).GetPayload(), payload) + channelHeader := &common.ChannelHeader{} + AssertUnmarshal(t, payload.GetHeader().GetChannelHeader(), channelHeader) + + require.Equal(t, expected, channelHeader.GetTlsCertHash()) + }) + } +} + +type blockEventsTester interface { + SetConnectError(error) + SetNetworkName(string) + SetGatewayOptions(...ConnectOption) + SetReceiveError(error) + SetResponses(...*peer.DeliverResponse) + Events(context.Context, ...BlockEventsOption) (<-chan proto.Message, error) + Requests() <-chan *common.Envelope + EventsWithCallOptions(context.Context, ...grpc.CallOption) (<-chan proto.Message, error) + CallOptions() <-chan []grpc.CallOption +} + +type baseBlockEventsTest struct { + t *testing.T + connectError error + networkName string + gatewayOptions []ConnectOption + receiveError error + responses []*peer.DeliverResponse + requests chan *common.Envelope + callOptions chan []grpc.CallOption +} + +type blockEventsTest struct { + baseBlockEventsTest +} + +func (b *baseBlockEventsTest) SetConnectError(err error) { + b.connectError = err +} + +func (b *baseBlockEventsTest) SetNetworkName(channel string) { + b.networkName = channel +} + +func (b *baseBlockEventsTest) SetGatewayOptions(options ...ConnectOption) { + b.gatewayOptions = append(b.gatewayOptions, options...) +} + +func (b *baseBlockEventsTest) SetReceiveError(err error) { + b.receiveError = err +} + +func (b *baseBlockEventsTest) SetResponses(responses ...*peer.DeliverResponse) { + b.responses = append(b.responses, responses...) +} + +func (b *baseBlockEventsTest) Requests() <-chan *common.Envelope { + return b.requests +} + +func (b *baseBlockEventsTest) CallOptions() <-chan []grpc.CallOption { + return b.callOptions +} + +func (b *baseBlockEventsTest) deliverOptions() []newStreamFunction { + var result []newStreamFunction + if b.connectError != nil { + result = append(result, WithNewStreamError(b.connectError)) + } else { + mockStream := NewMockClientStream(b.t) + b.callOptions = make(chan []grpc.CallOption, 1) + result = append(result, CaptureNewStreamOptions(b.callOptions), WithNewStreamResult(mockStream)) + + b.requests = make(chan *common.Envelope, 1) + ExpectSendMsg(mockStream, CaptureSendMsg(b.requests)) + mockStream.EXPECT().CloseSend().Maybe().Return(nil) + + if b.receiveError != nil { + ExpectRecvMsg(mockStream).Return(b.receiveError) + } else if len(b.responses) > 0 { + ExpectRecvMsg(mockStream, WithRecvMsgs(b.responses...)) + } else { + ExpectRecvMsg(mockStream).Maybe().Return(io.EOF) + } + } + + return result +} + +func (b *baseBlockEventsTest) newNetwork(connection grpc.ClientConnInterface) *Network { + options := []ConnectOption{ + WithClientConnection(connection), + } + options = append(options, b.gatewayOptions...) + return AssertNewTestNetwork(b.t, b.networkName, options...) +} + +func NewBlockEventsTest(t *testing.T) *blockEventsTest { + return &blockEventsTest{ + baseBlockEventsTest{ + t: t, + }, + } +} + +func (b *blockEventsTest) Events(ctx context.Context, options ...BlockEventsOption) (<-chan proto.Message, error) { + mockConnection := NewMockClientConnInterface(b.t) + ExpectDeliver(mockConnection, b.deliverOptions()...) + + network := b.newNetwork(mockConnection) + events, err := network.BlockEvents(ctx, options...) + + return asProtoMessageChannel(events), err +} + +func (b *blockEventsTest) EventsWithCallOptions(ctx context.Context, options ...grpc.CallOption) (<-chan proto.Message, error) { + mockConnection := NewMockClientConnInterface(b.t) + ExpectDeliver(mockConnection, b.deliverOptions()...) + + network := b.newNetwork(mockConnection) + request, err := network.NewBlockEventsRequest() + require.NoError(b.t, err, "NewBlockEventsRequest") + + events, err := request.Events(ctx, options...) + + return asProtoMessageChannel(events), err +} + +type filteredBlockEventsTest struct { + baseBlockEventsTest +} + +func NewFilteredBlockEventsTest(t *testing.T) *filteredBlockEventsTest { + return &filteredBlockEventsTest{ + baseBlockEventsTest{ + t: t, + }, + } +} + +func (b *filteredBlockEventsTest) Events(ctx context.Context, options ...BlockEventsOption) (<-chan proto.Message, error) { + mockConnection := NewMockClientConnInterface(b.t) + ExpectDeliverFiltered(mockConnection, b.deliverOptions()...) + + network := b.newNetwork(mockConnection) + events, err := network.FilteredBlockEvents(ctx, options...) + + return asProtoMessageChannel(events), err +} + +func (b *filteredBlockEventsTest) EventsWithCallOptions(ctx context.Context, options ...grpc.CallOption) (<-chan proto.Message, error) { + mockConnection := NewMockClientConnInterface(b.t) + ExpectDeliverFiltered(mockConnection, b.deliverOptions()...) + + network := b.newNetwork(mockConnection) + request, err := network.NewFilteredBlockEventsRequest() + require.NoError(b.t, err, "NewFilteredBlockEventsRequest") + + events, err := request.Events(ctx, options...) + + return asProtoMessageChannel(events), err +} + +type blockAndPrivateDataEventsTest struct { + baseBlockEventsTest +} + +func NewBlockAndPrivateDataEventsTest(t *testing.T) *blockAndPrivateDataEventsTest { + return &blockAndPrivateDataEventsTest{ + baseBlockEventsTest{ + t: t, + }, + } +} + +func (b *blockAndPrivateDataEventsTest) Events(ctx context.Context, options ...BlockEventsOption) (<-chan proto.Message, error) { + mockConnection := NewMockClientConnInterface(b.t) + ExpectDeliverWithPrivateData(mockConnection, b.deliverOptions()...) + + network := b.newNetwork(mockConnection) + events, err := network.BlockAndPrivateDataEvents(ctx, options...) + + return asProtoMessageChannel(events), err +} + +func (b *blockAndPrivateDataEventsTest) EventsWithCallOptions(ctx context.Context, options ...grpc.CallOption) (<-chan proto.Message, error) { + mockConnection := NewMockClientConnInterface(b.t) + ExpectDeliverWithPrivateData(mockConnection, b.deliverOptions()...) + + network := b.newNetwork(mockConnection) + request, err := network.NewBlockAndPrivateDataEventsRequest() + require.NoError(b.t, err, "NewBlockAndPrivateDataEventsRequest") + + events, err := request.Events(ctx, options...) + + return asProtoMessageChannel(events), err +} + +func asProtoMessageChannel[T proto.Message](events <-chan T) <-chan proto.Message { + result := make(chan proto.Message) + go func() { + for event := range events { + result <- event + } + close(result) + }() + return result +} diff --git a/pkg/client/blockeventsfiltered_test.go b/pkg/client/blockeventsfiltered_test.go index fe3014c10..aa8fbd51d 100644 --- a/pkg/client/blockeventsfiltered_test.go +++ b/pkg/client/blockeventsfiltered_test.go @@ -5,161 +5,17 @@ package client import ( "context" - "errors" "testing" - "github.com/hyperledger/fabric-gateway/pkg/internal/test" "github.com/hyperledger/fabric-protos-go-apiv2/common" - "github.com/hyperledger/fabric-protos-go-apiv2/orderer" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" ) func TestFilteredBlockEvents(t *testing.T) { - t.Run("Returns connect error", func(t *testing.T) { - expected := NewStatusError(t, codes.Aborted, "BLOCK_EVENTS_ERROR") - mockClient := NewMockDeliverClient(gomock.NewController(t)) - mockClient.EXPECT().DeliverFiltered(gomock.Any(), gomock.Any()). - Return(nil, expected) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - _, err := network.FilteredBlockEvents(ctx) - - require.Equal(t, status.Code(expected), status.Code(err), "status code") - require.ErrorIs(t, err, expected, "error type: %T", err) - require.ErrorContains(t, err, expected.Error(), "message") - }) - - t.Run("Sends valid request with default start position", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverFilteredClient(controller) - - mockClient.EXPECT().DeliverFiltered(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - _, err := network.FilteredBlockEvents(ctx) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_NextCommit{ - NextCommit: &orderer.SeekNextCommit{}, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Sends valid request with specified start block number", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverFilteredClient(controller) - - mockClient.EXPECT().DeliverFiltered(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - _, err := network.FilteredBlockEvents(ctx, WithStartBlock(418)) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 418, - }, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Closes event channel on receive error", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverFilteredClient(controller) - - mockClient.EXPECT().DeliverFiltered(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - receive, err := network.FilteredBlockEvents(ctx, WithStartBlock(418)) - require.NoError(t, err) - - actual, ok := <-receive - - require.False(t, ok, "Expected event listening to be cancelled, got %v", actual) - }) - t.Run("Receives events", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverFilteredClient(controller) - - mockClient.EXPECT().DeliverFiltered(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil) - - blocks := []*peer.FilteredBlock{ + expected := []*peer.FilteredBlock{ { ChannelId: "NETWORK", Number: 1, @@ -179,46 +35,32 @@ func TestFilteredBlockEvents(t *testing.T) { }, }, } - responseIndex := 0 - mockEvents.EXPECT().Recv(). - DoAndReturn(func() (*peer.DeliverResponse, error) { - if responseIndex >= len(blocks) { - return nil, errors.New("fake") - } - response := &peer.DeliverResponse{ - Type: &peer.DeliverResponse_FilteredBlock{ - FilteredBlock: blocks[responseIndex], - }, - } - responseIndex++ - return response, nil - }). - AnyTimes() + + var responses []*peer.DeliverResponse + for _, block := range expected { + responses = append(responses, &peer.DeliverResponse{ + Type: &peer.DeliverResponse_FilteredBlock{ + FilteredBlock: block, + }, + }) + } + + tester := NewFilteredBlockEventsTest(t) + tester.SetResponses(responses...) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - receive, err := network.FilteredBlockEvents(ctx) + events, err := tester.Events(ctx) require.NoError(t, err) - for _, event := range blocks { - actual := <-receive - test.AssertProtoEqual(t, event, actual) + for _, event := range expected { + actual := <-events + AssertProtoEqual(t, event, actual) } }) t.Run("Closes event channel on non-block message", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverFilteredClient(controller) - - mockClient.EXPECT().DeliverFiltered(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil) - block := &peer.FilteredBlock{ ChannelId: "NETWORK", Number: 1, @@ -245,102 +87,25 @@ func TestFilteredBlockEvents(t *testing.T) { }, }, } - responseIndex := 0 - mockEvents.EXPECT().Recv(). - DoAndReturn(func() (*peer.DeliverResponse, error) { - if responseIndex >= len(responses) { - return nil, errors.New("fake") - } - response := responses[responseIndex] - responseIndex++ - return response, nil - }). - AnyTimes() + + tester := NewFilteredBlockEventsTest(t) + tester.SetResponses(responses...) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - receive, err := network.FilteredBlockEvents(ctx) + events, err := tester.Events(ctx) require.NoError(t, err) - expected := []*peer.FilteredBlock{ + // proto.Message must be used to match the type of events channel; otherwise nil comparisons will fail + expected := []proto.Message{ block, nil, nil, } for _, event := range expected { - actual := <-receive - test.AssertProtoEqual(t, event, actual) + actual := <-events + AssertProtoEqual(t, event, actual) } }) - - t.Run("Uses specified gRPC call options", func(t *testing.T) { - var actual []grpc.CallOption - expected := grpc.WaitForReady(true) - - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().DeliverFiltered(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, opts ...grpc.CallOption) { - actual = opts - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil). - AnyTimes() - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - request, err := network.NewFilteredBlockEventsRequest() - require.NoError(t, err, "NewFilteredBlockEventsRequest") - - _, err = request.Events(ctx, expected) - require.NoError(t, err, "Events") - - require.Contains(t, actual, expected, "CallOptions") - }) - - t.Run("Sends request with TLS client certificate hash", func(t *testing.T) { - expected := []byte("TLS_CLIENT_CERTIFICATE_HASH") - - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().DeliverFiltered(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient), WithTLSClientCertificateHash(expected)) - _, err := network.FilteredBlockEvents(ctx) - require.NoError(t, err) - - channelHeader := &common.ChannelHeader{} - test.AssertUnmarshal(t, payload.GetHeader().GetChannelHeader(), channelHeader) - - require.Equal(t, expected, channelHeader.GetTlsCertHash()) - }) } diff --git a/pkg/client/blockeventswithprivatedata_test.go b/pkg/client/blockeventswithprivatedata_test.go index 34e9ce04e..c051569f4 100644 --- a/pkg/client/blockeventswithprivatedata_test.go +++ b/pkg/client/blockeventswithprivatedata_test.go @@ -5,162 +5,18 @@ package client import ( "context" - "errors" "testing" - "github.com/hyperledger/fabric-gateway/pkg/internal/test" "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/ledger/rwset" - "github.com/hyperledger/fabric-protos-go-apiv2/orderer" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" ) func TestBlockAndPrivateDataEvents(t *testing.T) { - t.Run("Returns connect error", func(t *testing.T) { - expected := NewStatusError(t, codes.Aborted, "BLOCK_EVENTS_ERROR") - mockClient := NewMockDeliverClient(gomock.NewController(t)) - mockClient.EXPECT().DeliverWithPrivateData(gomock.Any(), gomock.Any()). - Return(nil, expected) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - _, err := network.BlockAndPrivateDataEvents(ctx) - - require.Equal(t, status.Code(expected), status.Code(err), "status code") - require.ErrorIs(t, err, expected, "error type: %T", err) - require.ErrorContains(t, err, expected.Error(), "message") - }) - - t.Run("Sends valid request with default start position", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverWithPrivateDataClient(controller) - - mockClient.EXPECT().DeliverWithPrivateData(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - _, err := network.BlockAndPrivateDataEvents(ctx) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_NextCommit{ - NextCommit: &orderer.SeekNextCommit{}, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Sends valid request with specified start block number", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverWithPrivateDataClient(controller) - - mockClient.EXPECT().DeliverWithPrivateData(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - _, err := network.BlockAndPrivateDataEvents(ctx, WithStartBlock(418)) - require.NoError(t, err) - - AssertValidBlockEventRequestHeader(t, payload, network.Name()) - actual := &orderer.SeekInfo{} - test.AssertUnmarshal(t, payload.GetData(), actual) - - expected := &orderer.SeekInfo{ - Start: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 418, - }, - }, - }, - Stop: seekLargestBlockNumber(), - } - - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Closes event channel on receive error", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverWithPrivateDataClient(controller) - - mockClient.EXPECT().DeliverWithPrivateData(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - receive, err := network.BlockAndPrivateDataEvents(ctx, WithStartBlock(418)) - require.NoError(t, err) - - actual, ok := <-receive - - require.False(t, ok, "Expected event listening to be cancelled, got %v", actual) - }) - t.Run("Receives events", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverWithPrivateDataClient(controller) - - mockClient.EXPECT().DeliverWithPrivateData(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil) - - blocksAndPrivateData := []*peer.BlockAndPrivateData{ + expected := []*peer.BlockAndPrivateData{ { Block: &common.Block{ Header: &common.BlockHeader{ @@ -198,47 +54,33 @@ func TestBlockAndPrivateDataEvents(t *testing.T) { }, }, } - responseIndex := 0 - mockEvents.EXPECT().Recv(). - DoAndReturn(func() (*peer.DeliverResponse, error) { - if responseIndex >= len(blocksAndPrivateData) { - return nil, errors.New("fake") - } - response := &peer.DeliverResponse{ - Type: &peer.DeliverResponse_BlockAndPrivateData{ - BlockAndPrivateData: blocksAndPrivateData[responseIndex], - }, - } - responseIndex++ - return response, nil - }). - AnyTimes() + + var responses []*peer.DeliverResponse + for _, block := range expected { + responses = append(responses, &peer.DeliverResponse{ + Type: &peer.DeliverResponse_BlockAndPrivateData{ + BlockAndPrivateData: block, + }, + }) + } + + tester := NewBlockAndPrivateDataEventsTest(t) + tester.SetResponses(responses...) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - receive, err := network.BlockAndPrivateDataEvents(ctx) + events, err := tester.Events(ctx) require.NoError(t, err) - for _, event := range blocksAndPrivateData { - actual := <-receive - test.AssertProtoEqual(t, event, actual) + for _, event := range expected { + actual := <-events + AssertProtoEqual(t, event, actual) } }) t.Run("Closes event channel on non-block message", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverWithPrivateDataClient(controller) - - mockClient.EXPECT().DeliverWithPrivateData(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil) - - blockAndPrivateData := &peer.BlockAndPrivateData{ + block := &peer.BlockAndPrivateData{ Block: &common.Block{ Header: &common.BlockHeader{ Number: 1, @@ -259,7 +101,7 @@ func TestBlockAndPrivateDataEvents(t *testing.T) { responses := []*peer.DeliverResponse{ { Type: &peer.DeliverResponse_BlockAndPrivateData{ - BlockAndPrivateData: blockAndPrivateData, + BlockAndPrivateData: block, }, }, { @@ -269,106 +111,29 @@ func TestBlockAndPrivateDataEvents(t *testing.T) { }, { Type: &peer.DeliverResponse_BlockAndPrivateData{ - BlockAndPrivateData: blockAndPrivateData, + BlockAndPrivateData: block, }, }, } - responseIndex := 0 - mockEvents.EXPECT().Recv(). - DoAndReturn(func() (*peer.DeliverResponse, error) { - if responseIndex >= len(responses) { - return nil, errors.New("fake") - } - response := responses[responseIndex] - responseIndex++ - return response, nil - }). - AnyTimes() + + tester := NewBlockAndPrivateDataEventsTest(t) + tester.SetResponses(responses...) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - receive, err := network.BlockAndPrivateDataEvents(ctx) + events, err := tester.Events(ctx) require.NoError(t, err) - expected := []*peer.BlockAndPrivateData{ - blockAndPrivateData, + // proto.Message must be used to match the type of events channel; otherwise nil comparisons will fail + expected := []proto.Message{ + block, nil, nil, } for _, event := range expected { - actual := <-receive - test.AssertProtoEqual(t, event, actual) + actual := <-events + AssertProtoEqual(t, event, actual) } }) - - t.Run("Uses specified gRPC call options", func(t *testing.T) { - var actual []grpc.CallOption - expected := grpc.WaitForReady(true) - - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().DeliverWithPrivateData(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, opts ...grpc.CallOption) { - actual = opts - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Send(gomock.Any()). - Return(nil). - AnyTimes() - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient)) - request, err := network.NewBlockAndPrivateDataEventsRequest() - require.NoError(t, err, "NewBlockAndPrivateDataEventsRequest") - - _, err = request.Events(ctx, expected) - require.NoError(t, err, "Events") - - require.Contains(t, actual, expected, "CallOptions") - }) - - t.Run("Sends request with TLS client certificate hash", func(t *testing.T) { - expected := []byte("TLS_CLIENT_CERTIFICATE_HASH") - - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockClient.EXPECT().DeliverWithPrivateData(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - payload := &common.Payload{} - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - test.AssertUnmarshal(t, in.GetPayload(), payload) - }). - Return(nil). - Times(1) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithDeliverClient(mockClient), WithTLSClientCertificateHash(expected)) - _, err := network.BlockAndPrivateDataEvents(ctx) - require.NoError(t, err) - - channelHeader := &common.ChannelHeader{} - test.AssertUnmarshal(t, payload.GetHeader().GetChannelHeader(), channelHeader) - - require.Equal(t, expected, channelHeader.GetTlsCertHash()) - }) } diff --git a/pkg/client/chaincodeevents_test.go b/pkg/client/chaincodeevents_test.go index 8c54d2d3a..f98b5291c 100644 --- a/pkg/client/chaincodeevents_test.go +++ b/pkg/client/chaincodeevents_test.go @@ -6,17 +6,17 @@ package client import ( "context" "errors" + "io" "testing" - "github.com/hyperledger/fabric-gateway/pkg/internal/test" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/orderer" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" ) func TestChaincodeEvents(t *testing.T) { @@ -42,14 +42,14 @@ func TestChaincodeEvents(t *testing.T) { t.Run("Returns connect error", func(t *testing.T) { expected := NewStatusError(t, codes.Aborted, "CHAINCODE_EVENTS_ERROR") - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Return(nil, expected) + + mockConnection := NewMockClientConnInterface(t) + ExpectChaincodeEvents(mockConnection, WithNewStreamError(expected)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) + network := AssertNewTestNetwork(t, "NETWORK", WithClientConnection(mockConnection)) _, err := network.ChaincodeEvents(ctx, "CHAINCODE") require.Equal(t, status.Code(expected), status.Code(err), "status code") @@ -57,396 +57,189 @@ func TestChaincodeEvents(t *testing.T) { require.ErrorContains(t, err, expected.Error(), "message") }) - t.Run("Sends valid request with default start position", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - var actual *gateway.ChaincodeEventsRequest - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedChaincodeEventsRequest, _ ...grpc.CallOption) { - request := &gateway.ChaincodeEventsRequest{} - test.AssertUnmarshal(t, in.GetRequest(), request) - actual = request - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) - _, err := network.ChaincodeEvents(ctx, "CHAINCODE") - require.NoError(t, err) - - creator, err := network.signingID.Creator() - require.NoError(t, err) - - expected := &gateway.ChaincodeEventsRequest{ - ChannelId: "NETWORK", - ChaincodeId: "CHAINCODE", - Identity: creator, - StartPosition: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_NextCommit{ - NextCommit: &orderer.SeekNextCommit{}, + for testName, testCase := range map[string]struct { + options []ChaincodeEventsOption + expected *gateway.ChaincodeEventsRequest + }{ + "Sends valid request with default start position": { + options: nil, + expected: &gateway.ChaincodeEventsRequest{ + StartPosition: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_NextCommit{ + NextCommit: &orderer.SeekNextCommit{}, + }, }, }, - } - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Sends valid request with specified start block number", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - var actual *gateway.ChaincodeEventsRequest - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedChaincodeEventsRequest, _ ...grpc.CallOption) { - request := &gateway.ChaincodeEventsRequest{} - test.AssertUnmarshal(t, in.GetRequest(), request) - actual = request - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) - _, err := network.ChaincodeEvents(ctx, "CHAINCODE", WithStartBlock(418)) - require.NoError(t, err) - - creator, err := network.signingID.Creator() - require.NoError(t, err) - - expected := &gateway.ChaincodeEventsRequest{ - ChannelId: "NETWORK", - ChaincodeId: "CHAINCODE", - Identity: creator, - StartPosition: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 418, + }, + "Sends valid request with specified start block number": { + options: []ChaincodeEventsOption{ + WithStartBlock(418), + }, + expected: &gateway.ChaincodeEventsRequest{ + StartPosition: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 418, + }, }, }, }, - } - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Sends valid request with specified start block number and fresh checkpointer", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - var actual *gateway.ChaincodeEventsRequest - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedChaincodeEventsRequest, _ ...grpc.CallOption) { - request := &gateway.ChaincodeEventsRequest{} - test.AssertUnmarshal(t, in.GetRequest(), request) - actual = request - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - - _, err := network.ChaincodeEvents(ctx, "CHAINCODE", WithStartBlock(418), WithCheckpoint(checkpointer)) - require.NoError(t, err) - - creator, err := network.signingID.Creator() - require.NoError(t, err) - - expected := &gateway.ChaincodeEventsRequest{ - ChannelId: "NETWORK", - ChaincodeId: "CHAINCODE", - Identity: creator, - StartPosition: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 418, + }, + "Sends valid request with specified start block number and fresh checkpointer": { + options: []ChaincodeEventsOption{ + WithStartBlock(418), + WithCheckpoint(new(InMemoryCheckpointer)), + }, + expected: &gateway.ChaincodeEventsRequest{ + StartPosition: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 418, + }, }, }, }, - } - test.AssertProtoEqual(t, expected, actual) - }) - t.Run("Sends valid request with specified start block and checkpoint block", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - var actual *gateway.ChaincodeEventsRequest - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedChaincodeEventsRequest, _ ...grpc.CallOption) { - request := &gateway.ChaincodeEventsRequest{} - test.AssertUnmarshal(t, in.GetRequest(), request) - actual = request - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - checkpointer.CheckpointBlock(uint64(500)) - _, err := network.ChaincodeEvents(ctx, "CHAINCODE", WithStartBlock(418), WithCheckpoint(checkpointer)) - require.NoError(t, err) - - creator, err := network.signingID.Creator() - require.NoError(t, err) - - expected := &gateway.ChaincodeEventsRequest{ - ChannelId: "NETWORK", - ChaincodeId: "CHAINCODE", - Identity: creator, - StartPosition: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 501, + }, + "Sends valid request with specified start block and checkpoint block": { + options: func() []ChaincodeEventsOption { + checkpointer := new(InMemoryCheckpointer) + checkpointer.CheckpointBlock(500) + return []ChaincodeEventsOption{ + WithStartBlock(418), + WithCheckpoint(checkpointer), + } + }(), + expected: &gateway.ChaincodeEventsRequest{ + StartPosition: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 501, + }, }, }, }, - } - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Sends valid request with specified start block and checkpoint transaction ID", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - var actual *gateway.ChaincodeEventsRequest - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedChaincodeEventsRequest, _ ...grpc.CallOption) { - request := &gateway.ChaincodeEventsRequest{} - test.AssertUnmarshal(t, in.GetRequest(), request) - actual = request - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - checkpointer.CheckpointTransaction(uint64(500), "txn1") - _, err := network.ChaincodeEvents(ctx, "CHAINCODE", WithStartBlock(418), WithCheckpoint(checkpointer)) - require.NoError(t, err) - - creator, err := network.signingID.Creator() - require.NoError(t, err) - - expected := &gateway.ChaincodeEventsRequest{ - ChannelId: "NETWORK", - ChaincodeId: "CHAINCODE", - Identity: creator, - StartPosition: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 500, + }, + "Sends valid request with specified start block and checkpoint transaction ID": { + options: func() []ChaincodeEventsOption { + checkpointer := new(InMemoryCheckpointer) + checkpointer.CheckpointTransaction(500, "txn1") + return []ChaincodeEventsOption{ + WithStartBlock(418), + WithCheckpoint(checkpointer), + } + }(), + expected: &gateway.ChaincodeEventsRequest{ + StartPosition: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 500, + }, }, }, + AfterTransactionId: "txn1", }, - AfterTransactionId: "txn1", - } - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Sends valid request with no start block and fresh checkpointer", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - var actual *gateway.ChaincodeEventsRequest - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedChaincodeEventsRequest, _ ...grpc.CallOption) { - request := &gateway.ChaincodeEventsRequest{} - test.AssertUnmarshal(t, in.GetRequest(), request) - actual = request - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - _, err := network.ChaincodeEvents(ctx, "CHAINCODE", WithCheckpoint(checkpointer)) - require.NoError(t, err) - - creator, err := network.signingID.Creator() - require.NoError(t, err) - expected := &gateway.ChaincodeEventsRequest{ - ChannelId: "NETWORK", - ChaincodeId: "CHAINCODE", - Identity: creator, - StartPosition: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_NextCommit{ - NextCommit: &orderer.SeekNextCommit{}, + }, + "Sends valid request with no start block and fresh checkpointer": { + options: []ChaincodeEventsOption{ + WithCheckpoint(new(InMemoryCheckpointer)), + }, + expected: &gateway.ChaincodeEventsRequest{ + StartPosition: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_NextCommit{ + NextCommit: &orderer.SeekNextCommit{}, + }, }, }, - } - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Sends valid request with no start block and checkpoint transaction ID", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - var actual *gateway.ChaincodeEventsRequest - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedChaincodeEventsRequest, _ ...grpc.CallOption) { - request := &gateway.ChaincodeEventsRequest{} - test.AssertUnmarshal(t, in.GetRequest(), request) - actual = request - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - checkpointer.CheckpointTransaction(uint64(500), "txn1") - - _, err := network.ChaincodeEvents(ctx, "CHAINCODE", WithCheckpoint(checkpointer)) - require.NoError(t, err) - - creator, err := network.signingID.Creator() - require.NoError(t, err) - expected := &gateway.ChaincodeEventsRequest{ - ChannelId: "NETWORK", - ChaincodeId: "CHAINCODE", - Identity: creator, - StartPosition: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: 500, + }, + "Sends valid request with no start block and checkpoint transaction ID": { + options: func() []ChaincodeEventsOption { + checkpointer := new(InMemoryCheckpointer) + checkpointer.CheckpointTransaction(500, "txn1") + return []ChaincodeEventsOption{ + WithCheckpoint(checkpointer), + } + }(), + expected: &gateway.ChaincodeEventsRequest{ + StartPosition: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 500, + }, }, }, + AfterTransactionId: "txn1", }, - AfterTransactionId: "txn1", - } - test.AssertProtoEqual(t, expected, actual) - }) - - t.Run("Sends valid request with with start block and checkpoint chaincode event", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - var actual *gateway.ChaincodeEventsRequest - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedChaincodeEventsRequest, _ ...grpc.CallOption) { - request := &gateway.ChaincodeEventsRequest{} - test.AssertUnmarshal(t, in.GetRequest(), request) - actual = request - }). - Return(mockEvents, nil). - Times(1) - - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) - - checkpointer := new(InMemoryCheckpointer) - event := &ChaincodeEvent{ - BlockNumber: 1, - ChaincodeName: "CHAINCODE", - EventName: "EVENT_1", - Payload: []byte("PAYLOAD_1"), - TransactionID: "TRANSACTION_1", - } - - checkpointer.CheckpointChaincodeEvent(event) - - _, err := network.ChaincodeEvents(ctx, "CHAINCODE", WithStartBlock(418), WithCheckpoint(checkpointer)) - require.NoError(t, err) - - creator, err := network.signingID.Creator() - require.NoError(t, err) - expected := &gateway.ChaincodeEventsRequest{ - ChannelId: "NETWORK", - ChaincodeId: "CHAINCODE", - Identity: creator, - StartPosition: &orderer.SeekPosition{ - Type: &orderer.SeekPosition_Specified{ - Specified: &orderer.SeekSpecified{ - Number: event.BlockNumber, + }, + "Sends valid request with with start block and checkpoint chaincode event": { + options: func() []ChaincodeEventsOption { + checkpointer := new(InMemoryCheckpointer) + event := &ChaincodeEvent{ + BlockNumber: 1, + TransactionID: "TRANSACTION_1", + } + checkpointer.CheckpointChaincodeEvent(event) + return []ChaincodeEventsOption{ + WithCheckpoint(checkpointer), + } + }(), + expected: &gateway.ChaincodeEventsRequest{ + StartPosition: &orderer.SeekPosition{ + Type: &orderer.SeekPosition_Specified{ + Specified: &orderer.SeekSpecified{ + Number: 1, + }, }, }, + AfterTransactionId: "TRANSACTION_1", }, - AfterTransactionId: event.TransactionID, - } - test.AssertProtoEqual(t, expected, actual) - }) + }, + } { + t.Run(testName, func(t *testing.T) { + mockConnection := NewMockClientConnInterface(t) + mockStream := NewMockClientStream(t) + ExpectChaincodeEvents(mockConnection, WithNewStreamResult(mockStream)) + + messages := make(chan *gateway.SignedChaincodeEventsRequest, 1) + ExpectSendMsg(mockStream, CaptureSendMsg(messages)) + mockStream.EXPECT().CloseSend().Return(nil) + ExpectRecvMsg(mockStream).Maybe().Return(io.EOF) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + network := AssertNewTestNetwork(t, "NETWORK", WithClientConnection(mockConnection)) + _, err := network.ChaincodeEvents(ctx, "CHAINCODE", testCase.options...) + require.NoError(t, err) + + creator, err := network.signingID.Creator() + require.NoError(t, err) + + expected := &gateway.ChaincodeEventsRequest{ + ChannelId: "NETWORK", + ChaincodeId: "CHAINCODE", + Identity: creator, + } + proto.Merge(expected, testCase.expected) + actual := &gateway.ChaincodeEventsRequest{} + AssertUnmarshal(t, (<-messages).Request, actual) + AssertProtoEqual(t, expected, actual) + }) + } t.Run("Closes event channel on receive error", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) + mockConnection := NewMockClientConnInterface(t) + mockStream := NewMockClientStream(t) + ExpectChaincodeEvents(mockConnection, WithNewStreamResult(mockStream)) - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() + ExpectSendMsg(mockStream) + mockStream.EXPECT().CloseSend().Return(nil) + ExpectRecvMsg(mockStream).Maybe().Return(errors.New("fake")) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) + network := AssertNewTestNetwork(t, "NETWORK", WithClientConnection(mockConnection)) + receive, err := network.ChaincodeEvents(ctx, "CHAINCODE") require.NoError(t, err) @@ -456,13 +249,6 @@ func TestChaincodeEvents(t *testing.T) { }) t.Run("Receives events", func(t *testing.T) { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Return(mockEvents, nil) - expected := []*ChaincodeEvent{ { BlockNumber: 1, @@ -487,26 +273,24 @@ func TestChaincodeEvents(t *testing.T) { }, } - responses := []*gateway.ChaincodeEventsResponse{ + mockConnection := NewMockClientConnInterface(t) + mockStream := NewMockClientStream(t) + options := make(chan []grpc.CallOption, 1) + ExpectChaincodeEvents(mockConnection, CaptureNewStreamOptions(options), WithNewStreamResult(mockStream)) + + messages := make(chan *gateway.SignedChaincodeEventsRequest, 1) + ExpectSendMsg(mockStream, CaptureSendMsg(messages)) + mockStream.EXPECT().CloseSend().Return(nil) + ExpectRecvMsg(mockStream, WithRecvMsgs( newChaincodeEventsResponse(expected[0:2]), newChaincodeEventsResponse(expected[2:]), - } - responseIndex := 0 - mockEvents.EXPECT().Recv(). - DoAndReturn(func() (*gateway.ChaincodeEventsResponse, error) { - if responseIndex >= len(responses) { - return nil, errors.New("fake") - } - response := responses[responseIndex] - responseIndex++ - return response, nil - }). - AnyTimes() + )) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) + network := AssertNewTestNetwork(t, "NETWORK", WithClientConnection(mockConnection)) + receive, err := network.ChaincodeEvents(ctx, "CHAINCODE") require.NoError(t, err) @@ -517,34 +301,29 @@ func TestChaincodeEvents(t *testing.T) { }) t.Run("Uses specified gRPC call options", func(t *testing.T) { - var actual []grpc.CallOption expected := grpc.WaitForReady(true) - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) - - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any(), gomock.Any()). - Do(func(_ context.Context, _ *gateway.SignedChaincodeEventsRequest, opts ...grpc.CallOption) { - actual = opts - }). - Return(mockEvents, nil). - Times(1) + mockConnection := NewMockClientConnInterface(t) + mockStream := NewMockClientStream(t) + options := make(chan []grpc.CallOption, 1) + ExpectChaincodeEvents(mockConnection, CaptureNewStreamOptions(options), WithNewStreamResult(mockStream)) - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() + messages := make(chan *gateway.SignedChaincodeEventsRequest, 1) + ExpectSendMsg(mockStream, CaptureSendMsg(messages)) + mockStream.EXPECT().CloseSend().Return(nil) + ExpectRecvMsg(mockStream).Maybe().Return(io.EOF) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - network := AssertNewTestNetwork(t, "NETWORK", WithGatewayClient(mockClient)) + network := AssertNewTestNetwork(t, "NETWORK", WithClientConnection(mockConnection)) + request, err := network.NewChaincodeEventsRequest("CHAINCODE") require.NoError(t, err, "NewChaincodeEventsRequest") _, err = request.Events(ctx, expected) require.NoError(t, err, "Events") - require.Contains(t, actual, expected, "CallOptions") + require.Contains(t, <-options, expected, "CallOptions") }) } diff --git a/pkg/client/evaluate_test.go b/pkg/client/evaluate_test.go index c034ae9a3..2894c1fa7 100644 --- a/pkg/client/evaluate_test.go +++ b/pkg/client/evaluate_test.go @@ -6,43 +6,22 @@ package client import ( "context" "testing" - "time" - "github.com/hyperledger/fabric-gateway/pkg/internal/test" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" - "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/runtime/protoiface" ) -func NewStatusError(t *testing.T, code codes.Code, message string, details ...protoiface.MessageV1) error { - s, err := status.New(code, message).WithDetails(details...) - require.NoError(t, err) - - return s.Err() -} - func TestEvaluateTransaction(t *testing.T) { - newEvaluateResponse := func(value []byte) *gateway.EvaluateResponse { - return &gateway.EvaluateResponse{ - Result: &peer.Response{ - Payload: []byte(value), - }, - } - } - t.Run("Returns evaluate error", func(t *testing.T) { expected := NewStatusError(t, codes.Aborted, "EVALUATE_ERROR") - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Return(nil, expected) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + ExpectEvaluate(mockConnection, WithInvokeError(expected)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, err := contract.EvaluateTransaction("transaction") require.ErrorIs(t, err, expected, "error type: %T", err) @@ -66,11 +45,10 @@ func TestEvaluateTransaction(t *testing.T) { } { t.Run(name, func(t *testing.T) { expected := []byte("TRANSACTION_RESULT") - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Return(newEvaluateResponse(expected), nil) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + ExpectEvaluate(mockConnection, WithEvaluateResponse(expected)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) actual, err := testCase.run(t, contract) require.NoError(t, err) @@ -78,231 +56,196 @@ func TestEvaluateTransaction(t *testing.T) { require.EqualValues(t, expected, actual) }) } + t.Run("Includes channel name in proposal", func(t *testing.T) { - var actual string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - actual = test.AssertUnmarshalChannelheader(t, in.ProposedTransaction).ChannelId - }). - Return(newEvaluateResponse(nil), nil). - Times(1) + expected := "CHANNEL_NAME" - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + network := AssertNewTestNetwork(t, expected, WithClientConnection(mockConnection)) + + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) + contract := network.GetContract("chaincode") _, err := contract.EvaluateTransaction("transaction") require.NoError(t, err) - expected := contract.channelName + actual := AssertUnmarshalChannelheader(t, (<-requests).ProposedTransaction).ChannelId require.Equal(t, expected, actual) }) t.Run("Includes chaincode name in proposal", func(t *testing.T) { - var actual string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - actual = test.AssertUnmarshalInvocationSpec(t, in.ProposedTransaction).ChaincodeSpec.ChaincodeId.Name - }). - Return(newEvaluateResponse(nil), nil). - Times(1) + expected := "CHAINCODE_NAME" - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) + contract := AssertNewTestContract(t, expected, WithClientConnection(mockConnection)) _, err := contract.EvaluateTransaction("transaction") require.NoError(t, err) - expected := contract.chaincodeName + actual := AssertUnmarshalInvocationSpec(t, (<-requests).ProposedTransaction).ChaincodeSpec.ChaincodeId.Name require.Equal(t, expected, actual) }) t.Run("Includes transaction name in proposal for default smart contract", func(t *testing.T) { - var args [][]byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - args = test.AssertUnmarshalInvocationSpec(t, in.ProposedTransaction).ChaincodeSpec.Input.Args - }). - Return(newEvaluateResponse(nil), nil). - Times(1) + expected := "TRANSACTION_NAME" - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) - expected := "TRANSACTION_NAME" _, err := contract.EvaluateTransaction(expected) require.NoError(t, err) + args := AssertUnmarshalInvocationSpec(t, (<-requests).ProposedTransaction).ChaincodeSpec.Input.Args actual := string(args[0]) require.Equal(t, expected, actual, "got Args: %s", args) }) t.Run("Includes transaction name in proposal for named smart contract", func(t *testing.T) { - var args [][]byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - args = test.AssertUnmarshalInvocationSpec(t, in.ProposedTransaction).ChaincodeSpec.Input.Args - }). - Return(newEvaluateResponse(nil), nil). - Times(1) - - contract := AssertNewTestContractWithName(t, "chaincode", "CONTRACT_NAME", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) + contract := AssertNewTestContractWithName(t, "chaincode", "CONTRACT_NAME", WithClientConnection(mockConnection)) _, err := contract.EvaluateTransaction("TRANSACTION_NAME") require.NoError(t, err) + args := AssertUnmarshalInvocationSpec(t, (<-requests).ProposedTransaction).ChaincodeSpec.Input.Args actual := string(args[0]) expected := "CONTRACT_NAME:TRANSACTION_NAME" require.Equal(t, expected, actual, "got Args: %s", args) }) t.Run("Includes arguments in proposal", func(t *testing.T) { - var args [][]byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - args = test.AssertUnmarshalInvocationSpec(t, in.ProposedTransaction).ChaincodeSpec.Input.Args - }). - Return(newEvaluateResponse(nil), nil). - Times(1) + expected := []string{"one", "two", "three"} - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) - expected := []string{"one", "two", "three"} _, err := contract.EvaluateTransaction("transaction", expected...) require.NoError(t, err) + args := AssertUnmarshalInvocationSpec(t, (<-requests).ProposedTransaction).ChaincodeSpec.Input.Args actual := bytesAsStrings(args[1:]) require.EqualValues(t, expected, actual, "got Args: %s", args) }) t.Run("Includes channel name in proposed transaction", func(t *testing.T) { - var actual string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - actual = in.ChannelId - }). - Return(newEvaluateResponse(nil), nil). - Times(1) + expected := "CHANNEL_NAME" - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + network := AssertNewTestNetwork(t, expected, WithClientConnection(mockConnection)) + contract := network.GetContract("chaincode") + + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) _, err := contract.EvaluateTransaction("transaction") require.NoError(t, err) - expected := contract.channelName - require.Equal(t, expected, actual) + actual := (<-requests).ChannelId + require.Equal(t, contract.channelName, actual) }) t.Run("Includes transaction ID in proposed transaction", func(t *testing.T) { - var actual string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - actual = test.AssertUnmarshalChannelheader(t, in.ProposedTransaction).TxId - }). - Return(newEvaluateResponse(nil), nil). - Times(1) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") _, err = proposal.Evaluate() require.NoError(t, err, "Evaluate") + actual := AssertUnmarshalChannelheader(t, (<-requests).ProposedTransaction).TxId require.Equal(t, proposal.TransactionID(), actual) }) t.Run("Includes transaction ID in evaluate request", func(t *testing.T) { - var actual string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - actual = in.TransactionId - }). - Return(newEvaluateResponse(nil), nil). - Times(1) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") _, err = proposal.Evaluate() require.NoError(t, err, "Evaluate") + actual := (<-requests).TransactionId require.Equal(t, proposal.TransactionID(), actual) }) t.Run("Uses sign", func(t *testing.T) { - var actual []byte expected := []byte("MY_SIGNATURE") + + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) + sign := func(digest []byte) ([]byte, error) { return expected, nil } - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - actual = in.ProposedTransaction.Signature - }). - Return(newEvaluateResponse(nil), nil). - Times(1) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithSign(sign)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithSign(sign)) _, err := contract.EvaluateTransaction("transaction") require.NoError(t, err) + actual := (<-requests).ProposedTransaction.Signature require.EqualValues(t, expected, actual) }) t.Run("Uses hash", func(t *testing.T) { - var actual []byte expected := []byte("MY_DIGEST") + + mockConnection := NewMockClientConnInterface(t) + ExpectEvaluate(mockConnection, WithEvaluateResponse(nil)) + + digests := make(chan []byte, 1) sign := func(digest []byte) ([]byte, error) { - actual = digest - return expected, nil + digests <- digest + return digest, nil } hash := func(message []byte) []byte { return expected } - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Return(newEvaluateResponse(nil), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithSign(sign), WithHash(hash)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithSign(sign), WithHash(hash)) _, err := contract.EvaluateTransaction("transaction") require.NoError(t, err) + actual := <-digests require.EqualValues(t, expected, actual) }) t.Run("Sends private data with evaluate", func(t *testing.T) { - var actualOrgs []string expectedOrgs := []string{"MY_ORG"} - var actualPrice []byte expectedPrice := []byte("3000") - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - actualOrgs = in.TargetOrganizations - transient := test.AssertUnmarshalProposalPayload(t, in.ProposedTransaction).TransientMap - actualPrice = transient["price"] - }). - Return(newEvaluateResponse(nil), nil) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) privateData := map[string][]byte{ "price": []byte("3000"), } - _, err := contract.Evaluate("transaction", WithTransient(privateData), WithEndorsingOrganizations("MY_ORG")) require.NoError(t, err) + request := <-requests + actualOrgs := request.TargetOrganizations require.EqualValues(t, expectedOrgs, actualOrgs) + + transient := AssertUnmarshalProposalPayload(t, request.ProposedTransaction).TransientMap + actualPrice := transient["price"] require.EqualValues(t, expectedPrice, actualPrice) }) @@ -326,37 +269,20 @@ func TestEvaluateTransaction(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, _ *gateway.EvaluateRequest, _ ...grpc.CallOption) (*gateway.EvaluateResponse, error) { - err := ctx.Err() - if err != nil { - return nil, err - } - return newEvaluateResponse(nil), nil - }) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + ExpectEvaluate(mockConnection, WithInvokeContextErr()) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, err := testCase.run(t, ctx, contract) - require.ErrorIs(t, err, context.Canceled) }) } t.Run("Uses default context", func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, _ *gateway.EvaluateRequest, _ ...grpc.CallOption) (*gateway.EvaluateResponse, error) { - select { - case <-time.After(1 * time.Second): - return newEvaluateResponse(nil), nil - case <-ctx.Done(): // Zero timeout context should cancel immediately, selecting this case - return nil, ctx.Err() - } - }) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithEvaluateTimeout(0)) + mockConnection := NewMockClientConnInterface(t) + ExpectEvaluate(mockConnection, WithInvokeContextErr(), WithEvaluateResponse(nil)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithEvaluateTimeout(0)) _, err := contract.Evaluate("transaction") @@ -382,22 +308,17 @@ func TestEvaluateTransaction(t *testing.T) { }, } { t.Run(testName, func(t *testing.T) { - var actual []grpc.CallOption expected := grpc.WaitForReady(true) - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any(), gomock.Any()). - Do(func(_ context.Context, _ *gateway.EvaluateRequest, opts ...grpc.CallOption) { - actual = opts - }). - Return(newEvaluateResponse(nil), nil). - Times(1) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + options := make(chan []grpc.CallOption, 1) + mockConnection := NewMockClientConnInterface(t) + ExpectEvaluate(mockConnection, CaptureInvokeOptions(options), WithEvaluateResponse(nil)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, err := testCase.run(t, contract, []grpc.CallOption{expected}) require.NoError(t, err, "Evaluate") + actual := <-options require.Contains(t, actual, expected, "CallOptions") }) } diff --git a/pkg/client/gateway_test.go b/pkg/client/gateway_test.go index 540ad28e8..04e727b2a 100644 --- a/pkg/client/gateway_test.go +++ b/pkg/client/gateway_test.go @@ -8,32 +8,10 @@ import ( "testing" "github.com/hyperledger/fabric-gateway/pkg/identity" - "github.com/hyperledger/fabric-protos-go-apiv2/gateway" - "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" "google.golang.org/grpc" ) -//go:generate mockgen -destination ./gateway_mock_test.go -package ${GOPACKAGE} github.com/hyperledger/fabric-protos-go-apiv2/gateway GatewayClient,Gateway_ChaincodeEventsClient -//go:generate mockgen -destination ./deliver_mock_test.go -package ${GOPACKAGE} github.com/hyperledger/fabric-protos-go-apiv2/peer DeliverClient,Deliver_DeliverClient,Deliver_DeliverFilteredClient,Deliver_DeliverWithPrivateDataClient - -// WithGatewayClient uses the supplied client for the Gateway. Allows a stub implementation to be used for testing. -func WithGatewayClient(client gateway.GatewayClient) ConnectOption { - return func(gateway *Gateway) error { - gateway.client.grpcGatewayClient = client - return nil - } -} - -// WithDeliverClient uses the supplied client for the Deliver service. Allows a stub implementation to be used for testing. -func WithDeliverClient(client peer.DeliverClient) ConnectOption { - return func(gateway *Gateway) error { - gateway.client.grpcDeliverClient = client - return nil - } -} - // WithIdentity uses the supplied identity for the Gateway. func WithIdentity(id identity.Identity) ConnectOption { return func(gateway *Gateway) error { @@ -45,8 +23,7 @@ func WithIdentity(id identity.Identity) ConnectOption { func AssertNewTestGateway(t *testing.T, options ...ConnectOption) *Gateway { defaultOptions := []ConnectOption{ WithSign(TestCredentials.sign), - WithGatewayClient(NewMockGatewayClient(gomock.NewController(t))), - WithDeliverClient(NewMockDeliverClient(gomock.NewController(t))), + WithClientConnection(NewMockClientConnInterface(t)), } options = append(defaultOptions, options...) gateway, err := Connect(TestCredentials.Identity(), options...) @@ -94,8 +71,7 @@ func TestGateway(t *testing.T) { t.Run("GetNetwork returns correctly named Network", func(t *testing.T) { networkName := "network" - mockClient := NewMockGatewayClient(gomock.NewController(t)) - gateway := AssertNewTestGateway(t, WithGatewayClient(mockClient)) + gateway := AssertNewTestGateway(t) network := gateway.GetNetwork(networkName) @@ -104,8 +80,7 @@ func TestGateway(t *testing.T) { }) t.Run("Identity returns connecting identity", func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - gateway := AssertNewTestGateway(t, WithIdentity(id), WithGatewayClient(mockClient)) + gateway := AssertNewTestGateway(t, WithIdentity(id)) result := gateway.Identity() diff --git a/pkg/client/identity_test.go b/pkg/client/identity_test.go index 586dd15a3..f56f34191 100644 --- a/pkg/client/identity_test.go +++ b/pkg/client/identity_test.go @@ -4,7 +4,6 @@ package client import ( - "context" "testing" "github.com/hyperledger/fabric-gateway/pkg/identity" @@ -13,8 +12,6 @@ import ( "github.com/hyperledger/fabric-protos-go-apiv2/msp" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "google.golang.org/grpc" "google.golang.org/protobuf/proto" ) @@ -36,51 +33,33 @@ func TestIdentity(t *testing.T) { require.NoError(t, err) t.Run("Evaluate uses client identity for proposals", func(t *testing.T) { - var actual []byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - evaluateResponse := &gateway.EvaluateResponse{ - Result: &peer.Response{ - Payload: nil, - }, - } - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - actual = test.AssertUnmarshalSignatureHeader(t, in.ProposedTransaction).Creator - }). - Return(evaluateResponse, nil). - Times(1) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)) - contract := AssertNewTestContract(t, "contract", WithGatewayClient(mockClient), WithIdentity(id)) + contract := AssertNewTestContract(t, "contract", WithClientConnection(mockConnection), WithIdentity(id)) _, err := contract.EvaluateTransaction("transaction") require.NoError(t, err) + actual := AssertUnmarshalSignatureHeader(t, (<-requests).ProposedTransaction).Creator require.EqualValues(t, creator, actual) }) t.Run("Submit uses client identity for proposals", func(t *testing.T) { - var actual []byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) endorseResponse := AssertNewEndorseResponse(t, "result", "channel") - statusResponse := &gateway.CommitStatusResponse{ - Result: peer.TxValidationCode_VALID, - } - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - actual = test.AssertUnmarshalSignatureHeader(t, in.ProposedTransaction).Creator - }). - Return(endorseResponse, nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(statusResponse, nil) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(endorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) - contract := AssertNewTestContract(t, "contract", WithGatewayClient(mockClient), WithIdentity(id)) + contract := AssertNewTestContract(t, "contract", WithClientConnection(mockConnection), WithIdentity(id)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) + actual := AssertUnmarshalSignatureHeader(t, (<-requests).ProposedTransaction).Creator require.EqualValues(t, creator, actual) }) } diff --git a/pkg/client/network_test.go b/pkg/client/network_test.go index 676463443..9c30d6efa 100644 --- a/pkg/client/network_test.go +++ b/pkg/client/network_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func AssertNewTestNetwork(t *testing.T, networkName string, options ...ConnectOption) *Network { @@ -18,8 +17,7 @@ func AssertNewTestNetwork(t *testing.T, networkName string, options ...ConnectOp func TestNetwork(t *testing.T) { t.Run("GetContract returns correctly named Contract", func(t *testing.T) { chaincodeName := "chaincode" - mockClient := NewMockGatewayClient(gomock.NewController(t)) - network := AssertNewTestNetwork(t, "network", WithGatewayClient(mockClient)) + network := AssertNewTestNetwork(t, "network") contract := network.GetContract(chaincodeName) @@ -31,8 +29,7 @@ func TestNetwork(t *testing.T) { t.Run("GetContractWithName returns correctly named Contract", func(t *testing.T) { chaincodeName := "chaincode" contractName := "contract" - mockClient := NewMockGatewayClient(gomock.NewController(t)) - network := AssertNewTestNetwork(t, "network", WithGatewayClient(mockClient)) + network := AssertNewTestNetwork(t, "network") contract := network.GetContractWithName(chaincodeName, contractName) diff --git a/pkg/client/offlinesign_test.go b/pkg/client/offlinesign_test.go index 323649f0f..535d051fe 100644 --- a/pkg/client/offlinesign_test.go +++ b/pkg/client/offlinesign_test.go @@ -5,573 +5,551 @@ package client import ( "context" - "errors" + "io" "testing" "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "google.golang.org/grpc" ) func TestOfflineSign(t *testing.T) { - var signature []byte - - newGatewayWithNoSign := func(t *testing.T, options ...ConnectOption) *Gateway { - defaultOptions := []ConnectOption{ - WithDeliverClient(NewMockDeliverClient(gomock.NewController(t))), - } - options = append(defaultOptions, options...) - gateway, err := Connect(TestCredentials.Identity(), options...) - require.NoError(t, err) - - return gateway - } - - newMockDeliverEvents := func(controller *gomock.Controller) *MockDeliver_DeliverClient { - mockEvents := NewMockDeliver_DeliverClient(controller) - - mockEvents.EXPECT().Send(gomock.Any()). - Do(func(in *common.Envelope) { - signature = in.GetSignature() - }). - Return(nil). - AnyTimes() - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() - - return mockEvents - } - - type Invocation struct { - Invoke func() error - } - - type Signable struct { - Invocations map[string]Invocation - OfflineSign func([]byte) *Signable - State interface{} - Recreate func() *Signable - } - - var newSignableFromProposal func(t *testing.T, gateway *Gateway, proposal *Proposal) *Signable - newSignableFromProposal = func(t *testing.T, gateway *Gateway, proposal *Proposal) *Signable { - return &Signable{ - Invocations: map[string]Invocation{ - "Evaluate": { - Invoke: func() error { - _, err := proposal.Evaluate() - return err - }, + for testName, testCase := range map[string]struct { + New func(*signingTest) + Sign func(*signingTest, []byte) + Invocations map[string](func(*signingTest) ([]byte, error)) + Recreate func(*signingTest) + State func(*signingTest) any + }{ + "Proposal": { + New: func(s *signingTest) { + s.NewProposal() + }, + Sign: func(s *signingTest, signature []byte) { + s.SignProposal(signature) + }, + Invocations: map[string](func(*signingTest) ([]byte, error)){ + "Evaluate": func(s *signingTest) ([]byte, error) { + return s.Evaluate() }, - "Endorse": { - Invoke: func() error { - _, err := proposal.Endorse() - return err - }, + "Endorse": func(s *signingTest) ([]byte, error) { + return s.Endorse() }, }, - OfflineSign: func(signature []byte) *Signable { - bytes, err := proposal.Bytes() - require.NoError(t, err, "Bytes") - - result, err := gateway.NewSignedProposal(bytes, signature) - require.NoError(t, err, "NewSignedProposal") - - return newSignableFromProposal(t, gateway, result) + Recreate: func(s *signingTest) { + s.RecreateProposal() }, - State: struct { - Digest []byte - TransactionID string - EndorsingOrgs []string - }{ - Digest: proposal.Digest(), - TransactionID: proposal.TransactionID(), - EndorsingOrgs: proposal.proposedTransaction.GetEndorsingOrganizations(), + State: func(s *signingTest) any { + return struct { + Digest []byte + TransactionID string + EndorsingOrgs []string + }{ + Digest: s.proposal.Digest(), + TransactionID: s.proposal.TransactionID(), + EndorsingOrgs: s.proposal.proposedTransaction.GetEndorsingOrganizations(), + } }, - Recreate: func() *Signable { - signedBytes, err := proposal.Bytes() - require.NoError(t, err, "NewSignedProposal") - - newProposal, err := gateway.NewProposal(signedBytes) - require.NoError(t, err, "NewProposal") - - return newSignableFromProposal(t, gateway, newProposal) + }, + "Transaction": { + New: func(s *signingTest) { + s.NewTransaction() }, - } - } - - var newSignableFromTransaction func(t *testing.T, gateway *Gateway, transaction *Transaction) *Signable - newSignableFromTransaction = func(t *testing.T, gateway *Gateway, transaction *Transaction) *Signable { - return &Signable{ - Invocations: map[string]Invocation{ - "Submit": { - Invoke: func() error { - _, err := transaction.Submit() - return err - }, + Sign: func(s *signingTest, signature []byte) { + s.SignTransaction(signature) + }, + Invocations: map[string](func(*signingTest) ([]byte, error)){ + "Submit": func(s *signingTest) ([]byte, error) { + return s.Submit() }, }, - OfflineSign: func(signature []byte) *Signable { - bytes, err := transaction.Bytes() - require.NoError(t, err, "Bytes") - - result, err := gateway.NewSignedTransaction(bytes, signature) - require.NoError(t, err, "NewSignedTransaction") - - return newSignableFromTransaction(t, gateway, result) + Recreate: func(s *signingTest) { + s.RecreateTransaction() }, - State: struct { - Digest []byte - TransactionID string - }{ - Digest: transaction.Digest(), - TransactionID: transaction.TransactionID(), + State: func(s *signingTest) any { + return struct { + Digest []byte + TransactionID string + Result []byte + }{ + Digest: s.transaction.Digest(), + TransactionID: s.transaction.TransactionID(), + Result: s.transaction.Result(), + } }, - Recreate: func() *Signable { - signedBytes, err := transaction.Bytes() - require.NoError(t, err, "NewSignedTransactionBytes") - - newTransaction, err := gateway.NewTransaction(signedBytes) - require.NoError(t, err, "NewTransaction") - - return newSignableFromTransaction(t, gateway, newTransaction) + }, + "Commit": { + New: func(s *signingTest) { + s.NewCommit() }, - } - } - - var newSignableFromCommit func(t *testing.T, gateway *Gateway, commit *Commit) *Signable - newSignableFromCommit = func(t *testing.T, gateway *Gateway, commit *Commit) *Signable { - return &Signable{ - Invocations: map[string]Invocation{ - "Status": { - Invoke: func() error { - _, err := commit.Status() - return err - }, + Sign: func(s *signingTest, signature []byte) { + s.SignCommit(signature) + }, + Invocations: map[string](func(*signingTest) ([]byte, error)){ + "Status": func(s *signingTest) ([]byte, error) { + return s.CommitStatus() }, }, - OfflineSign: func(signature []byte) *Signable { - bytes, err := commit.Bytes() - require.NoError(t, err, "Bytes") - - result, err := gateway.NewSignedCommit(bytes, signature) - require.NoError(t, err, "NewSignedCommit") - - return newSignableFromCommit(t, gateway, result) + Recreate: func(s *signingTest) { + s.RecreateCommit() }, - State: struct { - Digest []byte - TransactionID string - }{ - Digest: commit.Digest(), - TransactionID: commit.TransactionID(), + State: func(s *signingTest) any { + return struct { + Digest []byte + TransactionID string + }{ + Digest: s.transaction.Digest(), + TransactionID: s.transaction.TransactionID(), + } }, - Recreate: func() *Signable { - signedBytes, err := commit.Bytes() - require.NoError(t, err, "NewSignedCommitBytes") - - newCommit, err := gateway.NewCommit(signedBytes) - require.NoError(t, err, "NewCommit") - - return newSignableFromCommit(t, gateway, newCommit) + }, + "ChaincodeEvents": { + New: func(s *signingTest) { + s.NewChaincodeEvents() }, - } - } - - var newSignableFromChaincodeEventsRequest func(t *testing.T, gateway *Gateway, request *ChaincodeEventsRequest) *Signable - newSignableFromChaincodeEventsRequest = func(t *testing.T, gateway *Gateway, request *ChaincodeEventsRequest) *Signable { - return &Signable{ - Invocations: map[string]Invocation{ - "Events": { - Invoke: func() error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, err := request.Events(ctx) - return err - }, + Sign: func(s *signingTest, signature []byte) { + s.SignChaincodeEvents(signature) + }, + Invocations: map[string](func(*signingTest) ([]byte, error)){ + "Events": func(s *signingTest) ([]byte, error) { + return s.ChaincodeEvents() }, }, - OfflineSign: func(signature []byte) *Signable { - bytes, err := request.Bytes() - require.NoError(t, err, "Bytes") - - result, err := gateway.NewSignedChaincodeEventsRequest(bytes, signature) - require.NoError(t, err, "NewSignedChaincodeEventsRequest") - - return newSignableFromChaincodeEventsRequest(t, gateway, result) + Recreate: func(s *signingTest) { + s.RecreateChaincodeEvents() }, - State: struct { - Digest []byte - }{ - Digest: request.Digest(), + State: func(s *signingTest) any { + return struct { + Digest []byte + }{ + Digest: s.chaincodeEvents.Digest(), + } }, - Recreate: func() *Signable { - signedBytes, err := request.Bytes() - require.NoError(t, err, "NewSignedChaincodeEventsRequestBytes") - - newChaincodeRequest, err := gateway.NewChaincodeEventsRequest(signedBytes) - require.NoError(t, err, "newChaincodeRequest") - - return newSignableFromChaincodeEventsRequest(t, gateway, newChaincodeRequest) + }, + "BlockEvents": { + New: func(s *signingTest) { + s.NewBlockEvents() }, - } - } - - var newSignableFromBlockEventsRequest func(t *testing.T, gateway *Gateway, request *BlockEventsRequest) *Signable - newSignableFromBlockEventsRequest = func(t *testing.T, gateway *Gateway, request *BlockEventsRequest) *Signable { - return &Signable{ - Invocations: map[string]Invocation{ - "Events": { - Invoke: func() error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, err := request.Events(ctx) - return err - }, + Sign: func(s *signingTest, signature []byte) { + s.SignBlockEvents(signature) + }, + Invocations: map[string](func(*signingTest) ([]byte, error)){ + "Events": func(s *signingTest) ([]byte, error) { + return s.BlockEvents() }, }, - OfflineSign: func(signature []byte) *Signable { - bytes, err := request.Bytes() - require.NoError(t, err, "Bytes") - - result, err := gateway.NewSignedBlockEventsRequest(bytes, signature) - require.NoError(t, err, "NewSignedBlockEventsRequest") - - return newSignableFromBlockEventsRequest(t, gateway, result) + Recreate: func(s *signingTest) { + s.RecreateBlockEvents() }, - State: struct { - Digest []byte - }{ - Digest: request.Digest(), + State: func(s *signingTest) any { + return struct { + Digest []byte + }{ + Digest: s.blockEvents.Digest(), + } }, - Recreate: func() *Signable { - signedBytes, err1 := request.Bytes() - require.NoError(t, err1, "NewSignedBlockEventsRequestBytes") - - newBlockRequest, err2 := gateway.NewBlockEventsRequest(signedBytes) - require.NoError(t, err2, "newBlockRequest") - - return newSignableFromBlockEventsRequest(t, gateway, newBlockRequest) + }, + "FilteredBlockEvents": { + New: func(s *signingTest) { + s.NewFilteredBlockEvents() }, - } - } - - var newSignableFromFilteredBlockEventsRequest func(t *testing.T, gateway *Gateway, request *FilteredBlockEventsRequest) *Signable - newSignableFromFilteredBlockEventsRequest = func(t *testing.T, gateway *Gateway, request *FilteredBlockEventsRequest) *Signable { - return &Signable{ - Invocations: map[string]Invocation{ - "Events": { - Invoke: func() error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, err := request.Events(ctx) - return err - }, + Sign: func(s *signingTest, signature []byte) { + s.SignFilteredBlockEvents(signature) + }, + Invocations: map[string](func(*signingTest) ([]byte, error)){ + "Events": func(s *signingTest) ([]byte, error) { + return s.FilteredBlockEvents() }, }, - OfflineSign: func(signature []byte) *Signable { - bytes, err := request.Bytes() - require.NoError(t, err, "Bytes") - - result, err := gateway.NewSignedFilteredBlockEventsRequest(bytes, signature) - require.NoError(t, err, "NewSignedFilteredBlockEventsRequest") - - return newSignableFromFilteredBlockEventsRequest(t, gateway, result) + Recreate: func(s *signingTest) { + s.RecreateFilteredBlockEvents() }, - State: struct { - Digest []byte - }{ - Digest: request.Digest(), + State: func(s *signingTest) any { + return struct { + Digest []byte + }{ + Digest: s.filteredBlockEvents.Digest(), + } }, - Recreate: func() *Signable { - signedBytes, err := request.Bytes() - require.NoError(t, err, "NewSignedFilteredBlockEventsRequestBytes") - - newFilteredBlockRequest, err := gateway.NewFilteredBlockEventsRequest(signedBytes) - require.NoError(t, err, "newRequest") - - return newSignableFromFilteredBlockEventsRequest(t, gateway, newFilteredBlockRequest) + }, + "BlockAndPrivateDataEvents": { + New: func(s *signingTest) { + s.NewBlockAndPrivateDataEvents() }, - } - } - - var newSignableFromBlockAndPrivateDataEventsRequest func(t *testing.T, gateway *Gateway, request *BlockAndPrivateDataEventsRequest) *Signable - newSignableFromBlockAndPrivateDataEventsRequest = func(t *testing.T, gateway *Gateway, request *BlockAndPrivateDataEventsRequest) *Signable { - return &Signable{ - Invocations: map[string]Invocation{ - "Events": { - Invoke: func() error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - _, err := request.Events(ctx) - return err - }, + Sign: func(s *signingTest, signature []byte) { + s.SignBlockAndPrivateDataEvents(signature) + }, + Invocations: map[string](func(*signingTest) ([]byte, error)){ + "Events": func(s *signingTest) ([]byte, error) { + return s.BlockAndPrivateDataEvents() }, }, - OfflineSign: func(signature []byte) *Signable { - bytes, err := request.Bytes() - require.NoError(t, err, "Bytes") - - result, err := gateway.NewSignedBlockAndPrivateDataEventsRequest(bytes, signature) - require.NoError(t, err, "NewSignedBlockEventsRequest") - - return newSignableFromBlockAndPrivateDataEventsRequest(t, gateway, result) + Recreate: func(s *signingTest) { + s.RecreateBlockAndPrivateDataEvents() }, - State: struct { - Digest []byte - }{ - Digest: request.Digest(), + State: func(s *signingTest) any { + return struct { + Digest []byte + }{ + Digest: s.blockAndPrivateDataEvents.Digest(), + } }, - Recreate: func() *Signable { - signedBytes, err1 := request.Bytes() - require.NoError(t, err1, "NewSignedBlockAndPrivateDataEventsRequestBytes") + }, + } { + t.Run(testName, func(t *testing.T) { + for invocationName, invoke := range testCase.Invocations { + t.Run(invocationName, func(t *testing.T) { + t.Run("Returns error with no signer and no explicit signing", func(t *testing.T) { + s := NewSigningTest(t) + testCase.New(s) + _, err := invoke(s) + require.Error(t, err) + }) - newBlockAndPrivateDataRequest, err2 := gateway.NewBlockAndPrivateDataEventsRequest(signedBytes) - require.NoError(t, err2, "newBlockAndPrivateDataRequest") + t.Run("Uses off-line signature", func(t *testing.T) { + expected := []byte("SIGNATURE") + s := NewSigningTest(t) - return newSignableFromBlockAndPrivateDataEventsRequest(t, gateway, newBlockAndPrivateDataRequest) - }, - } + testCase.New(s) + testCase.Sign(s, expected) + actual, err := invoke(s) + require.NoError(t, err) + + require.Equal(t, expected, actual) + }) + + t.Run("retains signature", func(t *testing.T) { + expected := []byte("SIGNATURE") + s := NewSigningTest(t) + + testCase.New(s) + testCase.Sign(s, expected) + + testCase.Recreate(s) + actual, err := invoke(s) + require.NoError(t, err) + + require.Equal(t, expected, actual) + }) + }) + } + + t.Run("Retains state after signing", func(t *testing.T) { + s := NewSigningTest(t) + + testCase.New(s) + expected := testCase.State(s) + + testCase.Sign(s, []byte("SIGNATURE")) + actual := testCase.State(s) + + require.Equal(t, expected, actual) + }) + }) } +} - for testName, testCase := range map[string]struct { - Create func(*testing.T) *Signable - }{ - "Proposal": { - Create: func(t *testing.T) *Signable { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - signature = in.GetProposedTransaction().GetSignature() - }). - Return(&gateway.EvaluateResponse{ - Result: &peer.Response{ - Payload: nil, - }, - }, nil). - AnyTimes() - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - signature = in.GetProposedTransaction().GetSignature() - }). - Return(AssertNewEndorseResponse(t, "result", "network"), nil). - AnyTimes() - - gateway := newGatewayWithNoSign(t, WithGatewayClient(mockClient)) - contract := gateway.GetNetwork("NETWORK").GetContract("CHAINCODE") - - proposal, err := contract.NewProposal("transaction") - require.NoError(t, err) - - return newSignableFromProposal(t, gateway, proposal) - }, - }, - "Transaction": { - Create: func(t *testing.T) *Signable { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "result", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SubmitRequest, _ ...grpc.CallOption) { - signature = in.GetPreparedTransaction().GetSignature() - }). - Return(nil, nil). - AnyTimes() +type signingTest struct { + t *testing.T + mockConnection *MockClientConnInterface + gateway *Gateway + proposal *Proposal + transaction *Transaction + commit *Commit + chaincodeEvents *ChaincodeEventsRequest + blockEvents *BlockEventsRequest + filteredBlockEvents *FilteredBlockEventsRequest + blockAndPrivateDataEvents *BlockAndPrivateDataEventsRequest +} - gateway := newGatewayWithNoSign(t, WithGatewayClient(mockClient)) - contract := gateway.GetNetwork("NETWORK").GetContract("CHAINCODE") +func NewSigningTest(t *testing.T) *signingTest { + mockConnection := NewMockClientConnInterface(t) + gateway, err := Connect(TestCredentials.Identity(), WithClientConnection(mockConnection)) + require.NoError(t, err, "Connect") - unsignedProposal, err := contract.NewProposal("transaction") - require.NoError(t, err) + return &signingTest{ + t: t, + mockConnection: mockConnection, + gateway: gateway, + } +} - proposalBytes, err := unsignedProposal.Bytes() - require.NoError(t, err) +func (s *signingTest) NewProposal() { + result, err := s.gateway.GetNetwork("channel").GetContract("chaincode").NewProposal("transaction") + require.NoError(s.t, err, "NewProposal") + s.proposal = result +} - signedProposal, err := gateway.NewSignedProposal(proposalBytes, []byte("SIGNATURE")) - require.NoError(t, err) +func (s *signingTest) SignProposal(signature []byte) { + bytes := s.getBytes(s.proposal) + result, err := s.gateway.NewSignedProposal(bytes, signature) + require.NoError(s.t, err, "NewSignedProposal") + s.proposal = result +} - transaction, err := signedProposal.Endorse() - require.NoError(t, err) +func (s *signingTest) getBytes(serializable interface { + Bytes() ([]byte, error) +}) []byte { + bytes, err := serializable.Bytes() + require.NoError(s.t, err, "Bytes") + return bytes +} - return newSignableFromTransaction(t, gateway, transaction) - }, - }, - "Commit": { - Create: func(t *testing.T) *Signable { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "result", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil). - AnyTimes() - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedCommitStatusRequest, _ ...grpc.CallOption) { - signature = in.GetSignature() - }). - Return(&gateway.CommitStatusResponse{ - Result: peer.TxValidationCode_VALID, - }, nil). - AnyTimes() +func (s *signingTest) RecreateProposal() { + bytes := s.getBytes(s.proposal) + result, err := s.gateway.NewProposal(bytes) + require.NoError(s.t, err, "NewProposal") + s.proposal = result +} - gateway := newGatewayWithNoSign(t, WithGatewayClient(mockClient)) - contract := gateway.GetNetwork("NETWORK").GetContract("CHAINCODE") +func (s *signingTest) Evaluate() ([]byte, error) { + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(s.mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(nil)).Maybe() - unsignedProposal, err := contract.NewProposal("transaction") - require.NoError(t, err) + _, err := s.proposal.Evaluate() + if err != nil { + return nil, err + } - proposalBytes, err := unsignedProposal.Bytes() - require.NoError(t, err) + return (<-requests).GetProposedTransaction().GetSignature(), nil +} - signedProposal, err := gateway.NewSignedProposal(proposalBytes, []byte("SIGNATURE")) - require.NoError(t, err) +func (s *signingTest) endorse() (*Transaction, []byte, error) { + requests := make(chan *gateway.EndorseRequest, 1) + response := AssertNewEndorseResponse(s.t, "TRANSACTION_RESULT", "network") + ExpectEndorse(s.mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(response)).Maybe() - unsignedTransaction, err := signedProposal.Endorse() - require.NoError(t, err) + transaction, err := s.proposal.Endorse() + if err != nil { + return nil, nil, err + } - transactionBytes, err := unsignedTransaction.Bytes() - require.NoError(t, err) + return transaction, (<-requests).ProposedTransaction.GetSignature(), nil +} - signedTransaction, err := gateway.NewSignedTransaction(transactionBytes, []byte("SIGNATURE")) - require.NoError(t, err) +func (s *signingTest) Endorse() ([]byte, error) { + _, signature, err := s.endorse() + return signature, err +} - commit, err := signedTransaction.Submit() - require.NoError(t, err) +func (s *signingTest) NewTransaction() { + s.NewProposal() + s.SignProposal([]byte("SIGNATURE")) + transaction, _, err := s.endorse() + require.NoError(s.t, err, "Endorse") - return newSignableFromCommit(t, gateway, commit) - }, - }, - "Chaincode events": { - Create: func(t *testing.T) *Signable { - controller := gomock.NewController(t) - mockClient := NewMockGatewayClient(controller) - mockEvents := NewMockGateway_ChaincodeEventsClient(controller) + s.transaction = transaction +} - mockClient.EXPECT().ChaincodeEvents(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedChaincodeEventsRequest, _ ...grpc.CallOption) { - signature = in.GetSignature() - }). - Return(mockEvents, nil). - AnyTimes() +func (s *signingTest) SignTransaction(signature []byte) { + bytes := s.getBytes(s.transaction) + result, err := s.gateway.NewSignedTransaction(bytes, signature) + require.NoError(s.t, err, "NewSignedTransaction") - mockEvents.EXPECT().Recv(). - Return(nil, errors.New("fake")). - AnyTimes() + s.transaction = result +} - gateway := newGatewayWithNoSign(t, WithGatewayClient(mockClient)) - network := gateway.GetNetwork("NETWORK") +func (s *signingTest) RecreateTransaction() { + bytes := s.getBytes(s.transaction) + result, err := s.gateway.NewTransaction(bytes) + require.NoError(s.t, err, "NewTransaction") - request, err := network.NewChaincodeEventsRequest("CHAINCODE") - require.NoError(t, err) + s.transaction = result +} - return newSignableFromChaincodeEventsRequest(t, gateway, request) - }, - }, - "Block events": { - Create: func(t *testing.T) *Signable { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := newMockDeliverEvents(controller) +func (s *signingTest) submit() (*Commit, []byte, error) { + requests := make(chan *gateway.SubmitRequest, 1) + ExpectSubmit(s.mockConnection, CaptureInvokeRequest(requests)).Maybe() - mockClient.EXPECT().Deliver(gomock.Any(), gomock.Any()). - Return(mockEvents, nil). - AnyTimes() + commit, err := s.transaction.Submit() + if err != nil { + return nil, nil, err + } - gateway := newGatewayWithNoSign(t, WithGatewayClient(NewMockGatewayClient(controller)), WithDeliverClient(mockClient)) - network := gateway.GetNetwork("NETWORK") + return commit, (<-requests).GetPreparedTransaction().GetSignature(), nil +} - request, err := network.NewBlockEventsRequest() - require.NoError(t, err) +func (s *signingTest) Submit() ([]byte, error) { + _, signature, err := s.submit() + return signature, err +} - return newSignableFromBlockEventsRequest(t, gateway, request) - }, - }, - "Filtered block events": { - Create: func(t *testing.T) *Signable { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := newMockDeliverEvents(controller) +func (s *signingTest) NewCommit() { + s.NewTransaction() + s.SignTransaction([]byte("SIGNATURE")) + commit, _, err := s.submit() + require.NoError(s.t, err, "Submit") - mockClient.EXPECT().DeliverFiltered(gomock.Any(), gomock.Any()). - Return(mockEvents, nil). - AnyTimes() + s.commit = commit +} - gateway := newGatewayWithNoSign(t, WithGatewayClient(NewMockGatewayClient(controller)), WithDeliverClient(mockClient)) - network := gateway.GetNetwork("NETWORK") +func (s *signingTest) SignCommit(signature []byte) { + bytes := s.getBytes(s.commit) + result, err := s.gateway.NewSignedCommit(bytes, signature) + require.NoError(s.t, err, "NewSignedCommit") - request, err := network.NewFilteredBlockEventsRequest() - require.NoError(t, err) + s.commit = result +} - return newSignableFromFilteredBlockEventsRequest(t, gateway, request) - }, - }, - "Block and private data events": { - Create: func(t *testing.T) *Signable { - controller := gomock.NewController(t) - mockClient := NewMockDeliverClient(controller) - mockEvents := newMockDeliverEvents(controller) +func (s *signingTest) RecreateCommit() { + bytes := s.getBytes(s.commit) + result, err := s.gateway.NewCommit(bytes) + require.NoError(s.t, err, "NewCommit") + s.commit = result +} - mockClient.EXPECT().DeliverWithPrivateData(gomock.Any(), gomock.Any()). - Return(mockEvents, nil). - AnyTimes() +func (s *signingTest) CommitStatus() ([]byte, error) { + requests := make(chan *gateway.SignedCommitStatusRequest, 1) + ExpectCommitStatus(s.mockConnection, CaptureInvokeRequest(requests), WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)).Maybe() - gateway := newGatewayWithNoSign(t, WithGatewayClient(NewMockGatewayClient(controller)), WithDeliverClient(mockClient)) - network := gateway.GetNetwork("NETWORK") + _, err := s.commit.Status() + if err != nil { + return nil, err + } - request, err := network.NewBlockAndPrivateDataEventsRequest() - require.NoError(t, err) + return (<-requests).GetSignature(), nil +} - return newSignableFromBlockAndPrivateDataEventsRequest(t, gateway, request) - }, - }, - } { - t.Run(testName, func(t *testing.T) { - unsigned := testCase.Create(t) +func (s *signingTest) NewChaincodeEvents() { + result, err := s.gateway.GetNetwork("channel").NewChaincodeEventsRequest("chaincode") + require.NoError(s.t, err, "NewChaincodeEventsRequest") + s.chaincodeEvents = result +} - for invocationName, invocation := range unsigned.Invocations { - t.Run(invocationName, func(t *testing.T) { - t.Run("Returns error with no signer and no explicit signing", func(t *testing.T) { - err := invocation.Invoke() - require.Error(t, err) - }) +func (s *signingTest) SignChaincodeEvents(signature []byte) { + bytes := s.getBytes(s.chaincodeEvents) + result, err := s.gateway.NewSignedChaincodeEventsRequest(bytes, signature) + require.NoError(s.t, err, "NewSignedChaincodeEventsRequest") + s.chaincodeEvents = result +} - t.Run("Uses off-line signature", func(t *testing.T) { - signature = nil - expected := []byte("SIGNATURE") +func (s *signingTest) RecreateChaincodeEvents() { + bytes := s.getBytes(s.chaincodeEvents) + result, err := s.gateway.NewChaincodeEventsRequest(bytes) + require.NoError(s.t, err, "NewChaincodeEventsRequest") + s.chaincodeEvents = result +} - signed := unsigned.OfflineSign(expected) - err := signed.Invocations[invocationName].Invoke() - require.NoError(t, err) +func (s *signingTest) ChaincodeEvents() ([]byte, error) { + mockStream := NewMockClientStream(s.t) + ExpectChaincodeEvents(s.mockConnection, WithNewStreamResult(mockStream)).Maybe() - require.EqualValues(t, expected, signature) - }) + messages := make(chan *gateway.SignedChaincodeEventsRequest, 1) + ExpectSendMsg(mockStream, CaptureSendMsg(messages)).Maybe() + mockStream.EXPECT().CloseSend().Return(nil).Maybe() + ExpectRecvMsg(mockStream).Return(io.EOF).Maybe() - t.Run("retains signature", func(t *testing.T) { - signature = nil - expected := []byte("SIGNATURE") + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - signed := unsigned.OfflineSign(expected) - recreated := signed.Recreate() - err := recreated.Invocations[invocationName].Invoke() - require.NoError(t, err) + _, err := s.chaincodeEvents.Events(ctx) + if err != nil { + return nil, err + } - require.EqualValues(t, expected, signature) + return (<-messages).GetSignature(), nil +} - }) - }) - } +func (s *signingTest) NewBlockEvents() { + result, err := s.gateway.GetNetwork("channel").NewBlockEventsRequest() + require.NoError(s.t, err, "NewBlockEventsRequest") + s.blockEvents = result +} - t.Run("Retains state after signing", func(t *testing.T) { - signed := unsigned.OfflineSign([]byte("SIGNATURE")) - require.EqualValues(t, unsigned.State, signed.State) - }) - }) +func (s *signingTest) SignBlockEvents(signature []byte) { + bytes := s.getBytes(s.blockEvents) + result, err := s.gateway.NewSignedBlockEventsRequest(bytes, signature) + require.NoError(s.t, err, "NewSignedBlockEventsRequest") + s.blockEvents = result +} + +func (s *signingTest) RecreateBlockEvents() { + bytes := s.getBytes(s.blockEvents) + result, err := s.gateway.NewBlockEventsRequest(bytes) + require.NoError(s.t, err, "NewBlockEventsRequest") + s.blockEvents = result +} + +func (s *signingTest) BlockEvents() ([]byte, error) { + return s.deliverEvents(func(ctx context.Context) error { + _, err := s.blockEvents.Events(ctx) + return err + }) +} + +func (s *signingTest) deliverEvents(invoke func(context.Context) error) ([]byte, error) { + mockStream := NewMockClientStream(s.t) + ExpectDeliver(s.mockConnection, WithNewStreamResult(mockStream)).Maybe() + ExpectDeliverFiltered(s.mockConnection, WithNewStreamResult(mockStream)).Maybe() + ExpectDeliverWithPrivateData(s.mockConnection, WithNewStreamResult(mockStream)).Maybe() + + messages := make(chan *common.Envelope, 1) + ExpectSendMsg(mockStream, CaptureSendMsg(messages)).Maybe() + mockStream.EXPECT().CloseSend().Return(nil).Maybe() + ExpectRecvMsg(mockStream).Return(io.EOF).Maybe() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err := invoke(ctx) + if err != nil { + return nil, err } + + return (<-messages).GetSignature(), nil +} + +func (s *signingTest) NewFilteredBlockEvents() { + result, err := s.gateway.GetNetwork("channel").NewFilteredBlockEventsRequest() + require.NoError(s.t, err, "NewFilteredBlockEventsRequest") + s.filteredBlockEvents = result +} + +func (s *signingTest) SignFilteredBlockEvents(signature []byte) { + bytes := s.getBytes(s.filteredBlockEvents) + result, err := s.gateway.NewSignedFilteredBlockEventsRequest(bytes, signature) + require.NoError(s.t, err, "NewSignedFilteredBlockEventsRequest") + s.filteredBlockEvents = result +} + +func (s *signingTest) RecreateFilteredBlockEvents() { + bytes := s.getBytes(s.filteredBlockEvents) + result, err := s.gateway.NewFilteredBlockEventsRequest(bytes) + require.NoError(s.t, err, "NewFilteredBlockEventsRequest") + s.filteredBlockEvents = result +} + +func (s *signingTest) FilteredBlockEvents() ([]byte, error) { + return s.deliverEvents(func(ctx context.Context) error { + _, err := s.filteredBlockEvents.Events(ctx) + return err + }) +} + +func (s *signingTest) NewBlockAndPrivateDataEvents() { + result, err := s.gateway.GetNetwork("channel").NewBlockAndPrivateDataEventsRequest() + require.NoError(s.t, err, "NewBlockAndPrivateDataEventsRequest") + s.blockAndPrivateDataEvents = result +} + +func (s *signingTest) SignBlockAndPrivateDataEvents(signature []byte) { + bytes := s.getBytes(s.blockAndPrivateDataEvents) + result, err := s.gateway.NewSignedBlockAndPrivateDataEventsRequest(bytes, signature) + require.NoError(s.t, err, "NewSignedBlockAndPrivateDataEventsRequest") + s.blockAndPrivateDataEvents = result +} + +func (s *signingTest) RecreateBlockAndPrivateDataEvents() { + bytes := s.getBytes(s.blockAndPrivateDataEvents) + result, err := s.gateway.NewBlockAndPrivateDataEventsRequest(bytes) + require.NoError(s.t, err, "NewBlockAndPrivateDataEventsRequest") + s.blockAndPrivateDataEvents = result +} + +func (s *signingTest) BlockAndPrivateDataEvents() ([]byte, error) { + return s.deliverEvents(func(ctx context.Context) error { + _, err := s.blockAndPrivateDataEvents.Events(ctx) + return err + }) } diff --git a/pkg/client/protobuf_test.go b/pkg/client/protobuf_test.go new file mode 100644 index 000000000..dc02d536e --- /dev/null +++ b/pkg/client/protobuf_test.go @@ -0,0 +1,422 @@ +// Copyright IBM Corp. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package client + +import ( + "context" + "fmt" + "io" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hyperledger/fabric-protos-go-apiv2/common" + "github.com/hyperledger/fabric-protos-go-apiv2/gateway" + "github.com/hyperledger/fabric-protos-go-apiv2/msp" + "github.com/hyperledger/fabric-protos-go-apiv2/peer" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/prototext" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/runtime/protoiface" + "google.golang.org/protobuf/testing/protocmp" +) + +// AssertProtoEqual ensures an expected protobuf message matches an actual message +func AssertProtoEqual(t *testing.T, expected protoreflect.ProtoMessage, actual protoreflect.ProtoMessage) { + if diff := cmp.Diff(expected, actual, protocmp.Transform()); diff != "" { + require.FailNow(t, fmt.Sprintf( + "Not equal:\nexpected: %s\nactual : %s\n\nDiff:\n- Expected\n+ Actual\n\n%s", + formatProto(expected), + formatProto(actual), + diff, + )) + } +} + +func formatProto(message proto.Message) string { + if message == nil { + return fmt.Sprintf("%T", message) + } + + marshal := prototext.MarshalOptions{ + Multiline: true, + Indent: "\t", + AllowPartial: true, + } + formatted := strings.TrimSpace(marshal.Format(message)) + return fmt.Sprintf("%s{\n%s\n}", protoMessageType(message), indent(formatted)) +} + +func protoMessageType(message proto.Message) string { + return string(message.ProtoReflect().Descriptor().Name()) +} + +func indent(text string) string { + return "\t" + strings.ReplaceAll(text, "\n", "\n\t") +} + +// AssertUnmarshal ensures that a protobuf is umarshaled without error +func AssertUnmarshal(t *testing.T, b []byte, m protoreflect.ProtoMessage) { + err := proto.Unmarshal(b, m) + require.NoError(t, err) +} + +// AssertUnmarshalProposalPayload ensures that a ChaincodeProposalPayload protobuf is umarshalled without error +func AssertUnmarshalProposalPayload(t *testing.T, proposedTransaction *peer.SignedProposal) *peer.ChaincodeProposalPayload { + proposal := &peer.Proposal{} + AssertUnmarshal(t, proposedTransaction.ProposalBytes, proposal) + + payload := &peer.ChaincodeProposalPayload{} + AssertUnmarshal(t, proposal.Payload, payload) + + return payload +} + +// AssertUnmarshalInvocationSpec ensures that a ChaincodeInvocationSpec protobuf is umarshalled without error +func AssertUnmarshalInvocationSpec(t *testing.T, proposedTransaction *peer.SignedProposal) *peer.ChaincodeInvocationSpec { + proposal := &peer.Proposal{} + AssertUnmarshal(t, proposedTransaction.ProposalBytes, proposal) + + payload := &peer.ChaincodeProposalPayload{} + AssertUnmarshal(t, proposal.Payload, payload) + + input := &peer.ChaincodeInvocationSpec{} + AssertUnmarshal(t, payload.Input, input) + + return input +} + +// AssertUnmarshalChannelheader ensures that a ChannelHeader protobuf is umarshalled without error +func AssertUnmarshalChannelheader(t *testing.T, proposedTransaction *peer.SignedProposal) *common.ChannelHeader { + header := AssertUnmarshalHeader(t, proposedTransaction) + + channelHeader := &common.ChannelHeader{} + AssertUnmarshal(t, header.ChannelHeader, channelHeader) + + return channelHeader +} + +// AssertUnmarshalHeader ensures that a Header protobuf is umarshalled without error +func AssertUnmarshalHeader(t *testing.T, proposedTransaction *peer.SignedProposal) *common.Header { + proposal := &peer.Proposal{} + AssertUnmarshal(t, proposedTransaction.ProposalBytes, proposal) + + header := &common.Header{} + AssertUnmarshal(t, proposal.Header, header) + + return header +} + +// AssertUnmarshalSignatureHeader ensures that a SignatureHeader protobuf is umarshalled without error +func AssertUnmarshalSignatureHeader(t *testing.T, proposedTransaction *peer.SignedProposal) *common.SignatureHeader { + header := AssertUnmarshalHeader(t, proposedTransaction) + + signatureHeader := &common.SignatureHeader{} + AssertUnmarshal(t, header.SignatureHeader, signatureHeader) + + return signatureHeader +} + +type invokeFunction func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error + +func ExpectEvaluate(mockConnection *MockClientConnInterface, options ...invokeFunction) *MockClientConnInterface_Invoke_Call { + invokeCall := mockConnection.EXPECT(). + Invoke(mock.Anything, "/gateway.Gateway/Evaluate", mock.Anything, mock.Anything, mock.Anything) + fakeInvoke(invokeCall, options...) + return invokeCall +} + +func fakeInvoke(mock *MockClientConnInterface_Invoke_Call, options ...invokeFunction) { + mock.RunAndReturn(func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + for _, option := range options { + if err := option(ctx, method, args, reply, opts...); err != nil { + return err + } + } + + return nil + }) +} + +func WithEvaluateResponse(value []byte) invokeFunction { + return func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + proto.Merge(reply.(proto.Message), &gateway.EvaluateResponse{ + Result: &peer.Response{ + Payload: value, + }, + }) + return nil + } +} + +// WithInvokeContextErr causes the invoke to return any error associated with the invocation context; otherwise it has +// no effect. +func WithInvokeContextErr() invokeFunction { + return func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + return ctx.Err() + } +} + +func WithInvokeError(err error) invokeFunction { + return func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + return err + } +} + +func CaptureInvokeRequest[T proto.Message](requests chan<- T) invokeFunction { + return func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + TrySend(requests, args.(T)) + return nil + } +} + +func CaptureInvokeOptions(callOptions chan<- []grpc.CallOption) invokeFunction { + return func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + TrySend(callOptions, opts) + return nil + } +} + +func CaptureInvokeContext(contexts chan<- context.Context) invokeFunction { + return func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + TrySend(contexts, ctx) + return nil + } +} + +func TrySend[T any](channel chan<- T, value T) bool { + select { + case channel <- value: + return true + default: + return false + } +} + +func NewStatusError(t *testing.T, code codes.Code, message string, details ...protoiface.MessageV1) error { + s, err := status.New(code, message).WithDetails(details...) + require.NoError(t, err) + + return s.Err() +} + +func ExpectEndorse(mockConnection *MockClientConnInterface, options ...invokeFunction) *MockClientConnInterface_Invoke_Call { + invokeCall := mockConnection.EXPECT(). + Invoke(mock.Anything, "/gateway.Gateway/Endorse", mock.Anything, mock.Anything, mock.Anything) + fakeInvoke(invokeCall, options...) + return invokeCall +} + +func ExpectSubmit(mockConnection *MockClientConnInterface, options ...invokeFunction) *MockClientConnInterface_Invoke_Call { + invokeCall := mockConnection.EXPECT(). + Invoke(mock.Anything, "/gateway.Gateway/Submit", mock.Anything, mock.Anything, mock.Anything) + fakeInvoke(invokeCall, options...) + return invokeCall +} + +func ExpectCommitStatus(mockConnection *MockClientConnInterface, options ...invokeFunction) *MockClientConnInterface_Invoke_Call { + invokeCall := mockConnection.EXPECT(). + Invoke(mock.Anything, "/gateway.Gateway/CommitStatus", mock.Anything, mock.Anything, mock.Anything) + fakeInvoke(invokeCall, options...) + return invokeCall +} + +func WithEndorseResponse(response *gateway.EndorseResponse) invokeFunction { + return func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + proto.Merge(reply.(proto.Message), response) + return nil + } +} + +func WithCommitStatusResponse(status peer.TxValidationCode, blockNumber uint64) invokeFunction { + return func(ctx context.Context, method string, args any, reply any, opts ...grpc.CallOption) error { + proto.Merge(reply.(proto.Message), &gateway.CommitStatusResponse{ + Result: status, + BlockNumber: blockNumber, + }) + return nil + } +} + +type newStreamFunction func(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) + +func ExpectChaincodeEvents(mockConnection *MockClientConnInterface, options ...newStreamFunction) *MockClientConnInterface_NewStream_Call { + newStreamCall := mockConnection.EXPECT(). + NewStream(mock.Anything, mock.Anything, "/gateway.Gateway/ChaincodeEvents", mock.Anything) + fakeNewStream(newStreamCall, options...) + return newStreamCall +} + +func ExpectDeliver(mockConnection *MockClientConnInterface, options ...newStreamFunction) *MockClientConnInterface_NewStream_Call { + newStreamCall := mockConnection.EXPECT(). + NewStream(mock.Anything, mock.Anything, "/protos.Deliver/Deliver", mock.Anything) + fakeNewStream(newStreamCall, options...) + return newStreamCall +} + +func ExpectDeliverFiltered(mockConnection *MockClientConnInterface, options ...newStreamFunction) *MockClientConnInterface_NewStream_Call { + newStreamCall := mockConnection.EXPECT(). + NewStream(mock.Anything, mock.Anything, "/protos.Deliver/DeliverFiltered", mock.Anything) + fakeNewStream(newStreamCall, options...) + return newStreamCall +} + +func ExpectDeliverWithPrivateData(mockConnection *MockClientConnInterface, options ...newStreamFunction) *MockClientConnInterface_NewStream_Call { + newStreamCall := mockConnection.EXPECT(). + NewStream(mock.Anything, mock.Anything, "/protos.Deliver/DeliverWithPrivateData", mock.Anything) + fakeNewStream(newStreamCall, options...) + return newStreamCall +} + +func fakeNewStream(mock *MockClientConnInterface_NewStream_Call, options ...newStreamFunction) { + mock.RunAndReturn(func(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { + for _, option := range options { + if stream, err := option(ctx, desc, method, opts...); stream != nil || err != nil { + return stream, err + } + } + + return nil, nil + }) +} + +func WithNewStreamResult(stream grpc.ClientStream) newStreamFunction { + return func(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { + return stream, nil + } +} + +func WithNewStreamError(err error) newStreamFunction { + return func(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { + return nil, err + } +} + +func CaptureNewStreamOptions(callOptions chan<- []grpc.CallOption) newStreamFunction { + return func(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { + TrySend(callOptions, opts) + return nil, nil + } +} + +type sendMsgFunction func(message any) error + +func ExpectSendMsg(mockStream *MockClientStream, options ...sendMsgFunction) *MockClientStream_SendMsg_Call { + result := mockStream.EXPECT().SendMsg(mock.Anything) + result.RunAndReturn(func(message any) error { + for _, option := range options { + if err := option(message); err != nil { + return err + } + } + + return nil + }) + return result +} + +func CaptureSendMsg[T proto.Message](messages chan<- T) sendMsgFunction { + return func(message any) error { + TrySend(messages, message.(T)) + return nil + } +} + +type recvMsgFunction func(message any) error + +func ExpectRecvMsg(mockStream *MockClientStream, options ...recvMsgFunction) *MockClientStream_RecvMsg_Call { + result := mockStream.EXPECT().RecvMsg(mock.Anything) + + if len(options) > 0 { + result.RunAndReturn(func(message any) error { + for _, option := range options { + if err := option(message); err != nil { + return err + } + } + + return nil + }) + } + + return result +} + +func WithRecvMsgs[T proto.Message](responses ...T) recvMsgFunction { + responseChannel := make(chan proto.Message, len(responses)) + for _, response := range responses { + responseChannel <- response + } + close(responseChannel) + + return func(message any) error { + response, ok := <-responseChannel + if !ok { + return io.EOF + } + + proto.Merge(message.(proto.Message), response) + return nil + } +} + +func AssertMarshal(t *testing.T, message protoreflect.ProtoMessage, msgAndArgs ...any) []byte { + bytes, err := proto.Marshal(message) + require.NoError(t, err, msgAndArgs...) + return bytes +} + +func AssertNewEndorseResponse(t *testing.T, result string, channelName string) *gateway.EndorseResponse { + return &gateway.EndorseResponse{ + PreparedTransaction: &common.Envelope{ + Payload: AssertMarshal(t, &common.Payload{ + Header: &common.Header{ + ChannelHeader: AssertMarshal(t, &common.ChannelHeader{ + ChannelId: channelName, + }), + }, + Data: AssertMarshal(t, &peer.Transaction{ + Actions: []*peer.TransactionAction{ + { + Payload: AssertMarshal(t, &peer.ChaincodeActionPayload{ + Action: &peer.ChaincodeEndorsedAction{ + ProposalResponsePayload: AssertMarshal(t, &peer.ProposalResponsePayload{ + Extension: AssertMarshal(t, &peer.ChaincodeAction{ + Response: &peer.Response{ + Payload: []byte(result), + }, + }), + }), + }, + }), + }, + }, + }), + }), + }, + } +} + +func AssertValidBlockEventRequestHeader(t *testing.T, payload *common.Payload, expectedChannel string) { + channelHeader := &common.ChannelHeader{} + AssertUnmarshal(t, payload.GetHeader().GetChannelHeader(), channelHeader) + + require.Equal(t, expectedChannel, channelHeader.GetChannelId(), "channel name") + + signatureHeader := &common.SignatureHeader{} + AssertUnmarshal(t, payload.GetHeader().GetSignatureHeader(), signatureHeader) + + expectedCreator := &msp.SerializedIdentity{ + Mspid: TestCredentials.Identity().MspID(), + IdBytes: TestCredentials.Identity().Credentials(), + } + actualCreator := &msp.SerializedIdentity{} + AssertUnmarshal(t, signatureHeader.GetCreator(), actualCreator) + AssertProtoEqual(t, expectedCreator, actualCreator) +} diff --git a/pkg/client/sign_test.go b/pkg/client/sign_test.go index d9cd96f37..1c567254b 100644 --- a/pkg/client/sign_test.go +++ b/pkg/client/sign_test.go @@ -4,110 +4,81 @@ package client import ( - "context" "testing" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - "google.golang.org/grpc" ) func TestSign(t *testing.T) { - evaluateResponse := &gateway.EvaluateResponse{ - Result: &peer.Response{ - Payload: nil, - }, - } - - statusResponse := &gateway.CommitStatusResponse{ - Result: peer.TxValidationCode_VALID, - } + endorseResponse := AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network") t.Run("Evaluate signs proposal using client signing implementation", func(t *testing.T) { expected := []byte("SIGNATURE") + + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EvaluateRequest, 1) + ExpectEvaluate(mockConnection, CaptureInvokeRequest(requests), WithEvaluateResponse(expected)) + sign := func(digest []byte) ([]byte, error) { return expected, nil } - var actual []byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EvaluateRequest, _ ...grpc.CallOption) { - actual = in.ProposedTransaction.Signature - }). - Return(evaluateResponse, nil). - Times(1) - - contract := AssertNewTestContract(t, "contract", WithGatewayClient(mockClient), WithSign(sign)) + contract := AssertNewTestContract(t, "contract", WithClientConnection(mockConnection), WithSign(sign)) _, err := contract.EvaluateTransaction("transaction") require.NoError(t, err) + actual := (<-requests).ProposedTransaction.Signature require.EqualValues(t, expected, actual) }) t.Run("Submit signs proposal using client signing implementation", func(t *testing.T) { expected := []byte("SIGNATURE") + + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(endorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + sign := func(digest []byte) ([]byte, error) { return expected, nil } - var actual []byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - actual = in.ProposedTransaction.Signature - }). - Return(AssertNewEndorseResponse(t, "result", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(statusResponse, nil) - - contract := AssertNewTestContract(t, "contract", WithGatewayClient(mockClient), WithSign(sign)) + contract := AssertNewTestContract(t, "contract", WithClientConnection(mockConnection), WithSign(sign)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) + actual := (<-requests).ProposedTransaction.Signature require.EqualValues(t, expected, actual) }) t.Run("Submit signs transaction using client signing implementation", func(t *testing.T) { expected := []byte("SIGNATURE") + + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(endorseResponse)) + requests := make(chan *gateway.SubmitRequest, 1) + ExpectSubmit(mockConnection, CaptureInvokeRequest(requests)) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + sign := func(digest []byte) ([]byte, error) { return expected, nil } - var actual []byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "result", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SubmitRequest, _ ...grpc.CallOption) { - actual = in.PreparedTransaction.Signature - }). - Return(nil, nil). - Times(1) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(statusResponse, nil) - - contract := AssertNewTestContract(t, "contract", WithGatewayClient(mockClient), WithSign(sign)) + contract := AssertNewTestContract(t, "contract", WithClientConnection(mockConnection), WithSign(sign)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) + actual := (<-requests).PreparedTransaction.Signature require.EqualValues(t, expected, actual) }) t.Run("Default error implementation is used if no signing implementation supplied", func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Evaluate(gomock.Any(), gomock.Any()). - Return(evaluateResponse, nil). - AnyTimes() - - mockDeliver := NewMockDeliverClient(gomock.NewController(t)) + mockConnection := NewMockClientConnInterface(t) - gateway, err := Connect(TestCredentials.Identity(), WithGatewayClient(mockClient), WithDeliverClient(mockDeliver)) + gateway, err := Connect(TestCredentials.Identity(), WithClientConnection(mockConnection)) require.NoError(t, err) contract := gateway.GetNetwork("network").GetContract("chaincode") diff --git a/pkg/client/submit_test.go b/pkg/client/submit_test.go index c63a3bdbb..6fea403d9 100644 --- a/pkg/client/submit_test.go +++ b/pkg/client/submit_test.go @@ -6,73 +6,47 @@ package client import ( "context" "testing" - "time" - "github.com/hyperledger/fabric-gateway/pkg/internal/test" - "github.com/hyperledger/fabric-protos-go-apiv2/common" "github.com/hyperledger/fabric-protos-go-apiv2/gateway" "github.com/hyperledger/fabric-protos-go-apiv2/peer" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" ) -func AssertMarshal(t *testing.T, message protoreflect.ProtoMessage, msgAndArgs ...interface{}) []byte { - bytes, err := proto.Marshal(message) - require.NoError(t, err, msgAndArgs...) - return bytes +func ReceiveAll[T any](channel <-chan T) []T { + var results []T + + for { + if value, ok := TryReceive(channel); !ok { + return results + } else { + results = append(results, value) + } + } } -func AssertNewEndorseResponse(t *testing.T, result string, channelName string) *gateway.EndorseResponse { - return &gateway.EndorseResponse{ - PreparedTransaction: &common.Envelope{ - Payload: AssertMarshal(t, &common.Payload{ - Header: &common.Header{ - ChannelHeader: AssertMarshal(t, &common.ChannelHeader{ - ChannelId: channelName, - }), - }, - Data: AssertMarshal(t, &peer.Transaction{ - Actions: []*peer.TransactionAction{ - { - Payload: AssertMarshal(t, &peer.ChaincodeActionPayload{ - Action: &peer.ChaincodeEndorsedAction{ - ProposalResponsePayload: AssertMarshal(t, &peer.ProposalResponsePayload{ - Extension: AssertMarshal(t, &peer.ChaincodeAction{ - Response: &peer.Response{ - Payload: []byte(result), - }, - }), - }), - }, - }), - }, - }, - }), - }), - }, +func TryReceive[T any](channel <-chan T) (T, bool) { + var result T + select { + case result = <-channel: + return result, true + default: + return result, false } } func TestSubmitTransaction(t *testing.T) { - newCommitStatusResponse := func(status peer.TxValidationCode, blockNumber uint64) *gateway.CommitStatusResponse { - return &gateway.CommitStatusResponse{ - Result: status, - BlockNumber: blockNumber, - } - } + defaultEndorseResponse := AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network") t.Run("Returns endorse error", func(t *testing.T) { expected := NewStatusError(t, codes.Aborted, "ENDORSE_ERROR") - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(nil, expected) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithInvokeError(expected)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") @@ -87,13 +61,12 @@ func TestSubmitTransaction(t *testing.T) { t.Run("Returns submit error", func(t *testing.T) { expected := NewStatusError(t, codes.Aborted, "SUBMIT_ERROR") - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, expected) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection, WithInvokeError(expected)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") transaction, err := proposal.Endorse() @@ -110,15 +83,13 @@ func TestSubmitTransaction(t *testing.T) { t.Run("Returns commit status error", func(t *testing.T) { expected := NewStatusError(t, codes.Aborted, "COMMIT_ERROR") - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(nil, expected) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithInvokeError(expected)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") transaction, err := proposal.Endorse() @@ -136,7 +107,7 @@ func TestSubmitTransaction(t *testing.T) { }) for name, testCase := range map[string]struct { - run func(t *testing.T, contract *Contract) ([]byte, error) + run func(*testing.T, *Contract) ([]byte, error) }{ "SubmitTransaction returns result for committed transaction": { run: func(t *testing.T, contract *Contract) ([]byte, error) { @@ -151,15 +122,13 @@ func TestSubmitTransaction(t *testing.T) { } { t.Run(name, func(t *testing.T) { expected := []byte("TRANSACTION_RESULT") - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) actual, err := testCase.run(t, contract) require.NoError(t, err) @@ -169,7 +138,7 @@ func TestSubmitTransaction(t *testing.T) { } for testName, testCase := range map[string]struct { - run func(t *testing.T, contract *Contract) ([]byte, error) + run func(*testing.T, *Contract) ([]byte, error) }{ "SubmitTransaction returns commit error for invalid commit status": { run: func(t *testing.T, contract *Contract) ([]byte, error) { @@ -183,15 +152,12 @@ func TestSubmitTransaction(t *testing.T) { }, } { t.Run(testName, func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_MVCC_READ_CONFLICT, 1), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_MVCC_READ_CONFLICT, 1)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, err := testCase.run(t, contract) var actual *CommitError @@ -202,388 +168,309 @@ func TestSubmitTransaction(t *testing.T) { } t.Run("Includes channel name in proposal", func(t *testing.T) { - var actual string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - actual = test.AssertUnmarshalChannelheader(t, in.ProposedTransaction).ChannelId - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) + actual := AssertUnmarshalChannelheader(t, (<-requests).ProposedTransaction).ChannelId expected := contract.channelName require.Equal(t, expected, actual) }) t.Run("Includes chaincode name in proposal", func(t *testing.T) { - var actual string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - actual = test.AssertUnmarshalInvocationSpec(t, in.ProposedTransaction).ChaincodeSpec.ChaincodeId.Name - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) + actual := AssertUnmarshalInvocationSpec(t, (<-requests).ProposedTransaction).ChaincodeSpec.ChaincodeId.Name expected := contract.chaincodeName require.Equal(t, expected, actual) }) t.Run("Includes transaction name in proposal for default contract", func(t *testing.T) { - var args [][]byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - args = test.AssertUnmarshalInvocationSpec(t, in.ProposedTransaction).ChaincodeSpec.Input.Args - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) - expected := "TRANSACTION_NAME" + + mockConnection := NewMockClientConnInterface(t) + + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) + _, err := contract.SubmitTransaction(expected) require.NoError(t, err) + args := AssertUnmarshalInvocationSpec(t, (<-requests).ProposedTransaction).ChaincodeSpec.Input.Args actual := string(args[0]) require.Equal(t, expected, actual) }) t.Run("Includes transaction name in proposal for named contract", func(t *testing.T) { - var args [][]byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - args = test.AssertUnmarshalInvocationSpec(t, in.ProposedTransaction).ChaincodeSpec.Input.Args - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - - contract := AssertNewTestContractWithName(t, "chaincode", "CONTRACT_NAME", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := AssertNewTestContractWithName(t, "chaincode", "CONTRACT_NAME", WithClientConnection(mockConnection)) _, err := contract.SubmitTransaction("TRANSACTION_NAME") require.NoError(t, err) + args := AssertUnmarshalInvocationSpec(t, (<-requests).ProposedTransaction).ChaincodeSpec.Input.Args actual := string(args[0]) expected := "CONTRACT_NAME:TRANSACTION_NAME" require.Equal(t, expected, actual) }) t.Run("Includes arguments in proposal", func(t *testing.T) { - var args [][]byte - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - args = test.AssertUnmarshalInvocationSpec(t, in.ProposedTransaction).ChaincodeSpec.Input.Args - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) expected := []string{"one", "two", "three"} _, err := contract.SubmitTransaction("transaction", expected...) require.NoError(t, err) + args := AssertUnmarshalInvocationSpec(t, (<-requests).ProposedTransaction).ChaincodeSpec.Input.Args actual := bytesAsStrings(args[1:]) require.EqualValues(t, expected, actual) }) - t.Run("Includes channel name in proposed transaction", func(t *testing.T) { - var actual string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - actual = in.ChannelId - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + t.Run("Includes channel name in endorse request", func(t *testing.T) { + expected := "CHANNEL_NAME" + + mockConnection := NewMockClientConnInterface(t) + network := AssertNewTestNetwork(t, expected, WithClientConnection(mockConnection)) + + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := network.GetContract("chaincode") _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) - expected := contract.channelName + actual := (<-requests).ChannelId require.Equal(t, expected, actual) }) - t.Run("Includes transaction ID in proposed transaction", func(t *testing.T) { - var actual string - - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - actual = test.AssertUnmarshalChannelheader(t, in.ProposedTransaction).TxId - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) + t.Run("Includes transaction ID in proposal", func(t *testing.T) { + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") _, err = proposal.Endorse() require.NoError(t, err, "Endorse") + actual := AssertUnmarshalChannelheader(t, (<-requests).ProposedTransaction).TxId require.Equal(t, proposal.TransactionID(), actual) }) t.Run("Includes transaction ID in endorse request", func(t *testing.T) { - var actual string + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - actual = in.TransactionId - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") _, err = proposal.Endorse() require.NoError(t, err, "Endorse") + actual := (<-requests).TransactionId require.Equal(t, proposal.TransactionID(), actual) }) t.Run("Includes channel name in commit status request", func(t *testing.T) { - var actual string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedCommitStatusRequest, _ ...grpc.CallOption) { - request := &gateway.CommitStatusRequest{} - test.AssertUnmarshal(t, in.Request, request) - actual = request.ChannelId - }). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil). - Times(1) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + expected := "CHANNEL_NAME" + + mockConnection := NewMockClientConnInterface(t) + network := AssertNewTestNetwork(t, expected, WithClientConnection(mockConnection)) + endorseResponse := AssertNewEndorseResponse(t, "TRANSACTION_RESULT", expected) + ExpectEndorse(mockConnection, WithEndorseResponse(endorseResponse)) + ExpectSubmit(mockConnection) + requests := make(chan *gateway.SignedCommitStatusRequest, 1) + ExpectCommitStatus(mockConnection, CaptureInvokeRequest(requests), WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := network.GetContract("chaincode") _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) - expected := contract.channelName + request := &gateway.CommitStatusRequest{} + AssertUnmarshal(t, (<-requests).Request, request) + actual := request.ChannelId require.Equal(t, expected, actual) }) t.Run("Includes transaction ID in commit status request", func(t *testing.T) { - var actual string - var expected string - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - expected = test.AssertUnmarshalChannelheader(t, in.ProposedTransaction).TxId - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedCommitStatusRequest, _ ...grpc.CallOption) { - request := &gateway.CommitStatusRequest{} - test.AssertUnmarshal(t, in.Request, request) - actual = request.TransactionId - }). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil). - Times(1) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + endorseRequests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(endorseRequests), WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + commitStatusRequests := make(chan *gateway.SignedCommitStatusRequest, 1) + ExpectCommitStatus(mockConnection, CaptureInvokeRequest(commitStatusRequests), WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) + expected := AssertUnmarshalChannelheader(t, (<-endorseRequests).ProposedTransaction).TxId + request := &gateway.CommitStatusRequest{} + AssertUnmarshal(t, (<-commitStatusRequests).Request, request) + actual := request.TransactionId require.Equal(t, expected, actual) }) t.Run("Uses signer for endorse", func(t *testing.T) { - var actual []byte expected := []byte("MY_SIGNATURE") + + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + sign := func(digest []byte) ([]byte, error) { return expected, nil } - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - actual = in.ProposedTransaction.Signature - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithSign(sign)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithSign(sign)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) + actual := (<-requests).ProposedTransaction.Signature require.EqualValues(t, expected, actual) }) t.Run("Uses signer for submit", func(t *testing.T) { - var actual []byte expected := []byte("MY_SIGNATURE") + + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + requests := make(chan *gateway.SubmitRequest, 1) + ExpectSubmit(mockConnection, CaptureInvokeRequest(requests)) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + sign := func(digest []byte) ([]byte, error) { return expected, nil } - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SubmitRequest, _ ...grpc.CallOption) { - actual = in.PreparedTransaction.Signature - }). - Return(nil, nil). - Times(1) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithSign(sign)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithSign(sign)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) + actual := (<-requests).PreparedTransaction.Signature require.EqualValues(t, expected, actual) }) t.Run("Sends private data with submit", func(t *testing.T) { - var actualOrgs []string - expectedOrgs := []string{"MY_ORG"} - var actualPrice []byte + expectedOrg := "MY_ORG" expectedPrice := []byte("3000") - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.EndorseRequest, _ ...grpc.CallOption) { - actualOrgs = in.EndorsingOrganizations - transient := test.AssertUnmarshalProposalPayload(t, in.ProposedTransaction).TransientMap - actualPrice = transient["price"] - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + + mockConnection := NewMockClientConnInterface(t) + requests := make(chan *gateway.EndorseRequest, 1) + ExpectEndorse(mockConnection, CaptureInvokeRequest(requests), WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) privateData := map[string][]byte{ - "price": []byte("3000"), + "price": expectedPrice, } - - _, err := contract.Submit("transaction", WithTransient(privateData), WithEndorsingOrganizations("MY_ORG")) + _, err := contract.Submit("transaction", WithTransient(privateData), WithEndorsingOrganizations(expectedOrg)) require.NoError(t, err) - require.EqualValues(t, expectedOrgs, actualOrgs) - require.EqualValues(t, expectedPrice, actualPrice) + request := <-requests + require.ElementsMatch(t, []string{expectedOrg}, request.EndorsingOrganizations) + + transient := AssertUnmarshalProposalPayload(t, request.ProposedTransaction).TransientMap + require.EqualValues(t, expectedPrice, transient["price"]) }) t.Run("Uses signer for commit status", func(t *testing.T) { - var actual []byte expected := []byte("MY_SIGNATURE") + + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + requests := make(chan *gateway.SignedCommitStatusRequest, 1) + ExpectCommitStatus(mockConnection, CaptureInvokeRequest(requests), WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) + sign := func(digest []byte) ([]byte, error) { return expected, nil } - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Do(func(_ context.Context, in *gateway.SignedCommitStatusRequest, _ ...grpc.CallOption) { - actual = in.Signature - }). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil). - Times(1) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithSign(sign)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithSign(sign)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) + actual := (<-requests).Signature require.EqualValues(t, expected, actual) }) t.Run("Uses hash", func(t *testing.T) { - var actual [][]byte + digests := make(chan []byte, 3) digest := []byte("MY_DIGEST") sign := func(digest []byte) ([]byte, error) { - actual = append(actual, digest) + digests <- digest return digest, nil } hash := func(message []byte) []byte { return digest } - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithSign(sign), WithHash(hash)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithSign(sign), WithHash(hash)) _, err := contract.SubmitTransaction("transaction") require.NoError(t, err) expected := [][]byte{digest, digest, digest} + actual := ReceiveAll(digests) require.EqualValues(t, expected, actual) }) t.Run("Commit returns transaction status", func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_MVCC_READ_CONFLICT, 1), nil) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_MVCC_READ_CONFLICT, 1)) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, commit, err := contract.SubmitAsync("transaction") require.NoError(t, err) @@ -595,15 +482,12 @@ func TestSubmitTransaction(t *testing.T) { }) t.Run("Commit returns successful for successful transaction", func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_VALID, 1)) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, commit, err := contract.SubmitAsync("transaction") require.NoError(t, err, "submit") @@ -615,15 +499,12 @@ func TestSubmitTransaction(t *testing.T) { }) t.Run("Commit returns unsuccessful for failed transaction", func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_MVCC_READ_CONFLICT, 1), nil) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_MVCC_READ_CONFLICT, 1)) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, commit, err := contract.SubmitAsync("transaction") require.NoError(t, err, "submit") @@ -636,15 +517,13 @@ func TestSubmitTransaction(t *testing.T) { t.Run("Commit returns block number", func(t *testing.T) { expectedBlockNumber := uint64(101) - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_MVCC_READ_CONFLICT, expectedBlockNumber), nil) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithCommitStatusResponse(peer.TxValidationCode_MVCC_READ_CONFLICT, expectedBlockNumber)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) _, commit, err := contract.SubmitAsync("transaction") require.NoError(t, err, "submit") @@ -656,24 +535,10 @@ func TestSubmitTransaction(t *testing.T) { }) t.Run("Uses default context for endorse", func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, _ *gateway.EndorseRequest, _ ...grpc.CallOption) (*gateway.EndorseResponse, error) { - select { - case <-time.After(1 * time.Second): - return AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil - case <-ctx.Done(): // Zero timeout context should cancel immediately, selecting this case - return nil, ctx.Err() - } - }) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil). - AnyTimes() - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil). - AnyTimes() - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithEndorseTimeout(0)) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithInvokeContextErr(), WithEndorseResponse(defaultEndorseResponse)) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithEndorseTimeout(0)) _, err := contract.Submit("transaction") @@ -681,23 +546,11 @@ func TestSubmitTransaction(t *testing.T) { }) t.Run("Uses default context for submit", func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, _ *gateway.SubmitRequest, _ ...grpc.CallOption) (*gateway.SubmitResponse, error) { - select { - case <-time.After(1 * time.Second): - return nil, nil - case <-ctx.Done(): // Zero timeout context should cancel immediately, selecting this case - return nil, ctx.Err() - } - }) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil). - AnyTimes() - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithSubmitTimeout(0)) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection, WithInvokeContextErr()) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithSubmitTimeout(0)) _, err := contract.Submit("transaction") @@ -724,61 +577,41 @@ func TestSubmitTransaction(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - var endorseCtxErr error - var submitCtxErr error - var commitStatusCtxErr error + endorseContexts := make(chan context.Context, 1) + submitContexts := make(chan context.Context, 1) + commitStatusContexts := make(chan context.Context, 1) ctx, cancel := context.WithCancel(context.Background()) cancel() - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Do(func(ctx context.Context, _ *gateway.EndorseRequest, _ ...grpc.CallOption) { - endorseCtxErr = ctx.Err() - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Do(func(ctx context.Context, _ *gateway.SubmitRequest, _ ...grpc.CallOption) { - submitCtxErr = ctx.Err() - }). - Return(nil, nil). - Times(1) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Do(func(ctx context.Context, _ *gateway.SignedCommitStatusRequest, _ ...grpc.CallOption) { - commitStatusCtxErr = ctx.Err() - }). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil). - Times(1) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, + CaptureInvokeContext(endorseContexts), + WithEndorseResponse(defaultEndorseResponse), + ) + ExpectSubmit(mockConnection, CaptureInvokeContext(submitContexts)) + ExpectCommitStatus(mockConnection, + CaptureInvokeContext(commitStatusContexts), + WithCommitStatusResponse(peer.TxValidationCode_VALID, 1), + ) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) testCase.run(t, ctx, contract) - require.ErrorIs(t, endorseCtxErr, context.Canceled, "endorse context") - require.ErrorIs(t, submitCtxErr, context.Canceled, "submit context") - require.ErrorIs(t, commitStatusCtxErr, context.Canceled, "commit status context") + require.ErrorIs(t, (<-endorseContexts).Err(), context.Canceled, "endorse context") + require.ErrorIs(t, (<-submitContexts).Err(), context.Canceled, "submit context") + require.ErrorIs(t, (<-commitStatusContexts).Err(), context.Canceled, "commit status context") }) } t.Run("Uses default context for commit status", func(t *testing.T) { - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil). - DoAndReturn(func(ctx context.Context, _ *gateway.SignedCommitStatusRequest, _ ...grpc.CallOption) (*gateway.CommitStatusResponse, error) { - select { - case <-time.After(1 * time.Second): - return nil, nil - case <-ctx.Done(): // Zero timeout context should cancel immediately, selecting this case - return nil, ctx.Err() - } - }) - - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient), WithCommitStatusTimeout(0)) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, WithInvokeContextErr()) + + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection), WithCommitStatusTimeout(0)) _, err := contract.Submit("transaction") @@ -786,18 +619,16 @@ func TestSubmitTransaction(t *testing.T) { }) t.Run("Endorse uses specified gRPC call options", func(t *testing.T) { - var actual []grpc.CallOption + callOptions := make(chan []grpc.CallOption, 1) expected := grpc.WaitForReady(true) - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any(), gomock.Any()). - Do(func(_ context.Context, _ *gateway.EndorseRequest, opts ...grpc.CallOption) { - actual = opts - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, + CaptureInvokeOptions(callOptions), + WithEndorseResponse(defaultEndorseResponse), + ) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") @@ -805,22 +636,20 @@ func TestSubmitTransaction(t *testing.T) { _, err = proposal.Endorse(expected) require.NoError(t, err, "Endorse") - require.Contains(t, actual, expected, "CallOptions") + require.Contains(t, <-callOptions, expected, "CallOptions") }) t.Run("Endorse uses specified gRPC call options with specified context", func(t *testing.T) { - var actual []grpc.CallOption + callOptions := make(chan []grpc.CallOption, 1) expected := grpc.WaitForReady(true) - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any(), gomock.Any()). - Do(func(_ context.Context, _ *gateway.EndorseRequest, opts ...grpc.CallOption) { - actual = opts - }). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil). - Times(1) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, + CaptureInvokeOptions(callOptions), + WithEndorseResponse(defaultEndorseResponse), + ) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") @@ -831,24 +660,18 @@ func TestSubmitTransaction(t *testing.T) { _, err = proposal.EndorseWithContext(ctx, expected) require.NoError(t, err, "Endorse") - require.Contains(t, actual, expected, "CallOptions") + require.Contains(t, <-callOptions, expected, "CallOptions") }) t.Run("Submit uses specified gRPC call options", func(t *testing.T) { - var actual []grpc.CallOption + callOptions := make(chan []grpc.CallOption, 1) expected := grpc.WaitForReady(true) - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any(), gomock.Any()). - Do(func(_ context.Context, _ *gateway.SubmitRequest, opts ...grpc.CallOption) { - actual = opts - }). - Return(nil, nil). - Times(1) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection, CaptureInvokeOptions(callOptions)) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") @@ -859,24 +682,18 @@ func TestSubmitTransaction(t *testing.T) { _, err = transaction.Submit(expected) require.NoError(t, err, "Submit") - require.Contains(t, actual, expected, "CallOptions") + require.Contains(t, <-callOptions, expected, "CallOptions") }) t.Run("Submit uses specified gRPC call options with specified context", func(t *testing.T) { - var actual []grpc.CallOption + callOptions := make(chan []grpc.CallOption, 1) expected := grpc.WaitForReady(true) - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any(), gomock.Any()). - Do(func(_ context.Context, _ *gateway.SubmitRequest, opts ...grpc.CallOption) { - actual = opts - }). - Return(nil, nil). - Times(1) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection, CaptureInvokeOptions(callOptions)) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") @@ -890,26 +707,22 @@ func TestSubmitTransaction(t *testing.T) { _, err = transaction.SubmitWithContext(ctx, expected) require.NoError(t, err, "Submit") - require.Contains(t, actual, expected, "CallOptions") + require.Contains(t, <-callOptions, expected, "CallOptions") }) t.Run("CommisStatus uses specified gRPC call options", func(t *testing.T) { - var actual []grpc.CallOption + callOptions := make(chan []grpc.CallOption, 1) expected := grpc.WaitForReady(true) - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil). - Do(func(ctx context.Context, _ *gateway.SignedCommitStatusRequest, opts ...grpc.CallOption) { - actual = opts - }). - Times(1) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, + CaptureInvokeOptions(callOptions), + WithCommitStatusResponse(peer.TxValidationCode_VALID, 1), + ) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") @@ -923,26 +736,22 @@ func TestSubmitTransaction(t *testing.T) { _, err = commit.Status(expected) require.NoError(t, err, "Status") - require.Contains(t, actual, expected, "CallOptions") + require.Contains(t, <-callOptions, expected, "CallOptions") }) t.Run("CommisStatus uses specified gRPC call options with specified context", func(t *testing.T) { - var actual []grpc.CallOption + callOptions := make(chan []grpc.CallOption, 1) expected := grpc.WaitForReady(true) - mockClient := NewMockGatewayClient(gomock.NewController(t)) - mockClient.EXPECT().Endorse(gomock.Any(), gomock.Any(), gomock.Any()). - Return(AssertNewEndorseResponse(t, "TRANSACTION_RESULT", "network"), nil) - mockClient.EXPECT().Submit(gomock.Any(), gomock.Any(), gomock.Any()). - Return(nil, nil) - mockClient.EXPECT().CommitStatus(gomock.Any(), gomock.Any(), gomock.Any()). - Return(newCommitStatusResponse(peer.TxValidationCode_VALID, 1), nil). - Do(func(ctx context.Context, _ *gateway.SignedCommitStatusRequest, opts ...grpc.CallOption) { - actual = opts - }). - Times(1) + mockConnection := NewMockClientConnInterface(t) + ExpectEndorse(mockConnection, WithEndorseResponse(defaultEndorseResponse)) + ExpectSubmit(mockConnection) + ExpectCommitStatus(mockConnection, + CaptureInvokeOptions(callOptions), + WithCommitStatusResponse(peer.TxValidationCode_VALID, 1), + ) - contract := AssertNewTestContract(t, "chaincode", WithGatewayClient(mockClient)) + contract := AssertNewTestContract(t, "chaincode", WithClientConnection(mockConnection)) proposal, err := contract.NewProposal("transaction") require.NoError(t, err, "NewProposal") @@ -959,6 +768,6 @@ func TestSubmitTransaction(t *testing.T) { _, err = commit.StatusWithContext(ctx, expected) require.NoError(t, err, "Status") - require.Contains(t, actual, expected, "CallOptions") + require.Contains(t, <-callOptions, expected, "CallOptions") }) } diff --git a/pkg/internal/test/credentials.go b/pkg/internal/test/credentials.go index 5813af844..252ec6f48 100644 --- a/pkg/internal/test/credentials.go +++ b/pkg/internal/test/credentials.go @@ -9,7 +9,6 @@ import ( "crypto/ed25519" "crypto/elliptic" "crypto/rand" - "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "fmt" @@ -27,19 +26,6 @@ func NewEd25519KeyPair() (ed25519.PublicKey, ed25519.PrivateKey, error) { return ed25519.GenerateKey(rand.Reader) } -func publicKey(priv crypto.PrivateKey) crypto.PublicKey { - switch k := priv.(type) { - case *rsa.PrivateKey: - return &k.PublicKey - case *ecdsa.PrivateKey: - return &k.PublicKey - case ed25519.PrivateKey: - return k.Public().(ed25519.PublicKey) - default: - return nil - } -} - // NewCertificate generates a new certificate from a private key for testing func NewCertificate(privateKey crypto.PrivateKey) (*x509.Certificate, error) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) @@ -66,7 +52,8 @@ func NewCertificate(privateKey crypto.PrivateKey) (*x509.Certificate, error) { DNSNames: []string{"test.example.org"}, } - certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(privateKey), privateKey) + publicKey := privateKey.(crypto.Signer).Public() + certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey) if err != nil { return nil, fmt.Errorf("failed to generate certificate: %w", err) } diff --git a/pkg/internal/test/transaction.go b/pkg/internal/test/transaction.go deleted file mode 100644 index 965f97784..000000000 --- a/pkg/internal/test/transaction.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright IBM Corp. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -package test - -import ( - "testing" - - "github.com/hyperledger/fabric-protos-go-apiv2/common" - "github.com/hyperledger/fabric-protos-go-apiv2/peer" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" -) - -// AssertProtoEqual ensures an expected protobuf message matches an actual message -func AssertProtoEqual(t *testing.T, expected protoreflect.ProtoMessage, actual protoreflect.ProtoMessage) { - require.True(t, proto.Equal(expected, actual), "Expected %v, got %v", expected, actual) -} - -// AssertUnmarshal ensures that a protobuf is umarshaled without error -func AssertUnmarshal(t *testing.T, b []byte, m protoreflect.ProtoMessage) { - err := proto.Unmarshal(b, m) - require.NoError(t, err) -} - -// AssertUnmarshalProposalPayload ensures that a ChaincodeProposalPayload protobuf is umarshalled without error -func AssertUnmarshalProposalPayload(t *testing.T, proposedTransaction *peer.SignedProposal) *peer.ChaincodeProposalPayload { - proposal := &peer.Proposal{} - AssertUnmarshal(t, proposedTransaction.ProposalBytes, proposal) - - payload := &peer.ChaincodeProposalPayload{} - AssertUnmarshal(t, proposal.Payload, payload) - - return payload -} - -// AssertUnmarshalInvocationSpec ensures that a ChaincodeInvocationSpec protobuf is umarshalled without error -func AssertUnmarshalInvocationSpec(t *testing.T, proposedTransaction *peer.SignedProposal) *peer.ChaincodeInvocationSpec { - proposal := &peer.Proposal{} - AssertUnmarshal(t, proposedTransaction.ProposalBytes, proposal) - - payload := &peer.ChaincodeProposalPayload{} - AssertUnmarshal(t, proposal.Payload, payload) - - input := &peer.ChaincodeInvocationSpec{} - AssertUnmarshal(t, payload.Input, input) - - return input -} - -// AssertUnmarshalChannelheader ensures that a ChannelHeader protobuf is umarshalled without error -func AssertUnmarshalChannelheader(t *testing.T, proposedTransaction *peer.SignedProposal) *common.ChannelHeader { - header := AssertUnmarshalHeader(t, proposedTransaction) - - channelHeader := &common.ChannelHeader{} - AssertUnmarshal(t, header.ChannelHeader, channelHeader) - - return channelHeader -} - -// AssertUnmarshalHeader ensures that a Header protobuf is umarshalled without error -func AssertUnmarshalHeader(t *testing.T, proposedTransaction *peer.SignedProposal) *common.Header { - proposal := &peer.Proposal{} - AssertUnmarshal(t, proposedTransaction.ProposalBytes, proposal) - - header := &common.Header{} - AssertUnmarshal(t, proposal.Header, header) - - return header -} - -// AssertUnmarshalSignatureHeader ensures that a SignatureHeader protobuf is umarshalled without error -func AssertUnmarshalSignatureHeader(t *testing.T, proposedTransaction *peer.SignedProposal) *common.SignatureHeader { - header := AssertUnmarshalHeader(t, proposedTransaction) - - signatureHeader := &common.SignatureHeader{} - AssertUnmarshal(t, header.SignatureHeader, signatureHeader) - - return signatureHeader -} diff --git a/scenario/go/scenario_test.go b/scenario/go/scenario_test.go index dfc5bacbc..7f98b3234 100644 --- a/scenario/go/scenario_test.go +++ b/scenario/go/scenario_test.go @@ -320,7 +320,7 @@ func theResponseShouldBeJSONMatching(arg *godog.DocString) error { } func jsonEqual(a, b []byte) (bool, error) { - var j, j2 interface{} + var j, j2 any if err := json.Unmarshal(a, &j); err != nil { return false, err }