Repository: favonia/cloudflare-ddns
Pull request #824 (2024-07-22 11:14:01)

refactor: apply WAF-independent changes from the WAF PR

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/favonia/cloudflare-ddns/pull/824?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 93.90%. Comparing base [(`75e4907`)](https://app.codecov.io/gh/favonia/cloudflare-ddns/commit/75e49075d0eebdda14559c88545eb37da290898e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) to head [(`48f368d`)](https://app.codecov.io/gh/favonia/cloudflare-ddns/commit/48f368dd9562c39271b2ed2b7d10e72105bf3d55?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #824      +/-   ##
==========================================
- Coverage   93.94%   93.90%   -0.04%     
==========================================
  Files          54       54              
  Lines        2181     2200      +19     
==========================================
+ Hits         2049     2066      +17     
- Misses        120      121       +1     
- Partials       12       13       +1     
```

| [Flag](https://app.codecov.io/gh/favonia/cloudflare-ddns/pull/824/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/favonia/cloudflare-ddns/pull/824/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) | `93.90% <100.00%> (-0.04%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/favonia/cloudflare-ddns/pull/824?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia).


Patch:
diff --git a/internal/api/cloudflare_records.go b/internal/api/cloudflare_records.go
index 2d39376..eb90e93 100644
--- a/internal/api/cloudflare_records.go
+++ b/internal/api/cloudflare_records.go
@@ -52,8 +52,7 @@ func (h CloudflareHandle) ListZones(ctx context.Context, ppfmt pp.PP, name strin
 			ppfmt.Infof(pp.EmojiWarning, "Zone %q is %q and thus skipped", name, zone.Status)
 			// skip these
 		default:
-			ppfmt.Warningf(pp.EmojiImpossible, "Zone %q is in an undocumented status %q", name, zone.Status)
-			ppfmt.Warningf(pp.EmojiImpossible, "Please report the bug at https://github.com/favonia/cloudflare-ddns/issues/new") //nolint:lll
+			ppfmt.Warningf(pp.EmojiImpossible, "Zone %q is in an undocumented status %q; please report this at https://github.com/favonia/cloudflare-ddns/issues/new", name, zone.Status) //nolint:lll
 			ids = append(ids, zone.ID)
 		}
 	}
@@ -125,7 +124,9 @@ func (h CloudflareHandle) ListRecords(ctx context.Context, ppfmt pp.PP,
 			Type: ipNet.RecordType(),
 		})
 	if err != nil {
-		ppfmt.Warningf(pp.EmojiError, "Failed to retrieve records of %q: %v", domain.Describe(), err)
+		ppfmt.Warningf(pp.EmojiError,
+			"Failed to retrieve %s records of %q: %v",
+			ipNet.RecordType(), domain.Describe(), err)
 		return nil, false, false
 	}
 
@@ -133,7 +134,9 @@ func (h CloudflareHandle) ListRecords(ctx context.Context, ppfmt pp.PP,
 	for i := range rs {
 		rmap[rs[i].ID], err = netip.ParseAddr(rs[i].Content)
 		if err != nil {
-			ppfmt.Warningf(pp.EmojiImpossible, "Failed to parse the IP address in records of %q: %v", domain.Describe(), err)
+			ppfmt.Warningf(pp.EmojiImpossible,
+				"Failed to parse the IP address in an %s record of %q (ID: %s): %v",
+				ipNet.RecordType(), domain.Describe(), rs[i].ID, err)
 			return nil, false, false
 		}
 	}
diff --git a/internal/config/config_read.go b/internal/config/config_read.go
index 92eee23..99459b4 100644
--- a/internal/config/config_read.go
+++ b/internal/config/config_read.go
@@ -50,7 +50,19 @@ func (c *Config) Normalize(ppfmt pp.PP) bool {
 		ppfmt = ppfmt.IncIndent()
 	}
 
-	// Part 1: check DELETE_ON_STOP and UpdateOnStart
+	// Step 1: is there something to do, and do wo have an auth?
+	if c.Auth == nil {
+		// if c.Auth == nil, the user should have been warned.
+		ppfmt.Errorf(pp.EmojiImpossible, "Authorization info is missing, but this should be impossible")
+		ppfmt.Errorf(pp.EmojiImpossible, "Please report the bug at https://github.com/favonia/cloudflare-ddns/issues/new")
+		return false
+	}
+	if len(c.Domains[ipnet.IP4]) == 0 && len(c.Domains[ipnet.IP6]) == 0 {
+		ppfmt.Errorf(pp.EmojiUserError, "Nothing was specified in DOMAINS, IP4_DOMAINS, or IP6_DOMAINS")
+		return false
+	}
+
+	// Part 2: check DELETE_ON_STOP and UpdateOnStart
 	if c.UpdateCron == nil {
 		if !c.UpdateOnStart {
 			ppfmt.Errorf(
@@ -61,30 +73,26 @@ func (c *Config) Normalize(ppfmt pp.PP) bool {
 		if c.DeleteOnStop {
 			ppfmt.Errorf(
 				pp.EmojiUserError,
-				"DELETE_ON_STOP=true will immediately delete all updated DNS records when UPDATE_CRON=@once")
+				"DELETE_ON_STOP=true will immediately delete all domains when UPDATE_CRON=@once")
 			return false
 		}
 	}
 
-	// Part 2: normalize domain maps
-	// New domain maps
+	// Step 3: normalize domains and providers
+	//
+	// Step 3.1: fill in providerMap and activeDomainSet
 	providerMap := map[ipnet.Type]provider.Provider{}
-	proxiedMap := map[domain.Domain]bool{}
 	activeDomainSet := map[domain.Domain]bool{}
-
-	if len(c.Domains[ipnet.IP4]) == 0 && len(c.Domains[ipnet.IP6]) == 0 {
-		ppfmt.Errorf(pp.EmojiUserError, "No domains were specified in DOMAINS, IP4_DOMAINS, or IP6_DOMAINS")
-		return false
-	}
-
-	// fill in providerMap and activeDomainSet
-	for ipNet, domains := range c.Domains {
+	for ipNet := range c.Provider {
 		if c.Provider[ipNet] == nil {
 			continue
 		}
 
+		domains := c.Domains[ipNet]
+
 		if len(domains) == 0 {
-			ppfmt.Warningf(pp.EmojiUserWarning, "IP%d_PROVIDER was changed to %q because no domains were set for %s",
+			ppfmt.Warningf(pp.EmojiUserWarning,
+				"IP%d_PROVIDER was changed to %q because no domains use %s",
 				ipNet.Int(), provider.Name(nil), ipNet.Describe())
 
 			continue
@@ -96,14 +104,14 @@ func (c *Config) Normalize(ppfmt pp.PP) bool {
 		}
 	}
 
-	// check if all providers were turned off
+	// Step 3.2: check if all providers were turned off
 	if providerMap[ipnet.IP4] == nil && providerMap[ipnet.IP6] == nil {
 		ppfmt.Errorf(pp.EmojiUserError, "Nothing to update because both IP4_PROVIDER and IP6_PROVIDER are %q",
 			provider.Name(nil))
 		return false
 	}
 
-	// check if some domains are unused
+	// Step 3.3: check if some domains are unused
 	for ipNet, domains := range c.Domains {
 		if providerMap[ipNet] != nil {
 			continue
@@ -120,16 +128,19 @@ func (c *Config) Normalize(ppfmt pp.PP) bool {
 		}
 	}
 
-	// fill in proxyMap
-	proxiedPred, ok := domainexp.ParseExpression(ppfmt, "PROXIED", c.ProxiedTemplate)
-	if !ok {
-		return false
-	}
-	for dom := range activeDomainSet {
-		proxiedMap[dom] = proxiedPred(dom)
+	// Step 4: fill in proxiedMap
+	proxiedMap := map[domain.Domain]bool{}
+	if len(activeDomainSet) > 0 {
+		proxiedPredicate, ok := domainexp.ParseExpression(ppfmt, "PROXIED", c.ProxiedTemplate)
+		if !ok {
+			return false
+		}
+		for dom := range activeDomainSet {
+			proxiedMap[dom] = proxiedPredicate(dom)
+		}
 	}
 
-	// Part 3: override the old values
+	// Part 5: override the old values
 	c.Provider = providerMap
 	c.Proxied = proxiedMap
 
diff --git a/internal/setter/setter.go b/internal/setter/setter.go
index 7cbe6f9..1c5bfcb 100644
--- a/internal/setter/setter.go
+++ b/internal/setter/setter.go
@@ -59,7 +59,6 @@ func (s setter) Set(ctx context.Context, ppfmt pp.PP,
 
 	rs, cached, ok := s.Handle.ListRecords(ctx, ppfmt, domain, ipnet)
 	if !ok {
-		ppfmt.Errorf(pp.EmojiError, "Failed to retrieve the current %s records of %q", recordType, domainDescription)
 		return ResponseFailed
 	}
 
@@ -82,9 +81,13 @@ func (s setter) Set(ctx context.Context, ppfmt pp.PP,
 	// If it's up to date and there are no other records, we are done!
 	if foundMatched && len(unprocessedMatched) == 0 && len(unprocessedUnmatched) == 0 {
 		if cached {
-			ppfmt.Infof(pp.EmojiAlreadyDone, "The %s records of %q are already up to date (cached)", recordType, domainDescription) //nolint:lll
+			ppfmt.Infof(pp.EmojiAlreadyDone,
+				"The %s records of %q are already up to date (cached)",
+				recordType, domainDescription)
 		} else {
-			ppfmt.Infof(pp.EmojiAlreadyDone, "The %s records of %q are already up to date", recordType, domainDescription)
+			ppfmt.Infof(pp.EmojiAlreadyDone,
+				"The %s records of %q are already up to date",
+				recordType, domainDescription)
 		}
 		return ResponseNoop
 	}
@@ -114,7 +117,8 @@ func (s setter) Set(ctx context.Context, ppfmt pp.PP,
 				//
 				// Note that there can still be stale records at this point.
 				ppfmt.Noticef(pp.EmojiUpdateRecord,
-					"Updated a stale %s record of %q (ID: %q)", recordType, domainDescription, id)
+					"Updated a stale %s record of %q (ID: %q)",
+					recordType, domainDescription, id)
 
 				// Now it's up to date! Note that unprocessedMatched must be empty
 				// otherwise foundMatched would have been true.
@@ -130,7 +134,8 @@ func (s setter) Set(ctx context.Context, ppfmt pp.PP,
 
 			// If the updating fails, we will delete it.
 			if s.Handle.DeleteRecord(ctx, ppfmt, domain, ipnet, id) {
-				ppfmt.Noticef(pp.EmojiDeleteRecord, "Deleted a stale %s record of %q (ID: %q)",
+				ppfmt.Noticef(pp.EmojiDeleteRecord,
+					"Deleted a stale %s record of %q (ID: %q)",
 					recordType, domainDescription, id)
 
 				// Only when the deletion succeeds, we decrease the counter of remaining stale records.
@@ -150,9 +155,12 @@ func (s setter) Set(ctx context.Context, ppfmt pp.PP,
 	if !foundMatched {
 		if id, ok := s.Handle.CreateRecord(ctx, ppfmt,
 			domain, ipnet, ip, ttl, proxied, recordComment); ok {
-			ppfmt.Noticef(pp.EmojiCreateRecord, "Added a new %s record of %q (ID: %q)", recordType, domainDescription, id)
+			ppfmt.Noticef(pp.EmojiCreateRecord,
+				"Added a new %s record of %q (ID: %q)",
+				recordType, domainDescription, id)
 
-			// Now it's up to date! unprocessedMatched and unprocessedUnmatched must both be empty at this point
+			// Now it's up to date! unprocessedMatched and unprocessedUnmatched
+			// must both be empty at this point
 			foundMatched = true
 		} else if ctx.Err() != nil {
 			goto timeout
@@ -162,7 +170,9 @@ func (s setter) Set(ctx context.Context, ppfmt pp.PP,
 	// Now, we should try to delete all remaining stale records.
 	for _, id := range unprocessedUnmatched {
 		if s.Handle.DeleteRecord(ctx, ppfmt, domain, ipnet, id) {
-			ppfmt.Noticef(pp.EmojiDeleteRecord, "Deleted a stale %s record of %q (ID: %q)", recordType, domainDescription, id)
+			ppfmt.Noticef(pp.EmojiDeleteRecord,
+				"Deleted a stale %s record of %q (ID: %q)",
+				recordType, domainDescription, id)
 			numUndeletedUnmatched--
 		} else if ctx.Err() != nil {
 			goto timeout
@@ -173,7 +183,8 @@ func (s setter) Set(ctx context.Context, ppfmt pp.PP,
 	// This has lower priority than deleting the stale records.
 	for _, id := range unprocessedMatched {
 		if s.Handle.DeleteRecord(ctx, ppfmt, domain, ipnet, id) {
-			ppfmt.Noticef(pp.EmojiDeleteRecord, "Deleted a duplicate %s record of %q (ID: %q)",
+			ppfmt.Noticef(pp.EmojiDeleteRecord,
+				"Deleted a duplicate %s record of %q (ID: %q)",
 				recordType, domainDescription, id)
 		} else if ctx.Err() != nil {
 			goto timeout
@@ -202,7 +213,6 @@ func (s setter) Delete(ctx context.Context, ppfmt pp.PP, domain domain.Domain, i
 
 	rmap, cached, ok := s.Handle.ListRecords(ctx, ppfmt, domain, ipnet)
 	if !ok {
-		ppfmt.Errorf(pp.EmojiError, "Failed to retrieve the current %s records of %q", recordType, domainDescription)
 		return ResponseFailed
 	}
 


Test patch:
diff --git a/internal/api/cloudflare_records_test.go b/internal/api/cloudflare_records_test.go
index aaf3ccb..8470c29 100644
--- a/internal/api/cloudflare_records_test.go
+++ b/internal/api/cloudflare_records_test.go
@@ -339,10 +339,7 @@ func TestZoneOfDomain(t *testing.T) {
 			map[string][]string{"test.org": {"some-undocumented-status"}},
 			1, mockID("test.org", 0), true,
 			func(m *mocks.MockPP) {
-				gomock.InOrder(
-					m.EXPECT().Warningf(pp.EmojiImpossible, "Zone %q is in an undocumented status %q", "test.org", "some-undocumented-status"), //nolint:lll
-					m.EXPECT().Warningf(pp.EmojiImpossible, "Please report the bug at https://github.com/favonia/cloudflare-ddns/issues/new"),  //nolint:lll
-				)
+				m.EXPECT().Warningf(pp.EmojiImpossible, "Zone %q is in an undocumented status %q; please report this at https://github.com/favonia/cloudflare-ddns/issues/new", "test.org", "some-undocumented-status") //nolint:lll
 			},
 		},
 	} {
@@ -551,8 +548,10 @@ func TestListRecordsInvalidIPAddress(t *testing.T) {
 	ipNet, accessCount = ipnet.IP6, 1
 	mockPP.EXPECT().Warningf(
 		pp.EmojiImpossible,
-		"Failed to parse the IP address in records of %q: %v",
+		"Failed to parse the IP address in an %s record of %q (ID: %s): %v",
+		"AAAA",
 		"sub.test.org",
+		"record2",
 		gomock.Any(),
 	)
 	ips, cached, ok := h.ListRecords(context.Background(), mockPP, domain.FQDN("sub.test.org"), ipnet.IP6)
@@ -565,7 +564,8 @@ func TestListRecordsInvalidIPAddress(t *testing.T) {
 	mockPP = mocks.NewMockPP(mockCtrl)
 	mockPP.EXPECT().Warningf(
 		pp.EmojiError,
-		"Failed to retrieve records of %q: %v",
+		"Failed to retrieve %s records of %q: %v",
+		"AAAA",
 		"sub.test.org",
 		gomock.Any(),
 	)
@@ -645,14 +645,14 @@ func TestListRecordsInvalidDomain(t *testing.T) {
 	zh := newZonesHandler(t, mux, false)
 	zh.set(map[string][]string{"test.org": {"active"}}, 2)
 
-	mockPP.EXPECT().Warningf(pp.EmojiError, "Failed to retrieve records of %q: %v", "sub.test.org", gomock.Any())
+	mockPP.EXPECT().Warningf(pp.EmojiError, "Failed to retrieve %s records of %q: %v", "A", "sub.test.org", gomock.Any())
 	ips, cached, ok := h.ListRecords(context.Background(), mockPP, domain.FQDN("sub.test.org"), ipnet.IP4)
 	require.False(t, ok)
 	require.False(t, cached)
 	require.Nil(t, ips)
 
 	mockPP = mocks.NewMockPP(mockCtrl)
-	mockPP.EXPECT().Warningf(pp.EmojiError, "Failed to retrieve records of %q: %v", "sub.test.org", gomock.Any())
+	mockPP.EXPECT().Warningf(pp.EmojiError, "Failed to retrieve %s records of %q: %v", "AAAA", "sub.test.org", gomock.Any()) //nolint:lll
 	ips, cached, ok = h.ListRecords(context.Background(), mockPP, domain.FQDN("sub.test.org"), ipnet.IP6)
 	require.False(t, ok)
 	require.False(t, cached)
diff --git a/internal/config/config_read_test.go b/internal/config/config_read_test.go
index 25bcf1e..286c4c5 100644
--- a/internal/config/config_read_test.go
+++ b/internal/config/config_read_test.go
@@ -1,6 +1,7 @@
 package config_test
 
 import (
+	"context"
 	"testing"
 	"time"
 
@@ -9,7 +10,6 @@ import (
 
 	"github.com/favonia/cloudflare-ddns/internal/api"
 	"github.com/favonia/cloudflare-ddns/internal/config"
-	"github.com/favonia/cloudflare-ddns/internal/cron"
 	"github.com/favonia/cloudflare-ddns/internal/domain"
 	"github.com/favonia/cloudflare-ddns/internal/ipnet"
 	"github.com/favonia/cloudflare-ddns/internal/mocks"
@@ -85,12 +85,17 @@ func TestReadEnvEmpty(t *testing.T) {
 	require.False(t, ok)
 }
 
+type fakeAuth struct{}
+
+func (a fakeAuth) New(context.Context, pp.PP, time.Duration) (api.Handle, bool) {
+	return nil, false
+}
+
 //nolint:funlen
 func TestNormalize(t *testing.T) {
 	t.Parallel()
 
 	keyProxied := "PROXIED"
-	var empty config.Config
 
 	for name, tc := range map[string]struct {
 		input         *config.Config
@@ -98,25 +103,42 @@ func TestNormalize(t *testing.T) {
 		expected      *config.Config
 		prepareMockPP func(m *mocks.MockPP)
 	}{
-		"nil": {
-			input:    &empty,
+		"authnil": {
+			input:    &config.Config{}, //nolint:exhaustruct
 			ok:       false,
-			expected: &empty,
+			expected: nil,
 			prepareMockPP: func(m *mocks.MockPP) {
 				gomock.InOrder(
 					m.EXPECT().IsEnabledFor(pp.Info).Return(true),
 					m.EXPECT().Infof(pp.EmojiEnvVars, "Checking settings . . ."),
 					m.EXPECT().IncIndent().Return(m),
-					m.EXPECT().Errorf(pp.EmojiUserError, "UPDATE_ON_START=false is incompatible with UPDATE_CRON=@once"),
+					m.EXPECT().Errorf(pp.EmojiImpossible, "Authorization info is missing, but this should be impossible"),
+					m.EXPECT().Errorf(pp.EmojiImpossible, "Please report the bug at https://github.com/favonia/cloudflare-ddns/issues/new"), //nolint:lll
+				)
+			},
+		},
+		"nothing-to-do": {
+			input: &config.Config{ //nolint:exhaustruct
+				Auth: fakeAuth{},
+			},
+			ok:       false,
+			expected: nil,
+			prepareMockPP: func(m *mocks.MockPP) {
+				gomock.InOrder(
+					m.EXPECT().IsEnabledFor(pp.Info).Return(true),
+					m.EXPECT().Infof(pp.EmojiEnvVars, "Checking settings . . ."),
+					m.EXPECT().IncIndent().Return(m),
+					m.EXPECT().Errorf(pp.EmojiUserError, "Nothing was specified in DOMAINS, IP4_DOMAINS, or IP6_DOMAINS"),
 				)
 			},
 		},
-		"empty/1": {
+		"once/update-on-start": {
 			input: &config.Config{ //nolint:exhaustruct
+				Auth: fakeAuth{},
 				Domains: map[ipnet.Type][]domain.Domain{
-					ipnet.IP4: {},
-					ipnet.IP6: {},
+					ipnet.IP4: {domain.FQDN("a.b.c")},
 				},
+				UpdateOnStart: false,
 			},
 			ok:       false,
 			expected: nil,
@@ -129,13 +151,14 @@ func TestNormalize(t *testing.T) {
 				)
 			},
 		},
-		"empty/2": {
+		"once/delete-on-stop": {
 			input: &config.Config{ //nolint:exhaustruct
-				UpdateOnStart: true,
+				Auth: fakeAuth{},
 				Domains: map[ipnet.Type][]domain.Domain{
-					ipnet.IP4: {},
-					ipnet.IP6: {},
+					ipnet.IP4: {domain.FQDN("a.b.c")},
 				},
+				DeleteOnStop:  true,
+				UpdateOnStart: true,
 			},
 			ok:       false,
 			expected: nil,
@@ -144,12 +167,13 @@ func TestNormalize(t *testing.T) {
 					m.EXPECT().IsEnabledFor(pp.Info).Return(true),
 					m.EXPECT().Infof(pp.EmojiEnvVars, "Checking settings . . ."),
 					m.EXPECT().IncIndent().Return(m),
-					m.EXPECT().Errorf(pp.EmojiUserError, "No domains were specified in DOMAINS, IP4_DOMAINS, or IP6_DOMAINS"),
+					m.EXPECT().Errorf(pp.EmojiUserError, "DELETE_ON_STOP=true will immediately delete all domains when UPDATE_CRON=@once"), //nolint:lll
 				)
 			},
 		},
-		"empty-ip6": {
+		"dns6empty": {
 			input: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP4: provider.NewCloudflareTrace(),
@@ -157,19 +181,18 @@ func TestNormalize(t *testing.T) {
 				},
 				Domains: map[ipnet.Type][]domain.Domain{
 					ipnet.IP4: {domain.FQDN("a.b.c")},
-					ipnet.IP6: {},
 				},
 				ProxiedTemplate: "false",
 			},
 			ok: true,
 			expected: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP4: provider.NewCloudflareTrace(),
 				},
 				Domains: map[ipnet.Type][]domain.Domain{
 					ipnet.IP4: {domain.FQDN("a.b.c")},
-					ipnet.IP6: {},
 				},
 				ProxiedTemplate: "false",
 				Proxied: map[domain.Domain]bool{
@@ -182,20 +205,20 @@ func TestNormalize(t *testing.T) {
 					m.EXPECT().Infof(pp.EmojiEnvVars, "Checking settings . . ."),
 					m.EXPECT().IncIndent().Return(m),
 					m.EXPECT().Warningf(pp.EmojiUserWarning,
-						"IP%d_PROVIDER was changed to %q because no domains were set for %s",
+						"IP%d_PROVIDER was changed to %q because no domains use %s",
 						6, "none", "IPv6"),
 				)
 			},
 		},
-		"empty-ip6-none-ip4": {
+		"dns6empty-ip4none": {
 			input: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP6: provider.NewCloudflareTrace(),
 				},
 				Domains: map[ipnet.Type][]domain.Domain{
 					ipnet.IP4: {domain.FQDN("a.b.c")},
-					ipnet.IP6: {},
 				},
 			},
 			ok:       false,
@@ -206,7 +229,7 @@ func TestNormalize(t *testing.T) {
 					m.EXPECT().Infof(pp.EmojiEnvVars, "Checking settings . . ."),
 					m.EXPECT().IncIndent().Return(m),
 					m.EXPECT().Warningf(pp.EmojiUserWarning,
-						"IP%d_PROVIDER was changed to %q because no domains were set for %s",
+						"IP%d_PROVIDER was changed to %q because no domains use %s",
 						6, "none", "IPv6"),
 					m.EXPECT().Errorf(pp.EmojiUserError,
 						"Nothing to update because both IP4_PROVIDER and IP6_PROVIDER are %q",
@@ -214,8 +237,9 @@ func TestNormalize(t *testing.T) {
 				)
 			},
 		},
-		"ignored-ip4-domains": {
+		"ip4none": {
 			input: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP6: provider.NewCloudflareTrace(),
@@ -228,6 +252,7 @@ func TestNormalize(t *testing.T) {
 			},
 			ok: true,
 			expected: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP6: provider.NewCloudflareTrace(),
@@ -253,8 +278,9 @@ func TestNormalize(t *testing.T) {
 				)
 			},
 		},
-		"template": {
+		"proxied": {
 			input: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP6: provider.NewCloudflareTrace(),
@@ -266,6 +292,7 @@ func TestNormalize(t *testing.T) {
 			},
 			ok: true,
 			expected: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP6: provider.NewCloudflareTrace(),
@@ -288,8 +315,9 @@ func TestNormalize(t *testing.T) {
 				)
 			},
 		},
-		"template/invalid/proxied": {
+		"proxied/invalid/1": {
 			input: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP6: provider.NewCloudflareTrace(),
@@ -310,8 +338,9 @@ func TestNormalize(t *testing.T) {
 				)
 			},
 		},
-		"template/error/proxied": {
+		"proxied/invalid/2": {
 			input: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP6: provider.NewCloudflareTrace(),
@@ -332,8 +361,9 @@ func TestNormalize(t *testing.T) {
 				)
 			},
 		},
-		"template/error/proxied/ill-formed": {
+		"proxied/invalid/3": {
 			input: &config.Config{ //nolint:exhaustruct
+				Auth:          fakeAuth{},
 				UpdateOnStart: true,
 				Provider: map[ipnet.Type]provider.Provider{
 					ipnet.IP6: provider.NewCloudflareTrace(),
@@ -354,57 +384,6 @@ func TestNormalize(t *testing.T) {
 				)
 			},
 		},
-		"delete-on-stop/without-cron": {
-			input: &config.Config{ //nolint:exhaustruct
-				DeleteOnStop:  true,
-				UpdateOnStart: true,
-			},
-			ok:       false,
-			expected: nil,
-			prepareMockPP: func(m *mocks.MockPP) {
-				gomock.InOrder(
-					m.EXPECT().IsEnabledFor(pp.Info).Return(true),
-					m.EXPECT().Infof(pp.EmojiEnvVars, "Checking settings . . ."),
-					m.EXPECT().IncIndent().Return(m),
-					m.EXPECT().Errorf(pp.EmojiUserError, "DELETE_ON_STOP=true will immediately delete all updated DNS records when UPDATE_CRON=@once"), //nolint:lll
-				)
-			},
-		},
-		"delete-on-stop/with-cron": {
-			input: &config.Config{ //nolint:exhaustruct
-				DeleteOnStop: true,
-				UpdateCron:   cron.MustNew("@every 5m"),
-				Provider: map[ipnet.Type]provider.Provider{
-					ipnet.IP6: provider.NewCloudflareTrace(),
-				},
-				Domains: map[ipnet.Type][]domain.Domain{
-					ipnet.IP6: {domain.FQDN("a.b.c")},
-				},
-				ProxiedTemplate: "false",
-			},
-			ok: true,
-			expected: &config.Config{ //nolint:exhaustruct
-				DeleteOnStop: true,
-				UpdateCron:   cron.MustNew("@every 5m"),
-				Provider: map[ipnet.Type]provider.Provider{
-					ipnet.IP6: provider.NewCloudflareTrace(),
-				},
-				Domains: map[ipnet.Type][]domain.Domain{
-					ipnet.IP6: {domain.FQDN("a.b.c")},
-				},
-				ProxiedTemplate: "false",
-				Proxied: map[domain.Domain]bool{
-					domain.FQDN("a.b.c"): false,
-				},
-			},
-			prepareMockPP: func(m *mocks.MockPP) {
-				gomock.InOrder(
-					m.EXPECT().IsEnabledFor(pp.Info).Return(true),
-					m.EXPECT().Infof(pp.EmojiEnvVars, "Checking settings . . ."),
-					m.EXPECT().IncIndent().Return(m),
-				)
-			},
-		},
 	} {
 		t.Run(name, func(t *testing.T) {
 			t.Parallel()
diff --git a/internal/setter/setter_test.go b/internal/setter/setter_test.go
index 278b461..fe1af2e 100644
--- a/internal/setter/setter_test.go
+++ b/internal/setter/setter_test.go
@@ -365,10 +365,7 @@ func TestSet(t *testing.T) {
 			ip1,
 			setter.ResponseFailed,
 			func(ctx context.Context, _ func(), p *mocks.MockPP, h *mocks.MockHandle) {
-				gomock.InOrder(
-					h.EXPECT().ListRecords(ctx, p, domain, ipNetwork).Return(nil, false, false),
-					p.EXPECT().Errorf(pp.EmojiError, "Failed to retrieve the current %s records of %q", "AAAA", "sub.test.org"),
-				)
+				h.EXPECT().ListRecords(ctx, p, domain, ipNetwork).Return(nil, false, false)
 			},
 		},
 	} {
@@ -479,10 +476,7 @@ func TestDelete(t *testing.T) {
 		"listfail": {
 			setter.ResponseFailed,
 			func(ctx context.Context, _ func(), p *mocks.MockPP, h *mocks.MockHandle) {
-				gomock.InOrder(
-					h.EXPECT().ListRecords(ctx, p, domain, ipNetwork).Return(nil, false, false),
-					p.EXPECT().Errorf(pp.EmojiError, "Failed to retrieve the current %s records of %q", "AAAA", "sub.test.org"),
-				)
+				h.EXPECT().ListRecords(ctx, p, domain, ipNetwork).Return(nil, false, false)
 			},
 		},
 	} {