diff --git a/internal/contracts/manager.go b/internal/contracts/manager.go index 1dc327102..122f07243 100644 --- a/internal/contracts/manager.go +++ b/internal/contracts/manager.go @@ -857,11 +857,14 @@ func (cm *contractManager) AddContractListener(ctx context.Context, listener *co } // Namespace + Topic + Location + Signature must be unique + if len(strings.TrimSpace(listener.Location.String())) == 0 { + listener.Location = nil // consistently store nil + } listener.Signature = cm.blockchain.GenerateEventSignature(ctx, &listener.Event.FFIEventDefinition) fb := database.ContractListenerQueryFactory.NewFilter(ctx) if existing, _, err := cm.database.GetContractListeners(ctx, cm.namespace, fb.And( fb.Eq("topic", listener.Topic), - fb.Eq("location", listener.Location), + fb.Eq("location", listener.Location), // we query nil fb.Eq("signature", listener.Signature), )); err != nil { return err diff --git a/internal/contracts/manager_test.go b/internal/contracts/manager_test.go index 2663cecdd..726ada3d8 100644 --- a/internal/contracts/manager_test.go +++ b/internal/contracts/manager_test.go @@ -788,6 +788,47 @@ func TestAddContractListenerInline(t *testing.T) { mdi.AssertExpectations(t) } +func TestAddContractListenerInlineEmptyString(t *testing.T) { + cm := newTestContractManager() + mbi := cm.blockchain.(*blockchainmocks.Plugin) + mdi := cm.database.(*databasemocks.Plugin) + + sub := &core.ContractListenerInput{ + ContractListener: core.ContractListener{ + Location: fftypes.JSONAnyPtr(" "), + Event: &core.FFISerializedEvent{ + FFIEventDefinition: fftypes.FFIEventDefinition{ + Name: "changed", + Params: fftypes.FFIParams{ + { + Name: "value", + Schema: fftypes.JSONAnyPtr(`{"type": "integer"}`), + }, + }, + }, + }, + Options: &core.ContractListenerOptions{}, + Topic: "test-topic", + }, + } + + mbi.On("NormalizeContractLocation", context.Background(), blockchain.NormalizeListener, sub.Location).Return(sub.Location, nil) + mbi.On("GenerateEventSignature", context.Background(), mock.Anything).Return("changed") + mdi.On("GetContractListeners", context.Background(), "ns1", mock.Anything).Return(nil, nil, nil) + mbi.On("AddContractListener", context.Background(), mock.MatchedBy(func(cl *core.ContractListener) bool { + return cl.Location == nil // converted from empty string + })).Return(nil) + mdi.On("InsertContractListener", context.Background(), &sub.ContractListener).Return(nil) + + result, err := cm.AddContractListener(context.Background(), sub) + assert.NoError(t, err) + assert.NotNil(t, result.ID) + assert.NotNil(t, result.Event) + + mbi.AssertExpectations(t) + mdi.AssertExpectations(t) +} + func TestAddContractListenerNoLocationOK(t *testing.T) { cm := newTestContractManager() mbi := cm.blockchain.(*blockchainmocks.Plugin)