Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / db / entity_state_backfill_test.go
5.8 kB 181 lines
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}