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

     1  // Copyright © 2021 Kaleido, Inc.
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package events
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/kaleido-io/firefly/internal/retry"
    26  	"github.com/kaleido-io/firefly/mocks/databasemocks"
    27  	"github.com/kaleido-io/firefly/pkg/database"
    28  	"github.com/kaleido-io/firefly/pkg/fftypes"
    29  	"github.com/stretchr/testify/assert"
    30  	"github.com/stretchr/testify/mock"
    31  )
    32  
    33  func newTestEventPoller(t *testing.T, mdi *databasemocks.Plugin, neh newEventsHandler, rewinder func() (bool, int64)) (ep *eventPoller, cancel func()) {
    34  	ctx, cancel := context.WithCancel(context.Background())
    35  	ep = newEventPoller(ctx, mdi, newEventNotifier(ctx, "ut"), &eventPollerConf{
    36  		eventBatchSize:             10,
    37  		eventBatchTimeout:          1 * time.Millisecond,
    38  		eventPollTimeout:           10 * time.Second,
    39  		startupOffsetRetryAttempts: 1,
    40  		retry: retry.Retry{
    41  			InitialDelay: 1 * time.Microsecond,
    42  			MaximumDelay: 1 * time.Microsecond,
    43  			Factor:       2.0,
    44  		},
    45  		newEventsHandler: neh,
    46  		offsetType:       fftypes.OffsetTypeSubscription,
    47  		offsetNamespace:  "unit",
    48  		offsetName:       "test",
    49  		queryFactory:     database.EventQueryFactory,
    50  		getItems: func(c context.Context, f database.Filter) ([]fftypes.LocallySequenced, error) {
    51  			events, err := mdi.GetEvents(c, f)
    52  			ls := make([]fftypes.LocallySequenced, len(events))
    53  			for i, e := range events {
    54  				ls[i] = e
    55  			}
    56  			return ls, err
    57  		},
    58  		maybeRewind: rewinder,
    59  		addCriteria: func(af database.AndFilter) database.AndFilter { return af },
    60  	})
    61  	return ep, cancel
    62  }
    63  
    64  func TestStartStopEventPoller(t *testing.T) {
    65  	mdi := &databasemocks.Plugin{}
    66  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
    67  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(&fftypes.Offset{
    68  		Type:      fftypes.OffsetTypeAggregator,
    69  		Namespace: fftypes.SystemNamespace,
    70  		Name:      aggregatorOffsetName,
    71  		Current:   12345,
    72  	}, nil)
    73  	mdi.On("GetEvents", mock.Anything, mock.Anything, mock.Anything).Return([]*fftypes.Event{}, nil)
    74  	err := ep.start()
    75  	assert.NoError(t, err)
    76  	assert.Equal(t, int64(12345), ep.pollingOffset)
    77  	ep.eventNotifier.newEvents <- 12345
    78  	cancel()
    79  	<-ep.closed
    80  }
    81  
    82  func TestRestoreOffsetNewestOK(t *testing.T) {
    83  	mdi := &databasemocks.Plugin{}
    84  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
    85  	defer cancel()
    86  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(nil, nil).Once()
    87  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(&fftypes.Offset{Current: 12345}, nil).Once()
    88  	mdi.On("GetEvents", mock.Anything, mock.Anything).Return([]*fftypes.Event{{Sequence: 12345}}, nil)
    89  	mdi.On("UpsertOffset", mock.Anything, mock.MatchedBy(func(offset *fftypes.Offset) bool {
    90  		return offset.Current == 12345
    91  	}), false).Return(nil)
    92  	err := ep.restoreOffset()
    93  	assert.NoError(t, err)
    94  	assert.Equal(t, int64(12345), ep.pollingOffset)
    95  	mdi.AssertExpectations(t)
    96  }
    97  
    98  func TestRestoreOffsetNewestNoEvents(t *testing.T) {
    99  	mdi := &databasemocks.Plugin{}
   100  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   101  	defer cancel()
   102  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(nil, nil).Once()
   103  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(&fftypes.Offset{Current: -1}, nil).Once()
   104  	mdi.On("GetEvents", mock.Anything, mock.Anything).Return([]*fftypes.Event{}, nil)
   105  	mdi.On("UpsertOffset", mock.Anything, mock.MatchedBy(func(offset *fftypes.Offset) bool {
   106  		return offset.Current == -1
   107  	}), false).Return(nil)
   108  	err := ep.restoreOffset()
   109  	assert.NoError(t, err)
   110  	assert.Equal(t, int64(-1), ep.pollingOffset)
   111  	mdi.AssertExpectations(t)
   112  }
   113  
   114  func TestRestoreOffsetNewestFail(t *testing.T) {
   115  	mdi := &databasemocks.Plugin{}
   116  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   117  	defer cancel()
   118  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(nil, nil)
   119  	mdi.On("GetEvents", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop"))
   120  	err := ep.restoreOffset()
   121  	assert.EqualError(t, err, "pop")
   122  	assert.Equal(t, int64(0), ep.pollingOffset)
   123  	mdi.AssertExpectations(t)
   124  }
   125  
   126  func TestRestoreOffsetOldest(t *testing.T) {
   127  	mdi := &databasemocks.Plugin{}
   128  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   129  	firstEvent := fftypes.SubOptsFirstEventOldest
   130  	ep.conf.firstEvent = &firstEvent
   131  	defer cancel()
   132  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(nil, nil).Once()
   133  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(&fftypes.Offset{Current: -1}, nil).Once()
   134  	mdi.On("UpsertOffset", mock.Anything, mock.MatchedBy(func(offset *fftypes.Offset) bool {
   135  		return offset.Current == -1
   136  	}), false).Return(nil)
   137  	err := ep.restoreOffset()
   138  	assert.NoError(t, err)
   139  	assert.Equal(t, int64(-1), ep.pollingOffset)
   140  	mdi.AssertExpectations(t)
   141  }
   142  
   143  func TestRestoreOffsetSpecific(t *testing.T) {
   144  	mdi := &databasemocks.Plugin{}
   145  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   146  	firstEvent := fftypes.SubOptsFirstEvent("123456")
   147  	ep.conf.firstEvent = &firstEvent
   148  	defer cancel()
   149  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(nil, nil).Once()
   150  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(&fftypes.Offset{Current: 123456}, nil)
   151  	mdi.On("UpsertOffset", mock.Anything, mock.MatchedBy(func(offset *fftypes.Offset) bool {
   152  		return offset.Current == 123456
   153  	}), false).Return(nil)
   154  	err := ep.restoreOffset()
   155  	assert.NoError(t, err)
   156  	assert.Equal(t, int64(123456), ep.pollingOffset)
   157  	mdi.AssertExpectations(t)
   158  }
   159  
   160  func TestRestoreOffsetFailRead(t *testing.T) {
   161  	mdi := &databasemocks.Plugin{}
   162  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   163  	defer cancel()
   164  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(nil, fmt.Errorf("pop"))
   165  	err := ep.start()
   166  	assert.EqualError(t, err, "pop")
   167  	mdi.AssertExpectations(t)
   168  }
   169  
   170  func TestRestoreOffsetFailWrite(t *testing.T) {
   171  	mdi := &databasemocks.Plugin{}
   172  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   173  	firstEvent := fftypes.SubOptsFirstEventOldest
   174  	ep.conf.firstEvent = &firstEvent
   175  	defer cancel()
   176  	mdi.On("GetOffset", mock.Anything, fftypes.OffsetTypeSubscription, "unit", "test").Return(nil, nil)
   177  	mdi.On("UpsertOffset", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop"))
   178  	err := ep.restoreOffset()
   179  	assert.EqualError(t, err, "pop")
   180  	mdi.AssertExpectations(t)
   181  }
   182  
   183  func TestRestoreOffsetEphemeral(t *testing.T) {
   184  	mdi := &databasemocks.Plugin{}
   185  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   186  	firstEvent := fftypes.SubOptsFirstEventOldest
   187  	ep.conf.firstEvent = &firstEvent
   188  	ep.conf.ephemeral = true
   189  	defer cancel()
   190  	err := ep.restoreOffset()
   191  	assert.NoError(t, err)
   192  	mdi.AssertExpectations(t)
   193  }
   194  
   195  func TestReadPageExit(t *testing.T) {
   196  	mdi := &databasemocks.Plugin{}
   197  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   198  	cancel()
   199  	mdi.On("GetEvents", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop"))
   200  	ep.eventLoop()
   201  	mdi.AssertExpectations(t)
   202  }
   203  
   204  func TestReadPageSingleCommitEvent(t *testing.T) {
   205  	mdi := &databasemocks.Plugin{}
   206  	processEventCalled := make(chan fftypes.LocallySequenced, 1)
   207  	ep, cancel := newTestEventPoller(t, mdi, func(events []fftypes.LocallySequenced) (bool, error) {
   208  		processEventCalled <- events[0]
   209  		return false, nil
   210  	}, nil)
   211  	cancel()
   212  	ev1 := fftypes.NewEvent(fftypes.EventTypeMessageConfirmed, "ns1", fftypes.NewUUID(), nil)
   213  	mdi.On("GetEvents", mock.Anything, mock.Anything).Return([]*fftypes.Event{ev1}, nil).Once()
   214  	mdi.On("GetEvents", mock.Anything, mock.Anything).Return([]*fftypes.Event{}, nil)
   215  	ep.eventLoop()
   216  
   217  	event := <-processEventCalled
   218  	assert.Equal(t, *ev1.ID, *event.(*fftypes.Event).ID)
   219  	mdi.AssertExpectations(t)
   220  }
   221  
   222  func TestReadPageRewind(t *testing.T) {
   223  	mdi := &databasemocks.Plugin{}
   224  	processEventCalled := make(chan fftypes.LocallySequenced, 1)
   225  	ep, cancel := newTestEventPoller(t, mdi, func(events []fftypes.LocallySequenced) (bool, error) {
   226  		processEventCalled <- events[0]
   227  		return false, nil
   228  	}, func() (bool, int64) {
   229  		return true, 12345
   230  	})
   231  	cancel()
   232  	ev1 := fftypes.NewEvent(fftypes.EventTypeMessageConfirmed, "ns1", fftypes.NewUUID(), nil)
   233  	mdi.On("GetEvents", mock.Anything, mock.MatchedBy(func(filter database.Filter) bool {
   234  		f, err := filter.Finalize()
   235  		assert.NoError(t, err)
   236  		assert.Equal(t, "sequence", f.Children[0].Field)
   237  		v, _ := f.Children[0].Value.Value()
   238  		assert.Equal(t, int64(12345), v)
   239  		return true
   240  	})).Return([]*fftypes.Event{ev1}, nil).Once()
   241  	mdi.On("GetEvents", mock.Anything, mock.Anything).Return([]*fftypes.Event{}, nil)
   242  	ep.eventLoop()
   243  
   244  	event := <-processEventCalled
   245  	assert.Equal(t, *ev1.ID, *event.(*fftypes.Event).ID)
   246  	mdi.AssertExpectations(t)
   247  }
   248  
   249  func TestReadPageProcessEventsRetryExit(t *testing.T) {
   250  	mdi := &databasemocks.Plugin{}
   251  	ep, cancel := newTestEventPoller(t, mdi, func(events []fftypes.LocallySequenced) (bool, error) { return false, fmt.Errorf("pop") }, nil)
   252  	cancel()
   253  	ev1 := fftypes.NewEvent(fftypes.EventTypeMessageConfirmed, "ns1", fftypes.NewUUID(), nil)
   254  	mdi.On("GetEvents", mock.Anything, mock.Anything).Return([]*fftypes.Event{ev1}, nil).Once()
   255  	ep.eventLoop()
   256  
   257  	mdi.AssertExpectations(t)
   258  }
   259  
   260  func TestProcessEventsFail(t *testing.T) {
   261  	mdi := &databasemocks.Plugin{}
   262  	ep, cancel := newTestEventPoller(t, mdi, func(events []fftypes.LocallySequenced) (bool, error) {
   263  		return false, fmt.Errorf("pop")
   264  	}, nil)
   265  	defer cancel()
   266  	_, err := ep.conf.newEventsHandler([]fftypes.LocallySequenced{
   267  		fftypes.NewEvent(fftypes.EventTypeMessageConfirmed, "ns1", fftypes.NewUUID(), nil),
   268  	})
   269  	assert.EqualError(t, err, "pop")
   270  	mdi.AssertExpectations(t)
   271  }
   272  
   273  func TestWaitForShoulderTapOrExitCloseBatch(t *testing.T) {
   274  	mdi := &databasemocks.Plugin{}
   275  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   276  	cancel()
   277  	ep.conf.eventBatchTimeout = 1 * time.Minute
   278  	ep.conf.eventBatchSize = 50
   279  	assert.False(t, ep.waitForShoulderTapOrPollTimeout(1))
   280  }
   281  
   282  func TestWaitForShoulderTapOrExitClosePoll(t *testing.T) {
   283  	mdi := &databasemocks.Plugin{}
   284  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   285  	cancel()
   286  	ep.conf.eventBatchTimeout = 1 * time.Minute
   287  	ep.conf.eventBatchSize = 1
   288  	assert.False(t, ep.waitForShoulderTapOrPollTimeout(1))
   289  }
   290  
   291  func TestWaitForShoulderTapOrPollTimeoutBatchAndPoll(t *testing.T) {
   292  	mdi := &databasemocks.Plugin{}
   293  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   294  	defer cancel()
   295  	ep.conf.eventBatchTimeout = 1 * time.Microsecond
   296  	ep.conf.eventPollTimeout = 1 * time.Microsecond
   297  	ep.conf.eventBatchSize = 50
   298  	assert.True(t, ep.waitForShoulderTapOrPollTimeout(1))
   299  }
   300  
   301  func TestWaitForShoulderTapOrPollTimeoutTap(t *testing.T) {
   302  	mdi := &databasemocks.Plugin{}
   303  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   304  	defer cancel()
   305  	ep.shoulderTap()
   306  	assert.True(t, ep.waitForShoulderTapOrPollTimeout(ep.conf.eventBatchSize))
   307  }
   308  
   309  func TestDoubleTap(t *testing.T) {
   310  	mdi := &databasemocks.Plugin{}
   311  	ep, cancel := newTestEventPoller(t, mdi, nil, nil)
   312  	defer cancel()
   313  	ep.shoulderTap()
   314  	ep.shoulderTap() // this should not block
   315  }