package spindle import ( "testing" "time" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/spindle/config" "tangled.org/core/spindle/db" ) const pausedOwner = syntax.DID("did:plc:thespindleowner") // enroll runs before seed, a repo only gets served if its owner is already admitted func newClosedPushEnv(t *testing.T, asAdmin bool, enroll func(*db.DB) error) *pushEnv { t.Helper() w := newWorld(t) w.admission = config.AdmissionClosed if !asAdmin { w.owner = pausedOwner } repo := w.serveGitRepo(testRepoDid) n := w.boot() if enroll != nil { if err := enroll(n.s.db); err != nil { t.Fatal(err) } // the allowlist xrpc tracks the owner too, or hydrant never backfills them n.s.backfill.Track(testOwner) } w.seed(n, testBob) return &pushEnv{w: w, n: n, repo: repo} } func listOwner(d *db.DB) error { return db.AddToAllowlist(d, testOwner) } func inviteOwner(d *db.DB) error { code, _, err := d.MintInvite("did:plc:someadmin", time.Now()) if err != nil { return err } return d.RedeemInvite(code, testOwner, time.Now()) } func (e *pushEnv) allow(t *testing.T) { t.Helper() if err := db.AddToAllowlist(e.n.s.db, testOwner); err != nil { t.Fatal(err) } } func (e *pushEnv) disallow(t *testing.T) { t.Helper() if err := db.RemoveFromAllowlist(e.n.s.db, testOwner); err != nil { t.Fatal(err) } } func (e *pushEnv) pushNext(t *testing.T) string { t.Helper() uri, err := e.notify(e.n.s, e.firstPush(t)) if err != nil { t.Fatalf("notify: %v", err) } return string(uri) } func TestRemovedFromTheAllowlistPausesTheOwnersRepos(t *testing.T) { e := newClosedPushEnv(t, false, listOwner) addTestSecret(t, e.n.s, testRepoDid) if e.pushNext(t) == "" { t.Fatal("setup: an admitted owner's push made no pipeline") } e.disallow(t) if uri := e.pushNext(t); uri != "" { t.Fatalf("a push of an owner who was removed from the allow-list made pipeline %q", uri) } e.wantPipelines(t, 1, "after the owner was removed") if got := secretCount(t, e.n.s, testRepoDid); got != 1 { t.Fatalf("%d secrets after the owner was removed, want the 1 that was there", got) } if len(repoRows(t, e.n.s, testRepoDid)) != 1 { t.Fatal("the repo row went away with the allow-list entry") } e.allow(t) if e.pushNext(t) == "" { t.Fatal("a push made no pipeline after the owner was added back") } e.wantPipelines(t, 2, "after the owner was added back") } func TestOwnerEnrolledByInviteIsPausedAndResumedToo(t *testing.T) { e := newClosedPushEnv(t, false, inviteOwner) if e.pushNext(t) == "" { t.Fatal("setup: an owner enrolled by invite made no pipeline") } e.disallow(t) if uri := e.pushNext(t); uri != "" { t.Fatalf("a push of an invited owner who was removed made pipeline %q", uri) } e.allow(t) if e.pushNext(t) == "" { t.Fatal("a push made no pipeline after the invited owner was added back") } } func TestAdminOwnerIsNeverPaused(t *testing.T) { e := newClosedPushEnv(t, true, nil) if e.pushNext(t) == "" { t.Fatal("an admin owner who isn't on the allow-list made no pipeline under closed admission") } } func TestOpeningAdmissionResumesAPausedOwner(t *testing.T) { e := newClosedPushEnv(t, false, listOwner) e.disallow(t) if uri := e.pushNext(t); uri != "" { t.Fatalf("an owner who isn't admitted made pipeline %q", uri) } // admission is read at boot, opening it takes a restart e.n.stop() e.w.admission = config.AdmissionOpen e.n = e.w.boot() if e.pushNext(t) == "" { t.Fatal("a push made no pipeline after admission opened") } }