v0.3.0: fix HTTP client leak, add tests and CI pipeline
Reuse a single long-poll HTTP client instead of creating one per Events() call (~every 30s). Make TLS skip-verify configurable via syncthing_insecure_tls. Log previously swallowed config errors. Add unit tests for all monitor trackers, config, and state logic. Add CI workflow (vet, golangci-lint, govulncheck, go test -race). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
@@ -9,9 +10,10 @@ import (
|
||||
// Config holds SyncWarden configuration.
|
||||
type Config struct {
|
||||
// Connection
|
||||
SyncthingAddress string `json:"syncthing_address"`
|
||||
SyncthingAPIKey string `json:"syncthing_api_key"`
|
||||
SyncthingUseTLS bool `json:"syncthing_use_tls"`
|
||||
SyncthingAddress string `json:"syncthing_address"`
|
||||
SyncthingAPIKey string `json:"syncthing_api_key"`
|
||||
SyncthingUseTLS bool `json:"syncthing_use_tls"`
|
||||
SyncthingInsecureTLS bool `json:"syncthing_insecure_tls"`
|
||||
|
||||
// Feature toggles
|
||||
EnableNotifications bool `json:"enable_notifications"`
|
||||
@@ -36,6 +38,7 @@ var defaults = Config{
|
||||
SyncthingAddress: "localhost:8384",
|
||||
SyncthingAPIKey: "",
|
||||
SyncthingUseTLS: false,
|
||||
SyncthingInsecureTLS: true,
|
||||
EnableNotifications: true,
|
||||
EnableRecentFiles: true,
|
||||
EnableConflictAlerts: true,
|
||||
@@ -65,7 +68,9 @@ func Load() Config {
|
||||
if err != nil {
|
||||
return cfg
|
||||
}
|
||||
_ = json.Unmarshal(data, &cfg)
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
log.Printf("config: parse error: %v", err)
|
||||
}
|
||||
cached = &cfg
|
||||
return cfg
|
||||
}
|
||||
@@ -75,7 +80,7 @@ func Save(cfg Config) error {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
dir := ConfigDir()
|
||||
dir := configDir()
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
90
internal/config/config_test.go
Normal file
90
internal/config/config_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func withTempDir(t *testing.T) {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
configDirOverride = dir
|
||||
t.Cleanup(func() { configDirOverride = "" })
|
||||
}
|
||||
|
||||
func TestLoad_Defaults(t *testing.T) {
|
||||
withTempDir(t)
|
||||
|
||||
cfg := Load()
|
||||
if cfg.SyncthingAddress != "localhost:8384" {
|
||||
t.Errorf("expected default address, got %q", cfg.SyncthingAddress)
|
||||
}
|
||||
if !cfg.SyncthingInsecureTLS {
|
||||
t.Error("SyncthingInsecureTLS should default to true")
|
||||
}
|
||||
if !cfg.EnableNotifications {
|
||||
t.Error("EnableNotifications should default to true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveLoadRoundTrip(t *testing.T) {
|
||||
withTempDir(t)
|
||||
|
||||
cfg := Load()
|
||||
cfg.SyncthingAPIKey = "test-key-12345"
|
||||
cfg.SyncthingAddress = "192.168.1.100:8384"
|
||||
cfg.EnableRecentFiles = false
|
||||
|
||||
if err := Save(cfg); err != nil {
|
||||
t.Fatalf("Save failed: %v", err)
|
||||
}
|
||||
|
||||
// Clear cache so Load reads from disk
|
||||
mu.Lock()
|
||||
cached = nil
|
||||
mu.Unlock()
|
||||
|
||||
loaded := Load()
|
||||
if loaded.SyncthingAPIKey != "test-key-12345" {
|
||||
t.Errorf("API key not round-tripped: got %q", loaded.SyncthingAPIKey)
|
||||
}
|
||||
if loaded.SyncthingAddress != "192.168.1.100:8384" {
|
||||
t.Errorf("address not round-tripped: got %q", loaded.SyncthingAddress)
|
||||
}
|
||||
if loaded.EnableRecentFiles {
|
||||
t.Error("EnableRecentFiles should be false after round-trip")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseURL_TLSToggle(t *testing.T) {
|
||||
cfg := Config{
|
||||
SyncthingAddress: "localhost:8384",
|
||||
SyncthingUseTLS: false,
|
||||
}
|
||||
if cfg.BaseURL() != "http://localhost:8384" {
|
||||
t.Errorf("expected http URL, got %q", cfg.BaseURL())
|
||||
}
|
||||
|
||||
cfg.SyncthingUseTLS = true
|
||||
if cfg.BaseURL() != "https://localhost:8384" {
|
||||
t.Errorf("expected https URL, got %q", cfg.BaseURL())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_InvalidJSON(t *testing.T) {
|
||||
withTempDir(t)
|
||||
|
||||
// Write invalid JSON to the config path
|
||||
if err := os.MkdirAll(configDir(), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(ConfigPath(), []byte("{invalid"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Should return defaults without panicking
|
||||
cfg := Load()
|
||||
if cfg.SyncthingAddress != "localhost:8384" {
|
||||
t.Errorf("expected defaults on invalid JSON, got %q", cfg.SyncthingAddress)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,18 @@ package config
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
// configDirOverride allows tests to redirect config I/O to a temp directory.
|
||||
var configDirOverride string
|
||||
|
||||
// configDir returns the override dir if set, otherwise the platform default.
|
||||
func configDir() string {
|
||||
if configDirOverride != "" {
|
||||
return configDirOverride
|
||||
}
|
||||
return ConfigDir()
|
||||
}
|
||||
|
||||
// ConfigPath returns the path to config.json.
|
||||
func ConfigPath() string {
|
||||
return filepath.Join(ConfigDir(), "config.json")
|
||||
return filepath.Join(configDir(), "config.json")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user