config_test.go raw

   1  package config
   2  
   3  import (
   4  	"os"
   5  	"strings"
   6  	"testing"
   7  )
   8  
   9  var envNames = []string{
  10  	"APP_NAME", "DESCRIPTION", "CONTACT", "ICON", "LISTEN", "PORT", "DATA_DIR",
  11  	"STATIC_DIR", "RELAY_URL", "SECRET_KEY", "AUTH_REQUIRED", "MAX_CONNECTIONS",
  12  	"BLOSSOM_ENABLED", "BLOSSOM_MAX_BLOB_MB", "MAX_EVENT_BYTES",
  13  	"MAX_SUBS_PER_CONN", "MAX_FILTERS", "MAX_LIMIT", "QUERY_LIMIT",
  14  	"MAX_FUTURE_SECS", "EXPIRY_INTERVAL", "LOG_LEVEL",
  15  }
  16  
  17  // clearEnv removes both the TORLY_ and ORLY_ spellings so each test starts
  18  // from the documented defaults.
  19  func clearEnv(t *testing.T) {
  20  	t.Helper()
  21  	for _, name := range envNames {
  22  		for _, prefix := range []string{"TORLY_", "ORLY_"} {
  23  			key := prefix + name
  24  			if old, ok := os.LookupEnv(key); ok {
  25  				t.Cleanup(func() { os.Setenv(key, old) })
  26  			}
  27  			os.Unsetenv(key)
  28  		}
  29  	}
  30  }
  31  
  32  func setEnv(t *testing.T, key, value string) {
  33  	t.Helper()
  34  	old, ok := os.LookupEnv(key)
  35  	t.Cleanup(func() {
  36  		if ok {
  37  			os.Setenv(key, old)
  38  		} else {
  39  			os.Unsetenv(key)
  40  		}
  41  	})
  42  	os.Setenv(key, value)
  43  }
  44  
  45  func TestDefaults(t *testing.T) {
  46  	clearEnv(t)
  47  	cfg, err := FromEnv()
  48  	if err != nil {
  49  		t.Fatal(err)
  50  	}
  51  	if cfg.AppName != "torly" || cfg.Port != 3334 || cfg.ListenHost != "127.0.0.1" {
  52  		t.Fatalf("unexpected defaults: %+v", cfg)
  53  	}
  54  	if cfg.AuthRequired || !cfg.BlossomEnabled {
  55  		t.Fatalf("boolean defaults: %+v", cfg)
  56  	}
  57  	if cfg.MaxEventBytes != 128<<10 || cfg.MaxSubsPerConn != 64 || cfg.MaxLimit != 5000 {
  58  		t.Fatalf("limit defaults: %+v", cfg)
  59  	}
  60  	if !strings.HasSuffix(cfg.DataDir, ".local/share/torly") {
  61  		t.Fatalf("data dir: %q", cfg.DataDir)
  62  	}
  63  	if cfg.RelayURL != "ws://127.0.0.1:3334/" {
  64  		t.Fatalf("relay url: %q", cfg.RelayURL)
  65  	}
  66  	if cfg.Addr() != "127.0.0.1:3334" {
  67  		t.Fatalf("addr: %q", cfg.Addr())
  68  	}
  69  	if !strings.Contains(EnvHelp(), "TORLY_PORT") {
  70  		t.Fatal("help does not mention TORLY_PORT")
  71  	}
  72  }
  73  
  74  func TestORLYFallbackAndTORLYPrecedence(t *testing.T) {
  75  	clearEnv(t)
  76  	setEnv(t, "ORLY_PORT", "4444")
  77  	setEnv(t, "ORLY_APP_NAME", "legacy")
  78  	setEnv(t, "ORLY_AUTH_REQUIRED", "true")
  79  	cfg, err := FromEnv()
  80  	if err != nil {
  81  		t.Fatal(err)
  82  	}
  83  	if cfg.Port != 4444 || cfg.AppName != "legacy" || !cfg.AuthRequired {
  84  		t.Fatalf("ORLY fallback not applied: %+v", cfg)
  85  	}
  86  
  87  	setEnv(t, "TORLY_PORT", "5555")
  88  	setEnv(t, "TORLY_APP_NAME", "torly")
  89  	cfg, err = FromEnv()
  90  	if err != nil {
  91  		t.Fatal(err)
  92  	}
  93  	if cfg.Port != 5555 || cfg.AppName != "torly" {
  94  		t.Fatalf("TORLY did not win: %+v", cfg)
  95  	}
  96  }
  97  
  98  func TestInvalidPort(t *testing.T) {
  99  	for _, value := range []string{"abc", "-1", "70000"} {
 100  		t.Run(value, func(t *testing.T) {
 101  			clearEnv(t)
 102  			setEnv(t, "TORLY_PORT", value)
 103  			if _, err := FromEnv(); err == nil {
 104  				t.Fatalf("port %q accepted", value)
 105  			}
 106  		})
 107  	}
 108  }
 109  
 110  func TestRelayURLDerivation(t *testing.T) {
 111  	clearEnv(t)
 112  	setEnv(t, "TORLY_LISTEN", "0.0.0.0")
 113  	cfg, err := FromEnv()
 114  	if err != nil {
 115  		t.Fatal(err)
 116  	}
 117  	if cfg.RelayURL != "ws://localhost:3334/" {
 118  		t.Fatalf("wildcard relay url: %q", cfg.RelayURL)
 119  	}
 120  
 121  	setEnv(t, "TORLY_RELAY_URL", "wss://relay.example/")
 122  	cfg, err = FromEnv()
 123  	if err != nil {
 124  		t.Fatal(err)
 125  	}
 126  	if cfg.RelayURL != "wss://relay.example/" {
 127  		t.Fatalf("explicit relay url: %q", cfg.RelayURL)
 128  	}
 129  }
 130  
 131  func TestBoolAndIntParsing(t *testing.T) {
 132  	cases := []struct {
 133  		value string
 134  		want  bool
 135  	}{
 136  		{"true", true},
 137  		{"1", true},
 138  		{"yes", true},
 139  		{"on", true},
 140  		{"false", false},
 141  		{"0", false},
 142  		{"no", false},
 143  		{"off", false},
 144  		{"garbage", true}, // unexpected values fall back to the default
 145  	}
 146  	for _, tc := range cases {
 147  		clearEnv(t)
 148  		setEnv(t, "TORLY_BLOSSOM_ENABLED", tc.value)
 149  		cfg, err := FromEnv()
 150  		if err != nil {
 151  			t.Fatal(err)
 152  		}
 153  		if cfg.BlossomEnabled != tc.want {
 154  			t.Errorf("BLOSSOM_ENABLED=%q -> %v, want %v", tc.value, cfg.BlossomEnabled, tc.want)
 155  		}
 156  	}
 157  }
 158  
 159  func TestBoundsAreEnforced(t *testing.T) {
 160  	clearEnv(t)
 161  	setEnv(t, "TORLY_MAX_EVENT_BYTES", "100")
 162  	if _, err := FromEnv(); err == nil {
 163  		t.Fatal("tiny max event size accepted")
 164  	}
 165  
 166  	clearEnv(t)
 167  	setEnv(t, "TORLY_MAX_FILTERS", "0")
 168  	setEnv(t, "TORLY_MAX_SUBS_PER_CONN", "0")
 169  	setEnv(t, "TORLY_MAX_LIMIT", "0")
 170  	setEnv(t, "TORLY_BLOSSOM_MAX_BLOB_MB", "0")
 171  	cfg, err := FromEnv()
 172  	if err != nil {
 173  		t.Fatal(err)
 174  	}
 175  	if cfg.MaxFilters != 1 || cfg.MaxSubsPerConn != 1 || cfg.MaxLimit != 1 {
 176  		t.Fatalf("clamps not applied: %+v", cfg)
 177  	}
 178  	if cfg.BlossomMaxBlob < 1<<20 {
 179  		t.Fatalf("blob minimum not applied: %d", cfg.BlossomMaxBlob)
 180  	}
 181  }
 182  
 183  func TestLogLevel(t *testing.T) {
 184  	clearEnv(t)
 185  	setEnv(t, "TORLY_LOG_LEVEL", "DEBUG")
 186  	cfg, err := FromEnv()
 187  	if err != nil {
 188  		t.Fatal(err)
 189  	}
 190  	if cfg.LogLevel != "debug" {
 191  		t.Fatalf("log level: %q", cfg.LogLevel)
 192  	}
 193  }
 194