package config import ( "os" "strings" "testing" ) var envNames = []string{ "APP_NAME", "DESCRIPTION", "CONTACT", "ICON", "LISTEN", "PORT", "DATA_DIR", "STATIC_DIR", "RELAY_URL", "SECRET_KEY", "AUTH_REQUIRED", "MAX_CONNECTIONS", "BLOSSOM_ENABLED", "BLOSSOM_MAX_BLOB_MB", "MAX_EVENT_BYTES", "MAX_SUBS_PER_CONN", "MAX_FILTERS", "MAX_LIMIT", "QUERY_LIMIT", "MAX_FUTURE_SECS", "EXPIRY_INTERVAL", "LOG_LEVEL", } // clearEnv removes both the TORLY_ and ORLY_ spellings so each test starts // from the documented defaults. func clearEnv(t *testing.T) { t.Helper() for _, name := range envNames { for _, prefix := range []string{"TORLY_", "ORLY_"} { key := prefix + name if old, ok := os.LookupEnv(key); ok { t.Cleanup(func() { os.Setenv(key, old) }) } os.Unsetenv(key) } } } func setEnv(t *testing.T, key, value string) { t.Helper() old, ok := os.LookupEnv(key) t.Cleanup(func() { if ok { os.Setenv(key, old) } else { os.Unsetenv(key) } }) os.Setenv(key, value) } func TestDefaults(t *testing.T) { clearEnv(t) cfg, err := FromEnv() if err != nil { t.Fatal(err) } if cfg.AppName != "torly" || cfg.Port != 3334 || cfg.ListenHost != "127.0.0.1" { t.Fatalf("unexpected defaults: %+v", cfg) } if cfg.AuthRequired || !cfg.BlossomEnabled { t.Fatalf("boolean defaults: %+v", cfg) } if cfg.MaxEventBytes != 128<<10 || cfg.MaxSubsPerConn != 64 || cfg.MaxLimit != 5000 { t.Fatalf("limit defaults: %+v", cfg) } if !strings.HasSuffix(cfg.DataDir, ".local/share/torly") { t.Fatalf("data dir: %q", cfg.DataDir) } if cfg.RelayURL != "ws://127.0.0.1:3334/" { t.Fatalf("relay url: %q", cfg.RelayURL) } if cfg.Addr() != "127.0.0.1:3334" { t.Fatalf("addr: %q", cfg.Addr()) } if !strings.Contains(EnvHelp(), "TORLY_PORT") { t.Fatal("help does not mention TORLY_PORT") } } func TestORLYFallbackAndTORLYPrecedence(t *testing.T) { clearEnv(t) setEnv(t, "ORLY_PORT", "4444") setEnv(t, "ORLY_APP_NAME", "legacy") setEnv(t, "ORLY_AUTH_REQUIRED", "true") cfg, err := FromEnv() if err != nil { t.Fatal(err) } if cfg.Port != 4444 || cfg.AppName != "legacy" || !cfg.AuthRequired { t.Fatalf("ORLY fallback not applied: %+v", cfg) } setEnv(t, "TORLY_PORT", "5555") setEnv(t, "TORLY_APP_NAME", "torly") cfg, err = FromEnv() if err != nil { t.Fatal(err) } if cfg.Port != 5555 || cfg.AppName != "torly" { t.Fatalf("TORLY did not win: %+v", cfg) } } func TestInvalidPort(t *testing.T) { for _, value := range []string{"abc", "-1", "70000"} { t.Run(value, func(t *testing.T) { clearEnv(t) setEnv(t, "TORLY_PORT", value) if _, err := FromEnv(); err == nil { t.Fatalf("port %q accepted", value) } }) } } func TestRelayURLDerivation(t *testing.T) { clearEnv(t) setEnv(t, "TORLY_LISTEN", "0.0.0.0") cfg, err := FromEnv() if err != nil { t.Fatal(err) } if cfg.RelayURL != "ws://localhost:3334/" { t.Fatalf("wildcard relay url: %q", cfg.RelayURL) } setEnv(t, "TORLY_RELAY_URL", "wss://relay.example/") cfg, err = FromEnv() if err != nil { t.Fatal(err) } if cfg.RelayURL != "wss://relay.example/" { t.Fatalf("explicit relay url: %q", cfg.RelayURL) } } func TestBoolAndIntParsing(t *testing.T) { cases := []struct { value string want bool }{ {"true", true}, {"1", true}, {"yes", true}, {"on", true}, {"false", false}, {"0", false}, {"no", false}, {"off", false}, {"garbage", true}, // unexpected values fall back to the default } for _, tc := range cases { clearEnv(t) setEnv(t, "TORLY_BLOSSOM_ENABLED", tc.value) cfg, err := FromEnv() if err != nil { t.Fatal(err) } if cfg.BlossomEnabled != tc.want { t.Errorf("BLOSSOM_ENABLED=%q -> %v, want %v", tc.value, cfg.BlossomEnabled, tc.want) } } } func TestBoundsAreEnforced(t *testing.T) { clearEnv(t) setEnv(t, "TORLY_MAX_EVENT_BYTES", "100") if _, err := FromEnv(); err == nil { t.Fatal("tiny max event size accepted") } clearEnv(t) setEnv(t, "TORLY_MAX_FILTERS", "0") setEnv(t, "TORLY_MAX_SUBS_PER_CONN", "0") setEnv(t, "TORLY_MAX_LIMIT", "0") setEnv(t, "TORLY_BLOSSOM_MAX_BLOB_MB", "0") cfg, err := FromEnv() if err != nil { t.Fatal(err) } if cfg.MaxFilters != 1 || cfg.MaxSubsPerConn != 1 || cfg.MaxLimit != 1 { t.Fatalf("clamps not applied: %+v", cfg) } if cfg.BlossomMaxBlob < 1<<20 { t.Fatalf("blob minimum not applied: %d", cfg.BlossomMaxBlob) } } func TestLogLevel(t *testing.T) { clearEnv(t) setEnv(t, "TORLY_LOG_LEVEL", "DEBUG") cfg, err := FromEnv() if err != nil { t.Fatal(err) } if cfg.LogLevel != "debug" { t.Fatalf("log level: %q", cfg.LogLevel) } }