package config import ( "os" "path/filepath" "testing" ) func TestLoadDefaults(t *testing.T) { // Point HOME/USERPROFILE to a temp dir with no config file. tmp := t.TempDir() t.Setenv("HOME", tmp) t.Setenv("USERPROFILE", tmp) // Windows compatibility cfg, err := Load() if err != nil { t.Fatalf("Load() error: %v", err) } if cfg.Daemon.Port != DefaultPort { t.Errorf("default port = %d, want %d", cfg.Daemon.Port, DefaultPort) } if cfg.Daemon.PollIntervalMs != DefaultPollIntervalMs { t.Errorf("default poll = %d, want %d", cfg.Daemon.PollIntervalMs, DefaultPollIntervalMs) } if len(cfg.Peers) != 0 { t.Errorf("default peers = %d, want 0", len(cfg.Peers)) } } func TestLoadFromFile(t *testing.T) { tmp := t.TempDir() cfgDir := filepath.Join(tmp, ConfigDir) if err := os.MkdirAll(cfgDir, 0755); err != nil { t.Fatalf("MkdirAll: %v", err) } content := ` [daemon] port = 9999 poll_interval_ms = 250 [[peers]] name = "bureau" addr = "192.168.1.10:9137" [[peers]] name = "portable" addr = "192.168.1.20:9137" ` if err := os.WriteFile(filepath.Join(cfgDir, ConfigFile), []byte(content), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } t.Setenv("HOME", tmp) t.Setenv("USERPROFILE", tmp) // Windows compatibility cfg, err := Load() if err != nil { t.Fatalf("Load() error: %v", err) } if cfg.Daemon.Port != 9999 { t.Errorf("port = %d, want 9999", cfg.Daemon.Port) } if cfg.Daemon.PollIntervalMs != 250 { t.Errorf("poll = %d, want 250", cfg.Daemon.PollIntervalMs) } if len(cfg.Peers) != 2 { t.Fatalf("peers = %d, want 2", len(cfg.Peers)) } if cfg.Peers[0].Name != "bureau" { t.Errorf("peer[0].Name = %q, want \"bureau\"", cfg.Peers[0].Name) } if cfg.Peers[1].Addr != "192.168.1.20:9137" { t.Errorf("peer[1].Addr = %q", cfg.Peers[1].Addr) } } func TestEnvOverride(t *testing.T) { tmp := t.TempDir() t.Setenv("HOME", tmp) t.Setenv("USERPROFILE", tmp) // Windows compatibility t.Setenv("CLIP_SYNC_PORT", "8080") t.Setenv("CLIP_SYNC_POLL_MS", "100") cfg, err := Load() if err != nil { t.Fatalf("Load() error: %v", err) } if cfg.Daemon.Port != 8080 { t.Errorf("port = %d, want 8080", cfg.Daemon.Port) } if cfg.Daemon.PollIntervalMs != 100 { t.Errorf("poll = %d, want 100", cfg.Daemon.PollIntervalMs) } } func TestEnvOverrideInvalidPort(t *testing.T) { tmp := t.TempDir() t.Setenv("HOME", tmp) t.Setenv("USERPROFILE", tmp) // Windows compatibility t.Setenv("CLIP_SYNC_PORT", "not-a-number") _, err := Load() if err == nil { t.Fatal("expected error for invalid CLIP_SYNC_PORT, got nil") } } func TestPollInterval(t *testing.T) { cfg := &Config{ Daemon: DaemonConfig{PollIntervalMs: 500}, } if d := cfg.PollInterval(); d.Milliseconds() != 500 { t.Errorf("PollInterval = %v, want 500ms", d) } } func TestListenAddr(t *testing.T) { cfg := &Config{ Daemon: DaemonConfig{Port: 9137}, } if addr := cfg.ListenAddr(); addr != ":9137" { t.Errorf("ListenAddr = %q, want \":9137\"", addr) } } func TestLoadFullConfig(t *testing.T) { tmp := t.TempDir() cfgDir := filepath.Join(tmp, ConfigDir) if err := os.MkdirAll(cfgDir, 0755); err != nil { t.Fatalf("MkdirAll: %v", err) } content := ` [daemon] port = 9999 poll_interval_ms = 250 max_body_bytes = 2097152 history_size = 100 discovery = true notify = true sync_images = true receive_files = true receive_dir = "/tmp/received" [security] shared_key = "secret-key" allowed_origins = ["host-a", "host-b"] tls = true cert_file = "/tmp/cert.pem" key_file = "/tmp/key.pem" insecure_skip_verify = true [[peers]] name = "bureau" addr = "192.168.1.10:9137" tls = true ` if err := os.WriteFile(filepath.Join(cfgDir, ConfigFile), []byte(content), 0644); err != nil { t.Fatalf("WriteFile: %v", err) } t.Setenv("HOME", tmp) t.Setenv("USERPROFILE", tmp) cfg, err := Load() if err != nil { t.Fatalf("Load() error: %v", err) } if cfg.Daemon.MaxBodyBytes != 2097152 { t.Errorf("max_body_bytes = %d, want 2097152", cfg.Daemon.MaxBodyBytes) } if cfg.Daemon.HistorySize != 100 { t.Errorf("history_size = %d, want 100", cfg.Daemon.HistorySize) } if !cfg.Daemon.Discovery || !cfg.Daemon.Notify || !cfg.Daemon.SyncImages || !cfg.Daemon.ReceiveFiles { t.Errorf("boolean flags not parsed: %+v", cfg.Daemon) } if cfg.Daemon.ReceiveDir != "/tmp/received" { t.Errorf("receive_dir = %q", cfg.Daemon.ReceiveDir) } if cfg.Security.SharedKey != "secret-key" { t.Errorf("shared_key = %q", cfg.Security.SharedKey) } if len(cfg.Security.AllowedOrigins) != 2 { t.Errorf("allowed_origins = %v, want 2", cfg.Security.AllowedOrigins) } if !cfg.Security.TLS || !cfg.Security.InsecureSkipVerify { t.Errorf("security flags not parsed: %+v", cfg.Security) } if len(cfg.Peers) != 1 || !cfg.Peers[0].TLS { t.Errorf("peer TLS override not parsed: %+v", cfg.Peers) } } func TestMaxBodyBytesDefault(t *testing.T) { cfg := &Config{} if got := cfg.MaxBodyBytes(); got != DefaultMaxBodyBytes { t.Errorf("MaxBodyBytes default = %d, want %d", got, DefaultMaxBodyBytes) } cfg.Daemon.MaxBodyBytes = 100 if got := cfg.MaxBodyBytes(); got != 100 { t.Errorf("MaxBodyBytes = %d, want 100", got) } } func TestReceiveDirDefault(t *testing.T) { tmp := t.TempDir() t.Setenv("HOME", tmp) t.Setenv("USERPROFILE", tmp) cfg := &Config{} got := cfg.ReceiveDir() if got != filepath.Join(tmp, "Downloads", "clip-sync") { t.Errorf("ReceiveDir default = %q, want %q", got, filepath.Join(tmp, "Downloads", "clip-sync")) } cfg.Daemon.ReceiveDir = "/custom" if cfg.ReceiveDir() != "/custom" { t.Errorf("ReceiveDir override = %q, want \"/custom\"", cfg.ReceiveDir()) } }