Skip to content
quarry Artifact [2646402efd]
Login

Artifact [2646402efd]

Artifact 2646402efd6239c0e699783d2c2a5995531a1d3470be8d55ca986269a2be522a:


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