Ctrl K
ComunidadGuías…

internal/resources/columns_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package resources

import (
	"fmt"
	"testing"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	"github.com/sophotechlabs/spinoza/internal/unstr"
)

func u(obj map[string]any) *unstructured.Unstructured {
	return &unstructured.Unstructured{Object: obj}
}

func eq(t *testing.T, got, want []string) {
	t.Helper()
	if len(got) != len(want) {
		t.Fatalf("cells = %v, want %v", got, want)
	}
	for i := range want {
		if got[i] != want[i] {
			t.Fatalf("cells = %v, want %v", got, want)
		}
	}
}

func colNames(t *testing.T, kind string) []string {
	t.Helper()
	cols := columnsFor(kind)
	names := make([]string, 0, len(cols))
	for _, c := range cols {
		names = append(names, c.Name)
	}
	return names
}

func TestColumnsFor(t *testing.T) {
	cases := []struct {
		kind string
		want []string
	}{
		{"Pod", []string{"Containers", "Status", "Restarts", "Node"}},
		{"Deployment", []string{"Ready", "Up-to-date", "Available"}},
		{"ReplicaSet", []string{"Ready", "Up-to-date", "Available"}},
		{"StatefulSet", []string{"Ready", "Up-to-date", "Available"}},
		{"ReplicationController", []string{"Ready", "Up-to-date", "Available"}},
		{"DaemonSet", []string{"Desired", "Ready", "Available"}},
		{"Service", []string{"Type", "Cluster-IP", "Ports"}},
		{"Node", []string{"Status", "Roles", "Version"}},
		{"Namespace", []string{"Status"}},
		{"Job", []string{"Completions"}},
		{"ConfigMap", []string{"Status"}},
	}
	for _, tc := range cases {
		t.Run(tc.kind, func(t *testing.T) {
			eq(t, colNames(t, tc.kind), tc.want)
		})
	}
}

func TestColumnRenders(t *testing.T) {
	cases := []struct {
		kind  string
		index int
		want  string
	}{
		{"Pod", 0, "containers"},
		{"Pod", 1, "status"},
		{"Pod", 2, "restarts"},
		{"Deployment", 0, "ratio"},
		{"Job", 0, "ratio"},
		{"ConfigMap", 0, "status"},
		{"Node", 0, "status"},
		{"Node", 1, ""},
		{"Namespace", 0, "status"},
	}
	for _, tc := range cases {
		t.Run(fmt.Sprintf("%s[%d]", tc.kind, tc.index), func(t *testing.T) {
			if got := columnsFor(tc.kind)[tc.index].Render; got != tc.want {
				t.Fatalf("render = %q, want %q", got, tc.want)
			}
		})
	}
}

func TestContainersForNonPod(t *testing.T) {
	if got := containersFor(u(map[string]any{}), "Deployment"); got != nil {
		t.Fatalf("containersFor non-pod = %v, want nil", got)
	}
}

func TestContainersForEmptyPod(t *testing.T) {
	if got := containersFor(u(map[string]any{}), "Pod"); got != nil {
		t.Fatalf("containersFor empty pod = %v, want nil", got)
	}
}

func TestContainersForPod(t *testing.T) {
	pod := u(map[string]any{
		"status": map[string]any{
			"initContainerStatuses": []any{
				map[string]any{
					"name":         "init",
					"ready":        true,
					"restartCount": int64(0),
					"state":        map[string]any{"terminated": map[string]any{"reason": "Completed"}},
				},
			},
			"containerStatuses": []any{
				"not-a-map",
				map[string]any{
					"name":         "app",
					"ready":        true,
					"restartCount": int64(1),
					"state":        map[string]any{"running": map[string]any{}},
				},
				map[string]any{
					"name":         "sidecar",
					"ready":        false,
					"restartCount": int64(4),
					"state":        map[string]any{"waiting": map[string]any{"reason": "CrashLoopBackOff"}},
				},
			},
		},
	})
	states := containersFor(pod, "Pod")
	if len(states) != 3 {
		t.Fatalf("states = %d, want 3", len(states))
	}
	if !states[0].Init || states[0].Name != "init" || states[0].State != "terminated" || states[0].Reason != "Completed" {
		t.Fatalf("init state = %+v", states[0])
	}
	if states[1].Init || states[1].Name != "app" || states[1].State != "running" || !states[1].Ready || states[1].Restarts != 1 {
		t.Fatalf("app state = %+v", states[1])
	}
	if states[2].Name != "sidecar" || states[2].State != "waiting" || states[2].Reason != "CrashLoopBackOff" || states[2].Ready {
		t.Fatalf("sidecar state = %+v", states[2])
	}
}

func TestContainerStateReason(t *testing.T) {
	if state, reason := containerStateReason(map[string]any{}); state != "waiting" || reason != "" {
		t.Fatalf("no state = %q,%q, want waiting,''", state, reason)
	}
	empty := map[string]any{"state": map[string]any{}}
	if state, reason := containerStateReason(empty); state != "waiting" || reason != "" {
		t.Fatalf("empty state = %q,%q, want waiting,''", state, reason)
	}
}

func TestPodCells(t *testing.T) {
	pod := u(map[string]any{
		"spec": map[string]any{
			"nodeName": "node-1",
			"containers": []any{
				map[string]any{"name": "a"},
				map[string]any{"name": "b"},
			},
		},
		"status": map[string]any{
			"phase": "Running",
			"containerStatuses": []any{
				map[string]any{"ready": true, "restartCount": int64(2)},
				map[string]any{"ready": false, "restartCount": int64(3)},
			},
		},
	})
	eq(t, podCells(pod), []string{"1/2", "Running", "5", "node-1"})
}

func TestPodCellsEmpty(t *testing.T) {
	pod := u(map[string]any{})
	eq(t, podCells(pod), []string{"0/0", "", "0", ""})
}

func TestPodCellsSkipsNonMapContainerStatus(t *testing.T) {
	pod := u(map[string]any{
		"spec": map[string]any{
			"containers": []any{map[string]any{"name": "a"}},
		},
		"status": map[string]any{
			"phase": "Running",
			"containerStatuses": []any{
				"not-a-map",
				map[string]any{"ready": true, "restartCount": int64(1)},
			},
		},
	})
	eq(t, podCells(pod), []string{"1/1", "Running", "1", ""})
}

func TestWorkloadCells(t *testing.T) {
	dep := u(map[string]any{
		"spec": map[string]any{"replicas": int64(3)},
		"status": map[string]any{
			"readyReplicas":     int64(2),
			"updatedReplicas":   int64(3),
			"availableReplicas": int64(2),
		},
	})
	eq(t, workloadCells(dep), []string{"2/3", "3", "2"})
}

func TestWorkloadCellsMissing(t *testing.T) {
	dep := u(map[string]any{})
	eq(t, workloadCells(dep), []string{"0/0", "0", "0"})
}

func TestDaemonCells(t *testing.T) {
	ds := u(map[string]any{
		"status": map[string]any{
			"desiredNumberScheduled": int64(4),
			"numberReady":            int64(3),
			"numberAvailable":        int64(3),
		},
	})
	eq(t, daemonCells(ds), []string{"4", "3", "3"})
}

func TestServiceCells(t *testing.T) {
	svc := u(map[string]any{
		"spec": map[string]any{
			"type":      "ClusterIP",
			"clusterIP": "10.0.0.1",
			"ports": []any{
				map[string]any{"port": int64(80), "protocol": "TCP"},
				map[string]any{"port": int64(443), "protocol": "TCP"},
			},
		},
	})
	eq(t, serviceCells(svc), []string{"ClusterIP", "10.0.0.1", "80/TCP,443/TCP"})
}

func TestServiceCellsPortWithoutProtocol(t *testing.T) {
	svc := u(map[string]any{
		"spec": map[string]any{
			"type":      "NodePort",
			"clusterIP": "10.0.0.2",
			"ports": []any{
				"not-a-map",
				map[string]any{"port": int64(8080)},
			},
		},
	})
	eq(t, serviceCells(svc), []string{"NodePort", "10.0.0.2", "8080/"})
}

func TestNodeCellsReady(t *testing.T) {
	node := u(map[string]any{
		"metadata": map[string]any{
			"labels": map[string]any{
				"node-role.kubernetes.io/control-plane": "",
				"node-role.kubernetes.io/worker":        "",
				"kubernetes.io/hostname":                "node-1",
			},
		},
		"status": map[string]any{
			"conditions": []any{
				map[string]any{"type": "MemoryPressure", "status": "False"},
				map[string]any{"type": "Ready", "status": "True"},
			},
			"nodeInfo": map[string]any{"kubeletVersion": "v1.31.0"},
		},
	})
	cells := nodeCells(node)
	if cells[0] != "Ready" {
		t.Fatalf("status = %q, want Ready", cells[0])
	}
	if cells[2] != "v1.31.0" {
		t.Fatalf("version = %q, want v1.31.0", cells[2])
	}
	roles := cells[1]
	if roles != "control-plane,worker" && roles != "worker,control-plane" {
		t.Fatalf("roles = %q, want control-plane and worker", roles)
	}
}

func TestNodeCellsNotReady(t *testing.T) {
	node := u(map[string]any{
		"status": map[string]any{
			"conditions": []any{
				"not-a-map",
				map[string]any{"type": "Ready", "status": "False"},
			},
		},
	})
	eq(t, nodeCells(node), []string{"NotReady", "", ""})
}

func TestNodeCellsSkipsEmptyRole(t *testing.T) {
	node := u(map[string]any{
		"metadata": map[string]any{
			"labels": map[string]any{
				"node-role.kubernetes.io/": "",
			},
		},
		"status": map[string]any{},
	})
	eq(t, nodeCells(node), []string{"NotReady", "", ""})
}

func TestJobCells(t *testing.T) {
	job := u(map[string]any{
		"spec":   map[string]any{"completions": int64(5)},
		"status": map[string]any{"succeeded": int64(3)},
	})
	eq(t, jobCells(job), []string{"3/5"})
}

func TestConditionSummary(t *testing.T) {
	cases := []struct {
		name       string
		conditions []any
		want       string
	}{
		{"ready", []any{
			map[string]any{"type": "Ready", "status": "True"},
		}, "Ready"},
		{"reason of the not-ready condition", []any{
			map[string]any{"type": "Available", "status": "False"},
			map[string]any{"type": "Ready", "status": "False", "reason": "MinimumReplicasUnavailable"},
		}, "MinimumReplicasUnavailable"},
		{"not ready without a reason", []any{
			"not-a-map",
			map[string]any{"type": "Ready", "status": "False"},
		}, "NotReady"},
		{"no conditions at all", nil, ""},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			obj := u(map[string]any{})
			if tc.conditions != nil {
				obj = u(map[string]any{
					"status": map[string]any{"conditions": tc.conditions},
				})
			}
			if got := unstr.ReadySummary(obj); got != tc.want {
				t.Fatalf("conditionSummary = %q, want %q", got, tc.want)
			}
		})
	}
}

func TestCellsForDispatch(t *testing.T) {
	pod := u(map[string]any{
		"spec":   map[string]any{"containers": []any{map[string]any{"name": "a"}}},
		"status": map[string]any{"phase": "Running"},
	})
	eq(t, cellsFor(pod, "Pod"), []string{"0/1", "Running", "0", ""})

	dep := u(map[string]any{
		"spec":   map[string]any{"replicas": int64(1)},
		"status": map[string]any{"readyReplicas": int64(1), "updatedReplicas": int64(1), "availableReplicas": int64(1)},
	})
	eq(t, cellsFor(dep, "Deployment"), []string{"1/1", "1", "1"})

	ds := u(map[string]any{
		"status": map[string]any{"desiredNumberScheduled": int64(1), "numberReady": int64(1), "numberAvailable": int64(1)},
	})
	eq(t, cellsFor(ds, "DaemonSet"), []string{"1", "1", "1"})

	svc := u(map[string]any{
		"spec": map[string]any{"type": "ClusterIP", "clusterIP": "10.0.0.1"},
	})
	eq(t, cellsFor(svc, "Service"), []string{"ClusterIP", "10.0.0.1", ""})

	node := u(map[string]any{"status": map[string]any{}})
	eq(t, cellsFor(node, "Node"), []string{"NotReady", "", ""})

	ns := u(map[string]any{"status": map[string]any{"phase": "Active"}})
	eq(t, cellsFor(ns, "Namespace"), []string{"Active"})

	job := u(map[string]any{
		"spec":   map[string]any{"completions": int64(1)},
		"status": map[string]any{"succeeded": int64(1)},
	})
	eq(t, cellsFor(job, "Job"), []string{"1/1"})

	generic := u(map[string]any{
		"status": map[string]any{"conditions": []any{map[string]any{"type": "Ready", "status": "True"}}},
	})
	eq(t, cellsFor(generic, "ConfigMap"), []string{"Ready"})
}

func TestToInt64(t *testing.T) {
	if got := toInt64(int64(7)); got != 7 {
		t.Fatalf("toInt64(int64) = %d, want 7", got)
	}
	if got := toInt64(float64(9)); got != 9 {
		t.Fatalf("toInt64(float64) = %d, want 9", got)
	}
	if got := toInt64("nope"); got != 0 {
		t.Fatalf("toInt64(string) = %d, want 0", got)
	}
	if got := toInt64(nil); got != 0 {
		t.Fatalf("toInt64(nil) = %d, want 0", got)
	}
}

func TestNestedHelpersOnWrongType(t *testing.T) {
	obj := u(map[string]any{
		"spec": map[string]any{
			"replicas": "three",
			"nodeName": int64(5),
			"ports":    "not-a-slice",
		},
	})
	if got := unstr.String(obj, "spec", "nodeName"); got != "" {
		t.Fatalf("nestedString on non-string = %q, want empty", got)
	}
	if got := unstr.Int(obj, "spec", "replicas"); got != 0 {
		t.Fatalf("nestedInt on non-int = %d, want 0", got)
	}
	if got := unstr.Slice(obj, "spec", "ports"); got != nil {
		t.Fatalf("nestedSlice on non-slice = %v, want nil", got)
	}
}

func TestContainersForMarksEphemeralContainers(t *testing.T) {
	pod := u(map[string]any{
		"status": map[string]any{
			"containerStatuses": []any{
				map[string]any{"name": "app", "ready": true, "restartCount": int64(0), "state": map[string]any{"running": map[string]any{}}},
			},
			"ephemeralContainerStatuses": []any{
				map[string]any{"name": "spinoza-debug-1", "ready": false, "restartCount": int64(0), "state": map[string]any{"running": map[string]any{}}},
			},
		},
	})
	states := containersFor(pod, "Pod")
	if len(states) != 2 {
		t.Fatalf("states = %d, want 2", len(states))
	}
	if states[0].Name != "app" || states[0].Ephemeral {
		t.Fatalf("regular container = %+v", states[0])
	}
	if states[1].Name != "spinoza-debug-1" || !states[1].Ephemeral {
		t.Fatalf("debug container = %+v, want Ephemeral true", states[1])
	}
	if states[1].Init {
		t.Fatal("an ephemeral container is not an init container")
	}
}

func TestNodeCellsMarkACordonedNode(t *testing.T) {
	node := &unstructured.Unstructured{Object: map[string]any{
		"apiVersion": "v1",
		"kind":       "Node",
		"metadata":   map[string]any{"name": "worker-1"},
		"spec":       map[string]any{"unschedulable": true},
		"status": map[string]any{
			"conditions": []any{
				map[string]any{"type": "Ready", "status": "True"},
			},
		},
	}}

	if got := nodeCells(node)[0]; got != "Ready,SchedulingDisabled" {
		t.Fatalf("status = %q", got)
	}
}

func TestNodeCellsLeaveASchedulableNodeAlone(t *testing.T) {
	node := &unstructured.Unstructured{Object: map[string]any{
		"apiVersion": "v1",
		"kind":       "Node",
		"metadata":   map[string]any{"name": "worker-1"},
		"spec":       map[string]any{"unschedulable": false},
		"status": map[string]any{
			"conditions": []any{
				map[string]any{"type": "Ready", "status": "True"},
			},
		},
	}}

	if got := nodeCells(node)[0]; got != "Ready" {
		t.Fatalf("status = %q", got)
	}
}

func TestNodeCellsIgnoreANonBoolUnschedulable(t *testing.T) {
	node := &unstructured.Unstructured{Object: map[string]any{
		"apiVersion": "v1",
		"kind":       "Node",
		"metadata":   map[string]any{"name": "worker-1"},
		"spec":       map[string]any{"unschedulable": "yes"},
		"status":     map[string]any{"conditions": []any{}},
	}}

	if got := nodeCells(node)[0]; got != "NotReady" {
		t.Fatalf("status = %q", got)
	}
}

func coreEvent() *unstructured.Unstructured {
	return &unstructured.Unstructured{Object: map[string]any{
		"apiVersion":     "v1",
		"kind":           "Event",
		"metadata":       map[string]any{"name": "web.17abc", "namespace": "prod"},
		"type":           "Warning",
		"reason":         "BackOff",
		"message":        "Back-off restarting failed container",
		"count":          int64(42),
		"lastTimestamp":  "2026-08-18T10:00:00Z",
		"firstTimestamp": "2026-08-18T09:00:00Z",
		"involvedObject": map[string]any{"kind": "Pod", "name": "web-0", "namespace": "prod"},
		"source":         map[string]any{"component": "kubelet", "host": "node-1"},
	}}
}

func newEvent() *unstructured.Unstructured {
	return &unstructured.Unstructured{Object: map[string]any{
		"apiVersion":         "events.k8s.io/v1",
		"kind":               "Event",
		"metadata":           map[string]any{"name": "web.17abd", "namespace": "prod"},
		"type":               "Normal",
		"reason":             "Scheduled",
		"note":               "Successfully assigned prod/web-0 to node-1",
		"eventTime":          "2026-08-18T09:30:00Z",
		"regarding":          map[string]any{"kind": "Pod", "name": "web-0", "namespace": "prod"},
		"reportingComponent": "default-scheduler",
		"series":             map[string]any{"count": int64(3)},
	}}
}

func TestEventColumnsSayWhatHappened(t *testing.T) {
	columns := columnsFor("Event")

	names := make([]string, 0, len(columns))
	for _, column := range columns {
		names = append(names, column.Name)
	}
	want := []string{"Type", "Reason", "Object", "Count", "Last seen", "Message"}
	if len(names) != len(want) {
		t.Fatalf("columns = %v, want %v", names, want)
	}
	for i, name := range want {
		if names[i] != name {
			t.Fatalf("columns = %v, want %v", names, want)
		}
	}
}

func TestCoreEventCells(t *testing.T) {
	cells := cellsFor(coreEvent(), "Event")

	want := []string{
		"Warning", "BackOff", "Pod/web-0", "42", "2026-08-18T10:00:00Z",
		"Back-off restarting failed container",
	}
	for i, value := range want {
		if cells[i] != value {
			t.Fatalf("cells = %v, want %v", cells, want)
		}
	}
}

func TestTheNewerEventApiReadsTheSame(t *testing.T) {
	cells := cellsFor(newEvent(), "Event")

	want := []string{
		"Normal", "Scheduled", "Pod/web-0", "3", "2026-08-18T09:30:00Z",
		"Successfully assigned prod/web-0 to node-1",
	}
	for i, value := range want {
		if cells[i] != value {
			t.Fatalf("cells = %v, want %v", cells, want)
		}
	}
}

func TestAnEventWithoutAReferencedObjectLeavesTheObjectCellEmpty(t *testing.T) {
	if got := eventObject(u(map[string]any{})); got != "" {
		t.Fatalf("event object = %q, want empty", got)
	}
}