package retention import ( "testing" "time" "fiatjaf.com/nostr" "fiatjaf.com/nostr/eventstore/slicestore" ) func mkStore(t *testing.T) *slicestore.SliceStore { t.Helper() s := &slicestore.SliceStore{} if err := s.Init(); err != nil { t.Fatalf("init: %v", err) } return s } func save(t *testing.T, s *slicestore.SliceStore, sk nostr.SecretKey, kind nostr.Kind, createdAt nostr.Timestamp) nostr.ID { t.Helper() e := nostr.Event{Kind: kind, CreatedAt: createdAt, Tags: nostr.Tags{{"h", "g"}}} if kind == nostr.KindSimpleGroupChatMessage { e.Content = "msg" } if err := e.Sign(sk); err != nil { t.Fatalf("sign: %v", err) } if err := s.SaveEvent(e); err != nil { t.Fatalf("save: %v", err) } return e.ID } func count(s *slicestore.SliceStore, kinds ...nostr.Kind) int { n := 0 for range s.QueryEvents(nostr.Filter{Kinds: kinds}, 1_000_000) { n++ } return n } func TestRetentionPrunesOldChatByAge(t *testing.T) { s := mkStore(t) sk := nostr.Generate() now := time.Unix(1_700_000_000, 0) old := nostr.Timestamp(now.Add(-48 * time.Hour).Unix()) fresh := nostr.Timestamp(now.Add(-1 * time.Hour).Unix()) save(t, s, sk, nostr.KindSimpleGroupChatMessage, old) save(t, s, sk, nostr.KindSimpleGroupChatMessage, fresh) deleted := Sweep(s, Config{MaxAge: 24 * time.Hour}, now) if deleted != 1 { t.Fatalf("expected 1 pruned, got %d", deleted) } if c := count(s, nostr.KindSimpleGroupChatMessage); c != 1 { t.Fatalf("expected 1 chat remaining, got %d", c) } } func TestRetentionPrunesSurplusByCount(t *testing.T) { s := mkStore(t) sk := nostr.Generate() now := time.Unix(1_700_000_000, 0) for i := 0; i < 5; i++ { save(t, s, sk, nostr.KindSimpleGroupChatMessage, nostr.Timestamp(1_700_000_000+int64(i))) } deleted := Sweep(s, Config{MaxMessages: 2}, now) if deleted != 3 { t.Fatalf("expected 3 pruned, got %d", deleted) } if c := count(s, nostr.KindSimpleGroupChatMessage); c != 2 { t.Fatalf("expected 2 chats remaining, got %d", c) } } func TestRetentionNeverPrunesManagementEvents(t *testing.T) { s := mkStore(t) sk := nostr.Generate() now := time.Unix(1_700_000_000, 0) ancient := nostr.Timestamp(now.Add(-1000 * time.Hour).Unix()) // Very old management + metadata events must be preserved. save(t, s, sk, nostr.KindSimpleGroupCreateGroup, ancient) save(t, s, sk, nostr.KindSimpleGroupPutUser, ancient) save(t, s, sk, nostr.KindSimpleGroupJoinRequest, ancient) save(t, s, sk, nostr.KindSimpleGroupMetadata, ancient) save(t, s, sk, nostr.KindSimpleGroupAdmins, ancient) save(t, s, sk, nostr.KindSimpleGroupMembers, ancient) // An old chat that SHOULD be pruned. save(t, s, sk, nostr.KindSimpleGroupChatMessage, ancient) deleted := Sweep(s, Config{MaxAge: time.Hour, MaxMessages: 1}, now) if deleted != 1 { t.Fatalf("expected only the 1 chat pruned, got %d", deleted) } // All 6 management/metadata events survive. mgmt := count(s, nostr.KindSimpleGroupCreateGroup, nostr.KindSimpleGroupPutUser, nostr.KindSimpleGroupJoinRequest, nostr.KindSimpleGroupMetadata, nostr.KindSimpleGroupAdmins, nostr.KindSimpleGroupMembers) if mgmt != 6 { t.Fatalf("management events must never be pruned; have %d/6", mgmt) } } func TestRetentionDisabledIsNoop(t *testing.T) { s := mkStore(t) sk := nostr.Generate() now := time.Unix(1_700_000_000, 0) save(t, s, sk, nostr.KindSimpleGroupChatMessage, nostr.Timestamp(1)) if d := Sweep(s, Config{}, now); d != 0 { t.Fatalf("no limits should prune nothing, got %d", d) } }