package api
import (
"errors"
"testing"
"craftdesign.group/quarry/internal/ops"
)
// rosterAccounts is a fixed roster; nothing else on AccountStore is
// reachable from the purge rules.
type rosterAccounts struct {
AccountStore
list []Account
}
func (r rosterAccounts) Accounts() ([]Account, error) { return r.list, nil }
type memPurgeGrants map[string]bool
func (m memPurgeGrants) GrantPurge(repo, account, by string) error { m[repo+"/"+account] = true; return nil }
func (m memPurgeGrants) RevokePurge(repo, account string) error { delete(m, repo+"/"+account); return nil }
func (m memPurgeGrants) HasPurgeGrant(repo, account string) bool { return m[repo+"/"+account] }
// Who may purge: a person who administers the repository; the
// repository's curator (an agent run by a site admin) only once a person
// has granted it; no other agent, a forge agent included.
func TestWhoMayPurge(t *testing.T) {
grants := memPurgeGrants{}
d := Deps{Version: "v", Repos: testRepos(wikiIndex(t)), PurgeGrants: grants, Redactions: memRedactions{},
Accounts: rosterAccounts{list: []Account{
{Name: "dan"}, {Name: "site", Admin: true}, {Name: "jan"},
{Name: "curator", Operator: "site"}, {Name: "helper", Operator: "jan"},
{Name: "zeitgeist", Operator: forgeOperator},
}}}
r := ops.NewRegistry()
if err := RegisterAll(r, d); err != nil {
t.Fatal(err)
}
may := func(who, letters string) bool { return d.mayPurge(as(who, letters), "test") }
for _, c := range []struct {
who, letters string
want bool
}{
{"dan", "a", true},
{"dan", "k5", false},
{"zeitgeist", "a", false},
{"curator", "a", false},
{"helper", "a", false},
} {
if got := may(c.who, c.letters); got != c.want {
t.Fatalf("FAIL: before any grant, %s holding %q may purge = %v, want %v", c.who, c.letters, got, c.want)
}
}
t.Log("ok: a person admin may; every agent may not")
if _, err := r.Invoke(as("curator", "a"), "purge.grant", purgeGrantIn{Repo: "test", Account: "curator"}); !errors.Is(err, ops.ErrForbidden) {
t.Fatalf("FAIL: an agent granting itself purge: err = %v", err)
}
for _, who := range []string{"helper", "zeitgeist", "jan"} {
if _, err := r.Invoke(as("dan", "a"), "purge.grant", purgeGrantIn{Repo: "test", Account: who}); !errors.Is(err, ops.ErrInvalid) {
t.Fatalf("FAIL: granting purge to non-curator %s: err = %v", who, err)
}
}
if _, err := r.Invoke(as("dan", "a"), "purge.grant", purgeGrantIn{Repo: "test", Account: "curator"}); err != nil {
t.Fatal(err)
}
if !may("curator", "a") || may("curator", "k") {
t.Fatal("FAIL: a granted curator: may purge with a, and only with a")
}
if _, err := r.Invoke(as("dan", "a"), "purge.grant", purgeGrantIn{Repo: "test", Account: "curator", Revoke: true}); err != nil {
t.Fatal(err)
}
if may("curator", "a") {
t.Fatal("FAIL: a revoked curator may still purge")
}
t.Log("ok: only a person grants; only a curator receives; revoke ends it")
// The fixture's Home has two versions: aaa3 current, aaa2 before it.
if _, err := r.Invoke(as("dan", "a"), "wiki.purge", purgeIn{Repo: "test", Name: "Home", Rev: "aaa2", Reason: "leaked token"}); !errors.Is(err, ops.ErrInvalid) {
t.Fatalf("FAIL: purging a version that was never redacted: err = %v", err)
}
if _, err := r.Invoke(as("jan", "k5"), "wiki.purge", purgeIn{Repo: "test", Name: "Home", Rev: "aaa2", Reason: "x"}); err == nil {
t.Fatal("FAIL: a non-admin purged")
}
t.Log("ok: purge refuses an unredacted version and a non-admin")
}