package config import ( "os" "path/filepath" "testing" "time" ) func TestDefaultIncludesServerNetworkSettings(t *testing.T) { cfg := Default() if cfg.Server.Addr != ":8080" { t.Fatalf("Addr = %q, want :8080", cfg.Server.Addr) } if cfg.Server.ReadTimeout != 15*time.Second { t.Fatalf("ReadTimeout = %v, want 15s", cfg.Server.ReadTimeout) } if cfg.Server.WriteTimeout != 15*time.Second { t.Fatalf("WriteTimeout = %v, want 15s", cfg.Server.WriteTimeout) } if cfg.Server.IdleTimeout != 60*time.Second { t.Fatalf("IdleTimeout = %v, want 60s", cfg.Server.IdleTimeout) } if cfg.Server.MaxHeaderBytes != 1048576 { t.Fatalf("MaxHeaderBytes = %d, want 1048576", cfg.Server.MaxHeaderBytes) } } func TestLoadParsesServerNetworkSettingsFromYAML(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") data := []byte(`server: addr: ":9090" readTimeout: "2s" writeTimeout: "3s" idleTimeout: "4s" maxHeaderBytes: 2048 workspace: root: "./tmp-workspaces" process: opencodeCommand: "fake-opencode" `) if err := os.WriteFile(path, data, 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } if cfg.Server.Addr != ":9090" { t.Fatalf("Addr = %q, want :9090", cfg.Server.Addr) } if cfg.Server.ReadTimeout != 2*time.Second || cfg.Server.WriteTimeout != 3*time.Second || cfg.Server.IdleTimeout != 4*time.Second { t.Fatalf("timeouts = %v/%v/%v, want 2s/3s/4s", cfg.Server.ReadTimeout, cfg.Server.WriteTimeout, cfg.Server.IdleTimeout) } if cfg.Server.MaxHeaderBytes != 2048 { t.Fatalf("MaxHeaderBytes = %d, want 2048", cfg.Server.MaxHeaderBytes) } } func TestLoadAppliesServerEnvironmentOverrides(t *testing.T) { t.Setenv("CODESPACE_ADDR", ":7070") t.Setenv("CODESPACE_READ_TIMEOUT", "5s") t.Setenv("CODESPACE_WRITE_TIMEOUT", "6s") t.Setenv("CODESPACE_IDLE_TIMEOUT", "7s") t.Setenv("CODESPACE_MAX_HEADER_BYTES", "4096") cfg, err := Load(filepath.Join(t.TempDir(), "missing.yaml")) if err != nil { t.Fatalf("Load returned error: %v", err) } if cfg.Server.Addr != ":7070" { t.Fatalf("Addr = %q, want :7070", cfg.Server.Addr) } if cfg.Server.ReadTimeout != 5*time.Second || cfg.Server.WriteTimeout != 6*time.Second || cfg.Server.IdleTimeout != 7*time.Second { t.Fatalf("timeouts = %v/%v/%v, want 5s/6s/7s", cfg.Server.ReadTimeout, cfg.Server.WriteTimeout, cfg.Server.IdleTimeout) } if cfg.Server.MaxHeaderBytes != 4096 { t.Fatalf("MaxHeaderBytes = %d, want 4096", cfg.Server.MaxHeaderBytes) } } func TestLoadRejectsInvalidDuration(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") data := []byte(`server: readTimeout: "garbage" `) if err := os.WriteFile(path, data, 0o644); err != nil { t.Fatal(err) } if _, err := Load(path); err == nil { t.Fatal("expected invalid duration error") } } func TestDefaultIncludesLogConfig(t *testing.T) { cfg := Default() if cfg.Log.Level != "info" { t.Fatalf("Log.Level = %q, want info", cfg.Log.Level) } if cfg.Log.Format != "json" { t.Fatalf("Log.Format = %q, want json", cfg.Log.Format) } } func TestLoadParsesLogConfigFromYAML(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") data := []byte(`log: level: "debug" format: "text" `) if err := os.WriteFile(path, data, 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } if cfg.Log.Level != "debug" { t.Fatalf("Log.Level = %q, want debug", cfg.Log.Level) } if cfg.Log.Format != "text" { t.Fatalf("Log.Format = %q, want text", cfg.Log.Format) } } func TestLoadAppliesLogEnvironmentOverrides(t *testing.T) { t.Setenv("CODESPACE_LOG_LEVEL", "warn") t.Setenv("CODESPACE_LOG_FORMAT", "json") cfg, err := Load(filepath.Join(t.TempDir(), "missing.yaml")) if err != nil { t.Fatalf("Load returned error: %v", err) } if cfg.Log.Level != "warn" { t.Fatalf("Log.Level = %q, want warn", cfg.Log.Level) } if cfg.Log.Format != "json" { t.Fatalf("Log.Format = %q, want json", cfg.Log.Format) } } func TestDefaultIncludesFileConfig(t *testing.T) { cfg := Default() if cfg.File.MaxWriteBytes != 1<<20 { t.Fatalf("File.MaxWriteBytes = %d, want 1048576", cfg.File.MaxWriteBytes) } } func TestLoadParsesFileConfigFromYAML(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") data := []byte(`file: maxWriteBytes: 128 `) if err := os.WriteFile(path, data, 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatal(err) } if cfg.File.MaxWriteBytes != 128 { t.Fatalf("got %d", cfg.File.MaxWriteBytes) } } func TestLoadAppliesFileEnvironmentOverride(t *testing.T) { t.Setenv("CODESPACE_FILE_MAX_WRITE_BYTES", "256") cfg, err := Load(filepath.Join(t.TempDir(), "missing.yaml")) if err != nil { t.Fatal(err) } if cfg.File.MaxWriteBytes != 256 { t.Fatalf("got %d", cfg.File.MaxWriteBytes) } } func TestDefaultIncludesGinConfig(t *testing.T) { cfg := Default() if cfg.Gin.Mode != "release" { t.Fatalf("Gin.Mode = %q, want release", cfg.Gin.Mode) } } func TestLoadParsesGinConfigFromYAML(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") data := []byte(`gin: mode: "debug" `) if err := os.WriteFile(path, data, 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) if err != nil { t.Fatalf("Load returned error: %v", err) } if cfg.Gin.Mode != "debug" { t.Fatalf("Gin.Mode = %q, want debug", cfg.Gin.Mode) } } func TestLoadAppliesGinModeEnvironmentOverride(t *testing.T) { t.Setenv("CODESPACE_GIN_MODE", "test") cfg, err := Load(filepath.Join(t.TempDir(), "missing.yaml")) if err != nil { t.Fatalf("Load returned error: %v", err) } if cfg.Gin.Mode != "test" { t.Fatalf("Gin.Mode = %q, want test", cfg.Gin.Mode) } } func TestLoadRejectsInvalidGinMode(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.yaml") data := []byte(`gin: mode: "verbose" `) if err := os.WriteFile(path, data, 0o644); err != nil { t.Fatal(err) } if _, err := Load(path); err == nil { t.Fatal("expected invalid gin mode error") } }