main
Raw Download raw file
  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}