package config import ( "os" "path/filepath" "testing" ) // validConfig returns a Config with all fields set to valid values. // TLS cert/key paths must be overridden by the caller for file validation tests. func validConfig() Config { return Config{ Server: ServerConfig{ ListenAddr: ":8443", GRPCAddr: ":9443", TLSCert: "/tmp/cert.pem", TLSKey: "/tmp/key.pem", }, Database: DatabaseConfig{ Path: "/srv/eng-pad/eng-pad.db", }, Auth: AuthConfig{ TokenTTL: "24h", Argon2Memory: 65536, Argon2Time: 3, Argon2Threads: 4, }, Log: LogConfig{ Level: "info", }, } } func TestValidateFields_ValidConfig(t *testing.T) { cfg := validConfig() if err := cfg.validateFields(); err != nil { t.Fatalf("valid config should pass field validation: %v", err) } } func TestValidateFields_MissingDatabasePath(t *testing.T) { cfg := validConfig() cfg.Database.Path = "" if err := cfg.validateFields(); err == nil { t.Fatal("expected error for missing database.path") } } func TestValidateFields_MissingTLSCert(t *testing.T) { cfg := validConfig() cfg.Server.TLSCert = "" if err := cfg.validateFields(); err == nil { t.Fatal("expected error for missing tls_cert") } } func TestValidateFields_MissingTLSKey(t *testing.T) { cfg := validConfig() cfg.Server.TLSKey = "" if err := cfg.validateFields(); err == nil { t.Fatal("expected error for missing tls_key") } } func TestValidateFields_MissingListenAddr(t *testing.T) { cfg := validConfig() cfg.Server.ListenAddr = "" if err := cfg.validateFields(); err == nil { t.Fatal("expected error for missing listen_addr") } } func TestValidateFields_MissingGRPCAddr(t *testing.T) { cfg := validConfig() cfg.Server.GRPCAddr = "" if err := cfg.validateFields(); err == nil { t.Fatal("expected error for missing grpc_addr") } } func TestValidateFields_InvalidTokenTTL(t *testing.T) { cfg := validConfig() cfg.Auth.TokenTTL = "not-a-duration" if err := cfg.validateFields(); err == nil { t.Fatal("expected error for invalid token_ttl") } } func TestValidateFields_NegativeTokenTTL(t *testing.T) { cfg := validConfig() cfg.Auth.TokenTTL = "-1h" if err := cfg.validateFields(); err == nil { t.Fatal("expected error for negative token_ttl") } } func TestValidateFields_ZeroTokenTTL(t *testing.T) { cfg := validConfig() cfg.Auth.TokenTTL = "0s" if err := cfg.validateFields(); err == nil { t.Fatal("expected error for zero token_ttl") } } func TestValidateFields_ZeroArgon2Memory(t *testing.T) { cfg := validConfig() cfg.Auth.Argon2Memory = 0 if err := cfg.validateFields(); err == nil { t.Fatal("expected error for zero argon2_memory") } } func TestValidateFields_ZeroArgon2Time(t *testing.T) { cfg := validConfig() cfg.Auth.Argon2Time = 0 if err := cfg.validateFields(); err == nil { t.Fatal("expected error for zero argon2_time") } } func TestValidateFields_ZeroArgon2Threads(t *testing.T) { cfg := validConfig() cfg.Auth.Argon2Threads = 0 if err := cfg.validateFields(); err == nil { t.Fatal("expected error for zero argon2_threads") } } func TestValidateFields_InvalidLogLevel(t *testing.T) { cfg := validConfig() cfg.Log.Level = "trace" if err := cfg.validateFields(); err == nil { t.Fatal("expected error for invalid log level") } } func TestValidateFields_AllLogLevels(t *testing.T) { for _, level := range []string{"debug", "info", "warn", "error"} { t.Run(level, func(t *testing.T) { cfg := validConfig() cfg.Log.Level = level if err := cfg.validateFields(); err != nil { t.Fatalf("log level %q should be valid: %v", level, err) } }) } } func TestValidateFiles_CertAndKeyExist(t *testing.T) { dir := t.TempDir() certPath := filepath.Join(dir, "cert.pem") keyPath := filepath.Join(dir, "key.pem") if err := os.WriteFile(certPath, []byte("cert"), 0600); err != nil { t.Fatalf("write cert: %v", err) } if err := os.WriteFile(keyPath, []byte("key"), 0600); err != nil { t.Fatalf("write key: %v", err) } cfg := validConfig() cfg.Server.TLSCert = certPath cfg.Server.TLSKey = keyPath if err := cfg.validateFiles(); err != nil { t.Fatalf("expected no error when cert/key files exist: %v", err) } } func TestValidateFiles_MissingCertFile(t *testing.T) { dir := t.TempDir() keyPath := filepath.Join(dir, "key.pem") if err := os.WriteFile(keyPath, []byte("key"), 0600); err != nil { t.Fatalf("write key: %v", err) } cfg := validConfig() cfg.Server.TLSCert = filepath.Join(dir, "nonexistent-cert.pem") cfg.Server.TLSKey = keyPath if err := cfg.validateFiles(); err == nil { t.Fatal("expected error for missing cert file") } } func TestValidateFiles_MissingKeyFile(t *testing.T) { dir := t.TempDir() certPath := filepath.Join(dir, "cert.pem") if err := os.WriteFile(certPath, []byte("cert"), 0600); err != nil { t.Fatalf("write cert: %v", err) } cfg := validConfig() cfg.Server.TLSCert = certPath cfg.Server.TLSKey = filepath.Join(dir, "nonexistent-key.pem") if err := cfg.validateFiles(); err == nil { t.Fatal("expected error for missing key file") } } func TestLoad_ValidTOML(t *testing.T) { dir := t.TempDir() certPath := filepath.Join(dir, "cert.pem") keyPath := filepath.Join(dir, "key.pem") if err := os.WriteFile(certPath, []byte("cert"), 0600); err != nil { t.Fatalf("write cert: %v", err) } if err := os.WriteFile(keyPath, []byte("key"), 0600); err != nil { t.Fatalf("write key: %v", err) } toml := ` [server] listen_addr = ":8443" grpc_addr = ":9443" tls_cert = "` + certPath + `" tls_key = "` + keyPath + `" [database] path = "/srv/eng-pad/eng-pad.db" [auth] token_ttl = "24h" argon2_memory = 65536 argon2_time = 3 argon2_threads = 4 [log] level = "info" ` configPath := filepath.Join(dir, "config.toml") if err := os.WriteFile(configPath, []byte(toml), 0600); err != nil { t.Fatalf("write config: %v", err) } cfg, err := Load(configPath) if err != nil { t.Fatalf("load valid config: %v", err) } if cfg.Server.ListenAddr != ":8443" { t.Fatalf("got listen_addr %q, want %q", cfg.Server.ListenAddr, ":8443") } }