Monorepo for Tangled tangled.org
2

Configure Feed

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

at icy/ytnwlw 8.3 kB View raw
1package knotserver 2 3import ( 4 "context" 5 "io" 6 "log/slog" 7 "path/filepath" 8 "testing" 9 10 "github.com/bluesky-social/indigo/atproto/syntax" 11 12 "tangled.org/core/knotserver/db" 13 "tangled.org/core/rbac" 14) 15 16const ( 17 bfOwner = "did:plc:akshay" 18 bfCollab = "did:plc:boltless" 19 bfRepo = "did:plc:limpet" 20) 21 22func newBackfillEnv(t *testing.T) (*db.DB, *rbac.Enforcer) { 23 t.Helper() 24 dir := t.TempDir() 25 d, err := db.Setup(context.Background(), filepath.Join(dir, "knot.db")) 26 if err != nil { 27 t.Fatalf("db.Setup: %v", err) 28 } 29 e, err := rbac.NewEnforcer(filepath.Join(dir, "rbac.db")) 30 if err != nil { 31 t.Fatalf("NewEnforcer: %v", err) 32 } 33 if err := e.AddKnot(rbac.ThisServer); err != nil { 34 t.Fatalf("AddKnot: %v", err) 35 } 36 if err := e.AddKnotOwner(rbac.ThisServer, bfOwner); err != nil { 37 t.Fatalf("AddKnotOwner: %v", err) 38 } 39 return d, e 40} 41 42func seedCasbinRepo(t *testing.T, d *db.DB, e *rbac.Enforcer, repoDid string, collaborators ...string) { 43 t.Helper() 44 if err := d.StoreRepoKey(repoDid, []byte("signing"), bfOwner, "reponame"); err != nil { 45 t.Fatalf("StoreRepoKey: %v", err) 46 } 47 if err := e.AddRepo(bfOwner, rbac.ThisServer, repoDid); err != nil { 48 t.Fatalf("AddRepo: %v", err) 49 } 50 for _, c := range collaborators { 51 if err := e.AddCollaborator(c, rbac.ThisServer, repoDid); err != nil { 52 t.Fatalf("AddCollaborator %s: %v", c, err) 53 } 54 } 55} 56 57func runBackfill(t *testing.T, d *db.DB, e *rbac.Enforcer) { 58 t.Helper() 59 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 60 if err := BackfillCollaborators(context.Background(), d, e, logger, true); err != nil { 61 t.Fatalf("BackfillCollaborators: %v", err) 62 } 63} 64 65func runMemberBackfill(t *testing.T, d *db.DB, e *rbac.Enforcer) { 66 t.Helper() 67 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 68 if err := BackfillKnotMembers(context.Background(), d, e, bfOwner, logger); err != nil { 69 t.Fatalf("BackfillKnotMembers: %v", err) 70 } 71} 72 73func TestBackfillCollaborators_FoldsCasbinAndExcludesOwner(t *testing.T) { 74 d, e := newBackfillEnv(t) 75 seedCasbinRepo(t, d, e, bfRepo, bfCollab) 76 77 runBackfill(t, d, e) 78 79 list, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) 80 if err != nil { 81 t.Fatalf("ListCollaborators: %v", err) 82 } 83 if len(list) != 1 { 84 t.Fatalf("collaborators = %+v, want exactly one (owner must be excluded)", list) 85 } 86 if list[0].Subject != syntax.DID(bfCollab) { 87 t.Errorf("subject = %s, want %s", list[0].Subject, bfCollab) 88 } 89 if list[0].AddedBy != syntax.DID(bfOwner) { 90 t.Errorf("addedBy = %s, want owner %s", list[0].AddedBy, bfOwner) 91 } 92} 93 94func TestBackfillCollaborators_OneTimeAndNonDestructive(t *testing.T) { 95 d, e := newBackfillEnv(t) 96 seedCasbinRepo(t, d, e, bfRepo, bfCollab) 97 98 runBackfill(t, d, e) 99 100 if err := e.AddCollaborator("did:plc:scallop", rbac.ThisServer, bfRepo); err != nil { 101 t.Fatalf("post-migration casbin add: %v", err) 102 } 103 runBackfill(t, d, e) 104 105 list, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) 106 if err != nil { 107 t.Fatalf("ListCollaborators: %v", err) 108 } 109 if len(list) != 1 { 110 t.Fatalf("collaborators = %d, want 1; backfill must run once and never resurrect later casbin state", len(list)) 111 } 112 if list[0].Subject != syntax.DID(bfCollab) { 113 t.Errorf("subject = %s, want original %s preserved", list[0].Subject, bfCollab) 114 } 115} 116 117func TestBackfillCollaborators_LeavesMembersUntouched(t *testing.T) { 118 d, e := newBackfillEnv(t) 119 seedCasbinRepo(t, d, e, bfRepo, bfCollab) 120 121 owner := syntax.DID(bfOwner) 122 member := syntax.DID("did:plc:whelk") 123 if err := db.AddKnotMemberDirect(d, owner, member); err != nil { 124 t.Fatalf("seed member: %v", err) 125 } 126 if err := e.AddKnotMember(rbac.ThisServer, member.String()); err != nil { 127 t.Fatalf("seed member acl: %v", err) 128 } 129 130 runBackfill(t, d, e) 131 132 members, _, err := db.ListKnotMembers(d, db.ListPage{Limit: db.ListMaxLimit}) 133 if err != nil { 134 t.Fatalf("ListKnotMembers: %v", err) 135 } 136 if len(members) != 1 || members[0].Subject != member { 137 t.Fatalf("members = %+v, want the seeded member preserved", members) 138 } 139 140 collabs, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) 141 if err != nil { 142 t.Fatalf("ListCollaborators: %v", err) 143 } 144 if len(collabs) != 1 || collabs[0].Subject != syntax.DID(bfCollab) { 145 t.Errorf("collaborators = %+v, want only the casbin collaborator", collabs) 146 } 147} 148 149func TestBackfillCollaborators_UnmarkedRunDefersMarker(t *testing.T) { 150 d, e := newBackfillEnv(t) 151 seedCasbinRepo(t, d, e, bfRepo, bfCollab) 152 153 logger := slog.New(slog.NewTextHandler(io.Discard, nil)) 154 if err := BackfillCollaborators(context.Background(), d, e, logger, false); err != nil { 155 t.Fatalf("unmarked backfill: %v", err) 156 } 157 158 list, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) 159 if err != nil { 160 t.Fatalf("ListCollaborators: %v", err) 161 } 162 if len(list) != 1 { 163 t.Fatalf("collaborators = %d, want 1 after unmarked run", len(list)) 164 } 165 applied, err := d.IsMigrationApplied(collaboratorBackfillMigration) 166 if err != nil { 167 t.Fatalf("IsMigrationApplied: %v", err) 168 } 169 if applied { 170 t.Fatal("unmarked run must not write the migration marker") 171 } 172 173 if err := e.AddCollaborator("did:plc:scallop", rbac.ThisServer, bfRepo); err != nil { 174 t.Fatalf("late casbin add: %v", err) 175 } 176 runBackfill(t, d, e) 177 178 list, _, err = db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) 179 if err != nil { 180 t.Fatalf("ListCollaborators after marked run: %v", err) 181 } 182 if len(list) != 2 { 183 t.Errorf("collaborators = %d, want 2; the marked rerun must fold late casbin state", len(list)) 184 } 185 if applied, _ := d.IsMigrationApplied(collaboratorBackfillMigration); !applied { 186 t.Error("marked run did not write the migration marker") 187 } 188} 189 190func TestBackfillKnotMembers_FoldsCasbinAndExcludesOwner(t *testing.T) { 191 d, e := newBackfillEnv(t) 192 member := syntax.DID("did:plc:whelk") 193 if err := e.AddKnotMember(rbac.ThisServer, member.String()); err != nil { 194 t.Fatalf("seed casbin member: %v", err) 195 } 196 197 runMemberBackfill(t, d, e) 198 199 members, _, err := db.ListKnotMembers(d, db.ListPage{Limit: db.ListMaxLimit}) 200 if err != nil { 201 t.Fatalf("ListKnotMembers: %v", err) 202 } 203 if len(members) != 1 { 204 t.Fatalf("members = %+v, want exactly one; the owner must be excluded", members) 205 } 206 if members[0].Subject != member { 207 t.Errorf("subject = %s, want %s", members[0].Subject, member) 208 } 209 if members[0].Did != syntax.DID(bfOwner) { 210 t.Errorf("added by = %s, want owner %s", members[0].Did, bfOwner) 211 } 212} 213 214func TestBackfillKnotMembers_OneTimeAndNonDestructive(t *testing.T) { 215 d, e := newBackfillEnv(t) 216 member := syntax.DID("did:plc:whelk") 217 if err := e.AddKnotMember(rbac.ThisServer, member.String()); err != nil { 218 t.Fatalf("seed casbin member: %v", err) 219 } 220 221 runMemberBackfill(t, d, e) 222 223 if err := e.AddKnotMember(rbac.ThisServer, "did:plc:scallop"); err != nil { 224 t.Fatalf("post-migration casbin add: %v", err) 225 } 226 runMemberBackfill(t, d, e) 227 228 members, _, err := db.ListKnotMembers(d, db.ListPage{Limit: db.ListMaxLimit}) 229 if err != nil { 230 t.Fatalf("ListKnotMembers: %v", err) 231 } 232 if len(members) != 1 || members[0].Subject != member { 233 t.Fatalf("members = %+v, want only the original member; backfill must run once", members) 234 } 235 if applied, _ := d.IsMigrationApplied(knotMemberBackfillMigration); !applied { 236 t.Error("member backfill did not write its migration marker") 237 } 238} 239 240func TestBackfillCollaborators_EmptyMarksApplied(t *testing.T) { 241 d, e := newBackfillEnv(t) 242 seedCasbinRepo(t, d, e, bfRepo) 243 244 runBackfill(t, d, e) 245 246 applied, err := d.IsMigrationApplied(collaboratorBackfillMigration) 247 if err != nil { 248 t.Fatalf("IsMigrationApplied: %v", err) 249 } 250 if !applied { 251 t.Fatal("migration not marked applied after a zero-collaborator backfill; it would re-scan every boot") 252 } 253 254 if err := e.AddCollaborator(bfCollab, rbac.ThisServer, bfRepo); err != nil { 255 t.Fatalf("post-migration casbin add: %v", err) 256 } 257 runBackfill(t, d, e) 258 259 list, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) 260 if err != nil { 261 t.Fatalf("ListCollaborators: %v", err) 262 } 263 if len(list) != 0 { 264 t.Errorf("collaborators = %d, want 0; an applied migration must not fold later casbin state", len(list)) 265 } 266}