package storage import "testing" func TestPushSubscriptionLifecycle(t *testing.T) { setupTestDB(t) if err := AddPushSubscription("sub-1", "https://push.example/ep-a", "p256-a", "auth-a"); err != nil { t.Fatal(err) } // A second endpoint for the same user (e.g. a second device). if err := AddPushSubscription("sub-1", "https://push.example/ep-b", "p256-b", "auth-b"); err != nil { t.Fatal(err) } // A different user. if err := AddPushSubscription("sub-2", "https://push.example/ep-c", "p256-c", "auth-c"); err != nil { t.Fatal(err) } subs, err := ListPushSubscriptions() if err != nil { t.Fatal(err) } if len(subs) != 3 { t.Fatalf("got %d subscriptions, want 3", len(subs)) } // Re-subscribing the same endpoint updates keys in place, not a new row. if err := AddPushSubscription("sub-1", "https://push.example/ep-a", "p256-a2", "auth-a2"); err != nil { t.Fatal(err) } subs, _ = ListPushSubscriptions() if len(subs) != 3 { t.Fatalf("after re-subscribe got %d rows, want 3 (upsert, not insert)", len(subs)) } var epA PushSubscription for _, s := range subs { if s.Endpoint == "https://push.example/ep-a" { epA = s } } if epA.P256dh != "p256-a2" || epA.Auth != "auth-a2" { t.Errorf("re-subscribe did not refresh keys: %+v", epA) } if err := RemovePushSubscription("https://push.example/ep-a"); err != nil { t.Fatal(err) } subs, _ = ListPushSubscriptions() if len(subs) != 2 { t.Fatalf("after remove got %d rows, want 2", len(subs)) } } func TestTouchPushSubscriptionAdvancesWatermark(t *testing.T) { setupTestDB(t) if err := AddPushSubscription("sub-1", "https://push.example/ep", "p", "a"); err != nil { t.Fatal(err) } subs, _ := ListPushSubscriptions() orig := subs[0].LastNotifiedAt want := orig + 5000 if err := TouchPushSubscription("https://push.example/ep", want); err != nil { t.Fatal(err) } subs, _ = ListPushSubscriptions() if subs[0].LastNotifiedAt != want { t.Errorf("watermark = %d, want %d", subs[0].LastNotifiedAt, want) } } func TestNewClassifiedSince(t *testing.T) { setupTestDB(t) now := nowUnix() insert := func(guid, headline, source, channel string, classified bool, seenAt int64) { if err := InsertStory(&Story{ GUID: guid, Headline: headline, ArticleURL: "https://x/" + guid, Source: source, Channel: channel, Classified: classified, SeenAt: seenAt, }); err != nil { t.Fatal(err) } } insert("old", "Old news", "Feed A", "tech", true, now-1000) insert("fresh1", "Fresh one", "Feed A", "tech", true, now-100) insert("fresh2", "Fresh two", "Feed B", "gaming", true, now-50) insert("unclassified", "Pending", "Feed A", "", false, now-10) insert("discarded", "Junk", "Feed A", "_discarded", true, now-10) got, err := NewClassifiedSince(now-500, 10) if err != nil { t.Fatal(err) } if len(got) != 2 { t.Fatalf("got %d stories, want 2 (fresh classified, non-discarded, after watermark)", len(got)) } // Newest first. if got[0].GUID != "" && got[0].Headline != "Fresh two" { t.Errorf("first result = %q, want newest 'Fresh two'", got[0].Headline) } if got[0].Source != "Feed B" || got[1].Source != "Feed A" { t.Errorf("unexpected order/sources: %q then %q", got[0].Source, got[1].Source) } }