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