package config import ( "errors" "os" "path/filepath" "testing" "time" ) func intPtr(value int) *int { return &value } func TestLoadConfig(t *testing.T) { path := filepath.Join(t.TempDir(), "tunnels.toml") contents := `sshConfig = "/etc/ssh/est.conf" [retry] count = 3 delay = "2s" [[entry]] direction = "ltr" host = "vps" localPort = 22 remotePort = 2222 identityFile = "/keys/vps" [entry.retry] count = -1 delay = "100ms" ` if err := os.WriteFile(path, []byte(contents), 0o600); err != nil { t.Fatalf("write config: %v", err) } cfg, err := LoadConfig(path) if err != nil { t.Fatalf("LoadConfig() error = %v", err) } if cfg.SSHConfig != "/etc/ssh/est.conf" { t.Errorf("SSHConfig = %q, want %q", cfg.SSHConfig, "/etc/ssh/est.conf") } if len(cfg.Entries) != 1 { t.Fatalf("entries = %d, want 1", len(cfg.Entries)) } entry := cfg.Entries[0] if entry.Direction != LocalToRemote || entry.Host != "vps" || entry.LocalPort != 22 || entry.RemotePort != 2222 || entry.IdentityFile != "/keys/vps" { t.Errorf("entry = %#v, want decoded tunnel values", entry) } retry, err := cfg.RetrySettings(entry) if err != nil { t.Fatalf("RetrySettings() error = %v", err) } if retry.Count != -1 || retry.Delay != 100*time.Millisecond { t.Errorf("entry retry = %#v, want count=-1 delay=100ms", retry) } } func TestConfigRetrySettings(t *testing.T) { tests := []struct { name string cfg Config entry ProxyEntry want RetrySettings }{ {name: "defaults", want: RetrySettings{Count: DefaultRetryCount, Delay: DefaultRetryDelay}}, {name: "top level", cfg: Config{Retry: &RetryConfig{Count: intPtr(2), Delay: "1s"}}, want: RetrySettings{Count: 2, Delay: time.Second}}, {name: "entry override", cfg: Config{Retry: &RetryConfig{Count: intPtr(2), Delay: "1s"}}, entry: ProxyEntry{Retry: &RetryConfig{Count: intPtr(0), Delay: "50ms"}}, want: RetrySettings{Count: 0, Delay: 50 * time.Millisecond}}, {name: "entry override uses defaults", cfg: Config{Retry: &RetryConfig{Count: intPtr(-1), Delay: "1s"}}, entry: ProxyEntry{Retry: &RetryConfig{}}, want: RetrySettings{Count: DefaultRetryCount, Delay: DefaultRetryDelay}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := tt.cfg.RetrySettings(tt.entry) if err != nil { t.Fatalf("RetrySettings() error = %v", err) } if got != tt.want { t.Errorf("RetrySettings() = %#v, want %#v", got, tt.want) } }) } } func TestRetryConfigSettingsRejectsInvalidValues(t *testing.T) { for _, retry := range []RetryConfig{ {Count: intPtr(-2)}, {Delay: "not-a-duration"}, {Delay: "0s"}, } { if _, err := retry.Settings(); err == nil { t.Errorf("Settings() error = nil for %#v", retry) } } } func TestLoadConfigMissingFile(t *testing.T) { _, err := LoadConfig(filepath.Join(t.TempDir(), "missing.toml")) if !errors.Is(err, os.ErrNotExist) { t.Fatalf("LoadConfig() error = %v, want not-exist error", err) } }