Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
8.3 kB · 266 lines
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267package knotserver
import ( "context" "io" "log/slog" "path/filepath" "testing"
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/knotserver/db" "tangled.org/core/rbac")
const ( bfOwner = "did:plc:akshay" bfCollab = "did:plc:boltless" bfRepo = "did:plc:limpet")
func newBackfillEnv(t *testing.T) (*db.DB, *rbac.Enforcer) { t.Helper() dir := t.TempDir() d, err := db.Setup(context.Background(), filepath.Join(dir, "knot.db")) if err != nil { t.Fatalf("db.Setup: %v", err) } e, err := rbac.NewEnforcer(filepath.Join(dir, "rbac.db")) if err != nil { t.Fatalf("NewEnforcer: %v", err) } if err := e.AddKnot(rbac.ThisServer); err != nil { t.Fatalf("AddKnot: %v", err) } if err := e.AddKnotOwner(rbac.ThisServer, bfOwner); err != nil { t.Fatalf("AddKnotOwner: %v", err) } return d, e}
func seedCasbinRepo(t *testing.T, d *db.DB, e *rbac.Enforcer, repoDid string, collaborators ...string) { t.Helper() if err := d.StoreRepoKey(repoDid, []byte("signing"), bfOwner, "reponame"); err != nil { t.Fatalf("StoreRepoKey: %v", err) } if err := e.AddRepo(bfOwner, rbac.ThisServer, repoDid); err != nil { t.Fatalf("AddRepo: %v", err) } for _, c := range collaborators { if err := e.AddCollaborator(c, rbac.ThisServer, repoDid); err != nil { t.Fatalf("AddCollaborator %s: %v", c, err) } }}
func runBackfill(t *testing.T, d *db.DB, e *rbac.Enforcer) { t.Helper() logger := slog.New(slog.NewTextHandler(io.Discard, nil)) if err := BackfillCollaborators(context.Background(), d, e, logger, true); err != nil { t.Fatalf("BackfillCollaborators: %v", err) }}
func runMemberBackfill(t *testing.T, d *db.DB, e *rbac.Enforcer) { t.Helper() logger := slog.New(slog.NewTextHandler(io.Discard, nil)) if err := BackfillKnotMembers(context.Background(), d, e, bfOwner, logger); err != nil { t.Fatalf("BackfillKnotMembers: %v", err) }}
func TestBackfillCollaborators_FoldsCasbinAndExcludesOwner(t *testing.T) { d, e := newBackfillEnv(t) seedCasbinRepo(t, d, e, bfRepo, bfCollab)
runBackfill(t, d, e)
list, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) if err != nil { t.Fatalf("ListCollaborators: %v", err) } if len(list) != 1 { t.Fatalf("collaborators = %+v, want exactly one (owner must be excluded)", list) } if list[0].Subject != syntax.DID(bfCollab) { t.Errorf("subject = %s, want %s", list[0].Subject, bfCollab) } if list[0].AddedBy != syntax.DID(bfOwner) { t.Errorf("addedBy = %s, want owner %s", list[0].AddedBy, bfOwner) }}
func TestBackfillCollaborators_OneTimeAndNonDestructive(t *testing.T) { d, e := newBackfillEnv(t) seedCasbinRepo(t, d, e, bfRepo, bfCollab)
runBackfill(t, d, e)
if err := e.AddCollaborator("did:plc:scallop", rbac.ThisServer, bfRepo); err != nil { t.Fatalf("post-migration casbin add: %v", err) } runBackfill(t, d, e)
list, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) if err != nil { t.Fatalf("ListCollaborators: %v", err) } if len(list) != 1 { t.Fatalf("collaborators = %d, want 1; backfill must run once and never resurrect later casbin state", len(list)) } if list[0].Subject != syntax.DID(bfCollab) { t.Errorf("subject = %s, want original %s preserved", list[0].Subject, bfCollab) }}
func TestBackfillCollaborators_LeavesMembersUntouched(t *testing.T) { d, e := newBackfillEnv(t) seedCasbinRepo(t, d, e, bfRepo, bfCollab)
owner := syntax.DID(bfOwner) member := syntax.DID("did:plc:whelk") if err := db.AddKnotMemberDirect(d, owner, member); err != nil { t.Fatalf("seed member: %v", err) } if err := e.AddKnotMember(rbac.ThisServer, member.String()); err != nil { t.Fatalf("seed member acl: %v", err) }
runBackfill(t, d, e)
members, _, err := db.ListKnotMembers(d, db.ListPage{Limit: db.ListMaxLimit}) if err != nil { t.Fatalf("ListKnotMembers: %v", err) } if len(members) != 1 || members[0].Subject != member { t.Fatalf("members = %+v, want the seeded member preserved", members) }
collabs, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) if err != nil { t.Fatalf("ListCollaborators: %v", err) } if len(collabs) != 1 || collabs[0].Subject != syntax.DID(bfCollab) { t.Errorf("collaborators = %+v, want only the casbin collaborator", collabs) }}
func TestBackfillCollaborators_UnmarkedRunDefersMarker(t *testing.T) { d, e := newBackfillEnv(t) seedCasbinRepo(t, d, e, bfRepo, bfCollab)
logger := slog.New(slog.NewTextHandler(io.Discard, nil)) if err := BackfillCollaborators(context.Background(), d, e, logger, false); err != nil { t.Fatalf("unmarked backfill: %v", err) }
list, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) if err != nil { t.Fatalf("ListCollaborators: %v", err) } if len(list) != 1 { t.Fatalf("collaborators = %d, want 1 after unmarked run", len(list)) } applied, err := d.IsMigrationApplied(collaboratorBackfillMigration) if err != nil { t.Fatalf("IsMigrationApplied: %v", err) } if applied { t.Fatal("unmarked run must not write the migration marker") }
if err := e.AddCollaborator("did:plc:scallop", rbac.ThisServer, bfRepo); err != nil { t.Fatalf("late casbin add: %v", err) } runBackfill(t, d, e)
list, _, err = db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) if err != nil { t.Fatalf("ListCollaborators after marked run: %v", err) } if len(list) != 2 { t.Errorf("collaborators = %d, want 2; the marked rerun must fold late casbin state", len(list)) } if applied, _ := d.IsMigrationApplied(collaboratorBackfillMigration); !applied { t.Error("marked run did not write the migration marker") }}
func TestBackfillKnotMembers_FoldsCasbinAndExcludesOwner(t *testing.T) { d, e := newBackfillEnv(t) member := syntax.DID("did:plc:whelk") if err := e.AddKnotMember(rbac.ThisServer, member.String()); err != nil { t.Fatalf("seed casbin member: %v", err) }
runMemberBackfill(t, d, e)
members, _, err := db.ListKnotMembers(d, db.ListPage{Limit: db.ListMaxLimit}) if err != nil { t.Fatalf("ListKnotMembers: %v", err) } if len(members) != 1 { t.Fatalf("members = %+v, want exactly one; the owner must be excluded", members) } if members[0].Subject != member { t.Errorf("subject = %s, want %s", members[0].Subject, member) } if members[0].Did != syntax.DID(bfOwner) { t.Errorf("added by = %s, want owner %s", members[0].Did, bfOwner) }}
func TestBackfillKnotMembers_OneTimeAndNonDestructive(t *testing.T) { d, e := newBackfillEnv(t) member := syntax.DID("did:plc:whelk") if err := e.AddKnotMember(rbac.ThisServer, member.String()); err != nil { t.Fatalf("seed casbin member: %v", err) }
runMemberBackfill(t, d, e)
if err := e.AddKnotMember(rbac.ThisServer, "did:plc:scallop"); err != nil { t.Fatalf("post-migration casbin add: %v", err) } runMemberBackfill(t, d, e)
members, _, err := db.ListKnotMembers(d, db.ListPage{Limit: db.ListMaxLimit}) if err != nil { t.Fatalf("ListKnotMembers: %v", err) } if len(members) != 1 || members[0].Subject != member { t.Fatalf("members = %+v, want only the original member; backfill must run once", members) } if applied, _ := d.IsMigrationApplied(knotMemberBackfillMigration); !applied { t.Error("member backfill did not write its migration marker") }}
func TestBackfillCollaborators_EmptyMarksApplied(t *testing.T) { d, e := newBackfillEnv(t) seedCasbinRepo(t, d, e, bfRepo)
runBackfill(t, d, e)
applied, err := d.IsMigrationApplied(collaboratorBackfillMigration) if err != nil { t.Fatalf("IsMigrationApplied: %v", err) } if !applied { t.Fatal("migration not marked applied after a zero-collaborator backfill; it would re-scan every boot") }
if err := e.AddCollaborator(bfCollab, rbac.ThisServer, bfRepo); err != nil { t.Fatalf("post-migration casbin add: %v", err) } runBackfill(t, d, e)
list, _, err := db.ListCollaborators(d, syntax.DID(bfRepo), db.ListPage{Limit: db.ListMaxLimit}) if err != nil { t.Fatalf("ListCollaborators: %v", err) } if len(list) != 0 { t.Errorf("collaborators = %d, want 0; an applied migration must not fold later casbin state", len(list)) }}