package api
import (
"context"
"errors"
"path/filepath"
"testing"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
func agentRig(t *testing.T) (*ops.Registry, *oltp.DB) {
t.Helper()
db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
for _, who := range []string{"dmestas", "jva", "zeitgeist", "mallory"} {
if err := db.AddAccount(who, "a-long-enough-password", who == "dmestas"); err != nil {
t.Fatal(err)
}
}
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(wikiIndex(t)),
Teams: db, Accounts: acctAdapter{db},
CapsFor: func(a, repo string) CapSet { return db.EffectiveCaps(a, repo) }}); err != nil {
t.Fatal(err)
}
return r, db
}
// Declaring is never the dangerous direction; hiding always is. So an
// account may always say "I am a machine", and only a site admin may
// say otherwise or take the mark off.
func TestAnAccountMayDeclareItselfAnAgentButNotUndeclare(t *testing.T) {
r, db := agentRig(t)
as := func(who string) context.Context { return ops.WithUser(context.Background(), who) }
// zeitgeist declares itself, naming itself as the operator.
if _, err := r.Invoke(as("zeitgeist"), "account.agent",
accountAgentIn{Name: "zeitgeist", Operator: "zeitgeist"}); err != nil {
t.Fatalf("FAIL: an account could not declare itself: %v", err)
}
if db.Operator("zeitgeist") != "zeitgeist" {
t.Error("FAIL: the declaration did not stick")
} else {
t.Log("ok: an account may declare itself an agent")
}
// And may not take it back off.
if _, err := r.Invoke(as("zeitgeist"), "account.agent",
accountAgentIn{Name: "zeitgeist"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: an agent unmarked itself: %v", err)
} else {
t.Log("ok: an agent may not undeclare itself")
}
// Nor name somebody else as answerable for it — that is a claim
// about a person, not about yourself.
if _, err := r.Invoke(as("zeitgeist"), "account.agent",
accountAgentIn{Name: "zeitgeist", Operator: "dmestas"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: an agent named somebody else as its operator: %v", err)
} else {
t.Log("ok: naming another person as operator needs an admin")
}
// A stranger may not mark anybody.
if _, err := r.Invoke(as("mallory"), "account.agent",
accountAgentIn{Name: "jva", Operator: "mallory"}); !errors.Is(err, ops.ErrForbidden) {
t.Errorf("FAIL: a stranger marked another account: %v", err)
} else {
t.Log("ok: marking somebody else needs an admin")
}
// The site admin may do both directions, for anybody.
if _, err := r.Invoke(as("dmestas"), "account.agent",
accountAgentIn{Name: "jva", Operator: "dmestas"}); err != nil {
t.Fatalf("FAIL: the admin could not mark jva: %v", err)
}
if db.Operator("jva") != "dmestas" {
t.Error("FAIL: the admin's mark did not stick")
} else {
t.Log("ok: an admin may mark another account, naming who answers for it")
}
if _, err := r.Invoke(as("dmestas"), "account.agent", accountAgentIn{Name: "jva"}); err != nil {
t.Fatalf("FAIL: the admin could not clear the mark: %v", err)
}
if db.Operator("jva") != "" {
t.Error("FAIL: the admin's clear did not stick")
} else {
t.Log("ok: an admin may clear a mark")
}
}
// An operator nobody can be asked about is the thing this exists to
// end, so the name has to be an account.
func TestAnOperatorMustBeSomebodyYouCanAsk(t *testing.T) {
r, _ := agentRig(t)
_, err := r.Invoke(ops.WithUser(context.Background(), "dmestas"), "account.agent",
accountAgentIn{Name: "jva", Operator: "nobody-here"})
if err == nil {
t.Error("FAIL: an operator who is not an account was accepted")
} else {
t.Logf("ok: refused — %v", err)
}
}