package events
import (
"testing"
"craftdesign.group/quarry/internal/evt"
)
// ForgetRepo takes a deleted repository's attention events, its event
// log and its traffic tallies, so a repository created later under the
// name inherits none of them — and a neighbour whose name merely starts
// with the deleted one's keeps all of its own (quarry#294).
func TestForgetRepoTakesOnlyThatRepository(t *testing.T) {
s := testServer(t)
const gone, dotted, kept = "gone", "gone.x", "kept"
for _, repo := range []string{gone, dotted, kept} {
for _, who := range []string{"ann", "bob"} {
if _, err := s.Attend(who, AttentionEvent{Kind: "mention", Repo: repo, Link: "/r/" + repo}); err != nil {
t.Fatal(err)
}
}
if err := s.Raise(evt.TicketClosed, repo, map[string]any{"repo": repo}); err != nil {
t.Fatal(err)
}
for _, name := range []string{"clone", "clone.auth.ann", "clone.client.zit.v1", "fetch.anon"} {
if err := s.Count(repo, name, 1); err != nil {
t.Fatal(err)
}
}
}
if err := s.ForgetRepo(gone); err != nil {
t.Fatal(err)
}
for _, who := range []string{"ann", "bob"} {
evs, err := s.RecentAttention(who, 50)
if err != nil {
t.Fatal(err)
}
held := map[string]int{}
for _, e := range evs {
held[e.Repo]++
}
if held[gone] != 0 {
t.Errorf("%s still holds %d attention events for the deleted repository", who, held[gone])
}
if held[dotted] != 1 || held[kept] != 1 {
t.Errorf("%s lost a neighbour's attention events: %v", who, held)
}
}
logged, err := s.Recent(gone, 50)
if err != nil {
t.Fatal(err)
}
for _, l := range logged {
if l.Repo == gone {
t.Errorf("the deleted repository's event log survived: %+v", l)
}
}
for _, repo := range []string{dotted, kept} {
if l, err := s.Recent(repo, 50); err != nil || len(l) == 0 {
t.Errorf("%s's event log went with %s's: %d, %v", repo, gone, len(l), err)
}
}
for _, fam := range []string{"clone", "fetch"} {
if tot, err := s.Totals(gone, fam); err != nil || len(tot) != 0 {
t.Errorf("%s's %s tallies survived: %v, %v", gone, fam, tot, err)
}
}
if n, _ := s.Total(gone, "clone"); n != 0 {
t.Errorf("%s's clone total survived: %d", gone, n)
}
for _, repo := range []string{dotted, kept} {
if n, _ := s.Total(repo, "clone"); n != 1 {
t.Errorf("%s's clone total = %d, want 1", repo, n)
}
if tot, _ := s.Totals(repo, "clone"); tot["auth.ann"] != 1 {
t.Errorf("%s's clone tallies went too: %v", repo, tot)
}
}
}
// A store re-added under the same project code finds no publisher
// cursor, publish record or fold mark of the deleted one: a surviving
// cursor over an emptied index would skip the census (quarry#294).
func TestForgetStoreTakesItsCursorKeys(t *testing.T) {
s := testServer(t)
kv, err := s.Cursors()
if err != nil {
t.Fatal(err)
}
const code, other = "aaaa1111", "aaaa11112"
keys := []string{code, "pub." + code, "seen.indexer-" + code, "seen.indexer-" + code + "-0123456789ab", other, "pub." + other}
for _, k := range keys {
if _, err := kv.Put(k, []byte("{}")); err != nil {
t.Fatal(err)
}
}
if err := s.ForgetStore(code); err != nil {
t.Fatal(err)
}
for _, k := range keys[:4] {
if _, err := kv.Get(k); err == nil {
t.Errorf("%s survived", k)
}
}
for _, k := range keys[4:] {
if _, err := kv.Get(k); err != nil {
t.Errorf("%s went with %s: %v", k, code, err)
}
}
}
// A scan that gets short batches off a large stream still finds every
// message: it ends when the consumer has nothing pending, not on the
// first batch under the size asked for.
func TestForgetRepoSurvivesShortBatches(t *testing.T) {
s := testServer(t)
old := forgetBatch
forgetBatch = 3
t.Cleanup(func() { forgetBatch = old })
for i := 0; i < 20; i++ {
if _, err := s.Attend("ann", AttentionEvent{Kind: "mention", Repo: "gone", Link: "/r/gone"}); err != nil {
t.Fatal(err)
}
}
if err := s.ForgetRepo("gone"); err != nil {
t.Fatal(err)
}
evs, err := s.RecentAttention("ann", 50)
if err != nil {
t.Fatal(err)
}
if len(evs) != 0 {
t.Errorf("%d events survived", len(evs))
}
}