package spindle import ( "context" "slices" "testing" "time" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/api/org_tangled" "tangled.org/core/spindle/db" "tangled.org/core/spindle/models" ) func ticketRecord(state string) map[string]any { return map[string]any{ "$type": org_tangled.TrackTicketNSID, "state": state, "title": map[string]any{"$type": "org.tangled.markup.markdown#inline", "text": "fix the thing"}, "createdAt": "2026-02-01T00:00:00Z", "targetBranch": "main", } } const syncedTicket = "3kticket00001" type ticketSync struct { t *testing.T env *ensureEnv repo *db.Repo } func newTicketSync(t *testing.T) *ticketSync { env := newEnsureEnv(t) env.serve("repo") return &ticketSync{t: t, env: env, repo: env.ensure(t)} } func (s *ticketSync) put(state string, editor syntax.DID) { s.env.knot.putVersion(testRepoDid, org_tangled.TrackTicketNSID, syncedTicket, editor, ticketRecord(state)) } func (s *ticketSync) sync() error { _, _, err := s.env.s.syncPullTicket(context.Background(), s.env.k, s.repo, syncedTicket) return err } func (s *ticketSync) mustSync() { s.t.Helper() if err := s.sync(); err != nil { s.t.Fatal(err) } } func (s *ticketSync) events() []string { s.t.Helper() due, err := s.env.s.db.DuePullEvents(testRepoDid, time.Now(), 100) if err != nil { s.t.Fatal(err) } var out []string for _, ev := range due { out = append(out, string(ev.Event)+" by "+ev.Sender.String()) } return out } func TestEveryStateChangeBetweenTwoSyncsFiresInOrder(t *testing.T) { s := newTicketSync(t) s.put("open", testBob) s.mustSync() s.put("closed", testCarol) s.put("open", testBob) s.mustSync() want := []string{ string(models.WebhookEventPullRequestClosed) + " by " + testCarol.String(), string(models.WebhookEventPullRequestReopened) + " by " + testBob.String(), } if got := s.events(); !slices.Equal(got, want) { t.Fatalf("pull events = %v, want %v", got, want) } s.mustSync() if got := s.events(); len(got) != 2 { t.Fatalf("a sync with nothing new left events %v, want the same two", got) } } func TestAFailedVersionListDoesNotSkipTheStatesInBetween(t *testing.T) { s := newTicketSync(t) s.put("open", testBob) s.mustSync() s.put("closed", testCarol) s.put("open", testBob) s.env.knot.failVersions = true if err := s.sync(); err == nil { t.Fatal("a sync whose version list failed reported success, so its mark would be dropped") } s.env.knot.failVersions = false s.mustSync() if got := s.events(); len(got) != 2 { t.Fatalf("pull events = %v, want the closed and the reopened that the failed sync must not skip", got) } }