package config import ( "testing" "time" ) // env builds a getenv func over a map, defaulting the two required vars so // each test only states what it cares about. func env(overrides map[string]string) func(string) string { base := map[string]string{ "THERMOGRAPH_INTERNAL_TOKEN": "sekrit", "THERMOGRAPH_API_BASE_INTERNAL": "http://web:8137", } for k, v := range overrides { base[k] = v } return func(k string) string { return base[k] } } func TestRequiredVarsRefuseToStart(t *testing.T) { for _, missing := range []string{"THERMOGRAPH_INTERNAL_TOKEN", "THERMOGRAPH_API_BASE_INTERNAL"} { if _, err := load(env(map[string]string{missing: ""})); err == nil { t.Errorf("expected error when %s is unset, got nil", missing) } } if cfg, err := load(env(nil)); err != nil || cfg == nil { t.Fatalf("both required vars set: unexpected error %v", err) } } func TestTruthyParsing(t *testing.T) { cases := []struct { flag, token string want bool }{ // Must match the Python _TRUTHY set exactly. {"1", "tok", true}, {"true", "tok", true}, {"TRUE", "tok", true}, {"yes", "tok", true}, {"on", "tok", true}, {" on ", "tok", true}, // whitespace tolerated, like .strip() in Python {"0", "tok", false}, {"false", "tok", false}, {"", "tok", false}, {"enabled", "tok", false}, // not in the truthy set // Flag alone is not enough: no token means no gateway. {"1", "", false}, } for _, c := range cases { cfg, err := load(env(map[string]string{ "THERMOGRAPH_DISCORD_BOT": c.flag, "THERMOGRAPH_DISCORD_BOT_TOKEN": c.token, })) if err != nil { t.Fatalf("flag=%q token=%q: unexpected error %v", c.flag, c.token, err) } if cfg.DiscordEnabled != c.want { t.Errorf("flag=%q token=%q: DiscordEnabled=%v, want %v", c.flag, c.token, cfg.DiscordEnabled, c.want) } } } func TestIntervalDefaultsAndFallbacks(t *testing.T) { cases := []struct { name string warm, indexnow string wantWarm time.Duration wantIndexNow time.Duration }{ {"unset uses defaults", "", "", 24 * time.Hour, 6 * time.Hour}, {"explicit values win", "12", "3", 12 * time.Hour, 3 * time.Hour}, // Malformed values degrade to the default instead of erroring, like // the Python int(... or 24) they replace. {"garbage falls back", "soon", "1.5", 24 * time.Hour, 6 * time.Hour}, {"non-positive falls back", "0", "-2", 24 * time.Hour, 6 * time.Hour}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { cfg, err := load(env(map[string]string{ "THERMOGRAPH_WARM_CITIES_INTERVAL_HOURS": c.warm, "THERMOGRAPH_INDEXNOW_INTERVAL_HOURS": c.indexnow, })) if err != nil { t.Fatalf("unexpected error: %v", err) } if cfg.WarmCitiesInterval != c.wantWarm { t.Errorf("WarmCitiesInterval=%v, want %v", cfg.WarmCitiesInterval, c.wantWarm) } if cfg.IndexNowInterval != c.wantIndexNow { t.Errorf("IndexNowInterval=%v, want %v", cfg.IndexNowInterval, c.wantIndexNow) } }) } }