package main import ( "context" "errors" "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/kami/maven/internal/ipc" ) // eventsCore serves a canned intake journal. Embedding // ipc.UnimplementedCoreAPI means any other call fails loudly. type eventsCore struct { ipc.UnimplementedCoreAPI events []ipc.IntakeEvent err error gotN int } func (c *eventsCore) RecentEvents(_ context.Context, n int) ([]ipc.IntakeEvent, error) { c.gotN = n return c.events, c.err } func getEvents(t *testing.T, core ipc.CoreAPI) *httptest.ResponseRecorder { t.Helper() w := httptest.NewRecorder() handleEvents(w, httptest.NewRequest(http.MethodGet, "/events", nil), core) return w } func TestEventsPageRendersTheJournal(t *testing.T) { core := &eventsCore{events: []ipc.IntakeEvent{ {Source: "rss:tech", Kind: "note", Title: "Вышло ядро 6.19", Priority: "low", OccurredAt: time.Date(2026, 8, 1, 7, 15, 0, 0, time.UTC)}, {Source: "ambient:notif", Kind: "fact", Title: "calendar_event_20260801_планёрка", Body: "10:00-11:00 планёрка", Priority: "low", OccurredAt: time.Date(2026, 8, 1, 10, 0, 0, 0, time.UTC)}, }} w := getEvents(t, core) if w.Code != http.StatusOK { t.Fatalf("status = %d, want 200", w.Code) } body := w.Body.String() for _, want := range []string{"rss:tech", "Вышло ядро 6.19", "ambient:notif", "10:00-11:00 планёрка", "01.08 10:00:00"} { if !strings.Contains(body, want) { t.Errorf("page does not mention %q", want) } } if core.gotN != eventsPageLimit { t.Errorf("asked core for %d events, want %d", core.gotN, eventsPageLimit) } } func TestEventsPageSaysNothingArrived(t *testing.T) { w := getEvents(t, &eventsCore{}) if w.Code != http.StatusOK { t.Fatalf("status = %d, want 200", w.Code) } if !strings.Contains(w.Body.String(), "nothing has arrived yet") { t.Error("empty journal did not render the empty-state line") } } func TestEventsPageReportsAReadFailure(t *testing.T) { // An unreachable journal must say so rather than render an empty table, // which would imply nothing arrived. w := getEvents(t, &eventsCore{err: errors.New("core is down")}) if w.Code != http.StatusOK { t.Fatalf("status = %d, want 200 with the error rendered", w.Code) } body := w.Body.String() if !strings.Contains(body, "journal unavailable") || !strings.Contains(body, "core is down") { t.Errorf("page did not report the read failure: %s", body) } if strings.Contains(body, "nothing has arrived yet") { t.Error("a failed read rendered as an empty journal") } } func TestEventsPageWithoutCore(t *testing.T) { w := getEvents(t, nil) if w.Code != http.StatusServiceUnavailable { t.Errorf("status = %d, want 503", w.Code) } } func TestEventsPageEscapesIntakeText(t *testing.T) { // Titles come from outside — a feed headline, a notification. They are shown // on a page and must never be able to inject markup into it. core := &eventsCore{events: []ipc.IntakeEvent{{ Source: "rss:x", Kind: "note", Priority: "low", Title: ``, OccurredAt: time.Date(2026, 8, 1, 7, 0, 0, 0, time.UTC), }}} body := getEvents(t, core).Body.String() if strings.Contains(body, "") { t.Error("intake title was not escaped") } if !strings.Contains(body, "<script>") { t.Error("intake title is missing from the page entirely") } }