config_test.go (6894B)
1 package config 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "slices" 9 "strings" 10 "testing" 11 ) 12 13 func TestLoadIncludesOverridesAndExpands(t *testing.T) { 14 t.Setenv(baseConfigEnv, t.TempDir()) 15 dir := t.TempDir() 16 child := filepath.Join(dir, "child.conf") 17 main := filepath.Join(dir, "main.conf") 18 if err := os.WriteFile(child, []byte("[taler]\ncurrency = TEST\n[paths]\nprefix = /srv/taler\n"), 0o600); err != nil { 19 t.Fatal(err) 20 } 21 if err := os.WriteFile(main, []byte("@INLINE@ child.conf\n[taler]\ncurrency = KUDOS\n[frontend-demo-blog]\nhttp_port = 8080\nhttp_unixpath = ${prefix}/blog.sock\n"), 0o600); err != nil { 22 t.Fatal(err) 23 } 24 c, err := Load(main) 25 if err != nil { 26 t.Fatal(err) 27 } 28 if got := c.Get("TALER", "CURRENCY"); got != "KUDOS" { 29 t.Fatalf("currency = %q", got) 30 } 31 if got := c.Filename("frontend-demo-blog", "http_unixpath"); got != "/srv/taler/blog.sock" { 32 t.Fatalf("expanded path = %q", got) 33 } 34 if got, err := c.Int("frontend-demo-blog", "http_port", 0); err != nil || got != 8080 { 35 t.Fatalf("port = %d, %v", got, err) 36 } 37 } 38 39 func TestIncludeCycle(t *testing.T) { 40 t.Setenv(baseConfigEnv, t.TempDir()) 41 path := filepath.Join(t.TempDir(), "cycle.conf") 42 if err := os.WriteFile(path, []byte("@INLINE@ cycle.conf\n"), 0o600); err != nil { 43 t.Fatal(err) 44 } 45 _, err := Load(path) 46 if err == nil || !strings.Contains(err.Error(), "include cycle") { 47 t.Fatalf("unexpected error: %v", err) 48 } 49 } 50 51 func TestDumpIncludesLoadOrderAndWinningSources(t *testing.T) { 52 defaults := t.TempDir() 53 base := filepath.Join(defaults, "base.conf") 54 dir := t.TempDir() 55 child := filepath.Join(dir, "child.conf") 56 main := filepath.Join(dir, "main.conf") 57 if err := os.WriteFile(base, []byte("[demo]\ndefault = base\noverridden = base\n"), 0o600); err != nil { 58 t.Fatal(err) 59 } 60 if err := os.WriteFile(child, []byte("[child]\nvalue = included\n"), 0o600); err != nil { 61 t.Fatal(err) 62 } 63 if err := os.WriteFile(main, []byte("@INLINE@ child.conf\n[demo]\noverridden = main\n"), 0o600); err != nil { 64 t.Fatal(err) 65 } 66 t.Setenv(baseConfigEnv, defaults) 67 68 c, err := Load(main) 69 if err != nil { 70 t.Fatal(err) 71 } 72 var output bytes.Buffer 73 if err := c.Dump(&output); err != nil { 74 t.Fatal(err) 75 } 76 want := fmt.Sprintf(`# 77 # Configuration file load order: 78 # %s 79 # %s 80 # %s 81 # 82 83 [child] 84 85 # %s:2 86 value = included 87 88 89 [demo] 90 91 # %s:2 92 default = base 93 94 # %s:3 95 overridden = main 96 97 `, base, main, child, child, base, main) 98 if got := output.String(); got != want { 99 t.Fatalf("dump =\n%s\nwant:\n%s", got, want) 100 } 101 } 102 103 func TestLoadDefaultsFromMerchantDemosPrefix(t *testing.T) { 104 prefix := t.TempDir() 105 defaults := filepath.Join(prefix, "share", "taler-merchant-demos", "config.d") 106 if err := os.MkdirAll(defaults, 0o700); err != nil { 107 t.Fatal(err) 108 } 109 if err := os.WriteFile(filepath.Join(defaults, "demo.conf"), []byte("[taler]\ncurrency = DEFAULT\n"), 0o600); err != nil { 110 t.Fatal(err) 111 } 112 main := filepath.Join(t.TempDir(), "main.conf") 113 if err := os.WriteFile(main, []byte("[unrelated]\nvalue = main\n"), 0o600); err != nil { 114 t.Fatal(err) 115 } 116 t.Setenv(baseConfigEnv, "") 117 t.Setenv(prefixEnv, prefix) 118 119 c, err := Load(main) 120 if err != nil { 121 t.Fatal(err) 122 } 123 if got := c.Get("taler", "currency"); got != "DEFAULT" { 124 t.Fatalf("currency = %q, want DEFAULT", got) 125 } 126 } 127 128 func TestLoadOrderMatchesComponentConfiguration(t *testing.T) { 129 defaults := t.TempDir() 130 if err := os.WriteFile(filepath.Join(defaults, "b.conf"), []byte("[order]\nvalue = second-default\n"), 0o600); err != nil { 131 t.Fatal(err) 132 } 133 if err := os.WriteFile(filepath.Join(defaults, "a.conf"), []byte("[order]\nvalue = first-default\n"), 0o600); err != nil { 134 t.Fatal(err) 135 } 136 xdg := t.TempDir() 137 child := filepath.Join(xdg, "child.conf") 138 main := filepath.Join(xdg, configFilename) 139 if err := os.WriteFile(child, []byte("[order]\nvalue = inline\n"), 0o600); err != nil { 140 t.Fatal(err) 141 } 142 if err := os.WriteFile(main, []byte("@INLINE@ child.conf\n[order]\nvalue = main\n"), 0o600); err != nil { 143 t.Fatal(err) 144 } 145 t.Setenv(baseConfigEnv, defaults) 146 t.Setenv("XDG_CONFIG_HOME", xdg) 147 148 c, err := Load("") 149 if err != nil { 150 t.Fatal(err) 151 } 152 if got := c.Get("order", "value"); got != "main" { 153 t.Fatalf("value = %q, want main", got) 154 } 155 wantFiles := []string{ 156 filepath.Join(defaults, "a.conf"), 157 filepath.Join(defaults, "b.conf"), 158 main, 159 child, 160 } 161 if got := c.loadedFiles; !slices.Equal(got, wantFiles) { 162 t.Fatalf("loaded files = %q, want %q", got, wantFiles) 163 } 164 165 explicit := filepath.Join(t.TempDir(), "explicit.conf") 166 if err := os.WriteFile(explicit, []byte("[order]\nvalue = explicit\n"), 0o600); err != nil { 167 t.Fatal(err) 168 } 169 c, err = Load(explicit) 170 if err != nil { 171 t.Fatal(err) 172 } 173 if got := c.Get("order", "value"); got != "explicit" { 174 t.Fatalf("value with explicit config = %q, want explicit", got) 175 } 176 wantFiles = []string{ 177 filepath.Join(defaults, "a.conf"), 178 filepath.Join(defaults, "b.conf"), 179 explicit, 180 } 181 if got := c.loadedFiles; !slices.Equal(got, wantFiles) { 182 t.Fatalf("loaded files with explicit config = %q, want %q", got, wantFiles) 183 } 184 } 185 186 func TestLoadWithoutMainConfiguration(t *testing.T) { 187 defaults := t.TempDir() 188 if err := os.WriteFile(filepath.Join(defaults, "defaults.conf"), []byte("[demo]\nvalue = default\n"), 0o600); err != nil { 189 t.Fatal(err) 190 } 191 t.Setenv(baseConfigEnv, defaults) 192 t.Setenv("XDG_CONFIG_HOME", t.TempDir()) 193 194 c, err := Load("") 195 if err != nil { 196 t.Fatal(err) 197 } 198 if got := c.Get("demo", "value"); got != "default" { 199 t.Fatalf("value = %q, want default", got) 200 } 201 } 202 203 func TestFilenameExpansionMatchesTalerSyntax(t *testing.T) { 204 t.Setenv("DEMO_ENV_ROOT", "/environment") 205 t.Setenv("DEMO_ENV_ONLY_ROOT", "/environment") 206 t.Setenv("DEMO_ENV_ALIAS", "$DEMO_ENV_ONLY_ROOT/from-env") 207 c := &Config{sections: map[string]map[string]string{ 208 "paths": { 209 "root": "/srv/taler", 210 "alias": "${ROOT}/shared", 211 "empty": "", 212 "cycle_one": "$CYCLE_TWO", 213 "cycle_two": "$CYCLE_ONE", 214 "demo_env_root": "/paths", 215 }, 216 "demo": { 217 "recursive": "${ALIAS}/socket", 218 "fallback": "${MISSING_ONE:-${MISSING_TWO:-/tmp}}/socket", 219 "environment": "$DEMO_ENV_ALIAS/socket", 220 "precedence": "$DEMO_ENV_ROOT/socket", 221 "empty": "before${EMPTY:-fallback}after", 222 "unknown": "$DEMO_NOT_DEFINED/socket", 223 "cycle": "$CYCLE_ONE/socket", 224 }, 225 }} 226 227 tests := map[string]string{ 228 "recursive": "/srv/taler/shared/socket", 229 "fallback": "/tmp/socket", 230 "environment": "/environment/from-env/socket", 231 "precedence": "/paths/socket", 232 "empty": "beforeafter", 233 "unknown": "$DEMO_NOT_DEFINED/socket", 234 } 235 for option, want := range tests { 236 if got := c.Filename("demo", option); got != want { 237 t.Errorf("%s expansion = %q, want %q", option, got, want) 238 } 239 } 240 if got := c.Filename("demo", "cycle"); !strings.Contains(got, "$CYCLE_") { 241 t.Fatalf("recursive expansion was not bounded: %q", got) 242 } 243 }