Repository: favonia/cloudflare-ddns
Pull request #68 (2021-08-09 17:20:21)

test(config): good coverage of config

codecov[bot]: # [Codecov](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) Report
> Merging [#68](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) (31be046) into [main](https://codecov.io/gh/favonia/cloudflare-ddns/commit/8c649706f509b8c4fee1e5a022f9d00702fe6334?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) (8c64970) will **increase** coverage by `3.14%`.
> The diff coverage is `50.00%`.

[![Impacted file tree graph](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68/graphs/tree.svg?width=650&height=150&src=pr&token=90Cwhy7OkI&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia)](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia)

```diff
@@            Coverage Diff             @@
##             main      #68      +/-   ##
==========================================
+ Coverage   60.25%   63.40%   +3.14%     
==========================================
  Files          20       21       +1     
  Lines        1175     1175              
==========================================
+ Hits          708      745      +37     
+ Misses        463      424      -39     
- Partials        4        6       +2     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `63.40% <50.00%> (+3.14%)` | :arrow_up: |

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.

| [Impacted Files](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia) | Coverage Δ | |
|---|---|---|
| [internal/config/config.go](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia#diff-aW50ZXJuYWwvY29uZmlnL2NvbmZpZy5nbw==) | `19.07% <50.00%> (+19.07%)` | :arrow_up: |
| [internal/detector/local.go](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia#diff-aW50ZXJuYWwvZGV0ZWN0b3IvbG9jYWwuZ28=) | `100.00% <0.00%> (ø)` | |
| [internal/detector/local\_cloudflare.go](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia#diff-aW50ZXJuYWwvZGV0ZWN0b3IvbG9jYWxfY2xvdWRmbGFyZS5nbw==) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia). Last update [8c64970...31be046](https://codecov.io/gh/favonia/cloudflare-ddns/pull/68?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=favonia).


Patch:
diff --git a/internal/config/config.go b/internal/config/config.go
index a11c287..da5b345 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -87,7 +87,7 @@ func readAuthToken(_ quiet.Quiet, indent pp.Indent) (string, bool) {
 	}
 }
 
-func readAuth(quiet quiet.Quiet, indent pp.Indent, field *api.Auth) bool {
+func ReadAuth(quiet quiet.Quiet, indent pp.Indent, field *api.Auth) bool {
 	token, ok := readAuthToken(quiet, indent)
 	if !ok {
 		return false
@@ -124,7 +124,7 @@ func deduplicate(list *[]api.FQDN) {
 	*list = (*list)[:j+1]
 }
 
-func readDomains(quiet quiet.Quiet, indent pp.Indent, field map[ipnet.Type][]api.FQDN) bool {
+func ReadDomainMap(quiet quiet.Quiet, indent pp.Indent, field map[ipnet.Type][]api.FQDN) bool {
 	var domains, ip4Domains, ip6Domains []api.FQDN
 
 	if !ReadDomains(quiet, indent, "DOMAINS", &domains) ||
@@ -145,15 +145,12 @@ func readDomains(quiet quiet.Quiet, indent pp.Indent, field map[ipnet.Type][]api
 	return true
 }
 
-func readPolicies(quiet quiet.Quiet, indent pp.Indent, field map[ipnet.Type]detector.Policy) bool {
+func ReadPolicyMap(quiet quiet.Quiet, indent pp.Indent, field map[ipnet.Type]detector.Policy) bool {
 	ip4Policy := field[ipnet.IP4]
 	ip6Policy := field[ipnet.IP6]
 
-	if !ReadPolicy(quiet, indent, "IP4_POLICY", &ip4Policy) {
-		return false
-	}
-
-	if !ReadPolicy(quiet, indent, "IP6_POLICY", &ip6Policy) {
+	if !ReadPolicy(quiet, indent, "IP4_POLICY", &ip4Policy) ||
+		!ReadPolicy(quiet, indent, "IP6_POLICY", &ip6Policy) {
 		return false
 	}
 
@@ -200,9 +197,9 @@ func (c *Config) ReadEnv(indent pp.Indent) bool { //nolint:cyclop
 		indent++
 	}
 
-	if !readAuth(c.Quiet, indent, &c.Auth) ||
-		!readPolicies(c.Quiet, indent, c.Policy) ||
-		!readDomains(c.Quiet, indent, c.Domains) ||
+	if !ReadAuth(c.Quiet, indent, &c.Auth) ||
+		!ReadPolicyMap(c.Quiet, indent, c.Policy) ||
+		!ReadDomainMap(c.Quiet, indent, c.Domains) ||
 		!ReadCron(c.Quiet, indent, "UPDATE_CRON", &c.UpdateCron) ||
 		!ReadBool(c.Quiet, indent, "UPDATE_ON_START", &c.UpdateOnStart) ||
 		!ReadBool(c.Quiet, indent, "DELETE_ON_STOP", &c.DeleteOnStop) ||


Test patch:
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
new file mode 100644
index 0000000..668d9ab
--- /dev/null
+++ b/internal/config/config_test.go
@@ -0,0 +1,217 @@
+package config_test
+
+import (
+	"os"
+	"testing"
+	"testing/fstest"
+	"time"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/favonia/cloudflare-ddns/internal/api"
+	"github.com/favonia/cloudflare-ddns/internal/config"
+	"github.com/favonia/cloudflare-ddns/internal/detector"
+	"github.com/favonia/cloudflare-ddns/internal/file"
+	"github.com/favonia/cloudflare-ddns/internal/ipnet"
+	"github.com/favonia/cloudflare-ddns/internal/quiet"
+)
+
+func TestDefaultConfigNotNil(t *testing.T) {
+	t.Parallel()
+
+	require.NotNil(t, config.Default())
+}
+
+//nolint: paralleltest // environment vars are global
+func TestReadAuthToken(t *testing.T) {
+	unset("CF_API_TOKEN")
+	unset("CF_API_TOKEN_FILE")
+	unset("CF_ACCOUNT_ID")
+
+	for name, tc := range map[string]struct {
+		token   string
+		account string
+		ok      bool
+	}{
+		"full":      {"123456789", "secret account", true},
+		"noaccount": {"123456789", "", true},
+		"notoken":   {"", "account", false},
+		"copycat":   {"YOUR-CLOUDFLARE-API-TOKEN", "", false},
+	} {
+		tc := tc
+		t.Run(name, func(t *testing.T) {
+			set("CF_API_TOKEN", tc.token)
+			set("CF_ACCOUNT_ID", tc.account)
+			defer unset("CF_API_TOKEN")
+			defer unset("CF_ACCOUNT_ID")
+
+			var field api.Auth
+			ok := config.ReadAuth(quiet.QUIET, 2, &field)
+			if tc.ok {
+				require.True(t, ok)
+				require.Equal(t, &api.CloudflareAuth{Token: tc.token, AccountID: tc.account, BaseURL: ""}, field)
+			} else {
+				require.False(t, ok)
+				require.Nil(t, field)
+			}
+		})
+	}
+}
+
+func useMemFS(memfs fstest.MapFS) {
+	file.FS = memfs
+}
+
+func useDirFS() {
+	file.FS = os.DirFS("/")
+}
+
+//nolint: paralleltest // environment vars and file system are global
+func TestReadAuthTokenWithFile(t *testing.T) {
+	unset("CF_API_TOKEN")
+	unset("CF_API_TOKEN_FILE")
+	unset("CF_ACCOUNT_ID")
+
+	for name, tc := range map[string]struct {
+		token         string
+		tokenFile     string
+		account       string
+		actualPath    string
+		actualContent string
+		expected      string
+	}{
+		"ok":           {"", "test.txt", "secret account", "test.txt", "hello", "hello"},
+		"both":         {"123456789", "test.txt", "secret account", "test.txt", "hello", ""},
+		"wrong.path":   {"123456789", "test.txt", "secret account", "wrong.txt", "hello", ""},
+		"empty":        {"", "test.txt", "secret account", "test.txt", "", ""},
+		"invalid path": {"", "dir/test.txt", "secret account", "dir", "hello", ""},
+	} {
+		tc := tc
+		t.Run(name, func(t *testing.T) {
+			set("CF_API_TOKEN", tc.token)
+			set("CF_API_TOKEN_FILE", tc.tokenFile)
+			set("CF_ACCOUNT_ID", tc.account)
+			defer unset("CF_API_TOKEN")
+			defer unset("CF_API_TOKEN_FILE")
+			defer unset("CF_ACCOUNT_ID")
+
+			useMemFS(fstest.MapFS{
+				tc.actualPath: &fstest.MapFile{
+					Data:    []byte(tc.actualContent),
+					Mode:    0o644,
+					ModTime: time.Unix(1234, 5678),
+					Sys:     nil,
+				},
+			})
+			defer useDirFS()
+
+			var field api.Auth
+			ok := config.ReadAuth(quiet.QUIET, 2, &field)
+			if tc.expected != "" {
+				require.True(t, ok)
+				require.Equal(t, &api.CloudflareAuth{Token: tc.expected, AccountID: tc.account, BaseURL: ""}, field)
+			} else {
+				require.False(t, ok)
+				require.Nil(t, field)
+			}
+		})
+	}
+}
+
+//nolint: paralleltest // environment vars are global
+func TestReadDomainMap(t *testing.T) {
+	unset("DOMAINS")
+	unset("IP4_DOMAINS")
+	unset("IP6_DOMAINS")
+
+	for name, tc := range map[string]struct {
+		domains    string
+		ip4Domains string
+		ip6Domains string
+		expected   map[ipnet.Type][]api.FQDN
+		ok         bool
+	}{
+		"full": {
+			"  a1, a2", "b1,  b2,b2", "c1,c2",
+			map[ipnet.Type][]api.FQDN{
+				ipnet.IP4: {"a1", "a2", "b1", "b2"},
+				ipnet.IP6: {"a1", "a2", "c1", "c2"},
+			},
+			true,
+		},
+		"empty": {
+			" ", "   ", "",
+			map[ipnet.Type][]api.FQDN{
+				ipnet.IP4: {},
+				ipnet.IP6: {},
+			},
+			true,
+		},
+	} {
+		tc := tc
+		t.Run(name, func(t *testing.T) {
+			set("DOMAINS", tc.domains)
+			set("IP4_DOMAINS", tc.ip4Domains)
+			set("IP6_DOMAINS", tc.ip6Domains)
+			defer unset("DOMAINS")
+			defer unset("IP4_DOMAINS")
+			defer unset("IP6_DOMAINS")
+
+			field := map[ipnet.Type][]api.FQDN{}
+			ok := config.ReadDomainMap(quiet.QUIET, 2, field)
+			require.Equal(t, tc.ok, ok)
+			require.Equal(t, tc.expected, field)
+		})
+	}
+}
+
+//nolint: paralleltest // environment vars are global
+func TestReadPolicyMap(t *testing.T) {
+	unset("IP4_POLICY")
+	unset("IP6_POLICY")
+
+	for name, tc := range map[string]struct {
+		ip4Policy string
+		ip6Policy string
+		expected  map[ipnet.Type]detector.Policy
+		ok        bool
+	}{
+		"full": {
+			"cloudflare", "ipify",
+			map[ipnet.Type]detector.Policy{
+				ipnet.IP4: detector.NewCloudflare(),
+				ipnet.IP6: detector.NewIpify(),
+			},
+			true,
+		},
+		"empty": {
+			" ", "   ",
+			map[ipnet.Type]detector.Policy{
+				ipnet.IP4: nil,
+				ipnet.IP6: nil,
+			},
+			true,
+		},
+		"illformed": {
+			" flare", "   ",
+			map[ipnet.Type]detector.Policy{
+				ipnet.IP4: nil,
+				ipnet.IP6: nil,
+			},
+			false,
+		},
+	} {
+		tc := tc
+		t.Run(name, func(t *testing.T) {
+			set("IP4_POLICY", tc.ip4Policy)
+			set("IP6_POLICY", tc.ip6Policy)
+			defer unset("IP4_POLICY")
+			defer unset("IP6_POLICY")
+
+			field := map[ipnet.Type]detector.Policy{ipnet.IP4: nil, ipnet.IP6: nil}
+			ok := config.ReadPolicyMap(quiet.QUIET, 2, field)
+			require.Equal(t, tc.ok, ok)
+			require.Equal(t, tc.expected, field)
+		})
+	}
+}
diff --git a/internal/detector/dns_test.go b/internal/detector/dns_test.go
index 2f2bc32..8e713a0 100644
--- a/internal/detector/dns_test.go
+++ b/internal/detector/dns_test.go
@@ -466,7 +466,7 @@ func TestDNSOverHTTPSGetIP(t *testing.T) {
 			nil,
 		},
 	} {
-		name, tc := name, tc
+		tc := tc
 		t.Run(name, func(t *testing.T) {
 			t.Parallel()
 
diff --git a/internal/detector/http_test.go b/internal/detector/http_test.go
index 5a0fdef..feb5618 100644
--- a/internal/detector/http_test.go
+++ b/internal/detector/http_test.go
@@ -71,7 +71,7 @@ func TestHTTPGetIP(t *testing.T) {
 			"4-nil3": {ipnet.IP4, ip4Server.URL, ipnet.IP6, nil},
 			"6-nil3": {ipnet.IP6, ip6Server.URL, ipnet.IP4, nil},
 		} {
-			name, tc := name, tc
+			tc := tc
 			t.Run(name, func(t *testing.T) {
 				t.Parallel()
 
diff --git a/internal/detector/local_test.go b/internal/detector/local_test.go
index 309d7cd..f63d195 100644
--- a/internal/detector/local_test.go
+++ b/internal/detector/local_test.go
@@ -52,7 +52,7 @@ func TestLocalGetIP(t *testing.T) {
 		"4-nil2": {ipnet.IP4, "127.0.0.1:80", ipnet.IP6, nil},
 		"6-nil2": {ipnet.IP6, "::1:80", ipnet.IP4, nil},
 	} {
-		name, tc := name, tc
+		tc := tc
 		t.Run(name, func(t *testing.T) {
 			t.Parallel()
 
diff --git a/internal/file/file_test.go b/internal/file/file_test.go
index cbee807..0359d3a 100644
--- a/internal/file/file_test.go
+++ b/internal/file/file_test.go
@@ -22,7 +22,7 @@ func useDirFS() {
 }
 
 //nolint:paralleltest // changing global var file.FS
-func TestReadStringSuccessful(t *testing.T) {
+func TestReadStringOkay(t *testing.T) {
 	path := "test/file.txt"
 	written := " hello world   " // space is intentionally added to test trimming
 	expected := strings.TrimSpace(written)
@@ -30,7 +30,7 @@ func TestReadStringSuccessful(t *testing.T) {
 	useMemFS(fstest.MapFS{
 		path: &fstest.MapFile{
 			Data:    []byte(written),
-			Mode:    0644,
+			Mode:    0o644,
 			ModTime: time.Unix(1234, 5678),
 			Sys:     nil,
 		},
@@ -43,11 +43,29 @@ func TestReadStringSuccessful(t *testing.T) {
 }
 
 //nolint:paralleltest // changing global var file.FS
-func TestReadStringFailing(t *testing.T) {
+func TestReadStringWrongPath(t *testing.T) {
 	useMemFS(fstest.MapFS{})
 	defer useDirFS()
 
-	path := "/wrong/path.txt"
-	_, ok := file.ReadString(pp.NoIndent, path)
+	path := "wrong/path.txt"
+	content, ok := file.ReadString(pp.NoIndent, path)
+	require.False(t, ok)
+	require.Empty(t, content)
+}
+
+//nolint:paralleltest // changing global var file.FS
+func TestReadStringNoAccess(t *testing.T) {
+	useMemFS(fstest.MapFS{
+		"dir/file.txt": &fstest.MapFile{
+			Data:    []byte("hello"),
+			Mode:    0,
+			ModTime: time.Unix(1234, 5678),
+			Sys:     nil,
+		},
+	})
+	defer useDirFS()
+
+	content, ok := file.ReadString(pp.NoIndent, "dir")
 	require.False(t, ok)
+	require.Empty(t, content)
 }