Skip to content
quarry Artifact [002cde6d85]
Login

Artifact [002cde6d85]

Artifact 002cde6d8581c04af0a26b316fb4c4f6b4aa4a7305f26a9b69c73ec79e7219d8:


package api

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"regexp"
	"strings"
	"sync"
	"testing"
	"time"

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

type recNotify struct {
	mu sync.Mutex
	n  int
}

func (r *recNotify) Trigger(context.Context, NotifyEvent) error {
	r.mu.Lock()
	defer r.mu.Unlock()
	r.n++
	return nil
}

// startRig is the setup rig with the start route in front of it.
type startRig struct {
	*setupRig
	st     *SetupStarter
	notify *recNotify
	clock  time.Time
	addr   int
}

func newStartRig(t *testing.T) *startRig {
	t.Helper()
	g := newSetupRig(t)
	n := &recNotify{}
	d := g.deps
	d.Notify = n
	sr := &startRig{setupRig: g, notify: n, clock: time.Now()}
	sr.st = NewSetupStarter(d, nil)
	sr.st.now = func() time.Time { return sr.clock }
	return sr
}

// start posts a start from its own address unless from is named.
func (g *startRig) start(body, from string) (int, string, http.Header) {
	g.t.Helper()
	if from == "" {
		g.addr++
		from = fmt.Sprintf("198.51.100.%d", g.addr)
	}
	req := httptest.NewRequest("POST", "/api/v1/setup/start", strings.NewReader(body))
	req.RemoteAddr = from + ":5000"
	w := httptest.NewRecorder()
	g.st.ServeHTTP(w, req)
	return w.Code, w.Body.String(), w.Header()
}

func startBody(name, kind, pickup string) string {
	return fmt.Sprintf(`{"name":%q,"kind":%q,"pickup":%q}`, name, kind, pickup)
}

func (g *startRig) begin(name, kind, pickup string) string {
	g.t.Helper()
	status, body, _ := g.start(startBody(name, kind, pickup), "")
	var out setupStartOut
	if err := json.Unmarshal([]byte(body), &out); err != nil || status != 200 || out.Code == "" {
		g.t.Fatalf("FAIL: start answered %d", status)
	}
	return out.Code
}

func (g *startRig) look(who, code string) (setupClaimLookOut, error) {
	out, err := g.r.Invoke(asUser(who), "setup.start.look", setupClaimLookIn{Code: code})
	if err != nil {
		return setupClaimLookOut{}, err
	}
	return out.(setupClaimLookOut), nil
}

func (g *startRig) claim(who, code, repos, role string) (string, error) {
	out, err := g.r.Invoke(asUser(who), "setup.start.claim", setupClaimIn{Code: code, Repos: repos, Role: role})
	if err != nil {
		return "", err
	}
	return out.(setupClaimOut).Request, nil
}

func (g *startRig) requestsOf(person string) []SignOffRequest {
	g.t.Helper()
	rows, err := g.deps.SignOffs.SignOffsFor(person, 100)
	if err != nil {
		g.t.Fatal(err)
	}
	return rows
}

func repoNames(cs []setupRepoChoice) []string {
	out := make([]string, 0, len(cs))
	for _, c := range cs {
		out = append(out, c.Name)
	}
	return out
}

var shortCode = regexp.MustCompile(`^[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$`)

// RFC 0030, Starting without a credential: a start answers a code, a
// link without the code, and when it lapses, and files nothing at all.
func TestAStartAnswersACodeALinkAndAnExpiryAndFilesNothing(t *testing.T) {
	g := newStartRig(t)
	_, pickup := newPickupKey(t)
	status, body, _ := g.start(startBody("e-desktop", "read", pickup), "")
	var out map[string]any
	if err := json.Unmarshal([]byte(body), &out); err != nil || status != 200 {
		t.Fatalf("FAIL: start answered %d", status)
	}
	if len(out) != 3 {
		t.Errorf("FAIL: the answer has %d keys, want code, link, expires", len(out))
	}
	code, _ := out["code"].(string)
	link, _ := out["link"].(string)
	expires, _ := out["expires"].(string)
	if !shortCode.MatchString(code) {
		t.Errorf("FAIL: the code is not XXXX-XXXX in the alphabet")
	}
	if link != "https://forge.test/setup" || strings.Contains(link, strings.ReplaceAll(code, "-", "")) || strings.Contains(link, "?") {
		t.Errorf("FAIL: the link: %q", link)
	}
	if at, err := time.Parse(time.RFC3339, expires); err != nil || !at.Equal(g.clock.UTC().Add(oltp.SetupStartLife).Truncate(time.Second)) {
		t.Errorf("FAIL: expires %q (%v)", expires, err)
	}
	for _, who := range []string{"dmestas", "jva", "dmestas-bot", "jva-bot", "zeitgeist"} {
		if rows := g.requestsOf(who); len(rows) != 0 {
			t.Errorf("FAIL: %s has %d sign-off rows after a start", who, len(rows))
		}
		if ev := g.attn.sent[who]; len(ev) != 0 {
			t.Errorf("FAIL: %s was attended %d times by a start", who, len(ev))
		}
	}
	if g.notify.n != 0 || len(g.rec.rows) != 0 {
		t.Errorf("FAIL: a start notified (%d) or logged a setting (%d)", g.notify.n, len(g.rec.rows))
	}
	if len(g.attn.sent) != 0 {
		t.Errorf("FAIL: a start raised attention events")
	}
	// What the client may say is what it is held to.
	for what, body := range map[string]string{
		"a repository named":  `{"name":"x","kind":"read","pickup":"` + pickup + `","repos":"suit"}`,
		"an account named":    `{"name":"x","kind":"read","pickup":"` + pickup + `","repos":"","account":"dmestas"}`,
		"a reason given":      `{"name":"x","kind":"read","pickup":"` + pickup + `","reason":"let me in"}`,
		"a name with a space": startBody("bad name", "read", pickup),
		"a name that is long": startBody(strings.Repeat("a", 65), "read", pickup),
		"a kind that is not":  startBody("x", "admin", pickup),
		"a pickup not a hash": startBody("x", "read", "nothex"),
	} {
		if status, _, _ := g.start(body, ""); status != 400 {
			t.Errorf("FAIL: %s answered %d, want 400", what, status)
		}
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A name that is already an account starts exactly as a free one does:
// the route is no oracle for accounts.
func TestAStartForATakenNameAnswersAsAFreeOneDoes(t *testing.T) {
	g := newStartRig(t)
	_, pickup := newPickupKey(t)
	shape := func(name string) (int, map[string]string) {
		status, body, _ := g.start(startBody(name, "push", pickup), "")
		var out map[string]string
		_ = json.Unmarshal([]byte(body), &out)
		return status, out
	}
	freeStatus, free := shape("brand-new-agent")
	for _, taken := range []string{"dmestas", "Dmestas", "zeitgeist", "suit-ci"} {
		status, out := shape(taken)
		if status != freeStatus || len(out) != len(free) || out["link"] != free["link"] || !shortCode.MatchString(out["code"]) || out["expires"] == "" {
			t.Errorf("FAIL: a start for %q answers %d %v, a free name %d %v", taken, status, out, freeStatus, free)
		}
	}
	// The claim says so, plainly, to the person: no repository is offered.
	code := g.begin("dmestas", "read", pickup)
	look, err := g.look("dmestas", code)
	if err != nil || look.Problem == "" || len(look.Repos) != 0 {
		t.Errorf("FAIL: the claim of a taken name: %+v %v", look, err)
	}
	if _, err := g.claim("dmestas", code, "suit", ""); !errors.Is(err, ops.ErrInvalid) {
		t.Errorf("FAIL: claiming a taken name: %v", err)
	}
	if rows := g.requestsOf("dmestas"); len(rows) != 0 {
		t.Errorf("FAIL: a taken name filed a request")
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestStartsAreLimitedPerAddressAndInAll(t *testing.T) {
	g := newStartRig(t)
	_, pickup := newPickupKey(t)
	var last http.Header
	var status int
	for i := 0; i <= setupStartPerAddress; i++ {
		status, _, last = g.start(startBody("a"+fmt.Sprint(i), "read", pickup), "203.0.113.9")
	}
	if status != 429 || last.Get("Retry-After") == "" {
		t.Errorf("FAIL: the sixth start from one address answered %d (Retry-After %q)", status, last.Get("Retry-After"))
	}
	// Another address is not held by it.
	if status, _, _ := g.start(startBody("other", "read", pickup), "203.0.113.10"); status != 200 {
		t.Errorf("FAIL: a neighbour was held: %d", status)
	}
	// The forge holds a fixed number unclaimed.
	db := g.db
	for n, _ := db.SetupStartRows(); n < setupStartMax; n++ {
		if _, err := db.StartSetup(oltp.SetupStart{Pickup: pickup, Name: "n", Kind: "read", Addr: fmt.Sprint("x", n)}, setupStartMax, setupStartPerShare, g.clock); err != nil {
			t.Fatal(err)
		}
	}
	status, _, h := g.start(startBody("one-too-many", "read", pickup), "")
	if status != 429 || h.Get("Retry-After") == "" {
		t.Errorf("FAIL: past the cap a start answered %d (Retry-After %q)", status, h.Get("Retry-After"))
	}
	// A moment later, once they lapse, starts are taken again.
	g.clock = g.clock.Add(oltp.SetupStartLife + time.Minute)
	if status, _, _ := g.start(startBody("after-the-lapse", "read", pickup), ""); status != 200 {
		t.Errorf("FAIL: the cap did not release as starts lapsed: %d", status)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// collectAs polls the collect route with key from its own address.
func (g *startRig) collectKey(key, from string) (int, string) {
	req := httptest.NewRequest("POST", collectPath, nil)
	req.Header.Set("Authorization", "Pickup "+key)
	req.RemoteAddr = from + ":4000"
	w := httptest.NewRecorder()
	g.coll.ServeHTTP(w, req)
	return w.Code, w.Body.String()
}

// An unclaimed start, a lapsed one, a discarded one and a key never seen
// are one answer at collect; a lapsed, a discarded, an unknown and a
// malformed code are one answer at claim.
func TestUnclaimedLapsedDiscardedAndUnknownAreOneAnswer(t *testing.T) {
	g := newStartRig(t)
	keys := map[string]string{}
	pickups := map[string]string{}
	for _, which := range []string{"unclaimed", "lapsed", "discarded", "unknown"} {
		k, p := newPickupKey(t)
		keys[which], pickups[which] = k, p
	}
	g.begin("e-unclaimed", "read", pickups["unclaimed"])
	// Lapsed: started in the past.
	lapsedCode, err := g.db.StartSetup(oltp.SetupStart{Pickup: pickups["lapsed"], Name: "e-lapsed", Kind: "read", Addr: "x"}, setupStartMax, setupStartPerShare, time.Now().Add(-oltp.SetupStartLife-time.Minute))
	if err != nil {
		t.Fatal(err)
	}
	discardedCode := g.begin("e-discarded", "read", pickups["discarded"])
	if _, err := g.r.Invoke(asUser("dmestas"), "setup.start.discard", setupClaimLookIn{Code: discardedCode}); err != nil {
		t.Fatal(err)
	}
	type seen struct {
		status int
		body   string
	}
	got := map[string]seen{}
	i := 0
	for which, key := range keys {
		i++
		status, body := g.collectKey(key, fmt.Sprintf("192.0.2.%d", i))
		got[which] = seen{status, body}
	}
	for which, s := range got {
		if s != got["unknown"] || s.status != 404 {
			t.Errorf("FAIL: collect for %s answered %d %q, unknown answered %d %q", which, s.status, s.body, got["unknown"].status, got["unknown"].body)
		}
	}
	// At claim, by a signed-in person.
	answers := map[string]string{}
	for which, code := range map[string]string{"lapsed": lapsedCode, "discarded": discardedCode, "unknown": "ZZZZ-ZZZZ", "malformed": "not a code!", "long": strings.Repeat("A", 40)} {
		_, lerr := g.look("dmestas", code)
		_, cerr := g.claim("jva", code, "suit", "")
		if !errors.Is(lerr, ops.ErrNotFound) || !errors.Is(cerr, ops.ErrNotFound) {
			t.Errorf("FAIL: %s: look %v, claim %v", which, lerr, cerr)
			continue
		}
		answers[which] = lerr.Error() + "|" + cerr.Error()
	}
	for which, a := range answers {
		if a != answers["unknown"] {
			t.Errorf("FAIL: %s answers %q, unknown %q", which, a, answers["unknown"])
		}
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestATokenCannotClaimOrLookOrDiscard(t *testing.T) {
	g := newStartRig(t)
	_, pickup := newPickupKey(t)
	code := g.begin("e-desktop", "read", pickup)
	for op, in := range map[string]any{
		"setup.start.look":    setupClaimLookIn{Code: code},
		"setup.start.claim":   setupClaimIn{Code: code, Repos: "suit"},
		"setup.start.discard": setupClaimLookIn{Code: code},
	} {
		if _, err := g.r.Invoke(tokenCtx("dmestas"), op, in); !errors.Is(err, ops.ErrForbidden) {
			t.Errorf("FAIL: %s on a token: %v", op, err)
		}
		// An agent signed in by its own account is no person either.
		if _, err := g.r.Invoke(asUser("dmestas-bot"), op, in); !errors.Is(err, ops.ErrForbidden) {
			t.Errorf("FAIL: %s as an agent: %v", op, err)
		}
	}
	if _, ok, _ := g.db.SetupStartByCode(code, time.Now()); !ok {
		t.Errorf("FAIL: the start did not survive the refusals")
	}
	if rows := g.requestsOf("dmestas"); len(rows) != 0 {
		t.Errorf("FAIL: a refused claim filed a request")
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// What a person may pick is what agent.setup would accept from them, and
// the request it files is theirs, with their picks, the start's pickup
// and the forge's own reason.
func TestAClaimOffersOnlyWhatThePersonMaySetUpAndFilesTheirRequest(t *testing.T) {
	g := newStartRig(t)
	_, pickup := newPickupKey(t)
	read := g.begin("e-desktop", "read", pickup)
	// jva reads only the public ones; dmestas administers all three.
	jv, err := g.look("jva", read)
	if err != nil {
		t.Fatal(err)
	}
	if strings.Join(repoNames(jv.Repos), ",") != "ctx,suit" || jv.Name != "e-desktop" || jv.Kind != "read" || jv.Address == "" || jv.Created == "" {
		t.Errorf("FAIL: jva is offered %+v", jv)
	}
	dm, _ := g.look("dmestas", read)
	if strings.Join(repoNames(dm.Repos), ",") != "ctx,secret,suit" {
		t.Errorf("FAIL: dmestas is offered %v", dm.Repos)
	}
	// A repository outside the list, posted by hand, is refused and the
	// start survives.
	if _, err := g.claim("jva", read, "suit,secret", ""); !errors.Is(err, ops.ErrInvalid) {
		t.Errorf("FAIL: a hand-posted repository: %v", err)
	}
	if _, err := g.claim("jva", read, "no-such-repo", ""); !errors.Is(err, ops.ErrInvalid) {
		t.Errorf("FAIL: a repository that is not there: %v", err)
	}
	if rows := g.requestsOf("jva"); len(rows) != 0 {
		t.Fatalf("FAIL: a refused claim filed a request")
	}
	id, err := g.claim("jva", read, "ctx, suit", "")
	if err != nil {
		t.Fatal(err)
	}
	rows := g.requestsOf("jva")
	if len(rows) != 1 || rows[0].ID != id {
		t.Fatalf("FAIL: jva's requests: %+v", rows)
	}
	s := rows[0]
	var in agentSetupIn
	if err := json.Unmarshal([]byte(s.Input), &in); err != nil {
		t.Fatal(err)
	}
	if s.Owner != "jva" || s.Op != setupOp || s.State != "pending" || s.Pickup != pickup || s.Reason != setupClaimReason ||
		s.Agent != clientAsker("jva") || in.Name != "e-desktop" || in.Kind != "read" || in.Repos != "ctx, suit" || in.Ceiling != oltp.SetupReadCeiling {
		t.Errorf("FAIL: the request: %+v %+v", s, in)
	}
	if _, ok := g.db.AccountByName(s.Agent); ok {
		t.Errorf("FAIL: the asker is an account")
	}
	if rows := g.requestsOf("dmestas"); len(rows) != 0 {
		t.Errorf("FAIL: dmestas was handed jva's request")
	}
	// The start is spent.
	if _, err := g.claim("jva", read, "suit", ""); !errors.Is(err, ops.ErrNotFound) {
		t.Errorf("FAIL: claiming twice: %v", err)
	}
	// No attention event or notification is raised by a claim either: the
	// person is looking at the request.
	if len(g.attn.sent["jva"]) != 0 || g.notify.n != 0 {
		t.Errorf("FAIL: a claim raised events (%d) or notifications (%d)", len(g.attn.sent["jva"]), g.notify.n)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestAPushClaimIsOneRepositoryAndARoleSetupAllows(t *testing.T) {
	g := newStartRig(t)
	_, pickup := newPickupKey(t)
	push := g.begin("suit-push", "push", pickup)
	// jva holds nothing that a push role needs on any repository.
	jv, err := g.look("jva", push)
	if err != nil || len(jv.Repos) != 0 || jv.Problem == "" {
		t.Errorf("FAIL: jva's push offer: %+v %v", jv, err)
	}
	dm, err := g.look("dmestas", push)
	if err != nil || strings.Join(repoNames(dm.Repos), ",") != "ctx,secret,suit" || strings.Join(dm.Roles, ",") != "developer,publisher,maintainer" {
		t.Errorf("FAIL: dmestas's push offer: %+v %v", dm, err)
	}
	for what, c := range map[string][2]string{
		"two repositories": {"suit,ctx", ""},
		"an admin role":    {"suit", "admin"},
		"an unknown role":  {"suit", "owner"},
	} {
		if _, err := g.claim("dmestas", push, c[0], c[1]); !errors.Is(err, ops.ErrInvalid) {
			t.Errorf("FAIL: %s: %v", what, err)
		}
	}
	if _, err := g.claim("jva", push, "suit", ""); err == nil {
		t.Errorf("FAIL: jva claimed a push setup on a repository they cannot give a role on")
	}
	if _, ok, _ := g.db.SetupStartByCode(push, time.Now()); !ok {
		t.Fatalf("FAIL: refusals consumed the start")
	}
	id, err := g.claim("dmestas", push, "suit", "maintainer")
	if err != nil {
		t.Fatal(err)
	}
	got := g.get("dmestas", id)
	if got.Setup == nil || got.Setup.Kind != "push" || got.Setup.Role != "maintainer" || strings.Join(got.Setup.Repos, ",") != "suit" || got.Setup.Pickup != pickup {
		t.Errorf("FAIL: the request's setup view: %+v", got.Setup)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestTwoClaimsAtOnceFileOneRequest(t *testing.T) {
	g := newStartRig(t)
	_, pickup := newPickupKey(t)
	code := g.begin("e-desktop", "read", pickup)
	var wg sync.WaitGroup
	var mu sync.Mutex
	won := 0
	for i := 0; i < 8; i++ {
		who := []string{"dmestas", "jva"}[i%2]
		wg.Add(1)
		go func() {
			defer wg.Done()
			if _, err := g.claim(who, code, "suit", ""); err == nil {
				mu.Lock()
				won++
				mu.Unlock()
			}
		}()
	}
	wg.Wait()
	total := len(g.requestsOf("dmestas")) + len(g.requestsOf("jva"))
	if won != 1 || total != 1 {
		t.Errorf("FAIL: %d claims won and %d requests were filed", won, total)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A claim that cannot file puts the start back.
func TestAClaimThatCannotBeFiledLeavesTheStart(t *testing.T) {
	g := newStartRig(t)
	_, pickup := newPickupKey(t)
	// Another request already holds the pickup code.
	other := g.begin("e-first", "read", pickup)
	second := g.begin("e-second", "read", pickup)
	if _, err := g.claim("dmestas", other, "suit", ""); err != nil {
		t.Fatal(err)
	}
	if _, err := g.claim("dmestas", second, "suit", ""); !errors.Is(err, ops.ErrInvalid) {
		t.Errorf("FAIL: a second start with the same pickup: %v", err)
	}
	if _, ok, _ := g.db.SetupStartByCode(second, time.Now()); !ok {
		t.Errorf("FAIL: the start was lost with the refusal")
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// The whole way: start, claim, approve, collect once.
func TestAClaimedStartIsApprovedAndCollectedOnceByTheKeyHolder(t *testing.T) {
	g := newStartRig(t)
	key, pickup := newPickupKey(t)
	code := g.begin("e-desktop", "read", pickup)
	// Before the claim there is nothing to collect.
	if status, _ := g.collectKey(key, "192.0.2.50"); status != 404 {
		t.Fatalf("FAIL: collect before the claim: %d", status)
	}
	id, err := g.claim("dmestas", code, "suit,ctx", "")
	if err != nil {
		t.Fatal(err)
	}
	if status, body := g.collectKey(key, "192.0.2.51"); status != 202 {
		t.Fatalf("FAIL: collect while the request waits: %d %s", status, body)
	}
	if err := g.decide("dmestas", id, true); err != nil {
		t.Fatalf("FAIL: approving a claimed request: %v", err)
	}
	a, ok := g.db.AccountByName("e-desktop")
	if !ok || a.Operator != "dmestas" {
		t.Fatalf("FAIL: the agent: %+v", a)
	}
	status, body := g.collectKey(key, "192.0.2.52")
	var got struct {
		Agent  string          `json:"agent"`
		Secret json.RawMessage `json:"secret"`
	}
	_ = json.Unmarshal([]byte(body), &got)
	if status != 200 || !strings.Contains(string(got.Secret), "qat_") || got.Agent != clientAsker("dmestas") {
		t.Fatalf("FAIL: the collect: %d (agent %q)", status, got.Agent)
	}
	if status, _ := g.collectKey(key, "192.0.2.53"); status != 410 {
		t.Errorf("FAIL: the second collect answered %d", status)
	}
	// Denying a claimed request needs no account behind its asker either.
	_, p2 := newPickupKey(t)
	id2, err := g.claim("dmestas", g.begin("e-other", "read", p2), "suit", "")
	if err != nil {
		t.Fatal(err)
	}
	if err := g.decide("dmestas", id2, false); err != nil {
		t.Errorf("FAIL: denying: %v", err)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestCodesNotHeldAreLimitedPerAccountAndPerAddressOnEveryDoor(t *testing.T) {
	g := newStartRig(t)
	var err error
	for i := 0; i <= setupMissesPerUser; i++ {
		_, err = g.look("jva", "ZZZZ-ZZZZ")
	}
	if !errors.Is(err, ops.ErrRateLimited) {
		t.Errorf("FAIL: the call past the account's misses: %v", err)
	}
	// A held account is held for a right code too.
	_, pickup := newPickupKey(t)
	code := g.begin("e-desktop", "read", pickup)
	if _, err := g.look("jva", code); !errors.Is(err, ops.ErrRateLimited) {
		t.Errorf("FAIL: a held account looked a code up: %v", err)
	}
	if _, err := g.look("dmestas", code); err != nil {
		t.Errorf("FAIL: another account was held: %v", err)
	}
	// A right code spends nothing.
	for i := 0; i < 3*setupMissesPerUser; i++ {
		if _, err := g.look("dmestas", code); err != nil {
			t.Fatalf("FAIL: a right code was counted: %v", err)
		}
	}
	// Per address: accounts that each stay under their own limit are held
	// together.
	var aerr error
	for i := 0; i <= setupMissesPerAddr; i++ {
		_, aerr = g.r.Invoke(ops.WithAddr(asUser(fmt.Sprint("p", i)), "203.0.113.77"), "setup.start.look", setupClaimLookIn{Code: "ZZZZ-ZZZZ"})
	}
	if !errors.Is(aerr, ops.ErrRateLimited) {
		t.Errorf("FAIL: the address was not held: %v", aerr)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// One address cannot hold more than its share; two addresses in one IPv6
// /64 are one address; another address still starts.
func TestOneAddressHoldsOnlyAShareOfTheStarts(t *testing.T) {
	g := newStartRig(t)
	_, pickup := newPickupKey(t)
	var status int
	for i := 0; i <= setupStartPerShare; i++ {
		g.clock = g.clock.Add(time.Minute) // under the per-minute rate
		status, _, _ = g.start(startBody(fmt.Sprint("a", i), "read", pickup), "198.51.100.200")
		if i < setupStartPerShare && status != 200 {
			t.Fatalf("FAIL: start %d answered %d", i, status)
		}
	}
	if status != 429 {
		t.Errorf("FAIL: a fourth live start from one address answered %d", status)
	}
	if status, _, _ := g.start(startBody("other", "read", pickup), "198.51.100.201"); status != 200 {
		t.Errorf("FAIL: a second address was held by the first: %d", status)
	}
	// Two addresses of one /64 share the limit.
	for i := 0; i < setupStartPerShare; i++ {
		g.clock = g.clock.Add(time.Minute)
		a := fmt.Sprintf("2001:db8:1:2::%x", i+1)
		if status, _, _ := g.start(startBody(fmt.Sprint("v", i), "read", pickup), "["+a+"]"); status != 200 {
			t.Fatalf("FAIL: v6 start %d answered %d", i, status)
		}
	}
	if status, _, _ := g.start(startBody("v-more", "read", pickup), "[2001:db8:1:2:ffff::9]"); status != 429 {
		t.Errorf("FAIL: another address in the same /64 started: %d", status)
	}
	if status, _, _ := g.start(startBody("v-else", "read", pickup), "[2001:db8:1:3::1]"); status != 200 {
		t.Errorf("FAIL: a different /64 was held: %d", status)
	}
	// A refusal for want of room needs no write: the lapsed rows stay
	// until a start that can be held sweeps them.
	if !t.Failed() {
		t.Log("ok")
	}
}

// What the key holder is told when the request was not approved carries
// no account name.
func TestACollectOfADeniedClaimedRequestNamesNobody(t *testing.T) {
	g := newStartRig(t)
	key, pickup := newPickupKey(t)
	id, err := g.claim("dmestas", g.begin("e-desktop", "read", pickup), "suit", "")
	if err != nil {
		t.Fatal(err)
	}
	if err := g.decide("dmestas", id, false); err != nil {
		t.Fatal(err)
	}
	status, body := g.collectKey(key, "192.0.2.60")
	if status != 410 || strings.Contains(body, "dmestas") || strings.Contains(body, clientAskerPrefix) {
		t.Errorf("FAIL: a denied claimed request collected as %d %s", status, body)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}