package resolver import ( "context" "errors" "testing" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/config" "git.zb-server.de/Sebi/ssh-netbox-wrapper/internal/netbox" ) // stubStrategy is a test double for Strategy. type stubStrategy struct { name string ip string err error } func (s *stubStrategy) Name() string { return s.name } func (s *stubStrategy) Resolve(_ context.Context, _ *netbox.HostEntry, _ *netbox.Client) (string, error) { return s.ip, s.err } func TestChain_FirstStrategySucceeds(t *testing.T) { c := &Chain{strategies: []Strategy{ &stubStrategy{name: "first", ip: "10.0.0.1"}, &stubStrategy{name: "second", ip: "10.0.0.2"}, }} ip, err := c.Resolve(context.Background(), &netbox.HostEntry{Name: "host"}, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } if ip != "10.0.0.1" { t.Errorf("got %q, want first strategy's IP %q", ip, "10.0.0.1") } } func TestChain_FallsBackToNextStrategy(t *testing.T) { c := &Chain{strategies: []Strategy{ &stubStrategy{name: "first", err: ErrNoIP}, &stubStrategy{name: "second", ip: "10.0.0.2"}, }} ip, err := c.Resolve(context.Background(), &netbox.HostEntry{Name: "host"}, nil) if err != nil { t.Fatalf("unexpected error: %v", err) } if ip != "10.0.0.2" { t.Errorf("got %q, want second strategy's IP %q", ip, "10.0.0.2") } } func TestChain_AllStrategiesFail(t *testing.T) { c := &Chain{strategies: []Strategy{ &stubStrategy{name: "a", err: ErrNoIP}, &stubStrategy{name: "b", err: errors.New("api error")}, }} _, err := c.Resolve(context.Background(), &netbox.HostEntry{Name: "host"}, nil) if err == nil { t.Error("expected error when all strategies fail") } } func TestChain_EmptyStrategies(t *testing.T) { c := &Chain{} _, err := c.Resolve(context.Background(), &netbox.HostEntry{Name: "host"}, nil) if err == nil { t.Error("empty chain should return an error") } } func TestNew_PrimaryIP(t *testing.T) { cfg := config.ResolverConfig{Strategies: []string{"primary_ip"}} c, err := New(cfg) if err != nil { t.Fatalf("New: %v", err) } if len(c.strategies) != 1 { t.Errorf("got %d strategies, want 1", len(c.strategies)) } if c.strategies[0].Name() != "primary_ip" { t.Errorf("strategy name: got %q, want %q", c.strategies[0].Name(), "primary_ip") } } func TestNew_ManagementSubnet(t *testing.T) { cfg := config.ResolverConfig{ Strategies: []string{"management_subnet"}, ManagementSubnets: []string{"10.0.0.0/8"}, } c, err := New(cfg) if err != nil { t.Fatalf("New: %v", err) } if c.strategies[0].Name() != "management_subnet" { t.Errorf("strategy name: got %q, want %q", c.strategies[0].Name(), "management_subnet") } } func TestNew_ManagementSubnet_InvalidCIDR(t *testing.T) { cfg := config.ResolverConfig{ Strategies: []string{"management_subnet"}, ManagementSubnets: []string{"not-a-cidr"}, } _, err := New(cfg) if err == nil { t.Error("invalid CIDR should return an error") } } func TestNew_InterfaceName(t *testing.T) { cfg := config.ResolverConfig{ Strategies: []string{"interface_name"}, InterfaceName: "mgmt0", } c, err := New(cfg) if err != nil { t.Fatalf("New: %v", err) } if c.strategies[0].Name() != "interface_name" { t.Errorf("strategy name: got %q", c.strategies[0].Name()) } } func TestNew_InterfaceName_MissingConfig(t *testing.T) { cfg := config.ResolverConfig{ Strategies: []string{"interface_name"}, InterfaceName: "", // not set } _, err := New(cfg) if err == nil { t.Error("interface_name without config should return an error") } } func TestNew_UnknownStrategy(t *testing.T) { cfg := config.ResolverConfig{Strategies: []string{"nonexistent"}} _, err := New(cfg) if err == nil { t.Error("unknown strategy should return an error") } } func TestNew_MultipleStrategies(t *testing.T) { cfg := config.ResolverConfig{ Strategies: []string{"management_subnet", "primary_ip"}, ManagementSubnets: []string{"10.0.0.0/8"}, } c, err := New(cfg) if err != nil { t.Fatalf("New: %v", err) } if len(c.strategies) != 2 { t.Errorf("got %d strategies, want 2", len(c.strategies)) } }