1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
package activity
import (
"os"
"path/filepath"
"testing"
"time"
)
// calSample matches the cal-events helper's output shape (ISO-8601 with
// offset, one object per non-all-day event, tagged with its calendar name).
const calSample = `[
{"title": "Standup", "start": "2026-07-07T09:00:00-05:00", "end": "2026-07-07T09:15:00-05:00", "calendar": "Archer"},
{"title": "Dentist", "start": "2026-07-07T13:00:00-05:00", "end": "2026-07-07T14:00:00-05:00", "calendar": "Personal"},
{"title": "Focus block", "start": "2026-07-07T15:00:00-05:00", "end": "2026-07-07T17:00:00-05:00", "calendar": "Focus"}
]`
func TestParseEventsSample(t *testing.T) {
events := parseEvents([]byte(calSample), CalendarFilter{})
if len(events) != 3 {
t.Fatalf("want 3 events, got %d", len(events))
}
if events[0].Title != "Standup" || events[0].Calendar != "Archer" {
t.Errorf("event0 = %+v", events[0])
}
wantStart, _ := time.Parse(time.RFC3339, "2026-07-07T09:00:00-05:00")
if !events[0].Start.Equal(wantStart) {
t.Errorf("start = %v, want %v", events[0].Start, wantStart)
}
if parseEvents([]byte("not json"), CalendarFilter{}) != nil {
t.Error("bad output ⇒ empty slice")
}
}
func TestCalendarFilterAllow(t *testing.T) {
cases := []struct {
name string
filter CalendarFilter
cal string
want bool
}{
{"empty filter passes all", CalendarFilter{}, "Personal", true},
{"show allowlist keeps listed", CalendarFilter{Show: []string{"Archer"}}, "Archer", true},
{"show allowlist drops unlisted", CalendarFilter{Show: []string{"Archer"}}, "Personal", false},
{"show is case-insensitive", CalendarFilter{Show: []string{"archer"}}, "Archer", true},
{"hide drops listed", CalendarFilter{Hide: []string{"Holidays"}}, "Holidays", false},
{"hide is case-insensitive", CalendarFilter{Hide: []string{"holidays"}}, "Holidays", false},
{"hide applies after show", CalendarFilter{Show: []string{"Archer", "Focus"}, Hide: []string{"Focus"}}, "Focus", false},
{"show+hide keeps the rest", CalendarFilter{Show: []string{"Archer", "Focus"}, Hide: []string{"Focus"}}, "Archer", true},
}
for _, c := range cases {
if got := c.filter.Allow(c.cal); got != c.want {
t.Errorf("%s: Allow(%q)=%v, want %v", c.name, c.cal, got, c.want)
}
}
}
func TestParseEventsAppliesFilter(t *testing.T) {
events := parseEvents([]byte(calSample), CalendarFilter{Show: []string{"Archer", "Focus"}})
if len(events) != 2 || events[0].Calendar != "Archer" || events[1].Calendar != "Focus" {
t.Fatalf("filtered = %+v", events)
}
}
func TestLoadCalendarDegradesGracefully(t *testing.T) {
if ev, err := LoadCalendar("", "2026-07-07", CalendarFilter{}); err != nil || ev != nil {
t.Errorf("empty bin ⇒ nil, nil; got %v, %v", ev, err)
}
if ev, err := LoadCalendar("/nonexistent/cal-events", "2026-07-07", CalendarFilter{}); err != nil || ev != nil {
t.Errorf("missing helper ⇒ nil, nil; got %v, %v", ev, err)
}
}
func TestLoadCalendarRunsHelper(t *testing.T) {
// Stand in for cal-events with a shell script that echoes the sample.
bin := filepath.Join(t.TempDir(), "cal-events")
script := "#!/bin/sh\ncat <<'EOF'\n" + calSample + "\nEOF\n"
if err := os.WriteFile(bin, []byte(script), 0o755); err != nil {
t.Fatal(err)
}
events, err := LoadCalendar(bin, "2026-07-07", CalendarFilter{Hide: []string{"Personal"}})
if err != nil {
t.Fatal(err)
}
if len(events) != 2 {
t.Fatalf("want 2 events after hide, got %+v", events)
}
}
|