751 lines
15 KiB
Go
751 lines
15 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|