package haproxy import ( "strings" "testing" ) func TestNewManager_DefaultPath(t *testing.T) { m := NewManager("") if m.GetConfigPath() != "/etc/haproxy/haproxy.cfg" { t.Errorf("got %q, want default path", m.GetConfigPath()) } } func TestNewManager_CustomPath(t *testing.T) { m := NewManager("/tmp/haproxy.cfg") if m.GetConfigPath() != "/tmp/haproxy.cfg" { t.Errorf("got %q, want /tmp/haproxy.cfg", m.GetConfigPath()) } } func TestSanitizeName(t *testing.T) { cases := []struct { in string want string }{ {"payne-cloud", "payne_cloud"}, {"cloud.payne.io", "cloud_payne_io"}, {"civil_ssh", "civil_ssh"}, {"test cloud 1", "test_cloud_1"}, {"abc123", "abc123"}, } for _, c := range cases { if got := sanitizeName(c.in); got != c.want { t.Errorf("sanitizeName(%q) = %q, want %q", c.in, got, c.want) } } } func TestGetListenPorts_BasePorts(t *testing.T) { m := NewManager("") ports := m.GetListenPorts(nil, nil) want := map[int]bool{80: true, 443: true, 8404: true} for _, p := range ports { delete(want, p) } if len(want) > 0 { t.Errorf("missing base ports: %v", want) } } func TestGetListenPorts_IncludesCustom(t *testing.T) { m := NewManager("") custom := []CustomRoute{{Name: "ssh", Port: 2222, Backend: "192.168.1.10:22"}} ports := m.GetListenPorts(nil, custom) found := false for _, p := range ports { if p == 2222 { found = true } } if !found { t.Errorf("custom port 2222 not in listen ports: %v", ports) } } func TestGenerate_AlwaysIncludesBoilerplate(t *testing.T) { m := NewManager("") out := m.Generate(nil, nil) for _, want := range []string{ "global\n", "defaults\n", "frontend stats\n", "frontend http_in\n", "redirect scheme https code 301", "bind *:8404", } { if !strings.Contains(out, want) { t.Errorf("expected %q in output, got:\n%s", want, out) } } } func TestGenerate_NoInstances_NoHTTPSFrontend(t *testing.T) { m := NewManager("") out := m.Generate(nil, nil) if strings.Contains(out, "frontend https_in") { t.Errorf("https_in frontend should be absent when no instances configured") } } func TestGenerate_WithInstance_SNIACLs(t *testing.T) { m := NewManager("") instances := []InstanceRoute{ {Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20"}, } out := m.Generate(instances, nil) // Both ACL lines for OR-matching (subdomain and exact) if !strings.Contains(out, "req_ssl_sni -m end .cloud.payne.io") { t.Errorf("expected subdomain SNI ACL, got:\n%s", out) } if !strings.Contains(out, "req_ssl_sni -m str cloud.payne.io") { t.Errorf("expected exact SNI ACL, got:\n%s", out) } if !strings.Contains(out, "use_backend be_https_payne_cloud if is_payne_cloud") { t.Errorf("expected use_backend directive, got:\n%s", out) } } func TestGenerate_WithInstance_Backend(t *testing.T) { m := NewManager("") instances := []InstanceRoute{ {Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20"}, } out := m.Generate(instances, nil) if !strings.Contains(out, "backend be_https_payne_cloud") { t.Errorf("expected backend block, got:\n%s", out) } if !strings.Contains(out, "server s0 192.168.8.20:443 check") { t.Errorf("expected server line with LB IP, got:\n%s", out) } } func TestGenerate_MultipleInstances(t *testing.T) { m := NewManager("") instances := []InstanceRoute{ {Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20"}, {Name: "test-cloud", Domain: "cloud2.payne.io", BackendIP: "192.168.8.30"}, } out := m.Generate(instances, nil) if !strings.Contains(out, "be_https_payne_cloud") { t.Errorf("expected payne-cloud backend, got:\n%s", out) } if !strings.Contains(out, "be_https_test_cloud") { t.Errorf("expected test-cloud backend, got:\n%s", out) } if !strings.Contains(out, "192.168.8.20:443") { t.Errorf("expected payne-cloud IP, got:\n%s", out) } if !strings.Contains(out, "192.168.8.30:443") { t.Errorf("expected test-cloud IP, got:\n%s", out) } } func TestGenerate_WithExtraDomains(t *testing.T) { m := NewManager("") instances := []InstanceRoute{ { Name: "payne-cloud", Domain: "cloud.payne.io", BackendIP: "192.168.8.20", ExtraDomains: []string{"payne.io", "mywildcloud.org"}, }, {Name: "test-cloud", Domain: "cloud2.payne.io", BackendIP: "192.168.8.30"}, } out := m.Generate(instances, nil) // Extra domains should add ACL entries under the same payne-cloud ACL name for _, domain := range []string{"payne.io", "mywildcloud.org"} { if !strings.Contains(out, "req_ssl_sni -m end ."+domain) { t.Errorf("expected subdomain ACL for %s, got:\n%s", domain, out) } if !strings.Contains(out, "req_ssl_sni -m str "+domain) { t.Errorf("expected exact ACL for %s, got:\n%s", domain, out) } } // Extra domains should route to payne-cloud, not test-cloud if !strings.Contains(out, "use_backend be_https_payne_cloud if is_payne_cloud") { t.Errorf("expected payne-cloud use_backend, got:\n%s", out) } } func TestGenerate_CustomRoute(t *testing.T) { m := NewManager("") custom := []CustomRoute{ {Name: "civil_ssh", Port: 2222, Backend: "192.168.8.10:22"}, } out := m.Generate(nil, custom) if !strings.Contains(out, "frontend fe_civil_ssh") { t.Errorf("expected custom frontend, got:\n%s", out) } if !strings.Contains(out, "bind *:2222") { t.Errorf("expected bind on port 2222, got:\n%s", out) } if !strings.Contains(out, "backend be_civil_ssh") { t.Errorf("expected custom backend, got:\n%s", out) } if !strings.Contains(out, "server s0 192.168.8.10:22") { t.Errorf("expected server line with backend address, got:\n%s", out) } }