diff --git a/cmd/common_test.go b/cmd/common_test.go new file mode 100644 index 0000000..f16eda5 --- /dev/null +++ b/cmd/common_test.go @@ -0,0 +1,87 @@ +package cmd + +import ( + "reflect" + "testing" +) + +func TestMapFromSlice(t *testing.T) { + tests := []struct { + name string + input []string + expected map[string]string + }{ + { + name: "empty slice", + input: []string{}, + expected: map[string]string{}, + }, + { + name: "single element", + input: []string{"step1"}, + expected: map[string]string{"step1": ""}, + }, + { + name: "multiple elements", + input: []string{"step1", "step2", "step3"}, + expected: map[string]string{"step1": "", "step2": "", "step3": ""}, + }, + { + name: "duplicate elements", + input: []string{"step1", "step1", "step2"}, + expected: map[string]string{"step1": "", "step2": ""}, + }, + { + name: "case sensitivity", + input: []string{"Step1", "step1", "STEP1"}, + expected: map[string]string{"Step1": "", "step1": "", "STEP1": ""}, + }, + { + name: "special characters", + input: []string{"step-1", "step_2", "step@3"}, + expected: map[string]string{"step-1": "", "step_2": "", "step@3": ""}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := mapFromSlice(tt.input) + if !reflect.DeepEqual(result, tt.expected) { + t.Errorf("mapFromSlice(%v) = %v, want %v", tt.input, result, tt.expected) + } + }) + } +} + +func TestGlobalVariables(t *testing.T) { + // Test global variables are accessible and modifiable + profile = "test-profile" + if profile != "test-profile" { + t.Errorf("profile = %s, want test-profile", profile) + } + + skipSteps = []string{"step1", "step2"} + if len(skipSteps) != 2 || skipSteps[0] != "step1" || skipSteps[1] != "step2" { + t.Errorf("skipSteps = %v, want [step1 step2]", skipSteps) + } + + outputFormat = "json" + if outputFormat != "json" { + t.Errorf("outputFormat = %s, want json", outputFormat) + } + + Config = "config.yaml" + if Config != "config.yaml" { + t.Errorf("Config = %s, want config.yaml", Config) + } +} + +func TestMapFromSlice_NilInput(t *testing.T) { + result := mapFromSlice(nil) + if result == nil { + t.Error("mapFromSlice(nil) should not return nil map") + } + if len(result) != 0 { + t.Errorf("mapFromSlice(nil) = %v, want empty map", result) + } +} diff --git a/pkg/cmd-util_test.go b/pkg/cmd-util_test.go new file mode 100644 index 0000000..d8492e9 --- /dev/null +++ b/pkg/cmd-util_test.go @@ -0,0 +1,143 @@ +package pkg + +import ( + "testing" + + "github.com/kubeslice/kubeslice-cli/pkg/internal" +) + +func TestSetCliOptions(t *testing.T) { + cliParams := CliParams{ + ObjectType: "project", + ObjectName: "test-project", + Namespace: "test-namespace", + FileName: "test-file.yaml", + Config: "", + OutputFormat: "json", + } + //set + SetCliOptions(cliParams) + + if CliOptions.ObjectType != "project" { + t.Errorf("Expected ObjectType to be 'project', got %s", CliOptions.ObjectType) + } + if CliOptions.ObjectName != "test-project" { + t.Errorf("Expected ObjectName to be 'test-project', got %s", CliOptions.ObjectName) + } + if CliOptions.Namespace != "test-namespace" { + t.Errorf("Expected Namespace to be 'test-namespace', got %s", CliOptions.Namespace) + } +} + +func TestReadAndValidateConfiguration_WithDefaults(t *testing.T) { + if defaultConfiguration == nil { + t.Fatal("Expected defaultConfiguration to not be nil") + } + if defaultConfiguration.Configuration.ClusterConfiguration.Profile != "full-demo" { + t.Errorf("Expected profile to be 'full-demo', got %s", defaultConfiguration.Configuration.ClusterConfiguration.Profile) + } + if defaultConfiguration.Configuration.KubeSliceConfiguration.ProjectName != "demo" { + t.Errorf("Expected project name to be 'demo', got %s", defaultConfiguration.Configuration.KubeSliceConfiguration.ProjectName) + } +} + +func TestReadAndValidateConfiguration_WithEntProfile(t *testing.T) { + if defaultEntConfiguration == nil { + t.Fatal("Expected defaultEntConfiguration to not be nil") + } + if defaultEntConfiguration.RepoAlias != "kubeslice-ent-demo" { + t.Errorf("Expected repo alias to be 'kubeslice-ent-demo', got %s", defaultEntConfiguration.RepoAlias) + } + if defaultEntConfiguration.UIChart.ChartName != "kubeslice-ui" { + t.Errorf("Expected UI chart name to be 'kubeslice-ui', got %s", defaultEntConfiguration.UIChart.ChartName) + } +} + +func TestValidateConfiguration_ValidConfig(t *testing.T) { + specs := &internal.ConfigurationSpecs{ + Configuration: internal.Configuration{ + ClusterConfiguration: internal.ClusterConfiguration{ + ControllerCluster: internal.Cluster{ + Name: "controller", + KubeConfigPath: "/path/to/kubeconfig", + ContextName: "controller-context", + }, + WorkerClusters: []internal.Cluster{ + { + Name: "worker1", + KubeConfigPath: "/path/to/kubeconfig", + ContextName: "worker1-context", + }, + { + Name: "worker2", + KubeConfigPath: "/path/to/kubeconfig", + ContextName: "worker2-context", + }, + }, + }, + KubeSliceConfiguration: internal.KubeSliceConfiguration{ + ProjectName: "test-project", + }, + HelmChartConfiguration: internal.HelmChartConfiguration{ + RepoAlias: "test-repo", + RepoUrl: "https://test.com", + CertManagerChart: internal.HelmChart{ + ChartName: "cert-manager", + }, + ControllerChart: internal.HelmChart{ + ChartName: "controller", + }, + WorkerChart: internal.HelmChart{ + ChartName: "worker", + }, + }, + }, + } + + errors := validateConfiguration(specs) + if len(errors) > 0 { + t.Errorf("Expected no validation errors, got %d errors: %v", len(errors), errors) + } +} + +func TestValidateConfiguration_InvalidProfile(t *testing.T) { + specs := &internal.ConfigurationSpecs{ + Configuration: internal.Configuration{ + ClusterConfiguration: internal.ClusterConfiguration{ + Profile: "invalid-profile", + }, + }, + } + + errors := validateConfiguration(specs) + if len(errors) == 0 { + t.Error("Expected validation errors for invalid profile, got none") + } +} + +func TestValidateConfiguration_MissingControllerName(t *testing.T) { + specs := &internal.ConfigurationSpecs{ + Configuration: internal.Configuration{ + ClusterConfiguration: internal.ClusterConfiguration{ + ControllerCluster: internal.Cluster{ + Name: "", // Missing name + }, + }, + KubeSliceConfiguration: internal.KubeSliceConfiguration{ + ProjectName: "test", + }, + HelmChartConfiguration: internal.HelmChartConfiguration{ + RepoAlias: "test", + RepoUrl: "https://test.com", + CertManagerChart: internal.HelmChart{ChartName: "cert"}, + ControllerChart: internal.HelmChart{ChartName: "ctrl"}, + WorkerChart: internal.HelmChart{ChartName: "worker"}, + }, + }, + } + + errors := validateConfiguration(specs) + if len(errors) == 0 { + t.Error("Expected validation error for missing controller name") + } +} diff --git a/util/executables_test.go b/util/executables_test.go new file mode 100644 index 0000000..9143c44 --- /dev/null +++ b/util/executables_test.go @@ -0,0 +1,271 @@ +package util + +import ( + "bytes" + "io" + "os" + "strings" + "testing" +) + +func TestRunCommand(t *testing.T) { + originalPaths := ExecutablePaths + ExecutablePaths = map[string]string{ + "go": "go", + "fake": "/nonexistent/path/fake", + } + defer func() { ExecutablePaths = originalPaths }() + + tests := []struct { + name string + cli string + args []string + expectError bool + }{ + { + name: "successful command", + cli: "go", + args: []string{"version"}, + expectError: false, + }, + { + name: "failing command", + cli: "fake", + args: []string{}, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := RunCommand(tt.cli, tt.args...) + if tt.expectError && err == nil { + t.Errorf("expected error but got none") + } + if !tt.expectError && err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +} + +func TestRunCommandWithoutPrint(t *testing.T) { + originalPaths := ExecutablePaths + ExecutablePaths = map[string]string{ + "go": "go", + "fake": "/nonexistent/path/fake", + } + defer func() { ExecutablePaths = originalPaths }() + + tests := []struct { + name string + cli string + args []string + expectError bool + }{ + { + name: "successful command without print", + cli: "go", + args: []string{"version"}, + expectError: false, + }, + { + name: "failing command without print", + cli: "fake", + args: []string{}, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := RunCommandWithoutPrint(tt.cli, tt.args...) + if tt.expectError && err == nil { + t.Errorf("expected error but got none") + } + if !tt.expectError && err != nil { + t.Errorf("unexpected error: %v", err) + } + }) + } +} + +func TestRunCommandOnStdIO(t *testing.T) { + originalPaths := ExecutablePaths + ExecutablePaths = map[string]string{ + "go": "go", + } + defer func() { ExecutablePaths = originalPaths }() + + originalStdout := os.Stdout + originalStderr := os.Stderr + defer func() { + os.Stdout = originalStdout + os.Stderr = originalStderr + }() + + r, w, _ := os.Pipe() + os.Stdout = w + os.Stderr = w + + err := RunCommandOnStdIO("go", "version") + w.Close() + + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + var buf bytes.Buffer + io.Copy(&buf, r) + r.Close() + + output := buf.String() + if !strings.Contains(output, "go version") { + t.Errorf("expected output to contain 'go version', got: %s", output) + } +} + +func TestRunCommandCustomIO(t *testing.T) { + originalPaths := ExecutablePaths + ExecutablePaths = map[string]string{ + "go": "go", + } + defer func() { ExecutablePaths = originalPaths }() + + tests := []struct { + name string + cli string + args []string + suppressPrint bool + expectError bool + expectOutput string + }{ + { + name: "go version command with print", + cli: "go", + args: []string{"version"}, + suppressPrint: false, + expectError: false, + expectOutput: "go version", + }, + { + name: "go version command suppress print", + cli: "go", + args: []string{"version"}, + suppressPrint: true, + expectError: false, + expectOutput: "go version", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var stdout, stderr bytes.Buffer + + err := RunCommandCustomIO(tt.cli, &stdout, &stderr, tt.suppressPrint, tt.args...) + + if tt.expectError && err == nil { + t.Errorf("expected error but got none") + } + if !tt.expectError && err != nil { + t.Errorf("unexpected error: %v", err) + } + + if tt.expectOutput != "" && !strings.Contains(stdout.String(), tt.expectOutput) { + t.Errorf("expected stdout to contain '%s', got: %s", tt.expectOutput, stdout.String()) + } + }) + } +} + +func TestExecutableVerifyCommands(t *testing.T) { + expectedCommands := map[string][]string{ + "kind": {"version"}, + "kubectl": {"version", "--client=true"}, + "docker": {"ps", "-a"}, + "helm": {"version"}, + } + + for tool, expectedArgs := range expectedCommands { + actualArgs, exists := ExecutableVerifyCommands[tool] + if !exists { + t.Errorf("ExecutableVerifyCommands missing entry for %s", tool) + continue + } + + if len(actualArgs) != len(expectedArgs) { + t.Errorf("ExecutableVerifyCommands[%s] has %d args, expected %d", tool, len(actualArgs), len(expectedArgs)) + continue + } + + for i, expectedArg := range expectedArgs { + if actualArgs[i] != expectedArg { + t.Errorf("ExecutableVerifyCommands[%s][%d] = %s, expected %s", tool, i, actualArgs[i], expectedArg) + } + } + } +} + +func TestExecutablePathsInitialization(t *testing.T) { + originalPaths := ExecutablePaths + defer func() { ExecutablePaths = originalPaths }() + + testPaths := map[string]string{ + "test-tool": "/usr/bin/test-tool", + "another": "/bin/another", + } + + ExecutablePaths = testPaths + + for tool, expectedPath := range testPaths { + actualPath, exists := ExecutablePaths[tool] + if !exists { + t.Errorf("ExecutablePaths missing entry for %s", tool) + continue + } + if actualPath != expectedPath { + t.Errorf("ExecutablePaths[%s] = %s, expected %s", tool, actualPath, expectedPath) + } + } +} + +func TestRunCommandCustomIOWithNilWriters(t *testing.T) { + originalPaths := ExecutablePaths + ExecutablePaths = map[string]string{ + "go": "go", + } + defer func() { ExecutablePaths = originalPaths }() + + err := RunCommandCustomIO("go", nil, nil, true, "version") + if err != nil { + t.Errorf("unexpected error with nil writers: %v", err) + } +} + +func TestRunCommandWithNonExistentExecutable(t *testing.T) { + originalPaths := ExecutablePaths + ExecutablePaths = map[string]string{ + "nonexistent": "/path/to/nonexistent/binary", + } + defer func() { ExecutablePaths = originalPaths }() + + err := RunCommand("nonexistent", "arg1") + if err == nil { + t.Errorf("expected error for nonexistent executable, but got none") + } +} + +func TestRunCommandCustomIOErrorHandling(t *testing.T) { + originalPaths := ExecutablePaths + ExecutablePaths = map[string]string{ + "go": "go", + } + defer func() { ExecutablePaths = originalPaths }() + + var stdout, stderr bytes.Buffer + err := RunCommandCustomIO("go", &stdout, &stderr, true, "invalid-command-that-should-fail") + + if err == nil { + t.Errorf("expected error from invalid go command, but got none") + } +}