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  }