package setup import ( "os" "path/filepath" "strings" "testing" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config" ) func TestSave_WritesFile(t *testing.T) { dir := t.TempDir() orig := os.Getenv("XDG_CONFIG_HOME") os.Setenv("XDG_CONFIG_HOME", dir) defer os.Setenv("XDG_CONFIG_HOME", orig) cfg := config.Config{ NetBox: config.NetBoxConfig{ URL: "https://netbox.example.com", Token: "nbt_abc123", TokenVersion: 2, }, SSH: config.SSHConfig{DefaultUser: "admin"}, Resolver: config.ResolverConfig{ Strategies: []string{"primary_ip", "management_subnet"}, ManagementSubnets: []string{"10.0.0.0/8"}, }, Cache: config.CacheConfig{TTL: 3600}, } if err := save(cfg); err != nil { t.Fatalf("save: %v", err) } data, err := os.ReadFile(filepath.Join(dir, "netssh.yaml")) if err != nil { t.Fatalf("reading saved file: %v", err) } content := string(data) for _, want := range []string{ `"https://netbox.example.com"`, `"nbt_abc123"`, `token_version: 2`, `- primary_ip`, `- management_subnet`, `- 10.0.0.0/8`, `ttl: 3600`, `"admin"`, } { if !strings.Contains(content, want) { t.Errorf("saved config missing %q\nfull content:\n%s", want, content) } } } func TestSave_FilePermissions(t *testing.T) { dir := t.TempDir() orig := os.Getenv("XDG_CONFIG_HOME") os.Setenv("XDG_CONFIG_HOME", dir) defer os.Setenv("XDG_CONFIG_HOME", orig) if err := save(config.Config{ NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1}, Cache: config.CacheConfig{TTL: 60}, }); err != nil { t.Fatalf("save: %v", err) } info, err := os.Stat(filepath.Join(dir, "netssh.yaml")) if err != nil { t.Fatalf("stat: %v", err) } if perm := info.Mode().Perm(); perm != 0o600 { t.Errorf("file permissions: got %o, want 600", perm) } } func TestSave_OmitsEmptyOptionalFields(t *testing.T) { dir := t.TempDir() orig := os.Getenv("XDG_CONFIG_HOME") os.Setenv("XDG_CONFIG_HOME", dir) defer os.Setenv("XDG_CONFIG_HOME", orig) cfg := config.Config{ NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1}, Cache: config.CacheConfig{TTL: 60}, // No DefaultUser, no ManagementSubnets, no InterfaceName } if err := save(cfg); err != nil { t.Fatalf("save: %v", err) } data, _ := os.ReadFile(filepath.Join(dir, "netssh.yaml")) content := string(data) for _, absent := range []string{"default_user", "management_subnets", "interface_name"} { if strings.Contains(content, absent) { t.Errorf("config should not contain %q when field is empty\nfull content:\n%s", absent, content) } } } func TestSave_CreatesConfigDir(t *testing.T) { dir := filepath.Join(t.TempDir(), "does", "not", "exist") orig := os.Getenv("XDG_CONFIG_HOME") os.Setenv("XDG_CONFIG_HOME", dir) defer os.Setenv("XDG_CONFIG_HOME", orig) if err := save(config.Config{ NetBox: config.NetBoxConfig{URL: "http://x", Token: "t", TokenVersion: 1}, Cache: config.CacheConfig{TTL: 60}, }); err != nil { t.Fatalf("save should create missing directories: %v", err) } } func TestParseStrategies(t *testing.T) { tests := []struct { in string want []string }{ {"primary_ip", []string{"primary_ip"}}, {"management_subnet, primary_ip", []string{"management_subnet", "primary_ip"}}, {"primary_ip,management_subnet,interface_name", []string{"primary_ip", "management_subnet", "interface_name"}}, {" primary_ip , management_subnet ", []string{"primary_ip", "management_subnet"}}, {"", nil}, {" , ", nil}, } for _, tt := range tests { got := parseStrategies(tt.in) if len(got) != len(tt.want) { t.Errorf("parseStrategies(%q): got %v, want %v", tt.in, got, tt.want) continue } for i := range got { if got[i] != tt.want[i] { t.Errorf("parseStrategies(%q)[%d]: got %q, want %q", tt.in, i, got[i], tt.want[i]) } } } } func TestParseStrategies_PreservesOrder(t *testing.T) { got := parseStrategies("interface_name, management_subnet, primary_ip") want := []string{"interface_name", "management_subnet", "primary_ip"} for i, s := range got { if s != want[i] { t.Errorf("order not preserved at [%d]: got %q, want %q", i, s, want[i]) } } } func TestValidateStrategies_Valid(t *testing.T) { cases := []string{ "primary_ip", "management_subnet, primary_ip", "interface_name, management_subnet, primary_ip", } for _, c := range cases { if err := validateStrategies(c); err != nil { t.Errorf("validateStrategies(%q) should be valid, got: %v", c, err) } } } func TestValidateStrategies_Invalid(t *testing.T) { cases := []string{ "", "unknown_strategy", "primary_ip, typo", } for _, c := range cases { if err := validateStrategies(c); err == nil { t.Errorf("validateStrategies(%q) should return an error", c) } } } func TestSave_RoundtripViaLoad(t *testing.T) { dir := t.TempDir() orig := os.Getenv("XDG_CONFIG_HOME") os.Setenv("XDG_CONFIG_HOME", dir) defer os.Setenv("XDG_CONFIG_HOME", orig) original := config.Config{ NetBox: config.NetBoxConfig{ URL: "https://netbox.zb-server.de", Token: "nbt_supersecret", TokenVersion: 2, }, SSH: config.SSHConfig{DefaultUser: "root"}, Resolver: config.ResolverConfig{ Strategies: []string{"primary_ip"}, ManagementSubnets: []string{"192.168.0.0/16"}, InterfaceName: "eth0", }, Cache: config.CacheConfig{TTL: 7200}, } if err := save(original); err != nil { t.Fatalf("save: %v", err) } loaded, err := config.Load() if err != nil { t.Fatalf("Load after save: %v", err) } if loaded.NetBox.URL != original.NetBox.URL { t.Errorf("URL: got %q, want %q", loaded.NetBox.URL, original.NetBox.URL) } if loaded.NetBox.Token != original.NetBox.Token { t.Errorf("Token: got %q, want %q", loaded.NetBox.Token, original.NetBox.Token) } if loaded.NetBox.TokenVersion != original.NetBox.TokenVersion { t.Errorf("TokenVersion: got %d, want %d", loaded.NetBox.TokenVersion, original.NetBox.TokenVersion) } if loaded.SSH.DefaultUser != original.SSH.DefaultUser { t.Errorf("DefaultUser: got %q, want %q", loaded.SSH.DefaultUser, original.SSH.DefaultUser) } if loaded.Cache.TTL != original.Cache.TTL { t.Errorf("TTL: got %d, want %d", loaded.Cache.TTL, original.Cache.TTL) } }