package config import ( "net/url" "os" "path/filepath" "strings" "testing" "time" ) func env(values map[string]string) func(string) string { return func(key string) string { return values[key] } } func TestLoadDefaults(t *testing.T) { cfg, err := load(env(map[string]string{ "BP_DB_USER": "botpontos_app", "BP_DB_PASSWORD": "segredo", })) if err != nil { t.Fatalf("erro inesperado: %v", err) } if cfg.DB.Host != "postgres" || cfg.DB.Port != 5432 || cfg.DB.Name != "botpontos" { t.Errorf("padrões do banco inesperados: %+v", cfg.DB) } if cfg.HTTP.Addr != ":8080" || cfg.Worker.HealthAddr != ":8081" { t.Errorf("endereços inesperados: api=%s worker=%s", cfg.HTTP.Addr, cfg.Worker.HealthAddr) } if cfg.Jobs.ReconciliationInterval != 15*time.Minute { t.Errorf("reconciliação = %s, esperado 15m", cfg.Jobs.ReconciliationInterval) } if cfg.Jobs.DispatchInterval != 2*time.Minute { t.Errorf("despacho = %s, esperado 2m", cfg.Jobs.DispatchInterval) } if cfg.Jobs.LateTolerance != 10*time.Minute { t.Errorf("tolerância = %s, esperado 10m", cfg.Jobs.LateTolerance) } if cfg.Jobs.APIFailureThreshold != 3 { t.Errorf("limite de falhas = %d, esperado 3", cfg.Jobs.APIFailureThreshold) } } func TestLoadSecretFromFile(t *testing.T) { path := filepath.Join(t.TempDir(), "pg_app_password") if err := os.WriteFile(path, []byte("segredo-do-arquivo\n"), 0o600); err != nil { t.Fatal(err) } cfg, err := load(env(map[string]string{ "BP_DB_USER": "botpontos_app", "BP_DB_PASSWORD_FILE": path, })) if err != nil { t.Fatalf("erro inesperado: %v", err) } if cfg.DB.Password != "segredo-do-arquivo" { t.Errorf("senha = %q, esperado sem quebra de linha", cfg.DB.Password) } } func TestLoadMissingSecretFile(t *testing.T) { _, err := load(env(map[string]string{ "BP_DB_USER": "botpontos_app", "BP_DB_PASSWORD_FILE": filepath.Join(t.TempDir(), "nao-existe"), })) if err == nil || !strings.Contains(err.Error(), "BP_DB_PASSWORD_FILE") { t.Fatalf("esperado erro de arquivo de segredo, veio: %v", err) } } func TestLoadInvalidValues(t *testing.T) { _, err := load(env(map[string]string{ "BP_LOG_LEVEL": "verboso", "BP_DISPATCH_INTERVAL": "abc", "BP_DB_PORT": "porta", "BP_DEFAULT_TIMEZONE": "Lugar/Inexistente", })) if err == nil { t.Fatal("esperado erro de validação") } for _, expected := range []string{ "BP_LOG_LEVEL", "BP_DISPATCH_INTERVAL", "BP_DB_PORT", "BP_DEFAULT_TIMEZONE", "BP_DB_USER", "BP_DB_PASSWORD", } { if !strings.Contains(err.Error(), expected) { t.Errorf("erro deveria citar %s: %v", expected, err) } } } func TestDSNEscapesPassword(t *testing.T) { db := DB{Host: "postgres", Port: 5432, Name: "botpontos", User: "app", Password: "p@ss/w:rd?#"} parsed, err := url.Parse(db.DSN()) if err != nil { t.Fatalf("DSN inválido: %v", err) } password, _ := parsed.User.Password() if password != db.Password { t.Errorf("senha = %q, esperado %q", password, db.Password) } if parsed.Query().Get("sslmode") != "disable" { t.Errorf("sslmode inesperado: %s", parsed.RawQuery) } }