forked from
tangled.org/core
Monorepo for Tangled
1package db
2
3import (
4 "context"
5 "testing"
6
7 "github.com/bluesky-social/indigo/atproto/syntax"
8 "github.com/samber/lo"
9 "tangled.org/core/appview/models"
10 "tangled.org/core/orm"
11)
12
13func TestColumnOnlyClosedSubjectsForOwner(t *testing.T) {
14 d := newTestDB(t)
15 owner := syntax.DID("did:plc:akshay")
16 repo := seedRepo(t, d, string(owner), "knot.example", "anemone", "anemone", "did:plc:anemone")
17
18 closedIssue := seedIssue(t, d, repo, "did:plc:boltless", "issueClosed")
19 seedIssue(t, d, repo, "did:plc:boltless", "issueOpen")
20 recordedIssue := seedIssue(t, d, repo, "did:plc:boltless", "issueRecorded")
21
22 if err := CloseIssues(d, orm.FilterEq("at_uri", closedIssue.AtUri())); err != nil {
23 t.Fatalf("CloseIssues closed: %v", err)
24 }
25 if err := CloseIssues(d, orm.FilterEq("at_uri", recordedIssue.AtUri())); err != nil {
26 t.Fatalf("CloseIssues recorded: %v", err)
27 }
28 putIssueStateRec(t, d, issueRec("did:plc:boltless", "srec", recordedIssue.AtUri(), models.StateClosed, 100))
29
30 emptyRkey := seedIssue(t, d, repo, "did:plc:boltless", "")
31 if err := CloseIssues(d, orm.FilterEq("at_uri", emptyRkey.AtUri())); err != nil {
32 t.Fatalf("CloseIssues empty rkey: %v", err)
33 }
34
35 mergedPull := seedPull(t, d, repo, "did:plc:boltless", "pullMerged")
36 closedPull := seedPull(t, d, repo, "did:plc:boltless", "pullClosed")
37 seedPull(t, d, repo, "did:plc:boltless", "pullOpen")
38
39 if err := MergePulls(d, orm.FilterEq("at_uri", mergedPull.AtUri())); err != nil {
40 t.Fatalf("MergePulls: %v", err)
41 }
42 if err := ClosePulls(d, orm.FilterEq("at_uri", closedPull.AtUri())); err != nil {
43 t.Fatalf("ClosePulls: %v", err)
44 }
45
46 subjects, err := ColumnOnlyClosedSubjectsForOwner(context.Background(), d, owner)
47 if err != nil {
48 t.Fatalf("ColumnOnlyClosedSubjectsForOwner: %v", err)
49 }
50
51 got := lo.SliceToMap(subjects, func(s BackfillSubject) (syntax.ATURI, models.StateValue) {
52 return s.Subject, s.Value
53 })
54
55 if len(got) != 3 {
56 t.Fatalf("got %d subjects, want 3: %+v", len(got), got)
57 }
58 if got[closedIssue.AtUri()] != models.StateClosed {
59 t.Fatalf("closed issue: got %q want closed", got[closedIssue.AtUri()])
60 }
61 if got[mergedPull.AtUri()] != models.StateMerged {
62 t.Fatalf("merged pull: got %q want merged", got[mergedPull.AtUri()])
63 }
64 if got[closedPull.AtUri()] != models.StateClosed {
65 t.Fatalf("closed pull: got %q want closed", got[closedPull.AtUri()])
66 }
67 if _, ok := got[emptyRkey.AtUri()]; ok {
68 t.Fatal("closed issue with empty rkey must be excluded")
69 }
70}
71
72func mustEnqueueBackfill(t *testing.T, d *DB) {
73 t.Helper()
74 if _, err := EnqueueEntityStateBackfill(context.Background(), d); err != nil {
75 t.Fatalf("EnqueueEntityStateBackfill: %v", err)
76 }
77}
78
79func markBackfillDone(t *testing.T, d *DB) {
80 t.Helper()
81 if _, err := d.Exec(
82 `update pds_migration set status = 'done' where name = ?`, EntityStateBackfillName,
83 ); err != nil {
84 t.Fatalf("mark done: %v", err)
85 }
86}
87
88func TestEnqueueEntityStateBackfill(t *testing.T) {
89 owner := syntax.DID("did:plc:akshay")
90 for _, tc := range []struct {
91 name string
92 arrange func(t *testing.T, d *DB, issue *models.Issue)
93 wantRows int64
94 wantNoRow bool
95 wantStatus models.PDSMigrationStatus
96 }{
97 {
98 name: "enqueues owner with column-only closed work",
99 arrange: func(t *testing.T, d *DB, issue *models.Issue) {},
100 wantRows: 1,
101 wantStatus: models.PDSMigrationStatusPending,
102 },
103 {
104 name: "idempotent while still pending",
105 arrange: func(t *testing.T, d *DB, issue *models.Issue) { mustEnqueueBackfill(t, d) },
106 wantRows: 0,
107 wantStatus: models.PDSMigrationStatusPending,
108 },
109 {
110 name: "re-arms completed owner with fresh work",
111 arrange: func(t *testing.T, d *DB, issue *models.Issue) {
112 mustEnqueueBackfill(t, d)
113 markBackfillDone(t, d)
114 },
115 wantRows: 1,
116 wantStatus: models.PDSMigrationStatusPending,
117 },
118 {
119 name: "leaves settled owner done",
120 arrange: func(t *testing.T, d *DB, issue *models.Issue) {
121 mustEnqueueBackfill(t, d)
122 putIssueStateRec(t, d, issueRec(string(owner), "srec", issue.AtUri(), models.StateClosed, 100))
123 markBackfillDone(t, d)
124 },
125 wantRows: 0,
126 wantStatus: models.PDSMigrationStatusDone,
127 },
128 {
129 name: "skips owner whose closed work is already recorded",
130 arrange: func(t *testing.T, d *DB, issue *models.Issue) {
131 putIssueStateRec(t, d, issueRec(string(owner), "srec", issue.AtUri(), models.StateClosed, 100))
132 },
133 wantRows: 0,
134 wantNoRow: true,
135 },
136 } {
137 t.Run(tc.name, func(t *testing.T) {
138 d := newTestDB(t)
139 repo := seedRepo(t, d, string(owner), "knot.example", "anemone", "anemone", "did:plc:anemone")
140 issue := seedIssue(t, d, repo, "did:plc:boltless", "issue1")
141 if err := CloseIssues(d, orm.FilterEq("at_uri", issue.AtUri())); err != nil {
142 t.Fatalf("CloseIssues: %v", err)
143 }
144 tc.arrange(t, d, issue)
145
146 n, err := EnqueueEntityStateBackfill(context.Background(), d)
147 if err != nil {
148 t.Fatalf("EnqueueEntityStateBackfill: %v", err)
149 }
150 if n != tc.wantRows {
151 t.Fatalf("enqueue affected %d rows, want %d", n, tc.wantRows)
152 }
153
154 if tc.wantNoRow {
155 var count int
156 if err := d.QueryRow(
157 `select count(*) from pds_migration where name = ?`, EntityStateBackfillName,
158 ).Scan(&count); err != nil {
159 t.Fatalf("query: %v", err)
160 }
161 if count != 0 {
162 t.Fatalf("owner with no column-only closed work produced %d rows, want 0", count)
163 }
164 return
165 }
166
167 var did, status string
168 if err := d.QueryRow(
169 `select did, status from pds_migration where name = ?`, EntityStateBackfillName,
170 ).Scan(&did, &status); err != nil {
171 t.Fatalf("query: %v", err)
172 }
173 if syntax.DID(did) != owner {
174 t.Fatalf("row did = %s, want owner %s", did, owner)
175 }
176 if status != string(tc.wantStatus) {
177 t.Fatalf("row status = %s, want %s", status, tc.wantStatus)
178 }
179 })
180 }
181}