feat(history): persist topics and messages in SQLite
Store topics, messages, and small UI preferences in $XDG_DATA_HOME/lazyntfy/history.db. Each topic keeps its newest 1000 messages, expired rows are pruned on startup, and credentials are never written. Saves use a single transaction and prune only when a topic is over the cap.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
package history
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"lazyntfy/internal/config"
|
||||
"lazyntfy/internal/store"
|
||||
)
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "history.db")
|
||||
db, err := Open(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.SaveTopic(config.Sub{Origin: "https://ntfy.example", Name: "alerts", Display: "Alerts"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg := store.Message{
|
||||
ID: "m1", Topic: "alerts", Time: 20, Title: "Disk", Body: "full",
|
||||
Tags: []string{"ops"}, Priority: 5, Click: "https://example.com",
|
||||
Attachment: &store.Attachment{Name: "a.png", URL: "https://example.com/a.png", Size: 4},
|
||||
}
|
||||
if err := db.SaveMessage("https://ntfy.example", msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.SaveMessage("https://ntfy.example", store.Message{ID: "m0", Topic: "alerts", Time: 10, Body: "older"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.SetState("topic", "alerts"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db, err = Open(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
topics, err := db.Topics()
|
||||
if err != nil || len(topics) != 1 || topics[0].Display != "Alerts" || topics[0].Auth.Token != "" {
|
||||
t.Fatalf("%v %+v", err, topics)
|
||||
}
|
||||
got, err := db.Messages("alerts", 10)
|
||||
if err != nil || len(got) != 2 || got[0].ID != "m1" || got[0].Tags[0] != "ops" || got[0].Attachment.Name != "a.png" {
|
||||
t.Fatalf("%v %+v", err, got)
|
||||
}
|
||||
if name, ok := db.State("topic"); !ok || name != "alerts" {
|
||||
t.Fatalf("state %q %v", name, ok)
|
||||
}
|
||||
if err := db.DeleteMessage("m0"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.ClearTopic("alerts"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
left, err := db.Messages("alerts", 10)
|
||||
if err != nil || len(left) != 0 {
|
||||
t.Fatalf("%v %+v", err, left)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPruneExpiredOnOpen(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "history.db")
|
||||
db, err := Open(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
for _, m := range []store.Message{
|
||||
{ID: "live", Topic: "t", Time: 1},
|
||||
{ID: "gone", Topic: "t", Time: 2, Expires: now - 60},
|
||||
{ID: "later", Topic: "t", Time: 3, Expires: now + 3600},
|
||||
} {
|
||||
if err := db.SaveMessage("o", m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := db.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db, err = Open(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
got, err := db.Messages("t", 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 2 || got[0].ID != "later" || got[1].ID != "live" {
|
||||
t.Fatalf("kept %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopicCapKeepsNewest(t *testing.T) {
|
||||
db, err := Open(filepath.Join(t.TempDir(), "history.db"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
for i := 0; i < maxPerTopic+5; i++ {
|
||||
msg := store.Message{ID: fmt.Sprintf("m%d", i), Topic: "t", Time: int64(i)}
|
||||
if err := db.SaveMessage("o", msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
got, err := db.Messages("t", maxPerTopic+100)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != maxPerTopic {
|
||||
t.Fatalf("kept %d, want %d", len(got), maxPerTopic)
|
||||
}
|
||||
if got[0].ID != fmt.Sprintf("m%d", maxPerTopic+4) {
|
||||
t.Fatalf("newest is %s", got[0].ID)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user