sigs.k8s.io/external-dns@v0.14.1/plan/plan_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 plan
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/suite"
    24  
    25  	"sigs.k8s.io/external-dns/endpoint"
    26  	"sigs.k8s.io/external-dns/internal/testutils"
    27  )
    28  
    29  type PlanTestSuite struct {
    30  	suite.Suite
    31  	fooV1Cname                       *endpoint.Endpoint
    32  	fooV2Cname                       *endpoint.Endpoint
    33  	fooV2CnameUppercase              *endpoint.Endpoint
    34  	fooV2TXT                         *endpoint.Endpoint
    35  	fooV2CnameNoLabel                *endpoint.Endpoint
    36  	fooV3CnameSameResource           *endpoint.Endpoint
    37  	fooA5                            *endpoint.Endpoint
    38  	fooAAAA                          *endpoint.Endpoint
    39  	dsA                              *endpoint.Endpoint
    40  	dsAAAA                           *endpoint.Endpoint
    41  	bar127A                          *endpoint.Endpoint
    42  	bar127AWithTTL                   *endpoint.Endpoint
    43  	bar127AWithProviderSpecificTrue  *endpoint.Endpoint
    44  	bar127AWithProviderSpecificFalse *endpoint.Endpoint
    45  	bar127AWithProviderSpecificUnset *endpoint.Endpoint
    46  	bar192A                          *endpoint.Endpoint
    47  	multiple1                        *endpoint.Endpoint
    48  	multiple2                        *endpoint.Endpoint
    49  	multiple3                        *endpoint.Endpoint
    50  	domainFilterFiltered1            *endpoint.Endpoint
    51  	domainFilterFiltered2            *endpoint.Endpoint
    52  	domainFilterFiltered3            *endpoint.Endpoint
    53  	domainFilterExcluded             *endpoint.Endpoint
    54  }
    55  
    56  func (suite *PlanTestSuite) SetupTest() {
    57  	suite.fooV1Cname = &endpoint.Endpoint{
    58  		DNSName:    "foo",
    59  		Targets:    endpoint.Targets{"v1"},
    60  		RecordType: "CNAME",
    61  		Labels: map[string]string{
    62  			endpoint.ResourceLabelKey: "ingress/default/foo-v1",
    63  			endpoint.OwnerLabelKey:    "pwner",
    64  		},
    65  	}
    66  	// same resource as fooV1Cname, but target is different. It will never be picked because its target lexicographically bigger than "v1"
    67  	suite.fooV3CnameSameResource = &endpoint.Endpoint{ // TODO: remove this once endpoint can support multiple targets
    68  		DNSName:    "foo",
    69  		Targets:    endpoint.Targets{"v3"},
    70  		RecordType: "CNAME",
    71  		Labels: map[string]string{
    72  			endpoint.ResourceLabelKey: "ingress/default/foo-v1",
    73  			endpoint.OwnerLabelKey:    "pwner",
    74  		},
    75  	}
    76  	suite.fooV2Cname = &endpoint.Endpoint{
    77  		DNSName:    "foo",
    78  		Targets:    endpoint.Targets{"v2"},
    79  		RecordType: "CNAME",
    80  		Labels: map[string]string{
    81  			endpoint.ResourceLabelKey: "ingress/default/foo-v2",
    82  		},
    83  	}
    84  	suite.fooV2CnameUppercase = &endpoint.Endpoint{
    85  		DNSName:    "foo",
    86  		Targets:    endpoint.Targets{"V2"},
    87  		RecordType: "CNAME",
    88  		Labels: map[string]string{
    89  			endpoint.ResourceLabelKey: "ingress/default/foo-v2",
    90  		},
    91  	}
    92  	suite.fooV2TXT = &endpoint.Endpoint{
    93  		DNSName:    "foo",
    94  		RecordType: "TXT",
    95  	}
    96  	suite.fooV2CnameNoLabel = &endpoint.Endpoint{
    97  		DNSName:    "foo",
    98  		Targets:    endpoint.Targets{"v2"},
    99  		RecordType: "CNAME",
   100  	}
   101  	suite.fooA5 = &endpoint.Endpoint{
   102  		DNSName:    "foo",
   103  		Targets:    endpoint.Targets{"5.5.5.5"},
   104  		RecordType: "A",
   105  		Labels: map[string]string{
   106  			endpoint.ResourceLabelKey: "ingress/default/foo-5",
   107  		},
   108  	}
   109  	suite.fooAAAA = &endpoint.Endpoint{
   110  		DNSName:    "foo",
   111  		Targets:    endpoint.Targets{"2001:DB8::1"},
   112  		RecordType: "AAAA",
   113  		Labels: map[string]string{
   114  			endpoint.ResourceLabelKey: "ingress/default/foo-AAAA",
   115  		},
   116  	}
   117  	suite.dsA = &endpoint.Endpoint{
   118  		DNSName:    "ds",
   119  		Targets:    endpoint.Targets{"1.1.1.1"},
   120  		RecordType: "A",
   121  		Labels: map[string]string{
   122  			endpoint.ResourceLabelKey: "ingress/default/ds",
   123  		},
   124  	}
   125  	suite.dsAAAA = &endpoint.Endpoint{
   126  		DNSName:    "ds",
   127  		Targets:    endpoint.Targets{"2001:DB8::1"},
   128  		RecordType: "AAAA",
   129  		Labels: map[string]string{
   130  			endpoint.ResourceLabelKey: "ingress/default/ds-AAAAA",
   131  		},
   132  	}
   133  	suite.bar127A = &endpoint.Endpoint{
   134  		DNSName:    "bar",
   135  		Targets:    endpoint.Targets{"127.0.0.1"},
   136  		RecordType: "A",
   137  		Labels: map[string]string{
   138  			endpoint.ResourceLabelKey: "ingress/default/bar-127",
   139  		},
   140  	}
   141  	suite.bar127AWithTTL = &endpoint.Endpoint{
   142  		DNSName:    "bar",
   143  		Targets:    endpoint.Targets{"127.0.0.1"},
   144  		RecordType: "A",
   145  		RecordTTL:  300,
   146  		Labels: map[string]string{
   147  			endpoint.ResourceLabelKey: "ingress/default/bar-127",
   148  		},
   149  	}
   150  	suite.bar127AWithProviderSpecificTrue = &endpoint.Endpoint{
   151  		DNSName:    "bar",
   152  		Targets:    endpoint.Targets{"127.0.0.1"},
   153  		RecordType: "A",
   154  		Labels: map[string]string{
   155  			endpoint.ResourceLabelKey: "ingress/default/bar-127",
   156  		},
   157  		ProviderSpecific: endpoint.ProviderSpecific{
   158  			endpoint.ProviderSpecificProperty{
   159  				Name:  "alias",
   160  				Value: "false",
   161  			},
   162  			endpoint.ProviderSpecificProperty{
   163  				Name:  "external-dns.alpha.kubernetes.io/cloudflare-proxied",
   164  				Value: "true",
   165  			},
   166  		},
   167  	}
   168  	suite.bar127AWithProviderSpecificFalse = &endpoint.Endpoint{
   169  		DNSName:    "bar",
   170  		Targets:    endpoint.Targets{"127.0.0.1"},
   171  		RecordType: "A",
   172  		Labels: map[string]string{
   173  			endpoint.ResourceLabelKey: "ingress/default/bar-127",
   174  		},
   175  		ProviderSpecific: endpoint.ProviderSpecific{
   176  			endpoint.ProviderSpecificProperty{
   177  				Name:  "external-dns.alpha.kubernetes.io/cloudflare-proxied",
   178  				Value: "false",
   179  			},
   180  			endpoint.ProviderSpecificProperty{
   181  				Name:  "alias",
   182  				Value: "false",
   183  			},
   184  		},
   185  	}
   186  	suite.bar127AWithProviderSpecificUnset = &endpoint.Endpoint{
   187  		DNSName:    "bar",
   188  		Targets:    endpoint.Targets{"127.0.0.1"},
   189  		RecordType: "A",
   190  		Labels: map[string]string{
   191  			endpoint.ResourceLabelKey: "ingress/default/bar-127",
   192  		},
   193  		ProviderSpecific: endpoint.ProviderSpecific{
   194  			endpoint.ProviderSpecificProperty{
   195  				Name:  "alias",
   196  				Value: "false",
   197  			},
   198  		},
   199  	}
   200  	suite.bar192A = &endpoint.Endpoint{
   201  		DNSName:    "bar",
   202  		Targets:    endpoint.Targets{"192.168.0.1"},
   203  		RecordType: "A",
   204  		Labels: map[string]string{
   205  			endpoint.ResourceLabelKey: "ingress/default/bar-192",
   206  		},
   207  	}
   208  	suite.multiple1 = &endpoint.Endpoint{
   209  		DNSName:       "multiple",
   210  		Targets:       endpoint.Targets{"192.168.0.1"},
   211  		RecordType:    "A",
   212  		SetIdentifier: "test-set-1",
   213  	}
   214  	suite.multiple2 = &endpoint.Endpoint{
   215  		DNSName:       "multiple",
   216  		Targets:       endpoint.Targets{"192.168.0.2"},
   217  		RecordType:    "A",
   218  		SetIdentifier: "test-set-1",
   219  	}
   220  	suite.multiple3 = &endpoint.Endpoint{
   221  		DNSName:       "multiple",
   222  		Targets:       endpoint.Targets{"192.168.0.2"},
   223  		RecordType:    "A",
   224  		SetIdentifier: "test-set-2",
   225  	}
   226  	suite.domainFilterFiltered1 = &endpoint.Endpoint{
   227  		DNSName:    "foo.domain.tld",
   228  		Targets:    endpoint.Targets{"1.2.3.4"},
   229  		RecordType: "A",
   230  	}
   231  	suite.domainFilterFiltered2 = &endpoint.Endpoint{
   232  		DNSName:    "bar.domain.tld",
   233  		Targets:    endpoint.Targets{"1.2.3.5"},
   234  		RecordType: "A",
   235  	}
   236  	suite.domainFilterFiltered3 = &endpoint.Endpoint{
   237  		DNSName:    "baz.domain.tld",
   238  		Targets:    endpoint.Targets{"1.2.3.6"},
   239  		RecordType: "A",
   240  	}
   241  	suite.domainFilterExcluded = &endpoint.Endpoint{
   242  		DNSName:    "foo.ex.domain.tld",
   243  		Targets:    endpoint.Targets{"1.1.1.1"},
   244  		RecordType: "A",
   245  	}
   246  }
   247  
   248  func (suite *PlanTestSuite) TestSyncFirstRound() {
   249  	current := []*endpoint.Endpoint{}
   250  	desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV2Cname, suite.bar127A}
   251  	expectedCreate := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar127A} // v1 is chosen because of resolver taking "min"
   252  	expectedUpdateOld := []*endpoint.Endpoint{}
   253  	expectedUpdateNew := []*endpoint.Endpoint{}
   254  	expectedDelete := []*endpoint.Endpoint{}
   255  
   256  	p := &Plan{
   257  		Policies:       []Policy{&SyncPolicy{}},
   258  		Current:        current,
   259  		Desired:        desired,
   260  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   261  	}
   262  
   263  	changes := p.Calculate().Changes
   264  	validateEntries(suite.T(), changes.Create, expectedCreate)
   265  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   266  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   267  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   268  }
   269  
   270  func (suite *PlanTestSuite) TestSyncSecondRound() {
   271  	current := []*endpoint.Endpoint{suite.fooV1Cname}
   272  	desired := []*endpoint.Endpoint{suite.fooV2Cname, suite.fooV1Cname, suite.bar127A}
   273  	expectedCreate := []*endpoint.Endpoint{suite.bar127A}
   274  	expectedUpdateOld := []*endpoint.Endpoint{}
   275  	expectedUpdateNew := []*endpoint.Endpoint{}
   276  	expectedDelete := []*endpoint.Endpoint{}
   277  
   278  	p := &Plan{
   279  		Policies:       []Policy{&SyncPolicy{}},
   280  		Current:        current,
   281  		Desired:        desired,
   282  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   283  	}
   284  
   285  	changes := p.Calculate().Changes
   286  	validateEntries(suite.T(), changes.Create, expectedCreate)
   287  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   288  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   289  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   290  }
   291  
   292  func (suite *PlanTestSuite) TestSyncSecondRoundMigration() {
   293  	current := []*endpoint.Endpoint{suite.fooV2CnameNoLabel}
   294  	desired := []*endpoint.Endpoint{suite.fooV2Cname, suite.fooV1Cname, suite.bar127A}
   295  	expectedCreate := []*endpoint.Endpoint{suite.bar127A}
   296  	expectedUpdateOld := []*endpoint.Endpoint{suite.fooV2CnameNoLabel}
   297  	expectedUpdateNew := []*endpoint.Endpoint{suite.fooV1Cname}
   298  	expectedDelete := []*endpoint.Endpoint{}
   299  
   300  	p := &Plan{
   301  		Policies:       []Policy{&SyncPolicy{}},
   302  		Current:        current,
   303  		Desired:        desired,
   304  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   305  	}
   306  
   307  	changes := p.Calculate().Changes
   308  	validateEntries(suite.T(), changes.Create, expectedCreate)
   309  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   310  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   311  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   312  }
   313  
   314  func (suite *PlanTestSuite) TestSyncSecondRoundWithTTLChange() {
   315  	current := []*endpoint.Endpoint{suite.bar127A}
   316  	desired := []*endpoint.Endpoint{suite.bar127AWithTTL}
   317  	expectedCreate := []*endpoint.Endpoint{}
   318  	expectedUpdateOld := []*endpoint.Endpoint{suite.bar127A}
   319  	expectedUpdateNew := []*endpoint.Endpoint{suite.bar127AWithTTL}
   320  	expectedDelete := []*endpoint.Endpoint{}
   321  
   322  	p := &Plan{
   323  		Policies:       []Policy{&SyncPolicy{}},
   324  		Current:        current,
   325  		Desired:        desired,
   326  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   327  	}
   328  
   329  	changes := p.Calculate().Changes
   330  	validateEntries(suite.T(), changes.Create, expectedCreate)
   331  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   332  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   333  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   334  }
   335  
   336  func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificChange() {
   337  	current := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificTrue}
   338  	desired := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificFalse}
   339  	expectedCreate := []*endpoint.Endpoint{}
   340  	expectedUpdateOld := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificTrue}
   341  	expectedUpdateNew := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificFalse}
   342  	expectedDelete := []*endpoint.Endpoint{}
   343  
   344  	p := &Plan{
   345  		Policies:       []Policy{&SyncPolicy{}},
   346  		Current:        current,
   347  		Desired:        desired,
   348  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   349  	}
   350  
   351  	changes := p.Calculate().Changes
   352  	validateEntries(suite.T(), changes.Create, expectedCreate)
   353  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   354  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   355  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   356  }
   357  
   358  func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificRemoval() {
   359  	current := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificFalse}
   360  	desired := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificUnset}
   361  	expectedCreate := []*endpoint.Endpoint{}
   362  	expectedUpdateOld := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificFalse}
   363  	expectedUpdateNew := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificUnset}
   364  	expectedDelete := []*endpoint.Endpoint{}
   365  
   366  	p := &Plan{
   367  		Policies:       []Policy{&SyncPolicy{}},
   368  		Current:        current,
   369  		Desired:        desired,
   370  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   371  	}
   372  
   373  	changes := p.Calculate().Changes
   374  	validateEntries(suite.T(), changes.Create, expectedCreate)
   375  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   376  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   377  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   378  }
   379  
   380  func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificAddition() {
   381  	current := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificUnset}
   382  	desired := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificTrue}
   383  	expectedCreate := []*endpoint.Endpoint{}
   384  	expectedUpdateOld := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificUnset}
   385  	expectedUpdateNew := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificTrue}
   386  	expectedDelete := []*endpoint.Endpoint{}
   387  
   388  	p := &Plan{
   389  		Policies:       []Policy{&SyncPolicy{}},
   390  		Current:        current,
   391  		Desired:        desired,
   392  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   393  	}
   394  
   395  	changes := p.Calculate().Changes
   396  	validateEntries(suite.T(), changes.Create, expectedCreate)
   397  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   398  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   399  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   400  }
   401  
   402  func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() {
   403  	current := []*endpoint.Endpoint{suite.fooV1Cname}
   404  	desired := []*endpoint.Endpoint{suite.fooV2Cname}
   405  
   406  	expectedCreate := []*endpoint.Endpoint{}
   407  	expectedUpdateOld := []*endpoint.Endpoint{suite.fooV1Cname}
   408  	expectedUpdateNew := []*endpoint.Endpoint{{
   409  		DNSName:    suite.fooV2Cname.DNSName,
   410  		Targets:    suite.fooV2Cname.Targets,
   411  		RecordType: suite.fooV2Cname.RecordType,
   412  		RecordTTL:  suite.fooV2Cname.RecordTTL,
   413  		Labels: map[string]string{
   414  			endpoint.ResourceLabelKey: suite.fooV2Cname.Labels[endpoint.ResourceLabelKey],
   415  			endpoint.OwnerLabelKey:    suite.fooV1Cname.Labels[endpoint.OwnerLabelKey],
   416  		},
   417  	}}
   418  	expectedDelete := []*endpoint.Endpoint{}
   419  
   420  	p := &Plan{
   421  		Policies:       []Policy{&SyncPolicy{}},
   422  		Current:        current,
   423  		Desired:        desired,
   424  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   425  	}
   426  
   427  	changes := p.Calculate().Changes
   428  	validateEntries(suite.T(), changes.Create, expectedCreate)
   429  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   430  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   431  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   432  }
   433  
   434  func (suite *PlanTestSuite) TestIdempotency() {
   435  	current := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV2Cname}
   436  	desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV2Cname}
   437  	expectedCreate := []*endpoint.Endpoint{}
   438  	expectedUpdateOld := []*endpoint.Endpoint{}
   439  	expectedUpdateNew := []*endpoint.Endpoint{}
   440  	expectedDelete := []*endpoint.Endpoint{}
   441  
   442  	p := &Plan{
   443  		Policies: []Policy{&SyncPolicy{}},
   444  		Current:  current,
   445  		Desired:  desired,
   446  	}
   447  
   448  	changes := p.Calculate().Changes
   449  	validateEntries(suite.T(), changes.Create, expectedCreate)
   450  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   451  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   452  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   453  }
   454  
   455  func (suite *PlanTestSuite) TestRecordTypeChange() {
   456  	current := []*endpoint.Endpoint{suite.fooV1Cname}
   457  	desired := []*endpoint.Endpoint{suite.fooA5}
   458  	expectedCreate := []*endpoint.Endpoint{suite.fooA5}
   459  	expectedUpdateOld := []*endpoint.Endpoint{}
   460  	expectedUpdateNew := []*endpoint.Endpoint{}
   461  	expectedDelete := []*endpoint.Endpoint{suite.fooV1Cname}
   462  
   463  	p := &Plan{
   464  		Policies:       []Policy{&SyncPolicy{}},
   465  		Current:        current,
   466  		Desired:        desired,
   467  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   468  		OwnerID:        suite.fooV1Cname.Labels[endpoint.OwnerLabelKey],
   469  	}
   470  
   471  	changes := p.Calculate().Changes
   472  	validateEntries(suite.T(), changes.Create, expectedCreate)
   473  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   474  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   475  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   476  }
   477  
   478  func (suite *PlanTestSuite) TestExistingCNameWithDualStackDesired() {
   479  	current := []*endpoint.Endpoint{suite.fooV1Cname}
   480  	desired := []*endpoint.Endpoint{suite.fooA5, suite.fooAAAA}
   481  	expectedCreate := []*endpoint.Endpoint{suite.fooA5, suite.fooAAAA}
   482  	expectedUpdateOld := []*endpoint.Endpoint{}
   483  	expectedUpdateNew := []*endpoint.Endpoint{}
   484  	expectedDelete := []*endpoint.Endpoint{suite.fooV1Cname}
   485  
   486  	p := &Plan{
   487  		Policies:       []Policy{&SyncPolicy{}},
   488  		Current:        current,
   489  		Desired:        desired,
   490  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   491  		OwnerID:        suite.fooV1Cname.Labels[endpoint.OwnerLabelKey],
   492  	}
   493  
   494  	changes := p.Calculate().Changes
   495  	validateEntries(suite.T(), changes.Create, expectedCreate)
   496  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   497  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   498  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   499  }
   500  
   501  func (suite *PlanTestSuite) TestExistingDualStackWithCNameDesired() {
   502  	suite.fooA5.Labels[endpoint.OwnerLabelKey] = "nerf"
   503  	suite.fooAAAA.Labels[endpoint.OwnerLabelKey] = "nerf"
   504  	current := []*endpoint.Endpoint{suite.fooA5, suite.fooAAAA}
   505  	desired := []*endpoint.Endpoint{suite.fooV2Cname}
   506  	expectedCreate := []*endpoint.Endpoint{suite.fooV2Cname}
   507  	expectedUpdateOld := []*endpoint.Endpoint{}
   508  	expectedUpdateNew := []*endpoint.Endpoint{}
   509  	expectedDelete := []*endpoint.Endpoint{suite.fooA5, suite.fooAAAA}
   510  
   511  	p := &Plan{
   512  		Policies:       []Policy{&SyncPolicy{}},
   513  		Current:        current,
   514  		Desired:        desired,
   515  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   516  		OwnerID:        suite.fooA5.Labels[endpoint.OwnerLabelKey],
   517  	}
   518  
   519  	changes := p.Calculate().Changes
   520  	validateEntries(suite.T(), changes.Create, expectedCreate)
   521  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   522  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   523  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   524  }
   525  
   526  // TestExistingOwnerNotMatchingDualStackDesired validates that if there is an existing
   527  // record for a domain but there is no ownership claim over it and there are desired
   528  // records no changes are planed. Only domains that have explicit ownership claims should
   529  // be updated.
   530  func (suite *PlanTestSuite) TestExistingOwnerNotMatchingDualStackDesired() {
   531  	suite.fooA5.Labels = nil
   532  	current := []*endpoint.Endpoint{suite.fooA5}
   533  	desired := []*endpoint.Endpoint{suite.fooV2Cname}
   534  	expectedCreate := []*endpoint.Endpoint{}
   535  	expectedUpdateOld := []*endpoint.Endpoint{}
   536  	expectedUpdateNew := []*endpoint.Endpoint{}
   537  	expectedDelete := []*endpoint.Endpoint{}
   538  
   539  	p := &Plan{
   540  		Policies:       []Policy{&SyncPolicy{}},
   541  		Current:        current,
   542  		Desired:        desired,
   543  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   544  		OwnerID:        "pwner",
   545  	}
   546  
   547  	changes := p.Calculate().Changes
   548  	validateEntries(suite.T(), changes.Create, expectedCreate)
   549  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   550  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   551  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   552  }
   553  
   554  // TestConflictingCurrentNonConflictingDesired is a bit of a corner case as it would indicate
   555  // that the provider is not following valid DNS rules or there may be some
   556  // caching issues. In this case since the desired records are not conflicting
   557  // the updates will end up with the conflict resolved.
   558  func (suite *PlanTestSuite) TestConflictingCurrentNonConflictingDesired() {
   559  	suite.fooA5.Labels[endpoint.OwnerLabelKey] = suite.fooV1Cname.Labels[endpoint.OwnerLabelKey]
   560  	current := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooA5}
   561  	desired := []*endpoint.Endpoint{suite.fooA5}
   562  	expectedCreate := []*endpoint.Endpoint{}
   563  	expectedUpdateOld := []*endpoint.Endpoint{}
   564  	expectedUpdateNew := []*endpoint.Endpoint{}
   565  	expectedDelete := []*endpoint.Endpoint{suite.fooV1Cname}
   566  
   567  	p := &Plan{
   568  		Policies:       []Policy{&SyncPolicy{}},
   569  		Current:        current,
   570  		Desired:        desired,
   571  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   572  		OwnerID:        suite.fooV1Cname.Labels[endpoint.OwnerLabelKey],
   573  	}
   574  
   575  	changes := p.Calculate().Changes
   576  	validateEntries(suite.T(), changes.Create, expectedCreate)
   577  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   578  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   579  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   580  }
   581  
   582  // TestConflictingCurrentNoDesired is a bit of a corner case as it would indicate
   583  // that the provider is not following valid DNS rules or there may be some
   584  // caching issues. In this case there are no desired enpoint candidates so plan
   585  // on deleting the records.
   586  func (suite *PlanTestSuite) TestConflictingCurrentNoDesired() {
   587  	suite.fooA5.Labels[endpoint.OwnerLabelKey] = suite.fooV1Cname.Labels[endpoint.OwnerLabelKey]
   588  	current := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooA5}
   589  	desired := []*endpoint.Endpoint{}
   590  	expectedCreate := []*endpoint.Endpoint{}
   591  	expectedUpdateOld := []*endpoint.Endpoint{}
   592  	expectedUpdateNew := []*endpoint.Endpoint{}
   593  	expectedDelete := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooA5}
   594  
   595  	p := &Plan{
   596  		Policies:       []Policy{&SyncPolicy{}},
   597  		Current:        current,
   598  		Desired:        desired,
   599  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   600  		OwnerID:        suite.fooV1Cname.Labels[endpoint.OwnerLabelKey],
   601  	}
   602  
   603  	changes := p.Calculate().Changes
   604  	validateEntries(suite.T(), changes.Create, expectedCreate)
   605  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   606  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   607  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   608  }
   609  
   610  // TestCurrentWithConflictingDesired simulates where the desired records result in conflicting records types.
   611  // This could be the result of multiple sources generating conflicting records types. In this case the conflict
   612  // resolver should prefer the A and AAAA record candidate and delete the other records.
   613  func (suite *PlanTestSuite) TestCurrentWithConflictingDesired() {
   614  	suite.fooV1Cname.Labels[endpoint.OwnerLabelKey] = "nerf"
   615  	current := []*endpoint.Endpoint{suite.fooV1Cname}
   616  	desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooA5, suite.fooAAAA}
   617  	expectedCreate := []*endpoint.Endpoint{suite.fooA5, suite.fooAAAA}
   618  	expectedUpdateOld := []*endpoint.Endpoint{}
   619  	expectedUpdateNew := []*endpoint.Endpoint{}
   620  	expectedDelete := []*endpoint.Endpoint{suite.fooV1Cname}
   621  
   622  	p := &Plan{
   623  		Policies:       []Policy{&SyncPolicy{}},
   624  		Current:        current,
   625  		Desired:        desired,
   626  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   627  		OwnerID:        suite.fooV1Cname.Labels[endpoint.OwnerLabelKey],
   628  	}
   629  
   630  	changes := p.Calculate().Changes
   631  	validateEntries(suite.T(), changes.Create, expectedCreate)
   632  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   633  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   634  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   635  }
   636  
   637  // TestNoCurrentWithConflictingDesired simulates where the desired records result in conflicting records types.
   638  // This could be the result of multiple sources generating conflicting records types. In this case there the
   639  // conflict resolver should prefer the A and AAAA record and drop the other candidate record types.
   640  func (suite *PlanTestSuite) TestNoCurrentWithConflictingDesired() {
   641  	current := []*endpoint.Endpoint{}
   642  	desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooA5, suite.fooAAAA}
   643  	expectedCreate := []*endpoint.Endpoint{suite.fooA5, suite.fooAAAA}
   644  	expectedUpdateOld := []*endpoint.Endpoint{}
   645  	expectedUpdateNew := []*endpoint.Endpoint{}
   646  	expectedDelete := []*endpoint.Endpoint{}
   647  
   648  	p := &Plan{
   649  		Policies:       []Policy{&SyncPolicy{}},
   650  		Current:        current,
   651  		Desired:        desired,
   652  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   653  	}
   654  
   655  	changes := p.Calculate().Changes
   656  	validateEntries(suite.T(), changes.Create, expectedCreate)
   657  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   658  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   659  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   660  }
   661  
   662  func (suite *PlanTestSuite) TestIgnoreTXT() {
   663  	current := []*endpoint.Endpoint{suite.fooV2TXT}
   664  	desired := []*endpoint.Endpoint{suite.fooV2Cname}
   665  	expectedCreate := []*endpoint.Endpoint{suite.fooV2Cname}
   666  	expectedUpdateOld := []*endpoint.Endpoint{}
   667  	expectedUpdateNew := []*endpoint.Endpoint{}
   668  	expectedDelete := []*endpoint.Endpoint{}
   669  
   670  	p := &Plan{
   671  		Policies:       []Policy{&SyncPolicy{}},
   672  		Current:        current,
   673  		Desired:        desired,
   674  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   675  	}
   676  
   677  	changes := p.Calculate().Changes
   678  	validateEntries(suite.T(), changes.Create, expectedCreate)
   679  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   680  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   681  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   682  }
   683  
   684  func (suite *PlanTestSuite) TestExcludeTXT() {
   685  	current := []*endpoint.Endpoint{suite.fooV2TXT}
   686  	desired := []*endpoint.Endpoint{suite.fooV2Cname}
   687  	expectedCreate := []*endpoint.Endpoint{suite.fooV2Cname}
   688  	expectedUpdateOld := []*endpoint.Endpoint{}
   689  	expectedUpdateNew := []*endpoint.Endpoint{}
   690  	expectedDelete := []*endpoint.Endpoint{}
   691  
   692  	p := &Plan{
   693  		Policies:       []Policy{&SyncPolicy{}},
   694  		Current:        current,
   695  		Desired:        desired,
   696  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeTXT},
   697  		ExcludeRecords: []string{endpoint.RecordTypeTXT},
   698  	}
   699  
   700  	changes := p.Calculate().Changes
   701  	validateEntries(suite.T(), changes.Create, expectedCreate)
   702  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   703  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   704  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   705  }
   706  
   707  func (suite *PlanTestSuite) TestIgnoreTargetCase() {
   708  	current := []*endpoint.Endpoint{suite.fooV2Cname}
   709  	desired := []*endpoint.Endpoint{suite.fooV2CnameUppercase}
   710  	expectedCreate := []*endpoint.Endpoint{}
   711  	expectedUpdateOld := []*endpoint.Endpoint{}
   712  	expectedUpdateNew := []*endpoint.Endpoint{}
   713  	expectedDelete := []*endpoint.Endpoint{}
   714  
   715  	p := &Plan{
   716  		Policies: []Policy{&SyncPolicy{}},
   717  		Current:  current,
   718  		Desired:  desired,
   719  	}
   720  
   721  	changes := p.Calculate().Changes
   722  	validateEntries(suite.T(), changes.Create, expectedCreate)
   723  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   724  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   725  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   726  }
   727  
   728  func (suite *PlanTestSuite) TestRemoveEndpoint() {
   729  	current := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar192A}
   730  	desired := []*endpoint.Endpoint{suite.fooV1Cname}
   731  	expectedCreate := []*endpoint.Endpoint{}
   732  	expectedUpdateOld := []*endpoint.Endpoint{}
   733  	expectedUpdateNew := []*endpoint.Endpoint{}
   734  	expectedDelete := []*endpoint.Endpoint{suite.bar192A}
   735  
   736  	p := &Plan{
   737  		Policies:       []Policy{&SyncPolicy{}},
   738  		Current:        current,
   739  		Desired:        desired,
   740  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   741  	}
   742  
   743  	changes := p.Calculate().Changes
   744  	validateEntries(suite.T(), changes.Create, expectedCreate)
   745  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   746  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   747  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   748  }
   749  
   750  func (suite *PlanTestSuite) TestRemoveEndpointWithUpsert() {
   751  	current := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar192A}
   752  	desired := []*endpoint.Endpoint{suite.fooV1Cname}
   753  	expectedCreate := []*endpoint.Endpoint{}
   754  	expectedUpdateOld := []*endpoint.Endpoint{}
   755  	expectedUpdateNew := []*endpoint.Endpoint{}
   756  	expectedDelete := []*endpoint.Endpoint{}
   757  
   758  	p := &Plan{
   759  		Policies:       []Policy{&UpsertOnlyPolicy{}},
   760  		Current:        current,
   761  		Desired:        desired,
   762  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   763  	}
   764  
   765  	changes := p.Calculate().Changes
   766  	validateEntries(suite.T(), changes.Create, expectedCreate)
   767  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   768  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   769  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   770  }
   771  
   772  func (suite *PlanTestSuite) TestMultipleRecordsSameNameDifferentSetIdentifier() {
   773  	current := []*endpoint.Endpoint{suite.multiple1}
   774  	desired := []*endpoint.Endpoint{suite.multiple2, suite.multiple3}
   775  	expectedCreate := []*endpoint.Endpoint{suite.multiple3}
   776  	expectedUpdateOld := []*endpoint.Endpoint{suite.multiple1}
   777  	expectedUpdateNew := []*endpoint.Endpoint{suite.multiple2}
   778  	expectedDelete := []*endpoint.Endpoint{}
   779  
   780  	p := &Plan{
   781  		Policies:       []Policy{&SyncPolicy{}},
   782  		Current:        current,
   783  		Desired:        desired,
   784  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   785  	}
   786  
   787  	changes := p.Calculate().Changes
   788  	validateEntries(suite.T(), changes.Create, expectedCreate)
   789  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   790  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   791  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   792  }
   793  
   794  func (suite *PlanTestSuite) TestSetIdentifierUpdateCreatesAndDeletes() {
   795  	current := []*endpoint.Endpoint{suite.multiple2}
   796  	desired := []*endpoint.Endpoint{suite.multiple3}
   797  	expectedCreate := []*endpoint.Endpoint{suite.multiple3}
   798  	expectedUpdateOld := []*endpoint.Endpoint{}
   799  	expectedUpdateNew := []*endpoint.Endpoint{}
   800  	expectedDelete := []*endpoint.Endpoint{suite.multiple2}
   801  
   802  	p := &Plan{
   803  		Policies:       []Policy{&SyncPolicy{}},
   804  		Current:        current,
   805  		Desired:        desired,
   806  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   807  	}
   808  
   809  	changes := p.Calculate().Changes
   810  	validateEntries(suite.T(), changes.Create, expectedCreate)
   811  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   812  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   813  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   814  }
   815  
   816  func (suite *PlanTestSuite) TestDomainFiltersInitial() {
   817  	current := []*endpoint.Endpoint{suite.domainFilterExcluded}
   818  	desired := []*endpoint.Endpoint{suite.domainFilterExcluded, suite.domainFilterFiltered1, suite.domainFilterFiltered2, suite.domainFilterFiltered3}
   819  	expectedCreate := []*endpoint.Endpoint{suite.domainFilterFiltered1, suite.domainFilterFiltered2, suite.domainFilterFiltered3}
   820  	expectedUpdateOld := []*endpoint.Endpoint{}
   821  	expectedUpdateNew := []*endpoint.Endpoint{}
   822  	expectedDelete := []*endpoint.Endpoint{}
   823  
   824  	domainFilter := endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"})
   825  	p := &Plan{
   826  		Policies:       []Policy{&SyncPolicy{}},
   827  		Current:        current,
   828  		Desired:        desired,
   829  		DomainFilter:   endpoint.MatchAllDomainFilters{&domainFilter},
   830  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   831  	}
   832  
   833  	changes := p.Calculate().Changes
   834  	validateEntries(suite.T(), changes.Create, expectedCreate)
   835  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   836  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   837  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   838  }
   839  
   840  func (suite *PlanTestSuite) TestDomainFiltersUpdate() {
   841  	current := []*endpoint.Endpoint{suite.domainFilterExcluded, suite.domainFilterFiltered1, suite.domainFilterFiltered2}
   842  	desired := []*endpoint.Endpoint{suite.domainFilterExcluded, suite.domainFilterFiltered1, suite.domainFilterFiltered2, suite.domainFilterFiltered3}
   843  	expectedCreate := []*endpoint.Endpoint{suite.domainFilterFiltered3}
   844  	expectedUpdateOld := []*endpoint.Endpoint{}
   845  	expectedUpdateNew := []*endpoint.Endpoint{}
   846  	expectedDelete := []*endpoint.Endpoint{}
   847  
   848  	domainFilter := endpoint.NewDomainFilterWithExclusions([]string{"domain.tld"}, []string{"ex.domain.tld"})
   849  	p := &Plan{
   850  		Policies:       []Policy{&SyncPolicy{}},
   851  		Current:        current,
   852  		Desired:        desired,
   853  		DomainFilter:   endpoint.MatchAllDomainFilters{&domainFilter},
   854  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
   855  	}
   856  
   857  	changes := p.Calculate().Changes
   858  	validateEntries(suite.T(), changes.Create, expectedCreate)
   859  	validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
   860  	validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
   861  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   862  }
   863  
   864  func (suite *PlanTestSuite) TestAAAARecords() {
   865  	current := []*endpoint.Endpoint{}
   866  	desired := []*endpoint.Endpoint{suite.fooAAAA}
   867  	expectedCreate := []*endpoint.Endpoint{suite.fooAAAA}
   868  	expectNoChanges := []*endpoint.Endpoint{}
   869  
   870  	p := &Plan{
   871  		Policies:       []Policy{&SyncPolicy{}},
   872  		Current:        current,
   873  		Desired:        desired,
   874  		ManagedRecords: []string{endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   875  	}
   876  
   877  	changes := p.Calculate().Changes
   878  	validateEntries(suite.T(), changes.Create, expectedCreate)
   879  	validateEntries(suite.T(), changes.Delete, expectNoChanges)
   880  	validateEntries(suite.T(), changes.UpdateOld, expectNoChanges)
   881  	validateEntries(suite.T(), changes.UpdateNew, expectNoChanges)
   882  }
   883  
   884  func (suite *PlanTestSuite) TestDualStackRecords() {
   885  	current := []*endpoint.Endpoint{}
   886  	desired := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
   887  	expectedCreate := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
   888  	expectNoChanges := []*endpoint.Endpoint{}
   889  
   890  	p := &Plan{
   891  		Policies:       []Policy{&SyncPolicy{}},
   892  		Current:        current,
   893  		Desired:        desired,
   894  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   895  	}
   896  
   897  	changes := p.Calculate().Changes
   898  	validateEntries(suite.T(), changes.Create, expectedCreate)
   899  	validateEntries(suite.T(), changes.Delete, expectNoChanges)
   900  	validateEntries(suite.T(), changes.UpdateOld, expectNoChanges)
   901  	validateEntries(suite.T(), changes.UpdateNew, expectNoChanges)
   902  }
   903  
   904  func (suite *PlanTestSuite) TestDualStackRecordsDelete() {
   905  	current := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
   906  	desired := []*endpoint.Endpoint{}
   907  	expectedDelete := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
   908  	expectNoChanges := []*endpoint.Endpoint{}
   909  
   910  	p := &Plan{
   911  		Policies:       []Policy{&SyncPolicy{}},
   912  		Current:        current,
   913  		Desired:        desired,
   914  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   915  	}
   916  
   917  	changes := p.Calculate().Changes
   918  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   919  	validateEntries(suite.T(), changes.Create, expectNoChanges)
   920  	validateEntries(suite.T(), changes.UpdateOld, expectNoChanges)
   921  	validateEntries(suite.T(), changes.UpdateNew, expectNoChanges)
   922  }
   923  
   924  func (suite *PlanTestSuite) TestDualStackToSingleStack() {
   925  	current := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
   926  	desired := []*endpoint.Endpoint{suite.dsA}
   927  	expectedDelete := []*endpoint.Endpoint{suite.dsAAAA}
   928  	expectNoChanges := []*endpoint.Endpoint{}
   929  
   930  	p := &Plan{
   931  		Policies:       []Policy{&SyncPolicy{}},
   932  		Current:        current,
   933  		Desired:        desired,
   934  		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
   935  	}
   936  
   937  	changes := p.Calculate().Changes
   938  	validateEntries(suite.T(), changes.Delete, expectedDelete)
   939  	validateEntries(suite.T(), changes.Create, expectNoChanges)
   940  	validateEntries(suite.T(), changes.UpdateOld, expectNoChanges)
   941  	validateEntries(suite.T(), changes.UpdateNew, expectNoChanges)
   942  }
   943  
   944  func TestPlan(t *testing.T) {
   945  	suite.Run(t, new(PlanTestSuite))
   946  }
   947  
   948  // validateEntries validates that the list of entries matches expected.
   949  func validateEntries(t *testing.T, entries, expected []*endpoint.Endpoint) {
   950  	if !testutils.SameEndpoints(entries, expected) {
   951  		t.Fatalf("expected %q to match %q", entries, expected)
   952  	}
   953  }
   954  
   955  func TestNormalizeDNSName(t *testing.T) {
   956  	records := []struct {
   957  		dnsName string
   958  		expect  string
   959  	}{
   960  		{
   961  			"3AAAA.FOO.BAR.COM    ",
   962  			"3aaaa.foo.bar.com.",
   963  		},
   964  		{
   965  			"   example.foo.com.",
   966  			"example.foo.com.",
   967  		},
   968  		{
   969  			"example123.foo.com ",
   970  			"example123.foo.com.",
   971  		},
   972  		{
   973  			"foo",
   974  			"foo.",
   975  		},
   976  		{
   977  			"123foo.bar",
   978  			"123foo.bar.",
   979  		},
   980  		{
   981  			"foo.com",
   982  			"foo.com.",
   983  		},
   984  		{
   985  			"foo.com.",
   986  			"foo.com.",
   987  		},
   988  		{
   989  			"foo123.COM",
   990  			"foo123.com.",
   991  		},
   992  		{
   993  			"my-exaMple3.FOO.BAR.COM",
   994  			"my-example3.foo.bar.com.",
   995  		},
   996  		{
   997  			"   my-example1214.FOO-1235.BAR-foo.COM   ",
   998  			"my-example1214.foo-1235.bar-foo.com.",
   999  		},
  1000  		{
  1001  			"my-example-my-example-1214.FOO-1235.BAR-foo.COM",
  1002  			"my-example-my-example-1214.foo-1235.bar-foo.com.",
  1003  		},
  1004  	}
  1005  	for _, r := range records {
  1006  		gotName := normalizeDNSName(r.dnsName)
  1007  		assert.Equal(t, r.expect, gotName)
  1008  	}
  1009  }
  1010  
  1011  func TestShouldUpdateProviderSpecific(tt *testing.T) {
  1012  	for _, test := range []struct {
  1013  		name         string
  1014  		current      *endpoint.Endpoint
  1015  		desired      *endpoint.Endpoint
  1016  		shouldUpdate bool
  1017  	}{
  1018  		{
  1019  			name: "skip AWS target health",
  1020  			current: &endpoint.Endpoint{
  1021  				DNSName: "foo.com",
  1022  				ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1023  					{Name: "aws/evaluate-target-health", Value: "true"},
  1024  				},
  1025  			},
  1026  			desired: &endpoint.Endpoint{
  1027  				DNSName: "bar.com",
  1028  				ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1029  					{Name: "aws/evaluate-target-health", Value: "true"},
  1030  				},
  1031  			},
  1032  			shouldUpdate: false,
  1033  		},
  1034  		{
  1035  			name: "custom property unchanged",
  1036  			current: &endpoint.Endpoint{
  1037  				ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1038  					{Name: "custom/property", Value: "true"},
  1039  				},
  1040  			},
  1041  			desired: &endpoint.Endpoint{
  1042  				ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1043  					{Name: "custom/property", Value: "true"},
  1044  				},
  1045  			},
  1046  			shouldUpdate: false,
  1047  		},
  1048  		{
  1049  			name: "custom property value changed",
  1050  			current: &endpoint.Endpoint{
  1051  				ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1052  					{Name: "custom/property", Value: "true"},
  1053  				},
  1054  			},
  1055  			desired: &endpoint.Endpoint{
  1056  				ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1057  					{Name: "custom/property", Value: "false"},
  1058  				},
  1059  			},
  1060  			shouldUpdate: true,
  1061  		},
  1062  		{
  1063  			name: "custom property key changed",
  1064  			current: &endpoint.Endpoint{
  1065  				ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1066  					{Name: "custom/property", Value: "true"},
  1067  				},
  1068  			},
  1069  			desired: &endpoint.Endpoint{
  1070  				ProviderSpecific: []endpoint.ProviderSpecificProperty{
  1071  					{Name: "new/property", Value: "true"},
  1072  				},
  1073  			},
  1074  			shouldUpdate: true,
  1075  		},
  1076  	} {
  1077  		tt.Run(test.name, func(t *testing.T) {
  1078  			plan := &Plan{
  1079  				Current:        []*endpoint.Endpoint{test.current},
  1080  				Desired:        []*endpoint.Endpoint{test.desired},
  1081  				ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
  1082  			}
  1083  			b := plan.shouldUpdateProviderSpecific(test.desired, test.current)
  1084  			assert.Equal(t, test.shouldUpdate, b)
  1085  		})
  1086  	}
  1087  }