From 47093b537c4aa1643e83f396f8f8d356a76f9be2 Mon Sep 17 00:00:00 2001 From: Rohit27 <137439791+GitNinja36@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:25:18 +0530 Subject: [PATCH] feat: Add comprehensive unit tests for UI functionality - Add unit tests for pkg/ui.go with 100% coverage - Add unit tests for pkg/internal/enterprise.go GetUIEndpoint with 94.4% coverage - Refactor code to use dependency injection for better testability - Test multiple scenarios like : NodePort, LoadBalancer, error handling - Made all tests self-contained and independent These changes boost test reliability and improve overall code quality. --- pkg/internal/enterprise.go | 7 +- pkg/internal/enterprise_test.go | 129 ++++++++++++++++++++++++++++++++ pkg/ui.go | 4 +- pkg/ui_test.go | 49 ++++++++++++ 4 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 pkg/internal/enterprise_test.go create mode 100644 pkg/ui_test.go diff --git a/pkg/internal/enterprise.go b/pkg/internal/enterprise.go index ac7ec15..692dfdc 100644 --- a/pkg/internal/enterprise.go +++ b/pkg/internal/enterprise.go @@ -24,6 +24,9 @@ kubeslice: type: %s ` +var runCommandCustomIO = util.RunCommandCustomIO +var getNodeIPFunc = getNodeIP + func InstallKubeSliceUI(ApplicationConfiguration *ConfigurationSpecs) { util.Printf("\nInstalling KubeSlice Manager...") if ApplicationConfiguration.Configuration.HelmChartConfiguration.UIChart.ChartName == "" { @@ -112,7 +115,7 @@ func GetUIEndpoint(cc *Cluster, profile string) string { ep := "" var outB, errB bytes.Buffer - err := util.RunCommandCustomIO("kubectl", &outB, &errB, true, "--context="+cc.ContextName, "--kubeconfig="+cc.KubeConfigPath, "get", "services", "kubeslice-ui-proxy", "-n", KUBESLICE_CONTROLLER_NAMESPACE, "-o", "jsonpath='{.spec}'") + err := runCommandCustomIO("kubectl", &outB, &errB, true, "--context="+cc.ContextName, "--kubeconfig="+cc.KubeConfigPath, "get", "services", "kubeslice-ui-proxy", "-n", KUBESLICE_CONTROLLER_NAMESPACE, "-o", "jsonpath='{.spec}'") if err == nil { jsonMap := make(map[string]interface{}) err = json.Unmarshal(outB.Bytes()[1:len(outB.Bytes())-1], &jsonMap) @@ -129,7 +132,7 @@ func GetUIEndpoint(cc *Cluster, profile string) string { portMap := port.(map[string]interface{}) if portMap["name"] == "http" { // Assuming that http is the name of the port that you want to use nodePort := int(portMap["nodePort"].(float64)) - nodeIP, err := getNodeIP(cc) + nodeIP, err := getNodeIPFunc(cc) if err == nil { ep = fmt.Sprintf("https://%s:%d", strings.Trim(nodeIP, "'"), nodePort) } else { diff --git a/pkg/internal/enterprise_test.go b/pkg/internal/enterprise_test.go new file mode 100644 index 0000000..18f2344 --- /dev/null +++ b/pkg/internal/enterprise_test.go @@ -0,0 +1,129 @@ +package internal + +import ( + "errors" + "io" + "testing" + + "github.com/kubeslice/kubeslice-cli/util" +) + +// Mock getNodeIP to return dummy IP +func mockGetNodeIP(_ *Cluster) (string, error) { + return "192.168.1.100", nil +} + +func TestGetUIEndpoint_NodePort(t *testing.T) { + runCalled := false + + // Mock kubectl output for NodePort + nodePortJSON := `{ + "type": "NodePort", + "ports": [{ + "name": "http", + "nodePort": 30080 + }] + }` + + runCommandCustomIO = func(name string, stdout, stderr io.Writer, _ bool, args ...string) error { + runCalled = true + stdout.Write([]byte("'" + nodePortJSON + "'")) // wrap in quotes to simulate jsonpath output + return nil + } + getNodeIPFunc = mockGetNodeIP + defer func() { + runCommandCustomIO = util.RunCommandCustomIO + getNodeIPFunc = getNodeIP + }() + + cluster := &Cluster{ + ContextName: "mock-context", + KubeConfigPath: "/fake/config", + } + endpoint := GetUIEndpoint(cluster, "some-profile") + + expected := "https://192.168.1.100:30080" + if endpoint != expected { + t.Errorf("Expected endpoint %q, got %q", expected, endpoint) + } + if !runCalled { + t.Error("Expected RunCommandCustomIO to be called") + } +} + +// Mocks a LoadBalancer service and checks the endpoint. +func TestGetUIEndpoint_LoadBalancer(t *testing.T) { + runCalled := false + + // Mock output for LoadBalancer + loadBalancerJSON := `{ + "type": "LoadBalancer", + "externalIPs": ["1.2.3.4"], + "ports": [{ + "name": "http", + "port": 443 + }] + }` + + runCommandCustomIO = func(name string, stdout, stderr io.Writer, _ bool, args ...string) error { + runCalled = true + stdout.Write([]byte("'" + loadBalancerJSON + "'")) + return nil + } + defer func() { + runCommandCustomIO = util.RunCommandCustomIO + }() + + cluster := &Cluster{ + ContextName: "mock-context", + KubeConfigPath: "/fake/config", + } + endpoint := GetUIEndpoint(cluster, "some-profile") + + expected := "https://1.2.3.4:443" + if endpoint != expected { + t.Errorf("Expected endpoint %q, got %q", expected, endpoint) + } + if !runCalled { + t.Error("Expected RunCommandCustomIO to be called") + } +} + +// Mocks invalid JSON output and checks that the function returns an empty string +func TestGetUIEndpoint_InvalidJSON(t *testing.T) { + runCommandCustomIO = func(name string, stdout, stderr io.Writer, _ bool, args ...string) error { + stdout.Write([]byte("'not-a-json'")) + return nil + } + defer func() { + runCommandCustomIO = util.RunCommandCustomIO + }() + + cluster := &Cluster{ + ContextName: "mock-context", + KubeConfigPath: "/fake/config", + } + endpoint := GetUIEndpoint(cluster, "profile") + if endpoint != "" { + t.Errorf("Expected empty endpoint on invalid JSON, got %q", endpoint) + } +} + +// Mocks a command failure and checks that the function returns an empty string +func TestGetUIEndpoint_CommandFailure(t *testing.T) { + runCommandCustomIO = func(name string, stdout, stderr io.Writer, _ bool, args ...string) error { + return errors.New("kubectl failed") + } + defer func() { + runCommandCustomIO = util.RunCommandCustomIO + }() + + cluster := &Cluster{ + ContextName: "mock-context", + KubeConfigPath: "/fake/config", + } + endpoint := GetUIEndpoint(cluster, "profile") + if endpoint != "" { + t.Errorf("Expected empty endpoint on command failure, got %q", endpoint) + } +} diff --git a/pkg/ui.go b/pkg/ui.go index 60858e3..b7f6934 100644 --- a/pkg/ui.go +++ b/pkg/ui.go @@ -2,6 +2,8 @@ package pkg import "github.com/kubeslice/kubeslice-cli/pkg/internal" +var getUIEndpointFunc = internal.GetUIEndpoint + func GetUIEndpoint() { - internal.GetUIEndpoint(CliOptions.Cluster, ApplicationConfiguration.Configuration.ClusterConfiguration.Profile) + getUIEndpointFunc(CliOptions.Cluster, ApplicationConfiguration.Configuration.ClusterConfiguration.Profile) } diff --git a/pkg/ui_test.go b/pkg/ui_test.go new file mode 100644 index 0000000..480bdaa --- /dev/null +++ b/pkg/ui_test.go @@ -0,0 +1,49 @@ +package pkg + +import ( + "testing" + + "github.com/kubeslice/kubeslice-cli/pkg/internal" +) + +func TestGetUIEndpoint_WithMock(t *testing.T) { + // Initialize CliOptions using the helper + cliParams := CliParams{ + ObjectType: "project", + ObjectName: "mock-cluster", + Namespace: "test-namespace", + FileName: "test-file.yaml", + Config: "", + OutputFormat: "json", + } + SetCliOptions(cliParams) + + // Initialize ApplicationConfiguration and nested fields + ApplicationConfiguration = &internal.ConfigurationSpecs{} + ApplicationConfiguration.Configuration = internal.Configuration{} + ApplicationConfiguration.Configuration.ClusterConfiguration = internal.ClusterConfiguration{} + ApplicationConfiguration.Configuration.ClusterConfiguration.Profile = "mock-profile" + + called := false + mockFunc := func(c *internal.Cluster, profile string) string { + called = true + if c.Name != "mock-cluster" || profile != "mock-profile" { + t.Errorf("Unexpected values: cluster=%v, profile=%v", c.Name, profile) + } + return "https://mock-endpoint" + } + + // Inject mock + getUIEndpointFunc = mockFunc + defer func() { getUIEndpointFunc = internal.GetUIEndpoint }() // Restore after test + + // Inject mock config + CliOptions.Cluster = &internal.Cluster{Name: "mock-cluster"} + ApplicationConfiguration.Configuration.ClusterConfiguration.Profile = "mock-profile" + + GetUIEndpoint() + + if !called { + t.Error("Expected mock GetUIEndpoint to be called") + } +}