github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/internal/pkg/gateway/commit/notifier_test.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package commit
     8  
     9  import (
    10  	"testing"
    11  
    12  	"github.com/golang/protobuf/proto"
    13  	"github.com/hechain20/hechain/core/ledger"
    14  	"github.com/hechain20/hechain/internal/pkg/gateway/commit/mocks"
    15  	"github.com/hyperledger/fabric-protos-go/peer"
    16  	"github.com/pkg/errors"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  //go:generate counterfeiter -o mocks/notificationsupplier.go --fake-name NotificationSupplier . notificationSupplier
    21  type notificationSupplier interface { // Mimic NotificationSupplier to avoid circular import with generated mock
    22  	NotificationSupplier
    23  }
    24  
    25  func newNotificationSupplier(commitSends ...<-chan *ledger.CommitNotification) *mocks.NotificationSupplier {
    26  	supplier := &mocks.NotificationSupplier{}
    27  	for i, commitSend := range commitSends {
    28  		supplier.CommitNotificationsReturnsOnCall(i, commitSend, nil)
    29  	}
    30  	return supplier
    31  }
    32  
    33  func newTestNotifier(commitSends ...<-chan *ledger.CommitNotification) *Notifier {
    34  	supplier := newNotificationSupplier(commitSends...)
    35  	return NewNotifier(supplier)
    36  }
    37  
    38  func assertMarshallProto(t *testing.T, message proto.Message) []byte {
    39  	result, err := proto.Marshal(message)
    40  	require.NoError(t, err)
    41  	return result
    42  }
    43  
    44  func assertEqualChaincodeEvents(t *testing.T, expected []*peer.ChaincodeEvent, actual []*peer.ChaincodeEvent) {
    45  	require.Equal(t, len(expected), len(actual), "number of events")
    46  	for i, event := range actual {
    47  		require.Truef(t, proto.Equal(expected[i], event), "expected %v, got %v", expected, actual)
    48  	}
    49  }
    50  
    51  func newTestChaincodeEvent(chaincodeName string) *peer.ChaincodeEvent {
    52  	return &peer.ChaincodeEvent{
    53  		ChaincodeId: chaincodeName,
    54  		EventName:   "EVENT_NAME",
    55  		TxId:        "TX_ID",
    56  		Payload:     []byte("PAYLOAD"),
    57  	}
    58  }
    59  
    60  func TestNotifier(t *testing.T) {
    61  	t.Run("notifyStatus", func(t *testing.T) {
    62  		t.Run("returns error from notification supplier", func(t *testing.T) {
    63  			supplier := &mocks.NotificationSupplier{}
    64  			supplier.CommitNotificationsReturns(nil, errors.New("MY_ERROR"))
    65  			notifier := NewNotifier(supplier)
    66  			defer notifier.close()
    67  
    68  			_, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
    69  
    70  			require.ErrorContains(t, err, "MY_ERROR")
    71  		})
    72  
    73  		t.Run("delivers notification for matching transaction", func(t *testing.T) {
    74  			commitSend := make(chan *ledger.CommitNotification, 1)
    75  			notifier := newTestNotifier(commitSend)
    76  			defer notifier.close()
    77  
    78  			commitReceive, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
    79  			require.NoError(t, err)
    80  
    81  			commitSend <- &ledger.CommitNotification{
    82  				BlockNumber: 1,
    83  				TxsInfo: []*ledger.CommitNotificationTxInfo{
    84  					{
    85  						TxID:           "TX_ID",
    86  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
    87  					},
    88  				},
    89  			}
    90  			actual := <-commitReceive
    91  
    92  			expected := &Status{
    93  				BlockNumber:   1,
    94  				TransactionID: "TX_ID",
    95  				Code:          peer.TxValidationCode_MVCC_READ_CONFLICT,
    96  			}
    97  			require.EqualValues(t, expected, actual)
    98  		})
    99  
   100  		t.Run("ignores non-matching transaction in same block", func(t *testing.T) {
   101  			commitSend := make(chan *ledger.CommitNotification, 1)
   102  			notifier := newTestNotifier(commitSend)
   103  			defer notifier.close()
   104  
   105  			commitReceive, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   106  			require.NoError(t, err)
   107  
   108  			commitSend <- &ledger.CommitNotification{
   109  				BlockNumber: 1,
   110  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   111  					{
   112  						TxID:           "WRONG_TX_ID",
   113  						ValidationCode: peer.TxValidationCode_VALID,
   114  					},
   115  					{
   116  						TxID:           "TX_ID",
   117  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
   118  					},
   119  				},
   120  			}
   121  			actual := <-commitReceive
   122  
   123  			expected := &Status{
   124  				BlockNumber:   1,
   125  				TransactionID: "TX_ID",
   126  				Code:          peer.TxValidationCode_MVCC_READ_CONFLICT,
   127  			}
   128  			require.EqualValues(t, expected, actual)
   129  		})
   130  
   131  		t.Run("ignores blocks without matching transaction", func(t *testing.T) {
   132  			commitSend := make(chan *ledger.CommitNotification, 2)
   133  			notifier := newTestNotifier(commitSend)
   134  			defer notifier.close()
   135  
   136  			commitReceive, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   137  			require.NoError(t, err)
   138  
   139  			commitSend <- &ledger.CommitNotification{
   140  				BlockNumber: 1,
   141  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   142  					{
   143  						TxID:           "WRONG_TX_ID",
   144  						ValidationCode: peer.TxValidationCode_VALID,
   145  					},
   146  				},
   147  			}
   148  			commitSend <- &ledger.CommitNotification{
   149  				BlockNumber: 2,
   150  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   151  					{
   152  						TxID:           "TX_ID",
   153  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
   154  					},
   155  				},
   156  			}
   157  			actual := <-commitReceive
   158  
   159  			expected := &Status{
   160  				BlockNumber:   2,
   161  				TransactionID: "TX_ID",
   162  				Code:          peer.TxValidationCode_MVCC_READ_CONFLICT,
   163  			}
   164  			require.EqualValues(t, expected, actual)
   165  		})
   166  
   167  		t.Run("processes blocks in order", func(t *testing.T) {
   168  			commitSend := make(chan *ledger.CommitNotification, 2)
   169  			notifier := newTestNotifier(commitSend)
   170  			defer notifier.close()
   171  
   172  			commitReceive, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   173  			require.NoError(t, err)
   174  
   175  			commitSend <- &ledger.CommitNotification{
   176  				BlockNumber: 1,
   177  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   178  					{
   179  						TxID:           "TX_ID",
   180  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
   181  					},
   182  				},
   183  			}
   184  			commitSend <- &ledger.CommitNotification{
   185  				BlockNumber: 2,
   186  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   187  					{
   188  						TxID:           "TX_ID",
   189  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
   190  					},
   191  				},
   192  			}
   193  			actual := <-commitReceive
   194  
   195  			expected := &Status{
   196  				BlockNumber:   1,
   197  				TransactionID: "TX_ID",
   198  				Code:          peer.TxValidationCode_MVCC_READ_CONFLICT,
   199  			}
   200  			require.EqualValues(t, expected, actual)
   201  		})
   202  
   203  		t.Run("closes channel after notification", func(t *testing.T) {
   204  			commitSend := make(chan *ledger.CommitNotification, 2)
   205  			notifier := newTestNotifier(commitSend)
   206  			defer notifier.close()
   207  
   208  			commitReceive, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   209  			require.NoError(t, err)
   210  
   211  			commitSend <- &ledger.CommitNotification{
   212  				BlockNumber: 1,
   213  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   214  					{
   215  						TxID:           "TX_ID",
   216  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
   217  					},
   218  				},
   219  			}
   220  			commitSend <- &ledger.CommitNotification{
   221  				BlockNumber: 2,
   222  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   223  					{
   224  						TxID:           "TX_ID",
   225  						ValidationCode: peer.TxValidationCode_VALID,
   226  					},
   227  				},
   228  			}
   229  			<-commitReceive
   230  			_, ok := <-commitReceive
   231  
   232  			require.False(t, ok, "Expected notification channel to be closed but receive was successful")
   233  		})
   234  
   235  		t.Run("stops notification when done channel closed", func(t *testing.T) {
   236  			commitSend := make(chan *ledger.CommitNotification, 1)
   237  			notifier := newTestNotifier(commitSend)
   238  			defer notifier.close()
   239  
   240  			done := make(chan struct{})
   241  			commitReceive, err := notifier.notifyStatus(done, "CHANNEL_NAME", "TX_ID")
   242  			require.NoError(t, err)
   243  
   244  			close(done)
   245  			commitSend <- &ledger.CommitNotification{
   246  				BlockNumber: 1,
   247  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   248  					{
   249  						TxID:           "TX_ID",
   250  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
   251  					},
   252  				},
   253  			}
   254  			_, ok := <-commitReceive
   255  
   256  			require.False(t, ok, "Expected notification channel to be closed but receive was successful")
   257  		})
   258  
   259  		t.Run("multiple listeners receive notifications", func(t *testing.T) {
   260  			commitSend := make(chan *ledger.CommitNotification, 1)
   261  			notifier := newTestNotifier(commitSend)
   262  			defer notifier.close()
   263  
   264  			commitReceive1, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   265  			require.NoError(t, err)
   266  
   267  			commitReceive2, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   268  			require.NoError(t, err)
   269  
   270  			commitSend <- &ledger.CommitNotification{
   271  				BlockNumber: 1,
   272  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   273  					{
   274  						TxID:           "TX_ID",
   275  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
   276  					},
   277  				},
   278  			}
   279  			actual1 := <-commitReceive1
   280  			actual2 := <-commitReceive2
   281  
   282  			expected := &Status{
   283  				BlockNumber:   1,
   284  				TransactionID: "TX_ID",
   285  				Code:          peer.TxValidationCode_MVCC_READ_CONFLICT,
   286  			}
   287  			require.EqualValues(t, expected, actual1)
   288  			require.EqualValues(t, expected, actual2)
   289  		})
   290  
   291  		t.Run("multiple listeners can stop listening independently", func(t *testing.T) {
   292  			commitSend := make(chan *ledger.CommitNotification, 1)
   293  			notifier := newTestNotifier(commitSend)
   294  			defer notifier.close()
   295  
   296  			done := make(chan struct{})
   297  			commitReceive1, err := notifier.notifyStatus(done, "CHANNEL_NAME", "TX_ID")
   298  			require.NoError(t, err)
   299  
   300  			commitReceive2, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   301  			require.NoError(t, err)
   302  
   303  			close(done)
   304  			commitSend <- &ledger.CommitNotification{
   305  				BlockNumber: 1,
   306  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   307  					{
   308  						TxID:           "TX_ID",
   309  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
   310  					},
   311  				},
   312  			}
   313  			_, ok1 := <-commitReceive1
   314  			_, ok2 := <-commitReceive2
   315  
   316  			require.False(t, ok1, "Expected notification channel to be closed but receive was successful")
   317  			require.True(t, ok2, "Expected notification channel to deliver a result but was closed")
   318  		})
   319  
   320  		t.Run("passes open done channel to notification supplier", func(t *testing.T) {
   321  			supplier := &mocks.NotificationSupplier{}
   322  			supplier.CommitNotificationsReturns(nil, nil)
   323  
   324  			notifier := NewNotifier(supplier)
   325  			defer notifier.close()
   326  
   327  			_, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   328  			require.NoError(t, err)
   329  
   330  			require.Equal(t, 1, supplier.CommitNotificationsCallCount())
   331  
   332  			done, _ := supplier.CommitNotificationsArgsForCall(0)
   333  			select {
   334  			case <-done:
   335  				require.FailNow(t, "Expected done channel to be open but was closed")
   336  			default:
   337  			}
   338  		})
   339  
   340  		t.Run("passes channel name to notification supplier", func(t *testing.T) {
   341  			supplier := &mocks.NotificationSupplier{}
   342  			supplier.CommitNotificationsReturns(nil, nil)
   343  
   344  			notifier := NewNotifier(supplier)
   345  			defer notifier.close()
   346  
   347  			_, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   348  			require.NoError(t, err)
   349  
   350  			require.Equal(t, 1, supplier.CommitNotificationsCallCount())
   351  
   352  			_, actual := supplier.CommitNotificationsArgsForCall(0)
   353  			require.Equal(t, "CHANNEL_NAME", actual)
   354  		})
   355  
   356  		t.Run("stops notification if supplier stops", func(t *testing.T) {
   357  			commitSend := make(chan *ledger.CommitNotification, 1)
   358  			notifier := newTestNotifier(commitSend)
   359  			defer notifier.close()
   360  
   361  			commitReceive, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   362  			require.NoError(t, err)
   363  
   364  			close(commitSend)
   365  			_, ok := <-commitReceive
   366  
   367  			require.False(t, ok, "Expected notification channel to be closed but receive was successful")
   368  		})
   369  
   370  		t.Run("can attach new listener after supplier stops", func(t *testing.T) {
   371  			commitSend1 := make(chan *ledger.CommitNotification, 1)
   372  			commitSend2 := make(chan *ledger.CommitNotification, 1)
   373  
   374  			notifier := newTestNotifier(commitSend1, commitSend2)
   375  			defer notifier.close()
   376  
   377  			commitReceive1, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   378  			require.NoError(t, err)
   379  
   380  			close(commitSend1)
   381  			_, ok := <-commitReceive1
   382  			require.False(t, ok, "Expected notification channel to be closed but receive was successful")
   383  
   384  			commitReceive2, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   385  			require.NoError(t, err)
   386  
   387  			commitSend2 <- &ledger.CommitNotification{
   388  				BlockNumber: 1,
   389  				TxsInfo: []*ledger.CommitNotificationTxInfo{
   390  					{
   391  						TxID:           "TX_ID",
   392  						ValidationCode: peer.TxValidationCode_MVCC_READ_CONFLICT,
   393  					},
   394  				},
   395  			}
   396  
   397  			actual, ok := <-commitReceive2
   398  			require.True(t, ok, "Expected notification channel to deliver a result but was closed")
   399  
   400  			expected := &Status{
   401  				BlockNumber:   1,
   402  				TransactionID: "TX_ID",
   403  				Code:          peer.TxValidationCode_MVCC_READ_CONFLICT,
   404  			}
   405  			require.EqualValues(t, expected, actual)
   406  		})
   407  	})
   408  
   409  	t.Run("Close", func(t *testing.T) {
   410  		t.Run("stops all listeners", func(t *testing.T) {
   411  			commitSend := make(chan *ledger.CommitNotification)
   412  			notifier := newTestNotifier(commitSend)
   413  
   414  			commitReceive, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   415  			require.NoError(t, err)
   416  			notifier.close()
   417  
   418  			_, ok := <-commitReceive
   419  
   420  			require.False(t, ok, "Expected notification channel to be closed but receive was successful")
   421  		})
   422  
   423  		t.Run("idempotent", func(t *testing.T) {
   424  			commitSend := make(chan *ledger.CommitNotification)
   425  			notifier := newTestNotifier(commitSend)
   426  			notifier.close()
   427  
   428  			require.NotPanics(t, func() {
   429  				notifier.close()
   430  			})
   431  		})
   432  
   433  		t.Run("stops notification supplier", func(t *testing.T) {
   434  			supplier := &mocks.NotificationSupplier{}
   435  			supplier.CommitNotificationsReturns(nil, nil)
   436  
   437  			notifier := NewNotifier(supplier)
   438  
   439  			_, err := notifier.notifyStatus(nil, "CHANNEL_NAME", "TX_ID")
   440  			require.NoError(t, err)
   441  			notifier.close()
   442  
   443  			require.Equal(t, 1, supplier.CommitNotificationsCallCount())
   444  
   445  			done, _ := supplier.CommitNotificationsArgsForCall(0)
   446  			_, ok := <-done
   447  			require.False(t, ok, "Expected notification supplier done channel to be closed but receive was successful")
   448  		})
   449  	})
   450  }