github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/events/subscription_manager_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 24 "github.com/kaleido-io/firefly/internal/config" 25 "github.com/kaleido-io/firefly/mocks/databasemocks" 26 "github.com/kaleido-io/firefly/mocks/eventsmocks" 27 "github.com/kaleido-io/firefly/pkg/events" 28 "github.com/kaleido-io/firefly/pkg/fftypes" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/mock" 31 ) 32 33 func newTestSubManager(t *testing.T, mdi *databasemocks.Plugin, mei *eventsmocks.Plugin) (*subscriptionManager, func()) { 34 config.Reset() 35 config.Set(config.EventTransportsEnabled, []string{}) 36 ctx, cancel := context.WithCancel(context.Background()) 37 mei.On("Name").Return("ut") 38 mei.On("InitPrefix", mock.Anything).Return() 39 mei.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(nil) 40 mdi.On("GetEvents", mock.Anything, mock.Anything, mock.Anything).Return([]*fftypes.Event{}, nil).Maybe() 41 mdi.On("GetOffset", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&fftypes.Offset{ID: fftypes.NewUUID(), Current: 0}, nil).Maybe() 42 sm, err := newSubscriptionManager(ctx, mdi, newEventNotifier(ctx, "ut")) 43 assert.NoError(t, err) 44 sm.transports = map[string]events.Plugin{ 45 "ut": mei, 46 } 47 return sm, cancel 48 } 49 50 func TestRegisterDurableSubscriptions(t *testing.T) { 51 mdi := &databasemocks.Plugin{} 52 mei := &eventsmocks.Plugin{} 53 sub1 := fftypes.NewUUID() 54 sub2 := fftypes.NewUUID() 55 mdi.On("GetSubscriptions", mock.Anything, mock.Anything).Return([]*fftypes.Subscription{ 56 {SubscriptionRef: fftypes.SubscriptionRef{ 57 ID: sub1, 58 }, Transport: "ut"}, 59 {SubscriptionRef: fftypes.SubscriptionRef{ 60 ID: sub2, 61 }, Transport: "ut"}, 62 }, nil) 63 sm, cancel := newTestSubManager(t, mdi, mei) 64 defer cancel() 65 err := sm.start() 66 assert.NoError(t, err) 67 68 // Set some existing ones to be cleaned out 69 testED1, cancel1 := newTestEventDispatcher(mdi, mei, &subscription{definition: &fftypes.Subscription{SubscriptionRef: fftypes.SubscriptionRef{ID: sub1}}}) 70 testED1.start() 71 defer cancel1() 72 sm.connections["conn1"] = &connection{ 73 ei: mei, 74 id: "conn1", 75 dispatchers: map[fftypes.UUID]*eventDispatcher{ 76 *sub1: testED1, 77 }, 78 } 79 be := &boundCallbacks{sm: sm, ei: mei} 80 81 be.RegisterConnection("conn1", func(sr fftypes.SubscriptionRef) bool { 82 return *sr.ID == *sub2 83 }) 84 be.RegisterConnection("conn2", func(sr fftypes.SubscriptionRef) bool { 85 return *sr.ID == *sub1 86 }) 87 88 assert.Equal(t, 1, len(sm.connections["conn1"].dispatchers)) 89 assert.Equal(t, *sub2, *sm.connections["conn1"].dispatchers[*sub2].subscription.definition.ID) 90 assert.Equal(t, 1, len(sm.connections["conn2"].dispatchers)) 91 assert.Equal(t, *sub1, *sm.connections["conn2"].dispatchers[*sub1].subscription.definition.ID) 92 93 // Close with active conns 94 sm.close() 95 assert.Nil(t, sm.connections["conn1"]) 96 assert.Nil(t, sm.connections["conn2"]) 97 } 98 99 func TestRegisterEphemeralSubscriptions(t *testing.T) { 100 mdi := &databasemocks.Plugin{} 101 mei := &eventsmocks.Plugin{} 102 mdi.On("GetSubscriptions", mock.Anything, mock.Anything).Return([]*fftypes.Subscription{}, nil) 103 sm, cancel := newTestSubManager(t, mdi, mei) 104 defer cancel() 105 err := sm.start() 106 assert.NoError(t, err) 107 be := &boundCallbacks{sm: sm, ei: mei} 108 109 err = be.EphemeralSubscription("conn1", "ns1", fftypes.SubscriptionFilter{}, fftypes.SubscriptionOptions{}) 110 assert.NoError(t, err) 111 112 assert.Equal(t, 1, len(sm.connections["conn1"].dispatchers)) 113 for _, d := range sm.connections["conn1"].dispatchers { 114 assert.True(t, d.subscription.definition.Ephemeral) 115 } 116 117 be.ConnnectionClosed("conn1") 118 assert.Nil(t, sm.connections["conn1"]) 119 // Check we swallow dup closes without errors 120 be.ConnnectionClosed("conn1") 121 assert.Nil(t, sm.connections["conn1"]) 122 } 123 124 func TestRegisterEphemeralSubscriptionsFail(t *testing.T) { 125 mdi := &databasemocks.Plugin{} 126 mei := &eventsmocks.Plugin{} 127 mdi.On("GetSubscriptions", mock.Anything, mock.Anything).Return([]*fftypes.Subscription{}, nil) 128 sm, cancel := newTestSubManager(t, mdi, mei) 129 defer cancel() 130 err := sm.start() 131 assert.NoError(t, err) 132 be := &boundCallbacks{sm: sm, ei: mei} 133 134 err = be.EphemeralSubscription("conn1", "ns1", fftypes.SubscriptionFilter{ 135 Topics: "[[[[[ !wrong", 136 }, fftypes.SubscriptionOptions{}) 137 assert.Regexp(t, "FF10171", err) 138 assert.Empty(t, sm.connections["conn1"].dispatchers) 139 140 } 141 142 func TestSubManagerBadPlugin(t *testing.T) { 143 mdi := &databasemocks.Plugin{} 144 config.Reset() 145 config.Set(config.EventTransportsEnabled, []string{"!unknown!"}) 146 _, err := newSubscriptionManager(context.Background(), mdi, newEventNotifier(context.Background(), "ut")) 147 assert.Regexp(t, "FF10172", err) 148 } 149 150 func TestSubManagerTransportInitError(t *testing.T) { 151 mdi := &databasemocks.Plugin{} 152 mei := &eventsmocks.Plugin{} 153 mei.On("Name").Return("ut") 154 mei.On("InitPrefix", mock.Anything).Return() 155 mei.On("Init", mock.Anything, mock.Anything, mock.Anything).Return(fmt.Errorf("pop")) 156 sm, cancel := newTestSubManager(t, mdi, mei) 157 defer cancel() 158 159 err := sm.initTransports() 160 assert.EqualError(t, err, "pop") 161 } 162 163 func TestStartSubRestoreFail(t *testing.T) { 164 mdi := &databasemocks.Plugin{} 165 mei := &eventsmocks.Plugin{} 166 mdi.On("GetSubscriptions", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("pop")) 167 sm, cancel := newTestSubManager(t, mdi, mei) 168 defer cancel() 169 err := sm.start() 170 assert.EqualError(t, err, "pop") 171 } 172 173 func TestStartSubRestoreOkSubsFail(t *testing.T) { 174 mdi := &databasemocks.Plugin{} 175 mei := &eventsmocks.Plugin{} 176 mdi.On("GetSubscriptions", mock.Anything, mock.Anything).Return([]*fftypes.Subscription{ 177 {SubscriptionRef: fftypes.SubscriptionRef{ 178 ID: fftypes.NewUUID(), 179 }, 180 Filter: fftypes.SubscriptionFilter{ 181 Events: "[[[[[[not a regex", 182 }}, 183 }, nil) 184 sm, cancel := newTestSubManager(t, mdi, mei) 185 defer cancel() 186 err := sm.start() 187 assert.NoError(t, err) // swallowed and startup continues 188 } 189 190 func TestStartSubRestoreOkSubsOK(t *testing.T) { 191 mdi := &databasemocks.Plugin{} 192 mei := &eventsmocks.Plugin{} 193 mdi.On("GetSubscriptions", mock.Anything, mock.Anything).Return([]*fftypes.Subscription{ 194 {SubscriptionRef: fftypes.SubscriptionRef{ 195 ID: fftypes.NewUUID(), 196 }, 197 Filter: fftypes.SubscriptionFilter{ 198 Events: ".*", 199 Topics: ".*", 200 Tag: ".*", 201 Group: ".*", 202 }}, 203 }, nil) 204 sm, cancel := newTestSubManager(t, mdi, mei) 205 defer cancel() 206 err := sm.start() 207 assert.NoError(t, err) // swallowed and startup continues 208 } 209 210 func TestCreateSubscriptionBadTransport(t *testing.T) { 211 mdi := &databasemocks.Plugin{} 212 mei := &eventsmocks.Plugin{} 213 sm, cancel := newTestSubManager(t, mdi, mei) 214 defer cancel() 215 _, err := sm.parseSubscriptionDef(sm.ctx, &fftypes.Subscription{}) 216 assert.Regexp(t, "FF1017", err) 217 } 218 219 func TestCreateSubscriptionBadEventilter(t *testing.T) { 220 mdi := &databasemocks.Plugin{} 221 mei := &eventsmocks.Plugin{} 222 sm, cancel := newTestSubManager(t, mdi, mei) 223 defer cancel() 224 _, err := sm.parseSubscriptionDef(sm.ctx, &fftypes.Subscription{ 225 Filter: fftypes.SubscriptionFilter{ 226 Events: "[[[[! badness", 227 }, 228 Transport: "ut", 229 }) 230 assert.Regexp(t, "FF10171.*events", err) 231 } 232 233 func TestCreateSubscriptionBadTopicFilter(t *testing.T) { 234 mdi := &databasemocks.Plugin{} 235 mei := &eventsmocks.Plugin{} 236 sm, cancel := newTestSubManager(t, mdi, mei) 237 defer cancel() 238 _, err := sm.parseSubscriptionDef(sm.ctx, &fftypes.Subscription{ 239 Filter: fftypes.SubscriptionFilter{ 240 Topics: "[[[[! badness", 241 }, 242 Transport: "ut", 243 }) 244 assert.Regexp(t, "FF10171.*topic", err) 245 } 246 247 func TestCreateSubscriptionBadContextFilter(t *testing.T) { 248 mdi := &databasemocks.Plugin{} 249 mei := &eventsmocks.Plugin{} 250 sm, cancel := newTestSubManager(t, mdi, mei) 251 defer cancel() 252 _, err := sm.parseSubscriptionDef(sm.ctx, &fftypes.Subscription{ 253 Filter: fftypes.SubscriptionFilter{ 254 Tag: "[[[[! badness", 255 }, 256 Transport: "ut", 257 }) 258 assert.Regexp(t, "FF10171.*tag", err) 259 } 260 261 func TestCreateSubscriptionBadGroupFilter(t *testing.T) { 262 mdi := &databasemocks.Plugin{} 263 mei := &eventsmocks.Plugin{} 264 sm, cancel := newTestSubManager(t, mdi, mei) 265 defer cancel() 266 _, err := sm.parseSubscriptionDef(sm.ctx, &fftypes.Subscription{ 267 Filter: fftypes.SubscriptionFilter{ 268 Group: "[[[[! badness", 269 }, 270 Transport: "ut", 271 }) 272 assert.Regexp(t, "FF10171.*group", err) 273 } 274 275 func TestDispatchDeliveryResponseOK(t *testing.T) { 276 mdi := &databasemocks.Plugin{} 277 mei := &eventsmocks.Plugin{} 278 mdi.On("GetSubscriptions", mock.Anything, mock.Anything).Return([]*fftypes.Subscription{}, nil) 279 sm, cancel := newTestSubManager(t, mdi, mei) 280 defer cancel() 281 err := sm.start() 282 assert.NoError(t, err) 283 be := &boundCallbacks{sm: sm, ei: mei} 284 285 err = be.EphemeralSubscription("conn1", "ns1", fftypes.SubscriptionFilter{}, fftypes.SubscriptionOptions{}) 286 assert.NoError(t, err) 287 288 assert.Equal(t, 1, len(sm.connections["conn1"].dispatchers)) 289 var subID *fftypes.UUID 290 for _, d := range sm.connections["conn1"].dispatchers { 291 assert.True(t, d.subscription.definition.Ephemeral) 292 subID = d.subscription.definition.ID 293 } 294 295 err = be.DeliveryResponse("conn1", fftypes.EventDeliveryResponse{ 296 ID: fftypes.NewUUID(), // Won't be in-flight, but that's fine 297 Subscription: fftypes.SubscriptionRef{ 298 ID: subID, 299 }, 300 }) 301 assert.NoError(t, err) 302 } 303 304 func TestDispatchDeliveryResponseInvalidSubscription(t *testing.T) { 305 mdi := &databasemocks.Plugin{} 306 mei := &eventsmocks.Plugin{} 307 mdi.On("GetSubscriptions", mock.Anything, mock.Anything).Return([]*fftypes.Subscription{}, nil) 308 sm, cancel := newTestSubManager(t, mdi, mei) 309 defer cancel() 310 err := sm.start() 311 assert.NoError(t, err) 312 be := &boundCallbacks{sm: sm, ei: mei} 313 314 err = be.DeliveryResponse("conn1", fftypes.EventDeliveryResponse{ 315 ID: fftypes.NewUUID(), 316 Subscription: fftypes.SubscriptionRef{ 317 ID: fftypes.NewUUID(), 318 }, 319 }) 320 assert.Regexp(t, "FF10181", err) 321 } 322 323 func TestConnIDSafetyChecking(t *testing.T) { 324 mdi := &databasemocks.Plugin{} 325 mei1 := &eventsmocks.Plugin{} 326 mei2 := &eventsmocks.Plugin{} 327 mei2.On("Name").Return("ut2") 328 sm, cancel := newTestSubManager(t, mdi, mei1) 329 defer cancel() 330 be2 := &boundCallbacks{sm: sm, ei: mei2} 331 332 sm.connections["conn1"] = &connection{ 333 ei: mei1, 334 id: "conn1", 335 dispatchers: map[fftypes.UUID]*eventDispatcher{}, 336 } 337 338 err := be2.RegisterConnection("conn1", func(sr fftypes.SubscriptionRef) bool { return true }) 339 assert.Regexp(t, "FF10190", err) 340 341 err = be2.EphemeralSubscription("conn1", "ns1", fftypes.SubscriptionFilter{}, fftypes.SubscriptionOptions{}) 342 assert.Regexp(t, "FF10190", err) 343 344 err = be2.DeliveryResponse("conn1", fftypes.EventDeliveryResponse{}) 345 assert.Regexp(t, "FF10190", err) 346 347 be2.ConnnectionClosed("conn1") 348 349 assert.NotNil(t, sm.connections["conn1"]) 350 351 } 352 353 func TestNewDurableSubscriptionBadSub(t *testing.T) { 354 mdi := &databasemocks.Plugin{} 355 mei := &eventsmocks.Plugin{} 356 sm, cancel := newTestSubManager(t, mdi, mei) 357 defer cancel() 358 359 subID := fftypes.NewUUID() 360 mdi.On("GetSubscriptionByID", mock.Anything, subID).Return(&fftypes.Subscription{ 361 Filter: fftypes.SubscriptionFilter{ 362 Events: "![[[[badness", 363 }, 364 }, nil) 365 sm.newDurableSubscription(subID) 366 367 assert.Empty(t, sm.durableSubs) 368 } 369 370 func TestNewDurableSubscriptionUnknownTransport(t *testing.T) { 371 mdi := &databasemocks.Plugin{} 372 mei := &eventsmocks.Plugin{} 373 sm, cancel := newTestSubManager(t, mdi, mei) 374 defer cancel() 375 376 sm.connections["conn1"] = &connection{ 377 ei: mei, 378 id: "conn1", 379 matcher: func(sr fftypes.SubscriptionRef) bool { 380 return sr.Namespace == "ns1" && sr.Name == "sub1" 381 }, 382 dispatchers: map[fftypes.UUID]*eventDispatcher{}, 383 } 384 385 subID := fftypes.NewUUID() 386 mdi.On("GetSubscriptionByID", mock.Anything, subID).Return(&fftypes.Subscription{ 387 SubscriptionRef: fftypes.SubscriptionRef{ 388 ID: subID, 389 Namespace: "ns1", 390 Name: "sub1", 391 }, 392 Transport: "unknown", 393 }, nil) 394 sm.newDurableSubscription(subID) 395 396 assert.Empty(t, sm.connections["conn1"].dispatchers) 397 assert.Empty(t, sm.durableSubs) 398 } 399 400 func TestNewDurableSubscriptionOK(t *testing.T) { 401 mdi := &databasemocks.Plugin{} 402 mei := &eventsmocks.Plugin{} 403 sm, cancel := newTestSubManager(t, mdi, mei) 404 defer cancel() 405 406 sm.connections["conn1"] = &connection{ 407 ei: mei, 408 id: "conn1", 409 matcher: func(sr fftypes.SubscriptionRef) bool { 410 return sr.Namespace == "ns1" && sr.Name == "sub1" 411 }, 412 dispatchers: map[fftypes.UUID]*eventDispatcher{}, 413 } 414 415 subID := fftypes.NewUUID() 416 mdi.On("GetSubscriptionByID", mock.Anything, subID).Return(&fftypes.Subscription{ 417 SubscriptionRef: fftypes.SubscriptionRef{ 418 ID: subID, 419 Namespace: "ns1", 420 Name: "sub1", 421 }, 422 Transport: "ut", 423 }, nil) 424 sm.newDurableSubscription(subID) 425 426 assert.NotEmpty(t, sm.connections["conn1"].dispatchers) 427 assert.NotEmpty(t, sm.durableSubs) 428 } 429 430 func TestMatchedSubscriptionWithLockUnknownTransport(t *testing.T) { 431 mdi := &databasemocks.Plugin{} 432 mei := &eventsmocks.Plugin{} 433 sm, cancel := newTestSubManager(t, mdi, mei) 434 defer cancel() 435 436 conn := &connection{} 437 sm.matchedSubscriptionWithLock(conn, &subscription{definition: &fftypes.Subscription{Transport: "Wrong!"}}) 438 assert.Nil(t, conn.dispatchers) 439 } 440 441 func TestDeletewDurableSubscriptionOk(t *testing.T) { 442 mdi := &databasemocks.Plugin{} 443 mei := &eventsmocks.Plugin{} 444 sm, cancel := newTestSubManager(t, mdi, mei) 445 defer cancel() 446 447 subID := fftypes.NewUUID() 448 subDef := &fftypes.Subscription{ 449 SubscriptionRef: fftypes.SubscriptionRef{ 450 ID: subID, 451 Namespace: "ns1", 452 Name: "sub1", 453 }, 454 Transport: "websockets", 455 } 456 sub := &subscription{ 457 definition: subDef, 458 } 459 sm.durableSubs[*subID] = sub 460 ed, _ := newTestEventDispatcher(mdi, mei, sub) 461 ed.start() 462 sm.connections["conn1"] = &connection{ 463 ei: mei, 464 id: "conn1", 465 matcher: func(sr fftypes.SubscriptionRef) bool { 466 return sr.Namespace == "ns1" && sr.Name == "sub1" 467 }, 468 dispatchers: map[fftypes.UUID]*eventDispatcher{ 469 *subID: ed, 470 }, 471 } 472 473 mdi.On("GetSubscriptionByID", mock.Anything, subID).Return(subDef, nil) 474 sm.deletedDurableSubscription(subID) 475 476 assert.Empty(t, sm.connections["conn1"].dispatchers) 477 assert.Empty(t, sm.durableSubs) 478 <-ed.closed 479 }