470 lines
11 KiB
Go
470 lines
11 KiB
Go
package ai
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
)
|
|
|
|
// ========================================
|
|
// truncateKubernetesMessage tests
|
|
// ========================================
|
|
|
|
func TestTruncateKubernetesMessage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "short message",
|
|
input: "This is a short message",
|
|
expected: "This is a short message",
|
|
},
|
|
{
|
|
name: "empty message",
|
|
input: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "max length message",
|
|
input: string(make([]byte, maxKubernetesMessageLength)),
|
|
expected: string(make([]byte, maxKubernetesMessageLength)),
|
|
},
|
|
{
|
|
name: "over max length",
|
|
input: string(make([]byte, maxKubernetesMessageLength+50)),
|
|
expected: string(make([]byte, maxKubernetesMessageLength)) + "...",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := truncateKubernetesMessage(tt.input)
|
|
if result != tt.expected {
|
|
if len(result) < 100 {
|
|
t.Errorf("truncateKubernetesMessage() = %q, want %q", result, tt.expected)
|
|
} else {
|
|
t.Errorf("truncateKubernetesMessage() length = %d, want %d", len(result), len(tt.expected))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// formatKubernetesAge tests
|
|
// ========================================
|
|
|
|
func TestFormatKubernetesAge(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
duration time.Duration
|
|
expected string
|
|
}{
|
|
{
|
|
name: "zero duration",
|
|
duration: 0,
|
|
expected: "0s",
|
|
},
|
|
{
|
|
name: "30 seconds",
|
|
duration: 30 * time.Second,
|
|
expected: "30s",
|
|
},
|
|
{
|
|
name: "5 minutes",
|
|
duration: 5 * time.Minute,
|
|
expected: "5m",
|
|
},
|
|
{
|
|
name: "59 minutes",
|
|
duration: 59 * time.Minute,
|
|
expected: "59m",
|
|
},
|
|
{
|
|
name: "1 hour",
|
|
duration: time.Hour,
|
|
expected: "1h",
|
|
},
|
|
{
|
|
name: "23 hours",
|
|
duration: 23 * time.Hour,
|
|
expected: "23h",
|
|
},
|
|
{
|
|
name: "1 day",
|
|
duration: 24 * time.Hour,
|
|
expected: "1d",
|
|
},
|
|
{
|
|
name: "7 days",
|
|
duration: 7 * 24 * time.Hour,
|
|
expected: "7d",
|
|
},
|
|
{
|
|
name: "negative duration",
|
|
duration: -5 * time.Second,
|
|
expected: "0s",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := formatKubernetesAge(tt.duration)
|
|
if result != tt.expected {
|
|
t.Errorf("formatKubernetesAge(%v) = %q, want %q", tt.duration, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// isKubernetesPodHealthy tests
|
|
// ========================================
|
|
|
|
func TestIsKubernetesPodHealthy(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pod models.KubernetesPod
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "empty phase",
|
|
pod: models.KubernetesPod{Phase: ""},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "pending phase",
|
|
pod: models.KubernetesPod{Phase: "Pending"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "running phase, no containers",
|
|
pod: models.KubernetesPod{Phase: "Running", Containers: nil},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "running phase, healthy container",
|
|
pod: models.KubernetesPod{
|
|
Phase: "Running",
|
|
Containers: []models.KubernetesPodContainer{
|
|
{Name: "app", Ready: true, State: "running"},
|
|
},
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "running phase, not ready container",
|
|
pod: models.KubernetesPod{
|
|
Phase: "Running",
|
|
Containers: []models.KubernetesPodContainer{
|
|
{Name: "app", Ready: false, State: "running"},
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "running phase, container not running",
|
|
pod: models.KubernetesPod{
|
|
Phase: "Running",
|
|
Containers: []models.KubernetesPodContainer{
|
|
{Name: "app", Ready: true, State: "waiting"},
|
|
},
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "failed phase",
|
|
pod: models.KubernetesPod{Phase: "Failed"},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "succeeded phase",
|
|
pod: models.KubernetesPod{Phase: "Succeeded"},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := isKubernetesPodHealthy(tt.pod)
|
|
if result != tt.expected {
|
|
t.Errorf("isKubernetesPodHealthy() = %v, want %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// kubernetesPodReason tests
|
|
// ========================================
|
|
|
|
func TestKubernetesPodReason(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pod models.KubernetesPod
|
|
contains []string
|
|
}{
|
|
{
|
|
name: "empty pod",
|
|
pod: models.KubernetesPod{},
|
|
contains: []string{},
|
|
},
|
|
{
|
|
name: "pod with phase",
|
|
pod: models.KubernetesPod{Phase: "Pending"},
|
|
contains: []string{"phase=Pending"},
|
|
},
|
|
{
|
|
name: "pod with reason",
|
|
pod: models.KubernetesPod{Phase: "Failed", Reason: "OOMKilled"},
|
|
contains: []string{"phase=Failed", "reason=OOMKilled"},
|
|
},
|
|
{
|
|
name: "pod with restarts",
|
|
pod: models.KubernetesPod{Phase: "Running", Restarts: 5},
|
|
contains: []string{"restarts=5"},
|
|
},
|
|
{
|
|
name: "pod with container issue",
|
|
pod: models.KubernetesPod{
|
|
Phase: "Running",
|
|
Containers: []models.KubernetesPodContainer{
|
|
{Name: "app", Ready: false, State: "waiting", Reason: "CrashLoopBackOff"},
|
|
},
|
|
},
|
|
contains: []string{"containers=app"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := kubernetesPodReason(tt.pod)
|
|
for _, expected := range tt.contains {
|
|
if len(expected) > 0 && !kubeTestContains(result, expected) {
|
|
t.Errorf("kubernetesPodReason() = %q, want to contain %q", result, expected)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func kubeTestContains(s, substr string) bool {
|
|
for i := 0; i <= len(s)-len(substr); i++ {
|
|
if s[i:i+len(substr)] == substr {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ========================================
|
|
// isKubernetesDeploymentHealthy tests
|
|
// ========================================
|
|
|
|
func TestIsKubernetesDeploymentHealthy(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
deployment models.KubernetesDeployment
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "empty deployment",
|
|
deployment: models.KubernetesDeployment{},
|
|
expected: true, // 0/0 replicas is considered healthy
|
|
},
|
|
{
|
|
name: "all replicas ready",
|
|
deployment: models.KubernetesDeployment{
|
|
DesiredReplicas: 3,
|
|
ReadyReplicas: 3,
|
|
AvailableReplicas: 3,
|
|
UpdatedReplicas: 3,
|
|
},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "some replicas not ready",
|
|
deployment: models.KubernetesDeployment{
|
|
DesiredReplicas: 3,
|
|
ReadyReplicas: 2,
|
|
AvailableReplicas: 3,
|
|
UpdatedReplicas: 3,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "no replicas ready",
|
|
deployment: models.KubernetesDeployment{
|
|
DesiredReplicas: 3,
|
|
ReadyReplicas: 0,
|
|
AvailableReplicas: 0,
|
|
UpdatedReplicas: 0,
|
|
},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "not all available",
|
|
deployment: models.KubernetesDeployment{
|
|
DesiredReplicas: 3,
|
|
ReadyReplicas: 3,
|
|
AvailableReplicas: 2,
|
|
UpdatedReplicas: 3,
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := isKubernetesDeploymentHealthy(tt.deployment)
|
|
if result != tt.expected {
|
|
t.Errorf("isKubernetesDeploymentHealthy() = %v, want %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// formatPodIssueLine tests
|
|
// ========================================
|
|
|
|
func TestFormatPodIssueLine(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
issue podIssue
|
|
expected string
|
|
}{
|
|
{
|
|
name: "with namespace and name only",
|
|
issue: podIssue{
|
|
namespace: "default",
|
|
name: "nginx-pod",
|
|
},
|
|
expected: "default/nginx-pod",
|
|
},
|
|
{
|
|
name: "with reason",
|
|
issue: podIssue{
|
|
namespace: "kube-system",
|
|
name: "coredns-pod",
|
|
reason: "CrashLoopBackOff",
|
|
},
|
|
expected: "kube-system/coredns-pod CrashLoopBackOff",
|
|
},
|
|
{
|
|
name: "empty reason",
|
|
issue: podIssue{
|
|
namespace: "production",
|
|
name: "api-server",
|
|
reason: "",
|
|
},
|
|
expected: "production/api-server",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := formatPodIssueLine(tt.issue)
|
|
if result != tt.expected {
|
|
t.Errorf("formatPodIssueLine() = %q, want %q", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// ========================================
|
|
// formatPodIssues tests
|
|
// ========================================
|
|
|
|
func TestFormatPodIssues(t *testing.T) {
|
|
issues := []podIssue{
|
|
{namespace: "ns1", name: "pod1", reason: "reason1"},
|
|
{namespace: "ns2", name: "pod2", reason: "reason2"},
|
|
{namespace: "ns3", name: "pod3", reason: "reason3"},
|
|
{namespace: "ns4", name: "pod4", reason: "reason4"},
|
|
}
|
|
|
|
t.Run("within limit", func(t *testing.T) {
|
|
result := formatPodIssues(issues, 10)
|
|
if len(result) != 4 {
|
|
t.Errorf("Expected 4 lines, got %d", len(result))
|
|
}
|
|
})
|
|
|
|
t.Run("exceeds limit", func(t *testing.T) {
|
|
result := formatPodIssues(issues, 2)
|
|
if len(result) != 3 { // 2 issues + "... and X more"
|
|
t.Errorf("Expected 3 lines (2 issues + truncation notice), got %d", len(result))
|
|
}
|
|
if !kubeTestContains(result[2], "and 2 more") {
|
|
t.Errorf("Expected truncation notice, got %q", result[2])
|
|
}
|
|
})
|
|
|
|
t.Run("empty issues", func(t *testing.T) {
|
|
result := formatPodIssues([]podIssue{}, 10)
|
|
if len(result) != 0 {
|
|
t.Errorf("Expected 0 lines for empty issues, got %d", len(result))
|
|
}
|
|
})
|
|
}
|
|
|
|
// ========================================
|
|
// formatPodRestarts tests
|
|
// ========================================
|
|
|
|
func TestFormatPodRestarts(t *testing.T) {
|
|
t.Run("empty issues", func(t *testing.T) {
|
|
result := formatPodRestarts(nil, 10)
|
|
if result != nil {
|
|
t.Errorf("Expected nil, got %v", result)
|
|
}
|
|
})
|
|
|
|
t.Run("sorts by restarts descending", func(t *testing.T) {
|
|
issues := []podIssue{
|
|
{namespace: "ns1", name: "pod1", restarts: 5},
|
|
{namespace: "ns2", name: "pod2", restarts: 10},
|
|
{namespace: "ns3", name: "pod3", restarts: 2},
|
|
}
|
|
result := formatPodRestarts(issues, 10)
|
|
if len(result) != 3 {
|
|
t.Errorf("Expected 3 lines, got %d", len(result))
|
|
}
|
|
// First should be pod2 (10 restarts)
|
|
if !kubeTestContains(result[0], "restarts=10") {
|
|
t.Errorf("Expected first to have 10 restarts, got %q", result[0])
|
|
}
|
|
})
|
|
|
|
t.Run("with reason", func(t *testing.T) {
|
|
issues := []podIssue{
|
|
{namespace: "ns1", name: "pod1", restarts: 5, reason: "OOMKilled"},
|
|
}
|
|
result := formatPodRestarts(issues, 10)
|
|
if len(result) != 1 {
|
|
t.Errorf("Expected 1 line, got %d", len(result))
|
|
}
|
|
if !kubeTestContains(result[0], "OOMKilled") {
|
|
t.Errorf("Expected reason in output, got %q", result[0])
|
|
}
|
|
})
|
|
|
|
t.Run("respects limit", func(t *testing.T) {
|
|
issues := []podIssue{
|
|
{namespace: "ns1", name: "pod1", restarts: 10},
|
|
{namespace: "ns2", name: "pod2", restarts: 8},
|
|
{namespace: "ns3", name: "pod3", restarts: 6},
|
|
{namespace: "ns4", name: "pod4", restarts: 4},
|
|
}
|
|
result := formatPodRestarts(issues, 2)
|
|
if len(result) != 2 {
|
|
t.Errorf("Expected 2 lines (limit), got %d", len(result))
|
|
}
|
|
})
|
|
}
|
|
|
|
|