Files
porthole/internal/store/store_test.go
OpenCode Test 1421b4659e feat: implement ControlTower TUI for cluster and host monitoring
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.
2025-12-24 13:29:51 -08:00

102 lines
2.9 KiB
Go

package store
import (
"testing"
"time"
"tower/internal/model"
)
func TestStore_Upsert_DedupAndTimestamps(t *testing.T) {
now1 := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
now2 := now1.Add(5 * time.Second)
s := New(30 * time.Second)
// Same ID twice in one Upsert should dedupe.
s.Upsert(now1, []model.Issue{
{ID: "i-1", Title: "first"},
{ID: "i-1", Title: "should be ignored"},
})
snap1 := s.Snapshot(now1)
if len(snap1) != 1 {
t.Fatalf("expected 1 issue, got %d", len(snap1))
}
if snap1[0].ID != "i-1" {
t.Fatalf("expected id i-1, got %q", snap1[0].ID)
}
if !snap1[0].FirstSeen.Equal(now1) {
t.Fatalf("expected FirstSeen=%v, got %v", now1, snap1[0].FirstSeen)
}
if !snap1[0].LastSeen.Equal(now1) {
t.Fatalf("expected LastSeen=%v, got %v", now1, snap1[0].LastSeen)
}
if snap1[0].State != model.StateOpen {
t.Fatalf("expected State=Open, got %q", snap1[0].State)
}
// Subsequent Upsert for same ID should preserve FirstSeen and update LastSeen.
s.Upsert(now2, []model.Issue{{ID: "i-1", Title: "updated"}})
snap2 := s.Snapshot(now2)
if len(snap2) != 1 {
t.Fatalf("expected 1 issue, got %d", len(snap2))
}
if !snap2[0].FirstSeen.Equal(now1) {
t.Fatalf("expected FirstSeen to remain %v, got %v", now1, snap2[0].FirstSeen)
}
if !snap2[0].LastSeen.Equal(now2) {
t.Fatalf("expected LastSeen=%v, got %v", now2, snap2[0].LastSeen)
}
}
func TestStore_AckPreservedWhilePresent(t *testing.T) {
now1 := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
now2 := now1.Add(1 * time.Second)
s := New(30 * time.Second)
s.Upsert(now1, []model.Issue{{ID: "i-1", Title: "t"}})
s.Acknowledge("i-1")
// Upsert again while present should remain Acked.
s.Upsert(now2, []model.Issue{{ID: "i-1", Title: "t2"}})
snap := s.Snapshot(now2)
if len(snap) != 1 {
t.Fatalf("expected 1 issue, got %d", len(snap))
}
if snap[0].State != model.StateAcknowledged {
t.Fatalf("expected State=Acknowledged, got %q", snap[0].State)
}
s.Unacknowledge("i-1")
snap2 := s.Snapshot(now2)
if snap2[0].State != model.StateOpen {
t.Fatalf("expected State=Open after unack, got %q", snap2[0].State)
}
}
func TestStore_ResolvesOnlyAfterAbsenceWindow(t *testing.T) {
resolveAfter := 10 * time.Second
now0 := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
s := New(resolveAfter)
s.Upsert(now0, []model.Issue{{ID: "i-1", Title: "t"}})
// Miss a tick shortly after; should not resolve due to flap suppression / window.
s.Upsert(now0.Add(1*time.Second), nil)
snap1 := s.Snapshot(now0.Add(9 * time.Second))
if len(snap1) != 1 {
t.Fatalf("expected 1 issue, got %d", len(snap1))
}
if snap1[0].State != model.StateOpen {
t.Fatalf("expected still Open before resolveAfter, got %q", snap1[0].State)
}
// Still absent beyond resolveAfter => should resolve.
snap2 := s.Snapshot(now0.Add(11 * time.Second))
if snap2[0].State != model.StateResolved {
t.Fatalf("expected Resolved after absence > resolveAfter, got %q", snap2[0].State)
}
}