package cluster import ( "errors" "testing" ) func TestIsLeadEligible(t *testing.T) { cases := []struct { name string kind NodeKind want bool }{ {"linux", NodeKindLinux, true}, {"proxmox", NodeKindProxmox, false}, {"empty", "", false}, {"unknown", NodeKind("foo"), false}, {"localhost", NodeKind("localhost"), false}, } for _, tc := range cases { if got := IsLeadEligible(tc.kind); got != tc.want { t.Errorf("IsLeadEligible(%q) = %v, want %v", tc.kind, got, tc.want) } } } func TestValidateLeadRotation_LinuxOK(t *testing.T) { nodes := []NodeInfo{ {Hostname: "n1", Kind: NodeKindLinux}, {Hostname: "n2", Kind: NodeKindLinux}, {Hostname: "pve1", Kind: NodeKindProxmox}, } if err := ValidateLeadRotation("n2", nodes); err != nil { t.Errorf("ValidateLeadRotation(n2): err = %v, want nil", err) } if err := ValidateLeadRotation("n1", nodes); err != nil { t.Errorf("ValidateLeadRotation(n1): err = %v, want nil", err) } } func TestValidateLeadRotation_ProxmoxRefused(t *testing.T) { nodes := []NodeInfo{ {Hostname: "n1", Kind: NodeKindLinux}, {Hostname: "pve1", Kind: NodeKindProxmox}, } err := ValidateLeadRotation("pve1", nodes) if err == nil { t.Fatal("ValidateLeadRotation(pve1): expected error, got nil") } if !errors.Is(err, ErrProxmoxNotLead) { t.Errorf("err = %v, want ErrProxmoxNotLead", err) } if got := err.Error(); got != "Proxmox nodes cannot hold the cluster lead role (R-003)" { t.Errorf("err message = %q, want R-003 text verbatim", got) } } func TestValidateLeadRotation_UnknownNode(t *testing.T) { nodes := []NodeInfo{ {Hostname: "n1", Kind: NodeKindLinux}, } err := ValidateLeadRotation("ghost", nodes) if err == nil { t.Fatal("ValidateLeadRotation(ghost): expected error, got nil") } if !errors.Is(err, ErrNodeNotRegistered) { t.Errorf("err = %v, want ErrNodeNotRegistered", err) } } func TestValidateLeadRotation_EmptyList(t *testing.T) { err := ValidateLeadRotation("anyone", nil) if err == nil { t.Fatal("ValidateLeadRotation on empty list: expected error, got nil") } if !errors.Is(err, ErrNodeNotRegistered) { t.Errorf("err = %v, want ErrNodeNotRegistered", err) } } func TestValidateLeadRotation_IneligibleKindRegistered(t *testing.T) { // A node registered with a kind that is neither linux nor // proxmox (e.g. the auto-registered "localhost" kind) is // rejected as ineligible, not as unregistered. nodes := []NodeInfo{ {Hostname: "self", Kind: NodeKind("localhost")}, } err := ValidateLeadRotation("self", nodes) if err == nil { t.Fatal("expected error for localhost kind, got nil") } if !errors.Is(err, ErrProxmoxNotLead) { t.Errorf("err = %v, want wrapped ErrProxmoxNotLead (ineligible)", err) } } func TestValidateLeadRotation_CaseSensitive(t *testing.T) { // Hostnames are case-normalized at the store layer; the rule // matches exactly. "N1" is NOT the same as "n1". nodes := []NodeInfo{ {Hostname: "n1", Kind: NodeKindLinux}, } if err := ValidateLeadRotation("N1", nodes); !errors.Is(err, ErrNodeNotRegistered) { t.Errorf("N1 (case mismatch): err = %v, want ErrNodeNotRegistered", err) } } func TestNodeKindString(t *testing.T) { if got := NodeKindLinux.String(); got != "linux" { t.Errorf("Linux.String() = %q", got) } if got := NodeKindProxmox.String(); got != "proxmox" { t.Errorf("Proxmox.String() = %q", got) } // Uppercase constructor should lower-case. if got := NodeKind("PROXMOX").String(); got != "proxmox" { t.Errorf("PROXMOX.String() = %q, want proxmox", got) } }