Skip to content
quarry Artifact [c9286baf24]
Login

Artifact [c9286baf24]

Artifact c9286baf24ff12d25e7cf936c278f30fa48aaa224f7ad313ab40300a151e663c:


package oltp

import (
	"errors"
	"strings"
	"testing"
)

// setupRig is the agent rig with dmestas holding the admin letter on
// priv, so a pushing agent set up there is the repository's own.
func setupRig(t *testing.T) *DB {
	t.Helper()
	db := agentRig(t)
	if err := db.Grant("dmestas", "priv", RepoAdmin); err != nil {
		t.Fatal(err)
	}
	return db
}

func pushSetup(name string) AgentSetup {
	return AgentSetup{Name: name, Approver: "dmestas", Operator: "repo:priv", Repos: []string{"priv"},
		Caps: Write.Caps(), RefuseClip: true, PerPerson: -1,
		Token: TokenSpec{Name: "setup", Repos: []string{"priv"}, Scopes: []string{ScopeSync}, Ceiling: Write.Caps()}}
}

func readSetup(name string, repos ...string) AgentSetup {
	return AgentSetup{Name: name, Approver: "jva", Operator: "jva", Repos: repos, Caps: Read.Caps(), PerPerson: -1,
		Token: TokenSpec{Name: "setup", Repos: repos, Scopes: []string{ScopeAPI}, Ceiling: SetupReadCeiling}}
}

// held is everything a setup writes under name: the account row, the
// grants and the tokens, and whether the name is retired.
func held(t *testing.T, db *DB, name string) (acct bool, grants, tokens int, retired bool) {
	t.Helper()
	_, acct = db.AccountByName(name)
	if err := db.db.QueryRow(`SELECT count(*) FROM repo_caps WHERE account = ?`, name).Scan(&grants); err != nil {
		t.Fatal(err)
	}
	if err := db.db.QueryRow(`SELECT count(*) FROM token WHERE account = ?`, name).Scan(&tokens); err != nil {
		t.Fatal(err)
	}
	return acct, grants, tokens, db.Tombstoned(name)
}

// RFC 0030 step 3: an approved setup makes an agent, a grant and a token
// whose scopes are sync and whose repositories are the one named.
func TestASetupMakesTheAgentItsGrantAndItsTokenTogether(t *testing.T) {
	db := setupRig(t)
	res, err := db.SetupAgent(pushSetup("priv-push"))
	if err != nil {
		t.Fatal(err)
	}
	a, ok := db.AccountByName("priv-push")
	if !ok || a.Operator != "repo:priv" || a.Answerable != "dmestas" {
		t.Fatalf("FAIL: the agent: %+v ", a)
	}
	if stored, _ := db.CapsOf("priv-push", "priv"); stored != Write.Caps() {
		t.Errorf("FAIL: the grant is %q", stored)
	}
	toks, _ := db.Tokens("priv-push")
	if len(toks) != 1 || strings.Join(toks[0].Scopes, ",") != ScopeSync || strings.Join(toks[0].Repos, ",") != "priv" || toks[0].Ceiling != Write.Caps() {
		t.Fatalf("FAIL: the token: %+v", toks)
	}
	if res.Value == "" || toks[0].Expires != "" {
		t.Errorf("FAIL: the value or the expiry: %q %q", res.Value, toks[0].Expires)
	}
	// A person's read client: the approver's own, inherit off, scoped to
	// the one repository it reads, api only.
	if _, err := db.SetupAgent(readSetup("e-one", "pub")); err != nil {
		t.Fatal(err)
	}
	e, _ := db.AccountByName("e-one")
	if e.Operator != "jva" || e.Inherit {
		t.Errorf("FAIL: the read client: operator=%q inherit=%v", e.Operator, e.Inherit)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A failure at any step leaves nothing: no account row, no grant, no
// token and no retired name. Each failure point is forced, and the same
// name then sets up cleanly.
func TestAFailedSetupLeavesNothingAtEveryStep(t *testing.T) {
	for _, step := range []string{"account", "grant", "mint"} {
		t.Run(step, func(t *testing.T) {
			db := setupRig(t)
			setupFault = func(s string) error {
				if s == step {
					return errors.New("disk I/O error")
				}
				return nil
			}
			defer func() { setupFault = nil }()
			if _, err := db.SetupAgent(readSetup("e-two", "pub", "priv")); err == nil {
				t.Fatalf("FAIL: a setup forced to fail at %s succeeded", step)
			}
			setupFault = nil
			if acct, grants, tokens, retired := held(t, db, "e-two"); acct || grants != 0 || tokens != 0 || retired {
				t.Errorf("FAIL: after a failure at %s: account=%v grants=%d tokens=%d retired=%v", step, acct, grants, tokens, retired)
			}
			if _, err := db.SetupAgent(readSetup("e-two", "pub")); err != nil {
				t.Errorf("FAIL: the name was not free after a failure at %s: %v", step, err)
			}
			if !t.Failed() {
				t.Log("ok")
			}
		})
	}
}

// A grant the store itself refuses, after the account row and one grant
// were written, rolls all of it back: the approver cannot reach priv.
func TestASetupWhoseSecondGrantIsRefusedLeavesNoAccountRow(t *testing.T) {
	db := setupRig(t)
	_, err := db.SetupAgent(readSetup("e-three", "pub", "priv"))
	var refusal Refusal
	if !errors.As(err, &refusal) {
		t.Fatalf("FAIL: the unreachable repository was not refused: %v", err)
	}
	if acct, grants, tokens, retired := held(t, db, "e-three"); acct || grants != 0 || tokens != 0 || retired {
		t.Errorf("FAIL: left behind: account=%v grants=%d tokens=%d retired=%v", acct, grants, tokens, retired)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A person's pushing agent is clipped to the approver's letters, and a
// clip that takes anything from the role refuses the whole setup.
func TestAPushSetupThatTheApproversLettersWouldClipIsRefused(t *testing.T) {
	db := setupRig(t)
	// jva holds reader on pub only; developer would be cut.
	s := pushSetup("pub-push")
	s.Approver, s.Operator, s.Scope, s.Repos = "jva", "jva", "repo:pub", []string{"pub"}
	s.Token.Repos = []string{"pub"}
	_, err := db.SetupAgent(s)
	var refusal Refusal
	if !errors.As(err, &refusal) {
		t.Fatalf("FAIL: a clipped push setup was not refused: %v", err)
	}
	if acct, grants, tokens, retired := held(t, db, "pub-push"); acct || grants != 0 || tokens != 0 || retired {
		t.Errorf("FAIL: left behind: account=%v grants=%d tokens=%d retired=%v", acct, grants, tokens, retired)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// Setup makes a new agent only: a name that is already an account, in
// any case, a person or an agent, or that was one, is refused, and what
// that account held is untouched.
func TestASetupRefusesAnyNameThatIsAlreadyAnAccount(t *testing.T) {
	db := setupRig(t)
	if _, err := db.SetupAgent(readSetup("e-four", "pub")); err != nil {
		t.Fatal(err)
	}
	before, _ := db.CapsOf("e-four", "pub")
	for _, name := range []string{"e-four", "E-Four", "dmestas", "DMestas", "dmestas-coder", "root"} {
		s := readSetup(name, "pub")
		s.Caps = Write.Caps()
		if _, err := db.SetupAgent(s); err == nil {
			t.Errorf("FAIL: a setup took the existing account %q", name)
		}
	}
	if got, _ := db.CapsOf("e-four", "pub"); got != before {
		t.Errorf("FAIL: the existing agent's grant changed to %q", got)
	}
	if toks, _ := db.Tokens("e-four"); len(toks) != 1 {
		t.Errorf("FAIL: the existing agent holds %d tokens", len(toks))
	}
	if err := db.DeleteAccount("e-four"); err != nil {
		t.Fatal(err)
	}
	if _, err := db.SetupAgent(readSetup("E-FOUR", "pub")); err == nil {
		t.Errorf("FAIL: a retired name was taken, in another case")
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// RetractSetup takes a committed setup back: the agent is gone and its
// name is free, not retired.
func TestRetractingASetupRemovesTheAgentAndFreesTheName(t *testing.T) {
	db := setupRig(t)
	res, err := db.SetupAgent(pushSetup("priv-push"))
	if err != nil {
		t.Fatal(err)
	}
	if err := db.RetractSetup("priv-push", res.Token.ID); err != nil {
		t.Fatal(err)
	}
	if acct, grants, tokens, retired := held(t, db, "priv-push"); acct || grants != 0 || tokens != 0 || retired {
		t.Errorf("FAIL: left behind: account=%v grants=%d tokens=%d retired=%v", acct, grants, tokens, retired)
	}
	if _, err := db.SetupAgent(pushSetup("priv-push")); err != nil {
		t.Errorf("FAIL: the name was not free: %v", err)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}