f00acbc280
Replace ~150 bare status string comparisons with typed models.Status constants (StatusUp, StatusDown, StatusPending, StatusLate, StatusStale, StatusSSLExp). Single IsBroken() method replaces the duplicated isBroken lambda in monitor.go and isDown function in sla.go. Adding a new status value (e.g. DEGRADED) now requires one constant definition instead of grep-and-pray across 16 files. CheckResult.Status stays string — the checker is the boundary between raw protocol results and typed status. Cast happens at the edge in handleStatusChange.
176 lines
4.2 KiB
Go
176 lines
4.2 KiB
Go
package tui
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.lerkolabs.com/lerkolabs/uptop/internal/models"
|
|
)
|
|
|
|
// styledModel carries a default-theme styles instance for render-helper tests.
|
|
var styledModel = Model{st: newStyles(themeFlexokiDark)}
|
|
|
|
func TestLimitStr(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
max int
|
|
want string
|
|
}{
|
|
{"hello", 10, "hello"},
|
|
{"hello", 5, "hello"},
|
|
{"hello world", 8, "hello..."},
|
|
{"", 5, ""},
|
|
{"abc", 3, "abc"},
|
|
{"abcd", 3, "..."},
|
|
{"日本語テスト", 4, "日..."},
|
|
}
|
|
for _, tt := range tests {
|
|
got := limitStr(tt.input, tt.max)
|
|
if got != tt.want {
|
|
t.Errorf("limitStr(%q, %d) = %q, want %q", tt.input, tt.max, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSiteOrder(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
site models.Site
|
|
want int
|
|
}{
|
|
{"down", models.Site{Status: "DOWN"}, 0},
|
|
{"ssl exp", models.Site{Status: "SSL EXP"}, 0},
|
|
{"late", models.Site{Status: "LATE"}, 1},
|
|
{"up", models.Site{Status: "UP"}, 2},
|
|
{"pending", models.Site{Status: "PENDING"}, 3},
|
|
{"paused up", models.Site{Status: "UP", Paused: true}, 3},
|
|
{"paused down", models.Site{Status: "DOWN", Paused: true}, 3},
|
|
}
|
|
for _, tt := range tests {
|
|
got := siteOrder(tt.site)
|
|
if got != tt.want {
|
|
t.Errorf("siteOrder(%s) = %d, want %d", tt.name, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFmtStatus(t *testing.T) {
|
|
tests := []struct {
|
|
status models.Status
|
|
paused bool
|
|
inMaint bool
|
|
wantSub string
|
|
}{
|
|
{models.StatusDown, false, false, "▼ DOWN"},
|
|
{models.StatusUp, false, false, "▲ UP"},
|
|
{models.StatusSSLExp, false, false, "▼ SSL EXP"},
|
|
{models.StatusLate, false, false, "◆ LATE"},
|
|
{models.StatusStale, false, false, "◆ STALE"},
|
|
{models.StatusPending, false, false, "○ PENDING"},
|
|
{models.StatusDown, true, false, "◇ PAUSED"},
|
|
{models.StatusDown, false, true, "◼ MAINT"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := styledModel.fmtStatus(tt.status, tt.paused, tt.inMaint)
|
|
if !containsPlain(got, tt.wantSub) {
|
|
t.Errorf("fmtStatus(%q, paused=%v, maint=%v): %q missing %q",
|
|
tt.status, tt.paused, tt.inMaint, got, tt.wantSub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFmtDuration(t *testing.T) {
|
|
tests := []struct {
|
|
d time.Duration
|
|
want string
|
|
}{
|
|
{30 * time.Second, "30s"},
|
|
{5 * time.Minute, "5m"},
|
|
{2*time.Hour + 30*time.Minute, "2h 30m"},
|
|
{2 * time.Hour, "2h"},
|
|
{25 * time.Hour, "1d 1h"},
|
|
{48 * time.Hour, "2d"},
|
|
{49 * time.Hour, "2d 1h"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := fmtDuration(tt.d)
|
|
if got != tt.want {
|
|
t.Errorf("fmtDuration(%v) = %q, want %q", tt.d, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTypeIcon(t *testing.T) {
|
|
tests := []struct {
|
|
siteType string
|
|
collapsed bool
|
|
want string
|
|
}{
|
|
{"http", false, "→"},
|
|
{"push", false, "↓"},
|
|
{"ping", false, "↔"},
|
|
{"port", false, "⊡"},
|
|
{"dns", false, "◆"},
|
|
{"group", false, "▼"},
|
|
{"group", true, "▶"},
|
|
{"unknown", false, "·"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := typeIcon(tt.siteType, tt.collapsed)
|
|
if got != tt.want {
|
|
t.Errorf("typeIcon(%q, %v) = %q, want %q", tt.siteType, tt.collapsed, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFmtUptime(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
statuses []bool
|
|
wantSub string
|
|
}{
|
|
{"empty", nil, "—"},
|
|
{"all up", []bool{true, true, true, true}, "100.0%"},
|
|
{"half", []bool{true, false, true, false}, "50.0%"},
|
|
{"all down", []bool{false, false}, "0.0%"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := styledModel.fmtUptime(tt.statuses)
|
|
if !containsPlain(got, tt.wantSub) {
|
|
t.Errorf("fmtUptime(%s): %q missing %q", tt.name, got, tt.wantSub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFmtLatency(t *testing.T) {
|
|
tests := []struct {
|
|
d time.Duration
|
|
wantSub string
|
|
}{
|
|
{0, "—"},
|
|
{50 * time.Millisecond, "50ms"},
|
|
{300 * time.Millisecond, "300ms"},
|
|
{1500 * time.Millisecond, "1.5s"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := styledModel.fmtLatency(tt.d)
|
|
if !containsPlain(got, tt.wantSub) {
|
|
t.Errorf("fmtLatency(%v): %q missing %q", tt.d, got, tt.wantSub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func containsPlain(styled, sub string) bool {
|
|
// ANSI-styled strings contain the substring somewhere
|
|
return len(styled) > 0 && contains(styled, sub)
|
|
}
|
|
|
|
func contains(s, sub string) bool {
|
|
for i := 0; i <= len(s)-len(sub); i++ {
|
|
if s[i:i+len(sub)] == sub {
|
|
return true
|
|
}
|
|
}
|
|
return len(sub) == 0
|
|
}
|