main
1package config
2
3import (
4 "testing"
5 "time"
6)
7
8func TestNewDefaultConfig(t *testing.T) {
9 cfg := NewDefaultConfig()
10
11 // Network configuration
12 if cfg.Address != "localhost" {
13 t.Errorf("Address = %q, want %q", cfg.Address, "localhost")
14 }
15 if cfg.Port != 8000 {
16 t.Errorf("Port = %d, want %d", cfg.Port, 8000)
17 }
18 if cfg.SocketTimeout != 60*time.Second {
19 t.Errorf("SocketTimeout = %v, want %v", cfg.SocketTimeout, 60*time.Second)
20 }
21
22 // WARC output configuration
23 if cfg.WARCDirectory != "./warcs" {
24 t.Errorf("WARCDirectory = %q, want %q", cfg.WARCDirectory, "./warcs")
25 }
26 if cfg.WARCPrefix != "warcprox" {
27 t.Errorf("WARCPrefix = %q, want %q", cfg.WARCPrefix, "warcprox")
28 }
29 if cfg.WARCSize != 1000000000 {
30 t.Errorf("WARCSize = %d, want %d", cfg.WARCSize, 1000000000)
31 }
32 if cfg.WARCCompression != "gzip" {
33 t.Errorf("WARCCompression = %q, want %q", cfg.WARCCompression, "gzip")
34 }
35 if !cfg.GzipEnabled {
36 t.Error("GzipEnabled = false, want true")
37 }
38 if cfg.DigestAlgorithm != "sha1" {
39 t.Errorf("DigestAlgorithm = %q, want %q", cfg.DigestAlgorithm, "sha1")
40 }
41 if cfg.WARCWriterThreads != 1 {
42 t.Errorf("WARCWriterThreads = %d, want %d", cfg.WARCWriterThreads, 1)
43 }
44
45 // HTTPS/Certificate configuration
46 if cfg.CACertFile != "warcprox-ca.pem" {
47 t.Errorf("CACertFile = %q, want %q", cfg.CACertFile, "warcprox-ca.pem")
48 }
49 if cfg.CertsDir != "./warcprox-ca" {
50 t.Errorf("CertsDir = %q, want %q", cfg.CertsDir, "./warcprox-ca")
51 }
52
53 // Deduplication configuration
54 if !cfg.DedupEnabled {
55 t.Error("DedupEnabled = false, want true")
56 }
57 if cfg.DedupDBFile != "warcprox.sqlite" {
58 t.Errorf("DedupDBFile = %q, want %q", cfg.DedupDBFile, "warcprox.sqlite")
59 }
60
61 // Statistics configuration
62 if !cfg.StatsEnabled {
63 t.Error("StatsEnabled = false, want true")
64 }
65 if cfg.StatsDBFile != "warcprox.sqlite" {
66 t.Errorf("StatsDBFile = %q, want %q", cfg.StatsDBFile, "warcprox.sqlite")
67 }
68
69 // Performance configuration
70 if cfg.MaxThreads != 100 {
71 t.Errorf("MaxThreads = %d, want %d", cfg.MaxThreads, 100)
72 }
73 if cfg.QueueSize != 1000 {
74 t.Errorf("QueueSize = %d, want %d", cfg.QueueSize, 1000)
75 }
76 if cfg.TmpFileMaxMemory != 524288 {
77 t.Errorf("TmpFileMaxMemory = %d, want %d", cfg.TmpFileMaxMemory, 524288)
78 }
79 if cfg.MaxResourceSize != 0 {
80 t.Errorf("MaxResourceSize = %d, want %d", cfg.MaxResourceSize, 0)
81 }
82 if cfg.BatchFlushTimeout != 10*time.Second {
83 t.Errorf("BatchFlushTimeout = %v, want %v", cfg.BatchFlushTimeout, 10*time.Second)
84 }
85 if cfg.BatchFlushMaxURLs != 500 {
86 t.Errorf("BatchFlushMaxURLs = %d, want %d", cfg.BatchFlushMaxURLs, 500)
87 }
88
89 // Logging configuration
90 if cfg.Verbose {
91 t.Error("Verbose = true, want false")
92 }
93 if cfg.LogLevel != "info" {
94 t.Errorf("LogLevel = %q, want %q", cfg.LogLevel, "info")
95 }
96}
97
98func TestConfig_NoNilFields(t *testing.T) {
99 cfg := NewDefaultConfig()
100
101 // Ensure no pointer fields are nil (we don't have any currently, but this is good practice)
102 // and all string fields have values
103 if cfg.Address == "" {
104 t.Error("Address is empty string")
105 }
106 if cfg.WARCDirectory == "" {
107 t.Error("WARCDirectory is empty string")
108 }
109 if cfg.WARCPrefix == "" {
110 t.Error("WARCPrefix is empty string")
111 }
112 if cfg.WARCCompression == "" {
113 t.Error("WARCCompression is empty string")
114 }
115 if cfg.DigestAlgorithm == "" {
116 t.Error("DigestAlgorithm is empty string")
117 }
118 if cfg.CACertFile == "" {
119 t.Error("CACertFile is empty string")
120 }
121 if cfg.CertsDir == "" {
122 t.Error("CertsDir is empty string")
123 }
124 if cfg.DedupDBFile == "" {
125 t.Error("DedupDBFile is empty string")
126 }
127 if cfg.StatsDBFile == "" {
128 t.Error("StatsDBFile is empty string")
129 }
130 if cfg.LogLevel == "" {
131 t.Error("LogLevel is empty string")
132 }
133}
134
135func TestConfig_ReasonableDefaults(t *testing.T) {
136 cfg := NewDefaultConfig()
137
138 // Port should be valid
139 if cfg.Port < 1 || cfg.Port > 65535 {
140 t.Errorf("Port = %d, should be in range 1-65535", cfg.Port)
141 }
142
143 // WARC size should be positive
144 if cfg.WARCSize <= 0 {
145 t.Errorf("WARCSize = %d, should be positive", cfg.WARCSize)
146 }
147
148 // WARC writer threads should be positive
149 if cfg.WARCWriterThreads < 1 {
150 t.Errorf("WARCWriterThreads = %d, should be at least 1", cfg.WARCWriterThreads)
151 }
152
153 // Timeouts should be positive
154 if cfg.SocketTimeout <= 0 {
155 t.Errorf("SocketTimeout = %v, should be positive", cfg.SocketTimeout)
156 }
157 if cfg.BatchFlushTimeout <= 0 {
158 t.Errorf("BatchFlushTimeout = %v, should be positive", cfg.BatchFlushTimeout)
159 }
160
161 // Queue sizes should be positive
162 if cfg.QueueSize < 1 {
163 t.Errorf("QueueSize = %d, should be at least 1", cfg.QueueSize)
164 }
165 if cfg.MaxThreads < 1 {
166 t.Errorf("MaxThreads = %d, should be at least 1", cfg.MaxThreads)
167 }
168 if cfg.BatchFlushMaxURLs < 1 {
169 t.Errorf("BatchFlushMaxURLs = %d, should be at least 1", cfg.BatchFlushMaxURLs)
170 }
171
172 // Digest algorithm should be valid
173 validDigests := map[string]bool{"sha1": true, "sha256": true, "blake3": true}
174 if !validDigests[cfg.DigestAlgorithm] {
175 t.Errorf("DigestAlgorithm = %q, should be one of sha1, sha256, blake3", cfg.DigestAlgorithm)
176 }
177
178 // Compression should be valid
179 validCompressions := map[string]bool{"gzip": true, "zstd": true, "": true}
180 if !validCompressions[cfg.WARCCompression] {
181 t.Errorf("WARCCompression = %q, should be one of gzip, zstd, or empty", cfg.WARCCompression)
182 }
183
184 // Log level should be valid
185 validLogLevels := map[string]bool{"debug": true, "info": true, "warn": true, "error": true}
186 if !validLogLevels[cfg.LogLevel] {
187 t.Errorf("LogLevel = %q, should be one of debug, info, warn, error", cfg.LogLevel)
188 }
189}
190
191func TestConfig_Validate_DefaultsValid(t *testing.T) {
192 cfg := NewDefaultConfig()
193 if err := cfg.Validate(); err != nil {
194 t.Errorf("default config should be valid, got error: %v", err)
195 }
196}
197
198func TestConfig_Validate_InvalidPort(t *testing.T) {
199 tests := []struct {
200 name string
201 port int
202 }{
203 {"zero", 0},
204 {"negative", -1},
205 {"too high", 65536},
206 }
207
208 for _, tt := range tests {
209 t.Run(tt.name, func(t *testing.T) {
210 cfg := NewDefaultConfig()
211 cfg.Port = tt.port
212 if err := cfg.Validate(); err == nil {
213 t.Errorf("expected error for port %d, got nil", tt.port)
214 }
215 })
216 }
217}
218
219func TestConfig_Validate_InvalidDigest(t *testing.T) {
220 cfg := NewDefaultConfig()
221 cfg.DigestAlgorithm = "md5"
222 if err := cfg.Validate(); err == nil {
223 t.Error("expected error for invalid digest algorithm, got nil")
224 }
225}
226
227func TestConfig_Validate_InvalidCompression(t *testing.T) {
228 cfg := NewDefaultConfig()
229 cfg.WARCCompression = "bzip2"
230 if err := cfg.Validate(); err == nil {
231 t.Error("expected error for invalid compression, got nil")
232 }
233}
234
235func TestConfig_Validate_InvalidLogLevel(t *testing.T) {
236 cfg := NewDefaultConfig()
237 cfg.LogLevel = "verbose"
238 if err := cfg.Validate(); err == nil {
239 t.Error("expected error for invalid log level, got nil")
240 }
241}
242
243func TestConfig_Validate_EmptyRequired(t *testing.T) {
244 tests := []struct {
245 name string
246 setup func(*Config)
247 }{
248 {"empty directory", func(c *Config) { c.WARCDirectory = "" }},
249 {"empty prefix", func(c *Config) { c.WARCPrefix = "" }},
250 {"empty cacert", func(c *Config) { c.CACertFile = "" }},
251 {"empty certs-dir", func(c *Config) { c.CertsDir = "" }},
252 }
253
254 for _, tt := range tests {
255 t.Run(tt.name, func(t *testing.T) {
256 cfg := NewDefaultConfig()
257 tt.setup(cfg)
258 if err := cfg.Validate(); err == nil {
259 t.Errorf("expected error for %s, got nil", tt.name)
260 }
261 })
262 }
263}
264
265func TestConfig_Validate_NegativeValues(t *testing.T) {
266 tests := []struct {
267 name string
268 setup func(*Config)
269 }{
270 {"negative WARC size", func(c *Config) { c.WARCSize = -1 }},
271 {"zero WARC size", func(c *Config) { c.WARCSize = 0 }},
272 {"negative max threads", func(c *Config) { c.MaxThreads = -1 }},
273 {"zero max threads", func(c *Config) { c.MaxThreads = 0 }},
274 {"negative queue size", func(c *Config) { c.QueueSize = -1 }},
275 {"zero queue size", func(c *Config) { c.QueueSize = 0 }},
276 {"negative tmp file memory", func(c *Config) { c.TmpFileMaxMemory = -1 }},
277 {"negative max resource size", func(c *Config) { c.MaxResourceSize = -1 }},
278 }
279
280 for _, tt := range tests {
281 t.Run(tt.name, func(t *testing.T) {
282 cfg := NewDefaultConfig()
283 tt.setup(cfg)
284 if err := cfg.Validate(); err == nil {
285 t.Errorf("expected error for %s, got nil", tt.name)
286 }
287 })
288 }
289}
290
291func TestConfig_Validate_MultipleErrors(t *testing.T) {
292 cfg := NewDefaultConfig()
293 cfg.Port = 0
294 cfg.WARCSize = 0
295 cfg.DigestAlgorithm = "invalid"
296
297 err := cfg.Validate()
298 if err == nil {
299 t.Fatal("expected multiple errors, got nil")
300 }
301
302 // errors.Join produces an error that contains all errors
303 errStr := err.Error()
304 if errStr == "" {
305 t.Error("expected non-empty error message")
306 }
307}