/*
Copyright 2017 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 source

import (
	"context"
	"fmt"
	"maps"
	"math/rand"
	"net"
	"sort"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
	appsv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	discoveryv1 "k8s.io/api/discovery/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/intstr"
	kubeinformers "k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes/fake"

	"sigs.k8s.io/external-dns/source/types"

	"sigs.k8s.io/external-dns/endpoint"
	"sigs.k8s.io/external-dns/internal/testutils"
	"sigs.k8s.io/external-dns/source/annotations"
	"sigs.k8s.io/external-dns/source/informers"
	"sigs.k8s.io/external-dns/source/informers/fakes"
	templatetest "sigs.k8s.io/external-dns/source/template/testutil"
)

type ServiceSuite struct {
	suite.Suite
	sc             Source
	fooWithTargets *v1.Service
}

func (suite *ServiceSuite) SetupTest() {
	fakeClient := fake.NewClientset()

	suite.fooWithTargets = &v1.Service{
		Spec: v1.ServiceSpec{
			Type: v1.ServiceTypeLoadBalancer,
		},
		ObjectMeta: metav1.ObjectMeta{
			Namespace:   "default",
			Name:        "foo-with-targets",
			Annotations: map[string]string{},
		},
		Status: v1.ServiceStatus{
			LoadBalancer: v1.LoadBalancerStatus{
				Ingress: []v1.LoadBalancerIngress{
					{IP: "8.8.8.8"},
					{Hostname: "foo"},
				},
			},
		},
	}
	_, err := fakeClient.CoreV1().Services(suite.fooWithTargets.Namespace).Create(context.Background(), suite.fooWithTargets, metav1.CreateOptions{})
	suite.NoError(err, "should successfully create service")

	suite.sc, err = NewServiceSource(
		context.TODO(),
		fakeClient,
		&Config{
			TemplateEngine: templatetest.MustEngine(suite.T(), "{{.Name}}", "", "", false),
			LabelFilter:    labels.Everything(),
		},
	)
	suite.NoError(err, "should initialize service source")
}

func (suite *ServiceSuite) TestResourceLabelIsSet() {
	endpoints, _ := suite.sc.Endpoints(context.Background())
	for _, ep := range endpoints {
		suite.Equal("service/default/foo-with-targets", ep.Labels[endpoint.ResourceLabelKey], "should set correct resource label")
	}
}

func TestServiceSource(t *testing.T) {
	t.Parallel()

	suite.Run(t, new(ServiceSuite))
	t.Run("Interface", testServiceSourceImplementsSource)
	t.Run("Endpoints", testServiceSourceEndpoints)
	t.Run("MultipleServices", testMultipleServicesEndpoints)
}

// testServiceSourceImplementsSource tests that serviceSource is a valid Source.
func testServiceSourceImplementsSource(t *testing.T) {
	assert.Implements(t, (*Source)(nil), new(serviceSource))
}

// testServiceSourceEndpoints tests that various services generate the correct endpoints.
func testServiceSourceEndpoints(t *testing.T) {
	exampleDotComIP4, err := net.DefaultResolver.LookupNetIP(t.Context(), "ip4", "example.com")
	assert.NoError(t, err)
	exampleDotComIP6, err := net.DefaultResolver.LookupNetIP(t.Context(), "ip6", "example.com")
	assert.NoError(t, err)

	t.Parallel()

	for _, tc := range []struct {
		title                       string
		targetNamespace             string
		annotationFilter            string
		svcNamespace                string
		svcName                     string
		svcType                     v1.ServiceType
		compatibility               string
		fqdnTemplate                string
		combineFQDNAndAnnotation    bool
		ignoreHostnameAnnotation    bool
		labels                      map[string]string
		annotations                 map[string]string
		clusterIP                   string
		externalIPs                 []string
		lbs                         []string
		serviceTypesFilter          []string
		expected                    []*endpoint.Endpoint
		expectError                 bool
		serviceLabelSelector        string
		resolveLoadBalancerHostname bool
	}{
		{
			title:              "no annotated services return no endpoints",
			svcNamespace:       "testing",
			svcName:            "foo",
			svcType:            v1.ServiceTypeLoadBalancer,
			labels:             map[string]string{},
			annotations:        map[string]string{},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:                    "no annotated services return no endpoints when ignoring annotations",
			svcNamespace:             "testing",
			svcName:                  "foo",
			svcType:                  v1.ServiceTypeLoadBalancer,
			ignoreHostnameAnnotation: true,
			labels:                   map[string]string{},
			annotations:              map[string]string{},
			externalIPs:              []string{},
			lbs:                      []string{"1.2.3.4"},
			serviceTypesFilter:       []string{},
			expected:                 []*endpoint.Endpoint{},
		},
		{
			title:        "annotated services return an endpoint with target IP",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:                    "hostname annotation on services is ignored",
			svcNamespace:             "testing",
			svcName:                  "foo",
			svcType:                  v1.ServiceTypeLoadBalancer,
			ignoreHostnameAnnotation: true,
			labels:                   map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:        "annotated ClusterIp aren't processed without explicit authorization",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			clusterIP:          "1.2.3.4",
			externalIPs:        []string{},
			lbs:                []string{},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:              "FQDN template with multiple hostnames return an endpoint with target IP",
			svcNamespace:       "testing",
			svcName:            "foo",
			svcType:            v1.ServiceTypeLoadBalancer,
			fqdnTemplate:       "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
			labels:             map[string]string{},
			annotations:        map[string]string{},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			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"}},
			},
		},
		{
			title:              "with excluded service type should not generate endpoints",
			svcNamespace:       "testing",
			svcName:            "foo",
			svcType:            v1.ServiceTypeLoadBalancer,
			fqdnTemplate:       "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
			labels:             map[string]string{},
			annotations:        map[string]string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{string(v1.ServiceTypeNodePort)},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:                    "FQDN template with multiple hostnames return an endpoint with target IP when ignoring annotations",
			svcNamespace:             "testing",
			svcName:                  "foo",
			svcType:                  v1.ServiceTypeLoadBalancer,
			fqdnTemplate:             "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
			ignoreHostnameAnnotation: true,
			labels:                   map[string]string{},
			annotations:              map[string]string{},
			externalIPs:              []string{},
			lbs:                      []string{"1.2.3.4"},
			serviceTypesFilter:       []string{},
			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"}},
			},
		},
		{
			title:                    "FQDN template and annotation both with multiple hostnames return an endpoint with target IP",
			svcNamespace:             "testing",
			svcName:                  "foo",
			svcType:                  v1.ServiceTypeLoadBalancer,
			fqdnTemplate:             "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
			combineFQDNAndAnnotation: true,
			labels:                   map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org., bar.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{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"}},
			},
		},
		{
			title:                    "FQDN template and annotation both with multiple hostnames while ignoring annotations will only return FQDN endpoints",
			svcNamespace:             "testing",
			svcName:                  "foo",
			svcType:                  v1.ServiceTypeLoadBalancer,
			fqdnTemplate:             "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com",
			combineFQDNAndAnnotation: true,
			ignoreHostnameAnnotation: true,
			labels:                   map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org., bar.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			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"}},
			},
		},
		{
			title:        "annotated services with multiple hostnames return an endpoint with target IP",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org., bar.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:        "annotated services with multiple hostnames and without trailing period return an endpoint with target IP",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org, bar.example.org",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:        "annotated services return an endpoint with target hostname",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"lb.example.com"}, // Kubernetes omits the trailing dot
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"lb.example.com"}},
			},
		},
		{
			title:        "annotated services return an endpoint with hostname then resolve hostname",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:                 []string{},
			lbs:                         []string{"example.com"}, // Use a resolvable hostname for testing.
			serviceTypesFilter:          []string{},
			resolveLoadBalancerHostname: true,
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: testutils.NewTargetsFromAddr(exampleDotComIP4)},
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: testutils.NewTargetsFromAddr(exampleDotComIP6)},
			},
		},
		{
			title:        "unresolvable LB hostname is skipped when resolveLoadBalancerHostname is true",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:                 []string{},
			lbs:                         []string{"this-host-does-not-exist.invalid"},
			serviceTypesFilter:          []string{},
			resolveLoadBalancerHostname: true,
			expected:                    []*endpoint.Endpoint{},
		},
		{
			title:        "annotated services can omit trailing dot",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org", // Trailing dot is omitted
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"lb.example.com"}},
			},
		},
		{
			title:        "our controller type is kops dns controller",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.ControllerKey: annotations.ControllerValue,
				annotations.HostnameKey:   "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer), string(v1.ServiceTypeNodePort)},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:        "different controller types are ignored even (with template specified)",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			fqdnTemplate: "{{.Name}}.ext-dns.test.com",
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.ControllerKey: "some-other-tool",
				annotations.HostnameKey:   "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:           "services are found in target namespace",
			targetNamespace: "testing",
			svcNamespace:    "testing",
			svcName:         "foo",
			svcType:         v1.ServiceTypeLoadBalancer,
			labels:          map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:           "services that are not in target namespace are ignored",
			targetNamespace: "testing",
			svcNamespace:    "other-testing",
			svcName:         "foo",
			svcType:         v1.ServiceTypeLoadBalancer,
			labels:          map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:        "services are found in all namespaces",
			svcNamespace: "other-testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:            "valid matching annotation filter expression",
			annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeLoadBalancer,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey:                       "foo.example.org.",
				"service.beta.kubernetes.io/external-traffic": "OnlyLocal",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:            "valid non-matching annotation filter expression",
			annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeLoadBalancer,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey:                       "foo.example.org.",
				"service.beta.kubernetes.io/external-traffic": "SomethingElse",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:            "invalid annotation filter expression",
			annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global OnlyLocal)",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeLoadBalancer,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey:                       "foo.example.org.",
				"service.beta.kubernetes.io/external-traffic": "OnlyLocal",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
			expectError:        true,
		},
		{
			title:            "valid matching annotation filter label",
			annotationFilter: "service.beta.kubernetes.io/external-traffic=Global",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeLoadBalancer,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey:                       "foo.example.org.",
				"service.beta.kubernetes.io/external-traffic": "Global",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:            "valid non-matching annotation filter label",
			annotationFilter: "service.beta.kubernetes.io/external-traffic=Global",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeLoadBalancer,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey:                       "foo.example.org.",
				"service.beta.kubernetes.io/external-traffic": "OnlyLocal",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:        "no external entrypoints return no endpoints",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:        "annotated service with externalIPs returns a single endpoint with multiple targets",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{"10.2.3.4", "11.2.3.4"},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.2.3.4", "11.2.3.4"}},
			},
		},
		{
			title:        "multiple external entrypoints return a single endpoint with multiple targets",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4", "8.8.8.8"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4", "8.8.8.8"}},
			},
		},
		{
			title:        "services annotated with legacy mate annotations are ignored in default mode",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				"zalando.org/dnsname": "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:         "services annotated with legacy mate annotations return an endpoint in compatibility mode",
			svcNamespace:  "testing",
			svcName:       "foo",
			svcType:       v1.ServiceTypeLoadBalancer,
			compatibility: "mate",
			labels:        map[string]string{},
			annotations: map[string]string{
				"zalando.org/dnsname": "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:         "services annotated with legacy molecule annotations return an endpoint in compatibility mode",
			svcNamespace:  "testing",
			svcName:       "foo",
			svcType:       v1.ServiceTypeLoadBalancer,
			compatibility: "molecule",
			labels: map[string]string{
				"dns": "route53",
			},
			annotations: map[string]string{
				"domainName": "foo.example.org., bar.example.org",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:         "load balancer services annotated with DNS Controller annotations return an endpoint with A and CNAME targets in compatibility mode",
			svcNamespace:  "testing",
			svcName:       "foo",
			svcType:       v1.ServiceTypeLoadBalancer,
			compatibility: "kops-dns-controller",
			labels:        map[string]string{},
			annotations: map[string]string{
				kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4", "lb.example.com"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"lb.example.com"}},
			},
		},
		{
			title:         "load balancer services annotated with DNS Controller annotations return an endpoint with both annotations in compatibility mode",
			svcNamespace:  "testing",
			svcName:       "foo",
			svcType:       v1.ServiceTypeLoadBalancer,
			compatibility: "kops-dns-controller",
			labels:        map[string]string{},
			annotations: map[string]string{
				kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org",
				kopsDNSControllerHostnameAnnotationKey:         "foo.example.org., bar.example.org",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:              "not annotated services with set fqdnTemplate return an endpoint with target IP",
			svcNamespace:       "testing",
			svcName:            "foo",
			svcType:            v1.ServiceTypeLoadBalancer,
			fqdnTemplate:       "{{.Name}}.bar.example.com",
			labels:             map[string]string{},
			annotations:        map[string]string{},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4", "elb.com"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"elb.com"}},
			},
		},
		{
			title:        "annotated services with set fqdnTemplate annotation takes precedence",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			fqdnTemplate: "{{.Name}}.bar.example.com",
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4", "elb.com"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"elb.com"}},
			},
		},
		{
			title:         "compatibility annotated services with tmpl. compatibility takes precedence",
			svcNamespace:  "testing",
			svcName:       "foo",
			svcType:       v1.ServiceTypeLoadBalancer,
			compatibility: "mate",
			fqdnTemplate:  "{{.Name}}.bar.example.com",
			labels:        map[string]string{},
			annotations: map[string]string{
				"zalando.org/dnsname": "mate.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "mate.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:              "not annotated services with unknown tmpl field should not return anything",
			svcNamespace:       "testing",
			svcName:            "foo",
			svcType:            v1.ServiceTypeLoadBalancer,
			fqdnTemplate:       "{{.Calibre}}.bar.example.com",
			labels:             map[string]string{},
			annotations:        map[string]string{},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected:           []*endpoint.Endpoint{},
			expectError:        true,
		},
		{
			title:        "ttl not annotated should have RecordTTL.IsConfigured set to false",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
			},
		},
		{
			title:        "ttl annotated but invalid should have RecordTTL.IsConfigured set to false",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TtlKey:      "foo",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
			},
		},
		{
			title:        "ttl annotated and is valid should set Record.TTL",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TtlKey:      "10",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(10)},
			},
		},
		{
			title:        "ttl annotated (in duration format) and is valid should set Record.TTL",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TtlKey:      "1m",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(60)},
			},
		},
		{
			title:        "Negative ttl is not valid",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TtlKey:      "-10",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)},
			},
		},
		{
			title:        "filter on service types should include matching services",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:        "filter on service types should exclude non-matching services",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeNodePort,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)},
			expected:           []*endpoint.Endpoint{},
		},
		{
			title:        "internal-host annotated and host annotated clusterip services return an endpoint with Cluster IP",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey:         "foo.example.org.",
				annotations.InternalHostnameKey: "foo.internal.example.org.",
			},
			clusterIP:          "1.1.1.1",
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.internal.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
			},
		},
		{
			title:        "internal-host annotated loadbalancer services return an endpoint with Cluster IP",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.InternalHostnameKey: "foo.internal.example.org.",
			},
			clusterIP:          "1.1.1.1",
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.internal.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
			},
		},
		{
			title:        "internal-host annotated and host annotated loadbalancer services return an endpoint with Cluster IP and an endpoint with lb IP",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels:       map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey:         "foo.example.org.",
				annotations.InternalHostnameKey: "foo.internal.example.org.",
			},
			clusterIP:          "1.1.1.1",
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.internal.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:        "service with matching labels and fqdn filter should be included",
			svcNamespace: "testing",
			svcName:      "fqdn",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels: map[string]string{
				"app": "web-external",
			},
			clusterIP:            "1.1.1.1",
			externalIPs:          []string{},
			lbs:                  []string{"1.2.3.4"},
			serviceTypesFilter:   []string{},
			serviceLabelSelector: "app=web-external",
			fqdnTemplate:         "{{.Name}}.bar.example.com",
			expected: []*endpoint.Endpoint{
				{DNSName: "fqdn.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:        "service with matching labels and hostname annotation should be included",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels: map[string]string{
				"app": "web-external",
			},
			clusterIP:            "1.1.1.1",
			externalIPs:          []string{},
			lbs:                  []string{"1.2.3.4"},
			serviceTypesFilter:   []string{},
			serviceLabelSelector: "app=web-external",
			annotations:          map[string]string{annotations.HostnameKey: "annotation.bar.example.com"},
			expected: []*endpoint.Endpoint{
				{DNSName: "annotation.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:        "service without matching labels and fqdn filter should be excluded",
			svcNamespace: "testing",
			svcName:      "fqdn",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels: map[string]string{
				"app": "web-internal",
			},
			clusterIP:            "1.1.1.1",
			externalIPs:          []string{},
			lbs:                  []string{"1.2.3.4"},
			serviceTypesFilter:   []string{},
			serviceLabelSelector: "app=web-external",
			fqdnTemplate:         "{{.Name}}.bar.example.com",
			expected:             []*endpoint.Endpoint{},
		},
		{
			title:        "service without matching labels and hostname annotation should be excluded",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeLoadBalancer,
			labels: map[string]string{
				"app": "web-internal",
			},
			clusterIP:            "1.1.1.1",
			externalIPs:          []string{},
			lbs:                  []string{"1.2.3.4"},
			serviceTypesFilter:   []string{},
			serviceLabelSelector: "app=web-external",
			annotations:          map[string]string{annotations.HostnameKey: "annotation.bar.example.com"},
			expected:             []*endpoint.Endpoint{},
		},
		{
			title:              "dual-stack load-balancer service gets both addresses",
			svcNamespace:       "testing",
			svcName:            "foobar",
			svcType:            v1.ServiceTypeLoadBalancer,
			labels:             map[string]string{},
			clusterIP:          "1.1.1.2,2001:db8::2",
			externalIPs:        []string{},
			lbs:                []string{"1.1.1.1", "2001:db8::1"},
			serviceTypesFilter: []string{},
			annotations:        map[string]string{annotations.HostnameKey: "foobar.example.org"},
			expected: []*endpoint.Endpoint{
				{DNSName: "foobar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}},
				{DNSName: "foobar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1"}},
			},
		},
		{
			title:              "IPv6-only load-balancer service gets IPv6 endpoint",
			svcNamespace:       "testing",
			svcName:            "foobar-v6",
			svcType:            v1.ServiceTypeLoadBalancer,
			labels:             map[string]string{},
			clusterIP:          "2001:db8::1",
			externalIPs:        []string{},
			lbs:                []string{"2001:db8::2"},
			serviceTypesFilter: []string{},
			annotations:        map[string]string{annotations.HostnameKey: "foobar-v6.example.org"},
			expected: []*endpoint.Endpoint{
				{DNSName: "foobar-v6.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::2"}},
			},
		},
		{
			title:              "provider-specific annotation is converted to endpoint property",
			svcNamespace:       "testing",
			svcName:            "foo",
			svcType:            v1.ServiceTypeLoadBalancer,
			labels:             map[string]string{},
			externalIPs:        []string{},
			lbs:                []string{"1.2.3.4"},
			serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)},
			annotations: map[string]string{
				annotations.HostnameKey:          "foo.example.org",
				annotations.AWSPrefix + "weight": "10",
			},
			expected: []*endpoint.Endpoint{
				{
					DNSName:    "foo.example.org",
					RecordType: endpoint.RecordTypeA,
					Targets:    endpoint.Targets{"1.2.3.4"},
					ProviderSpecific: endpoint.ProviderSpecific{
						{Name: "aws/weight", Value: "10"},
					},
				},
			},
		},
	} {

		t.Run(tc.title, func(t *testing.T) {
			t.Parallel()

			// Create a Kubernetes testing client
			kubernetes := fake.NewClientset()

			// Create a service to test against
			ingresses := []v1.LoadBalancerIngress{}
			for _, lb := range tc.lbs {
				if net.ParseIP(lb) != nil {
					ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb})
				} else {
					ingresses = append(ingresses, v1.LoadBalancerIngress{Hostname: lb})
				}
			}

			service := &v1.Service{
				Spec: v1.ServiceSpec{
					Type:        tc.svcType,
					ClusterIP:   tc.clusterIP,
					ExternalIPs: tc.externalIPs,
				},
				ObjectMeta: metav1.ObjectMeta{
					Namespace:   tc.svcNamespace,
					Name:        tc.svcName,
					Labels:      tc.labels,
					Annotations: tc.annotations,
				},
				Status: v1.ServiceStatus{
					LoadBalancer: v1.LoadBalancerStatus{
						Ingress: ingresses,
					},
				},
			}

			_, err := kubernetes.CoreV1().Services(service.Namespace).Create(t.Context(), service, metav1.CreateOptions{})
			require.NoError(t, err)

			sourceLabel := labels.Everything()
			if tc.serviceLabelSelector != "" {
				sourceLabel, err = labels.Parse(tc.serviceLabelSelector)
				require.NoError(t, err)
			}

			// Create our object under test and get the endpoints.
			client, err := NewServiceSource(t.Context(), kubernetes,
				&Config{
					TemplateEngine:              templatetest.MustEngine(t, tc.fqdnTemplate, "", "", tc.combineFQDNAndAnnotation),
					AnnotationFilter:            tc.annotationFilter,
					ServiceTypeFilter:           tc.serviceTypesFilter,
					Compatibility:               tc.compatibility,
					Namespace:                   tc.targetNamespace,
					ResolveLoadBalancerHostname: tc.resolveLoadBalancerHostname,
					IgnoreHostnameAnnotation:    tc.ignoreHostnameAnnotation,
					LabelFilter:                 sourceLabel,
				},
			)

			require.NoError(t, err)

			res, err := client.Endpoints(t.Context())
			if tc.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}

			// Validate returned endpoints against desired endpoints.
			testutils.ValidateEndpoints(t, res, tc.expected)
		})
	}
}

// testMultipleServicesEndpoints tests that multiple services generate correct merged endpoints
func testMultipleServicesEndpoints(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		title                    string
		targetNamespace          string
		annotationFilter         string
		svcNamespace             string
		svcName                  string
		svcType                  v1.ServiceType
		compatibility            string
		fqdnTemplate             string
		combineFQDNAndAnnotation bool
		ignoreHostnameAnnotation bool
		labels                   map[string]string
		clusterIP                string
		services                 map[string]map[string]string
		serviceTypesFilter       []string
		expected                 []*endpoint.Endpoint
		expectError              bool
	}{
		{
			"test service returns a correct end point",
			"",
			"",
			"testing",
			"foo",
			v1.ServiceTypeLoadBalancer,
			"",
			"",
			false,
			false,
			map[string]string{},
			"",
			map[string]map[string]string{
				"1.2.3.4": {annotations.HostnameKey: "foo.example.org"},
			},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo1.2.3.4"}},
			},
			false,
		},
		{
			"multiple services that share same DNS should be merged into one endpoint",
			"",
			"",
			"testing",
			"foo",
			v1.ServiceTypeLoadBalancer,
			"",
			"",
			false,
			false,
			map[string]string{},
			"",
			map[string]map[string]string{
				"1.2.3.4": {annotations.HostnameKey: "foo.example.org"},
				"1.2.3.5": {annotations.HostnameKey: "foo.example.org"},
				"1.2.3.6": {annotations.HostnameKey: "foo.example.org"},
			},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4", "1.2.3.5", "1.2.3.6"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo1.2.3.4"}},
			},
			false,
		},
		{
			"test that services with different hostnames do not get merged together",
			"",
			"",
			"testing",
			"foo",
			v1.ServiceTypeLoadBalancer,
			"",
			"",
			false,
			false,
			map[string]string{},
			"",
			map[string]map[string]string{
				"1.2.3.5":  {annotations.HostnameKey: "foo.example.org"},
				"10.1.1.3": {annotations.HostnameKey: "bar.example.org"},
				"10.1.1.1": {annotations.HostnameKey: "bar.example.org"},
				"1.2.3.4":  {annotations.HostnameKey: "foo.example.org"},
				"10.1.1.2": {annotations.HostnameKey: "bar.example.org"},
				"20.1.1.1": {annotations.HostnameKey: "foobar.example.org"},
				"1.2.3.6":  {annotations.HostnameKey: "foo.example.org"},
			},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4", "1.2.3.5", "1.2.3.6"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo1.2.3.4"}},
				{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.1.1", "10.1.1.2", "10.1.1.3"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo10.1.1.1"}},
				{DNSName: "foobar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"20.1.1.1"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo20.1.1.1"}},
			},
			false,
		},
		{
			"test that services with different set-identifier do not get merged together",
			"",
			"",
			"testing",
			"foo",
			v1.ServiceTypeLoadBalancer,
			"",
			"",
			false,
			false,
			map[string]string{},
			"",
			map[string]map[string]string{
				"1.2.3.5":  {annotations.HostnameKey: "foo.example.org", annotations.SetIdentifierKey: "a"},
				"10.1.1.3": {annotations.HostnameKey: "foo.example.org", annotations.SetIdentifierKey: "b"},
			},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.5"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo1.2.3.5"}, SetIdentifier: "a"},
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.1.1.3"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foo10.1.1.3"}, SetIdentifier: "b"},
			},
			false,
		},
		{
			"test that services with CNAME types do not get merged together",
			"",
			"",
			"testing",
			"foo",
			v1.ServiceTypeLoadBalancer,
			"",
			"",
			false,
			false,
			map[string]string{},
			"",
			map[string]map[string]string{
				"a.elb.com": {annotations.HostnameKey: "foo.example.org"},
				"b.elb.com": {annotations.HostnameKey: "foo.example.org"},
			},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"a.elb.com"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/fooa.elb.com"}},
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"b.elb.com"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foob.elb.com"}},
			},
			false,
		},
	} {

		t.Run(tc.title, func(t *testing.T) {
			t.Parallel()

			// Create a Kubernetes testing client
			kubernetes := fake.NewClientset()

			// Create services to test against
			for lb, ants := range tc.services {
				ingresses := []v1.LoadBalancerIngress{}
				ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb})

				service := &v1.Service{
					Spec: v1.ServiceSpec{
						Type:      tc.svcType,
						ClusterIP: tc.clusterIP,
					},
					ObjectMeta: metav1.ObjectMeta{
						Namespace:   tc.svcNamespace,
						Name:        tc.svcName + lb,
						Labels:      tc.labels,
						Annotations: ants,
					},
					Status: v1.ServiceStatus{
						LoadBalancer: v1.LoadBalancerStatus{
							Ingress: ingresses,
						},
					},
				}

				_, err := kubernetes.CoreV1().Services(service.Namespace).Create(t.Context(), service, metav1.CreateOptions{})
				require.NoError(t, err)
			}

			// Create our object under test and get the endpoints.
			client, err := NewServiceSource(t.Context(), kubernetes,
				&Config{
					TemplateEngine:           templatetest.MustEngine(t, tc.fqdnTemplate, "", "", tc.combineFQDNAndAnnotation),
					AnnotationFilter:         tc.annotationFilter,
					ServiceTypeFilter:        tc.serviceTypesFilter,
					Compatibility:            tc.compatibility,
					Namespace:                tc.targetNamespace,
					IgnoreHostnameAnnotation: tc.ignoreHostnameAnnotation,
					LabelFilter:              labels.Everything(),
				},
			)
			require.NoError(t, err)

			res, err := client.Endpoints(t.Context())
			if tc.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}

			// Validate returned endpoints against desired endpoints.
			testutils.ValidateEndpoints(t, res, tc.expected)
			// Test that endpoint resourceLabelKey matches desired endpoint
			sort.SliceStable(res, func(i, j int) bool {
				return strings.Compare(res[i].DNSName, res[j].DNSName) < 0
			})
			sort.SliceStable(tc.expected, func(i, j int) bool {
				return strings.Compare(tc.expected[i].DNSName, tc.expected[j].DNSName) < 0
			})

			for i := range res {
				if res[i].Labels[endpoint.ResourceLabelKey] != tc.expected[i].Labels[endpoint.ResourceLabelKey] {
					t.Errorf("expected %s, got %s", tc.expected[i].Labels[endpoint.ResourceLabelKey], res[i].Labels[endpoint.ResourceLabelKey])
				}
			}
		})
	}
}

// testServiceSourceEndpoints tests that various services generate the correct endpoints.
func TestClusterIpServices(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		title                    string
		targetNamespace          string
		annotationFilter         string
		svcNamespace             string
		svcName                  string
		svcType                  v1.ServiceType
		compatibility            string
		fqdnTemplate             string
		ignoreHostnameAnnotation bool
		labels                   map[string]string
		annotations              map[string]string
		clusterIP                string
		expected                 []*endpoint.Endpoint
		expectError              bool
		labelSelector            string
	}{
		{
			title:        "hostname annotated ClusterIp services return an endpoint with Cluster IP",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			clusterIP: "1.2.3.4",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
		},
		{
			title:        "target annotated ClusterIp services return an endpoint with the specified A",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TargetKey:   "4.3.2.1",
			},
			clusterIP: "1.2.3.4",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}},
			},
		},
		{
			title:        "target annotated ClusterIp services return an endpoint with the specified CNAME",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TargetKey:   "bar.example.org.",
			},
			clusterIP: "1.2.3.4",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org"}},
			},
		},
		{
			title:        "target annotated ClusterIp services return an endpoint with the specified AAAA",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TargetKey:   "2001:DB8::1",
			},
			clusterIP: "1.2.3.4",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
			},
		},
		{
			title:        "multiple target annotated ClusterIp services return an endpoint with the specified CNAMES",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TargetKey:   "bar.example.org.,baz.example.org.",
			},
			clusterIP: "1.2.3.4",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org", "baz.example.org"}},
			},
		},
		{
			title:        "multiple target annotated ClusterIp services return two endpoints with the specified CNAMES and AAAA",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TargetKey:   "bar.example.org.,baz.example.org.,2001:DB8::1",
			},
			clusterIP: "1.2.3.4",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org", "baz.example.org"}},
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
			},
		},
		{
			title:                    "hostname annotated ClusterIp services are ignored",
			svcNamespace:             "testing",
			svcName:                  "foo",
			svcType:                  v1.ServiceTypeClusterIP,
			ignoreHostnameAnnotation: true,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			clusterIP: "1.2.3.4",
			expected:  []*endpoint.Endpoint{},
		},
		{
			title:                    "hostname and target annotated ClusterIp services are ignored",
			svcNamespace:             "testing",
			svcName:                  "foo",
			svcType:                  v1.ServiceTypeClusterIP,
			ignoreHostnameAnnotation: true,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TargetKey:   "bar.example.org.",
			},
			clusterIP: "1.2.3.4",
			expected:  []*endpoint.Endpoint{},
		},
		{
			title:        "hostname and target annotated ClusterIp services return an endpoint with the specified CNAME",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TargetKey:   "bar.example.org.",
			},
			clusterIP: "1.2.3.4",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org"}},
			},
		},
		{
			title:        "non-annotated ClusterIp services with set fqdnTemplate return an endpoint with target IP",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			fqdnTemplate: "{{.Name}}.bar.example.com",
			clusterIP:    "4.5.6.7",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.5.6.7"}},
			},
		},
		{
			title:        "Headless services do not generate endpoints",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			clusterIP:    v1.ClusterIPNone,
			expected:     []*endpoint.Endpoint{},
		},
		{
			title:        "Headless services generate endpoints when target is specified",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TargetKey:   "bar.example.org.",
			},
			clusterIP: v1.ClusterIPNone,
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.org"}},
			},
		},
		{
			title:        "ClusterIP service with matching label generates an endpoint",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			fqdnTemplate: "{{.Name}}.bar.example.com",
			labels:       map[string]string{"app": "web-internal"},
			clusterIP:    "4.5.6.7",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.5.6.7"}},
			},
			labelSelector: "app=web-internal",
		},
		{
			title:        "ClusterIP service with matching label and target generates a CNAME endpoint",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			fqdnTemplate: "{{.Name}}.bar.example.com",
			labels:       map[string]string{"app": "web-internal"},
			annotations:  map[string]string{annotations.TargetKey: "bar.example.com."},
			clusterIP:    "4.5.6.7",
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.bar.example.com", RecordType: endpoint.RecordTypeCNAME, Targets: endpoint.Targets{"bar.example.com"}},
			},
			labelSelector: "app=web-internal",
		},
		{
			title:         "ClusterIP service without matching label generates an endpoint",
			svcNamespace:  "testing",
			svcName:       "foo",
			svcType:       v1.ServiceTypeClusterIP,
			fqdnTemplate:  "{{.Name}}.bar.example.com",
			labels:        map[string]string{"app": "web-internal"},
			clusterIP:     "4.5.6.7",
			expected:      []*endpoint.Endpoint{},
			labelSelector: "app=web-external",
		},
		{
			title:        "invalid hostname does not generate endpoints",
			svcNamespace: "testing",
			svcName:      "foo",
			svcType:      v1.ServiceTypeClusterIP,
			annotations: map[string]string{
				annotations.HostnameKey: "this-is-an-exceedingly-long-label-that-external-dns-should-reject.example.org.",
			},
			clusterIP: "1.2.3.4",
			expected:  []*endpoint.Endpoint{},
		},
	} {

		t.Run(tc.title, func(t *testing.T) {
			t.Parallel()

			// Create a Kubernetes testing client
			kubernetes := fake.NewClientset()

			// Create a service to test against
			service := &v1.Service{
				Spec: v1.ServiceSpec{
					Type:      tc.svcType,
					ClusterIP: tc.clusterIP,
				},
				ObjectMeta: metav1.ObjectMeta{
					Namespace:   tc.svcNamespace,
					Name:        tc.svcName,
					Labels:      tc.labels,
					Annotations: tc.annotations,
				},
			}

			_, err := kubernetes.CoreV1().Services(service.Namespace).Create(t.Context(), service, metav1.CreateOptions{})
			require.NoError(t, err)

			labelSelector := labels.Everything()
			if tc.labelSelector != "" {
				labelSelector, err = labels.Parse(tc.labelSelector)
				require.NoError(t, err)
			}
			// Create our object under test and get the endpoints.
			client, _ := NewServiceSource(t.Context(), kubernetes,
				&Config{
					TemplateEngine:           templatetest.MustEngine(t, tc.fqdnTemplate, "", "", false),
					AnnotationFilter:         tc.annotationFilter,
					Compatibility:            tc.compatibility,
					Namespace:                tc.targetNamespace,
					PublishInternal:          true,
					IgnoreHostnameAnnotation: tc.ignoreHostnameAnnotation,
					LabelFilter:              labelSelector,
				},
			)
			require.NoError(t, err)

			endpoints, err := client.Endpoints(t.Context())
			if tc.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}

			// Validate returned endpoints against desired endpoints.
			testutils.ValidateEndpoints(t, endpoints, tc.expected)
		})
	}
}

// testNodePortServices tests that various services generate the correct endpoints.
func TestServiceSourceNodePortServices(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		title                    string
		targetNamespace          string
		annotationFilter         string
		svcNamespace             string
		svcName                  string
		svcType                  v1.ServiceType
		svcTrafficPolicy         v1.ServiceExternalTrafficPolicyType
		compatibility            string
		fqdnTemplate             string
		ignoreHostnameAnnotation bool
		exposeInternalIPv6       bool
		ignoreUnscheduledNodes   bool
		labels                   map[string]string
		annotations              map[string]string
		lbs                      []string
		expected                 []*endpoint.Endpoint
		expectError              bool
		nodes                    []*v1.Node
		podNames                 []string
		nodeIndex                []int
		phases                   []v1.PodPhase
		conditions               []v1.PodCondition
		labelSelector            labels.Selector
		deletionTimestamp        []*metav1.Time
	}{
		{
			title:            "annotated NodePort services return an endpoint with IP addresses of the cluster's nodes",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
					},
				},
			}},
		},
		{
			title:                    "hostname annotated NodePort services are ignored",
			svcNamespace:             "testing",
			svcName:                  "foo",
			svcType:                  v1.ServiceTypeNodePort,
			svcTrafficPolicy:         v1.ServiceExternalTrafficPolicyTypeCluster,
			ignoreHostnameAnnotation: true,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
					},
				},
			}},
			expected: []*endpoint.Endpoint{},
		},
		{
			title:            "non-annotated NodePort services with set fqdnTemplate return an endpoint with target IP",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			fqdnTemplate:     "{{.Name}}.bar.example.com",
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
				{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
					},
				},
			}},
		},
		{
			title:            "annotated NodePort services return an endpoint with IP addresses of the private cluster's nodes",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}},
		},
		{
			title:            "annotated NodePort services with ExternalTrafficPolicy=Local return an endpoint with IP addresses of the cluster's nodes where pods is running only",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
					},
				},
			}},
			podNames:          []string{"pod-0"},
			nodeIndex:         []int{1},
			phases:            []v1.PodPhase{v1.PodRunning},
			conditions:        []v1.PodCondition{{Type: v1.PodReady, Status: v1.ConditionFalse}},
			deletionTimestamp: []*metav1.Time{{}},
		},
		{
			title:            "annotated NodePort services with ExternalTrafficPolicy=Local and multiple pods on a single node return an endpoint with unique IP addresses of the cluster's nodes where pods is running only",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
					},
				},
			}},
			podNames:  []string{"pod-0", "pod-1"},
			nodeIndex: []int{1, 1},
			phases:    []v1.PodPhase{v1.PodRunning, v1.PodRunning},
			conditions: []v1.PodCondition{
				{Type: v1.PodReady, Status: v1.ConditionFalse},
				{Type: v1.PodReady, Status: v1.ConditionFalse},
			},
			deletionTimestamp: []*metav1.Time{{}, {}},
		},
		{
			title:            "annotated NodePort services with ExternalTrafficPolicy=Local return pods in Ready & Running state",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1"}, RecordType: endpoint.RecordTypeA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
					},
				},
			}},
			podNames:  []string{"pod-0", "pod-1"},
			nodeIndex: []int{0, 1},
			phases:    []v1.PodPhase{v1.PodRunning, v1.PodRunning},
			conditions: []v1.PodCondition{
				{Type: v1.PodReady, Status: v1.ConditionTrue},
				{Type: v1.PodReady, Status: v1.ConditionFalse},
			},
			deletionTimestamp: []*metav1.Time{{}, {}},
		},
		{
			title:            "annotated NodePort services with ExternalTrafficPolicy=Local return pods in Ready & Running state & not in Terminating",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1"}, RecordType: endpoint.RecordTypeA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node3",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.3"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.3"},
					},
				},
			}},
			podNames:  []string{"pod-0", "pod-1", "pod-2"},
			nodeIndex: []int{0, 1, 2},
			phases:    []v1.PodPhase{v1.PodRunning, v1.PodRunning, v1.PodRunning},
			conditions: []v1.PodCondition{
				{Type: v1.PodReady, Status: v1.ConditionTrue},
				{Type: v1.PodReady, Status: v1.ConditionFalse},
				{Type: v1.PodReady, Status: v1.ConditionTrue},
			},
			deletionTimestamp: []*metav1.Time{nil, nil, {}},
		},
		{
			title:            "access=private annotation NodePort services return an endpoint with private IP addresses of the cluster's nodes",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.AccessKey:   "private",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}},
		},
		{
			title:            "access=public annotation NodePort services return an endpoint with external IP addresses of the cluster's nodes if exposeInternalIPv6 is unset",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.AccessKey:   "public",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
					},
				},
			}},
		},
		{
			title:            "access=public annotation NodePort services return an endpoint with public IP addresses of the cluster's nodes if exposeInternalIPv6 is set to true",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			labels:           map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.AccessKey:   "public",
			},
			exposeInternalIPv6: true,
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2", "2001:DB8::3", "2001:DB8::4"}, RecordType: endpoint.RecordTypeAAAA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
					},
				},
			}},
		},
		{
			title:            "node port services annotated DNS Controller annotations return an endpoint where all targets has the node role",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			compatibility:    "kops-dns-controller",
			labels:           map[string]string{},
			annotations: map[string]string{
				kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1"}},
				{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
				{DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1"}},
				{DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
					Labels: map[string]string{
						"node-role.kubernetes.io/node": "",
					},
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
					Labels: map[string]string{
						"node-role.kubernetes.io/control-plane": "",
					},
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}},
		},
		{
			title:            "node port services annotated with internal DNS Controller annotations return an endpoint in compatibility mode",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			compatibility:    "kops-dns-controller",
			annotations: map[string]string{
				kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}},
				{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}},
				{DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}},
				{DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
					Labels: map[string]string{
						"node-role.kubernetes.io/node": "",
					},
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
					Labels: map[string]string{
						"node-role.kubernetes.io/node": "",
					},
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}},
		},
		{
			title:              "node port services annotated with external DNS Controller annotations return an endpoint in compatibility mode with exposeInternalIPv6 flag set",
			svcNamespace:       "testing",
			svcName:            "foo",
			svcType:            v1.ServiceTypeNodePort,
			svcTrafficPolicy:   v1.ServiceExternalTrafficPolicyTypeCluster,
			compatibility:      "kops-dns-controller",
			exposeInternalIPv6: true,
			annotations: map[string]string{
				kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}},
				{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}},
				{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}},
				{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
					Labels: map[string]string{
						"node-role.kubernetes.io/node": "",
					},
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
					Labels: map[string]string{
						"node-role.kubernetes.io/node": "",
					},
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}},
		},
		{
			title:            "node port services annotated with both kops dns controller annotations return an empty set of addons",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			compatibility:    "kops-dns-controller",
			labels:           map[string]string{},
			annotations: map[string]string{
				kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org",
				kopsDNSControllerHostnameAnnotationKey:         "foo.example.org., bar.example.org",
			},
			expected: []*endpoint.Endpoint{},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
					Labels: map[string]string{
						"node-role.kubernetes.io/node": "",
					},
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
					Labels: map[string]string{
						"node-role.kubernetes.io/node": "",
					},
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}},
		},
		{
			title:                  "NodePort services ignore unschedulable node",
			ignoreUnscheduledNodes: true,
			svcNamespace:           "testing",
			svcName:                "foo",
			svcType:                v1.ServiceTypeNodePort,
			svcTrafficPolicy:       v1.ServiceExternalTrafficPolicyTypeCluster,
			labels:                 map[string]string{},
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.AccessKey:   "public",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
				},
				Spec: v1.NodeSpec{
					Unschedulable: true,
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{
					Name: "node2",
				},
				Spec: v1.NodeSpec{
					Unschedulable: false,
				},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
					},
				},
			}},
		},
		{
			title:            "annotated NodePort service with TTL produces SRV endpoint with TTL",
			svcNamespace:     "testing",
			svcName:          "foo",
			svcType:          v1.ServiceTypeNodePort,
			svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster,
			annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
				annotations.TtlKey:      "300",
			},
			expected: []*endpoint.Endpoint{
				{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org."}, RecordType: endpoint.RecordTypeSRV, RecordTTL: 300},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA, RecordTTL: 300},
				{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::3"}, RecordType: endpoint.RecordTypeAAAA, RecordTTL: 300},
			},
			nodes: []*v1.Node{{
				ObjectMeta: metav1.ObjectMeta{Name: "node1"},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::1"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
					},
				},
			}, {
				ObjectMeta: metav1.ObjectMeta{Name: "node2"},
				Status: v1.NodeStatus{
					Addresses: []v1.NodeAddress{
						{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
						{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
						{Type: v1.NodeExternalIP, Address: "2001:DB8::3"},
						{Type: v1.NodeInternalIP, Address: "2001:DB8::4"},
					},
				},
			}},
		},
	} {

		t.Run(tc.title, func(t *testing.T) {
			t.Parallel()

			// Create a Kubernetes testing client
			kubernetes := fake.NewClientset()

			// Create the nodes
			for _, node := range tc.nodes {
				if _, err := kubernetes.CoreV1().Nodes().Create(t.Context(), node, metav1.CreateOptions{}); err != nil {
					t.Fatal(err)
				}
			}

			// Create  pods
			for i, podname := range tc.podNames {
				pod := &v1.Pod{
					Spec: v1.PodSpec{
						Containers: []v1.Container{},
						Hostname:   podname,
						NodeName:   tc.nodes[tc.nodeIndex[i]].Name,
					},
					ObjectMeta: metav1.ObjectMeta{
						Namespace:         tc.svcNamespace,
						Name:              podname,
						Labels:            tc.labels,
						Annotations:       tc.annotations,
						DeletionTimestamp: tc.deletionTimestamp[i],
					},
					Status: v1.PodStatus{
						Phase:      tc.phases[i],
						Conditions: []v1.PodCondition{tc.conditions[i]},
					},
				}

				_, err := kubernetes.CoreV1().Pods(tc.svcNamespace).Create(t.Context(), pod, metav1.CreateOptions{})
				require.NoError(t, err)
			}

			// Create a service to test against
			service := &v1.Service{
				Spec: v1.ServiceSpec{
					Type:                  tc.svcType,
					ExternalTrafficPolicy: tc.svcTrafficPolicy,
					Ports: []v1.ServicePort{
						{
							NodePort: 30192,
						},
					},
				},
				ObjectMeta: metav1.ObjectMeta{
					Namespace:   tc.svcNamespace,
					Name:        tc.svcName,
					Labels:      tc.labels,
					Annotations: tc.annotations,
				},
			}

			_, err := kubernetes.CoreV1().Services(service.Namespace).Create(t.Context(), service, metav1.CreateOptions{})
			require.NoError(t, err)

			// Create our object under test and get the endpoints.
			client, _ := NewServiceSource(t.Context(), kubernetes,
				&Config{
					TemplateEngine:           templatetest.MustEngine(t, tc.fqdnTemplate, "", "", false),
					AnnotationFilter:         tc.annotationFilter,
					Compatibility:            tc.compatibility,
					Namespace:                tc.targetNamespace,
					IgnoreHostnameAnnotation: tc.ignoreHostnameAnnotation,
					ExposeInternalIPv6:       tc.exposeInternalIPv6,
					ExcludeUnschedulable:     tc.ignoreUnscheduledNodes,
					LabelFilter:              labels.Everything(),
				},
			)
			require.NoError(t, err)

			endpoints, err := client.Endpoints(t.Context())
			if tc.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}

			// Validate returned endpoints against desired endpoints.
			testutils.ValidateEndpoints(t, endpoints, tc.expected)
		})
	}
}

// TestHeadlessServices tests that headless services generate the correct endpoints.
func TestHeadlessServices(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		title                    string
		targetNamespace          string
		svcNamespace             string
		svcName                  string
		svcType                  v1.ServiceType
		compatibility            string
		fqdnTemplate             string
		ignoreHostnameAnnotation bool
		exposeInternalIPv6       bool
		labels                   map[string]string
		svcAnnotations           map[string]string
		podAnnotations           map[string]string
		clusterIP                string
		podIPs                   []string
		hostIPs                  []string
		selector                 map[string]string
		lbs                      []string
		podnames                 []string
		hostnames                []string
		podsReady                []bool
		publishNotReadyAddresses bool
		nodes                    []v1.Node
		serviceTypesFilter       []string
		expected                 []*endpoint.Endpoint
		expectError              bool
	}{
		{
			"annotated Headless services return IPv4 endpoints for each selected Pod",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			[]string{"", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]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"}},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv6 endpoints for each selected Pod",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"2001:db8::1", "2001:db8::2"},
			[]string{"", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]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"}},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
			},
			false,
		},
		{
			"hostname annotated Headless services are ignored",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			true,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			[]string{"", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]bool{true, true},
			false,
			[]v1.Node{},
			[]string{},
			[]*endpoint.Endpoint{},
			false,
		},
		{
			"annotated Headless services return IPv4 endpoints with TTL for each selected Pod",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
				annotations.TtlKey:      "1",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			[]string{"", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]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)},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}, RecordTTL: endpoint.TTL(1)},
			},
			false,
		},
		{
			"annotated Headless services return IPv6 endpoints with TTL for each selected Pod",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
				annotations.TtlKey:      "1",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"2001:db8::1", "2001:db8::2"},
			[]string{"", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]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)},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
			},
			false,
		},
		{
			"annotated Headless services return endpoints for each selected Pod, which are in running state",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			[]string{"", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]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"}},
			},
			false,
		},
		{
			"annotated Headless services return endpoints for all Pod if publishNotReadyAddresses is set",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			[]string{"", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]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"}},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
			},
			false,
		},
		{
			"annotated Headless services return endpoints for pods missing hostname",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			[]string{"", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"", ""},
			[]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"}},
			},
			false,
		},
		{
			"annotated Headless services return only a unique set of IPv4 targets",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.1", "1.1.1.2"},
			[]string{"", "", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1", "foo-3"},
			[]string{"", "", ""},
			[]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"}},
			},
			false,
		},
		{
			"annotated Headless services return only a unique set of IPv6 targets",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"2001:db8::1", "2001:db8::1", "2001:db8::2"},
			[]string{"", "", ""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1", "foo-3"},
			[]string{"", "", ""},
			[]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"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv4 targets from pod annotation",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{
				annotations.TargetKey: "1.2.3.4",
			},
			v1.ClusterIPNone,
			[]string{"1.1.1.1"},
			[]string{""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]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"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv6 targets from pod annotation",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			map[string]string{
				annotations.TargetKey: "2001:db8::4",
			},
			v1.ClusterIPNone,
			[]string{"2001:db8::1"},
			[]string{""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]bool{true, true, true},
			false,
			[]v1.Node{},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv4 targets from node external IP if endpoints-type annotation is set",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey:      "service.example.org",
				annotations.EndpointsTypeKey: EndpointsTypeNodeExternalIP,
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1"},
			[]string{""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]bool{true, true, true},
			false,
			[]v1.Node{
				{
					Status: v1.NodeStatus{
						Addresses: []v1.NodeAddress{
							{
								Type:    v1.NodeExternalIP,
								Address: "1.2.3.4",
							},
						},
					},
				},
			},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
			false,
		},
		{
			"annotated Headless services return only external IPv6 targets from node IP if endpoints-type annotation is set and exposeInternalIPv6 flag is unset",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey:      "service.example.org",
				annotations.EndpointsTypeKey: EndpointsTypeNodeExternalIP,
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"2001:db8::1"},
			[]string{""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]bool{true, true, true},
			false,
			[]v1.Node{
				{
					Status: v1.NodeStatus{
						Addresses: []v1.NodeAddress{
							{
								Type:    v1.NodeInternalIP,
								Address: "2001:db8::4",
							},
							{
								Type:    v1.NodeExternalIP,
								Address: "2001:db8::5",
							},
						},
					},
				},
			},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::5"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv6 targets from node external IP if endpoints-type annotation is set and exposeInternalIPv6 flag set",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			true,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey:      "service.example.org",
				annotations.EndpointsTypeKey: EndpointsTypeNodeExternalIP,
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"2001:db8::1"},
			[]string{""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]bool{true, true, true},
			false,
			[]v1.Node{
				{
					Status: v1.NodeStatus{
						Addresses: []v1.NodeAddress{
							{
								Type:    v1.NodeInternalIP,
								Address: "2001:db8::4",
							},
						},
					},
				},
			},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
			},
			false,
		},
		{
			"annotated Headless services return dual-stack targets from node external IP if endpoints-type annotation is set and exposeInternalIPv6 flag set",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			true,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey:      "service.example.org",
				annotations.EndpointsTypeKey: EndpointsTypeNodeExternalIP,
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1"},
			[]string{""},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]bool{true, true, true},
			false,
			[]v1.Node{
				{
					Status: v1.NodeStatus{
						Addresses: []v1.NodeAddress{
							{
								Type:    v1.NodeExternalIP,
								Address: "1.2.3.4",
							},
							{
								Type:    v1.NodeInternalIP,
								Address: "2001:db8::4",
							},
						},
					},
				},
			},
			[]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"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv4 targets from hostIP if endpoints-type annotation is set",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey:      "service.example.org",
				annotations.EndpointsTypeKey: EndpointsTypeHostIP,
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"1.1.1.1"},
			[]string{"1.2.3.4"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]bool{true, true, true},
			false,
			[]v1.Node{},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv6 targets from hostIP if endpoints-type annotation is set",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey:      "service.example.org",
				annotations.EndpointsTypeKey: EndpointsTypeHostIP,
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"2001:db8::1"},
			[]string{"2001:db8::4"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]bool{true, true, true},
			false,
			[]v1.Node{},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::4"}},
			},
			false,
		},
		{
			"headless service with endpoints-type annotation is outside of serviceTypeFilter scope",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey:      "service.example.org",
				annotations.EndpointsTypeKey: EndpointsTypeNodeExternalIP,
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"2001:db8::1"},
			[]string{"2001:db8::4"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]bool{true, true, true},
			false,
			[]v1.Node{
				{
					Status: v1.NodeStatus{
						Addresses: []v1.NodeAddress{
							{
								Type:    v1.NodeExternalIP,
								Address: "1.2.3.4",
							},
							{
								Type:    v1.NodeInternalIP,
								Address: "10.0.10.12",
							},
						},
					},
				},
			},
			[]string{string(v1.ServiceTypeClusterIP)},
			[]*endpoint.Endpoint{},
			false,
		},
		{
			"headless service with endpoints-type annotation is in the scope of serviceTypeFilter",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey:      "service.example.org",
				annotations.EndpointsTypeKey: EndpointsTypeNodeExternalIP,
			},
			map[string]string{},
			v1.ClusterIPNone,
			[]string{"2001:db8::1"},
			[]string{"1.2.3.4"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo"},
			[]string{"", "", ""},
			[]bool{true, true, true},
			false,
			[]v1.Node{
				{
					Status: v1.NodeStatus{
						Addresses: []v1.NodeAddress{
							{
								Type:    v1.NodeExternalIP,
								Address: "1.2.3.4",
							},
							{
								Type:    v1.NodeInternalIP,
								Address: "10.0.10.12",
							},
						},
					},
				},
			},
			[]string{string(v1.ServiceTypeClusterIP), string(v1.ServiceTypeNodePort)},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
			},
			false,
		},
	} {

		t.Run(tc.title, func(t *testing.T) {
			t.Parallel()

			// Create a Kubernetes testing client
			kubernetes := fake.NewClientset()

			service := &v1.Service{
				Spec: v1.ServiceSpec{
					Type:                     tc.svcType,
					ClusterIP:                tc.clusterIP,
					Selector:                 tc.selector,
					PublishNotReadyAddresses: tc.publishNotReadyAddresses,
				},
				ObjectMeta: metav1.ObjectMeta{
					Namespace:   tc.svcNamespace,
					Name:        tc.svcName,
					Labels:      tc.labels,
					Annotations: tc.svcAnnotations,
				},
				Status: v1.ServiceStatus{},
			}
			_, err := kubernetes.CoreV1().Services(service.Namespace).Create(t.Context(), service, metav1.CreateOptions{})
			require.NoError(t, err)

			var endpointSliceEndpoints []discoveryv1.Endpoint
			for i, podName := range tc.podnames {
				pod := &v1.Pod{
					Spec: v1.PodSpec{
						Containers: []v1.Container{},
						Hostname:   tc.hostnames[i],
					},
					ObjectMeta: metav1.ObjectMeta{
						Namespace:   tc.svcNamespace,
						Name:        podName,
						Labels:      tc.labels,
						Annotations: tc.podAnnotations,
					},
					Status: v1.PodStatus{
						PodIP:  tc.podIPs[i],
						HostIP: tc.hostIPs[i],
					},
				}

				_, err = kubernetes.CoreV1().Pods(tc.svcNamespace).Create(t.Context(), pod, metav1.CreateOptions{})
				require.NoError(t, err)

				ep := discoveryv1.Endpoint{
					Addresses: []string{tc.podIPs[i]},
					TargetRef: &v1.ObjectReference{
						APIVersion: "",
						Kind:       "Pod",
						Name:       podName,
					},
					Conditions: discoveryv1.EndpointConditions{
						Ready: &tc.podsReady[i],
					},
				}
				endpointSliceEndpoints = append(endpointSliceEndpoints, ep)
			}
			endpointSliceLabels := maps.Clone(tc.labels)
			endpointSliceLabels[discoveryv1.LabelServiceName] = tc.svcName
			endpointSlice := &discoveryv1.EndpointSlice{
				ObjectMeta: metav1.ObjectMeta{
					Namespace: tc.svcNamespace,
					Name:      tc.svcName,
					Labels:    endpointSliceLabels,
				},
				AddressType: discoveryv1.AddressTypeIPv4,
				Endpoints:   endpointSliceEndpoints,
			}
			_, err = kubernetes.DiscoveryV1().EndpointSlices(tc.svcNamespace).Create(t.Context(), endpointSlice, metav1.CreateOptions{})
			require.NoError(t, err)
			for _, node := range tc.nodes {
				_, err = kubernetes.CoreV1().Nodes().Create(t.Context(), &node, metav1.CreateOptions{})
				require.NoError(t, err)
			}

			// Create our object under test and get the endpoints.
			client, _ := NewServiceSource(t.Context(), kubernetes,
				&Config{
					TemplateEngine:           templatetest.MustEngine(t, tc.fqdnTemplate, "", "", false),
					ServiceTypeFilter:        tc.serviceTypesFilter,
					Compatibility:            tc.compatibility,
					Namespace:                tc.targetNamespace,
					IgnoreHostnameAnnotation: tc.ignoreHostnameAnnotation,
					ExposeInternalIPv6:       tc.exposeInternalIPv6,
					LabelFilter:              labels.Everything(),
				},
			)
			require.NoError(t, err)

			endpoints, err := client.Endpoints(t.Context())
			if tc.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}

			// Validate returned endpoints against desired endpoints.
			testutils.ValidateEndpoints(t, endpoints, tc.expected)
		})
	}
}

func TestMultipleServicesPointingToSameLoadBalancer(t *testing.T) {
	kubernetes := fake.NewClientset()

	services := []*v1.Service{
		{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "istio-ingressgateway",
				Namespace: "default",
				Labels: map[string]string{
					"app":   "istio-ingressgateway",
					"istio": "ingressgateway",
				},
				Annotations: map[string]string{
					"external-dns.alpha.kubernetes.io/hostname": "example.org",
				},
			},
			Spec: v1.ServiceSpec{
				Type:                  v1.ServiceTypeLoadBalancer,
				ClusterIP:             "10.118.223.3",
				ClusterIPs:            []string{"10.118.223.3"},
				ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyCluster,
				IPFamilies:            []v1.IPFamily{v1.IPv4Protocol},
				IPFamilyPolicy:        new(v1.IPFamilyPolicySingleStack),
				Ports: []v1.ServicePort{
					{
						Name:       "http2",
						Port:       80,
						Protocol:   v1.ProtocolTCP,
						TargetPort: intstr.FromInt32(8080),
						NodePort:   30127,
					},
				},
				Selector: map[string]string{
					"app":   "istio-ingressgateway",
					"istio": "ingressgateway",
				},
				SessionAffinity: v1.ServiceAffinityNone,
			},
			Status: v1.ServiceStatus{
				LoadBalancer: v1.LoadBalancerStatus{
					Ingress: []v1.LoadBalancerIngress{
						{
							IP:     "34.66.66.77",
							IPMode: new(v1.LoadBalancerIPModeVIP),
						},
					},
				},
			},
		},
		{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "istio-ingressgatewayudp",
				Namespace: "default",
				Labels: map[string]string{
					"app":   "istio-ingressgatewayudp",
					"istio": "ingressgateway",
				},
				Annotations: map[string]string{
					"external-dns.alpha.kubernetes.io/hostname": "example.org",
				},
			},
			Spec: v1.ServiceSpec{
				Type:                  v1.ServiceTypeLoadBalancer,
				ClusterIP:             "10.118.220.130",
				ClusterIPs:            []string{"10.118.220.130"},
				ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyCluster,
				IPFamilies:            []v1.IPFamily{v1.IPv4Protocol},
				IPFamilyPolicy:        new(v1.IPFamilyPolicySingleStack),
				Ports: []v1.ServicePort{
					{
						Name:       "upd-dns",
						Port:       53,
						Protocol:   v1.ProtocolUDP,
						TargetPort: intstr.FromInt32(5353),
						NodePort:   30873,
					},
				},
				Selector: map[string]string{
					"app":   "istio-ingressgatewayudp",
					"istio": "ingressgateway",
				},
				SessionAffinity: v1.ServiceAffinityNone,
			},
			Status: v1.ServiceStatus{
				LoadBalancer: v1.LoadBalancerStatus{
					Ingress: []v1.LoadBalancerIngress{
						{
							IP:     "34.66.66.77",
							IPMode: new(v1.LoadBalancerIPModeVIP),
						},
					},
				},
			},
		},
	}

	assert.NotNil(t, services)

	for _, svc := range services {
		_, err := kubernetes.CoreV1().Services(svc.Namespace).Create(t.Context(), svc, metav1.CreateOptions{})
		require.NoError(t, err)
	}

	src, err := NewServiceSource(t.Context(), kubernetes,
		&Config{
			Namespace:            v1.NamespaceAll,
			ExcludeUnschedulable: true,
			LabelFilter:          labels.Everything(),
		},
	)
	require.NoError(t, err)
	assert.NotNil(t, src)

	got, err := src.Endpoints(t.Context())
	require.NoError(t, err)

	testutils.ValidateEndpoints(t, got, []*endpoint.Endpoint{
		endpoint.NewEndpoint("example.org", endpoint.RecordTypeA, "34.66.66.77").WithLabel(endpoint.ResourceLabelKey, "service/default/istio-ingressgateway"),
	})
}

func TestMultipleHeadlessServicesPointingToPodsOnTheSameNode(t *testing.T) {
	kubernetes := fake.NewClientset()

	headless := []*v1.Service{
		{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "kafka",
				Namespace: "default",
				Labels: map[string]string{
					"app": "kafka",
				},
				Annotations: map[string]string{
					annotations.HostnameKey: "example.org",
				},
			},
			Spec: v1.ServiceSpec{
				Type:                  v1.ServiceTypeClusterIP,
				ClusterIP:             v1.ClusterIPNone,
				ClusterIPs:            []string{v1.ClusterIPNone},
				InternalTrafficPolicy: new(v1.ServiceInternalTrafficPolicyCluster),
				IPFamilies:            []v1.IPFamily{v1.IPv4Protocol},
				IPFamilyPolicy:        new(v1.IPFamilyPolicySingleStack),
				Ports: []v1.ServicePort{
					{
						Name:       "web",
						Port:       80,
						Protocol:   v1.ProtocolTCP,
						TargetPort: intstr.FromInt32(80),
					},
				},
				Selector: map[string]string{
					"app": "kafka",
				},
				SessionAffinity: v1.ServiceAffinityNone,
			},
			Status: v1.ServiceStatus{
				LoadBalancer: v1.LoadBalancerStatus{},
			},
		},
		{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "kafka-2",
				Namespace: "default",
				Labels: map[string]string{
					"app": "kafka",
				},
				Annotations: map[string]string{
					annotations.HostnameKey: "example.org",
				},
			},
			Spec: v1.ServiceSpec{
				Type:                  v1.ServiceTypeClusterIP,
				ClusterIP:             v1.ClusterIPNone,
				ClusterIPs:            []string{v1.ClusterIPNone},
				InternalTrafficPolicy: new(v1.ServiceInternalTrafficPolicyCluster),
				IPFamilies:            []v1.IPFamily{v1.IPv4Protocol},
				IPFamilyPolicy:        new(v1.IPFamilyPolicySingleStack),
				Ports: []v1.ServicePort{
					{
						Name:       "web",
						Port:       80,
						Protocol:   v1.ProtocolTCP,
						TargetPort: intstr.FromInt32(80),
					},
				},
				Selector: map[string]string{
					"app": "kafka",
				},
				SessionAffinity: v1.ServiceAffinityNone,
			},
			Status: v1.ServiceStatus{
				LoadBalancer: v1.LoadBalancerStatus{},
			},
		},
	}

	assert.NotNil(t, headless)

	pods := []*v1.Pod{
		{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "kafka-0",
				Namespace: "default",
				Labels: map[string]string{
					"app":                                 "kafka",
					appsv1.PodIndexLabel:                  "0",
					appsv1.ControllerRevisionHashLabelKey: "kafka-b8d79cdb6",
					appsv1.StatefulSetPodNameLabel:        "kafka-0",
				},
				OwnerReferences: []metav1.OwnerReference{
					{
						APIVersion: "apps/v1",
						Kind:       "StatefulSet",
						Name:       "kafka",
					},
				},
			},
			Spec: v1.PodSpec{
				Hostname:  "kafka-0",
				Subdomain: "kafka",
				NodeName:  "local-dev-worker",
				Containers: []v1.Container{
					{
						Name: "nginx",
						Ports: []v1.ContainerPort{
							{Name: "web", ContainerPort: 80, Protocol: v1.ProtocolTCP},
						},
					},
				},
			},
			Status: v1.PodStatus{
				Phase:   v1.PodRunning,
				PodIP:   "10.244.1.2",
				PodIPs:  []v1.PodIP{{IP: "10.244.1.2"}},
				HostIP:  "172.18.0.2",
				HostIPs: []v1.HostIP{{IP: "172.18.0.2"}},
			},
		},
		{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "kafka-1",
				Namespace: "default",
				Labels: map[string]string{
					"app":                                 "kafka",
					appsv1.PodIndexLabel:                  "1",
					appsv1.ControllerRevisionHashLabelKey: "kafka-b8d79cdb6",
					appsv1.StatefulSetPodNameLabel:        "kafka-1",
				},
				OwnerReferences: []metav1.OwnerReference{
					{
						APIVersion: "apps/v1",
						Kind:       "StatefulSet",
						Name:       "kafka",
					},
				},
			},
			Spec: v1.PodSpec{
				Hostname:  "kafka-1",
				Subdomain: "kafka",
				NodeName:  "local-dev-worker",
				Containers: []v1.Container{
					{
						Name: "nginx",
						Ports: []v1.ContainerPort{
							{Name: "web", ContainerPort: 80, Protocol: v1.ProtocolTCP},
						},
					},
				},
			},
			Status: v1.PodStatus{
				Phase:   v1.PodRunning,
				PodIP:   "10.244.1.3",
				PodIPs:  []v1.PodIP{{IP: "10.244.1.3"}},
				HostIP:  "172.18.0.2",
				HostIPs: []v1.HostIP{{IP: "172.18.0.2"}},
			},
		},
		{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "kafka-2",
				Namespace: "default",
				Labels: map[string]string{
					"app":                                 "kafka",
					appsv1.PodIndexLabel:                  "2",
					appsv1.ControllerRevisionHashLabelKey: "kafka-b8d79cdb6",
					appsv1.StatefulSetPodNameLabel:        "kafka-2",
				},
				OwnerReferences: []metav1.OwnerReference{
					{
						APIVersion: "apps/v1",
						Kind:       "StatefulSet",
						Name:       "kafka",
					},
				},
			},
			Spec: v1.PodSpec{
				Hostname:  "kafka-2",
				Subdomain: "kafka",
				NodeName:  "local-dev-worker",
				Containers: []v1.Container{
					{
						Name: "nginx",
						Ports: []v1.ContainerPort{
							{Name: "web", ContainerPort: 80, Protocol: v1.ProtocolTCP},
						},
					},
				},
			},
			Status: v1.PodStatus{
				Phase:   v1.PodRunning,
				PodIP:   "10.244.1.4",
				PodIPs:  []v1.PodIP{{IP: "10.244.1.4"}},
				HostIP:  "172.18.0.2",
				HostIPs: []v1.HostIP{{IP: "172.18.0.2"}},
			},
		},
	}
	assert.Len(t, pods, 3)

	endpoints := []*discoveryv1.EndpointSlice{
		{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "kafka-xhrc9",
				Namespace: "default",
				Labels: map[string]string{
					"app":                        "kafka",
					discoveryv1.LabelServiceName: "kafka",
					discoveryv1.LabelManagedBy:   "endpointslice-controller.k8s.io",
					v1.IsHeadlessService:         "",
				},
			},
			AddressType: discoveryv1.AddressTypeIPv4,
			Endpoints: []discoveryv1.Endpoint{
				{
					Addresses: []string{"10.244.1.2"},
					Hostname:  new("kafka-0"),
					NodeName:  new("local-dev-worker"),
					TargetRef: &v1.ObjectReference{
						Kind:      "Pod",
						Name:      "kafka-0",
						Namespace: "default",
					},
					Conditions: discoveryv1.EndpointConditions{
						Ready:       new(true),
						Serving:     new(true),
						Terminating: new(false),
					},
				},
				{
					Addresses: []string{"10.244.1.3"},
					Hostname:  new("kafka-1"),
					NodeName:  new("local-dev-worker"),
					TargetRef: &v1.ObjectReference{
						Kind:      "Pod",
						Name:      "kafka-1",
						Namespace: "default",
					},
					Conditions: discoveryv1.EndpointConditions{
						Ready:       new(true),
						Serving:     new(true),
						Terminating: new(false),
					},
				},
				{
					Addresses: []string{"10.244.1.4"},
					Hostname:  new("kafka-2"),
					NodeName:  new("local-dev-worker"),
					TargetRef: &v1.ObjectReference{
						Kind:      "Pod",
						Name:      "kafka-2",
						Namespace: "default",
					},
					Conditions: discoveryv1.EndpointConditions{
						Ready:       new(true),
						Serving:     new(true),
						Terminating: new(false),
					},
				},
			},
		},
		{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "kafka-2-svwsg",
				Namespace: "default",
				Labels: map[string]string{
					"app":                        "kafka",
					discoveryv1.LabelServiceName: "kafka-2",
					discoveryv1.LabelManagedBy:   "endpointslice-controller.k8s.io",
					v1.IsHeadlessService:         "",
				},
			},
			AddressType: discoveryv1.AddressTypeIPv4,
			Endpoints: []discoveryv1.Endpoint{
				{
					Addresses: []string{"10.244.1.2"},
					Hostname:  new("kafka-0"),
					NodeName:  new("local-dev-worker"),
					TargetRef: &v1.ObjectReference{
						Kind:      "Pod",
						Name:      "kafka-0",
						Namespace: "default",
					},
					Conditions: discoveryv1.EndpointConditions{
						Ready:       new(true),
						Serving:     new(true),
						Terminating: new(false),
					},
				},
				{
					Addresses: []string{"10.244.1.3"},
					Hostname:  new("kafka-1"),
					NodeName:  new("local-dev-worker"),
					TargetRef: &v1.ObjectReference{
						Kind:      "Pod",
						Name:      "kafka-1",
						Namespace: "default",
					},
					Conditions: discoveryv1.EndpointConditions{
						Ready:       new(true),
						Serving:     new(true),
						Terminating: new(false),
					},
				},
				{
					Addresses: []string{"10.244.1.4"},
					Hostname:  new("kafka-2"),
					NodeName:  new("local-dev-worker"),
					TargetRef: &v1.ObjectReference{
						Kind:      "Pod",
						Name:      "kafka-2",
						Namespace: "default",
					},
					Conditions: discoveryv1.EndpointConditions{
						Ready:       new(true),
						Serving:     new(true),
						Terminating: new(false),
					},
				},
			},
		},
	}

	for _, svc := range headless {
		_, err := kubernetes.CoreV1().Services(svc.Namespace).Create(t.Context(), svc, metav1.CreateOptions{})
		require.NoError(t, err)
	}

	for _, pod := range pods {
		_, err := kubernetes.CoreV1().Pods(pod.Namespace).Create(t.Context(), pod, metav1.CreateOptions{})
		require.NoError(t, err)
	}

	for _, ep := range endpoints {
		_, err := kubernetes.DiscoveryV1().EndpointSlices(ep.Namespace).Create(t.Context(), ep, metav1.CreateOptions{})
		require.NoError(t, err)
	}

	src, err := NewServiceSource(t.Context(), kubernetes,
		&Config{
			Namespace:            v1.NamespaceAll,
			LabelFilter:          labels.Everything(),
			ExcludeUnschedulable: true,
		},
	)
	require.NoError(t, err)
	assert.NotNil(t, src)

	got, err := src.Endpoints(t.Context())
	require.NoError(t, err)

	want := []*endpoint.Endpoint{
		// TODO: root domain records should not be created. Address them in a follow-up PR.
		{DNSName: "example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.244.1.2", "10.244.1.3", "10.244.1.4"}},
		{DNSName: "kafka-0.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.244.1.2"}},
		{DNSName: "kafka-1.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.244.1.3"}},
		{DNSName: "kafka-2.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.244.1.4"}},
	}

	testutils.ValidateEndpoints(t, got, want)
}

// TestHeadlessServices tests that headless services generate the correct endpoints.
func TestHeadlessServicesHostIP(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		title                    string
		targetNamespace          string
		svcNamespace             string
		svcName                  string
		svcType                  v1.ServiceType
		compatibility            string
		fqdnTemplate             string
		ignoreHostnameAnnotation bool
		labels                   map[string]string
		annotations              map[string]string
		clusterIP                string
		hostIPs                  []string
		selector                 map[string]string
		lbs                      []string
		podnames                 []string
		hostnames                []string
		podsReady                []bool
		targetRefs               []*v1.ObjectReference
		publishNotReadyAddresses bool
		expected                 []*endpoint.Endpoint
		expectError              bool
	}{
		{
			"annotated Headless services return IPv4 endpoints for each selected Pod",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]bool{true, true},
			[]*v1.ObjectReference{
				{APIVersion: "", Kind: "Pod", Name: "foo-0"},
				{APIVersion: "", Kind: "Pod", Name: "foo-1"},
			},
			false,
			[]*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"}},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv6 endpoints for each selected Pod",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			v1.ClusterIPNone,
			[]string{"2001:db8::1", "2001:db8::2"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]bool{true, true},
			[]*v1.ObjectReference{
				{APIVersion: "", Kind: "Pod", Name: "foo-0"},
				{APIVersion: "", Kind: "Pod", Name: "foo-1"},
			},
			false,
			[]*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"}},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
			},
			false,
		},
		{
			"hostname annotated Headless services are ignored",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			true,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]bool{true, true},
			[]*v1.ObjectReference{
				{APIVersion: "", Kind: "Pod", Name: "foo-0"},
				{APIVersion: "", Kind: "Pod", Name: "foo-1"},
			},
			false,
			[]*endpoint.Endpoint{},
			false,
		},
		{
			"annotated Headless services return IPv4 endpoints with TTL for each selected Pod",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
				annotations.TtlKey:      "1",
			},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]bool{true, true},
			[]*v1.ObjectReference{
				{APIVersion: "", Kind: "Pod", Name: "foo-0"},
				{APIVersion: "", Kind: "Pod", Name: "foo-1"},
			},
			false,
			[]*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)},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}, RecordTTL: endpoint.TTL(1)},
			},
			false,
		},
		{
			"annotated Headless services return IPv6 endpoints with TTL for each selected Pod",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
				annotations.TtlKey:      "1",
			},
			v1.ClusterIPNone,
			[]string{"2001:db8::1", "2001:db8::2"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]bool{true, true},
			[]*v1.ObjectReference{
				{APIVersion: "", Kind: "Pod", Name: "foo-0"},
				{APIVersion: "", Kind: "Pod", Name: "foo-1"},
			},
			false,
			[]*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)},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}, RecordTTL: endpoint.TTL(1)},
			},
			false,
		},
		{
			"annotated Headless services return endpoints for each selected Pod, which are in running state",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]bool{true, false},
			[]*v1.ObjectReference{
				{APIVersion: "", Kind: "Pod", Name: "foo-0"},
				{APIVersion: "", Kind: "Pod", Name: "foo-1"},
			},
			false,
			[]*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"}},
			},
			false,
		},
		{
			"annotated Headless services return endpoints for all Pod if publishNotReadyAddresses is set",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"foo-0", "foo-1"},
			[]bool{true, false},
			[]*v1.ObjectReference{
				{APIVersion: "", Kind: "Pod", Name: "foo-0"},
				{APIVersion: "", Kind: "Pod", Name: "foo-1"},
			},
			true,
			[]*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"}},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv4 endpoints for pods missing hostname",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			v1.ClusterIPNone,
			[]string{"1.1.1.1", "1.1.1.2"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"", ""},
			[]bool{true, true},
			[]*v1.ObjectReference{
				{APIVersion: "", Kind: "Pod", Name: "foo-0"},
				{APIVersion: "", Kind: "Pod", Name: "foo-1"},
			},
			false,
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}},
			},
			false,
		},
		{
			"annotated Headless services return IPv6 endpoints for pods missing hostname",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			v1.ClusterIPNone,
			[]string{"2001:db8::1", "2001:db8::2"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0", "foo-1"},
			[]string{"", ""},
			[]bool{true, true},
			[]*v1.ObjectReference{
				{APIVersion: "", Kind: "Pod", Name: "foo-0"},
				{APIVersion: "", Kind: "Pod", Name: "foo-1"},
			},
			false,
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
			},
			false,
		},
		{
			"annotated Headless services without a targetRef has no endpoints",
			"",
			"testing",
			"foo",
			v1.ServiceTypeClusterIP,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			v1.ClusterIPNone,
			[]string{"1.1.1.1"},
			map[string]string{
				"component": "foo",
			},
			[]string{},
			[]string{"foo-0"},
			[]string{"foo-0"},
			[]bool{true, true},
			[]*v1.ObjectReference{nil},
			false,
			[]*endpoint.Endpoint{},
			false,
		},
	} {

		t.Run(tc.title, func(t *testing.T) {
			t.Parallel()

			kubernetes := fake.NewClientset()

			service := &v1.Service{
				Spec: v1.ServiceSpec{
					Type:                     tc.svcType,
					ClusterIP:                tc.clusterIP,
					Selector:                 tc.selector,
					PublishNotReadyAddresses: tc.publishNotReadyAddresses,
				},
				ObjectMeta: metav1.ObjectMeta{
					Namespace:   tc.svcNamespace,
					Name:        tc.svcName,
					Labels:      tc.labels,
					Annotations: tc.annotations,
				},
				Status: v1.ServiceStatus{},
			}
			_, err := kubernetes.CoreV1().Services(service.Namespace).Create(t.Context(), service, metav1.CreateOptions{})
			require.NoError(t, err)

			var endpointsSlicesEndpoints []discoveryv1.Endpoint
			for i, podname := range tc.podnames {
				pod := &v1.Pod{
					Spec: v1.PodSpec{
						Containers: []v1.Container{},
						Hostname:   tc.hostnames[i],
					},
					ObjectMeta: metav1.ObjectMeta{
						Namespace:   tc.svcNamespace,
						Name:        podname,
						Labels:      tc.labels,
						Annotations: tc.annotations,
					},
					Status: v1.PodStatus{
						HostIP: tc.hostIPs[i],
					},
				}

				_, err = kubernetes.CoreV1().Pods(tc.svcNamespace).Create(t.Context(), pod, metav1.CreateOptions{})
				require.NoError(t, err)

				ep := discoveryv1.Endpoint{
					Addresses: []string{"4.3.2.1"},
					TargetRef: tc.targetRefs[i],
					Conditions: discoveryv1.EndpointConditions{
						Ready: &tc.podsReady[i],
					},
				}
				endpointsSlicesEndpoints = append(endpointsSlicesEndpoints, ep)
			}
			endpointSliceLabels := maps.Clone(tc.labels)
			endpointSliceLabels[discoveryv1.LabelServiceName] = tc.svcName
			endpointSlice := &discoveryv1.EndpointSlice{
				ObjectMeta: metav1.ObjectMeta{
					Namespace: tc.svcNamespace,
					Name:      tc.svcName,
					Labels:    endpointSliceLabels,
				},
				AddressType: discoveryv1.AddressTypeIPv4,
				Endpoints:   endpointsSlicesEndpoints,
			}
			_, err = kubernetes.DiscoveryV1().EndpointSlices(tc.svcNamespace).Create(t.Context(), endpointSlice, metav1.CreateOptions{})
			require.NoError(t, err)

			// Create our object under test and get the endpoints.
			client, _ := NewServiceSource(t.Context(), kubernetes,
				&Config{
					Namespace:                tc.targetNamespace,
					LabelFilter:              labels.Everything(),
					Compatibility:            tc.compatibility,
					TemplateEngine:           templatetest.MustEngine(t, tc.fqdnTemplate, "", "", false),
					IgnoreHostnameAnnotation: tc.ignoreHostnameAnnotation,
					ExcludeUnschedulable:     true,
					PublishHostIP:            true,
					PublishInternal:          true,
				},
			)
			require.NoError(t, err)

			endpoints, err := client.Endpoints(t.Context())
			if tc.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}

			// Validate returned endpoints against desired endpoints.
			testutils.ValidateEndpoints(t, endpoints, tc.expected)

			// TODO; when all resources have the resource label, we could add this check to the validateEndpoints function.
			for _, ep := range endpoints {
				require.Contains(t, ep.Labels, endpoint.ResourceLabelKey)
			}
		})
	}
}

// TestExternalServices tests that external services generate the correct endpoints.
func TestExternalServices(t *testing.T) {
	t.Parallel()

	for _, tc := range []struct {
		title                    string
		targetNamespace          string
		svcNamespace             string
		svcName                  string
		svcType                  v1.ServiceType
		compatibility            string
		fqdnTemplate             string
		ignoreHostnameAnnotation bool
		labels                   map[string]string
		annotations              map[string]string
		externalName             string
		externalIPs              []string
		serviceTypeFilter        []string
		expected                 []*endpoint.Endpoint
		expectError              bool
	}{
		{
			"external services return an A endpoint for the external name that is an IPv4 address",
			"",
			"testing",
			"foo",
			v1.ServiceTypeExternalName,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			"111.111.111.111",
			[]string{},
			[]string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", Targets: endpoint.Targets{"111.111.111.111"}, RecordType: endpoint.RecordTypeA},
			},
			false,
		},
		{
			"external services return an AAAA endpoint for the external name that is an IPv6 address",
			"",
			"testing",
			"foo",
			v1.ServiceTypeExternalName,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			"2001:db8::111",
			[]string{},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", Targets: endpoint.Targets{"2001:db8::111"}, RecordType: endpoint.RecordTypeAAAA},
			},
			false,
		},
		{
			"external services return a CNAME endpoint for the external name that is a domain",
			"",
			"testing",
			"foo",
			v1.ServiceTypeExternalName,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			"remote.example.com",
			[]string{},
			[]string{string(v1.ServiceTypeExternalName)},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", Targets: endpoint.Targets{"remote.example.com"}, RecordType: endpoint.RecordTypeCNAME},
			},
			false,
		},
		{
			"annotated ExternalName service with externalIPs returns a single endpoint with multiple targets",
			"",
			"testing",
			"foo",
			v1.ServiceTypeExternalName,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			"service.example.org",
			[]string{"10.2.3.4", "11.2.3.4"},
			[]string{},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.2.3.4", "11.2.3.4"}},
			},
			false,
		},
		{
			"annotated ExternalName service with externalIPs of dualstack addresses returns 2 endpoints with multiple targets",
			"",
			"testing",
			"foo",
			v1.ServiceTypeExternalName,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			"service.example.org",
			[]string{"10.2.3.4", "11.2.3.4", "2001:db8::1", "2001:db8::2"},
			[]string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeExternalName)},
			[]*endpoint.Endpoint{
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.2.3.4", "11.2.3.4"}},
				{DNSName: "service.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:db8::1", "2001:db8::2"}},
			},
			false,
		},
		{
			"annotated ExternalName service with externalIPs of dualstack and excluded in serviceTypeFilter",
			"",
			"testing",
			"foo",
			v1.ServiceTypeExternalName,
			"",
			"",
			false,
			map[string]string{"component": "foo"},
			map[string]string{
				annotations.HostnameKey: "service.example.org",
			},
			"service.example.org",
			[]string{"10.2.3.4", "11.2.3.4", "2001:db8::1", "2001:db8::2"},
			[]string{string(v1.ServiceTypeNodePort), string(v1.ServiceTypeClusterIP)},
			[]*endpoint.Endpoint{},
			false,
		},
	} {

		t.Run(tc.title, func(t *testing.T) {
			t.Parallel()

			// Create a Kubernetes testing client
			kubernetes := fake.NewClientset()

			service := &v1.Service{
				Spec: v1.ServiceSpec{
					Type:         tc.svcType,
					ExternalName: tc.externalName,
					ExternalIPs:  tc.externalIPs,
				},
				ObjectMeta: metav1.ObjectMeta{
					Namespace:   tc.svcNamespace,
					Name:        tc.svcName,
					Labels:      tc.labels,
					Annotations: tc.annotations,
				},
				Status: v1.ServiceStatus{},
			}
			_, err := kubernetes.CoreV1().Services(service.Namespace).Create(t.Context(), service, metav1.CreateOptions{})
			require.NoError(t, err)

			// Create our object under test and get the endpoints.
			client, _ := NewServiceSource(t.Context(), kubernetes,
				&Config{
					TemplateEngine:           templatetest.MustEngine(t, tc.fqdnTemplate, "", "", false),
					Compatibility:            tc.compatibility,
					ServiceTypeFilter:        tc.serviceTypeFilter,
					Namespace:                tc.targetNamespace,
					IgnoreHostnameAnnotation: tc.ignoreHostnameAnnotation,
					ExcludeUnschedulable:     true,
					LabelFilter:              labels.Everything(),
				},
			)
			require.NoError(t, err)

			endpoints, err := client.Endpoints(t.Context())
			if tc.expectError {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}

			// Validate returned endpoints against desired endpoints.
			testutils.ValidateEndpoints(t, endpoints, tc.expected)

			// TODO; when all resources have the resource label, we could add this check to the validateEndpoints function.
			for _, ep := range endpoints {
				require.Contains(t, ep.Labels, endpoint.ResourceLabelKey)
			}
		})
	}
}

func BenchmarkServiceEndpoints(b *testing.B) {
	kubernetes := fake.NewClientset()

	service := &v1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "testing",
			Name:      "foo",
			Annotations: map[string]string{
				annotations.HostnameKey: "foo.example.org.",
			},
		},
		Status: v1.ServiceStatus{
			LoadBalancer: v1.LoadBalancerStatus{
				Ingress: []v1.LoadBalancerIngress{
					{IP: "1.2.3.4"},
					{IP: "8.8.8.8"},
				},
			},
		},
	}

	_, err := kubernetes.CoreV1().Services(service.Namespace).Create(b.Context(), service, metav1.CreateOptions{})
	require.NoError(b, err)

	client, err := NewServiceSource(b.Context(), kubernetes,
		&Config{
			Namespace:            v1.NamespaceAll,
			ExcludeUnschedulable: true,
			LabelFilter:          labels.Everything(),
		},
	)
	require.NoError(b, err)

	for b.Loop() {
		_, err := client.Endpoints(b.Context())
		require.NoError(b, err)
	}
}

func TestNewServiceSourceInformersEnabled(t *testing.T) {
	tests := []struct {
		name      string
		asserts   func(svc *serviceSource)
		svcFilter []string
	}{
		{
			name: "serviceTypeFilter is set to empty",
			asserts: func(svc *serviceSource) {
				assert.NotNil(t, svc)
				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)
			},
		},
		{
			name:      "serviceTypeFilter contains NodePort",
			svcFilter: []string{string(v1.ServiceTypeClusterIP)},
			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.NotNil(t, svc.endpointSlicesInformer)
				assert.NotNil(t, svc.podInformer)
			},
		},
		{
			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)
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			svc, err := NewServiceSource(t.Context(), fake.NewClientset(),
				&Config{
					Namespace:                      "default",
					ServiceTypeFilter:              tc.svcFilter,
					AlwaysPublishNotReadyAddresses: true,
					LabelFilter:                    labels.Everything(),
				},
			)
			require.NoError(t, err)
			svcSrc, ok := svc.(*serviceSource)
			if !ok {
				require.Fail(t, "expected serviceSource")
			}
			tc.asserts(svcSrc)
		})
	}
}

func TestNewServiceSourceWithServiceTypeFilters_Unsupported(t *testing.T) {
	serviceTypeFilter := []string{"ClusterIP", "ServiceTypeNotExist"}

	svc, err := NewServiceSource(t.Context(), fake.NewClientset(),
		&Config{
			Namespace:         "default",
			ServiceTypeFilter: serviceTypeFilter,
			LabelFilter:       labels.Everything(),
		},
	)
	require.Errorf(t, err, "unsupported service type filter: \"UnknownType\". Supported types are: [\"ClusterIP\" \"NodePort\" \"LoadBalancer\" \"ExternalName\"]")
	require.Nil(t, svc, "ServiceSource should be nil when an unsupported service type is provided")
}

func TestIsPodStatusReady(t *testing.T) {
	for _, tt := range []struct {
		name   string
		status v1.PodStatus
		want   bool
	}{
		{
			name: "PodReady condition true",
			status: v1.PodStatus{Conditions: []v1.PodCondition{
				{Type: v1.PodReady, Status: v1.ConditionTrue},
			}},
			want: true,
		},
		{
			name: "PodReady condition false",
			status: v1.PodStatus{Conditions: []v1.PodCondition{
				{Type: v1.PodReady, Status: v1.ConditionFalse},
			}},
			want: false,
		},
		{
			name: "no PodReady condition returns false",
			status: v1.PodStatus{Conditions: []v1.PodCondition{
				{Type: v1.PodScheduled, Status: v1.ConditionTrue},
			}},
			want: false,
		},
	} {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.want, isPodStatusReady(tt.status))
		})
	}
}

func TestNewServiceTypes(t *testing.T) {
	tests := []struct {
		name        string
		filter      []string
		wantEnabled bool
		wantTypes   map[v1.ServiceType]bool
		wantErr     bool
	}{
		{
			name:        "empty filter disables serviceTypes",
			filter:      []string{},
			wantEnabled: false,
			wantTypes:   nil,
			wantErr:     false,
		},
		{
			name:        "filter with empty string disables serviceTypes",
			filter:      []string{""},
			wantEnabled: false,
			wantTypes:   nil,
			wantErr:     false,
		},
		{
			name:        "valid filter enables serviceTypes",
			filter:      []string{string(v1.ServiceTypeClusterIP), string(v1.ServiceTypeNodePort)},
			wantEnabled: true,
			wantTypes: map[v1.ServiceType]bool{
				v1.ServiceTypeClusterIP: true,
				v1.ServiceTypeNodePort:  true,
			},
			wantErr: false,
		},
		{
			name:        "filter with unknown type returns error",
			filter:      []string{"UnknownType"},
			wantEnabled: false,
			wantTypes:   nil,
			wantErr:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			st, err := newServiceTypesFilter(tt.filter)
			if tt.wantErr {
				assert.Error(t, err)
				assert.Nil(t, st)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.wantEnabled, st.enabled)
				if tt.wantTypes != nil {
					assert.Equal(t, tt.wantTypes, st.types)
				}
			}
		})
	}
}

func TestFilterByServiceType_WithFixture(t *testing.T) {
	tests := []struct {
		name            string
		filter          *serviceTypes
		currentServices []*v1.Service
		expected        int
	}{
		{
			name: "all types of services with filter enabled for ServiceTypeNodePort and ServiceTypeClusterIP",
			currentServices: createTestServicesByType("kube-system", map[v1.ServiceType]int{
				v1.ServiceTypeLoadBalancer: 3,
				v1.ServiceTypeNodePort:     4,
				v1.ServiceTypeClusterIP:    5,
				v1.ServiceTypeExternalName: 2,
			}),
			filter: &serviceTypes{
				enabled: true,
				types: map[v1.ServiceType]bool{
					v1.ServiceTypeNodePort:  true,
					v1.ServiceTypeClusterIP: true,
				},
			},
			expected: 4 + 5,
		},
		{
			name: "all types of services with filter enabled for ServiceTypeLoadBalancer",
			currentServices: createTestServicesByType("default", map[v1.ServiceType]int{
				v1.ServiceTypeLoadBalancer: 3,
				v1.ServiceTypeNodePort:     4,
				v1.ServiceTypeClusterIP:    5,
				v1.ServiceTypeExternalName: 2,
			}),
			filter: &serviceTypes{
				enabled: true,
				types: map[v1.ServiceType]bool{
					v1.ServiceTypeLoadBalancer: true,
				},
			},
			expected: 3,
		},
		{
			name: "enabled for ServiceTypeLoadBalancer when not all types are present",
			currentServices: createTestServicesByType("default", map[v1.ServiceType]int{
				v1.ServiceTypeNodePort:     4,
				v1.ServiceTypeClusterIP:    5,
				v1.ServiceTypeExternalName: 2,
			}),
			filter: &serviceTypes{
				enabled: true,
				types: map[v1.ServiceType]bool{
					v1.ServiceTypeLoadBalancer: true,
				},
			},
			expected: 0,
		},
		{
			name: "filter disabled returns all services",
			currentServices: createTestServicesByType("default", map[v1.ServiceType]int{
				v1.ServiceTypeLoadBalancer: 3,
				v1.ServiceTypeNodePort:     4,
				v1.ServiceTypeClusterIP:    5,
				v1.ServiceTypeExternalName: 2,
			}),
			filter: &serviceTypes{
				enabled: false,
				types:   map[v1.ServiceType]bool{},
			},
			expected: 14,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sc := &serviceSource{serviceTypeFilter: tt.filter}
			assert.NotNil(t, sc)
			got := sc.filterByServiceType(tt.currentServices)
			assert.Len(t, got, tt.expected)
		})
	}
}

func TestEndpointSlicesIndexer(t *testing.T) {
	ctx := t.Context()
	fakeClient := fake.NewClientset()

	es := &discoveryv1.EndpointSlice{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "default",
			Name:      "test-slice",
			Labels:    map[string]string{discoveryv1.LabelServiceName: "test-service"},
			Annotations: map[string]string{
				"some-annotation": "value",
			},
			UID: "esuid",
			ManagedFields: []metav1.ManagedFieldsEntry{
				{Manager: "endpointslice-controller", Operation: metav1.ManagedFieldsOperationUpdate},
			},
		},
		AddressType: discoveryv1.AddressTypeIPv4,
		Endpoints:   []discoveryv1.Endpoint{{Addresses: []string{"10.0.0.1"}}},
	}
	_, err := fakeClient.DiscoveryV1().EndpointSlices("default").Create(ctx, es, metav1.CreateOptions{})
	require.NoError(t, err)

	// Should not error when creating the source
	src, err := NewServiceSource(ctx, fakeClient,
		&Config{
			TemplateEngine:       templatetest.MustEngine(t, "{{.Name}}", "", "", false),
			Namespace:            "default",
			ExcludeUnschedulable: true,
			LabelFilter:          labels.Everything(),
		},
	)
	require.NoError(t, err)
	ss, ok := src.(*serviceSource)
	require.True(t, ok)

	// Try to get EndpointSlices by index; should not panic or error, should return empty slice
	indexer := ss.endpointSlicesInformer.Informer().GetIndexer()
	slices, err := indexer.ByIndex(informers.IndexWithSelectors, "default/foo")
	require.NoError(t, err)
	require.Empty(t, slices)

	// Insert an object of the wrong type into the indexer; indexFunc should return an error and Add() should panic
	require.PanicsWithError(t,
		"unable to calculate an index entry for key \"default/not-an-endpointslice\" on index \"withSelectors\": "+
			"object is not of type **v1.EndpointSlice",
		func() {
			_ = indexer.Add(&v1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "not-an-endpointslice",
					Namespace: "default",
				},
			})
		})
}

func TestPodTransformerInServiceSource(t *testing.T) {
	ctx := t.Context()
	fakeClient := fake.NewClientset()

	es := &discoveryv1.EndpointSlice{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "test-ns",
			Name:      "test-slice",
			Labels:    map[string]string{discoveryv1.LabelServiceName: "test-service"},
			Annotations: map[string]string{
				"some-annotation": "value",
			},
			UID: "esuid",
			ManagedFields: []metav1.ManagedFieldsEntry{
				{Manager: "endpointslice-controller", Operation: metav1.ManagedFieldsOperationUpdate},
			},
		},
		AddressType: discoveryv1.AddressTypeIPv4,
		Endpoints:   []discoveryv1.Endpoint{{Addresses: []string{"10.0.0.1"}}},
	}

	pod := &v1.Pod{
		Spec: v1.PodSpec{
			Containers: []v1.Container{{
				Name: "test",
			}},
			Hostname: "test-hostname",
			NodeName: "test-node",
		},
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "test-ns",
			Name:      "test-name",
			Labels: map[string]string{
				"label1": "value1",
				"label2": "value2",
				"label3": "value3",
			},
			Annotations: map[string]string{
				"user-annotation": "value",
				"external-dns.alpha.kubernetes.io/hostname": "test-hostname",
				"external-dns.alpha.kubernetes.io/random":   "value",
				"other/annotation":                          "value",
			},
			UID: "someuid",
		},
		Status: v1.PodStatus{
			PodIP:  "127.0.0.1",
			HostIP: "127.0.0.2",
			Conditions: []v1.PodCondition{{
				Type:   v1.PodReady,
				Status: v1.ConditionTrue,
			}, {
				Type:   v1.ContainersReady,
				Status: v1.ConditionFalse,
			}},
		},
	}

	_, err := fakeClient.CoreV1().Pods(pod.Namespace).Create(t.Context(), pod, metav1.CreateOptions{})
	require.NoError(t, err)
	_, err = fakeClient.DiscoveryV1().EndpointSlices(es.Namespace).Create(t.Context(), es, metav1.CreateOptions{})
	require.NoError(t, err)

	// Should not error when creating the source
	src, err := NewServiceSource(ctx, fakeClient,
		&Config{
			TemplateEngine: templatetest.MustEngine(t, "{{.Name}}", "", "", false),
			LabelFilter:    labels.Everything(),
		},
	)
	require.NoError(t, err)
	ss, ok := src.(*serviceSource)
	require.True(t, ok)

	retrieved, err := ss.podInformer.Lister().Pods("test-ns").Get("test-name")
	require.NoError(t, err)

	// Metadata
	assert.Equal(t, "test-name", retrieved.Name)
	assert.Equal(t, "test-ns", retrieved.Namespace)
	assert.Equal(t, pod.UID, retrieved.UID)
	assert.Equal(t, pod.Labels, retrieved.Labels)
	// Annotations filtered to external-dns prefix only
	assert.Equal(t, map[string]string{
		"external-dns.alpha.kubernetes.io/hostname": "test-hostname",
		"external-dns.alpha.kubernetes.io/random":   "value",
	}, retrieved.Annotations)

	// Spec — fully preserved
	assert.NotEmpty(t, retrieved.Spec.Containers)
	assert.Equal(t, "test-hostname", retrieved.Spec.Hostname)
	assert.Equal(t, "test-node", retrieved.Spec.NodeName)

	// Status — fully preserved
	assert.Equal(t, "127.0.0.2", retrieved.Status.HostIP)
	assert.Equal(t, "127.0.0.1", retrieved.Status.PodIP)
	assert.ElementsMatch(t, []v1.PodCondition{{
		Type:   v1.PodReady,
		Status: v1.ConditionTrue,
	}, {
		Type:   v1.ContainersReady,
		Status: v1.ConditionFalse,
	}}, retrieved.Status.Conditions)

	// EndpointSlice — managedFields stripped, everything else preserved
	retrievedES, err := ss.endpointSlicesInformer.Lister().EndpointSlices(es.Namespace).Get(es.Name)
	require.NoError(t, err)
	assert.Empty(t, retrievedES.ManagedFields)
	assert.Equal(t, es.Labels, retrievedES.Labels)
	assert.Equal(t, es.Annotations, retrievedES.Annotations)
	assert.Equal(t, es.Endpoints, retrievedES.Endpoints)
}

func TestNodeTransformerInServiceSource(t *testing.T) {
	ctx := t.Context()

	node := &v1.Node{
		ObjectMeta: metav1.ObjectMeta{
			Name: "test-node",
			Labels: map[string]string{
				"label1": "value1",
			},
			Annotations: map[string]string{
				"user-annotation":              "value",
				v1.LastAppliedConfigAnnotation: `{"apiVersion":"v1"}`,
			},
			UID: "someuid",
			ManagedFields: []metav1.ManagedFieldsEntry{
				{Manager: "kubectl", Operation: metav1.ManagedFieldsOperationApply},
			},
		},
		Status: v1.NodeStatus{
			Addresses: []v1.NodeAddress{
				{Type: v1.NodeExternalIP, Address: "1.2.3.4"},
			},
			Conditions: []v1.NodeCondition{
				{Type: v1.NodeReady, Status: v1.ConditionTrue},
			},
		},
	}

	fakeClient := fake.NewClientset(node)

	src, err := NewServiceSource(ctx, fakeClient, &Config{
		ServiceTypeFilter: []string{string(v1.ServiceTypeNodePort)},
		LabelFilter:       labels.Everything(),
	})
	require.NoError(t, err)
	ss, ok := src.(*serviceSource)
	require.True(t, ok)

	retrieved, err := ss.nodeInformer.Lister().Get(node.Name)
	require.NoError(t, err)

	assert.Equal(t, node.Name, retrieved.Name)
	assert.Equal(t, node.Labels, retrieved.Labels)
	assert.Equal(t, node.UID, retrieved.UID)
	assert.Empty(t, retrieved.ManagedFields)
	assert.NotContains(t, retrieved.Annotations, v1.LastAppliedConfigAnnotation)
	assert.Contains(t, retrieved.Annotations, "user-annotation")
	// Status.Addresses preserved — used for endpoint generation
	assert.Equal(t, node.Status.Addresses, retrieved.Status.Addresses)
	// Status.Conditions stripped
	assert.Empty(t, retrieved.Status.Conditions)
}

func TestServiceTransformerInServiceSource(t *testing.T) {
	ctx := t.Context()

	svc := &v1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-service",
			Namespace: "default",
			Labels:    map[string]string{"label1": "value1"},
			Annotations: map[string]string{
				"user-annotation":              "value",
				v1.LastAppliedConfigAnnotation: `{"apiVersion":"v1"}`,
			},
			UID: "someuid",
			ManagedFields: []metav1.ManagedFieldsEntry{
				{Manager: "kubectl", Operation: metav1.ManagedFieldsOperationApply},
			},
		},
		Spec: v1.ServiceSpec{
			Type:        v1.ServiceTypeLoadBalancer,
			ExternalIPs: []string{"1.2.3.4"},
		},
		Status: v1.ServiceStatus{
			LoadBalancer: v1.LoadBalancerStatus{
				Ingress: []v1.LoadBalancerIngress{{IP: "5.6.7.8"}},
			},
			Conditions: []metav1.Condition{
				{Type: "Available", Status: metav1.ConditionTrue, Reason: "Ready"},
			},
		},
	}

	fakeClient := fake.NewClientset(svc)

	src, err := NewServiceSource(ctx, fakeClient, &Config{
		Namespace:   svc.Namespace,
		LabelFilter: labels.Everything(),
	})
	require.NoError(t, err)
	ss, ok := src.(*serviceSource)
	require.True(t, ok)

	retrieved, err := ss.serviceInformer.Lister().Services(svc.Namespace).Get(svc.Name)
	require.NoError(t, err)

	assert.Equal(t, svc.Name, retrieved.Name)
	assert.Equal(t, svc.Labels, retrieved.Labels)
	assert.Equal(t, svc.UID, retrieved.UID)
	assert.Empty(t, retrieved.ManagedFields)
	assert.NotContains(t, retrieved.Annotations, v1.LastAppliedConfigAnnotation)
	assert.Contains(t, retrieved.Annotations, "user-annotation")
	// Spec and Status.LoadBalancer preserved — used for endpoint generation
	assert.Equal(t, svc.Spec.ExternalIPs, retrieved.Spec.ExternalIPs)
	assert.Equal(t, svc.Status.LoadBalancer, retrieved.Status.LoadBalancer)
	// Status.Conditions stripped
	assert.Empty(t, retrieved.Status.Conditions)
}

// createTestServicesByType creates the requested number of services per type in the given namespace.
func createTestServicesByType(ns string, typeCounts map[v1.ServiceType]int) []*v1.Service {
	var services []*v1.Service
	idx := 0
	for svcType, count := range typeCounts {
		for range count {
			svc := &v1.Service{
				ObjectMeta: metav1.ObjectMeta{
					Name:      fmt.Sprintf("svc-%s-%d", svcType, idx),
					Namespace: ns,
				},
				Spec: v1.ServiceSpec{
					Type: svcType,
				},
			}
			if svcType == v1.ServiceTypeExternalName {
				svc.Spec.ExternalName = fmt.Sprintf("external-%d.example.com", idx)
			}
			services = append(services, svc)
			idx++
		}
	}
	// Shuffle the resulting services to ensure randomness in the order.
	rand.New(rand.NewSource(time.Now().UnixNano()))
	rand.Shuffle(len(services), func(i, j int) {
		services[i], services[j] = services[j], services[i]
	})
	return services
}

func TestServiceTypes_isNodeInformerRequired(t *testing.T) {
	tests := []struct {
		name     string
		filter   []string
		required []v1.ServiceType
		want     bool
	}{
		{
			name:     "NodePort required and filter is empty",
			filter:   []string{},
			required: []v1.ServiceType{v1.ServiceTypeNodePort},
			want:     true,
		},
		{
			name:     "NodePort type present",
			filter:   []string{string(v1.ServiceTypeNodePort)},
			required: []v1.ServiceType{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.isRequired(tt.required...)
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestServiceSource_AddEventHandler(t *testing.T) {
	var fakeServiceInformer *fakes.FakeServiceInformer
	var fakeEdpInformer *fakes.FakeEndpointSliceInformer
	var fakeNodeInformer *fakes.FakeNodeInformer
	tests := []struct {
		name    string
		filter  []string
		times   int
		asserts func(t *testing.T)
	}{
		{
			name:   "AddEventHandler should trigger all event handlers when empty filter is provided",
			filter: []string{},
			times:  2,
			asserts: func(t *testing.T) {
				fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
				fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1)
				fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0)
			},
		},
		{
			name:   "AddEventHandler should trigger only service event handler",
			filter: []string{string(v1.ServiceTypeExternalName), string(v1.ServiceTypeLoadBalancer)},
			times:  1,
			asserts: func(t *testing.T) {
				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) {
				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:  2,
			asserts: func(t *testing.T) {
				fakeServiceInformer.AssertNumberOfCalls(t, "Informer", 1)
				fakeEdpInformer.AssertNumberOfCalls(t, "Informer", 1)
				fakeNodeInformer.AssertNumberOfCalls(t, "Informer", 0)
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			fakeServiceInformer = new(fakes.FakeServiceInformer)
			infSvc := testInformer{}
			fakeServiceInformer.On("Informer").Return(&infSvc)

			fakeEdpInformer = new(fakes.FakeEndpointSliceInformer)
			infEdp := testInformer{}
			fakeEdpInformer.On("Informer").Return(&infEdp)

			fakeNodeInformer = new(fakes.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)
		})
	}
}

// Test helper functions created during extractHeadlessEndpoints refactoring
func TestConvertToEndpointSlices(t *testing.T) {
	t.Run("converts valid EndpointSlices", func(t *testing.T) {
		validSlice := &discoveryv1.EndpointSlice{
			ObjectMeta:  metav1.ObjectMeta{Name: "valid-slice"},
			AddressType: discoveryv1.AddressTypeIPv4,
		}

		rawObjects := []any{validSlice}
		result := convertToEndpointSlices(rawObjects)

		assert.Len(t, result, 1)
		assert.Equal(t, "valid-slice", result[0].Name)
	})

	t.Run("skips invalid objects", func(t *testing.T) {
		invalidObject := "not-an-endpoint-slice"
		validSlice := &discoveryv1.EndpointSlice{
			ObjectMeta:  metav1.ObjectMeta{Name: "valid-slice"},
			AddressType: discoveryv1.AddressTypeIPv4,
		}

		rawObjects := []any{invalidObject, validSlice}
		result := convertToEndpointSlices(rawObjects)

		assert.Len(t, result, 1)
		assert.Equal(t, "valid-slice", result[0].Name)
	})

	t.Run("handles empty input", func(t *testing.T) {
		result := convertToEndpointSlices([]any{})
		assert.Empty(t, result)
	})

	t.Run("handles all invalid objects", func(t *testing.T) {
		rawObjects := []any{"invalid1", 123, map[string]string{"key": "value"}}
		result := convertToEndpointSlices(rawObjects)
		assert.Empty(t, result)
	})
}

// Test for processEndpointSlice: publishPodIPs true and pod == nil

func TestProcessEndpointSlices_PublishPodIPsPodNil(t *testing.T) {
	sc := &serviceSource{}

	endpointSlice := &discoveryv1.EndpointSlice{
		ObjectMeta:  metav1.ObjectMeta{Name: "slice1", Namespace: "default"},
		AddressType: discoveryv1.AddressTypeIPv4,
		Endpoints: []discoveryv1.Endpoint{
			{
				TargetRef:  &v1.ObjectReference{Kind: "Pod", Name: "missing-pod"},
				Conditions: discoveryv1.EndpointConditions{Ready: new(true)},
			},
		},
	}
	pods := []*v1.Pod{} // No pods, so pod == nil
	hostname := "test.example.com"
	endpointsType := "IPv4"
	publishPodIPs := true
	publishNotReadyAddresses := false

	result := sc.processHeadlessEndpointsFromSlices(
		pods, []*discoveryv1.EndpointSlice{endpointSlice},
		hostname, endpointsType, publishPodIPs, publishNotReadyAddresses)
	assert.Empty(t, result, "No targets should be added when pod is nil and publishPodIPs is true")
}

// Test for processEndpointSlice: publishPodIPs true and unsupported address type triggers log.Debugf skip
func TestProcessEndpointSlices_PublishPodIPsUnsupportedAddressType(t *testing.T) {
	sc := &serviceSource{}

	endpointSlice := &discoveryv1.EndpointSlice{
		ObjectMeta:  metav1.ObjectMeta{Name: "slice2", Namespace: "default"},
		AddressType: discoveryv1.AddressTypeFQDN, // unsupported type
		Endpoints: []discoveryv1.Endpoint{
			{
				TargetRef:  &v1.ObjectReference{Kind: "Pod", Name: "some-pod"},
				Conditions: discoveryv1.EndpointConditions{Ready: new(true)},
			},
		},
	}
	pods := []*v1.Pod{{ObjectMeta: metav1.ObjectMeta{Name: "some-pod"}}}
	hostname := "test.example.com"
	endpointsType := "FQDN"
	publishPodIPs := true
	publishNotReadyAddresses := false

	result := sc.processHeadlessEndpointsFromSlices(
		pods, []*discoveryv1.EndpointSlice{endpointSlice},
		hostname, endpointsType, publishPodIPs, publishNotReadyAddresses)
	assert.Empty(t, result, "No targets should be added for unsupported address type when publishPodIPs is true")
}

// Test for missing coverage: publishPodIPs false scenario
func TestProcessEndpointSlices_PublishPodIPsFalse(t *testing.T) {
	sc := &serviceSource{}

	endpointSlice := &discoveryv1.EndpointSlice{
		ObjectMeta:  metav1.ObjectMeta{Name: "slice1", Namespace: "default"},
		AddressType: discoveryv1.AddressTypeIPv4,
		Endpoints: []discoveryv1.Endpoint{
			{
				TargetRef:  &v1.ObjectReference{Kind: "Pod", Name: "test-pod"},
				Conditions: discoveryv1.EndpointConditions{Ready: new(true)},
				Addresses:  []string{"10.0.0.1"},
			},
		},
	}
	pods := []*v1.Pod{{
		ObjectMeta: metav1.ObjectMeta{Name: "test-pod"},
		Status:     v1.PodStatus{PodIP: "10.0.0.1"},
	}}
	hostname := "test.example.com"
	endpointsType := "IPv4"
	publishPodIPs := false // This should allow processing
	publishNotReadyAddresses := false

	result := sc.processHeadlessEndpointsFromSlices(
		pods, []*discoveryv1.EndpointSlice{endpointSlice},
		hostname, endpointsType, publishPodIPs, publishNotReadyAddresses)
	assert.NotEmpty(t, result, "Targets should be added when publishPodIPs is false")
}

// Test for missing coverage: not ready endpoints with publishNotReadyAddresses true
func TestProcessEndpointSlices_NotReadyWithPublishNotReady(t *testing.T) {
	sc := &serviceSource{}

	endpointSlice := &discoveryv1.EndpointSlice{
		ObjectMeta:  metav1.ObjectMeta{Name: "slice1", Namespace: "default"},
		AddressType: discoveryv1.AddressTypeIPv4,
		Endpoints: []discoveryv1.Endpoint{
			{
				TargetRef:  &v1.ObjectReference{Kind: "Pod", Name: "test-pod"},
				Conditions: discoveryv1.EndpointConditions{Ready: new(false)}, // Not ready
				Addresses:  []string{"10.0.0.1"},
			},
		},
	}
	pods := []*v1.Pod{{
		ObjectMeta: metav1.ObjectMeta{Name: "test-pod"},
		Status:     v1.PodStatus{PodIP: "10.0.0.1"},
	}}
	hostname := "test.example.com"
	endpointsType := "IPv4"
	publishPodIPs := false
	publishNotReadyAddresses := true // This should allow not-ready endpoints

	result := sc.processHeadlessEndpointsFromSlices(
		pods, []*discoveryv1.EndpointSlice{endpointSlice},
		hostname, endpointsType, publishPodIPs, publishNotReadyAddresses)
	assert.NotEmpty(t, result, "Not ready endpoints should be processed when publishNotReadyAddresses is true")
}

// Test getTargetsForDomain with empty ep.Addresses
func TestGetTargetsForDomain_EmptyAddresses(t *testing.T) {
	sc := &serviceSource{}
	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{Name: "test-pod"},
		Status:     v1.PodStatus{PodIP: "10.0.0.1"},
	}
	ep := discoveryv1.Endpoint{
		Addresses: []string{}, // Empty addresses
	}
	endpointSlice := &discoveryv1.EndpointSlice{
		ObjectMeta:  metav1.ObjectMeta{Name: "test-slice", Namespace: "default"},
		AddressType: discoveryv1.AddressTypeIPv4,
	}
	endpointsType := "IPv4"
	headlessDomain := "test.example.com"

	targets := sc.getTargetsForDomain(pod, ep, endpointSlice, endpointsType, headlessDomain)
	assert.Empty(t, targets, "Should return empty targets when ep.Addresses is empty")
}

// Test getTargetsForDomain with EndpointsTypeHostIP
func TestGetTargetsForDomain_HostIP(t *testing.T) {
	sc := &serviceSource{publishHostIP: false}
	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{Name: "test-pod"},
		Status:     v1.PodStatus{HostIP: "192.168.1.100", PodIP: "10.0.0.1"},
	}
	ep := discoveryv1.Endpoint{
		Addresses: []string{"10.0.0.1"},
	}
	endpointSlice := &discoveryv1.EndpointSlice{
		ObjectMeta:  metav1.ObjectMeta{Name: "test-slice", Namespace: "default"},
		AddressType: discoveryv1.AddressTypeIPv4,
	}
	endpointsType := EndpointsTypeHostIP
	headlessDomain := "test.example.com"

	targets := sc.getTargetsForDomain(pod, ep, endpointSlice, endpointsType, headlessDomain)
	assert.Contains(t, targets, "192.168.1.100", "Should return HostIP when endpointsType is HostIP")
}

// Test getTargetsForDomain with NodeExternalIP and nodeInformer
func TestGetTargetsForDomain_NodeExternalIP(t *testing.T) {
	// Create a fake node informer with a node
	node := &v1.Node{
		ObjectMeta: metav1.ObjectMeta{Name: "test-node"},
		Status: v1.NodeStatus{
			Addresses: []v1.NodeAddress{
				{Type: v1.NodeExternalIP, Address: "203.0.113.10"},
				{Type: v1.NodeInternalIP, Address: "10.0.0.10"},
			},
		},
	}

	client := fake.NewClientset(node)
	kubeInformers := kubeinformers.NewSharedInformerFactory(client, 0)
	nodeInformer := kubeInformers.Core().V1().Nodes()

	// Add the node to the informer
	nodeInformer.Informer().GetStore().Add(node)

	sc := &serviceSource{
		nodeInformer: nodeInformer,
	}

	pod := &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{Name: "test-pod"},
		Spec:       v1.PodSpec{NodeName: "test-node"},
		Status:     v1.PodStatus{PodIP: "10.0.0.1"},
	}
	ep := discoveryv1.Endpoint{
		Addresses: []string{"10.0.0.1"},
	}
	endpointSlice := &discoveryv1.EndpointSlice{
		ObjectMeta:  metav1.ObjectMeta{Name: "test-slice", Namespace: "default"},
		AddressType: discoveryv1.AddressTypeIPv4,
	}
	endpointsType := EndpointsTypeNodeExternalIP
	headlessDomain := "test.example.com"

	targets := sc.getTargetsForDomain(pod, ep, endpointSlice, endpointsType, headlessDomain)
	assert.Contains(t, targets, "203.0.113.10", "Should return NodeExternalIP")
}

func TestFindPodForEndpoint(t *testing.T) {
	pods := []*v1.Pod{
		{
			ObjectMeta: metav1.ObjectMeta{Name: "pod1"},
		},
		{
			ObjectMeta: metav1.ObjectMeta{Name: "pod2"},
		},
	}

	t.Run("finds matching pod", func(t *testing.T) {
		endpoint := discoveryv1.Endpoint{
			TargetRef: &v1.ObjectReference{
				Kind: "Pod",
				Name: "pod1",
			},
		}

		result := findPodForEndpoint(endpoint, pods)
		assert.NotNil(t, result)
		assert.Equal(t, "pod1", result.Name)
	})

	t.Run("returns nil for nil TargetRef", func(t *testing.T) {
		endpoint := discoveryv1.Endpoint{
			TargetRef: nil,
		}

		result := findPodForEndpoint(endpoint, pods)
		assert.Nil(t, result)
	})

	t.Run("returns nil for non-Pod kind", func(t *testing.T) {
		endpoint := discoveryv1.Endpoint{
			TargetRef: &v1.ObjectReference{
				Kind: "Service",
				Name: "pod1",
			},
		}

		result := findPodForEndpoint(endpoint, pods)
		assert.Nil(t, result)
	})

	t.Run("returns nil for non-empty APIVersion", func(t *testing.T) {
		endpoint := discoveryv1.Endpoint{
			TargetRef: &v1.ObjectReference{
				Kind:       "Pod",
				Name:       "pod1",
				APIVersion: "v1",
			},
		}

		result := findPodForEndpoint(endpoint, pods)
		assert.Nil(t, result)
	})

	t.Run("returns nil for non-existent pod", func(t *testing.T) {
		endpoint := discoveryv1.Endpoint{
			TargetRef: &v1.ObjectReference{
				Kind: "Pod",
				Name: "non-existent-pod",
			},
		}

		result := findPodForEndpoint(endpoint, pods)
		assert.Nil(t, result)
	})
}

func TestBuildHeadlessEndpoints(t *testing.T) {
	svc := &v1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-service",
			Namespace: "default",
		},
	}

	t.Run("builds endpoints from targets", func(t *testing.T) {
		targetsByHeadlessDomainAndType := map[endpoint.EndpointKey]endpoint.Targets{
			{DNSName: "test.example.com", RecordType: endpoint.RecordTypeA}:    {"1.2.3.4", "5.6.7.8"},
			{DNSName: "test.example.com", RecordType: endpoint.RecordTypeAAAA}: {"2001:db8::1"},
		}

		result := buildHeadlessEndpoints(svc, targetsByHeadlessDomainAndType, endpoint.TTL(0))

		assert.Len(t, result, 2)

		// Check A record
		aRecord := findEndpointByType(result, endpoint.RecordTypeA)
		assert.NotNil(t, aRecord)
		assert.Equal(t, "test.example.com", aRecord.DNSName)
		assert.Contains(t, aRecord.Targets, "1.2.3.4")
		assert.Contains(t, aRecord.Targets, "5.6.7.8")
		assert.Equal(t, "service/default/test-service", aRecord.Labels[endpoint.ResourceLabelKey])

		// Check AAAA record
		aaaaRecord := findEndpointByType(result, endpoint.RecordTypeAAAA)
		assert.NotNil(t, aaaaRecord)
		assert.Equal(t, "test.example.com", aaaaRecord.DNSName)
		assert.Contains(t, aaaaRecord.Targets, "2001:db8::1")
	})

	t.Run("deduplicates targets", func(t *testing.T) {
		targetsByHeadlessDomainAndType := map[endpoint.EndpointKey]endpoint.Targets{
			{DNSName: "test.example.com", RecordType: endpoint.RecordTypeA}: {"1.2.3.4", "1.2.3.4", "5.6.7.8"},
		}

		result := buildHeadlessEndpoints(svc, targetsByHeadlessDomainAndType, endpoint.TTL(0))

		assert.Len(t, result, 1)
		assert.Len(t, result[0].Targets, 2)
		assert.Contains(t, result[0].Targets, "1.2.3.4")
		assert.Contains(t, result[0].Targets, "5.6.7.8")
	})

	t.Run("handles TTL configuration", func(t *testing.T) {
		targetsByHeadlessDomainAndType := map[endpoint.EndpointKey]endpoint.Targets{
			{DNSName: "test.example.com", RecordType: endpoint.RecordTypeA}: {"1.2.3.4"},
		}

		result := buildHeadlessEndpoints(svc, targetsByHeadlessDomainAndType, endpoint.TTL(300))

		assert.Len(t, result, 1)
		assert.Equal(t, endpoint.TTL(300), result[0].RecordTTL)
	})

	t.Run("sorts endpoints deterministically", func(t *testing.T) {
		targetsByHeadlessDomainAndType := map[endpoint.EndpointKey]endpoint.Targets{
			{DNSName: "z.example.com", RecordType: endpoint.RecordTypeA}:    {"1.2.3.4"},
			{DNSName: "a.example.com", RecordType: endpoint.RecordTypeA}:    {"5.6.7.8"},
			{DNSName: "a.example.com", RecordType: endpoint.RecordTypeAAAA}: {"2001:db8::1"},
		}

		result := buildHeadlessEndpoints(svc, targetsByHeadlessDomainAndType, endpoint.TTL(0))

		assert.Len(t, result, 3)
		// Should be sorted by DNSName first, then by RecordType
		assert.Equal(t, "a.example.com", result[0].DNSName)
		assert.Equal(t, endpoint.RecordTypeA, result[0].RecordType)
		assert.Equal(t, "a.example.com", result[1].DNSName)
		assert.Equal(t, endpoint.RecordTypeAAAA, result[1].RecordType)
		assert.Equal(t, "z.example.com", result[2].DNSName)
	})

	t.Run("handles empty targets", func(t *testing.T) {
		result := buildHeadlessEndpoints(svc, map[endpoint.EndpointKey]endpoint.Targets{}, endpoint.TTL(0))
		assert.Empty(t, result)
	})
}

// Test for missing coverage: pod with hostname creates additional headless domains
func TestProcessEndpointSlices_PodWithHostname(t *testing.T) {
	sc := &serviceSource{}

	endpointSlice := &discoveryv1.EndpointSlice{
		ObjectMeta:  metav1.ObjectMeta{Name: "slice1", Namespace: "default"},
		AddressType: discoveryv1.AddressTypeIPv4,
		Endpoints: []discoveryv1.Endpoint{
			{
				TargetRef:  &v1.ObjectReference{Kind: "Pod", Name: "test-pod"},
				Conditions: discoveryv1.EndpointConditions{Ready: new(true)},
				Addresses:  []string{"10.0.0.1"},
			},
		},
	}
	pods := []*v1.Pod{{
		ObjectMeta: metav1.ObjectMeta{Name: "test-pod"},
		Spec:       v1.PodSpec{Hostname: "my-pod"}, // Non-empty hostname
		Status:     v1.PodStatus{PodIP: "10.0.0.1"},
	}}
	hostname := "test.example.com"
	endpointsType := "IPv4"
	publishPodIPs := false
	publishNotReadyAddresses := false

	result := sc.processHeadlessEndpointsFromSlices(
		pods, []*discoveryv1.EndpointSlice{endpointSlice},
		hostname, endpointsType, publishPodIPs, publishNotReadyAddresses)

	assert.NotEmpty(t, result, "Should create targets for pod with hostname")

	// Check that both the base hostname and pod-specific hostname are created
	var foundBaseHostname, foundPodHostname bool
	for key := range result {
		if key.DNSName == "test.example.com" {
			foundBaseHostname = true
		}
		if key.DNSName == "my-pod.test.example.com" {
			foundPodHostname = true
		}
	}

	assert.True(t, foundBaseHostname, "Should create endpoint for base hostname")
	assert.True(t, foundPodHostname, "Should create endpoint for pod-specific hostname when pod.Spec.Hostname is set")
}

func TestProcessEndpoint_Service_RefObjectExist(t *testing.T) {
	elements := []runtime.Object{
		&v1.Service{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: "01",
				Name:      "foo",
				Annotations: map[string]string{
					annotations.HostnameKey: "foo.example.com",
					annotations.TargetKey:   "1.2.3",
				},
				UID: "uid-1",
			},
		},
		&v1.Service{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: "02",
				Name:      "bar",
				Annotations: map[string]string{
					annotations.HostnameKey: "bar.example.com",
					annotations.TargetKey:   "3.4.5",
				},
				UID: "uid-2",
			},
		},
	}

	fakeClient := fake.NewClientset(elements...)

	client, err := NewServiceSource(
		t.Context(),
		fakeClient,
		&Config{
			LabelFilter: labels.Everything(),
		},
	)
	require.NoError(t, err)

	endpoints, err := client.Endpoints(t.Context())
	require.NoError(t, err)
	testutils.AssertEndpointsHaveRefObject(t, endpoints, types.Service, len(elements))
}

func TestNodesExternalTrafficPolicyTypeLocal(t *testing.T) {
	now := metav1.Now()

	makeNode := func(name, ip string) *v1.Node {
		return &v1.Node{
			ObjectMeta: metav1.ObjectMeta{Name: name},
			Status:     v1.NodeStatus{Addresses: []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: ip}}},
		}
	}

	makePod := func(name, nodeName string, ready bool, deletionTimestamp *metav1.Time) *v1.Pod {
		readiness := v1.ConditionFalse
		if ready {
			readiness = v1.ConditionTrue
		}
		return &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "testing", DeletionTimestamp: deletionTimestamp},
			Spec:       v1.PodSpec{NodeName: nodeName},
			Status: v1.PodStatus{
				Phase:      v1.PodRunning,
				Conditions: []v1.PodCondition{{Type: v1.PodReady, Status: readiness}},
			},
		}
	}

	makeResourceSource := func(t *testing.T, nodes []*v1.Node, pods []*v1.Pod) *serviceSource {
		t.Helper()
		client := fake.NewClientset()
		inf := kubeinformers.NewSharedInformerFactory(client, 0)
		nodeInformer := inf.Core().V1().Nodes()
		podInformer := inf.Core().V1().Pods()
		for _, n := range nodes {
			require.NoError(t, nodeInformer.Informer().GetStore().Add(n))
		}
		for _, p := range pods {
			require.NoError(t, podInformer.Informer().GetStore().Add(p))
		}
		return &serviceSource{podInformer: podInformer, nodeInformer: nodeInformer}
	}

	svc := &v1.Service{
		ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "testing"},
		Spec: v1.ServiceSpec{
			Type:                  v1.ServiceTypeNodePort,
			ExternalTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal,
		},
	}

	// nodeNames extracts node names from a result slice for easy assertion.
	nodeNames := func(nodes []*v1.Node) []string {
		names := make([]string, len(nodes))
		for i, n := range nodes {
			names[i] = n.Name
		}
		sort.Strings(names)
		return names
	}

	t.Run("best pod wins during rolling update regardless of iteration order", func(t *testing.T) {
		// node1: not-ready replacement pod + ready existing pod (rolling update)
		// node2: single ready pod
		// The informer cache is a Go map so pod iteration order is randomised
		// each call. Over 100 iterations pod-0 will be processed before pod-1
		// roughly half the time, reliably triggering the bug if present.
		sc := makeResourceSource(t,
			[]*v1.Node{makeNode("node1", "54.10.11.1"), makeNode("node2", "54.10.11.2")},
			[]*v1.Pod{
				makePod("pod-0", "node1", false, nil), // not-ready replacement
				makePod("pod-1", "node1", true, nil),  // ready existing
				makePod("pod-2", "node2", true, nil),  // ready
			},
		)
		for i := range 100 {
			got := nodeNames(sc.nodesExternalTrafficPolicyTypeLocal(svc))
			require.Containsf(t, got, "node1", "iteration %d: node1 dropped despite having a ready pod", i)
			require.Containsf(t, got, "node2", "iteration %d: node2 dropped despite having a ready pod", i)
		}
	})

	t.Run("best pod state wins per node deterministically", func(t *testing.T) {
		// Explicitly verify that each priority upgrade path lands in the correct tier,
		// independent of iteration order.
		//   node1: notReady pod + readyNonTerminating pod  → must appear in nodes (top tier)
		//   node2: notReady pod + readyTerminating pod     → must appear in nodesReady (mid tier)
		//   node3: notReady pod only                       → must appear in nodesRunning (low tier)
		// Because node1 is in the top tier the fallback switch returns only node1.
		sc := makeResourceSource(t,
			[]*v1.Node{
				makeNode("node1", "54.10.11.1"),
				makeNode("node2", "54.10.11.2"),
				makeNode("node3", "54.10.11.3"),
			},
			[]*v1.Pod{
				makePod("pod-0", "node1", false, nil), // notReady
				makePod("pod-1", "node1", true, nil),  // readyNonTerminating — upgrades node1
				makePod("pod-2", "node2", false, nil), // notReady
				makePod("pod-3", "node2", true, &now), // readyTerminating — upgrades node2
				makePod("pod-4", "node3", false, nil), // notReady only
			},
		)
		got := nodeNames(sc.nodesExternalTrafficPolicyTypeLocal(svc))
		assert.Equal(t, []string{"node1"}, got, "fallback should select the top-tier node only")
	})

	t.Run("falls back to nodesReady when all ready pods are terminating", func(t *testing.T) {
		sc := makeResourceSource(t,
			[]*v1.Node{makeNode("node1", "54.10.11.1")},
			[]*v1.Pod{makePod("pod-0", "node1", true, &now)}, // ready + terminating
		)
		got := nodeNames(sc.nodesExternalTrafficPolicyTypeLocal(svc))
		assert.Equal(t, []string{"node1"}, got)
	})

	t.Run("falls back to nodesRunning when no pod is ready", func(t *testing.T) {
		sc := makeResourceSource(t,
			[]*v1.Node{makeNode("node1", "54.10.11.1")},
			[]*v1.Pod{makePod("pod-0", "node1", false, nil)}, // running, not ready
		)
		got := nodeNames(sc.nodesExternalTrafficPolicyTypeLocal(svc))
		assert.Equal(t, []string{"node1"}, got)
	})

	t.Run("skips pods that are not in Running phase", func(t *testing.T) {
		pendingPod := &v1.Pod{
			ObjectMeta: metav1.ObjectMeta{Name: "pod-pending", Namespace: "testing"},
			Spec:       v1.PodSpec{NodeName: "node1"},
			Status:     v1.PodStatus{Phase: v1.PodPending},
		}
		sc := makeResourceSource(t,
			[]*v1.Node{makeNode("node1", "54.10.11.1")},
			[]*v1.Pod{pendingPod},
		)
		got := nodeNames(sc.nodesExternalTrafficPolicyTypeLocal(svc))
		assert.Empty(t, got)
	})

	t.Run("skips pods whose node is not found in the informer", func(t *testing.T) {
		sc := makeResourceSource(t,
			[]*v1.Node{}, // node1 not registered
			[]*v1.Pod{makePod("pod-0", "node1", true, nil)},
		)
		got := nodeNames(sc.nodesExternalTrafficPolicyTypeLocal(svc))
		assert.Empty(t, got)
	})

	t.Run("returns nil when pod list is empty", func(t *testing.T) {
		sc := makeResourceSource(t,
			[]*v1.Node{makeNode("node1", "54.10.11.1")},
			[]*v1.Pod{},
		)
		got := sc.nodesExternalTrafficPolicyTypeLocal(svc)
		assert.Nil(t, got)
	})

	t.Run("returns only non-terminating ready nodes when mixed with terminating ready nodes", func(t *testing.T) {
		sc := makeResourceSource(t,
			[]*v1.Node{makeNode("node1", "54.10.11.1"), makeNode("node2", "54.10.11.2")},
			[]*v1.Pod{
				makePod("pod-0", "node1", true, nil),  // ready, non-terminating
				makePod("pod-1", "node2", true, &now), // ready, terminating
			},
		)
		got := nodeNames(sc.nodesExternalTrafficPolicyTypeLocal(svc))
		assert.Equal(t, []string{"node1"}, got)
	})
}

// Helper function to find endpoint by record type
func findEndpointByType(endpoints []*endpoint.Endpoint, recordType string) *endpoint.Endpoint {
	for _, ep := range endpoints {
		if ep.RecordType == recordType {
			return ep
		}
	}
	return nil
}
