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 }