package config import ( "fmt" "strings" "testing" "time" ) func env(m map[string]string) func(string) string { return func(k string) string { return m[k] } } func files(m map[string]string) func(string) ([]byte, error) { return func(p string) ([]byte, error) { if v, ok := m[p]; ok { return []byte(v), nil } return nil, fmt.Errorf("no such file %s", p) } } var goodEnv = map[string]string{ "SELECTEL_USERNAME": "u", "SELECTEL_PASSWORD": "p", "SELECTEL_ACCOUNT_ID": "1", "SELECTEL_PROJECT_ID": "2", } func withEnv(extra map[string]string) map[string]string { m := map[string]string{} for k, v := range goodEnv { m[k] = v } for k, v := range extra { m[k] = v } return m } func TestLoadYAMLAndDefaults(t *testing.T) { yml := "zones: [Example.com., example.com, dev.example.org]\nip: 1.2.3.4\ndelete:\n enabled: true\n gracePeriod: 15m\n" cfg, err := Load("/cfg.yaml", env(goodEnv), files(map[string]string{"/cfg.yaml": yml})) if err != nil { t.Fatal(err) } if len(cfg.Zones) != 2 || cfg.Zones[0] != "example.com" { t.Errorf("zones = %v", cfg.Zones) } if cfg.TTL != 300 || cfg.Interval != 30*time.Second || cfg.Listen != ":9090" || cfg.Traefik.APIURL != "http://traefik:8080" { t.Errorf("defaults: %+v", cfg) } if !cfg.Delete.Enabled || cfg.Delete.GracePeriod != 15*time.Minute { t.Errorf("delete: %+v", cfg.Delete) } } func TestDeleteDisabledByDefault(t *testing.T) { cfg, err := Load("", env(withEnv(map[string]string{"TSD_ZONES": "example.com", "TSD_IP": "1.2.3.4"})), files(nil)) if err != nil { t.Fatal(err) } if cfg.Delete.Enabled || cfg.Delete.GracePeriod != 10*time.Minute { t.Errorf("delete: %+v", cfg.Delete) } } func TestEnvOverrides(t *testing.T) { yml := "zones: [example.com]\nip: 1.2.3.4\nttl: 300\n" cfg, err := Load("/c.yaml", env(withEnv(map[string]string{ "TSD_IP": "5.6.7.8", "TSD_TTL": "120", "TSD_ZONES": "a.com, b.com", "TSD_DRY_RUN": "true", "TSD_INTERVAL": "1m", })), files(map[string]string{"/c.yaml": yml})) if err != nil { t.Fatal(err) } if cfg.IP != "5.6.7.8" || cfg.TTL != 120 || len(cfg.Zones) != 2 || cfg.Zones[1] != "b.com" || !cfg.DryRun || cfg.Interval != time.Minute { t.Errorf("cfg = %+v", cfg) } } func TestUnknownYAMLFieldRejected(t *testing.T) { _, err := Load("/c.yaml", env(goodEnv), files(map[string]string{"/c.yaml": "zones: [a.com]\nip: 1.1.1.1\npassword: leak\n"})) if err == nil { t.Fatal("ожидалась ошибка на неизвестное поле") } } func TestValidation(t *testing.T) { base := func() map[string]string { return withEnv(map[string]string{"TSD_ZONES": "example.com", "TSD_IP": "1.2.3.4"}) } cases := map[string]map[string]string{ "ip не IPv4": {"TSD_IP": "::1"}, "ip мусор": {"TSD_IP": "example"}, "ttl мал": {"TSD_TTL": "59"}, "ttl велик": {"TSD_TTL": "604801"}, "нет зон": {"TSD_ZONES": " "}, "grace < interval": {"TSD_DELETE_ENABLED": "true", "TSD_DELETE_GRACE_PERIOD": "1s"}, "bad loglevel": {"TSD_LOG_LEVEL": "trace"}, "bad bool": {"TSD_DRY_RUN": "maybe"}, "нет пароля": {"SELECTEL_PASSWORD": ""}, } for name, extra := range cases { t.Run(name, func(t *testing.T) { m := base() for k, v := range extra { m[k] = v } if _, err := Load("", env(m), files(nil)); err == nil { t.Fatal("ожидалась ошибка") } }) } if _, err := Load("", env(base()), files(nil)); err != nil { t.Fatalf("базовый конфиг должен быть валиден: %v", err) } } func TestSecretFileSupport(t *testing.T) { e := map[string]string{ "SELECTEL_USERNAME_FILE": "/run/secrets/u", "SELECTEL_PASSWORD_FILE": "/run/secrets/p", "SELECTEL_ACCOUNT_ID": "1", "SELECTEL_PROJECT_ID_FILE": "/run/secrets/pid", } f := files(map[string]string{"/run/secrets/u": "user\n", "/run/secrets/p": " pass \n", "/run/secrets/pid": "pid-1\n"}) c, err := LoadCredentials(env(e), f) if err != nil { t.Fatal(err) } if c.Username != "user" || c.Password != "pass" || c.ProjectID != "pid-1" || c.AccountID != "1" { t.Errorf("creds = %s", c) } } func TestSecretBothSetIsError(t *testing.T) { _, err := Secret("X", env(map[string]string{"X": "a", "X_FILE": "/f"}), files(map[string]string{"/f": "b"})) if err == nil || !strings.Contains(err.Error(), "одно") { t.Fatalf("err = %v", err) } } func TestSecretMissingFile(t *testing.T) { if _, err := Secret("X", env(map[string]string{"X_FILE": "/nope"}), files(nil)); err == nil { t.Fatal("ожидалась ошибка") } }