package config import ( "strings" "testing" ) func TestValidate(t *testing.T) { tests := []struct { name string cfg Config wantErr bool errSub string // substring to match in error if wantErr }{ { name: "valid default", cfg: GetDefaultConfig(), wantErr: false, }, { name: "valid zero concurrency", cfg: func() Config { c := GetDefaultConfig() c.MaxConcurrentRequests = 0 c.MaxRequestsPerClient = 0 return c }(), wantErr: false, }, { name: "valid negative? no, but zero ok; positive values", cfg: func() Config { c := GetDefaultConfig() c.MaxConcurrentRequests = 100 c.MaxRequestsPerClient = 10 c.Cache.Memory.GCAlgorithm = "lru" c.Cache.Disk.GCAlgorithm = "hybrid" c.Cache.Disk.Size = "10GB" c.Cache.Disk.Path = "/tmp/cache" return c }(), wantErr: false, }, { name: "negative max concurrent requests", cfg: func() Config { c := GetDefaultConfig() c.MaxConcurrentRequests = -1 return c }(), wantErr: true, errSub: "negative concurrency not allowed", }, { name: "negative max requests per client", cfg: func() Config { c := GetDefaultConfig() c.MaxRequestsPerClient = -5 return c }(), wantErr: true, errSub: "negative per-client limit not allowed", }, { name: "invalid memory gc algorithm", cfg: func() Config { c := GetDefaultConfig() c.Cache.Memory.GCAlgorithm = "invalid-alg" return c }(), wantErr: true, errSub: "invalid memory gc algorithm: invalid-alg", }, { name: "empty memory gc ok (treated as default)", cfg: func() Config { c := GetDefaultConfig() c.Cache.Memory.GCAlgorithm = "" return c }(), wantErr: false, }, { name: "valid memory gc values", cfg: func() Config { c := GetDefaultConfig() for _, alg := range []string{"lru", "lfu", "fifo", "largest", "smallest", "hybrid"} { c.Cache.Memory.GCAlgorithm = alg if err := c.Validate(); err != nil { t.Errorf("valid gc %s should not error: %v", alg, err) } } return c // last one }(), wantErr: false, }, { name: "disk enabled (non-zero size) but no path", cfg: func() Config { c := GetDefaultConfig() c.Cache.Disk.Size = "50GB" c.Cache.Disk.Path = "" return c }(), wantErr: true, errSub: "disk cache enabled but no path specified", }, { name: "disk size 0 (disabled) no path ok", cfg: func() Config { c := GetDefaultConfig() c.Cache.Disk.Size = "0" c.Cache.Disk.Path = "" return c }(), wantErr: false, }, { name: "disk size empty (disabled) no path ok", cfg: func() Config { c := GetDefaultConfig() c.Cache.Disk.Size = "" c.Cache.Disk.Path = "" return c }(), wantErr: false, }, { name: "disk enabled with path ok", cfg: func() Config { c := GetDefaultConfig() c.Cache.Disk.Size = "1TB" c.Cache.Disk.Path = "./disk" return c }(), wantErr: false, }, { name: "disk gc invalid does not fail (not validated by current impl)", cfg: func() Config { c := GetDefaultConfig() c.Cache.Disk.GCAlgorithm = "bad-disk-gc" c.Cache.Disk.Size = "10GB" c.Cache.Disk.Path = "/p" return c }(), wantErr: false, }, { name: "p1 new fields default ok (maxobj 0 + empty trusted proxies)", cfg: func() Config { c := GetDefaultConfig() c.MaxObjectSize = "0" c.TrustedProxies = nil return c }(), wantErr: false, }, { name: "valid negative_ttl duration", cfg: func() Config { c := GetDefaultConfig() c.Cache.NegativeTTL = "1m" return c }(), wantErr: false, }, { name: "empty negative_ttl ok", cfg: func() Config { c := GetDefaultConfig() c.Cache.NegativeTTL = "" return c }(), wantErr: false, }, { name: "invalid negative_ttl", cfg: func() Config { c := GetDefaultConfig() c.Cache.NegativeTTL = "not-a-duration" return c }(), wantErr: true, errSub: "invalid cache.negative_ttl", }, { name: "negative duration negative_ttl", cfg: func() Config { c := GetDefaultConfig() c.Cache.NegativeTTL = "-1s" return c }(), wantErr: true, errSub: "invalid cache.negative_ttl", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.cfg.Validate() if (err != nil) != tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr && tt.errSub != "" && err != nil { if !strings.Contains(err.Error(), tt.errSub) { t.Errorf("Validate() error %q does not contain %q", err.Error(), tt.errSub) } } }) } } func TestValidateUplinkBandwidth(t *testing.T) { cases := []struct { name string mutate func(*Config) wantErr bool errSub string }{ { name: "empty uplink ok", mutate: func(c *Config) { c.UplinkBandwidth = "" c.MaxBytesPerClientPerSec = 0 }, }, { name: "zero uplink ok", mutate: func(c *Config) { c.UplinkBandwidth = "0" }, }, { name: "valid human size", mutate: func(c *Config) { c.UplinkBandwidth = "10MB" }, }, { name: "invalid uplink", mutate: func(c *Config) { c.UplinkBandwidth = "not-a-size" }, wantErr: true, errSub: "uplink_bandwidth", }, { name: "negative max bytes", mutate: func(c *Config) { c.MaxBytesPerClientPerSec = -1 }, wantErr: true, errSub: "max_bytes_per_client_per_sec", }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { c := GetDefaultConfig() tt.mutate(&c) err := c.Validate() if tt.wantErr { if err == nil { t.Fatalf("Validate() error = nil, wantErr") } if tt.errSub != "" && !contains(err.Error(), tt.errSub) { t.Fatalf("Validate() error %q does not contain %q", err.Error(), tt.errSub) } return } if err != nil { t.Fatalf("Validate() unexpected error: %v", err) } }) } } func contains(s, sub string) bool { return strings.Contains(s, sub) }