package config import ( "os" "path/filepath" "testing" ) func TestLoadFileReadsYAML(t *testing.T) { clearConfigEnv(t) path := writeConfig(t, ` addr: ":9000" pdns_api_url: "http://pdns.example.test:8081" pdns_api_key: "from-file" pdns_server_id: "authoritative" auth: disabled: true `) cfg, err := LoadFile(path) if err != nil { t.Fatalf("LoadFile returned error: %v", err) } if cfg.Addr != ":9000" { t.Fatalf("unexpected addr: %q", cfg.Addr) } if cfg.PDNSAPIURL != "http://pdns.example.test:8081" { t.Fatalf("unexpected api url: %q", cfg.PDNSAPIURL) } if cfg.PDNSAPIKey != "from-file" { t.Fatalf("unexpected api key: %q", cfg.PDNSAPIKey) } if cfg.PDNSServerID != "authoritative" { t.Fatalf("unexpected server id: %q", cfg.PDNSServerID) } } func TestLoadFileEnvironmentOverridesYAML(t *testing.T) { clearConfigEnv(t) t.Setenv("PDNS_API_KEY", "from-env") t.Setenv("PDNS_API_URL", "http://env.example.test:8081") path := writeConfig(t, ` pdns_api_url: "http://file.example.test:8081" pdns_api_key: "from-file" pdns_server_id: "from-file" auth: disabled: true `) cfg, err := LoadFile(path) if err != nil { t.Fatalf("LoadFile returned error: %v", err) } if cfg.PDNSAPIKey != "from-env" { t.Fatalf("expected env api key to win, got %q", cfg.PDNSAPIKey) } if cfg.PDNSAPIURL != "http://env.example.test:8081" { t.Fatalf("expected env api url to win, got %q", cfg.PDNSAPIURL) } if cfg.PDNSServerID != "from-file" { t.Fatalf("expected yaml server id, got %q", cfg.PDNSServerID) } } func TestLoadFileUsesDefaultsWithoutYAML(t *testing.T) { clearConfigEnv(t) t.Setenv("PDNS_API_KEY", "secret") t.Setenv("AUTH_DISABLED", "true") cfg, err := LoadFile("") if err != nil { t.Fatalf("LoadFile returned error: %v", err) } if cfg.Addr != ":8080" { t.Fatalf("unexpected default addr: %q", cfg.Addr) } if cfg.PDNSAPIURL != "http://localhost:8081" { t.Fatalf("unexpected default api url: %q", cfg.PDNSAPIURL) } if cfg.PDNSServerID != "localhost" { t.Fatalf("unexpected default server id: %q", cfg.PDNSServerID) } } func TestLoadFileRequiresAPIKey(t *testing.T) { clearConfigEnv(t) _, err := LoadFile("") if err == nil { t.Fatal("expected missing api key error") } } func TestLoadFileReadsLDAPConfig(t *testing.T) { clearConfigEnv(t) path := writeConfig(t, ` pdns_api_key: "secret" auth: ldap: url: ldap://ldap.example.com:389 start_tls: true insecure_skip_verify: true bind_dn: cn=dashboard-reader,ou=service,dc=example,dc=com bind_password: change-me user_base_dn: ou=users,dc=example,dc=com username_attribute: uid user_filter: "({username_attribute}={username})" group_base_dn: ou=groups,dc=example,dc=com group_filter: "(&(objectClass=groupOfNames)(cn=media-admins)(member={user_dn}))" `) cfg, err := LoadFile(path) if err != nil { t.Fatalf("LoadFile returned error: %v", err) } if cfg.Auth.Disabled { t.Fatal("auth should be enabled") } if cfg.Auth.LDAP.URL != "ldap://ldap.example.com:389" { t.Fatalf("unexpected ldap url: %q", cfg.Auth.LDAP.URL) } if !cfg.Auth.LDAP.StartTLS { t.Fatal("expected start_tls to be true") } if !cfg.Auth.LDAP.InsecureSkipVerify { t.Fatal("expected insecure_skip_verify to be true") } if cfg.Auth.LDAP.GroupFilter == "" { t.Fatal("expected group filter") } } func TestLoadFileLDAPEnvironmentOverridesYAML(t *testing.T) { clearConfigEnv(t) t.Setenv("AUTH_LDAP_URL", "ldap://env.example.com:389") t.Setenv("AUTH_LDAP_BIND_PASSWORD", "from-env") path := writeConfig(t, ` pdns_api_key: "secret" auth: ldap: url: ldap://file.example.com:389 bind_dn: cn=dashboard-reader,ou=service,dc=example,dc=com bind_password: from-file user_base_dn: ou=users,dc=example,dc=com `) cfg, err := LoadFile(path) if err != nil { t.Fatalf("LoadFile returned error: %v", err) } if cfg.Auth.LDAP.URL != "ldap://env.example.com:389" { t.Fatalf("expected env ldap url, got %q", cfg.Auth.LDAP.URL) } if cfg.Auth.LDAP.BindPassword != "from-env" { t.Fatalf("expected env bind password, got %q", cfg.Auth.LDAP.BindPassword) } } func TestLoadFileRequiresLDAPUnlessAuthDisabled(t *testing.T) { clearConfigEnv(t) t.Setenv("PDNS_API_KEY", "secret") _, err := LoadFile("") if err == nil { t.Fatal("expected missing ldap configuration error") } } func TestConfigSearchPathsOrderOnUnix(t *testing.T) { paths := configSearchPaths("/home/tester", "linux") want := []string{ "/etc/pdns_admin/config.yaml", filepath.Join("/home/tester", "config.yaml"), "config.yaml", } if len(paths) != len(want) { t.Fatalf("unexpected path count: %#v", paths) } for i := range want { if paths[i] != want[i] { t.Fatalf("path %d = %q, want %q", i, paths[i], want[i]) } } } func TestConfigSearchPathsOrderOnNonUnix(t *testing.T) { paths := configSearchPaths(`C:\Users\tester`, "windows") want := []string{ filepath.Join(`C:\Users\tester`, "config.yaml"), "config.yaml", } if len(paths) != len(want) { t.Fatalf("unexpected path count: %#v", paths) } for i := range want { if paths[i] != want[i] { t.Fatalf("path %d = %q, want %q", i, paths[i], want[i]) } } } func TestConfigSearchPathsWithoutHome(t *testing.T) { paths := configSearchPaths("", "windows") want := []string{"config.yaml"} if len(paths) != len(want) { t.Fatalf("unexpected path count: %#v", paths) } if paths[0] != want[0] { t.Fatalf("path = %q, want %q", paths[0], want[0]) } } func TestFirstExistingConfigPathUsesOrder(t *testing.T) { root := t.TempDir() first := filepath.Join(root, "first.yaml") second := filepath.Join(root, "second.yaml") if err := os.WriteFile(second, []byte("pdns_api_key: second"), 0o600); err != nil { t.Fatalf("write second config: %v", err) } if err := os.WriteFile(first, []byte("pdns_api_key: first"), 0o600); err != nil { t.Fatalf("write first config: %v", err) } got := firstExistingConfigPath([]string{first, second}) if got != first { t.Fatalf("got %q, want %q", got, first) } } func writeConfig(t *testing.T, contents string) string { t.Helper() path := filepath.Join(t.TempDir(), "config.yaml") if err := os.WriteFile(path, []byte(contents), 0o600); err != nil { t.Fatalf("write config: %v", err) } return path } func clearConfigEnv(t *testing.T) { t.Helper() for _, key := range []string{ "CONFIG_FILE", "ADDR", "PDNS_API_URL", "PDNS_API_KEY", "PDNS_SERVER_ID", "AUTH_DISABLED", "AUTH_LDAP_URL", "AUTH_LDAP_START_TLS", "AUTH_LDAP_INSECURE_SKIP_VERIFY", "AUTH_LDAP_BIND_DN", "AUTH_LDAP_BIND_PASSWORD", "AUTH_LDAP_USER_BASE_DN", "AUTH_LDAP_USERNAME_ATTRIBUTE", "AUTH_LDAP_USER_FILTER", "AUTH_LDAP_GROUP_BASE_DN", "AUTH_LDAP_GROUP_FILTER", } { unsetEnv(t, key) } } func unsetEnv(t *testing.T, key string) { t.Helper() oldValue, hadValue := os.LookupEnv(key) if err := os.Unsetenv(key); err != nil { t.Fatalf("unset %s: %v", key, err) } t.Cleanup(func() { if hadValue { _ = os.Setenv(key, oldValue) return } _ = os.Unsetenv(key) }) }