package tui import ( "path/filepath" "strings" "testing" tea "charm.land/bubbletea/v2" "lazyntfy/internal/config" "lazyntfy/internal/history" "lazyntfy/internal/ntfy" "lazyntfy/internal/store" ) func sample() Model { m := New(config.App{ DefaultHost: "https://ntfy.example", Auth: config.Auth{Token: "tk_test"}, Subs: []config.Sub{{Origin: "https://ntfy.example", Name: "alerts", Auth: config.Auth{Token: "tk_test"}}}, }, nil, ntfy.NewClient(), nil, nil) m.width = 120 m.height = 30 m.store.Put(store.Message{ID: "new", Time: 200, Topic: "alerts", Title: "DiskFull", Body: `{"status":"firing","commonLabels":{"alertname":"DiskFull","severity":"critical"}}`}) m.store.Put(store.Message{ID: "old", Time: 100, Topic: "alerts", Body: "backup finished"}) m = m.ensureSelection() return m } func TestViewShowsNewestAlert(t *testing.T) { view := sample().render() if !strings.Contains(view, "lazyntfy") || !strings.Contains(view, "DiskFull") || !strings.Contains(view, "firing") { t.Fatalf("%s", view) } lines := strings.Split(view, "\n") if len(lines) != 30 { t.Fatalf("height %d", len(lines)) } } func TestMoveKeepsSelectionByID(t *testing.T) { m := sample() if m.selected["alerts"] != "new" { t.Fatalf("selected %s", m.selected["alerts"]) } m = m.move(1) if m.selected["alerts"] != "old" { t.Fatalf("after down %s", m.selected["alerts"]) } next, cmd := m.Update(tea.KeyPressMsg{Text: "q", Code: 'q'}) if cmd == nil { t.Fatal("expected quit") } if next.(Model).selected["alerts"] != "old" { t.Fatal("quit changed selection") } } func TestFilterAndDelete(t *testing.T) { m := sample() m.filter = "backup" m = m.ensureSelection() if m.selected["alerts"] != "old" { t.Fatalf("filter selected %s", m.selected["alerts"]) } if !strings.Contains(m.render(), "backup finished") { t.Fatal(m.render()) } m = m.apply(ntfy.Notice{Event: &ntfy.Event{ID: "old", Event: "message_delete", Topic: "alerts"}}) if len(m.store.List("alerts")) != 1 { t.Fatal("delete") } m = m.apply(ntfy.Notice{Event: &ntfy.Event{Event: "message_clear", Topic: "alerts"}}) if len(m.store.List("alerts")) != 0 { t.Fatal("clear") } } func TestPublishRequiresMessage(t *testing.T) { m := sample() m.mode = modePublish m.formIdx = 3 got, _ := m.updatePublish(tea.KeyPressMsg{Text: "enter", Code: tea.KeyEnter}) if got.(Model).status != "message is empty" { t.Fatalf("status %q", got.(Model).status) } } func TestAddTopicUsesDefaultHost(t *testing.T) { m := sample() m.addInput.SetValue("backups") m = m.submitAdd() if len(m.subs) != 2 || m.subs[1].Name != "backups" || m.subs[1].Origin != "https://ntfy.example" { t.Fatalf("%+v", m.subs) } if m.subs[1].Auth.Token != "tk_test" { t.Fatal("auth not copied") } } func TestRejectsNonHTTPClick(t *testing.T) { if err := validateURL("file:///etc/passwd"); err == nil { t.Fatal("expected reject") } if err := validateURL("https://example.com/a"); err != nil { t.Fatal(err) } } func TestRestoresMessagesAndTopicFromHistory(t *testing.T) { db, err := history.Open(filepath.Join(t.TempDir(), "history.db")) if err != nil { t.Fatal(err) } defer db.Close() if err := db.SaveMessage("https://ntfy.example", store.Message{ ID: "h1", Topic: "alerts", Time: 50, Title: "from disk", }); err != nil { t.Fatal(err) } if err := db.SetState("topic", "alerts"); err != nil { t.Fatal(err) } m := New(config.App{ DefaultHost: "https://ntfy.example", Subs: []config.Sub{{Origin: "https://ntfy.example", Name: "alerts"}}, }, nil, ntfy.NewClient(), nil, db) if m.restored != 1 || len(m.store.List("alerts")) != 1 { t.Fatalf("restored %d messages %d", m.restored, len(m.store.List("alerts"))) } if m.savedTopic != "alerts" || m.currentName() != "alerts" { t.Fatalf("saved %q current %q", m.savedTopic, m.currentName()) } m.width, m.height = 120, 30 if !strings.Contains(m.render(), "from disk") { t.Fatalf("restored message missing from view:\n%s", m.render()) } }