Add complete TUI application for monitoring Kubernetes clusters and host systems. Features include: Core features: - Collector framework with concurrent scheduling - Host collectors: disk, memory, load, network - Kubernetes collectors: pods, nodes, workloads, events with informers - Issue deduplication, state management, and resolve-after logic - Bubble Tea TUI with table view, details pane, and filtering - JSON export functionality UX improvements: - Help overlay with keybindings - Priority/category filters with visual indicators - Direct priority jump (0/1/2/3) - Bulk acknowledge (Shift+A) - Clipboard copy (y) - Theme toggle (T) - Age format toggle (d) - Wide title toggle (t) - Vi-style navigation (j/k) - Home/End jump (g/G) - Rollup drill-down in details Robustness: - Grace period for unreachable clusters - Rollups for high-volume issues - Flap suppression - RBAC error handling Files: All core application code with tests for host collectors, engine, store, model, and export packages.
76 lines
2.5 KiB
Go
76 lines
2.5 KiB
Go
package model
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSortIssuesDefault_PriorityThenRecency(t *testing.T) {
|
|
t0 := time.Date(2025, 12, 1, 10, 0, 0, 0, time.UTC)
|
|
|
|
issues := []Issue{
|
|
{ID: "b", Priority: PriorityP1, LastSeen: t0.Add(10 * time.Second)},
|
|
{ID: "a", Priority: PriorityP0, LastSeen: t0.Add(1 * time.Second)},
|
|
{ID: "c", Priority: PriorityP1, LastSeen: t0.Add(20 * time.Second)},
|
|
{ID: "d", Priority: PriorityP2, LastSeen: t0.Add(30 * time.Second)},
|
|
}
|
|
|
|
SortIssuesDefault(issues)
|
|
got := []string{issues[0].ID, issues[1].ID, issues[2].ID, issues[3].ID}
|
|
want := []string{"a", "c", "b", "d"} // P0 first; within P1 higher LastSeen first
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("order mismatch: got %v want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestJSONRoundTrip_EnumsStable(t *testing.T) {
|
|
when := time.Date(2025, 12, 20, 12, 0, 0, 0, time.UTC)
|
|
in := Issue{
|
|
ID: "host:disk:/home:usage",
|
|
Category: CategoryStorage,
|
|
Priority: PriorityP1,
|
|
Title: "Disk nearly full",
|
|
Details: "Usage above threshold",
|
|
Evidence: map[string]string{"mount": "/home", "used_pct": "93"},
|
|
SuggestedFix: "du -sh * | sort -h",
|
|
State: StateOpen,
|
|
FirstSeen: when,
|
|
LastSeen: when.Add(5 * time.Second),
|
|
}
|
|
|
|
b, err := json.Marshal(in)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
|
|
var out Issue
|
|
if err := json.Unmarshal(b, &out); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
// Compare fields we care about; time.Time compares directly.
|
|
if in.ID != out.ID || in.Category != out.Category || in.Priority != out.Priority || in.State != out.State {
|
|
t.Fatalf("basic fields mismatch after round-trip: in=%+v out=%+v", in, out)
|
|
}
|
|
if in.Title != out.Title || in.Details != out.Details || in.SuggestedFix != out.SuggestedFix {
|
|
t.Fatalf("string fields mismatch after round-trip")
|
|
}
|
|
if !reflect.DeepEqual(in.Evidence, out.Evidence) {
|
|
t.Fatalf("evidence mismatch after round-trip: in=%v out=%v", in.Evidence, out.Evidence)
|
|
}
|
|
if !in.FirstSeen.Equal(out.FirstSeen) || !in.LastSeen.Equal(out.LastSeen) {
|
|
t.Fatalf("time mismatch after round-trip: in=(%v,%v) out=(%v,%v)", in.FirstSeen, in.LastSeen, out.FirstSeen, out.LastSeen)
|
|
}
|
|
}
|
|
|
|
func TestJSON_InvalidEnumRejected(t *testing.T) {
|
|
// Priority invalid should be rejected.
|
|
var i Issue
|
|
if err := json.Unmarshal([]byte(`{"id":"x","category":"Storage","priority":"P9","title":"t","state":"Open","first_seen":"2025-12-20T12:00:00Z","last_seen":"2025-12-20T12:00:01Z"}`), &i); err == nil {
|
|
t.Fatalf("expected error for invalid priority")
|
|
}
|
|
}
|