summaryrefslogtreecommitdiff
path: root/internal/config/config_test.go
blob: 06c0234b3b7ea12b3afa6dcc572a9a54c303f4b0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package config

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func write(t *testing.T, body string) string {
	t.Helper()
	path := filepath.Join(t.TempDir(), "config")
	if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
		t.Fatal(err)
	}
	return path
}

func TestLoadMissingFileKeepsDefaults(t *testing.T) {
	cfg, err := Load(filepath.Join(t.TempDir(), "absent"))
	if err != nil {
		t.Fatalf("missing file should not be an error: %v", err)
	}
	if cfg.Hours != 12 || cfg.Icons != "nerd" {
		t.Fatalf("defaults not returned: %+v", cfg)
	}
}

func TestLoadOverridesOnlyWhatIsSet(t *testing.T) {
	cfg, err := Load(write(t, "hours=24\nicons=emoji\n"))
	if err != nil {
		t.Fatal(err)
	}
	if cfg.Hours != 24 {
		t.Errorf("hours = %d, want 24", cfg.Hours)
	}
	if cfg.Icons != "emoji" {
		t.Errorf("icons = %q, want emoji", cfg.Icons)
	}
	// Untouched keys must keep their defaults.
	if cfg.GraphHeight != 5 || !cfg.Graph {
		t.Errorf("unset keys lost their defaults: %+v", cfg)
	}
}

func TestLoadCommentsAndBlanks(t *testing.T) {
	cfg, err := Load(write(t, "\n# a comment\n\nhours=6   # trailing comment\n"))
	if err != nil {
		t.Fatal(err)
	}
	if cfg.Hours != 6 {
		t.Fatalf("hours = %d, want 6 (trailing comment must be stripped)", cfg.Hours)
	}
}

func TestLoadErrors(t *testing.T) {
	for name, body := range map[string]string{
		"no equals":    "hours 12\n",
		"unknown key":  "colour=always\n",
		"not a number": "hours=soon\n",
		"not a bool":   "graph=maybe\n",
	} {
		t.Run(name, func(t *testing.T) {
			if _, err := Load(write(t, body)); err == nil {
				t.Fatalf("expected an error for %q", body)
			}
		})
	}
}

func TestPollenAllAndNone(t *testing.T) {
	cfg, _ := Load(write(t, "pollen=all\n"))
	if len(cfg.Pollen) != len(AllSpecies) {
		t.Errorf("pollen=all gave %v", cfg.Pollen)
	}
	cfg, _ = Load(write(t, "pollen=none\n"))
	if len(cfg.Pollen) != 0 {
		t.Errorf("pollen=none gave %v", cfg.Pollen)
	}
}

func TestValidateNamesTheOffender(t *testing.T) {
	cfg := Default()
	cfg.Columns = []string{"hour", "tempature"}
	err := cfg.Validate()
	if err == nil {
		t.Fatal("expected an error for an unknown column")
	}
	if !strings.Contains(err.Error(), "tempature") {
		t.Errorf("error must name the offending column, got: %v", err)
	}
	if !strings.Contains(err.Error(), "conditions") {
		t.Errorf("error must list valid columns, got: %v", err)
	}
}

func TestValidateRejectsBadEnums(t *testing.T) {
	for name, mutate := range map[string]func(*Config){
		"icons":        func(c *Config) { c.Icons = "pictures" },
		"color":        func(c *Config) { c.Color = "sometimes" },
		"units":        func(c *Config) { c.Units = "furlongs" },
		"hours":        func(c *Config) { c.Hours = 0 },
		"graph_height": func(c *Config) { c.GraphHeight = 1 },
		"pollen":       func(c *Config) { c.Pollen = []string{"oak"} },
	} {
		t.Run(name, func(t *testing.T) {
			cfg := Default()
			mutate(&cfg)
			if err := cfg.Validate(); err == nil {
				t.Fatalf("expected %s to be rejected", name)
			}
		})
	}
}

func TestDefaultIsValid(t *testing.T) {
	if err := Default().Validate(); err != nil {
		t.Fatalf("the built-in default must be valid: %v", err)
	}
}

func TestFieldsRequestsOnlySelectedColumns(t *testing.T) {
	cfg := Default()
	cfg.Columns = []string{"hour", "temp", "wind"}
	got := strings.Join(cfg.Fields(), ",")
	want := "temperature_2m,wind_speed_10m"
	if got != want {
		t.Fatalf("Fields() = %q, want %q", got, want)
	}
}

func TestFieldsDeduplicates(t *testing.T) {
	cfg := Default()
	// icon and conditions both come from weather_code.
	cfg.Columns = []string{"icon", "conditions"}
	if got := cfg.Fields(); len(got) != 1 || got[0] != "weather_code" {
		t.Fatalf("Fields() = %v, want one weather_code", got)
	}
}

func TestWriteDefaultRoundTrips(t *testing.T) {
	path := filepath.Join(t.TempDir(), "sub", "config")
	want := Default()
	want.Location = "Krakow"
	if err := WriteDefault(path, want); err != nil {
		t.Fatal(err)
	}
	got, err := Load(path)
	if err != nil {
		t.Fatalf("the file we generate must parse: %v", err)
	}
	if got.Location != "Krakow" || got.Hours != want.Hours || got.Icons != want.Icons {
		t.Fatalf("round trip changed values:\n got %+v\nwant %+v", got, want)
	}
	if err := got.Validate(); err != nil {
		t.Fatalf("the file we generate must validate: %v", err)
	}
}