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 }