sigs.k8s.io/external-dns@v0.14.1/endpoint/domain_filter_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 endpoint
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"regexp"
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  type domainFilterTest struct {
    30  	domainFilter          []string
    31  	exclusions            []string
    32  	domains               []string
    33  	expected              bool
    34  	expectedSerialization map[string][]string
    35  }
    36  
    37  type regexDomainFilterTest struct {
    38  	regex                 *regexp.Regexp
    39  	regexExclusion        *regexp.Regexp
    40  	domains               []string
    41  	expected              bool
    42  	expectedSerialization map[string]string
    43  }
    44  
    45  var domainFilterTests = []domainFilterTest{
    46  	{
    47  		[]string{"google.com.", "exaring.de", "inovex.de"},
    48  		[]string{},
    49  		[]string{"google.com", "exaring.de", "inovex.de"},
    50  		true,
    51  		map[string][]string{
    52  			"include": {"exaring.de", "google.com", "inovex.de"},
    53  		},
    54  	},
    55  	{
    56  		[]string{"google.com.", "exaring.de", "inovex.de"},
    57  		[]string{},
    58  		[]string{"google.com", "exaring.de", "inovex.de"},
    59  		true,
    60  		map[string][]string{
    61  			"include": {"exaring.de", "google.com", "inovex.de"},
    62  		},
    63  	},
    64  	{
    65  		[]string{"google.com.", "exaring.de.", "inovex.de"},
    66  		[]string{},
    67  		[]string{"google.com", "exaring.de", "inovex.de"},
    68  		true,
    69  		map[string][]string{
    70  			"include": {"exaring.de", "google.com", "inovex.de"},
    71  		},
    72  	},
    73  	{
    74  		[]string{"foo.org.      "},
    75  		[]string{},
    76  		[]string{"foo.org"},
    77  		true,
    78  		map[string][]string{
    79  			"include": {"foo.org"},
    80  		},
    81  	},
    82  	{
    83  		[]string{"   foo.org"},
    84  		[]string{},
    85  		[]string{"foo.org"},
    86  		true,
    87  		map[string][]string{
    88  			"include": {"foo.org"},
    89  		},
    90  	},
    91  	{
    92  		[]string{"foo.org."},
    93  		[]string{},
    94  		[]string{"foo.org"},
    95  		true,
    96  		map[string][]string{
    97  			"include": {"foo.org"},
    98  		},
    99  	},
   100  	{
   101  		[]string{"foo.org."},
   102  		[]string{},
   103  		[]string{"baz.org"},
   104  		false,
   105  		map[string][]string{
   106  			"include": {"foo.org"},
   107  		},
   108  	},
   109  	{
   110  		[]string{"baz.foo.org."},
   111  		[]string{},
   112  		[]string{"foo.org"},
   113  		false,
   114  		map[string][]string{
   115  			"include": {"baz.foo.org"},
   116  		},
   117  	},
   118  	{
   119  		[]string{"", "foo.org."},
   120  		[]string{},
   121  		[]string{"foo.org"},
   122  		true,
   123  		map[string][]string{
   124  			"include": {"foo.org"},
   125  		},
   126  	},
   127  	{
   128  		[]string{"", "foo.org."},
   129  		[]string{},
   130  		[]string{},
   131  		true,
   132  		map[string][]string{
   133  			"include": {"foo.org"},
   134  		},
   135  	},
   136  	{
   137  		[]string{""},
   138  		[]string{},
   139  		[]string{"foo.org"},
   140  		true,
   141  		map[string][]string{},
   142  	},
   143  	{
   144  		[]string{""},
   145  		[]string{},
   146  		[]string{},
   147  		true,
   148  		map[string][]string{},
   149  	},
   150  	{
   151  		[]string{" "},
   152  		[]string{},
   153  		[]string{},
   154  		true,
   155  		map[string][]string{},
   156  	},
   157  	{
   158  		[]string{"bar.sub.example.org"},
   159  		[]string{},
   160  		[]string{"foo.bar.sub.example.org"},
   161  		true,
   162  		map[string][]string{
   163  			"include": {"bar.sub.example.org"},
   164  		},
   165  	},
   166  	{
   167  		[]string{"example.org"},
   168  		[]string{},
   169  		[]string{"anexample.org", "test.anexample.org"},
   170  		false,
   171  		map[string][]string{
   172  			"include": {"example.org"},
   173  		},
   174  	},
   175  	{
   176  		[]string{".example.org"},
   177  		[]string{},
   178  		[]string{"anexample.org", "test.anexample.org"},
   179  		false,
   180  		map[string][]string{
   181  			"include": {".example.org"},
   182  		},
   183  	},
   184  	{
   185  		[]string{".example.org"},
   186  		[]string{},
   187  		[]string{"example.org"},
   188  		false,
   189  		map[string][]string{
   190  			"include": {".example.org"},
   191  		},
   192  	},
   193  	{
   194  		[]string{".example.org"},
   195  		[]string{},
   196  		[]string{"test.example.org"},
   197  		true,
   198  		map[string][]string{
   199  			"include": {".example.org"},
   200  		},
   201  	},
   202  	{
   203  		[]string{"anexample.org"},
   204  		[]string{},
   205  		[]string{"example.org", "test.example.org"},
   206  		false,
   207  		map[string][]string{
   208  			"include": {"anexample.org"},
   209  		},
   210  	},
   211  	{
   212  		[]string{".org"},
   213  		[]string{},
   214  		[]string{"example.org", "test.example.org", "foo.test.example.org"},
   215  		true,
   216  		map[string][]string{
   217  			"include": {".org"},
   218  		},
   219  	},
   220  	{
   221  		[]string{"example.org"},
   222  		[]string{"api.example.org"},
   223  		[]string{"example.org", "test.example.org", "foo.test.example.org"},
   224  		true,
   225  		map[string][]string{
   226  			"include": {"example.org"},
   227  			"exclude": {"api.example.org"},
   228  		},
   229  	},
   230  	{
   231  		[]string{"example.org"},
   232  		[]string{"api.example.org"},
   233  		[]string{"foo.api.example.org", "api.example.org"},
   234  		false,
   235  		map[string][]string{
   236  			"include": {"example.org"},
   237  			"exclude": {"api.example.org"},
   238  		},
   239  	},
   240  	{
   241  		[]string{"   example.org. "},
   242  		[]string{"   .api.example.org    "},
   243  		[]string{"foo.api.example.org", "bar.baz.api.example.org."},
   244  		false,
   245  		map[string][]string{
   246  			"include": {"example.org"},
   247  			"exclude": {".api.example.org"},
   248  		},
   249  	},
   250  	{
   251  		[]string{"example.org."},
   252  		[]string{"api.example.org"},
   253  		[]string{"dev-api.example.org", "qa-api.example.org"},
   254  		true,
   255  		map[string][]string{
   256  			"include": {"example.org"},
   257  			"exclude": {"api.example.org"},
   258  		},
   259  	},
   260  	{
   261  		[]string{"example.org."},
   262  		[]string{"api.example.org"},
   263  		[]string{"dev.api.example.org", "qa.api.example.org"},
   264  		false,
   265  		map[string][]string{
   266  			"include": {"example.org"},
   267  			"exclude": {"api.example.org"},
   268  		},
   269  	},
   270  	{
   271  		[]string{"example.org", "api.example.org"},
   272  		[]string{"internal.api.example.org"},
   273  		[]string{"foo.api.example.org"},
   274  		true,
   275  		map[string][]string{
   276  			"include": {"api.example.org", "example.org"},
   277  			"exclude": {"internal.api.example.org"},
   278  		},
   279  	},
   280  	{
   281  		[]string{"example.org", "api.example.org"},
   282  		[]string{"internal.api.example.org"},
   283  		[]string{"foo.internal.api.example.org"},
   284  		false,
   285  		map[string][]string{
   286  			"include": {"api.example.org", "example.org"},
   287  			"exclude": {"internal.api.example.org"},
   288  		},
   289  	},
   290  	{
   291  		[]string{"eXaMPle.ORG", "API.example.ORG"},
   292  		[]string{"Foo-Bar.Example.Org"},
   293  		[]string{"FoOoo.Api.Example.Org"},
   294  		true,
   295  		map[string][]string{
   296  			"include": {"api.example.org", "example.org"},
   297  			"exclude": {"foo-bar.example.org"},
   298  		},
   299  	},
   300  	{
   301  		[]string{"eXaMPle.ORG", "API.example.ORG"},
   302  		[]string{"api.example.org"},
   303  		[]string{"foobar.Example.Org"},
   304  		true,
   305  		map[string][]string{
   306  			"include": {"api.example.org", "example.org"},
   307  			"exclude": {"api.example.org"},
   308  		},
   309  	},
   310  	{
   311  		[]string{"eXaMPle.ORG", "API.example.ORG"},
   312  		[]string{"api.example.org"},
   313  		[]string{"foobar.API.Example.Org"},
   314  		false,
   315  		map[string][]string{
   316  			"include": {"api.example.org", "example.org"},
   317  			"exclude": {"api.example.org"},
   318  		},
   319  	},
   320  }
   321  
   322  var regexDomainFilterTests = []regexDomainFilterTest{
   323  	{
   324  		regexp.MustCompile("\\.org$"),
   325  		regexp.MustCompile(""),
   326  		[]string{"foo.org", "bar.org", "foo.bar.org"},
   327  		true,
   328  		map[string]string{
   329  			"regexInclude": "\\.org$",
   330  		},
   331  	},
   332  	{
   333  		regexp.MustCompile("\\.bar\\.org$"),
   334  		regexp.MustCompile(""),
   335  		[]string{"foo.org", "bar.org", "example.com"},
   336  		false,
   337  		map[string]string{
   338  			"regexInclude": "\\.bar\\.org$",
   339  		},
   340  	},
   341  	{
   342  		regexp.MustCompile("(?:foo|bar)\\.org$"),
   343  		regexp.MustCompile(""),
   344  		[]string{"foo.org", "bar.org", "example.foo.org", "example.bar.org", "a.example.foo.org", "a.example.bar.org"},
   345  		true,
   346  		map[string]string{
   347  			"regexInclude": "(?:foo|bar)\\.org$",
   348  		},
   349  	},
   350  	{
   351  		regexp.MustCompile("(?:foo|bar)\\.org$"),
   352  		regexp.MustCompile("^example\\.(?:foo|bar)\\.org$"),
   353  		[]string{"foo.org", "bar.org", "a.example.foo.org", "a.example.bar.org"},
   354  		true,
   355  		map[string]string{
   356  			"regexInclude": "(?:foo|bar)\\.org$",
   357  			"regexExclude": "^example\\.(?:foo|bar)\\.org$",
   358  		},
   359  	},
   360  	{
   361  		regexp.MustCompile("(?:foo|bar)\\.org$"),
   362  		regexp.MustCompile("^example\\.(?:foo|bar)\\.org$"),
   363  		[]string{"example.foo.org", "example.bar.org"},
   364  		false,
   365  		map[string]string{
   366  			"regexInclude": "(?:foo|bar)\\.org$",
   367  			"regexExclude": "^example\\.(?:foo|bar)\\.org$",
   368  		},
   369  	},
   370  	{
   371  		regexp.MustCompile("(?:foo|bar)\\.org$"),
   372  		regexp.MustCompile("^example\\.(?:foo|bar)\\.org$"),
   373  		[]string{"foo.org", "bar.org", "a.example.foo.org", "a.example.bar.org"},
   374  		true,
   375  		map[string]string{
   376  			"regexInclude": "(?:foo|bar)\\.org$",
   377  			"regexExclude": "^example\\.(?:foo|bar)\\.org$",
   378  		},
   379  	},
   380  }
   381  
   382  func TestDomainFilterMatch(t *testing.T) {
   383  	for i, tt := range domainFilterTests {
   384  		if len(tt.exclusions) > 0 {
   385  			t.Logf("NewDomainFilter() doesn't support exclusions - skipping test %+v", tt)
   386  			continue
   387  		}
   388  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   389  			domainFilter := NewDomainFilter(tt.domainFilter)
   390  
   391  			assertSerializes(t, domainFilter, tt.expectedSerialization)
   392  			deserialized := deserialize(t, map[string][]string{
   393  				"include": tt.domainFilter,
   394  			})
   395  
   396  			for _, domain := range tt.domains {
   397  				assert.Equal(t, tt.expected, domainFilter.Match(domain), "%v", domain)
   398  				assert.Equal(t, tt.expected, domainFilter.Match(domain+"."), "%v", domain+".")
   399  
   400  				assert.Equal(t, tt.expected, deserialized.Match(domain), "deserialized %v", domain)
   401  				assert.Equal(t, tt.expected, deserialized.Match(domain+"."), "deserialized %v", domain+".")
   402  			}
   403  		})
   404  	}
   405  }
   406  
   407  func TestDomainFilterWithExclusions(t *testing.T) {
   408  	for i, tt := range domainFilterTests {
   409  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   410  			if len(tt.exclusions) == 0 {
   411  				tt.exclusions = append(tt.exclusions, "")
   412  			}
   413  			domainFilter := NewDomainFilterWithExclusions(tt.domainFilter, tt.exclusions)
   414  
   415  			assertSerializes(t, domainFilter, tt.expectedSerialization)
   416  			deserialized := deserialize(t, map[string][]string{
   417  				"include": tt.domainFilter,
   418  				"exclude": tt.exclusions,
   419  			})
   420  
   421  			for _, domain := range tt.domains {
   422  				assert.Equal(t, tt.expected, domainFilter.Match(domain), "%v", domain)
   423  				assert.Equal(t, tt.expected, domainFilter.Match(domain+"."), "%v", domain+".")
   424  
   425  				assert.Equal(t, tt.expected, deserialized.Match(domain), "deserialized %v", domain)
   426  				assert.Equal(t, tt.expected, deserialized.Match(domain+"."), "deserialized %v", domain+".")
   427  			}
   428  		})
   429  	}
   430  }
   431  
   432  func TestDomainFilterMatchWithEmptyFilter(t *testing.T) {
   433  	for _, tt := range domainFilterTests {
   434  		domainFilter := DomainFilter{}
   435  		for i, domain := range tt.domains {
   436  			assert.True(t, domainFilter.Match(domain), "should not fail: %v in test-case #%v", domain, i)
   437  			assert.True(t, domainFilter.Match(domain+"."), "should not fail: %v in test-case #%v", domain+".", i)
   438  		}
   439  	}
   440  }
   441  
   442  func TestRegexDomainFilter(t *testing.T) {
   443  	for i, tt := range regexDomainFilterTests {
   444  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   445  			domainFilter := NewRegexDomainFilter(tt.regex, tt.regexExclusion)
   446  
   447  			assertSerializes(t, domainFilter, tt.expectedSerialization)
   448  			deserialized := deserialize(t, map[string]string{
   449  				"regexInclude": tt.regex.String(),
   450  				"regexExclude": tt.regexExclusion.String(),
   451  			})
   452  
   453  			for _, domain := range tt.domains {
   454  				assert.Equal(t, tt.expected, domainFilter.Match(domain), "%v", domain)
   455  				assert.Equal(t, tt.expected, domainFilter.Match(domain+"."), "%v", domain+".")
   456  
   457  				assert.Equal(t, tt.expected, deserialized.Match(domain), "deserialized %v", domain)
   458  				assert.Equal(t, tt.expected, deserialized.Match(domain+"."), "deserialized %v", domain+".")
   459  			}
   460  		})
   461  	}
   462  }
   463  
   464  func TestPrepareFiltersStripsWhitespaceAndDotSuffix(t *testing.T) {
   465  	for _, tt := range []struct {
   466  		input  []string
   467  		output []string
   468  	}{
   469  		{
   470  			[]string{},
   471  			nil,
   472  		},
   473  		{
   474  			[]string{""},
   475  			nil,
   476  		},
   477  		{
   478  			[]string{" ", "   ", ""},
   479  			nil,
   480  		},
   481  		{
   482  			[]string{"  foo   ", "  bar. ", "baz."},
   483  			[]string{"foo", "bar", "baz"},
   484  		},
   485  		{
   486  			[]string{"foo.bar", "  foo.bar.  ", " foo.bar.baz ", " foo.bar.baz.  "},
   487  			[]string{"foo.bar", "foo.bar", "foo.bar.baz", "foo.bar.baz"},
   488  		},
   489  	} {
   490  		t.Run("test string", func(t *testing.T) {
   491  			assert.Equal(t, tt.output, prepareFilters(tt.input))
   492  		})
   493  	}
   494  }
   495  
   496  func TestMatchFilterReturnsProperEmptyVal(t *testing.T) {
   497  	emptyFilters := []string{}
   498  	assert.Equal(t, true, matchFilter(emptyFilters, "somedomain.com", true))
   499  	assert.Equal(t, false, matchFilter(emptyFilters, "somedomain.com", false))
   500  }
   501  
   502  func TestDomainFilterIsConfigured(t *testing.T) {
   503  	for i, tt := range []struct {
   504  		filters  []string
   505  		exclude  []string
   506  		expected bool
   507  	}{
   508  		{
   509  			[]string{""},
   510  			[]string{""},
   511  			false,
   512  		},
   513  		{
   514  			[]string{"    "},
   515  			[]string{"    "},
   516  			false,
   517  		},
   518  		{
   519  			[]string{"", ""},
   520  			[]string{""},
   521  			false,
   522  		},
   523  		{
   524  			[]string{" . "},
   525  			[]string{" . "},
   526  			false,
   527  		},
   528  		{
   529  			[]string{" notempty.com "},
   530  			[]string{"  "},
   531  			true,
   532  		},
   533  		{
   534  			[]string{" notempty.com "},
   535  			[]string{"  thisdoesntmatter.com "},
   536  			true,
   537  		},
   538  		{
   539  			[]string{""},
   540  			[]string{"  thisdoesntmatter.com "},
   541  			true,
   542  		},
   543  	} {
   544  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   545  			df := NewDomainFilterWithExclusions(tt.filters, tt.exclude)
   546  			assert.Equal(t, tt.expected, df.IsConfigured())
   547  		})
   548  	}
   549  }
   550  
   551  func TestRegexDomainFilterIsConfigured(t *testing.T) {
   552  	for i, tt := range []struct {
   553  		regex        string
   554  		regexExclude string
   555  		expected     bool
   556  	}{
   557  		{
   558  			"",
   559  			"",
   560  			false,
   561  		},
   562  		{
   563  			"(?:foo|bar)\\.org$",
   564  			"",
   565  			true,
   566  		},
   567  		{
   568  			"",
   569  			"\\.org$",
   570  			true,
   571  		},
   572  		{
   573  			"(?:foo|bar)\\.org$",
   574  			"\\.org$",
   575  			true,
   576  		},
   577  	} {
   578  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   579  			df := NewRegexDomainFilter(regexp.MustCompile(tt.regex), regexp.MustCompile(tt.regexExclude))
   580  			assert.Equal(t, tt.expected, df.IsConfigured())
   581  		})
   582  	}
   583  }
   584  
   585  func TestDomainFilterDeserializeError(t *testing.T) {
   586  	for _, tt := range []struct {
   587  		name          string
   588  		serialized    map[string]interface{}
   589  		expectedError string
   590  	}{
   591  		{
   592  			name: "invalid json",
   593  			serialized: map[string]interface{}{
   594  				"include": 3,
   595  			},
   596  			expectedError: "json: cannot unmarshal number into Go struct field domainFilterSerde.include of type []string",
   597  		},
   598  		{
   599  			name: "include and regex",
   600  			serialized: map[string]interface{}{
   601  				"include":      []string{"example.com"},
   602  				"regexInclude": "example.com",
   603  			},
   604  			expectedError: "cannot have both domain list and regex",
   605  		},
   606  		{
   607  			name: "exclude and regex",
   608  			serialized: map[string]interface{}{
   609  				"exclude":      []string{"example.com"},
   610  				"regexInclude": "example.com",
   611  			},
   612  			expectedError: "cannot have both domain list and regex",
   613  		},
   614  		{
   615  			name: "include and regexExclude",
   616  			serialized: map[string]interface{}{
   617  				"include":      []string{"example.com"},
   618  				"regexExclude": "example.com",
   619  			},
   620  			expectedError: "cannot have both domain list and regex",
   621  		},
   622  		{
   623  			name: "exclude and regexExclude",
   624  			serialized: map[string]interface{}{
   625  				"exclude":      []string{"example.com"},
   626  				"regexExclude": "example.com",
   627  			},
   628  			expectedError: "cannot have both domain list and regex",
   629  		},
   630  		{
   631  			name: "invalid regex",
   632  			serialized: map[string]interface{}{
   633  				"regexInclude": "*",
   634  			},
   635  			expectedError: "invalid regexInclude: error parsing regexp: missing argument to repetition operator: `*`",
   636  		},
   637  		{
   638  			name: "invalid regexExclude",
   639  			serialized: map[string]interface{}{
   640  				"regexExclude": "*",
   641  			},
   642  			expectedError: "invalid regexExclude: error parsing regexp: missing argument to repetition operator: `*`",
   643  		},
   644  	} {
   645  		t.Run(tt.name, func(t *testing.T) {
   646  			var deserialized DomainFilter
   647  			toJson, _ := json.Marshal(tt.serialized)
   648  			err := json.Unmarshal(toJson, &deserialized)
   649  			assert.EqualError(t, err, tt.expectedError)
   650  		})
   651  	}
   652  }
   653  
   654  func assertSerializes[T any](t *testing.T, domainFilter DomainFilter, expectedSerialization map[string]T) {
   655  	serialized, err := json.Marshal(domainFilter)
   656  	assert.NoError(t, err, "serializing")
   657  	expected, err := json.Marshal(expectedSerialization)
   658  	require.NoError(t, err)
   659  	assert.JSONEq(t, string(expected), string(serialized), "json serialization")
   660  }
   661  
   662  func deserialize[T any](t *testing.T, serialized map[string]T) DomainFilter {
   663  	inJson, err := json.Marshal(serialized)
   664  	require.NoError(t, err)
   665  	var deserialized DomainFilter
   666  	err = json.Unmarshal(inJson, &deserialized)
   667  	assert.NoError(t, err, "deserializing")
   668  
   669  	return deserialized
   670  }
   671  
   672  func TestDomainFilterMatchParent(t *testing.T) {
   673  	parentMatchTests := []domainFilterTest{
   674  		{
   675  			[]string{"a.example.com."},
   676  			[]string{},
   677  			[]string{"example.com"},
   678  			true,
   679  			map[string][]string{
   680  				"include": {"a.example.com"},
   681  			},
   682  		},
   683  		{
   684  			[]string{" a.example.com "},
   685  			[]string{},
   686  			[]string{"example.com"},
   687  			true,
   688  			map[string][]string{
   689  				"include": {"a.example.com"},
   690  			},
   691  		},
   692  		{
   693  			[]string{""},
   694  			[]string{},
   695  			[]string{"example.com"},
   696  			true,
   697  			map[string][]string{},
   698  		},
   699  		{
   700  			[]string{".a.example.com."},
   701  			[]string{},
   702  			[]string{"example.com"},
   703  			false,
   704  			map[string][]string{
   705  				"include": {".a.example.com"},
   706  			},
   707  		},
   708  		{
   709  			[]string{"a.example.com.", "b.example.com"},
   710  			[]string{},
   711  			[]string{"example.com"},
   712  			true,
   713  			map[string][]string{
   714  				"include": {"a.example.com", "b.example.com"},
   715  			},
   716  		},
   717  		{
   718  			[]string{"a.example.com"},
   719  			[]string{},
   720  			[]string{"b.example.com"},
   721  			false,
   722  			map[string][]string{
   723  				"include": {"a.example.com"},
   724  			},
   725  		},
   726  		{
   727  			[]string{"example.com"},
   728  			[]string{},
   729  			[]string{"example.com"},
   730  			false,
   731  			map[string][]string{
   732  				"include": {"example.com"},
   733  			},
   734  		},
   735  		{
   736  			[]string{"example.com"},
   737  			[]string{},
   738  			[]string{"anexample.com"},
   739  			false,
   740  			map[string][]string{
   741  				"include": {"example.com"},
   742  			},
   743  		},
   744  		{
   745  			[]string{""},
   746  			[]string{},
   747  			[]string{""},
   748  			true,
   749  			map[string][]string{},
   750  		},
   751  	}
   752  	for i, tt := range parentMatchTests {
   753  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   754  			domainFilter := NewDomainFilterWithExclusions(tt.domainFilter, tt.exclusions)
   755  
   756  			assertSerializes(t, domainFilter, tt.expectedSerialization)
   757  			deserialized := deserialize(t, map[string][]string{
   758  				"include": tt.domainFilter,
   759  				"exclude": tt.exclusions,
   760  			})
   761  
   762  			for _, domain := range tt.domains {
   763  				assert.Equal(t, tt.expected, domainFilter.MatchParent(domain), "%v", domain)
   764  				assert.Equal(t, tt.expected, domainFilter.MatchParent(domain+"."), "%v", domain+".")
   765  
   766  				assert.Equal(t, tt.expected, deserialized.MatchParent(domain), "deserialized %v", domain)
   767  				assert.Equal(t, tt.expected, deserialized.MatchParent(domain+"."), "deserialized %v", domain+".")
   768  			}
   769  		})
   770  	}
   771  }