Skip to content
quarry Artifact [50f2e67ddd]
Login

Artifact [50f2e67ddd]

Artifact 50f2e67ddd6631aeb88d704e4655e0014fcb91ae533f4a5ba30a6b7fdc7c4aca:


package api

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"sync"
	"testing"

	"craftdesign.group/quarry/internal/ci"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
)

type logRow struct{ repo, actor, via, field, was, now string }

type settingRecorder struct {
	mu   sync.Mutex
	rows []logRow
}

func (s *settingRecorder) LogSetting(repo, actor, via, field, was, now string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.rows = append(s.rows, logRow{repo, actor, via, field, was, now})
	return nil
}

type workerEngine struct {
	mu      sync.Mutex
	tokens  []ci.WorkerToken
	revoked []string
	groups  []string
	mints   int
}

func (e *workerEngine) WorkerTokens() ([]ci.WorkerToken, error) {
	e.mu.Lock()
	defer e.mu.Unlock()
	return append([]ci.WorkerToken(nil), e.tokens...), nil
}
func (e *workerEngine) MintWorkerToken(label string, prefixes, groups []string) (ci.WorkerToken, error) {
	e.mu.Lock()
	defer e.mu.Unlock()
	e.groups = groups
	e.mints++
	t := ci.WorkerToken{ID: fmt.Sprintf("new-%d", e.mints), Token: "s3cret-bearer-value", Label: label, WorkerGroups: groups,
		TaskTypePrefixes: prefixes, CreatedAt: "2026-11-12T10:00:00Z"}
	stored := t
	stored.Token = "" // the engine never lists a bearer
	e.tokens = append(e.tokens, stored)
	return t, nil
}
func (e *workerEngine) RevokeWorkerToken(id string) error {
	e.mu.Lock()
	defer e.mu.Unlock()
	e.revoked = append(e.revoked, id)
	return nil
}

func workerTokenRegistry(t *testing.T, eng *workerEngine) *ops.Registry {
	t.Helper()
	r := ops.NewRegistry()
	caps := func(account, repo string) CapSet {
		if account == "dan" {
			return oltp.CapSet("a")
		}
		return oltp.CapSet("3")
	}
	if err := RegisterAll(r, Deps{Version: "v", WorkerTokens: eng, CapsFor: caps,
		Repos: NewRepos(map[string]*Repo{"alpha": {Index: wikiIndex(t)}, "beta": {Index: wikiIndex(t)}})}); err != nil {
		t.Fatal(err)
	}
	return r
}

// A mint answers with the value once; the list never carries it nor
// anything that could be replayed; and the token is scoped to the
// repository's own group.
func TestWorkerTokenMintAnswersOnceAndListNeverShowsIt(t *testing.T) {
	eng := &workerEngine{}
	r := workerTokenRegistry(t, eng)
	dan := ops.WithUser(context.Background(), "dan")

	out, err := r.Invoke(dan, "ci.worker.token.new", workerTokenNewIn{Repo: "alpha", Label: "runner"})
	if err != nil {
		t.Fatal(err)
	}
	o := out.(workerTokenNewOut)
	if o.Value != "s3cret-bearer-value" || o.Token.ID != "new-1" || o.Token.Label != "runner" {
		t.Errorf("FAIL: the mint answered %+v", o)
	}
	if len(eng.groups) != 1 || eng.groups[0] != ci.GroupFor("alpha") {
		t.Errorf("FAIL: scoped to %v, want only alpha's group", eng.groups)
	}
	if body, _ := json.Marshal(o.Token); strings.Contains(string(body), "s3cret") {
		t.Errorf("FAIL: the token row carries the value: %s", body)
	}

	list, err := r.Invoke(dan, "ci.worker.token.list", workerTokenListIn{Repo: "alpha"})
	if err != nil {
		t.Fatal(err)
	}
	body, _ := json.Marshal(list)
	if strings.Contains(string(body), "s3cret") || strings.Contains(string(body), "value") {
		t.Errorf("FAIL: the list carries a value: %s", body)
	}
	if rows := list.(workerTokenListOut).Tokens; len(rows) != 1 || rows[0].ID != "new-1" {
		t.Errorf("FAIL: the list is %+v", rows)
	}
	t.Log("ok")
}

// An account without the repository's admin letter is refused all three.
func TestWorkerTokenOpsRefuseANonAdmin(t *testing.T) {
	eng := &workerEngine{tokens: []ci.WorkerToken{{ID: "own", WorkerGroups: []string{ci.GroupFor("alpha")}}}}
	r := workerTokenRegistry(t, eng)
	eve := ops.WithUser(context.Background(), "eve")
	if _, err := r.Invoke(eve, "ci.worker.token.new", workerTokenNewIn{Repo: "alpha"}); !errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: a non-admin minted: %v", err)
	}
	if _, err := r.Invoke(eve, "ci.worker.token.list", workerTokenListIn{Repo: "alpha"}); !errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: a non-admin listed: %v", err)
	}
	if _, err := r.Invoke(eve, "ci.worker.token.revoke", workerTokenRevokeIn{Repo: "alpha", ID: "own"}); !errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: a non-admin revoked: %v", err)
	}
	if len(eng.revoked) != 0 || eng.groups != nil {
		t.Errorf("FAIL: a refused call reached the engine: revoked %v groups %v", eng.revoked, eng.groups)
	}
	t.Log("ok")
}

// Revoke finds only a token this repository's list shows; another
// repository's, a fleet-wide one and an unknown id are all not found.
func TestWorkerTokenRevokeIsScopedToTheRepository(t *testing.T) {
	eng := &workerEngine{tokens: []ci.WorkerToken{
		{ID: "own", WorkerGroups: []string{ci.GroupFor("alpha")}},
		{ID: "other", WorkerGroups: []string{ci.GroupFor("beta")}},
		{ID: "fleet", TaskTypePrefixes: []string{"x"}},
	}}
	r := workerTokenRegistry(t, eng)
	dan := ops.WithUser(context.Background(), "dan")
	for _, id := range []string{"other", "fleet", "nope"} {
		if _, err := r.Invoke(dan, "ci.worker.token.revoke", workerTokenRevokeIn{Repo: "alpha", ID: id}); !errors.Is(err, ops.ErrNotFound) {
			t.Errorf("FAIL: revoking %q from alpha: %v, want not found", id, err)
		}
	}
	if len(eng.revoked) != 0 {
		t.Fatalf("FAIL: the engine revoked %v for a token not alpha's", eng.revoked)
	}
	if _, err := r.Invoke(dan, "ci.worker.token.revoke", workerTokenRevokeIn{Repo: "alpha", ID: "own"}); err != nil {
		t.Fatal(err)
	}
	if len(eng.revoked) != 1 || eng.revoked[0] != "own" {
		t.Errorf("FAIL: revoked %v, want only own", eng.revoked)
	}
	t.Log("ok")
}

// No engine, no door: not found.
func TestWorkerTokenOpsWithoutAnEngineAreNotFound(t *testing.T) {
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"alpha": {Index: wikiIndex(t)}})}); err != nil {
		t.Fatal(err)
	}
	if _, err := r.Invoke(context.Background(), "ci.worker.token.new", workerTokenNewIn{Repo: "alpha"}); !errors.Is(err, ops.ErrNotFound) {
		t.Errorf("FAIL: %v", err)
	}
	// And a repository that is not served: refused before the engine.
	r = workerTokenRegistry(t, &workerEngine{})
	if _, err := r.Invoke(ops.WithUser(context.Background(), "dan"), "ci.worker.token.new", workerTokenNewIn{Repo: "gone"}); !errors.Is(err, ops.ErrNotFound) {
		t.Errorf("FAIL: minting on an unserved repository: %v", err)
	}
	t.Log("ok")
}

// Mint and revoke are written to the repository's settings log with the
// actor and the token's label and id, never its value; revoking one that
// is already revoked answers without the engine and without a second row.
func TestWorkerTokenMintAndRevokeAreLogged(t *testing.T) {
	eng := &workerEngine{}
	rec := &settingRecorder{}
	r := ops.NewRegistry()
	caps := func(string, string) CapSet { return oltp.CapSet("a") }
	if err := RegisterAll(r, Deps{Version: "v", WorkerTokens: eng, SettingLog: rec, CapsFor: caps,
		Repos: NewRepos(map[string]*Repo{"alpha": {Index: wikiIndex(t)}})}); err != nil {
		t.Fatal(err)
	}
	dan := ops.WithUser(context.Background(), "dan")
	if _, err := r.Invoke(dan, "ci.worker.token.new", workerTokenNewIn{Repo: "alpha", Label: "runner"}); err != nil {
		t.Fatal(err)
	}
	if len(rec.rows) != 1 || rec.rows[0].actor != "dan" || rec.rows[0].repo != "alpha" || rec.rows[0].field != "worker-token" ||
		!strings.Contains(rec.rows[0].now, "runner") || !strings.Contains(rec.rows[0].now, "new-1") {
		t.Fatalf("FAIL: the mint's row: %+v", rec.rows)
	}
	if _, err := r.Invoke(dan, "ci.worker.token.revoke", workerTokenRevokeIn{Repo: "alpha", ID: "new-1"}); err != nil {
		t.Fatal(err)
	}
	if len(rec.rows) != 2 || rec.rows[1].actor != "dan" || rec.rows[1].now != "revoked" || !strings.Contains(rec.rows[1].was, "new-1") {
		t.Fatalf("FAIL: the revoke's row: %+v", rec.rows)
	}
	for _, row := range rec.rows {
		if strings.Contains(row.was+row.now, "s3cret") {
			t.Errorf("FAIL: a row carries the value: %+v", row)
		}
	}
	// The engine lists the token as revoked now.
	eng.mu.Lock()
	eng.tokens[0].RevokedAt = "2026-11-12T11:00:00Z"
	eng.mu.Unlock()
	if _, err := r.Invoke(dan, "ci.worker.token.revoke", workerTokenRevokeIn{Repo: "alpha", ID: "new-1"}); err != nil {
		t.Fatal(err)
	}
	if len(eng.revoked) != 1 || len(rec.rows) != 2 {
		t.Errorf("FAIL: revoking a revoked token reached the engine %d times and wrote %d rows", len(eng.revoked), len(rec.rows))
	}
	t.Log("ok")
}

// An agent does not mint a worker token; asking its person with
// Quarry-Ask files a request, and once the person approves, the token is
// minted as them. The agent still lists and revokes with `a`.
func TestAnAgentAsksItsPersonForAWorkerToken(t *testing.T) {
	eng := &workerEngine{}
	rec := &settingRecorder{}
	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"} {
		if err := db.AddAccount(who, "a-long-enough-password", who == "dmestas"); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.AddAccount("jva-bot", "a-long-enough-password", false); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOperator("jva-bot", "jva"); err != nil {
		t.Fatal(err)
	}
	holds := func(string, string) CapSet { return oltp.CapSet("a") }
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", WorkerTokens: eng, SettingLog: rec,
		Repos:    NewRepos(map[string]*Repo{"test": {Index: wikiIndex(t), Code: "code-test"}}),
		Accounts: adminAdapter{acctAdapter{db}}, SignOffs: testSignOffs{db}, CapsFor: holds, SecretBox: testBox(t)}); err != nil {
		t.Fatal(err)
	}
	if _, err := r.Invoke(asUser("jva-bot"), "ci.worker.token.new", workerTokenNewIn{Repo: "test", Label: "ci"}); !errors.Is(err, ops.ErrForbidden) {
		t.Fatalf("FAIL: an agent minted: %v", err)
	}
	if eng.mints != 0 {
		t.Fatal("FAIL: a refused agent reached the engine")
	}
	if _, err := r.Invoke(asUser("jva-bot"), "ci.worker.token.list", workerTokenListIn{Repo: "test"}); err != nil {
		t.Errorf("FAIL: an agent could not list: %v", err)
	}

	h, err := RouterWithCaps(r, "v", holds, nil)
	if err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithToken(ops.WithUser(context.Background(), "jva-bot"), ops.TokenAuth{ID: "T", Caps: func(string) ops.Caps { return oltp.CapSet("a") }})
	req := httptest.NewRequest("POST", "/api/v1/r/test/ci/worker-tokens", strings.NewReader(`{"label":"ci"}`)).WithContext(ctx)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Quarry-Ask", "a runner for the repository")
	rec2 := httptest.NewRecorder()
	h.ServeHTTP(rec2, req)
	if rec2.Code != 202 {
		t.Fatalf("FAIL: asking answered %d: %s", rec2.Code, rec2.Body)
	}
	var asked struct {
		SignOff SignOffRequest `json:"signoff"`
	}
	if err := json.Unmarshal(rec2.Body.Bytes(), &asked); err != nil || asked.SignOff.ID == "" || asked.SignOff.Owner != "jva" || asked.SignOff.State != "pending" {
		t.Fatalf("FAIL: the request: %s", rec2.Body)
	}
	if eng.mints != 0 {
		t.Fatal("FAIL: the ask minted before the person approved")
	}
	if _, err := r.Invoke(asUser("jva"), "signoff.decide", signOffDecideIn{ID: asked.SignOff.ID, Approve: true}); err != nil {
		t.Fatalf("FAIL: approving: %v", err)
	}
	if eng.mints != 1 {
		t.Fatalf("FAIL: approving minted %d tokens, want 1", eng.mints)
	}
	if len(rec.rows) != 1 || rec.rows[0].actor != "jva" {
		t.Errorf("FAIL: the mint was not logged as the person: %+v", rec.rows)
	} else if !strings.Contains(rec.rows[0].now, "asked by jva-bot") || !strings.Contains(rec.rows[0].now, asked.SignOff.ID) {
		t.Errorf("FAIL: the row does not name the asking agent and request: %q", rec.rows[0].now)
	}
	t.Log("ok")
}