feat: traefik-selectel-dns — A-записи Selectel по роутам Traefik + CI сборка образа
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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("ожидалась ошибка")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user