internal/resources/columns_test.go
Rama main · réplica de lectura del repositorio original.
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)
}
}