diff --git a/source/informers/fake.go b/source/informers/fake.go new file mode 100644 index 000000000..aed7b3aee --- /dev/null +++ b/source/informers/fake.go @@ -0,0 +1,60 @@ +/* +Copyright 2025 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package informers + +import ( + "github.com/stretchr/testify/mock" + corev1lister "k8s.io/client-go/listers/core/v1" + discoveryv1lister "k8s.io/client-go/listers/discovery/v1" + "k8s.io/client-go/tools/cache" +) + +type FakeServiceInformer struct { + mock.Mock +} + +func (f *FakeServiceInformer) Informer() cache.SharedIndexInformer { + args := f.Called() + return args.Get(0).(cache.SharedIndexInformer) +} + +func (f *FakeServiceInformer) Lister() corev1lister.ServiceLister { + return corev1lister.NewServiceLister(f.Informer().GetIndexer()) +} + +type FakeEndpointSliceInformer struct { + mock.Mock +} + +func (f *FakeEndpointSliceInformer) Informer() cache.SharedIndexInformer { + args := f.Called() + return args.Get(0).(cache.SharedIndexInformer) +} + +func (f *FakeEndpointSliceInformer) Lister() discoveryv1lister.EndpointSliceLister { + return discoveryv1lister.NewEndpointSliceLister(f.Informer().GetIndexer()) +} + +type FakeNodeInformer struct { + mock.Mock +} + +func (f *FakeNodeInformer) Informer() cache.SharedIndexInformer { + args := f.Called() + return args.Get(0).(cache.SharedIndexInformer) +} + +func (f *FakeNodeInformer) Lister() corev1lister.NodeLister { + return corev1lister.NewNodeLister(f.Informer().GetIndexer()) +} diff --git a/source/service.go b/source/service.go index a339b192f..a2a8f8f7f 100644 --- a/source/service.go +++ b/source/service.go @@ -96,28 +96,9 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name // Set the resync period to 0 to prevent processing when nothing has changed informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace)) serviceInformer := informerFactory.Core().V1().Services() - endpointSlicesInformer := informerFactory.Discovery().V1().EndpointSlices() - podInformer := informerFactory.Core().V1().Pods() // Add default resource event handlers to properly initialize informer. - _, _ = serviceInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - }, - }, - ) - _, _ = endpointSlicesInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - }, - }, - ) - _, _ = podInformer.Informer().AddEventHandler( - cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj interface{}) { - }, - }, - ) + _, _ = serviceInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) // Transform the slice into a map so it will be way much easier and fast to filter later sTypesFilter, err := newServiceTypesFilter(serviceTypeFilter) @@ -125,30 +106,40 @@ func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, name return nil, err } - var nodeInformer coreinformers.NodeInformer - if sTypesFilter.isNodeInformerRequired() { - nodeInformer = informerFactory.Core().V1().Nodes() - _, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) + var endpointSlicesInformer discoveryinformers.EndpointSliceInformer + var podInformer coreinformers.PodInformer + if sTypesFilter.isRequired(v1.ServiceTypeNodePort, v1.ServiceTypeClusterIP) { + endpointSlicesInformer = informerFactory.Discovery().V1().EndpointSlices() + podInformer = informerFactory.Core().V1().Pods() + + _, _ = endpointSlicesInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) + _, _ = podInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) + + // Add an indexer to the EndpointSlice informer to index by the service name label + err = endpointSlicesInformer.Informer().AddIndexers(cache.Indexers{ + serviceNameIndexKey: func(obj any) ([]string, error) { + endpointSlice, ok := obj.(*discoveryv1.EndpointSlice) + if !ok { + // This should never happen because the Informer should only contain EndpointSlice objects + return nil, fmt.Errorf("expected %T but got %T instead", endpointSlice, obj) + } + serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName] + if serviceName == "" { + return nil, nil + } + key := types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}.String() + return []string{key}, nil + }, + }) + if err != nil { + return nil, err + } } - // Add an indexer to the EndpointSlice informer to index by the service name label - err = endpointSlicesInformer.Informer().AddIndexers(cache.Indexers{ - serviceNameIndexKey: func(obj any) ([]string, error) { - endpointSlice, ok := obj.(*discoveryv1.EndpointSlice) - if !ok { - // This should never happen because the Informer should only contain EndpointSlice objects - return nil, fmt.Errorf("expected %T but got %T instead", endpointSlice, obj) - } - serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName] - if serviceName == "" { - return nil, nil - } - key := types.NamespacedName{Namespace: endpointSlice.Namespace, Name: serviceName}.String() - return []string{key}, nil - }, - }) - if err != nil { - return nil, err + var nodeInformer coreinformers.NodeInformer + if sTypesFilter.isRequired(v1.ServiceTypeNodePort) { + nodeInformer = informerFactory.Core().V1().Nodes() + _, _ = nodeInformer.Informer().AddEventHandler(informers.DefaultEventHandler()) } informerFactory.Start(ctx.Done()) @@ -808,10 +799,10 @@ func (sc *serviceSource) AddEventHandler(_ context.Context, handler func()) { // Right now there is no way to remove event handler from informer, see: // https://github.com/kubernetes/kubernetes/issues/79610 _, _ = sc.serviceInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) - if sc.listenEndpointEvents { + if sc.listenEndpointEvents && sc.serviceTypeFilter.isRequired(v1.ServiceTypeNodePort, v1.ServiceTypeClusterIP) { _, _ = sc.endpointSlicesInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } - if sc.serviceTypeFilter.isNodeInformerRequired() { + if sc.serviceTypeFilter.isRequired(v1.ServiceTypeNodePort) { _, _ = sc.nodeInformer.Informer().AddEventHandler(eventHandlerFunc(handler)) } } @@ -848,12 +839,18 @@ func (sc *serviceTypes) isProcessed(serviceType v1.ServiceType) bool { return !sc.enabled || sc.types[serviceType] } -func (sc *serviceTypes) isNodeInformerRequired() bool { - if !sc.enabled { +// isRequired returns true if service type filtering is disabled or if any of the provided service types are present in the filter. +// If no options are provided, it returns true. +func (sc *serviceTypes) isRequired(opts ...v1.ServiceType) bool { + if len(opts) == 0 || !sc.enabled { return true } - _, ok := sc.types[v1.ServiceTypeNodePort] - return ok + for _, opt := range opts { + if _, ok := sc.types[opt]; ok { + return true + } + } + return false } // conditionToBool converts an EndpointConditions condition to a bool value. diff --git a/source/service_test.go b/source/service_test.go index dd21331ef..07df803bd 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -37,6 +37,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes/fake" + "sigs.k8s.io/external-dns/source/informers" "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/internal/testutils" @@ -251,7 +252,7 @@ func testServiceSourceEndpoints(t *testing.T) { }, externalIPs: []string{}, lbs: []string{"1.2.3.4"}, - serviceTypesFilter: []string{}, + serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)}, expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -296,7 +297,7 @@ func testServiceSourceEndpoints(t *testing.T) { annotations: map[string]string{}, externalIPs: []string{}, lbs: []string{"1.2.3.4"}, - serviceTypesFilter: []string{}, + serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)}, expected: []*endpoint.Endpoint{ {DNSName: "foo.fqdn.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, @@ -2498,6 +2499,7 @@ func TestHeadlessServices(t *testing.T) { podsReady []bool publishNotReadyAddresses bool nodes []v1.Node + serviceTypesFilter []string expected []*endpoint.Endpoint expectError bool }{ @@ -2528,6 +2530,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}}, @@ -2562,6 +2565,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{string(v1.ServiceTypeClusterIP), string(v1.ServiceTypeLoadBalancer)}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}}, @@ -2596,6 +2600,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{}, false, }, @@ -2627,6 +2632,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)}, @@ -2662,6 +2668,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}, RecordTTL: endpoint.TTL(1)}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}, RecordTTL: endpoint.TTL(1)}, @@ -2696,6 +2703,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, false}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, @@ -2729,6 +2737,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, false}, true, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}}, {DNSName: "foo-1.service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.2"}}, @@ -2763,6 +2772,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, }, @@ -2795,6 +2805,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, }, @@ -2827,6 +2838,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}}, }, @@ -2861,6 +2873,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{string(v1.ServiceTypeClusterIP)}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -2895,6 +2908,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, }, @@ -2939,6 +2953,7 @@ func TestHeadlessServices(t *testing.T) { }, }, }, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -2987,6 +3002,7 @@ func TestHeadlessServices(t *testing.T) { }, }, }, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::5"}}, }, @@ -3031,6 +3047,7 @@ func TestHeadlessServices(t *testing.T) { }, }, }, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, }, @@ -3079,6 +3096,7 @@ func TestHeadlessServices(t *testing.T) { }, }, }, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, @@ -3113,6 +3131,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}}, }, @@ -3146,6 +3165,7 @@ func TestHeadlessServices(t *testing.T) { []bool{true, true, true}, false, []v1.Node{}, + []string{}, []*endpoint.Endpoint{ {DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}}, }, @@ -3242,7 +3262,7 @@ func TestHeadlessServices(t *testing.T) { true, false, false, - []string{}, + tc.serviceTypesFilter, tc.ignoreHostnameAnnotation, labels.Everything(), false, @@ -3994,7 +4014,6 @@ func TestHeadlessServicesHostIP(t *testing.T) { t.Run(tc.title, func(t *testing.T) { t.Parallel() - // Create a Kubernetes testing client kubernetes := fake.NewClientset() service := &v1.Service{ @@ -4134,7 +4153,7 @@ func TestExternalServices(t *testing.T) { }, "111.111.111.111", []string{}, - []string{}, + []string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)}, []*endpoint.Endpoint{ {DNSName: "service.example.org", Targets: endpoint.Targets{"111.111.111.111"}, RecordType: endpoint.RecordTypeA}, }, @@ -4176,7 +4195,7 @@ func TestExternalServices(t *testing.T) { }, "remote.example.com", []string{}, - []string{}, + []string{string(v1.ServiceTypeExternalName)}, []*endpoint.Endpoint{ {DNSName: "service.example.org", Targets: endpoint.Targets{"remote.example.com"}, RecordType: endpoint.RecordTypeCNAME}, }, @@ -4371,6 +4390,8 @@ func TestNewServiceSourceInformersEnabled(t *testing.T) { assert.NotNil(t, svc.serviceTypeFilter) assert.False(t, svc.serviceTypeFilter.enabled) assert.NotNil(t, svc.nodeInformer) + assert.NotNil(t, svc.serviceInformer) + assert.NotNil(t, svc.endpointSlicesInformer) }, }, { @@ -4380,17 +4401,49 @@ func TestNewServiceSourceInformersEnabled(t *testing.T) { assert.NotNil(t, svc) assert.NotNil(t, svc.serviceTypeFilter) assert.True(t, svc.serviceTypeFilter.enabled) + assert.NotNil(t, svc.serviceInformer) assert.Nil(t, svc.nodeInformer) + assert.NotNil(t, svc.endpointSlicesInformer) + assert.NotNil(t, svc.podInformer) }, }, { - name: "serviceTypeFilter contains NodePort", - svcFilter: []string{string(v1.ServiceTypeNodePort)}, + name: "serviceTypeFilter contains NodePort and ExternalName", + svcFilter: []string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)}, asserts: func(svc *serviceSource) { assert.NotNil(t, svc) assert.NotNil(t, svc.serviceTypeFilter) assert.True(t, svc.serviceTypeFilter.enabled) + assert.NotNil(t, svc.serviceInformer) assert.NotNil(t, svc.nodeInformer) + assert.NotNil(t, svc.endpointSlicesInformer) + assert.NotNil(t, svc.podInformer) + }, + }, + { + name: "serviceTypeFilter contains ExternalName", + svcFilter: []string{string(v1.ServiceTypeExternalName)}, + asserts: func(svc *serviceSource) { + assert.NotNil(t, svc) + assert.NotNil(t, svc.serviceTypeFilter) + assert.True(t, svc.serviceTypeFilter.enabled) + assert.NotNil(t, svc.serviceInformer) + assert.Nil(t, svc.nodeInformer) + assert.Nil(t, svc.endpointSlicesInformer) + assert.Nil(t, svc.podInformer) + }, + }, + { + name: "serviceTypeFilter contains LoadBalancer", + svcFilter: []string{string(v1.ServiceTypeLoadBalancer)}, + asserts: func(svc *serviceSource) { + assert.NotNil(t, svc) + assert.NotNil(t, svc.serviceTypeFilter) + assert.True(t, svc.serviceTypeFilter.enabled) + assert.NotNil(t, svc.serviceInformer) + assert.Nil(t, svc.nodeInformer) + assert.Nil(t, svc.endpointSlicesInformer) + assert.Nil(t, svc.podInformer) }, }, } @@ -4681,32 +4734,126 @@ func createTestServicesByType(namespace string, typeCounts map[v1.ServiceType]in func TestServiceTypes_isNodeInformerRequired(t *testing.T) { tests := []struct { - name string - filter []string - want bool + name string + filter []string + required []v1.ServiceType + want bool }{ { - name: "NodePort type present", - filter: []string{string(v1.ServiceTypeNodePort)}, - want: true, + name: "NodePort required and filter is empty", + filter: []string{}, + required: []v1.ServiceType{v1.ServiceTypeNodePort}, + want: true, }, { - name: "NodePort type absent, filter enabled", - filter: []string{string(v1.ServiceTypeLoadBalancer)}, - want: false, + name: "NodePort type present", + filter: []string{string(v1.ServiceTypeNodePort)}, + required: []v1.ServiceType{v1.ServiceTypeNodePort}, + want: true, }, { - name: "NodePort and other filters present", - filter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)}, - want: true, + name: "NodePort type absent, filter enabled", + filter: []string{string(v1.ServiceTypeLoadBalancer)}, + required: []v1.ServiceType{v1.ServiceTypeNodePort}, + want: false, + }, + { + name: "NodePort and other filters present", + filter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)}, + required: []v1.ServiceType{v1.ServiceTypeNodePort}, + want: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { filter, _ := newServiceTypesFilter(tt.filter) - got := filter.isNodeInformerRequired() + got := filter.isRequired(tt.required...) assert.Equal(t, tt.want, got) }) } } + +func TestServiceSource_AddEventHandler(t *testing.T) { + var fakeServiceInformer *informers.FakeServiceInformer + var fakeEdpInformer *informers.FakeEndpointSliceInformer + var fakeNodeInformer *informers.FakeNodeInformer + tests := []struct { + name string + filter []string + times int + asserts func(t *testing.T, s *serviceSource) + }{ + { + name: "AddEventHandler should trigger all event handlers when empty filter is provided", + filter: []string{}, + times: 3, + asserts: func(t *testing.T, s *serviceSource) { + fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 1) + }, + }, + { + name: "AddEventHandler should trigger only service event handler", + filter: []string{string(v1.ServiceTypeExternalName), string(v1.ServiceTypeLoadBalancer)}, + times: 1, + asserts: func(t *testing.T, s *serviceSource) { + fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 0) + fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0) + }, + }, + { + name: "AddEventHandler should configure only service event handler", + filter: []string{string(v1.ServiceTypeExternalName), string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeClusterIP)}, + times: 2, + asserts: func(t *testing.T, s *serviceSource) { + fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0) + }, + }, + { + name: "AddEventHandler should configure all service event handlers", + filter: []string{string(v1.ServiceTypeNodePort)}, + times: 3, + asserts: func(t *testing.T, s *serviceSource) { + fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1) + fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 1) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fakeServiceInformer = new(informers.FakeServiceInformer) + infSvc := testInformer{} + fakeServiceInformer.On("Informer").Return(&infSvc) + + fakeEdpInformer = new(informers.FakeEndpointSliceInformer) + infEdp := testInformer{} + fakeEdpInformer.On("Informer").Return(&infEdp) + + fakeNodeInformer = new(informers.FakeNodeInformer) + infNode := testInformer{} + fakeNodeInformer.On("Informer").Return(&infNode) + + filter, _ := newServiceTypesFilter(tt.filter) + + svcSource := &serviceSource{ + endpointSlicesInformer: fakeEdpInformer, + serviceInformer: fakeServiceInformer, + nodeInformer: fakeNodeInformer, + serviceTypeFilter: filter, + listenEndpointEvents: true, + } + + svcSource.AddEventHandler(t.Context(), func() {}) + + assert.Equal(t, tt.times, infSvc.times+infEdp.times+infNode.times) + + tt.asserts(t, svcSource) + }) + } +}