Skip to content
quarry Artifact [7942f74ed4]
Login

Artifact [7942f74ed4]

Artifact 7942f74ed409c59aaeaeac9763fef3c632efaa8ae58ddf9bbaf6f33dcc9267b9:


package web

import (
	"context"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"path/filepath"
	"slices"
	"sort"
	"strings"
	"sync"
	"testing"

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

// editRecorder is a repository registry that remembers every settings
// patch it was asked to apply, which is the only way to tell a write
// that changed nothing from one that changed a field to its zero value.
type editRecorder struct {
	mu    sync.Mutex
	edits []api.RepoEdit
}

func (e *editRecorder) Add(api.RepoSpec) (string, string, error) { return "", "", nil }
func (e *editRecorder) Owner(a string) (string, error)           { return a, nil }
func (e *editRecorder) NameFree(string, string, string) error    { return nil }
func (e *editRecorder) Remove(string) error                      { return nil }
func (e *editRecorder) Delete(string, string) error              { return nil }
func (e *editRecorder) Update(_ string, edit api.RepoEdit, _, _ string) error {
	e.mu.Lock()
	defer e.mu.Unlock()
	e.edits = append(e.edits, edit)
	return nil
}
func (e *editRecorder) Imports() ([]api.RepoImport, error) { return nil, nil }
func (e *editRecorder) ConfirmImport(string, string) error { return nil }
func (e *editRecorder) count() int {
	e.mu.Lock()
	defer e.mu.Unlock()
	return len(e.edits)
}
func (e *editRecorder) last() api.RepoEdit {
	e.mu.Lock()
	defer e.mu.Unlock()
	return e.edits[len(e.edits)-1]
}

func aboutServer(t *testing.T, deps func(*api.Deps)) (*httptest.Server, *editRecorder) {
	t.Helper()
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	rec := &editRecorder{}
	d := api.Deps{Version: "v", Repos: api.NewRepos(map[string]*api.Repo{"alpha": {Index: ix}}), Registry: rec}
	if deps != nil {
		deps(&d)
	}
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}
	verify := Verify(func(string, string, string, ops.Auth) api.Decision { return api.Allowed })
	srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
		WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"alpha": ops.Admin}}),
		WithCaps(stubCaps{letters: "a"}),
	}, verify))
	t.Cleanup(srv.Close)
	return srv, rec
}

// A body that names none of the About form's fields changes nothing.
// What happened on 2026-11-12: a worker-token mint's `label=` body was
// re-POSTed to the About form fifty times, and each one read the absent
// checkboxes as "off" (quarry#424).
func TestAboutFormRefusesABodyThatDoesNotNameIt(t *testing.T) {
	srv, rec := aboutServer(t, nil)
	resp := postAsDan(t, srv, "/r/alpha/settings", url.Values{"label": {"x"}})
	if resp.StatusCode != http.StatusBadRequest {
		t.Errorf("FAIL: a body naming no form answered %d, want 400", resp.StatusCode)
	}
	if rec.count() != 0 {
		t.Errorf("FAIL: a refused post still wrote %d settings patch(es)", rec.count())
	}
	t.Log("ok")
}

// Named, but with no checkbox drawn: nothing boolean may change, and
// neither may a text field the body did not carry.
func TestAboutFormMarkerAloneChangesNothingBoolean(t *testing.T) {
	srv, rec := aboutServer(t, nil)
	resp := postAsDan(t, srv, "/r/alpha/settings", url.Values{"form": {"about"}})
	if resp.StatusCode >= 400 {
		t.Fatalf("FAIL: the marker alone answered %d", resp.StatusCode)
	}
	if rec.count() > 0 {
		e := rec.last()
		if e.Chat != nil || e.UV != nil || e.Mirrorable != nil {
			t.Errorf("FAIL: a boolean was written with no checkbox in the body: %+v", e)
		}
		if e.Description != nil || e.Homepage != nil || e.Topics != nil {
			t.Errorf("FAIL: a text field was written with none in the body: %+v", e)
		}
	}
	t.Log("ok")
}

// The real form round-trips: a box that was drawn and left unticked is
// off, one that was ticked is on, and a disabled box (Mirrorable on a
// private repository draws none) is left alone.
func TestAboutFormRoundTripsDrawnCheckboxes(t *testing.T) {
	srv, rec := aboutServer(t, nil)
	resp := postAsDan(t, srv, "/r/alpha/settings", url.Values{
		"form": {"about"}, "description": {"d"}, "homepage": {""}, "topics": {""},
		"has": {"chat", "uv", "mirrorable"}, "uv": {"on"},
	})
	if resp.StatusCode >= 400 {
		t.Fatalf("FAIL: the real form answered %d", resp.StatusCode)
	}
	if rec.count() != 1 {
		t.Fatalf("FAIL: want one patch, got %d", rec.count())
	}
	e := rec.last()
	if e.Chat == nil || *e.Chat {
		t.Errorf("FAIL: an unticked drawn Chat box was not read as off: %+v", e.Chat)
	}
	if e.UV == nil || !*e.UV {
		t.Errorf("FAIL: a ticked UV box was not read as on: %+v", e.UV)
	}
	if e.Mirrorable == nil || *e.Mirrorable {
		t.Errorf("FAIL: an unticked drawn Mirrorable box was not read as off: %+v", e.Mirrorable)
	}
	if e.Description == nil || *e.Description != "d" {
		t.Errorf("FAIL: the description did not arrive: %+v", e.Description)
	}

	// A disabled box posts nothing and its `has` is not drawn.
	resp = postAsDan(t, srv, "/r/alpha/settings", url.Values{
		"form": {"about"}, "description": {"d2"}, "has": {"chat", "uv"}, "chat": {"on"}, "uv": {"on"},
	})
	if resp.StatusCode >= 400 {
		t.Fatalf("FAIL: the form without Mirrorable answered %d", resp.StatusCode)
	}
	if e = rec.last(); e.Mirrorable != nil {
		t.Errorf("FAIL: an undrawn (disabled) Mirrorable was written: %v", *e.Mirrorable)
	}
	t.Log("ok")
}

// The page itself draws the marker and one `has` per drawn checkbox,
// and none for a disabled Mirrorable.
func TestAboutFormDrawsMarkerAndPresenceFields(t *testing.T) {
	has := func(private bool) []string {
		body := renderSettings(t, pageData{Repo: "alpha", RepoPrivate: private, Caps: &capsPanel{}})
		if !strings.Contains(body, `name="form" value="about"`) {
			t.Errorf("FAIL: the About form does not carry its marker (private=%v)", private)
		}
		var got []string
		rest := body
		for {
			i := strings.Index(rest, `name="has" value="`)
			if i < 0 {
				break
			}
			rest = rest[i+len(`name="has" value="`):]
			got = append(got, rest[:strings.Index(rest, `"`)])
		}
		return got
	}
	if got := strings.Join(has(false), " "); !strings.Contains(got, "chat") || !strings.Contains(got, "uv") || !strings.Contains(got, "mirrorable") {
		t.Errorf("FAIL: a public repository draws presence fields %q", got)
	}
	if got := strings.Join(has(true), " "); strings.Contains(got, "mirrorable") {
		t.Errorf("FAIL: a disabled Mirrorable still draws its presence field: %q", got)
	}
	t.Log("ok")
}

// stubIn takes every field any guarded route sends, as a string, so one
// recording op can stand behind each of them.
type stubIn struct {
	Repo          string `json:"repo"`
	Name          string `json:"name"`
	ID            string `json:"id"`
	Agent         string `json:"agent"`
	On            string `json:"on"`
	Listed        string `json:"listed"`
	Private       string `json:"private"`
	Title         string `json:"title"`
	Descr         string `json:"descr"`
	Mode          string `json:"mode"`
	State         string `json:"state"`
	Accept        string `json:"accept"`
	Answerable    string `json:"answerable"`
	Email         string `json:"email"`
	Body          string `json:"body"`
	Account       string `json:"account"`
	Role          string `json:"role"`
	Post          string `json:"post"`
	Root          string `json:"root"`
	Event         string `json:"event"`
	Comment       string `json:"comment"`
	Remove        string `json:"remove"`
	Description   string `json:"description"`
	Events        string `json:"events"`
	Archives      string `json:"archives"`
	DeleteObjects string `json:"deleteObjects"`
}

type stubOut struct {
	OK bool `json:"ok"`
}

// guardPlane is a capability plane that also records the two settings
// the repository page writes through the plane itself.
type guardPlane struct {
	stubCaps
	mu           sync.Mutex
	tiers, modes []string
}

func (g *guardPlane) TicketTier(string) string { return "" }
func (g *guardPlane) SetTicketTier(_, tier string) error {
	g.mu.Lock()
	defer g.mu.Unlock()
	g.tiers = append(g.tiers, tier)
	return nil
}
func (g *guardPlane) CITagsMode(string) string { return "" }
func (g *guardPlane) SetCITagsMode(_, mode string) error {
	g.mu.Lock()
	defer g.mu.Unlock()
	g.modes = append(g.modes, mode)
	return nil
}

// guardServer stands a recording op behind every op a guarded route
// calls: a request that gets past its guard REACHES the stub, so a
// route whose guard is removed is seen to change something.
func guardServer(t *testing.T, opNames []string) (*httptest.Server, *guardPlane, func(string) int) {
	srv, plane, called, _ := guardServerIn(t, opNames)
	return srv, plane, called
}

// guardServerIn is guardServer that also reports the last input each
// stub op received.
func guardServerIn(t *testing.T, opNames []string) (*httptest.Server, *guardPlane, func(string) int, func(string) stubIn) {
	t.Helper()
	var mu sync.Mutex
	calls := map[string]int{}
	last := map[string]stubIn{}
	r := ops.NewRegistry()
	for _, name := range opNames {
		name := name
		if err := ops.Register(r, ops.Op[stubIn, stubOut]{
			Name: name, Doc: "records that it was called", Kind: ops.Command, Auth: ops.Anonymous,
			Route: "POST /api/v1/stub/" + name,
			Run: func(_ context.Context, in stubIn) (stubOut, error) {
				mu.Lock()
				defer mu.Unlock()
				calls[name]++
				last[name] = in
				return stubOut{OK: true}, nil
			},
		}); err != nil {
			t.Fatal(err)
		}
	}
	// The refusal page reads the version for its chrome.
	if err := ops.Register(r, ops.Op[stubIn, struct {
		Version string `json:"version"`
	}]{
		Name: "meta.version", Doc: "the version", Kind: ops.Query, Auth: ops.Anonymous, Route: "GET /api/v1/stub/version",
		Run: func(context.Context, stubIn) (struct {
			Version string `json:"version"`
		}, error) {
			return struct {
				Version string `json:"version"`
			}{"v"}, nil
		},
	}); err != nil {
		t.Fatal(err)
	}
	plane := &guardPlane{stubCaps: stubCaps{letters: "aC3kfeimnopqrtvwxyz8LS"}}
	verify := Verify(func(string, string, string, ops.Auth) api.Decision { return api.Allowed })
	srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
		WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"alpha": ops.Admin, "": ops.Admin}}),
		WithCaps(plane),
	}, verify))
	t.Cleanup(srv.Close)
	return srv, plane, func(name string) int {
			mu.Lock()
			defer mu.Unlock()
			return calls[name]
		}, func(name string) stubIn {
			mu.Lock()
			defer mu.Unlock()
			return last[name]
		}
}

// Every route below changes a stored value from a field of its own form
// or toggle. For each: a body carrying none of that form's fields is
// refused with 400 and the op behind it is never reached; the same route
// with its field does reach it. The second half is what makes the first
// mean something: with the guard removed, the stray body reaches the op
// too, and the test fails.
func TestGuardedRoutesRefuseAStrayBodyAndAcceptTheirOwn(t *testing.T) {
	cases := []struct {
		path, op string
		valid    url.Values
	}{
		{"/t/ops/listed", "team.listed", url.Values{"listed": {"true"}}},
		{"/t/ops/private", "team.private", url.Values{"private": {"true"}}},
		{"/t/ops/text", "team.text", url.Values{"form": {"team-text"}, "title": {"x"}}},
		{"/t/ops/member", "team.member", url.Values{"account": {"bob"}, "role": {""}}},
		{"/me/agents/bot/ask", "agent.ask", url.Values{"on": {"true"}}},
		{"/me/agents/bot/inherit", "agent.inherit", url.Values{"on": {"true"}}},
		{"/r/alpha/stamp", "stamp.set", url.Values{"on": {"1"}}},
		{"/r/alpha/landing/abc123/auto", "landing.auto", url.Values{"on": {"true"}}},
		{"/fleet/people/disable", "account.disable", url.Values{"name": {"bob"}, "on": {"true"}}},
		{"/fleet/people/answerable", "account.answerable", url.Values{"name": {"bob"}, "answerable": {"dan"}}},
		{"/fleet/people/email", "account.email.set", url.Values{"name": {"bob"}, "email": {"[email protected]"}}},
		{"/fleet/mirror/writes", "mirror.writes", url.Values{"repo": {"alpha"}, "accept": {"true"}}},
		{"/r/alpha/settings/mirror/writes", "mirror.writes", url.Values{"form": {"mirror-writes"}, "accept": {"1"}}},
		{"/r/alpha/settings/robot", "chat.robot", url.Values{"form": {"robot"}, "events": {"checkin"}}},
		{"/r/alpha/settings/bucket/policy", "bucket.policy", url.Values{"form": {"bucket-policy"}, "has": {"archives"}, "archives": {"1"}}},
		{"/r/alpha/settings", "repo.update", url.Values{"form": {"about"}, "description": {"d"}}},
		{"/r/alpha/chat/notify", "chat.notify.set", url.Values{"mode": {"all"}}},
		{"/r/alpha/forum/abc123/subscribe", "forum.subscribe", url.Values{"state": {"on"}}},
		{"/r/alpha/wiki/Home/edit", "wiki.save", url.Values{"body": {"text"}}},
		{"/r/alpha/forum/abc123/edit/def456", "forum.edit", url.Values{"body": {"text"}}},
		{"/r/alpha/technote/abc123/edit", "technote.add", url.Values{"body": {"text"}}},
	}
	var names []string
	for _, c := range cases {
		names = append(names, c.op)
	}
	sort.Strings(names)
	names = slices.Compact(names)
	srv, _, called, _ := guardServerIn(t, names)
	for _, c := range cases {
		t.Run(c.path, func(t *testing.T) {
			before := called(c.op)
			if resp := postAsDan(t, srv, c.path, url.Values{"label": {"x"}}); resp.StatusCode != http.StatusBadRequest {
				b, _ := io.ReadAll(resp.Body)
				t.Errorf("FAIL: a stray body answered %d, want 400: %.200s", resp.StatusCode, b)
			}
			if got := called(c.op); got != before {
				t.Errorf("FAIL: a stray body reached %s", c.op)
			}
			// The fields in the URL are not the form: a stray body whose
			// query string carries the route's own fields is still stray.
			if resp := postAsDan(t, srv, c.path+"?"+c.valid.Encode(), url.Values{"label": {"x"}}); resp.StatusCode != http.StatusBadRequest {
				t.Errorf("FAIL: the route's fields in the query string answered %d, want 400", resp.StatusCode)
			}
			if got := called(c.op); got != before {
				t.Errorf("FAIL: the route's fields in the query string reached %s", c.op)
			}
			if resp := postAsDan(t, srv, c.path, c.valid); resp.StatusCode >= 400 {
				t.Errorf("FAIL: the route's own body answered %d", resp.StatusCode)
			}
			if called(c.op) != before+1 {
				t.Errorf("FAIL: the route's own body did not reach %s", c.op)
			}
		})
	}
	t.Log("ok")
}

// team.text reads the body only: a named form with no title in it, posted
// to a URL that carries one, sets no title.
func TestTeamTextIgnoresTheQueryString(t *testing.T) {
	srv, _, called, lastIn := guardServerIn(t, []string{"team.text"})
	if resp := postAsDan(t, srv, "/t/ops/text?title=zzz&descr=yyy", url.Values{"form": {"team-text"}, "descr": {"kept"}}); resp.StatusCode >= 400 {
		t.Fatalf("FAIL: answered %d", resp.StatusCode)
	}
	if called("team.text") != 1 {
		t.Fatal("FAIL: the op was not reached")
	}
	if in := lastIn("team.text"); in.Title != "" || in.Descr != "kept" {
		t.Errorf("FAIL: the op was given %+v; the query string leaked in", in)
	}
	t.Log("ok")
}

// The two settings the repository page writes through the plane, and
// the appearance cookies, with the same proof: the route's own body
// writes, a stray one writes nothing.
func TestGuardedPlaneRoutesAndCookies(t *testing.T) {
	srv, plane, _ := guardServer(t, nil)
	for _, c := range []struct {
		path  string
		valid url.Values
		wrote func() int
	}{
		{"/r/alpha/settings/ticket-tier", url.Values{"tier": {"fossil"}}, func() int { return len(plane.tiers) }},
		{"/r/alpha/settings/ci-tags", url.Values{"mode": {"on"}}, func() int { return len(plane.modes) }},
	} {
		t.Run(c.path, func(t *testing.T) {
			if resp := postAsDan(t, srv, c.path, url.Values{"label": {"x"}}); resp.StatusCode != http.StatusBadRequest {
				b, _ := io.ReadAll(resp.Body)
				t.Errorf("FAIL: a stray body answered %d, want 400: %.300s", resp.StatusCode, b)
			}
			if c.wrote() != 0 {
				t.Errorf("FAIL: a stray body wrote the setting")
			}
			if resp := postAsDan(t, srv, c.path, c.valid); resp.StatusCode >= 400 {
				t.Errorf("FAIL: its own body answered %d", resp.StatusCode)
			}
			if c.wrote() != 1 {
				t.Errorf("FAIL: its own body did not write the setting")
			}
		})
	}
	t.Run("/me/appearance", func(t *testing.T) {
		resp := postAsDan(t, srv, "/me/appearance", url.Values{"label": {"x"}})
		if resp.StatusCode != http.StatusBadRequest || len(resp.Cookies()) != 0 {
			t.Errorf("FAIL: a stray body answered %d and set %d cookies", resp.StatusCode, len(resp.Cookies()))
		}
		resp = postAsDan(t, srv, "/me/appearance", url.Values{"site": {""}, "code": {""}})
		if resp.StatusCode >= 400 || len(resp.Cookies()) != 2 {
			t.Errorf("FAIL: its own body answered %d and set %d cookies, want 2", resp.StatusCode, len(resp.Cookies()))
		}
	})
	t.Log("ok")
}

// Participation and protection are written through planes that keep
// state, so they are proved by reading it back: a stray body leaves the
// anonymous string and the rule set as they were.
func TestParticipationAndProtectionKeepTheirStateOnAStrayBody(t *testing.T) {
	caps := &memCaps{stored: map[string]string{"dan": "a"}}
	prot := &recProtect{set: map[string][]string{}, rules: map[string][]oltp.Protection{
		"alpha": {{Branch: "trunk", Owners: []string{"alice"}}}}}
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: "v", Protect: prot,
		Repos: api.NewRepos(map[string]*api.Repo{"alpha": {Index: landingIndex(t, false)}})}); err != nil {
		t.Fatal(err)
	}
	srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
		WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"alpha": ops.Admin}}),
		WithCaps(caps), WithProtect(prot)}))
	t.Cleanup(srv.Close)

	before := caps.cat("alpha", "anonymous")
	if resp := postAsDan(t, srv, "/r/alpha/settings/participation", url.Values{"label": {"x"}}); resp.StatusCode != http.StatusBadRequest {
		t.Errorf("FAIL: participation answered %d to a stray body", resp.StatusCode)
	}
	if got := caps.cat("alpha", "anonymous"); got != before {
		t.Errorf("FAIL: a stray body changed the anonymous string %q -> %q", before, got)
	}
	postAsDan(t, srv, "/r/alpha/settings/participation", url.Values{"form": {"participation"}, "part": {"nc"}})
	if got := caps.cat("alpha", "anonymous"); got == before {
		t.Errorf("FAIL: the form's own body changed nothing, so the test above proves nothing")
	}

	if resp := postAsDan(t, srv, "/r/alpha/settings/protect", url.Values{"label": {"x"}}); resp.StatusCode != http.StatusBadRequest {
		t.Errorf("FAIL: protect answered %d to a stray body", resp.StatusCode)
	}
	if len(prot.set) != 0 {
		t.Errorf("FAIL: a stray body rewrote the protection rules: %v", prot.set)
	}
	postAsDan(t, srv, "/r/alpha/settings/protect", url.Values{"form": {"protect"}})
	if _, cleared := prot.set["trunk"]; !cleared {
		t.Errorf("FAIL: the form's own (empty) body did not clear the unnamed rule, so the test above proves nothing")
	}
	t.Log("ok")
}

// ---- worker tokens through the ops ----

func tokenServer(t *testing.T, eng *mintRecorder, letters string) *httptest.Server {
	t.Helper()
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: "v", WorkerTokens: eng,
		Repos: api.NewRepos(map[string]*api.Repo{"alpha": {Index: ix}})}); err != nil {
		t.Fatal(err)
	}
	verify := Verify(func(string, string, string, ops.Auth) api.Decision { return api.Allowed })
	srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
		WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"alpha": ops.Admin}}),
		WithCaps(stubCaps{letters: letters}),
		WithEngine(eng),
	}, verify))
	t.Cleanup(srv.Close)
	return srv
}

// The web form mints and revokes through the ops: the group scope the op
// sets is what reaches the engine, and revoke of another repository's
// token is not found.
func TestWorkerTokenFormGoesThroughTheOps(t *testing.T) {
	eng := &mintRecorder{tokens: tokensFixture()}
	srv := tokenServer(t, eng, "a")

	resp := postAsDan(t, srv, "/r/alpha/settings/worker-tokens", url.Values{"label": {"runner"}})
	if resp.StatusCode != http.StatusSeeOther {
		t.Fatalf("FAIL: the mint answered %d", resp.StatusCode)
	}
	eng.mu.Lock()
	groups, prefixes := eng.groups, eng.prefixes
	eng.mu.Unlock()
	want := ci.GroupFor("alpha")
	if len(groups) != 1 || groups[0] != want || len(prefixes) != 1 || prefixes[0] != want {
		t.Errorf("FAIL: minted with groups %v prefixes %v, want only %q", groups, prefixes, want)
	}
	if loc := resp.Header.Get("Location"); strings.Contains(loc, "tok1") || strings.Contains(loc, "s&") {
		t.Errorf("FAIL: the redirect carries the minted value")
	}

	if resp := postAsDan(t, srv, "/r/alpha/settings/worker-tokens", url.Values{"revoke": {"beta-token-1"}}); resp.StatusCode != http.StatusNotFound {
		t.Errorf("FAIL: revoking another repository's token answered %d, want 404", resp.StatusCode)
	}
	if resp := postAsDan(t, srv, "/r/alpha/settings/worker-tokens", url.Values{"revoke": {"fleet-token-1"}}); resp.StatusCode != http.StatusNotFound {
		t.Errorf("FAIL: revoking a fleet-wide token answered %d, want 404", resp.StatusCode)
	}
	if resp := postAsDan(t, srv, "/r/alpha/settings/worker-tokens", url.Values{"revoke": {"own-token-1"}}); resp.StatusCode != http.StatusSeeOther {
		t.Errorf("FAIL: revoking its own token answered %d", resp.StatusCode)
	}
	eng.mu.Lock()
	revoked := append([]string(nil), eng.revoked...)
	eng.mu.Unlock()
	if len(revoked) != 1 || revoked[0] != "own-token-1" {
		t.Errorf("FAIL: the engine revoked %v, want only own-token-1", revoked)
	}
	t.Log("ok")
}