package cmd import ( "testing" "time" ) func TestParseDuration(t *testing.T) { tests := []struct { input string expected time.Duration wantErr bool }{ {"1h", time.Hour, false}, {"30m", 30 * time.Minute, false}, {"24h", 24 * time.Hour, false}, {"1d", 24 * time.Hour, false}, {"7d", 7 * 24 * time.Hour, false}, {"2s", 2 * time.Second, false}, {"invalid", 0, true}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { got, err := parseDuration(tt.input) if tt.wantErr { if err == nil { t.Errorf("parseDuration(%q) expected error, got nil", tt.input) } return } if err != nil { t.Errorf("parseDuration(%q) unexpected error: %v", tt.input, err) return } if got != tt.expected { t.Errorf("parseDuration(%q) = %v, want %v", tt.input, got, tt.expected) } }) } } func TestExtractAuthorName(t *testing.T) { tests := []struct { input string expected string }{ {"gastown/crew/joe", "joe"}, {"gastown/polecats/toast", "toast"}, {"mayor", "mayor"}, {"gastown/witness", "witness"}, {"", ""}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { got := extractAuthorName(tt.input) if got != tt.expected { t.Errorf("extractAuthorName(%q) = %q, want %q", tt.input, got, tt.expected) } }) } } func TestMatchesActor(t *testing.T) { tests := []struct { name string actor string expected bool }{ // Exact matches {"joe", "joe", true}, {"Joe", "joe", true}, // Case insensitive {"JOE", "joe", true}, // Actor as path, name as simple name {"joe", "gastown/crew/joe", true}, {"Joe", "gastown/crew/joe", true}, // Partial matches {"joe-session1", "joe", true}, {"gastown-joe", "joe", true}, // Non-matches {"bob", "joe", false}, {"", "joe", false}, {"witness", "gastown/crew/joe", false}, } for _, tt := range tests { t.Run(tt.name+"_"+tt.actor, func(t *testing.T) { got := matchesActor(tt.name, tt.actor) if got != tt.expected { t.Errorf("matchesActor(%q, %q) = %v, want %v", tt.name, tt.actor, got, tt.expected) } }) } } func TestParseBeadsTimestamp(t *testing.T) { tests := []struct { input string expected string // Format: "2006-01-02 15:04" isZero bool }{ {"2025-12-30T16:19:00Z", "2025-12-30 16:19", false}, {"2025-12-30 16:19", "2025-12-30 16:19", false}, {"2025-12-30", "2025-12-30 00:00", false}, {"invalid", "", true}, {"", "", true}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { got := parseBeadsTimestamp(tt.input) if tt.isZero { if !got.IsZero() { t.Errorf("parseBeadsTimestamp(%q) expected zero time, got %v", tt.input, got) } return } gotStr := got.Format("2006-01-02 15:04") if gotStr != tt.expected { t.Errorf("parseBeadsTimestamp(%q) = %q, want %q", tt.input, gotStr, tt.expected) } }) } } func TestFormatSource(t *testing.T) { // Just verify it doesn't panic and returns non-empty strings sources := []string{"git", "beads", "townlog", "events", "unknown"} for _, s := range sources { result := formatSource(s) if result == "" { t.Errorf("formatSource(%q) returned empty string", s) } } } func TestFormatType(t *testing.T) { // Just verify it doesn't panic and returns non-empty strings types := []string{"commit", "bead_created", "bead_closed", "spawn", "done", "handoff", "crash", "kill", "merged", "merge_failed", "unknown"} for _, typ := range types { result := formatType(typ) if result == "" { t.Errorf("formatType(%q) returned empty string", typ) } } }