sigs.k8s.io/external-dns@v0.14.1/registry/txt_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package registry
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  
    29  	"sigs.k8s.io/external-dns/endpoint"
    30  	"sigs.k8s.io/external-dns/internal/testutils"
    31  	"sigs.k8s.io/external-dns/plan"
    32  	"sigs.k8s.io/external-dns/provider"
    33  	"sigs.k8s.io/external-dns/provider/inmemory"
    34  )
    35  
    36  const (
    37  	testZone = "test-zone.example.org"
    38  )
    39  
    40  func TestTXTRegistry(t *testing.T) {
    41  	t.Run("TestNewTXTRegistry", testTXTRegistryNew)
    42  	t.Run("TestRecords", testTXTRegistryRecords)
    43  	t.Run("TestApplyChanges", testTXTRegistryApplyChanges)
    44  	t.Run("TestMissingRecords", testTXTRegistryMissingRecords)
    45  }
    46  
    47  func testTXTRegistryNew(t *testing.T) {
    48  	p := inmemory.NewInMemoryProvider()
    49  	_, err := NewTXTRegistry(p, "txt", "", "", time.Hour, "", []string{}, []string{}, false, nil)
    50  	require.Error(t, err)
    51  
    52  	_, err = NewTXTRegistry(p, "", "txt", "", time.Hour, "", []string{}, []string{}, false, nil)
    53  	require.Error(t, err)
    54  
    55  	r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
    56  	require.NoError(t, err)
    57  	assert.Equal(t, p, r.provider)
    58  
    59  	r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour, "", []string{}, []string{}, false, nil)
    60  	require.NoError(t, err)
    61  
    62  	_, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour, "", []string{}, []string{}, false, nil)
    63  	require.Error(t, err)
    64  
    65  	_, ok := r.mapper.(affixNameMapper)
    66  	require.True(t, ok)
    67  	assert.Equal(t, "owner", r.ownerID)
    68  	assert.Equal(t, p, r.provider)
    69  
    70  	aesKey := []byte(";k&l)nUC/33:{?d{3)54+,AD?]SX%yh^")
    71  	_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
    72  	require.NoError(t, err)
    73  
    74  	_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, aesKey)
    75  	require.NoError(t, err)
    76  
    77  	_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, nil)
    78  	require.Error(t, err)
    79  
    80  	r, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, true, aesKey)
    81  	require.NoError(t, err)
    82  
    83  	_, ok = r.mapper.(affixNameMapper)
    84  	assert.True(t, ok)
    85  }
    86  
    87  func testTXTRegistryRecords(t *testing.T) {
    88  	t.Run("With prefix", testTXTRegistryRecordsPrefixed)
    89  	t.Run("With suffix", testTXTRegistryRecordsSuffixed)
    90  	t.Run("No prefix", testTXTRegistryRecordsNoPrefix)
    91  	t.Run("With templated prefix", testTXTRegistryRecordsPrefixedTemplated)
    92  	t.Run("With templated suffix", testTXTRegistryRecordsSuffixedTemplated)
    93  }
    94  
    95  func testTXTRegistryRecordsPrefixed(t *testing.T) {
    96  	ctx := context.Background()
    97  	p := inmemory.NewInMemoryProvider()
    98  	p.CreateZone(testZone)
    99  	p.ApplyChanges(ctx, &plan.Changes{
   100  		Create: []*endpoint.Endpoint{
   101  			newEndpointWithOwnerAndLabels("foo.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, "", endpoint.Labels{"foo": "somefoo"}),
   102  			newEndpointWithOwnerAndLabels("bar.test-zone.example.org", "my-domain.com", endpoint.RecordTypeCNAME, "", endpoint.Labels{"bar": "somebar"}),
   103  			newEndpointWithOwner("txt.bar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   104  			newEndpointWithOwner("txt.bar.test-zone.example.org", "baz.test-zone.example.org", endpoint.RecordTypeCNAME, ""),
   105  			newEndpointWithOwner("qux.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
   106  			newEndpointWithOwnerAndLabels("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "", endpoint.Labels{"tar": "sometar"}),
   107  			newEndpointWithOwner("TxT.tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""), // case-insensitive TXT prefix
   108  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   109  			newEndpointWithOwner("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   110  			newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-1"),
   111  			newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
   112  			newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-2"),
   113  			newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
   114  			newEndpointWithOwner("*.wildcard.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   115  			newEndpointWithOwner("txt.wc.wildcard.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   116  			newEndpointWithOwner("dualstack.test-zone.example.org", "1.1.1.1", endpoint.RecordTypeA, ""),
   117  			newEndpointWithOwner("txt.dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   118  			newEndpointWithOwner("dualstack.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, ""),
   119  			newEndpointWithOwner("txt.aaaa-dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""),
   120  		},
   121  	})
   122  	expectedRecords := []*endpoint.Endpoint{
   123  		{
   124  			DNSName:    "foo.test-zone.example.org",
   125  			Targets:    endpoint.Targets{"foo.loadbalancer.com"},
   126  			RecordType: endpoint.RecordTypeCNAME,
   127  			Labels: map[string]string{
   128  				endpoint.OwnerLabelKey: "",
   129  				"foo":                  "somefoo",
   130  			},
   131  		},
   132  		{
   133  			DNSName:    "bar.test-zone.example.org",
   134  			Targets:    endpoint.Targets{"my-domain.com"},
   135  			RecordType: endpoint.RecordTypeCNAME,
   136  			Labels: map[string]string{
   137  				endpoint.OwnerLabelKey: "owner",
   138  				"bar":                  "somebar",
   139  			},
   140  		},
   141  		{
   142  			DNSName:    "txt.bar.test-zone.example.org",
   143  			Targets:    endpoint.Targets{"baz.test-zone.example.org"},
   144  			RecordType: endpoint.RecordTypeCNAME,
   145  			Labels: map[string]string{
   146  				endpoint.OwnerLabelKey: "",
   147  			},
   148  		},
   149  		{
   150  			DNSName:    "qux.test-zone.example.org",
   151  			Targets:    endpoint.Targets{"random"},
   152  			RecordType: endpoint.RecordTypeTXT,
   153  			Labels: map[string]string{
   154  				endpoint.OwnerLabelKey: "",
   155  			},
   156  		},
   157  		{
   158  			DNSName:    "tar.test-zone.example.org",
   159  			Targets:    endpoint.Targets{"tar.loadbalancer.com"},
   160  			RecordType: endpoint.RecordTypeCNAME,
   161  			Labels: map[string]string{
   162  				endpoint.OwnerLabelKey: "owner-2",
   163  				"tar":                  "sometar",
   164  			},
   165  		},
   166  		{
   167  			DNSName:    "foobar.test-zone.example.org",
   168  			Targets:    endpoint.Targets{"foobar.loadbalancer.com"},
   169  			RecordType: endpoint.RecordTypeCNAME,
   170  			Labels: map[string]string{
   171  				endpoint.OwnerLabelKey: "",
   172  			},
   173  		},
   174  		{
   175  			DNSName:       "multiple.test-zone.example.org",
   176  			Targets:       endpoint.Targets{"lb1.loadbalancer.com"},
   177  			SetIdentifier: "test-set-1",
   178  			RecordType:    endpoint.RecordTypeCNAME,
   179  			Labels: map[string]string{
   180  				endpoint.OwnerLabelKey: "",
   181  			},
   182  		},
   183  		{
   184  			DNSName:       "multiple.test-zone.example.org",
   185  			Targets:       endpoint.Targets{"lb2.loadbalancer.com"},
   186  			SetIdentifier: "test-set-2",
   187  			RecordType:    endpoint.RecordTypeCNAME,
   188  			Labels: map[string]string{
   189  				endpoint.OwnerLabelKey: "",
   190  			},
   191  		},
   192  		{
   193  			DNSName:    "*.wildcard.test-zone.example.org",
   194  			Targets:    endpoint.Targets{"foo.loadbalancer.com"},
   195  			RecordType: endpoint.RecordTypeCNAME,
   196  			Labels: map[string]string{
   197  				endpoint.OwnerLabelKey: "owner",
   198  			},
   199  		},
   200  		{
   201  			DNSName:    "dualstack.test-zone.example.org",
   202  			Targets:    endpoint.Targets{"1.1.1.1"},
   203  			RecordType: endpoint.RecordTypeA,
   204  			Labels: map[string]string{
   205  				endpoint.OwnerLabelKey: "owner",
   206  			},
   207  		},
   208  		{
   209  			DNSName:    "dualstack.test-zone.example.org",
   210  			Targets:    endpoint.Targets{"2001:DB8::1"},
   211  			RecordType: endpoint.RecordTypeAAAA,
   212  			Labels: map[string]string{
   213  				endpoint.OwnerLabelKey: "owner-2",
   214  			},
   215  		},
   216  	}
   217  
   218  	r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
   219  	records, _ := r.Records(ctx)
   220  
   221  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
   222  
   223  	// Ensure prefix is case-insensitive
   224  	r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
   225  	records, _ = r.Records(ctx)
   226  
   227  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
   228  }
   229  
   230  func testTXTRegistryRecordsSuffixed(t *testing.T) {
   231  	ctx := context.Background()
   232  	p := inmemory.NewInMemoryProvider()
   233  	p.CreateZone(testZone)
   234  	p.ApplyChanges(ctx, &plan.Changes{
   235  		Create: []*endpoint.Endpoint{
   236  			newEndpointWithOwnerAndLabels("foo.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, "", endpoint.Labels{"foo": "somefoo"}),
   237  			newEndpointWithOwnerAndLabels("bar.test-zone.example.org", "my-domain.com", endpoint.RecordTypeCNAME, "", endpoint.Labels{"bar": "somebar"}),
   238  			newEndpointWithOwner("bar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   239  			newEndpointWithOwner("bar-txt.test-zone.example.org", "baz.test-zone.example.org", endpoint.RecordTypeCNAME, ""),
   240  			newEndpointWithOwner("qux.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
   241  			newEndpointWithOwnerAndLabels("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "", endpoint.Labels{"tar": "sometar"}),
   242  			newEndpointWithOwner("tar-TxT.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""), // case-insensitive TXT prefix
   243  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   244  			newEndpointWithOwner("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   245  			newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-1"),
   246  			newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
   247  			newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-2"),
   248  			newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
   249  			newEndpointWithOwner("dualstack.test-zone.example.org", "1.1.1.1", endpoint.RecordTypeA, ""),
   250  			newEndpointWithOwner("dualstack-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   251  			newEndpointWithOwner("dualstack.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, ""),
   252  			newEndpointWithOwner("aaaa-dualstack-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""),
   253  		},
   254  	})
   255  	expectedRecords := []*endpoint.Endpoint{
   256  		{
   257  			DNSName:    "foo.test-zone.example.org",
   258  			Targets:    endpoint.Targets{"foo.loadbalancer.com"},
   259  			RecordType: endpoint.RecordTypeCNAME,
   260  			Labels: map[string]string{
   261  				endpoint.OwnerLabelKey: "",
   262  				"foo":                  "somefoo",
   263  			},
   264  		},
   265  		{
   266  			DNSName:    "bar.test-zone.example.org",
   267  			Targets:    endpoint.Targets{"my-domain.com"},
   268  			RecordType: endpoint.RecordTypeCNAME,
   269  			Labels: map[string]string{
   270  				endpoint.OwnerLabelKey: "owner",
   271  				"bar":                  "somebar",
   272  			},
   273  		},
   274  		{
   275  			DNSName:    "bar-txt.test-zone.example.org",
   276  			Targets:    endpoint.Targets{"baz.test-zone.example.org"},
   277  			RecordType: endpoint.RecordTypeCNAME,
   278  			Labels: map[string]string{
   279  				endpoint.OwnerLabelKey: "",
   280  			},
   281  		},
   282  		{
   283  			DNSName:    "qux.test-zone.example.org",
   284  			Targets:    endpoint.Targets{"random"},
   285  			RecordType: endpoint.RecordTypeTXT,
   286  			Labels: map[string]string{
   287  				endpoint.OwnerLabelKey: "",
   288  			},
   289  		},
   290  		{
   291  			DNSName:    "tar.test-zone.example.org",
   292  			Targets:    endpoint.Targets{"tar.loadbalancer.com"},
   293  			RecordType: endpoint.RecordTypeCNAME,
   294  			Labels: map[string]string{
   295  				endpoint.OwnerLabelKey: "owner-2",
   296  				"tar":                  "sometar",
   297  			},
   298  		},
   299  		{
   300  			DNSName:    "foobar.test-zone.example.org",
   301  			Targets:    endpoint.Targets{"foobar.loadbalancer.com"},
   302  			RecordType: endpoint.RecordTypeCNAME,
   303  			Labels: map[string]string{
   304  				endpoint.OwnerLabelKey: "",
   305  			},
   306  		},
   307  		{
   308  			DNSName:       "multiple.test-zone.example.org",
   309  			Targets:       endpoint.Targets{"lb1.loadbalancer.com"},
   310  			SetIdentifier: "test-set-1",
   311  			RecordType:    endpoint.RecordTypeCNAME,
   312  			Labels: map[string]string{
   313  				endpoint.OwnerLabelKey: "",
   314  			},
   315  		},
   316  		{
   317  			DNSName:       "multiple.test-zone.example.org",
   318  			Targets:       endpoint.Targets{"lb2.loadbalancer.com"},
   319  			SetIdentifier: "test-set-2",
   320  			RecordType:    endpoint.RecordTypeCNAME,
   321  			Labels: map[string]string{
   322  				endpoint.OwnerLabelKey: "",
   323  			},
   324  		},
   325  		{
   326  			DNSName:    "dualstack.test-zone.example.org",
   327  			Targets:    endpoint.Targets{"1.1.1.1"},
   328  			RecordType: endpoint.RecordTypeA,
   329  			Labels: map[string]string{
   330  				endpoint.OwnerLabelKey: "owner",
   331  			},
   332  		},
   333  		{
   334  			DNSName:    "dualstack.test-zone.example.org",
   335  			Targets:    endpoint.Targets{"2001:DB8::1"},
   336  			RecordType: endpoint.RecordTypeAAAA,
   337  			Labels: map[string]string{
   338  				endpoint.OwnerLabelKey: "owner-2",
   339  			},
   340  		},
   341  	}
   342  
   343  	r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "", []string{}, []string{}, false, nil)
   344  	records, _ := r.Records(ctx)
   345  
   346  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
   347  
   348  	// Ensure prefix is case-insensitive
   349  	r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour, "", []string{}, []string{}, false, nil)
   350  	records, _ = r.Records(ctx)
   351  
   352  	assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
   353  }
   354  
   355  func testTXTRegistryRecordsNoPrefix(t *testing.T) {
   356  	p := inmemory.NewInMemoryProvider()
   357  	ctx := context.Background()
   358  	p.CreateZone(testZone)
   359  	p.ApplyChanges(ctx, &plan.Changes{
   360  		Create: []*endpoint.Endpoint{
   361  			newEndpointWithOwner("foo.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   362  			newEndpointWithOwner("bar.test-zone.example.org", "my-domain.com", endpoint.RecordTypeCNAME, ""),
   363  			newEndpointWithOwner("alias.test-zone.example.org", "my-domain.com", endpoint.RecordTypeA, "").WithProviderSpecific("alias", "true"),
   364  			newEndpointWithOwner("cname-alias.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   365  			newEndpointWithOwner("txt.bar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, ""),
   366  			newEndpointWithOwner("txt.bar.test-zone.example.org", "baz.test-zone.example.org", endpoint.RecordTypeCNAME, ""),
   367  			newEndpointWithOwner("qux.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
   368  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   369  			newEndpointWithOwner("txt.tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""),
   370  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   371  			newEndpointWithOwner("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   372  			newEndpointWithOwner("dualstack.test-zone.example.org", "1.1.1.1", endpoint.RecordTypeA, ""),
   373  			newEndpointWithOwner("dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   374  			newEndpointWithOwner("dualstack.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, ""),
   375  			newEndpointWithOwner("aaaa-dualstack.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""),
   376  		},
   377  	})
   378  	expectedRecords := []*endpoint.Endpoint{
   379  		{
   380  			DNSName:    "foo.test-zone.example.org",
   381  			Targets:    endpoint.Targets{"foo.loadbalancer.com"},
   382  			RecordType: endpoint.RecordTypeCNAME,
   383  			Labels: map[string]string{
   384  				endpoint.OwnerLabelKey: "",
   385  			},
   386  		},
   387  		{
   388  			DNSName:    "bar.test-zone.example.org",
   389  			Targets:    endpoint.Targets{"my-domain.com"},
   390  			RecordType: endpoint.RecordTypeCNAME,
   391  			Labels: map[string]string{
   392  				endpoint.OwnerLabelKey: "",
   393  			},
   394  		},
   395  		{
   396  			DNSName:    "alias.test-zone.example.org",
   397  			Targets:    endpoint.Targets{"my-domain.com"},
   398  			RecordType: endpoint.RecordTypeA,
   399  			Labels: map[string]string{
   400  				endpoint.OwnerLabelKey: "owner",
   401  			},
   402  			ProviderSpecific: []endpoint.ProviderSpecificProperty{
   403  				{
   404  					Name:  "alias",
   405  					Value: "true",
   406  				},
   407  			},
   408  		},
   409  		{
   410  			DNSName:    "txt.bar.test-zone.example.org",
   411  			Targets:    endpoint.Targets{"baz.test-zone.example.org"},
   412  			RecordType: endpoint.RecordTypeCNAME,
   413  			Labels: map[string]string{
   414  				endpoint.OwnerLabelKey:    "owner",
   415  				endpoint.ResourceLabelKey: "ingress/default/my-ingress",
   416  			},
   417  		},
   418  		{
   419  			DNSName:    "qux.test-zone.example.org",
   420  			Targets:    endpoint.Targets{"random"},
   421  			RecordType: endpoint.RecordTypeTXT,
   422  			Labels: map[string]string{
   423  				endpoint.OwnerLabelKey: "",
   424  			},
   425  		},
   426  		{
   427  			DNSName:    "tar.test-zone.example.org",
   428  			Targets:    endpoint.Targets{"tar.loadbalancer.com"},
   429  			RecordType: endpoint.RecordTypeCNAME,
   430  			Labels: map[string]string{
   431  				endpoint.OwnerLabelKey: "",
   432  			},
   433  		},
   434  		{
   435  			DNSName:    "foobar.test-zone.example.org",
   436  			Targets:    endpoint.Targets{"foobar.loadbalancer.com"},
   437  			RecordType: endpoint.RecordTypeCNAME,
   438  			Labels: map[string]string{
   439  				endpoint.OwnerLabelKey: "owner",
   440  			},
   441  		},
   442  		{
   443  			DNSName:    "dualstack.test-zone.example.org",
   444  			Targets:    endpoint.Targets{"1.1.1.1"},
   445  			RecordType: endpoint.RecordTypeA,
   446  			Labels: map[string]string{
   447  				endpoint.OwnerLabelKey: "owner",
   448  			},
   449  		},
   450  		{
   451  			DNSName:    "dualstack.test-zone.example.org",
   452  			Targets:    endpoint.Targets{"2001:DB8::1"},
   453  			RecordType: endpoint.RecordTypeAAAA,
   454  			Labels: map[string]string{
   455  				endpoint.OwnerLabelKey: "owner-2",
   456  			},
   457  		},
   458  	}
   459  
   460  	r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
   461  	records, _ := r.Records(ctx)
   462  
   463  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
   464  }
   465  
   466  func testTXTRegistryRecordsPrefixedTemplated(t *testing.T) {
   467  	ctx := context.Background()
   468  	p := inmemory.NewInMemoryProvider()
   469  	p.CreateZone(testZone)
   470  	p.ApplyChanges(ctx, &plan.Changes{
   471  		Create: []*endpoint.Endpoint{
   472  			newEndpointWithOwner("foo.test-zone.example.org", "1.1.1.1", endpoint.RecordTypeA, ""),
   473  			newEndpointWithOwner("txt-a.foo.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   474  		},
   475  	})
   476  	expectedRecords := []*endpoint.Endpoint{
   477  		{
   478  			DNSName:    "foo.test-zone.example.org",
   479  			Targets:    endpoint.Targets{"1.1.1.1"},
   480  			RecordType: endpoint.RecordTypeA,
   481  			Labels: map[string]string{
   482  				endpoint.OwnerLabelKey: "owner",
   483  			},
   484  		},
   485  	}
   486  
   487  	r, _ := NewTXTRegistry(p, "txt-%{record_type}.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
   488  	records, _ := r.Records(ctx)
   489  
   490  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
   491  
   492  	r, _ = NewTXTRegistry(p, "TxT-%{record_type}.", "", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
   493  	records, _ = r.Records(ctx)
   494  
   495  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
   496  }
   497  
   498  func testTXTRegistryRecordsSuffixedTemplated(t *testing.T) {
   499  	ctx := context.Background()
   500  	p := inmemory.NewInMemoryProvider()
   501  	p.CreateZone(testZone)
   502  	p.ApplyChanges(ctx, &plan.Changes{
   503  		Create: []*endpoint.Endpoint{
   504  			newEndpointWithOwner("bar.test-zone.example.org", "8.8.8.8", endpoint.RecordTypeCNAME, ""),
   505  			newEndpointWithOwner("bartxtcname.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   506  		},
   507  	})
   508  	expectedRecords := []*endpoint.Endpoint{
   509  		{
   510  			DNSName:    "bar.test-zone.example.org",
   511  			Targets:    endpoint.Targets{"8.8.8.8"},
   512  			RecordType: endpoint.RecordTypeCNAME,
   513  			Labels: map[string]string{
   514  				endpoint.OwnerLabelKey: "owner",
   515  			},
   516  		},
   517  	}
   518  
   519  	r, _ := NewTXTRegistry(p, "", "txt%{record_type}", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
   520  	records, _ := r.Records(ctx)
   521  
   522  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
   523  
   524  	r, _ = NewTXTRegistry(p, "", "TxT%{record_type}", "owner", time.Hour, "wc", []string{}, []string{}, false, nil)
   525  	records, _ = r.Records(ctx)
   526  
   527  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
   528  }
   529  
   530  func testTXTRegistryApplyChanges(t *testing.T) {
   531  	t.Run("With Prefix", testTXTRegistryApplyChangesWithPrefix)
   532  	t.Run("With Templated Prefix", testTXTRegistryApplyChangesWithTemplatedPrefix)
   533  	t.Run("With Templated Suffix", testTXTRegistryApplyChangesWithTemplatedSuffix)
   534  	t.Run("With Suffix", testTXTRegistryApplyChangesWithSuffix)
   535  	t.Run("No prefix", testTXTRegistryApplyChangesNoPrefix)
   536  }
   537  
   538  func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
   539  	p := inmemory.NewInMemoryProvider()
   540  	p.CreateZone(testZone)
   541  	ctxEndpoints := []*endpoint.Endpoint{}
   542  	ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
   543  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   544  		assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey))
   545  	}
   546  	p.ApplyChanges(ctx, &plan.Changes{
   547  		Create: []*endpoint.Endpoint{
   548  			newEndpointWithOwner("foo.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   549  			newEndpointWithOwner("bar.test-zone.example.org", "my-domain.com", endpoint.RecordTypeCNAME, ""),
   550  			newEndpointWithOwner("txt.bar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   551  			newEndpointWithOwner("txt.bar.test-zone.example.org", "baz.test-zone.example.org", endpoint.RecordTypeCNAME, ""),
   552  			newEndpointWithOwner("qux.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
   553  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   554  			newEndpointWithOwner("txt.tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   555  			newEndpointWithOwner("txt.cname-tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   556  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   557  			newEndpointWithOwner("txt.foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   558  			newEndpointWithOwner("txt.cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   559  			newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-1"),
   560  			newEndpointWithOwner("txt.multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
   561  			newEndpointWithOwner("txt.cname-multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
   562  			newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-2"),
   563  			newEndpointWithOwner("txt.multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
   564  			newEndpointWithOwner("txt.cname-multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
   565  		},
   566  	})
   567  	r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
   568  
   569  	changes := &plan.Changes{
   570  		Create: []*endpoint.Endpoint{
   571  			newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
   572  			newEndpointWithOwnerResource("multiple.test-zone.example.org", "lb3.loadbalancer.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress").WithSetIdentifier("test-set-3"),
   573  			newEndpointWithOwnerResource("example", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
   574  		},
   575  		Delete: []*endpoint.Endpoint{
   576  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   577  			newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-1"),
   578  		},
   579  		UpdateNew: []*endpoint.Endpoint{
   580  			newEndpointWithOwnerResource("tar.test-zone.example.org", "new-tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2"),
   581  			newEndpointWithOwnerResource("multiple.test-zone.example.org", "new.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2").WithSetIdentifier("test-set-2"),
   582  		},
   583  		UpdateOld: []*endpoint.Endpoint{
   584  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   585  			newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-2"),
   586  		},
   587  	}
   588  	expected := &plan.Changes{
   589  		Create: []*endpoint.Endpoint{
   590  			newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress"),
   591  			newEndpointWithOwnerAndOwnedRecord("txt.new-record-1.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
   592  			newEndpointWithOwnerAndOwnedRecord("txt.cname-new-record-1.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
   593  			newEndpointWithOwnerResource("multiple.test-zone.example.org", "lb3.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress").WithSetIdentifier("test-set-3"),
   594  			newEndpointWithOwnerAndOwnedRecord("txt.multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-3"),
   595  			newEndpointWithOwnerAndOwnedRecord("txt.cname-multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-3"),
   596  			newEndpointWithOwnerResource("example", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress"),
   597  			newEndpointWithOwnerAndOwnedRecord("txt.example", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "example"),
   598  			newEndpointWithOwnerAndOwnedRecord("txt.cname-example", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "example"),
   599  		},
   600  		Delete: []*endpoint.Endpoint{
   601  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   602  			newEndpointWithOwnerAndOwnedRecord("txt.foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
   603  			newEndpointWithOwnerAndOwnedRecord("txt.cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
   604  			newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-1"),
   605  			newEndpointWithOwnerAndOwnedRecord("txt.multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-1"),
   606  			newEndpointWithOwnerAndOwnedRecord("txt.cname-multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-1"),
   607  		},
   608  		UpdateNew: []*endpoint.Endpoint{
   609  			newEndpointWithOwnerResource("tar.test-zone.example.org", "new-tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2"),
   610  			newEndpointWithOwnerAndOwnedRecord("txt.tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, "", "tar.test-zone.example.org"),
   611  			newEndpointWithOwnerAndOwnedRecord("txt.cname-tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, "", "tar.test-zone.example.org"),
   612  			newEndpointWithOwnerResource("multiple.test-zone.example.org", "new.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2").WithSetIdentifier("test-set-2"),
   613  			newEndpointWithOwnerAndOwnedRecord("txt.multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-2"),
   614  			newEndpointWithOwnerAndOwnedRecord("txt.cname-multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-2"),
   615  		},
   616  		UpdateOld: []*endpoint.Endpoint{
   617  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   618  			newEndpointWithOwnerAndOwnedRecord("txt.tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "tar.test-zone.example.org"),
   619  			newEndpointWithOwnerAndOwnedRecord("txt.cname-tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "tar.test-zone.example.org"),
   620  			newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-2"),
   621  			newEndpointWithOwnerAndOwnedRecord("txt.multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-2"),
   622  			newEndpointWithOwnerAndOwnedRecord("txt.cname-multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-2"),
   623  		},
   624  	}
   625  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   626  		mExpected := map[string][]*endpoint.Endpoint{
   627  			"Create":    expected.Create,
   628  			"UpdateNew": expected.UpdateNew,
   629  			"UpdateOld": expected.UpdateOld,
   630  			"Delete":    expected.Delete,
   631  		}
   632  		mGot := map[string][]*endpoint.Endpoint{
   633  			"Create":    got.Create,
   634  			"UpdateNew": got.UpdateNew,
   635  			"UpdateOld": got.UpdateOld,
   636  			"Delete":    got.Delete,
   637  		}
   638  		assert.True(t, testutils.SamePlanChanges(mGot, mExpected))
   639  		assert.Equal(t, nil, ctx.Value(provider.RecordsContextKey))
   640  	}
   641  	err := r.ApplyChanges(ctx, changes)
   642  	require.NoError(t, err)
   643  }
   644  
   645  func testTXTRegistryApplyChangesWithTemplatedPrefix(t *testing.T) {
   646  	p := inmemory.NewInMemoryProvider()
   647  	p.CreateZone(testZone)
   648  	ctxEndpoints := []*endpoint.Endpoint{}
   649  	ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
   650  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   651  		assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey))
   652  	}
   653  	p.ApplyChanges(ctx, &plan.Changes{
   654  		Create: []*endpoint.Endpoint{},
   655  	})
   656  	r, _ := NewTXTRegistry(p, "prefix%{record_type}.", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
   657  	changes := &plan.Changes{
   658  		Create: []*endpoint.Endpoint{
   659  			newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
   660  		},
   661  		Delete:    []*endpoint.Endpoint{},
   662  		UpdateOld: []*endpoint.Endpoint{},
   663  		UpdateNew: []*endpoint.Endpoint{},
   664  	}
   665  	expected := &plan.Changes{
   666  		Create: []*endpoint.Endpoint{
   667  			newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress"),
   668  			newEndpointWithOwnerAndOwnedRecord("prefixcname.new-record-1.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
   669  		},
   670  	}
   671  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   672  		mExpected := map[string][]*endpoint.Endpoint{
   673  			"Create":    expected.Create,
   674  			"UpdateNew": expected.UpdateNew,
   675  			"UpdateOld": expected.UpdateOld,
   676  			"Delete":    expected.Delete,
   677  		}
   678  		mGot := map[string][]*endpoint.Endpoint{
   679  			"Create":    got.Create,
   680  			"UpdateNew": got.UpdateNew,
   681  			"UpdateOld": got.UpdateOld,
   682  			"Delete":    got.Delete,
   683  		}
   684  		assert.True(t, testutils.SamePlanChanges(mGot, mExpected))
   685  		assert.Equal(t, nil, ctx.Value(provider.RecordsContextKey))
   686  	}
   687  	err := r.ApplyChanges(ctx, changes)
   688  	require.NoError(t, err)
   689  }
   690  
   691  func testTXTRegistryApplyChangesWithTemplatedSuffix(t *testing.T) {
   692  	p := inmemory.NewInMemoryProvider()
   693  	p.CreateZone(testZone)
   694  	ctxEndpoints := []*endpoint.Endpoint{}
   695  	ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
   696  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   697  		assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey))
   698  	}
   699  	r, _ := NewTXTRegistry(p, "", "-%{record_type}suffix", "owner", time.Hour, "", []string{}, []string{}, false, nil)
   700  	changes := &plan.Changes{
   701  		Create: []*endpoint.Endpoint{
   702  			newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
   703  		},
   704  		Delete:    []*endpoint.Endpoint{},
   705  		UpdateOld: []*endpoint.Endpoint{},
   706  		UpdateNew: []*endpoint.Endpoint{},
   707  	}
   708  	expected := &plan.Changes{
   709  		Create: []*endpoint.Endpoint{
   710  			newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress"),
   711  			newEndpointWithOwnerAndOwnedRecord("new-record-1-cnamesuffix.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
   712  		},
   713  	}
   714  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   715  		mExpected := map[string][]*endpoint.Endpoint{
   716  			"Create":    expected.Create,
   717  			"UpdateNew": expected.UpdateNew,
   718  			"UpdateOld": expected.UpdateOld,
   719  			"Delete":    expected.Delete,
   720  		}
   721  		mGot := map[string][]*endpoint.Endpoint{
   722  			"Create":    got.Create,
   723  			"UpdateNew": got.UpdateNew,
   724  			"UpdateOld": got.UpdateOld,
   725  			"Delete":    got.Delete,
   726  		}
   727  		assert.True(t, testutils.SamePlanChanges(mGot, mExpected))
   728  		assert.Equal(t, nil, ctx.Value(provider.RecordsContextKey))
   729  	}
   730  	err := r.ApplyChanges(ctx, changes)
   731  	require.NoError(t, err)
   732  }
   733  
   734  func testTXTRegistryApplyChangesWithSuffix(t *testing.T) {
   735  	p := inmemory.NewInMemoryProvider()
   736  	p.CreateZone(testZone)
   737  	ctxEndpoints := []*endpoint.Endpoint{}
   738  	ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
   739  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   740  		assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey))
   741  	}
   742  	p.ApplyChanges(ctx, &plan.Changes{
   743  		Create: []*endpoint.Endpoint{
   744  			newEndpointWithOwner("foo.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   745  			newEndpointWithOwner("bar.test-zone.example.org", "my-domain.com", endpoint.RecordTypeCNAME, ""),
   746  			newEndpointWithOwner("bar-txt.test-zone.example.org", "baz.test-zone.example.org", endpoint.RecordTypeCNAME, ""),
   747  			newEndpointWithOwner("bar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   748  			newEndpointWithOwner("cname-bar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   749  			newEndpointWithOwner("qux.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
   750  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   751  			newEndpointWithOwner("tar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   752  			newEndpointWithOwner("cname-tar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   753  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   754  			newEndpointWithOwner("foobar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   755  			newEndpointWithOwner("cname-foobar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   756  			newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-1"),
   757  			newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
   758  			newEndpointWithOwner("cname-multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
   759  			newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-2"),
   760  			newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
   761  			newEndpointWithOwner("cname-multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
   762  		},
   763  	})
   764  	r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "wildcard", []string{}, []string{}, false, nil)
   765  
   766  	changes := &plan.Changes{
   767  		Create: []*endpoint.Endpoint{
   768  			newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
   769  			newEndpointWithOwnerResource("multiple.test-zone.example.org", "lb3.loadbalancer.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress").WithSetIdentifier("test-set-3"),
   770  			newEndpointWithOwnerResource("example", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
   771  			newEndpointWithOwnerResource("*.wildcard.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
   772  		},
   773  		Delete: []*endpoint.Endpoint{
   774  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   775  			newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-1"),
   776  		},
   777  		UpdateNew: []*endpoint.Endpoint{
   778  			newEndpointWithOwnerResource("tar.test-zone.example.org", "new-tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2"),
   779  			newEndpointWithOwnerResource("multiple.test-zone.example.org", "new.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2").WithSetIdentifier("test-set-2"),
   780  		},
   781  		UpdateOld: []*endpoint.Endpoint{
   782  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   783  			newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-2"),
   784  		},
   785  	}
   786  	expected := &plan.Changes{
   787  		Create: []*endpoint.Endpoint{
   788  			newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress"),
   789  			newEndpointWithOwnerAndOwnedRecord("new-record-1-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
   790  			newEndpointWithOwnerAndOwnedRecord("cname-new-record-1-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
   791  			newEndpointWithOwnerResource("multiple.test-zone.example.org", "lb3.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress").WithSetIdentifier("test-set-3"),
   792  			newEndpointWithOwnerAndOwnedRecord("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-3"),
   793  			newEndpointWithOwnerAndOwnedRecord("cname-multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-3"),
   794  			newEndpointWithOwnerResource("example", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress"),
   795  			newEndpointWithOwnerAndOwnedRecord("example-txt", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "example"),
   796  			newEndpointWithOwnerAndOwnedRecord("cname-example-txt", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "example"),
   797  			newEndpointWithOwnerResource("*.wildcard.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress"),
   798  			newEndpointWithOwnerAndOwnedRecord("wildcard-txt.wildcard.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "*.wildcard.test-zone.example.org"),
   799  			newEndpointWithOwnerAndOwnedRecord("cname-wildcard-txt.wildcard.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "", "*.wildcard.test-zone.example.org"),
   800  		},
   801  		Delete: []*endpoint.Endpoint{
   802  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   803  			newEndpointWithOwnerAndOwnedRecord("foobar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
   804  			newEndpointWithOwnerAndOwnedRecord("cname-foobar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
   805  			newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-1"),
   806  			newEndpointWithOwnerAndOwnedRecord("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-1"),
   807  			newEndpointWithOwnerAndOwnedRecord("cname-multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-1"),
   808  		},
   809  		UpdateNew: []*endpoint.Endpoint{
   810  			newEndpointWithOwnerResource("tar.test-zone.example.org", "new-tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2"),
   811  			newEndpointWithOwnerAndOwnedRecord("tar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, "", "tar.test-zone.example.org"),
   812  			newEndpointWithOwnerAndOwnedRecord("cname-tar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, "", "tar.test-zone.example.org"),
   813  			newEndpointWithOwnerResource("multiple.test-zone.example.org", "new.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2").WithSetIdentifier("test-set-2"),
   814  			newEndpointWithOwnerAndOwnedRecord("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-2"),
   815  			newEndpointWithOwnerAndOwnedRecord("cname-multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-2"),
   816  		},
   817  		UpdateOld: []*endpoint.Endpoint{
   818  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   819  			newEndpointWithOwnerAndOwnedRecord("tar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "tar.test-zone.example.org"),
   820  			newEndpointWithOwnerAndOwnedRecord("cname-tar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "tar.test-zone.example.org"),
   821  			newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-2"),
   822  			newEndpointWithOwnerAndOwnedRecord("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-2"),
   823  			newEndpointWithOwnerAndOwnedRecord("cname-multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "multiple.test-zone.example.org").WithSetIdentifier("test-set-2"),
   824  		},
   825  	}
   826  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   827  		mExpected := map[string][]*endpoint.Endpoint{
   828  			"Create":    expected.Create,
   829  			"UpdateNew": expected.UpdateNew,
   830  			"UpdateOld": expected.UpdateOld,
   831  			"Delete":    expected.Delete,
   832  		}
   833  		mGot := map[string][]*endpoint.Endpoint{
   834  			"Create":    got.Create,
   835  			"UpdateNew": got.UpdateNew,
   836  			"UpdateOld": got.UpdateOld,
   837  			"Delete":    got.Delete,
   838  		}
   839  		assert.True(t, testutils.SamePlanChanges(mGot, mExpected))
   840  		assert.Equal(t, nil, ctx.Value(provider.RecordsContextKey))
   841  	}
   842  	err := r.ApplyChanges(ctx, changes)
   843  	require.NoError(t, err)
   844  }
   845  
   846  func testTXTRegistryApplyChangesNoPrefix(t *testing.T) {
   847  	p := inmemory.NewInMemoryProvider()
   848  	p.CreateZone(testZone)
   849  	ctxEndpoints := []*endpoint.Endpoint{}
   850  	ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
   851  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   852  		assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey))
   853  	}
   854  	p.ApplyChanges(ctx, &plan.Changes{
   855  		Create: []*endpoint.Endpoint{
   856  			newEndpointWithOwner("foo.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   857  			newEndpointWithOwner("bar.test-zone.example.org", "my-domain.com", endpoint.RecordTypeCNAME, ""),
   858  			newEndpointWithOwner("txt.bar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   859  			newEndpointWithOwner("txt.bar.test-zone.example.org", "baz.test-zone.example.org", endpoint.RecordTypeCNAME, ""),
   860  			newEndpointWithOwner("qux.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
   861  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   862  			newEndpointWithOwner("txt.tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   863  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   864  			newEndpointWithOwner("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   865  			newEndpointWithOwner("cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   866  		},
   867  	})
   868  	r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
   869  
   870  	changes := &plan.Changes{
   871  		Create: []*endpoint.Endpoint{
   872  			newEndpointWithOwner("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, ""),
   873  			newEndpointWithOwner("example", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, ""),
   874  			newEndpointWithOwner("new-alias.test-zone.example.org", "my-domain.com", endpoint.RecordTypeA, "").WithProviderSpecific("alias", "true"),
   875  		},
   876  		Delete: []*endpoint.Endpoint{
   877  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   878  		},
   879  		UpdateNew: []*endpoint.Endpoint{
   880  			newEndpointWithOwner("tar.test-zone.example.org", "new-tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner-2"),
   881  		},
   882  		UpdateOld: []*endpoint.Endpoint{
   883  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner-2"),
   884  		},
   885  	}
   886  	expected := &plan.Changes{
   887  		Create: []*endpoint.Endpoint{
   888  			newEndpointWithOwner("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner"),
   889  			newEndpointWithOwnerAndOwnedRecord("new-record-1.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
   890  			newEndpointWithOwnerAndOwnedRecord("cname-new-record-1.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
   891  			newEndpointWithOwner("example", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner"),
   892  			newEndpointWithOwnerAndOwnedRecord("example", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "example"),
   893  			newEndpointWithOwnerAndOwnedRecord("cname-example", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "example"),
   894  			newEndpointWithOwner("new-alias.test-zone.example.org", "my-domain.com", endpoint.RecordTypeA, "owner").WithProviderSpecific("alias", "true"),
   895  			// TODO: It's not clear why the TXT registry copies ProviderSpecificProperties to ownership records; that doesn't seem correct.
   896  			newEndpointWithOwnerAndOwnedRecord("new-alias.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "new-alias.test-zone.example.org").WithProviderSpecific("alias", "true"),
   897  			newEndpointWithOwnerAndOwnedRecord("cname-new-alias.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "new-alias.test-zone.example.org").WithProviderSpecific("alias", "true"),
   898  		},
   899  		Delete: []*endpoint.Endpoint{
   900  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
   901  			newEndpointWithOwnerAndOwnedRecord("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
   902  			newEndpointWithOwnerAndOwnedRecord("cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
   903  		},
   904  		UpdateNew: []*endpoint.Endpoint{},
   905  		UpdateOld: []*endpoint.Endpoint{},
   906  	}
   907  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
   908  		mExpected := map[string][]*endpoint.Endpoint{
   909  			"Create":    expected.Create,
   910  			"UpdateNew": expected.UpdateNew,
   911  			"UpdateOld": expected.UpdateOld,
   912  			"Delete":    expected.Delete,
   913  		}
   914  		mGot := map[string][]*endpoint.Endpoint{
   915  			"Create":    got.Create,
   916  			"UpdateNew": got.UpdateNew,
   917  			"UpdateOld": got.UpdateOld,
   918  			"Delete":    got.Delete,
   919  		}
   920  		assert.True(t, testutils.SamePlanChanges(mGot, mExpected))
   921  		assert.Equal(t, nil, ctx.Value(provider.RecordsContextKey))
   922  	}
   923  	err := r.ApplyChanges(ctx, changes)
   924  	require.NoError(t, err)
   925  }
   926  
   927  func testTXTRegistryMissingRecords(t *testing.T) {
   928  	t.Run("No prefix", testTXTRegistryMissingRecordsNoPrefix)
   929  	t.Run("With Prefix", testTXTRegistryMissingRecordsWithPrefix)
   930  }
   931  
   932  func testTXTRegistryMissingRecordsNoPrefix(t *testing.T) {
   933  	ctx := context.Background()
   934  	p := inmemory.NewInMemoryProvider()
   935  	p.CreateZone(testZone)
   936  	p.ApplyChanges(ctx, &plan.Changes{
   937  		Create: []*endpoint.Endpoint{
   938  			newEndpointWithOwner("oldformat.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   939  			newEndpointWithOwner("oldformat.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   940  			newEndpointWithOwner("oldformat2.test-zone.example.org", "bar.loadbalancer.com", endpoint.RecordTypeA, ""),
   941  			newEndpointWithOwner("oldformat2.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   942  			newEndpointWithOwner("newformat.test-zone.example.org", "foobar.nameserver.com", endpoint.RecordTypeNS, ""),
   943  			newEndpointWithOwner("ns-newformat.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   944  			newEndpointWithOwner("newformat.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   945  			newEndpointWithOwner("noheritage.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
   946  			newEndpointWithOwner("oldformat-otherowner.test-zone.example.org", "bar.loadbalancer.com", endpoint.RecordTypeA, ""),
   947  			newEndpointWithOwner("oldformat-otherowner.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=otherowner\"", endpoint.RecordTypeTXT, ""),
   948  			endpoint.NewEndpoint("unmanaged1.test-zone.example.org", endpoint.RecordTypeA, "unmanaged1.loadbalancer.com"),
   949  			endpoint.NewEndpoint("unmanaged2.test-zone.example.org", endpoint.RecordTypeCNAME, "unmanaged2.loadbalancer.com"),
   950  			newEndpointWithOwner("this-is-a-63-characters-long-label-that-we-do-expect-will-work.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
   951  			newEndpointWithOwner("this-is-a-63-characters-long-label-that-we-do-expect-will-work.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
   952  		},
   953  	})
   954  	expectedRecords := []*endpoint.Endpoint{
   955  		{
   956  			DNSName:    "oldformat.test-zone.example.org",
   957  			Targets:    endpoint.Targets{"foo.loadbalancer.com"},
   958  			RecordType: endpoint.RecordTypeCNAME,
   959  			Labels: map[string]string{
   960  				// owner was added from the TXT record's target
   961  				endpoint.OwnerLabelKey: "owner",
   962  			},
   963  			ProviderSpecific: []endpoint.ProviderSpecificProperty{
   964  				{
   965  					Name:  "txt/force-update",
   966  					Value: "true",
   967  				},
   968  			},
   969  		},
   970  		{
   971  			DNSName:    "oldformat2.test-zone.example.org",
   972  			Targets:    endpoint.Targets{"bar.loadbalancer.com"},
   973  			RecordType: endpoint.RecordTypeA,
   974  			Labels: map[string]string{
   975  				endpoint.OwnerLabelKey: "owner",
   976  			},
   977  			ProviderSpecific: []endpoint.ProviderSpecificProperty{
   978  				{
   979  					Name:  "txt/force-update",
   980  					Value: "true",
   981  				},
   982  			},
   983  		},
   984  		{
   985  			DNSName:    "newformat.test-zone.example.org",
   986  			Targets:    endpoint.Targets{"foobar.nameserver.com"},
   987  			RecordType: endpoint.RecordTypeNS,
   988  			Labels: map[string]string{
   989  				endpoint.OwnerLabelKey: "owner",
   990  			},
   991  		},
   992  		// Only TXT records with the wrong heritage are returned by Records()
   993  		{
   994  			DNSName:    "noheritage.test-zone.example.org",
   995  			Targets:    endpoint.Targets{"random"},
   996  			RecordType: endpoint.RecordTypeTXT,
   997  			Labels: map[string]string{
   998  				// No owner because it's not external-dns heritage
   999  				endpoint.OwnerLabelKey: "",
  1000  			},
  1001  		},
  1002  		{
  1003  			DNSName:    "oldformat-otherowner.test-zone.example.org",
  1004  			Targets:    endpoint.Targets{"bar.loadbalancer.com"},
  1005  			RecordType: endpoint.RecordTypeA,
  1006  			Labels: map[string]string{
  1007  				// Records() retrieves all the records of the zone, no matter the owner
  1008  				endpoint.OwnerLabelKey: "otherowner",
  1009  			},
  1010  		},
  1011  		{
  1012  			DNSName:    "unmanaged1.test-zone.example.org",
  1013  			Targets:    endpoint.Targets{"unmanaged1.loadbalancer.com"},
  1014  			RecordType: endpoint.RecordTypeA,
  1015  		},
  1016  		{
  1017  			DNSName:    "unmanaged2.test-zone.example.org",
  1018  			Targets:    endpoint.Targets{"unmanaged2.loadbalancer.com"},
  1019  			RecordType: endpoint.RecordTypeCNAME,
  1020  		},
  1021  		{
  1022  			DNSName:    "this-is-a-63-characters-long-label-that-we-do-expect-will-work.test-zone.example.org",
  1023  			Targets:    endpoint.Targets{"foo.loadbalancer.com"},
  1024  			RecordType: endpoint.RecordTypeCNAME,
  1025  			Labels: map[string]string{
  1026  				endpoint.OwnerLabelKey: "owner",
  1027  			},
  1028  		},
  1029  	}
  1030  
  1031  	r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS}, []string{}, false, nil)
  1032  	records, _ := r.Records(ctx)
  1033  
  1034  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
  1035  }
  1036  
  1037  func testTXTRegistryMissingRecordsWithPrefix(t *testing.T) {
  1038  	ctx := context.Background()
  1039  	p := inmemory.NewInMemoryProvider()
  1040  	p.CreateZone(testZone)
  1041  	p.ApplyChanges(ctx, &plan.Changes{
  1042  		Create: []*endpoint.Endpoint{
  1043  			newEndpointWithOwner("oldformat.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
  1044  			newEndpointWithOwner("txt.oldformat.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
  1045  			newEndpointWithOwner("oldformat2.test-zone.example.org", "bar.loadbalancer.com", endpoint.RecordTypeA, ""),
  1046  			newEndpointWithOwner("txt.oldformat2.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
  1047  			newEndpointWithOwner("newformat.test-zone.example.org", "foobar.nameserver.com", endpoint.RecordTypeNS, ""),
  1048  			newEndpointWithOwner("txt.ns-newformat.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
  1049  			newEndpointWithOwner("oldformat3.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
  1050  			newEndpointWithOwner("txt.oldformat3.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
  1051  			newEndpointWithOwner("txt.newformat.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
  1052  			newEndpointWithOwner("noheritage.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
  1053  			newEndpointWithOwner("oldformat-otherowner.test-zone.example.org", "bar.loadbalancer.com", endpoint.RecordTypeA, ""),
  1054  			newEndpointWithOwner("txt.oldformat-otherowner.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=otherowner\"", endpoint.RecordTypeTXT, ""),
  1055  			endpoint.NewEndpoint("unmanaged1.test-zone.example.org", endpoint.RecordTypeA, "unmanaged1.loadbalancer.com"),
  1056  			endpoint.NewEndpoint("unmanaged2.test-zone.example.org", endpoint.RecordTypeCNAME, "unmanaged2.loadbalancer.com"),
  1057  		},
  1058  	})
  1059  	expectedRecords := []*endpoint.Endpoint{
  1060  		{
  1061  			DNSName:    "oldformat.test-zone.example.org",
  1062  			Targets:    endpoint.Targets{"foo.loadbalancer.com"},
  1063  			RecordType: endpoint.RecordTypeCNAME,
  1064  			Labels: map[string]string{
  1065  				// owner was added from the TXT record's target
  1066  				endpoint.OwnerLabelKey: "owner",
  1067  			},
  1068  			ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1069  				{
  1070  					Name:  "txt/force-update",
  1071  					Value: "true",
  1072  				},
  1073  			},
  1074  		},
  1075  		{
  1076  			DNSName:    "oldformat2.test-zone.example.org",
  1077  			Targets:    endpoint.Targets{"bar.loadbalancer.com"},
  1078  			RecordType: endpoint.RecordTypeA,
  1079  			Labels: map[string]string{
  1080  				endpoint.OwnerLabelKey: "owner",
  1081  			},
  1082  			ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1083  				{
  1084  					Name:  "txt/force-update",
  1085  					Value: "true",
  1086  				},
  1087  			},
  1088  		},
  1089  		{
  1090  			DNSName:    "oldformat3.test-zone.example.org",
  1091  			Targets:    endpoint.Targets{"random"},
  1092  			RecordType: endpoint.RecordTypeTXT,
  1093  			Labels: map[string]string{
  1094  				endpoint.OwnerLabelKey: "owner",
  1095  			},
  1096  			ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1097  				{
  1098  					Name:  "txt/force-update",
  1099  					Value: "true",
  1100  				},
  1101  			},
  1102  		},
  1103  		{
  1104  			DNSName:    "newformat.test-zone.example.org",
  1105  			Targets:    endpoint.Targets{"foobar.nameserver.com"},
  1106  			RecordType: endpoint.RecordTypeNS,
  1107  			Labels: map[string]string{
  1108  				endpoint.OwnerLabelKey: "owner",
  1109  			},
  1110  		},
  1111  		{
  1112  			DNSName:    "noheritage.test-zone.example.org",
  1113  			Targets:    endpoint.Targets{"random"},
  1114  			RecordType: endpoint.RecordTypeTXT,
  1115  			Labels: map[string]string{
  1116  				// No owner because it's not external-dns heritage
  1117  				endpoint.OwnerLabelKey: "",
  1118  			},
  1119  		},
  1120  		{
  1121  			DNSName:    "oldformat-otherowner.test-zone.example.org",
  1122  			Targets:    endpoint.Targets{"bar.loadbalancer.com"},
  1123  			RecordType: endpoint.RecordTypeA,
  1124  			Labels: map[string]string{
  1125  				// All the records of the zone are retrieved, no matter the owner
  1126  				endpoint.OwnerLabelKey: "otherowner",
  1127  			},
  1128  		},
  1129  		{
  1130  			DNSName:    "unmanaged1.test-zone.example.org",
  1131  			Targets:    endpoint.Targets{"unmanaged1.loadbalancer.com"},
  1132  			RecordType: endpoint.RecordTypeA,
  1133  		},
  1134  		{
  1135  			DNSName:    "unmanaged2.test-zone.example.org",
  1136  			Targets:    endpoint.Targets{"unmanaged2.loadbalancer.com"},
  1137  			RecordType: endpoint.RecordTypeCNAME,
  1138  		},
  1139  	}
  1140  
  1141  	r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS, endpoint.RecordTypeTXT}, []string{}, false, nil)
  1142  	records, _ := r.Records(ctx)
  1143  
  1144  	assert.True(t, testutils.SameEndpoints(records, expectedRecords))
  1145  }
  1146  
  1147  func TestCacheMethods(t *testing.T) {
  1148  	cache := []*endpoint.Endpoint{
  1149  		newEndpointWithOwner("thing.com", "1.2.3.4", "A", "owner"),
  1150  		newEndpointWithOwner("thing1.com", "1.2.3.6", "A", "owner"),
  1151  		newEndpointWithOwner("thing2.com", "1.2.3.4", "CNAME", "owner"),
  1152  		newEndpointWithOwner("thing3.com", "1.2.3.4", "A", "owner"),
  1153  		newEndpointWithOwner("thing4.com", "1.2.3.4", "A", "owner"),
  1154  	}
  1155  	registry := &TXTRegistry{
  1156  		recordsCache:  cache,
  1157  		cacheInterval: time.Hour,
  1158  	}
  1159  
  1160  	expectedCacheAfterAdd := []*endpoint.Endpoint{
  1161  		newEndpointWithOwner("thing.com", "1.2.3.4", "A", "owner"),
  1162  		newEndpointWithOwner("thing1.com", "1.2.3.6", "A", "owner"),
  1163  		newEndpointWithOwner("thing2.com", "1.2.3.4", "CNAME", "owner"),
  1164  		newEndpointWithOwner("thing3.com", "1.2.3.4", "A", "owner"),
  1165  		newEndpointWithOwner("thing4.com", "1.2.3.4", "A", "owner"),
  1166  		newEndpointWithOwner("thing4.com", "2001:DB8::1", "AAAA", "owner"),
  1167  		newEndpointWithOwner("thing5.com", "1.2.3.5", "A", "owner"),
  1168  	}
  1169  
  1170  	expectedCacheAfterUpdate := []*endpoint.Endpoint{
  1171  		newEndpointWithOwner("thing1.com", "1.2.3.6", "A", "owner"),
  1172  		newEndpointWithOwner("thing2.com", "1.2.3.4", "CNAME", "owner"),
  1173  		newEndpointWithOwner("thing3.com", "1.2.3.4", "A", "owner"),
  1174  		newEndpointWithOwner("thing4.com", "1.2.3.4", "A", "owner"),
  1175  		newEndpointWithOwner("thing5.com", "1.2.3.5", "A", "owner"),
  1176  		newEndpointWithOwner("thing.com", "1.2.3.6", "A", "owner2"),
  1177  		newEndpointWithOwner("thing4.com", "2001:DB8::2", "AAAA", "owner"),
  1178  	}
  1179  
  1180  	expectedCacheAfterDelete := []*endpoint.Endpoint{
  1181  		newEndpointWithOwner("thing1.com", "1.2.3.6", "A", "owner"),
  1182  		newEndpointWithOwner("thing2.com", "1.2.3.4", "CNAME", "owner"),
  1183  		newEndpointWithOwner("thing3.com", "1.2.3.4", "A", "owner"),
  1184  		newEndpointWithOwner("thing4.com", "1.2.3.4", "A", "owner"),
  1185  		newEndpointWithOwner("thing5.com", "1.2.3.5", "A", "owner"),
  1186  	}
  1187  	// test add cache
  1188  	registry.addToCache(newEndpointWithOwner("thing4.com", "2001:DB8::1", "AAAA", "owner"))
  1189  	registry.addToCache(newEndpointWithOwner("thing5.com", "1.2.3.5", "A", "owner"))
  1190  
  1191  	if !reflect.DeepEqual(expectedCacheAfterAdd, registry.recordsCache) {
  1192  		t.Fatalf("expected endpoints should match endpoints from cache: expected %v, but got %v", expectedCacheAfterAdd, registry.recordsCache)
  1193  	}
  1194  
  1195  	// test update cache
  1196  	registry.removeFromCache(newEndpointWithOwner("thing.com", "1.2.3.4", "A", "owner"))
  1197  	registry.addToCache(newEndpointWithOwner("thing.com", "1.2.3.6", "A", "owner2"))
  1198  	registry.removeFromCache(newEndpointWithOwner("thing4.com", "2001:DB8::1", "AAAA", "owner"))
  1199  	registry.addToCache(newEndpointWithOwner("thing4.com", "2001:DB8::2", "AAAA", "owner"))
  1200  	// ensure it was updated
  1201  	if !reflect.DeepEqual(expectedCacheAfterUpdate, registry.recordsCache) {
  1202  		t.Fatalf("expected endpoints should match endpoints from cache: expected %v, but got %v", expectedCacheAfterUpdate, registry.recordsCache)
  1203  	}
  1204  
  1205  	// test deleting a record
  1206  	registry.removeFromCache(newEndpointWithOwner("thing.com", "1.2.3.6", "A", "owner2"))
  1207  	registry.removeFromCache(newEndpointWithOwner("thing4.com", "2001:DB8::2", "AAAA", "owner"))
  1208  	// ensure it was deleted
  1209  	if !reflect.DeepEqual(expectedCacheAfterDelete, registry.recordsCache) {
  1210  		t.Fatalf("expected endpoints should match endpoints from cache: expected %v, but got %v", expectedCacheAfterDelete, registry.recordsCache)
  1211  	}
  1212  }
  1213  
  1214  func TestDropPrefix(t *testing.T) {
  1215  	mapper := newaffixNameMapper("foo-%{record_type}-", "", "")
  1216  	expectedOutput := "test.example.com"
  1217  
  1218  	tests := []string{
  1219  		"foo-cname-test.example.com",
  1220  		"foo-a-test.example.com",
  1221  		"foo--test.example.com",
  1222  	}
  1223  
  1224  	for _, tc := range tests {
  1225  		t.Run(tc, func(t *testing.T) {
  1226  			actualOutput, _ := mapper.dropAffixExtractType(tc)
  1227  			assert.Equal(t, expectedOutput, actualOutput)
  1228  		})
  1229  	}
  1230  }
  1231  
  1232  func TestDropSuffix(t *testing.T) {
  1233  	mapper := newaffixNameMapper("", "-%{record_type}-foo", "")
  1234  	expectedOutput := "test.example.com"
  1235  
  1236  	tests := []string{
  1237  		"test-a-foo.example.com",
  1238  		"test--foo.example.com",
  1239  	}
  1240  
  1241  	for _, tc := range tests {
  1242  		t.Run(tc, func(t *testing.T) {
  1243  			r := strings.SplitN(tc, ".", 2)
  1244  			rClean, _ := mapper.dropAffixExtractType(r[0])
  1245  			actualOutput := rClean + "." + r[1]
  1246  			assert.Equal(t, expectedOutput, actualOutput)
  1247  		})
  1248  	}
  1249  }
  1250  
  1251  func TestExtractRecordTypeDefaultPosition(t *testing.T) {
  1252  	tests := []struct {
  1253  		input        string
  1254  		expectedName string
  1255  		expectedType string
  1256  	}{
  1257  		{
  1258  			input:        "ns-zone.example.com",
  1259  			expectedName: "zone.example.com",
  1260  			expectedType: "NS",
  1261  		},
  1262  		{
  1263  			input:        "aaaa-zone.example.com",
  1264  			expectedName: "zone.example.com",
  1265  			expectedType: "AAAA",
  1266  		},
  1267  		{
  1268  			input:        "ptr-zone.example.com",
  1269  			expectedName: "ptr-zone.example.com",
  1270  			expectedType: "",
  1271  		},
  1272  		{
  1273  			input:        "zone.example.com",
  1274  			expectedName: "zone.example.com",
  1275  			expectedType: "",
  1276  		},
  1277  	}
  1278  
  1279  	for _, tc := range tests {
  1280  		t.Run(tc.input, func(t *testing.T) {
  1281  			actualName, actualType := extractRecordTypeDefaultPosition(tc.input)
  1282  			assert.Equal(t, tc.expectedName, actualName)
  1283  			assert.Equal(t, tc.expectedType, actualType)
  1284  		})
  1285  	}
  1286  }
  1287  
  1288  func TestToEndpointNameNewTXT(t *testing.T) {
  1289  	tests := []struct {
  1290  		name       string
  1291  		mapper     affixNameMapper
  1292  		domain     string
  1293  		txtDomain  string
  1294  		recordType string
  1295  	}{
  1296  		{
  1297  			name:       "prefix",
  1298  			mapper:     newaffixNameMapper("foo", "", ""),
  1299  			domain:     "example.com",
  1300  			recordType: "A",
  1301  			txtDomain:  "fooa-example.com",
  1302  		},
  1303  		{
  1304  			name:       "suffix",
  1305  			mapper:     newaffixNameMapper("", "foo", ""),
  1306  			domain:     "example.com",
  1307  			recordType: "AAAA",
  1308  			txtDomain:  "aaaa-examplefoo.com",
  1309  		},
  1310  		{
  1311  			name:       "prefix with dash",
  1312  			mapper:     newaffixNameMapper("foo-", "", ""),
  1313  			domain:     "example.com",
  1314  			recordType: "A",
  1315  			txtDomain:  "foo-a-example.com",
  1316  		},
  1317  		{
  1318  			name:       "suffix with dash",
  1319  			mapper:     newaffixNameMapper("", "-foo", ""),
  1320  			domain:     "example.com",
  1321  			recordType: "CNAME",
  1322  			txtDomain:  "cname-example-foo.com",
  1323  		},
  1324  		{
  1325  			name:       "prefix with dot",
  1326  			mapper:     newaffixNameMapper("foo.", "", ""),
  1327  			domain:     "example.com",
  1328  			recordType: "CNAME",
  1329  			txtDomain:  "foo.cname-example.com",
  1330  		},
  1331  		{
  1332  			name:       "suffix with dot",
  1333  			mapper:     newaffixNameMapper("", ".foo", ""),
  1334  			domain:     "example.com",
  1335  			recordType: "CNAME",
  1336  			txtDomain:  "cname-example.foo.com",
  1337  		},
  1338  		{
  1339  			name:       "prefix with multiple dots",
  1340  			mapper:     newaffixNameMapper("foo.bar.", "", ""),
  1341  			domain:     "example.com",
  1342  			recordType: "CNAME",
  1343  			txtDomain:  "foo.bar.cname-example.com",
  1344  		},
  1345  		{
  1346  			name:       "suffix with multiple dots",
  1347  			mapper:     newaffixNameMapper("", ".foo.bar.test", ""),
  1348  			domain:     "example.com",
  1349  			recordType: "CNAME",
  1350  			txtDomain:  "cname-example.foo.bar.test.com",
  1351  		},
  1352  		{
  1353  			name:       "templated prefix",
  1354  			mapper:     newaffixNameMapper("%{record_type}-foo", "", ""),
  1355  			domain:     "example.com",
  1356  			recordType: "A",
  1357  			txtDomain:  "a-fooexample.com",
  1358  		},
  1359  		{
  1360  			name:       "templated suffix",
  1361  			mapper:     newaffixNameMapper("", "foo-%{record_type}", ""),
  1362  			domain:     "example.com",
  1363  			recordType: "A",
  1364  			txtDomain:  "examplefoo-a.com",
  1365  		},
  1366  		{
  1367  			name:       "templated prefix with dot",
  1368  			mapper:     newaffixNameMapper("%{record_type}foo.", "", ""),
  1369  			domain:     "example.com",
  1370  			recordType: "CNAME",
  1371  			txtDomain:  "cnamefoo.example.com",
  1372  		},
  1373  		{
  1374  			name:       "templated suffix with dot",
  1375  			mapper:     newaffixNameMapper("", ".foo%{record_type}", ""),
  1376  			domain:     "example.com",
  1377  			recordType: "A",
  1378  			txtDomain:  "example.fooa.com",
  1379  		},
  1380  		{
  1381  			name:       "templated prefix with multiple dots",
  1382  			mapper:     newaffixNameMapper("bar.%{record_type}.foo.", "", ""),
  1383  			domain:     "example.com",
  1384  			recordType: "CNAME",
  1385  			txtDomain:  "bar.cname.foo.example.com",
  1386  		},
  1387  		{
  1388  			name:       "templated suffix with multiple dots",
  1389  			mapper:     newaffixNameMapper("", ".foo%{record_type}.bar", ""),
  1390  			domain:     "example.com",
  1391  			recordType: "A",
  1392  			txtDomain:  "example.fooa.bar.com",
  1393  		},
  1394  	}
  1395  
  1396  	for _, tc := range tests {
  1397  		t.Run(tc.name, func(t *testing.T) {
  1398  			txtDomain := tc.mapper.toNewTXTName(tc.domain, tc.recordType)
  1399  			assert.Equal(t, tc.txtDomain, txtDomain)
  1400  
  1401  			domain, _ := tc.mapper.toEndpointName(txtDomain)
  1402  			assert.Equal(t, tc.domain, domain)
  1403  		})
  1404  	}
  1405  }
  1406  
  1407  func TestNewTXTScheme(t *testing.T) {
  1408  	p := inmemory.NewInMemoryProvider()
  1409  	p.CreateZone(testZone)
  1410  	ctxEndpoints := []*endpoint.Endpoint{}
  1411  	ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
  1412  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
  1413  		assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey))
  1414  	}
  1415  	p.ApplyChanges(ctx, &plan.Changes{
  1416  		Create: []*endpoint.Endpoint{
  1417  			newEndpointWithOwner("foo.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
  1418  			newEndpointWithOwner("bar.test-zone.example.org", "my-domain.com", endpoint.RecordTypeCNAME, ""),
  1419  			newEndpointWithOwner("txt.bar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
  1420  			newEndpointWithOwner("txt.bar.test-zone.example.org", "baz.test-zone.example.org", endpoint.RecordTypeCNAME, ""),
  1421  			newEndpointWithOwner("qux.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
  1422  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
  1423  			newEndpointWithOwner("txt.tar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
  1424  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
  1425  			newEndpointWithOwner("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
  1426  			newEndpointWithOwner("cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
  1427  		},
  1428  	})
  1429  	r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
  1430  
  1431  	changes := &plan.Changes{
  1432  		Create: []*endpoint.Endpoint{
  1433  			newEndpointWithOwner("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, ""),
  1434  			newEndpointWithOwner("example", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, ""),
  1435  		},
  1436  		Delete: []*endpoint.Endpoint{
  1437  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
  1438  		},
  1439  		UpdateNew: []*endpoint.Endpoint{
  1440  			newEndpointWithOwner("tar.test-zone.example.org", "new-tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner-2"),
  1441  		},
  1442  		UpdateOld: []*endpoint.Endpoint{
  1443  			newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner-2"),
  1444  		},
  1445  	}
  1446  	expected := &plan.Changes{
  1447  		Create: []*endpoint.Endpoint{
  1448  			newEndpointWithOwner("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner"),
  1449  			newEndpointWithOwnerAndOwnedRecord("new-record-1.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
  1450  			newEndpointWithOwnerAndOwnedRecord("cname-new-record-1.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "new-record-1.test-zone.example.org"),
  1451  			newEndpointWithOwner("example", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "owner"),
  1452  			newEndpointWithOwnerAndOwnedRecord("example", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "example"),
  1453  			newEndpointWithOwnerAndOwnedRecord("cname-example", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "example"),
  1454  		},
  1455  		Delete: []*endpoint.Endpoint{
  1456  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
  1457  			newEndpointWithOwnerAndOwnedRecord("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
  1458  			newEndpointWithOwnerAndOwnedRecord("cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
  1459  		},
  1460  		UpdateNew: []*endpoint.Endpoint{},
  1461  		UpdateOld: []*endpoint.Endpoint{},
  1462  	}
  1463  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
  1464  		mExpected := map[string][]*endpoint.Endpoint{
  1465  			"Create":    expected.Create,
  1466  			"UpdateNew": expected.UpdateNew,
  1467  			"UpdateOld": expected.UpdateOld,
  1468  			"Delete":    expected.Delete,
  1469  		}
  1470  		mGot := map[string][]*endpoint.Endpoint{
  1471  			"Create":    got.Create,
  1472  			"UpdateNew": got.UpdateNew,
  1473  			"UpdateOld": got.UpdateOld,
  1474  			"Delete":    got.Delete,
  1475  		}
  1476  		assert.True(t, testutils.SamePlanChanges(mGot, mExpected))
  1477  		assert.Equal(t, nil, ctx.Value(provider.RecordsContextKey))
  1478  	}
  1479  	err := r.ApplyChanges(ctx, changes)
  1480  	require.NoError(t, err)
  1481  }
  1482  
  1483  func TestGenerateTXT(t *testing.T) {
  1484  	record := newEndpointWithOwner("foo.test-zone.example.org", "new-foo.loadbalancer.com", endpoint.RecordTypeCNAME, "owner")
  1485  	expectedTXT := []*endpoint.Endpoint{
  1486  		{
  1487  			DNSName:    "foo.test-zone.example.org",
  1488  			Targets:    endpoint.Targets{"\"heritage=external-dns,external-dns/owner=owner\""},
  1489  			RecordType: endpoint.RecordTypeTXT,
  1490  			Labels: map[string]string{
  1491  				endpoint.OwnedRecordLabelKey: "foo.test-zone.example.org",
  1492  			},
  1493  		},
  1494  		{
  1495  			DNSName:    "cname-foo.test-zone.example.org",
  1496  			Targets:    endpoint.Targets{"\"heritage=external-dns,external-dns/owner=owner\""},
  1497  			RecordType: endpoint.RecordTypeTXT,
  1498  			Labels: map[string]string{
  1499  				endpoint.OwnedRecordLabelKey: "foo.test-zone.example.org",
  1500  			},
  1501  		},
  1502  	}
  1503  	p := inmemory.NewInMemoryProvider()
  1504  	p.CreateZone(testZone)
  1505  	r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
  1506  	gotTXT := r.generateTXTRecord(record)
  1507  	assert.Equal(t, expectedTXT, gotTXT)
  1508  }
  1509  
  1510  func TestGenerateTXTForAAAA(t *testing.T) {
  1511  	record := newEndpointWithOwner("foo.test-zone.example.org", "2001:DB8::1", endpoint.RecordTypeAAAA, "owner")
  1512  	expectedTXT := []*endpoint.Endpoint{
  1513  		{
  1514  			DNSName:    "aaaa-foo.test-zone.example.org",
  1515  			Targets:    endpoint.Targets{"\"heritage=external-dns,external-dns/owner=owner\""},
  1516  			RecordType: endpoint.RecordTypeTXT,
  1517  			Labels: map[string]string{
  1518  				endpoint.OwnedRecordLabelKey: "foo.test-zone.example.org",
  1519  			},
  1520  		},
  1521  	}
  1522  	p := inmemory.NewInMemoryProvider()
  1523  	p.CreateZone(testZone)
  1524  	r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
  1525  	gotTXT := r.generateTXTRecord(record)
  1526  	assert.Equal(t, expectedTXT, gotTXT)
  1527  }
  1528  
  1529  func TestFailGenerateTXT(t *testing.T) {
  1530  
  1531  	cnameRecord := &endpoint.Endpoint{
  1532  		DNSName:    "foo-some-really-big-name-not-supported-and-will-fail-000000000000000000.test-zone.example.org",
  1533  		Targets:    endpoint.Targets{"new-foo.loadbalancer.com"},
  1534  		RecordType: endpoint.RecordTypeCNAME,
  1535  		Labels:     map[string]string{},
  1536  	}
  1537  	// A bad DNS name returns empty expected TXT
  1538  	expectedTXT := []*endpoint.Endpoint{}
  1539  	p := inmemory.NewInMemoryProvider()
  1540  	p.CreateZone(testZone)
  1541  	r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, []string{}, false, nil)
  1542  	gotTXT := r.generateTXTRecord(cnameRecord)
  1543  	assert.Equal(t, expectedTXT, gotTXT)
  1544  }
  1545  
  1546  func TestTXTRegistryApplyChangesEncrypt(t *testing.T) {
  1547  	p := inmemory.NewInMemoryProvider()
  1548  	p.CreateZone(testZone)
  1549  	ctxEndpoints := []*endpoint.Endpoint{}
  1550  	ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
  1551  
  1552  	p.ApplyChanges(ctx, &plan.Changes{
  1553  		Create: []*endpoint.Endpoint{
  1554  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
  1555  			newEndpointWithOwnerAndOwnedRecord("txt.cname-foobar.test-zone.example.org", "\"h8UQ6jelUFUsEIn7SbFktc2MYXPx/q8lySqI4VwfVtVaIbb2nkHWV/88KKbuLtu7fJNzMir8ELVeVnRSY01KdiIuj7ledqZe5ailEjQaU5Z6uEKd5pgs6sH8\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
  1556  		},
  1557  	})
  1558  
  1559  	r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, []string{}, true, []byte("12345678901234567890123456789012"))
  1560  	records, _ := r.Records(ctx)
  1561  	changes := &plan.Changes{
  1562  		Delete: records,
  1563  	}
  1564  
  1565  	// ensure that encryption nonce gets reused when deleting records
  1566  	expected := &plan.Changes{
  1567  		Delete: []*endpoint.Endpoint{
  1568  			newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
  1569  			newEndpointWithOwnerAndOwnedRecord("txt.cname-foobar.test-zone.example.org", "\"h8UQ6jelUFUsEIn7SbFktc2MYXPx/q8lySqI4VwfVtVaIbb2nkHWV/88KKbuLtu7fJNzMir8ELVeVnRSY01KdiIuj7ledqZe5ailEjQaU5Z6uEKd5pgs6sH8\"", endpoint.RecordTypeTXT, "", "foobar.test-zone.example.org"),
  1570  		},
  1571  	}
  1572  
  1573  	p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
  1574  		mExpected := map[string][]*endpoint.Endpoint{
  1575  			"Delete": expected.Delete,
  1576  		}
  1577  		mGot := map[string][]*endpoint.Endpoint{
  1578  			"Delete": got.Delete,
  1579  		}
  1580  		assert.True(t, testutils.SamePlanChanges(mGot, mExpected))
  1581  		assert.Equal(t, nil, ctx.Value(provider.RecordsContextKey))
  1582  	}
  1583  	err := r.ApplyChanges(ctx, changes)
  1584  	require.NoError(t, err)
  1585  }
  1586  
  1587  // TestMultiClusterDifferentRecordTypeOwnership validates the registry handles environments where the same zone is managed by
  1588  // external-dns in different clusters and the ingress record type is different. For example one uses A records and the other
  1589  // uses CNAME. In this environment the first cluster that establishes the owner record should maintain ownership even
  1590  // if the same ingress host is deployed to the other. With the introduction of Dual Record support each record type
  1591  // was treated independently and would cause each cluster to fight over ownership. This tests ensure that the default
  1592  // Dual Stack record support only treats AAAA records independently and while keeping A and CNAME record ownership intact.
  1593  func TestMultiClusterDifferentRecordTypeOwnership(t *testing.T) {
  1594  	ctx := context.Background()
  1595  	p := inmemory.NewInMemoryProvider()
  1596  	p.CreateZone(testZone)
  1597  	p.ApplyChanges(ctx, &plan.Changes{
  1598  		Create: []*endpoint.Endpoint{
  1599  			// records on cluster using A record for ingress address
  1600  			newEndpointWithOwner("bar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=cat,external-dns/resource=ingress/default/foo\"", endpoint.RecordTypeTXT, ""),
  1601  			newEndpointWithOwner("bar.test-zone.example.org", "1.2.3.4", endpoint.RecordTypeA, ""),
  1602  		},
  1603  	})
  1604  
  1605  	r, _ := NewTXTRegistry(p, "_owner.", "", "bar", time.Hour, "", []string{}, []string{}, false, nil)
  1606  	records, _ := r.Records(ctx)
  1607  
  1608  	// new cluster has same ingress host as other cluster and uses CNAME ingress address
  1609  	cname := &endpoint.Endpoint{
  1610  		DNSName:    "bar.test-zone.example.org",
  1611  		Targets:    endpoint.Targets{"cluster-b"},
  1612  		RecordType: "CNAME",
  1613  		Labels: map[string]string{
  1614  			endpoint.ResourceLabelKey: "ingress/default/foo-127",
  1615  		},
  1616  	}
  1617  	desired := []*endpoint.Endpoint{cname}
  1618  
  1619  	pl := &plan.Plan{
  1620  		Policies:       []plan.Policy{&plan.SyncPolicy{}},
  1621  		Current:        records,
  1622  		Desired:        desired,
  1623  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
  1624  	}
  1625  
  1626  	changes := pl.Calculate()
  1627  	p.OnApplyChanges = func(ctx context.Context, changes *plan.Changes) {
  1628  		got := map[string][]*endpoint.Endpoint{
  1629  			"Create":    changes.Create,
  1630  			"UpdateNew": changes.UpdateNew,
  1631  			"UpdateOld": changes.UpdateOld,
  1632  			"Delete":    changes.Delete,
  1633  		}
  1634  		expected := map[string][]*endpoint.Endpoint{
  1635  			"Create":    {},
  1636  			"UpdateNew": {},
  1637  			"UpdateOld": {},
  1638  			"Delete":    {},
  1639  		}
  1640  		testutils.SamePlanChanges(got, expected)
  1641  	}
  1642  
  1643  	err := r.ApplyChanges(ctx, changes.Changes)
  1644  	if err != nil {
  1645  		t.Error(err)
  1646  	}
  1647  }
  1648  
  1649  /**
  1650  
  1651  helper methods
  1652  
  1653  */
  1654  
  1655  func newEndpointWithOwner(dnsName, target, recordType, ownerID string) *endpoint.Endpoint {
  1656  	return newEndpointWithOwnerAndLabels(dnsName, target, recordType, ownerID, nil)
  1657  }
  1658  
  1659  func newEndpointWithOwnerAndOwnedRecord(dnsName, target, recordType, ownerID, ownedRecord string) *endpoint.Endpoint {
  1660  	return newEndpointWithOwnerAndLabels(dnsName, target, recordType, ownerID, endpoint.Labels{endpoint.OwnedRecordLabelKey: ownedRecord})
  1661  }
  1662  
  1663  func newEndpointWithOwnerAndLabels(dnsName, target, recordType, ownerID string, labels endpoint.Labels) *endpoint.Endpoint {
  1664  	e := endpoint.NewEndpoint(dnsName, recordType, target)
  1665  	e.Labels[endpoint.OwnerLabelKey] = ownerID
  1666  	for k, v := range labels {
  1667  		e.Labels[k] = v
  1668  	}
  1669  	return e
  1670  }
  1671  
  1672  func newEndpointWithOwnerResource(dnsName, target, recordType, ownerID, resource string) *endpoint.Endpoint {
  1673  	e := endpoint.NewEndpoint(dnsName, recordType, target)
  1674  	e.Labels[endpoint.OwnerLabelKey] = ownerID
  1675  	e.Labels[endpoint.ResourceLabelKey] = resource
  1676  	return e
  1677  }