github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/batch/batch_manager_test.go (about)

     1  // Copyright © 2021 Kaleido, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in comdiliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or imdilied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package batch
    16  
    17  import (
    18  	"context"
    19  	"crypto/sha256"
    20  	"encoding/hex"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/kaleido-io/firefly/internal/config"
    26  	"github.com/kaleido-io/firefly/internal/log"
    27  	"github.com/kaleido-io/firefly/mocks/databasemocks"
    28  	"github.com/kaleido-io/firefly/mocks/datamocks"
    29  	"github.com/kaleido-io/firefly/pkg/database"
    30  	"github.com/kaleido-io/firefly/pkg/fftypes"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/mock"
    33  )
    34  
    35  func TestE2EDispatchBroadcast(t *testing.T) {
    36  	log.SetLevel("debug")
    37  
    38  	mdi := &databasemocks.Plugin{}
    39  	mdm := &datamocks.Manager{}
    40  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, fftypes.SystemNamespace, msgBatchOffsetName).Return(nil, nil).Once()
    41  	mdi.On("UpsertOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
    42  	mdi.On("UpdateOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
    43  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, fftypes.SystemNamespace, msgBatchOffsetName).Return(&fftypes.Offset{
    44  		ID: fftypes.NewUUID(),
    45  	}, nil)
    46  	readyForDispatch := make(chan bool)
    47  	waitForDispatch := make(chan *fftypes.Batch)
    48  	handler := func(ctx context.Context, b *fftypes.Batch, s []*fftypes.Bytes32) error {
    49  		_, ok := <-readyForDispatch
    50  		if !ok {
    51  			return nil
    52  		}
    53  		assert.Len(t, s, 2)
    54  		h := sha256.New()
    55  		nonceBytes, _ := hex.DecodeString(
    56  			"746f70696331",
    57  		/*|  topic1   | */
    58  		) // little endian 12345 in 8 byte hex
    59  		h.Write(nonceBytes)
    60  		assert.Equal(t, hex.EncodeToString(h.Sum([]byte{})), s[0].String())
    61  
    62  		h = sha256.New()
    63  		nonceBytes, _ = hex.DecodeString(
    64  			"746f70696332",
    65  		/*|   topic2  | */
    66  		) // little endian 12345 in 8 byte hex
    67  		h.Write(nonceBytes)
    68  		assert.Equal(t, hex.EncodeToString(h.Sum([]byte{})), s[1].String())
    69  
    70  		waitForDispatch <- b
    71  		return nil
    72  	}
    73  	ctx, cancel := context.WithCancel(context.Background())
    74  	bmi, _ := NewBatchManager(ctx, mdi, mdm)
    75  	bm := bmi.(*batchManager)
    76  
    77  	bm.RegisterDispatcher([]fftypes.MessageType{fftypes.MessageTypeBroadcast}, handler, Options{
    78  		BatchMaxSize:   2,
    79  		BatchTimeout:   0,
    80  		DisposeTimeout: 120 * time.Second,
    81  	})
    82  
    83  	dataID1 := fftypes.NewUUID()
    84  	dataHash := fftypes.NewRandB32()
    85  	msg := &fftypes.Message{
    86  		Header: fftypes.MessageHeader{
    87  			Type:      fftypes.MessageTypeBroadcast,
    88  			ID:        fftypes.NewUUID(),
    89  			Topics:    []string{"topic1", "topic2"},
    90  			Namespace: "ns1",
    91  			Author:    "0x12345",
    92  		},
    93  		Data: fftypes.DataRefs{
    94  			{ID: dataID1, Hash: dataHash},
    95  		},
    96  	}
    97  	data := &fftypes.Data{
    98  		ID:   dataID1,
    99  		Hash: dataHash,
   100  	}
   101  	mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return([]*fftypes.Data{data}, true, nil)
   102  	mdi.On("GetMessages", mock.Anything, mock.Anything).Return([]*fftypes.Message{msg}, nil).Once()
   103  	mdi.On("GetMessages", mock.Anything, mock.Anything).Return([]*fftypes.Message{}, nil)
   104  	mdi.On("UpsertBatch", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   105  	mdi.On("UpdateBatch", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   106  	rag := mdi.On("RunAsGroup", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   107  	rag.RunFn = func(a mock.Arguments) {
   108  		ctx := a.Get(0).(context.Context)
   109  		fn := a.Get(1).(func(context.Context) error)
   110  		fn(ctx)
   111  	}
   112  	mdi.On("UpdateMessages", mock.Anything, mock.MatchedBy(func(f database.Filter) bool {
   113  		fi, err := f.Finalize()
   114  		assert.NoError(t, err)
   115  		assert.Equal(t, fmt.Sprintf("id IN ['%s']", msg.Header.ID.String()), fi.String())
   116  		return true
   117  	}), mock.Anything).Return(nil)
   118  
   119  	err := bm.Start()
   120  	assert.NoError(t, err)
   121  
   122  	bm.NewMessages() <- msg.Sequence
   123  
   124  	readyForDispatch <- true
   125  	b := <-waitForDispatch
   126  	assert.Equal(t, *msg.Header.ID, *b.Payload.Messages[0].Header.ID)
   127  	assert.Equal(t, *data.ID, *b.Payload.Data[0].ID)
   128  
   129  	// Wait until everything closes
   130  	close(readyForDispatch)
   131  	cancel()
   132  	bm.WaitStop()
   133  
   134  }
   135  
   136  func TestE2EDispatchPrivate(t *testing.T) {
   137  	log.SetLevel("debug")
   138  
   139  	mdi := &databasemocks.Plugin{}
   140  	mdm := &datamocks.Manager{}
   141  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, fftypes.SystemNamespace, msgBatchOffsetName).Return(nil, nil).Once()
   142  	mdi.On("UpsertOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   143  	mdi.On("UpdateOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   144  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, fftypes.SystemNamespace, msgBatchOffsetName).Return(&fftypes.Offset{
   145  		ID: fftypes.NewUUID(),
   146  	}, nil)
   147  	readyForDispatch := make(chan bool)
   148  	waitForDispatch := make(chan *fftypes.Batch)
   149  	var groupID fftypes.Bytes32
   150  	_ = groupID.UnmarshalText([]byte("44dc0861e69d9bab17dd5e90a8898c2ea156ad04e5fabf83119cc010486e6c1b"))
   151  	handler := func(ctx context.Context, b *fftypes.Batch, s []*fftypes.Bytes32) error {
   152  		_, ok := <-readyForDispatch
   153  		if !ok {
   154  			return nil
   155  		}
   156  		assert.Len(t, s, 2)
   157  		h := sha256.New()
   158  		nonceBytes, _ := hex.DecodeString(
   159  			"746f70696331" + "44dc0861e69d9bab17dd5e90a8898c2ea156ad04e5fabf83119cc010486e6c1b" + "30783132333435" + "0000000000003039",
   160  		/*|  topic1   |    | ---- group id -------------------------------------------------|   |author'0x12345'|  |i64 nonce (12345) */
   161  		/*|               context                                                           |   |          sender + nonce             */
   162  		) // little endian 12345 in 8 byte hex
   163  		h.Write(nonceBytes)
   164  		assert.Equal(t, hex.EncodeToString(h.Sum([]byte{})), s[0].String())
   165  
   166  		h = sha256.New()
   167  		nonceBytes, _ = hex.DecodeString(
   168  			"746f70696332" + "44dc0861e69d9bab17dd5e90a8898c2ea156ad04e5fabf83119cc010486e6c1b" + "30783132333435" + "000000000000303a",
   169  		/*|   topic2  |    | ---- group id -------------------------------------------------|   |author'0x12345'|  |i64 nonce (12346) */
   170  		/*|               context                                                           |   |          sender + nonce             */
   171  		) // little endian 12345 in 8 byte hex
   172  		h.Write(nonceBytes)
   173  		assert.Equal(t, hex.EncodeToString(h.Sum([]byte{})), s[1].String())
   174  		waitForDispatch <- b
   175  		return nil
   176  	}
   177  	ctx, cancel := context.WithCancel(context.Background())
   178  	bmi, _ := NewBatchManager(ctx, mdi, mdm)
   179  	bm := bmi.(*batchManager)
   180  
   181  	bm.RegisterDispatcher([]fftypes.MessageType{fftypes.MessageTypePrivate}, handler, Options{
   182  		BatchMaxSize:   2,
   183  		BatchTimeout:   0,
   184  		DisposeTimeout: 120 * time.Second,
   185  	})
   186  
   187  	dataID1 := fftypes.NewUUID()
   188  	dataHash := fftypes.NewRandB32()
   189  	msg := &fftypes.Message{
   190  		Header: fftypes.MessageHeader{
   191  			Type:      fftypes.MessageTypePrivate,
   192  			ID:        fftypes.NewUUID(),
   193  			Topics:    []string{"topic1", "topic2"},
   194  			Namespace: "ns1",
   195  			Author:    "0x12345",
   196  			Group:     &groupID,
   197  		},
   198  		Data: fftypes.DataRefs{
   199  			{ID: dataID1, Hash: dataHash},
   200  		},
   201  	}
   202  	data := &fftypes.Data{
   203  		ID:   dataID1,
   204  		Hash: dataHash,
   205  	}
   206  	mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return([]*fftypes.Data{data}, true, nil)
   207  	mdi.On("GetMessages", mock.Anything, mock.Anything).Return([]*fftypes.Message{msg}, nil).Once()
   208  	mdi.On("GetMessages", mock.Anything, mock.Anything).Return([]*fftypes.Message{}, nil)
   209  	mdi.On("UpsertBatch", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   210  	mdi.On("UpdateBatch", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
   211  	rag := mdi.On("RunAsGroup", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   212  	rag.RunFn = func(a mock.Arguments) {
   213  		ctx := a.Get(0).(context.Context)
   214  		fn := a.Get(1).(func(context.Context) error)
   215  		fn(ctx)
   216  	}
   217  	mdi.On("UpdateMessages", mock.Anything, mock.MatchedBy(func(f database.Filter) bool {
   218  		fi, err := f.Finalize()
   219  		assert.NoError(t, err)
   220  		assert.Equal(t, fmt.Sprintf("id IN ['%s']", msg.Header.ID.String()), fi.String())
   221  		return true
   222  	}), mock.Anything).Return(nil)
   223  	ugcn := mdi.On("UpsertNonceNext", mock.Anything, mock.Anything).Return(nil)
   224  	nextNonce := int64(12345)
   225  	ugcn.RunFn = func(a mock.Arguments) {
   226  		a[1].(*fftypes.Nonce).Nonce = nextNonce
   227  		nextNonce++
   228  	}
   229  
   230  	err := bm.Start()
   231  	assert.NoError(t, err)
   232  
   233  	bm.NewMessages() <- msg.Sequence
   234  
   235  	readyForDispatch <- true
   236  	b := <-waitForDispatch
   237  	assert.Equal(t, *msg.Header.ID, *b.Payload.Messages[0].Header.ID)
   238  	assert.Equal(t, *data.ID, *b.Payload.Data[0].ID)
   239  
   240  	// Wait until everything closes
   241  	close(readyForDispatch)
   242  	cancel()
   243  	bm.WaitStop()
   244  
   245  }
   246  func TestInitFailNoPersistence(t *testing.T) {
   247  	_, err := NewBatchManager(context.Background(), nil, nil)
   248  	assert.Error(t, err)
   249  }
   250  
   251  func TestInitRestoreExistingOffset(t *testing.T) {
   252  	mdi := &databasemocks.Plugin{}
   253  	mdm := &datamocks.Manager{}
   254  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, fftypes.SystemNamespace, msgBatchOffsetName).Return(&fftypes.Offset{
   255  		Type:      fftypes.OffsetTypeBatch,
   256  		Namespace: fftypes.SystemNamespace,
   257  		Name:      msgBatchOffsetName,
   258  		Current:   12345,
   259  	}, nil)
   260  	bm, err := NewBatchManager(context.Background(), mdi, mdm)
   261  	assert.NoError(t, err)
   262  	defer bm.Close()
   263  	err = bm.Start()
   264  	assert.NoError(t, err)
   265  	assert.Equal(t, int64(12345), bm.(*batchManager).offset)
   266  }
   267  
   268  func TestInitFailCannotRestoreOffset(t *testing.T) {
   269  	mdi := &databasemocks.Plugin{}
   270  	mdm := &datamocks.Manager{}
   271  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, fftypes.SystemNamespace, msgBatchOffsetName).Return(nil, fmt.Errorf("pop"))
   272  	bm, err := NewBatchManager(context.Background(), mdi, mdm)
   273  	assert.NoError(t, err)
   274  	defer bm.Close()
   275  	bm.(*batchManager).retry.MaximumDelay = 1 * time.Microsecond
   276  	err = bm.Start()
   277  	assert.Regexp(t, "pop", err)
   278  }
   279  
   280  func TestInitFailCannotCreateOffset(t *testing.T) {
   281  	mdi := &databasemocks.Plugin{}
   282  	mdm := &datamocks.Manager{}
   283  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, fftypes.SystemNamespace, msgBatchOffsetName).Return(nil, nil).Once()
   284  	mdi.On("UpsertOffset", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop"))
   285  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, fftypes.SystemNamespace, msgBatchOffsetName).Return(nil, fmt.Errorf("pop"))
   286  	bm, err := NewBatchManager(context.Background(), mdi, mdm)
   287  	assert.NoError(t, err)
   288  	defer bm.Close()
   289  	bm.(*batchManager).retry.MaximumDelay = 1 * time.Microsecond
   290  	err = bm.Start()
   291  	assert.Regexp(t, "pop", err)
   292  }
   293  
   294  func TestGetInvalidBatchTypeMsg(t *testing.T) {
   295  
   296  	mdi := &databasemocks.Plugin{}
   297  	mdm := &datamocks.Manager{}
   298  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeBatch, fftypes.SystemNamespace, msgBatchOffsetName).Return(&fftypes.Offset{
   299  		Current: 12345,
   300  	}, nil)
   301  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   302  	defer bm.Close()
   303  	msg := &fftypes.Message{Header: fftypes.MessageHeader{}}
   304  	err := bm.(*batchManager).dispatchMessage(nil, msg)
   305  	assert.Regexp(t, "FF10126", err)
   306  }
   307  
   308  func TestMessageSequencerCancelledContext(t *testing.T) {
   309  	mdi := &databasemocks.Plugin{}
   310  	mdm := &datamocks.Manager{}
   311  	mdi.On("GetMessages", mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop"))
   312  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   313  	defer bm.Close()
   314  	ctx, cancel := context.WithCancel(context.Background())
   315  	cancel()
   316  	bm.(*batchManager).ctx = ctx
   317  	bm.(*batchManager).messageSequencer()
   318  	assert.Equal(t, 1, len(mdi.Calls))
   319  }
   320  
   321  func TestMessageSequencerMissingMessageData(t *testing.T) {
   322  	mdi := &databasemocks.Plugin{}
   323  	mdm := &datamocks.Manager{}
   324  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   325  
   326  	dataID := fftypes.NewUUID()
   327  	gmMock := mdi.On("GetMessages", mock.Anything, mock.Anything, mock.Anything).Return([]*fftypes.Message{
   328  		{
   329  			Header: fftypes.MessageHeader{
   330  				ID:        fftypes.NewUUID(),
   331  				Namespace: "ns1",
   332  			},
   333  			Data: []*fftypes.DataRef{
   334  				{ID: dataID},
   335  			}},
   336  	}, nil)
   337  	gmMock.RunFn = func(a mock.Arguments) {
   338  		bm.Close() // so we only go round once
   339  	}
   340  	mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return(nil, false, nil)
   341  
   342  	bm.(*batchManager).messageSequencer()
   343  	mdi.AssertExpectations(t)
   344  	mdm.AssertExpectations(t)
   345  }
   346  
   347  func TestMessageSequencerDispatchFail(t *testing.T) {
   348  	mdi := &databasemocks.Plugin{}
   349  	mdm := &datamocks.Manager{}
   350  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   351  
   352  	dataID := fftypes.NewUUID()
   353  	gmMock := mdi.On("GetMessages", mock.Anything, mock.Anything, mock.Anything).Return([]*fftypes.Message{
   354  		{
   355  			Header: fftypes.MessageHeader{
   356  				ID:        fftypes.NewUUID(),
   357  				Type:      fftypes.MessageTypePrivate,
   358  				Namespace: "ns1",
   359  			},
   360  			Data: []*fftypes.DataRef{
   361  				{ID: dataID},
   362  			}},
   363  	}, nil)
   364  	gmMock.RunFn = func(a mock.Arguments) {
   365  		bm.Close() // so we only go round once
   366  	}
   367  	mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return([]*fftypes.Data{{ID: dataID}}, true, nil)
   368  
   369  	bm.(*batchManager).messageSequencer()
   370  	mdi.AssertExpectations(t)
   371  	mdm.AssertExpectations(t)
   372  }
   373  
   374  func TestMessageSequencerUpdateMessagesFail(t *testing.T) {
   375  	mdi := &databasemocks.Plugin{}
   376  	mdm := &datamocks.Manager{}
   377  	ctx, cancelCtx := context.WithCancel(context.Background())
   378  	bm, _ := NewBatchManager(ctx, mdi, mdm)
   379  	bm.RegisterDispatcher([]fftypes.MessageType{fftypes.MessageTypeBroadcast}, func(c context.Context, b *fftypes.Batch, s []*fftypes.Bytes32) error {
   380  		return nil
   381  	}, Options{BatchMaxSize: 1, DisposeTimeout: 0})
   382  
   383  	dataID := fftypes.NewUUID()
   384  	mdi.On("GetMessages", mock.Anything, mock.Anything, mock.Anything).Return([]*fftypes.Message{
   385  		{
   386  			Header: fftypes.MessageHeader{
   387  				ID:        fftypes.NewUUID(),
   388  				Type:      fftypes.MessageTypeBroadcast,
   389  				Namespace: "ns1",
   390  			},
   391  			Data: []*fftypes.DataRef{
   392  				{ID: dataID},
   393  			}},
   394  	}, nil)
   395  	mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return([]*fftypes.Data{{ID: dataID}}, true, nil)
   396  	mdi.On("UpdateMessages", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("fizzle"))
   397  	rag := mdi.On("RunAsGroup", mock.Anything, mock.Anything, mock.Anything)
   398  	rag.RunFn = func(a mock.Arguments) {
   399  		ctx := a.Get(0).(context.Context)
   400  		fn := a.Get(1).(func(context.Context) error)
   401  		err := fn(ctx).(error)
   402  		if err != nil && err.Error() == "fizzle" {
   403  			cancelCtx() // so we only go round once
   404  			bm.Close()
   405  		}
   406  		rag.ReturnArguments = mock.Arguments{err}
   407  	}
   408  
   409  	bm.(*batchManager).messageSequencer()
   410  	mdi.AssertExpectations(t)
   411  	mdm.AssertExpectations(t)
   412  }
   413  
   414  func TestMessageSequencerUpdateBatchFail(t *testing.T) {
   415  	mdi := &databasemocks.Plugin{}
   416  	mdm := &datamocks.Manager{}
   417  	ctx, cancelCtx := context.WithCancel(context.Background())
   418  	bm, _ := NewBatchManager(ctx, mdi, mdm)
   419  	bm.RegisterDispatcher([]fftypes.MessageType{fftypes.MessageTypeBroadcast}, func(c context.Context, b *fftypes.Batch, s []*fftypes.Bytes32) error {
   420  		return nil
   421  	}, Options{BatchMaxSize: 1, DisposeTimeout: 0})
   422  
   423  	dataID := fftypes.NewUUID()
   424  	mdi.On("GetMessages", mock.Anything, mock.Anything, mock.Anything).Return([]*fftypes.Message{
   425  		{
   426  			Header: fftypes.MessageHeader{
   427  				ID:        fftypes.NewUUID(),
   428  				Type:      fftypes.MessageTypeBroadcast,
   429  				Namespace: "ns1",
   430  			},
   431  			Data: []*fftypes.DataRef{
   432  				{ID: dataID},
   433  			}},
   434  	}, nil)
   435  	mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return([]*fftypes.Data{{ID: dataID}}, true, nil)
   436  	mdi.On("UpdateMessages", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   437  	mdi.On("UpsertBatch", mock.Anything, mock.Anything, true, mock.Anything).Return(fmt.Errorf("fizzle"))
   438  	rag := mdi.On("RunAsGroup", mock.Anything, mock.Anything, mock.Anything)
   439  	rag.RunFn = func(a mock.Arguments) {
   440  		ctx := a.Get(0).(context.Context)
   441  		fn := a.Get(1).(func(context.Context) error)
   442  		err := fn(ctx).(error)
   443  		if err != nil && err.Error() == "fizzle" {
   444  			cancelCtx() // so we only go round once
   445  			bm.Close()
   446  		}
   447  		rag.ReturnArguments = mock.Arguments{err}
   448  	}
   449  
   450  	bm.(*batchManager).messageSequencer()
   451  	mdi.AssertExpectations(t)
   452  	mdm.AssertExpectations(t)
   453  }
   454  
   455  func TestWaitForPollTimeout(t *testing.T) {
   456  	mdi := &databasemocks.Plugin{}
   457  	mdm := &datamocks.Manager{}
   458  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   459  	bm.(*batchManager).messagePollTimeout = 1 * time.Microsecond
   460  	bm.(*batchManager).waitForShoulderTapOrPollTimeout()
   461  }
   462  
   463  func TestWaitConsumesMessagesAndDoesNotBlock(t *testing.T) {
   464  	config.Reset()
   465  	mdi := &databasemocks.Plugin{}
   466  	mdm := &datamocks.Manager{}
   467  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   468  	go bm.(*batchManager).newEventNotifications()
   469  	for i := 0; i < int(bm.(*batchManager).readPageSize); i++ {
   470  		bm.NewMessages() <- 12345
   471  	}
   472  	// And should generate a shoulder tap
   473  	<-bm.(*batchManager).shoulderTap
   474  	bm.Close()
   475  }
   476  
   477  func TestAssembleMessageDataNilData(t *testing.T) {
   478  	mdi := &databasemocks.Plugin{}
   479  	mdm := &datamocks.Manager{}
   480  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   481  	bm.Close()
   482  	mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return(nil, false, nil)
   483  	_, err := bm.(*batchManager).assembleMessageData(&fftypes.Message{
   484  		Header: fftypes.MessageHeader{
   485  			ID: fftypes.NewUUID(),
   486  		},
   487  		Data: fftypes.DataRefs{{ID: nil}},
   488  	})
   489  	assert.Regexp(t, "FF10133", err)
   490  }
   491  
   492  func TestAssembleMessageDataClosed(t *testing.T) {
   493  	mdi := &databasemocks.Plugin{}
   494  	mdm := &datamocks.Manager{}
   495  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   496  	bm.(*batchManager).retry.MaximumDelay = 1 * time.Microsecond
   497  	mdi.On("UpdateOffset", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop"))
   498  	err := bm.(*batchManager).updateOffset(false, 10)
   499  	assert.EqualError(t, err, "pop")
   500  }
   501  
   502  func TestGetMessageDataFail(t *testing.T) {
   503  	mdi := &databasemocks.Plugin{}
   504  	mdm := &datamocks.Manager{}
   505  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   506  	mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return(nil, false, fmt.Errorf("pop"))
   507  	bm.Close()
   508  	_, err := bm.(*batchManager).assembleMessageData(&fftypes.Message{
   509  		Header: fftypes.MessageHeader{
   510  			ID: fftypes.NewUUID(),
   511  		},
   512  		Data: fftypes.DataRefs{
   513  			{ID: fftypes.NewUUID(), Hash: fftypes.NewRandB32()},
   514  		},
   515  	})
   516  	assert.EqualError(t, err, "pop")
   517  }
   518  
   519  func TestGetMessageNotFound(t *testing.T) {
   520  	mdi := &databasemocks.Plugin{}
   521  	mdm := &datamocks.Manager{}
   522  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   523  	mdm.On("GetMessageData", mock.Anything, mock.Anything, true).Return(nil, false, nil)
   524  	bm.Close()
   525  	_, err := bm.(*batchManager).assembleMessageData(&fftypes.Message{
   526  		Header: fftypes.MessageHeader{
   527  			ID: fftypes.NewUUID(),
   528  		},
   529  		Data: fftypes.DataRefs{
   530  			{ID: fftypes.NewUUID(), Hash: fftypes.NewRandB32()},
   531  		},
   532  	})
   533  	assert.Regexp(t, "FF10133", err)
   534  }
   535  
   536  func TestWaitForShoulderTap(t *testing.T) {
   537  	mdi := &databasemocks.Plugin{}
   538  	mdm := &datamocks.Manager{}
   539  	bm, _ := NewBatchManager(context.Background(), mdi, mdm)
   540  	bm.(*batchManager).shoulderTap <- true
   541  	bm.(*batchManager).waitForShoulderTapOrPollTimeout()
   542  }