package tools import ( "testing" "time" ) func TestNewKubectl(t *testing.T) { tests := []struct { name string kubeconfigPath string }{ { name: "creates Kubectl with kubeconfig path", kubeconfigPath: "/path/to/kubeconfig", }, { name: "creates Kubectl with empty path", kubeconfigPath: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { k := NewKubectl(tt.kubeconfigPath) if k == nil { t.Fatal("NewKubectl() returned nil") } if k.kubeconfigPath != tt.kubeconfigPath { t.Errorf("kubeconfigPath = %q, want %q", k.kubeconfigPath, tt.kubeconfigPath) } }) } } func TestKubectlDeploymentExists(t *testing.T) { tests := []struct { name string depName string namespace string skipTest bool }{ { name: "check deployment exists", depName: "test-deployment", namespace: "default", skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") exists := k.DeploymentExists(tt.depName, tt.namespace) _ = exists // Result depends on actual cluster state }) } } func TestKubectlGetPods(t *testing.T) { tests := []struct { name string namespace string detailed bool skipTest bool }{ { name: "get pods basic", namespace: "default", detailed: false, skipTest: true, }, { name: "get pods detailed", namespace: "kube-system", detailed: true, skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") pods, err := k.GetPods(tt.namespace, tt.detailed) if err == nil { if pods == nil { t.Error("GetPods() returned nil slice without error") } // Verify pod structure for i, pod := range pods { if pod.Name == "" { t.Errorf("pod[%d].Name is empty", i) } if pod.Status == "" { t.Errorf("pod[%d].Status is empty", i) } if pod.Ready == "" { t.Errorf("pod[%d].Ready is empty", i) } if pod.Age == "" { t.Errorf("pod[%d].Age is empty", i) } if tt.detailed && pod.Containers == nil { t.Errorf("pod[%d].Containers is nil in detailed mode", i) } } } }) } } func TestKubectlGetFirstPodName(t *testing.T) { tests := []struct { name string namespace string skipTest bool }{ { name: "get first pod name", namespace: "kube-system", skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") podName, err := k.GetFirstPodName(tt.namespace) if err == nil { if podName == "" { t.Error("GetFirstPodName() returned empty string without error") } } }) } } func TestKubectlGetPodContainers(t *testing.T) { tests := []struct { name string namespace string podName string skipTest bool }{ { name: "get pod containers", namespace: "kube-system", podName: "coredns-123", skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") containers, err := k.GetPodContainers(tt.namespace, tt.podName) if err == nil { if containers == nil { t.Error("GetPodContainers() returned nil slice without error") } } }) } } func TestKubectlGetDeployment(t *testing.T) { tests := []struct { name string depName string namespace string skipTest bool }{ { name: "get deployment info", depName: "test-deployment", namespace: "default", skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") depInfo, err := k.GetDeployment(tt.depName, tt.namespace) if err == nil { if depInfo == nil { t.Error("GetDeployment() returned nil without error") } // Desired should be non-negative if depInfo.Desired < 0 { t.Errorf("Desired = %d, should be non-negative", depInfo.Desired) } } }) } } func TestKubectlGetReplicas(t *testing.T) { tests := []struct { name string namespace string skipTest bool }{ { name: "get replicas for namespace", namespace: "default", skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") replicaInfo, err := k.GetReplicas(tt.namespace) if err == nil { if replicaInfo == nil { t.Error("GetReplicas() returned nil without error") } // All values should be non-negative if replicaInfo.Desired < 0 { t.Error("Desired < 0") } if replicaInfo.Current < 0 { t.Error("Current < 0") } if replicaInfo.Ready < 0 { t.Error("Ready < 0") } if replicaInfo.Available < 0 { t.Error("Available < 0") } } }) } } func TestKubectlGetResources(t *testing.T) { tests := []struct { name string namespace string skipTest bool }{ { name: "get resources for namespace", namespace: "default", skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") usage, err := k.GetResources(tt.namespace) if err == nil { if usage == nil { t.Error("GetResources() returned nil without error") } } }) } } func TestKubectlGetRecentEvents(t *testing.T) { tests := []struct { name string namespace string limit int skipTest bool }{ { name: "get recent events", namespace: "default", limit: 10, skipTest: true, }, { name: "get all events with zero limit", namespace: "default", limit: 0, skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") events, err := k.GetRecentEvents(tt.namespace, tt.limit) if err == nil { if events == nil { t.Error("GetRecentEvents() returned nil slice without error") } if tt.limit > 0 && len(events) > tt.limit { t.Errorf("len(events) = %d, want <= %d", len(events), tt.limit) } } }) } } func TestKubectlGetLogs(t *testing.T) { tests := []struct { name string namespace string podName string opts LogOptions skipTest bool }{ { name: "get logs with tail", namespace: "kube-system", podName: "coredns-123", opts: LogOptions{Tail: 100}, skipTest: true, }, { name: "get logs with container", namespace: "kube-system", podName: "coredns-123", opts: LogOptions{Container: "coredns", Tail: 50}, skipTest: true, }, { name: "get previous logs", namespace: "default", podName: "test-pod", opts: LogOptions{Previous: true, Tail: 100}, skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") logs, err := k.GetLogs(tt.namespace, tt.podName, tt.opts) if err == nil { if logs == nil { t.Error("GetLogs() returned nil slice without error") } } }) } } func TestKubectlStreamLogs(t *testing.T) { tests := []struct { name string namespace string podName string opts LogOptions skipTest bool }{ { name: "stream logs", namespace: "default", podName: "test-pod", opts: LogOptions{Tail: 10}, skipTest: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipTest { t.Skip("Skipping test that requires kubectl and running cluster") } k := NewKubectl("") cmd, err := k.StreamLogs(tt.namespace, tt.podName, tt.opts) if err == nil { if cmd == nil { t.Error("StreamLogs() returned nil command without error") } } }) } } func TestFormatAge(t *testing.T) { tests := []struct { name string duration time.Duration want string }{ { name: "seconds", duration: 45 * time.Second, want: "45s", }, { name: "minutes", duration: 5 * time.Minute, want: "5m", }, { name: "hours", duration: 3 * time.Hour, want: "3h", }, { name: "days", duration: 48 * time.Hour, want: "2d", }, { name: "less than minute", duration: 30 * time.Second, want: "30s", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := formatAge(tt.duration) if got != tt.want { t.Errorf("formatAge(%v) = %q, want %q", tt.duration, got, tt.want) } }) } } func TestParseResourceQuantity(t *testing.T) { tests := []struct { name string quantity string want int64 }{ { name: "millicores", quantity: "500m", want: 500, }, { name: "cores as plain number", quantity: "2", want: 2, }, { name: "Ki suffix", quantity: "100Ki", want: 100 * 1024, }, { name: "Mi suffix", quantity: "512Mi", want: 512 * 1024 * 1024, }, { name: "Gi suffix", quantity: "2Gi", want: 2 * 1024 * 1024 * 1024, }, { name: "K suffix", quantity: "100K", want: 100 * 1000, }, { name: "M suffix", quantity: "500M", want: 500 * 1000 * 1000, }, { name: "G suffix", quantity: "1G", want: 1 * 1000 * 1000 * 1000, }, { name: "empty string", quantity: "", want: 0, }, { name: "whitespace", quantity: " ", want: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := parseResourceQuantity(tt.quantity) if got != tt.want { t.Errorf("parseResourceQuantity(%q) = %d, want %d", tt.quantity, got, tt.want) } }) } } func TestFormatCPU(t *testing.T) { tests := []struct { name string millicores int64 want string }{ { name: "zero", millicores: 0, want: "0", }, { name: "millicores", millicores: 500, want: "500m", }, { name: "one core", millicores: 1000, want: "1.0", }, { name: "two and half cores", millicores: 2500, want: "2.5", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := formatCPU(tt.millicores) if got != tt.want { t.Errorf("formatCPU(%d) = %q, want %q", tt.millicores, got, tt.want) } }) } } func TestFormatMemory(t *testing.T) { tests := []struct { name string bytes int64 want string }{ { name: "zero", bytes: 0, want: "0", }, { name: "bytes", bytes: 512, want: "512B", }, { name: "kibibytes", bytes: 1024, want: "1.0Ki", }, { name: "mebibytes", bytes: 1024 * 1024, want: "1.0Mi", }, { name: "gibibytes", bytes: 2 * 1024 * 1024 * 1024, want: "2.0Gi", }, { name: "tebibytes", bytes: 1024 * 1024 * 1024 * 1024, want: "1.0Ti", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := formatMemory(tt.bytes) if got != tt.want { t.Errorf("formatMemory(%d) = %q, want %q", tt.bytes, got, tt.want) } }) } } func TestPodInfoStruct(t *testing.T) { t.Run("PodInfo has required fields", func(t *testing.T) { pod := PodInfo{ Name: "test-pod", Status: "Running", Ready: "1/1", Restarts: 0, Age: "5m", Node: "node-1", IP: "10.0.0.1", } if pod.Name != "test-pod" { t.Errorf("Name = %q, want %q", pod.Name, "test-pod") } if pod.Status != "Running" { t.Errorf("Status = %q, want %q", pod.Status, "Running") } if pod.Ready != "1/1" { t.Errorf("Ready = %q, want %q", pod.Ready, "1/1") } if pod.Restarts != 0 { t.Errorf("Restarts = %d, want %d", pod.Restarts, 0) } }) } func TestContainerInfoStruct(t *testing.T) { t.Run("ContainerInfo has required fields", func(t *testing.T) { container := ContainerInfo{ Name: "test-container", Image: "nginx:latest", Ready: true, RestartCount: 0, State: ContainerState{ Status: "running", Since: time.Now(), }, } if container.Name != "test-container" { t.Errorf("Name = %q, want %q", container.Name, "test-container") } if !container.Ready { t.Error("Ready should be true") } if container.State.Status != "running" { t.Errorf("State.Status = %q, want %q", container.State.Status, "running") } }) } func TestDeploymentInfoStruct(t *testing.T) { t.Run("DeploymentInfo has required fields", func(t *testing.T) { dep := DeploymentInfo{ Desired: 3, Current: 3, Ready: 3, Available: 3, } if dep.Desired != 3 { t.Errorf("Desired = %d, want %d", dep.Desired, 3) } if dep.Current != 3 { t.Errorf("Current = %d, want %d", dep.Current, 3) } }) } func TestResourceMetricStruct(t *testing.T) { t.Run("ResourceMetric has required fields", func(t *testing.T) { metric := ResourceMetric{ Used: "1.5", Requested: "2.0", Limit: "4.0", Percentage: 37.5, } if metric.Used != "1.5" { t.Errorf("Used = %q, want %q", metric.Used, "1.5") } if metric.Percentage != 37.5 { t.Errorf("Percentage = %f, want %f", metric.Percentage, 37.5) } }) } func TestLogOptionsStruct(t *testing.T) { t.Run("LogOptions has all option fields", func(t *testing.T) { opts := LogOptions{ Container: "nginx", Tail: 100, Previous: true, Since: "5m", SinceSeconds: 300, } if opts.Container != "nginx" { t.Errorf("Container = %q, want %q", opts.Container, "nginx") } if opts.Tail != 100 { t.Errorf("Tail = %d, want %d", opts.Tail, 100) } if !opts.Previous { t.Error("Previous should be true") } }) } func TestKubernetesEventStruct(t *testing.T) { t.Run("KubernetesEvent has required fields", func(t *testing.T) { now := time.Now() event := KubernetesEvent{ Type: "Warning", Reason: "BackOff", Message: "Back-off restarting failed container", Count: 5, FirstSeen: now.Add(-5 * time.Minute), LastSeen: now, Object: "Pod/test-pod", } if event.Type != "Warning" { t.Errorf("Type = %q, want %q", event.Type, "Warning") } if event.Count != 5 { t.Errorf("Count = %d, want %d", event.Count, 5) } }) }