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")
}
}