Skip to content
quarry Artifact [09b6b50dfe]
Login

Artifact [09b6b50dfe]

Artifact 09b6b50dfe573193998919c4c3eada49b0a626073dc999e2cce0d6be058109f0:


package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/hex"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"net/http"
	"net/http/httptest"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/events"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ingest"
	"craftdesign.group/quarry/internal/lease"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/xfer"
	"craftdesign.group/quarry/internal/zitcli"
)

// firstPushRig is the sync lane as serve wires it for a repository
// created empty (ticket #416): real stores made by `zit init`, the
// forge's token store as the credentials, the project-code cache the
// registrar keeps, and adoption on.
type firstPushRig struct {
	rg    *registrar
	base  string
	home  string
	token string
	dirs  map[string]string
	front *xfer.Fronter
	// probes counts every question the front put to the forge about
	// adopting a code, and claims the claims among them.
	probes, claims atomic.Int64
	// guarded is the project code each push was leased under, in order.
	guarded []string
	mu      sync.Mutex
}

func newFirstPushRig(t *testing.T, repos ...string) *firstPushRig {
	t.Helper()
	if _, err := exec.LookPath("zit"); err != nil {
		if os.Getenv("CI") == "true" {
			t.Fatalf("FAIL: zit not on PATH under CI")
		}
		t.Skip("zit not on PATH")
	}
	if v := zitcli.ProbeVersion(); !zitcli.VersionAtLeast(v, zitcli.AdoptProjectVersion) {
		t.Skipf("zit %q cannot adopt a project code; needs %s", v, zitcli.AdoptProjectVersion)
	}
	rg := newTestRegistrar(t, 25)
	rg.codes, rg.codeMu = map[string]string{}, &sync.Mutex{}
	db := rg.odb
	db.SyncProjectCode = func(repo string) (string, bool) {
		rg.codeMu.Lock()
		defer rg.codeMu.Unlock()
		c, ok := rg.codes[repo]
		return c, ok
	}
	if err := db.AddAccount("jan", "pw-jan-12345", false); err != nil {
		t.Fatal(err)
	}
	if err := db.AddAccount("kim", "pw-kim-12345", false); err != nil {
		t.Fatal(err)
	}
	r := &firstPushRig{rg: rg, home: t.TempDir(), dirs: map[string]string{}}
	f := xfer.New()
	t.Cleanup(f.Close)
	for _, name := range repos {
		dir := t.TempDir()
		r.zit(t, dir, "init")
		code, err := xfer.ProjectCode(dir)
		if err != nil {
			t.Fatal(err)
		}
		vis := oltp.Private
		if strings.HasPrefix(name, "pub") {
			vis = oltp.Public
		}
		if err := db.AddRepo(name, dir, "", vis); err != nil {
			t.Fatal(err)
		}
		if !strings.HasPrefix(name, "nolet") {
			if err := db.SetCaps("jan", name, "oiy"); err != nil {
				t.Fatal(err)
			}
		}
		rg.codes[name] = code
		ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
		if err != nil {
			t.Fatal(err)
		}
		t.Cleanup(func() { ix.Close() })
		if err := db.SetRepoCode(name, code); err != nil {
			t.Fatal(err)
		}
		rg.deps.Repos.Put(name, &api.Repo{Code: code, Index: ix})
		f.Register(name, dir)
		r.dirs[name] = dir
	}
	rg.refreshPrivate()
	rg.doors = &tokenDoors{db: db}
	f.SetAuth(db)
	f.SetLoginLimit(rg.doors)
	f.SetReadGate(rg.SyncReadGate)
	ad := rg.adoption(func(repo string) (string, bool) { d, ok := r.dirs[repo]; return d, ok })
	inner := *ad
	ad.Empty = func(repo string) bool { r.probes.Add(1); return inner.Empty(repo) }
	ad.Recorded = func(repo string) string { r.probes.Add(1); return inner.Recorded(repo) }
	ad.Claim = func(repo, code string) xfer.Claim { r.probes.Add(1); r.claims.Add(1); return inner.Claim(repo, code) }
	ad.Adopted = func(repo string) bool { r.probes.Add(1); return inner.Adopted(repo) }
	f.SetAdoption(ad)
	r.front = f
	mux := http.NewServeMux()
	mux.Handle("POST /r/{repo}", f)
	srv := httptest.NewServer(mux)
	t.Cleanup(srv.Close)
	r.base = srv.URL + "/r/"
	v, _, err := db.MintToken("jan", oltp.TokenSpec{Name: "t", Scopes: []string{oltp.ScopeSync}})
	if err != nil {
		t.Fatal(err)
	}
	r.token = v
	return r
}

// lease puts the pushes under a real lease, as serve does, and records
// the code each was leased under.
func (r *firstPushRig) lease(t *testing.T) *lease.Manager {
	t.Helper()
	ev, err := events.Start(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(ev.Close)
	m, err := lease.New(ev.JS(), "node-a", 5*time.Second)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(m.Close)
	r.rg.leases = m
	r.front.SetGuard(func(repo string, proxy func() error) error {
		r.mu.Lock()
		r.guarded = append(r.guarded, r.rg.codeOr(repo, ""))
		r.mu.Unlock()
		return r.rg.guardPush(repo, proxy)
	})
	return m
}

// fold brings the repository's index up to its store, as the forge's
// poll does after a push.
func (r *firstPushRig) fold(t *testing.T, repo string) {
	t.Helper()
	ev, err := events.Start(t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	defer ev.Close()
	e, _ := r.rg.deps.Repos.Get(repo)
	if _, err := ingest.NewPipeline(zitcli.New(r.dirs[repo]), ev, e.Index, repo, "test").Reindex(); err != nil {
		t.Fatalf("FAIL: folding %s: %v", repo, err)
	}
}

func (r *firstPushRig) zit(t *testing.T, dir string, args ...string) (string, error) {
	t.Helper()
	cmd := exec.Command("zit", args...)
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "HOME="+r.home, "ZIT_PASSWORD=")
	out, err := cmd.CombinedOutput()
	return string(out), err
}

func (r *firstPushRig) mustZit(t *testing.T, dir string, args ...string) string {
	t.Helper()
	out, err := r.zit(t, dir, args...)
	if err != nil {
		t.Fatalf("FAIL: zit %v: %v\n%s", args, err, out)
	}
	return out
}

// project makes a checkout of a project of its own with n check-ins.
func (r *firstPushRig) project(t *testing.T, n int) string {
	t.Helper()
	dir := t.TempDir()
	r.mustZit(t, dir, "init")
	for i := 0; i < n; i++ {
		r.commit(t, dir)
	}
	return dir
}

func (r *firstPushRig) commit(t *testing.T, dir string) {
	t.Helper()
	name := filepath.Base(t.TempDir()) + ".txt"
	if err := os.WriteFile(filepath.Join(dir, name), []byte(name+"\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	r.mustZit(t, dir, "add", name)
	r.mustZit(t, dir, "commit", "-m", "c "+name)
}

func (r *firstPushRig) push(t *testing.T, dir, repo, user, token string) (string, error) {
	t.Helper()
	return r.zit(t, dir, "push", r.base+repo, "--user", user, "--password", token)
}

// state is a store's project code and artifact count.
func (r *firstPushRig) state(t *testing.T, dir string) (string, int) {
	t.Helper()
	code, n, err := xfer.StoreState(dir)
	if err != nil {
		t.Fatal(err)
	}
	return code, n
}

func (r *firstPushRig) checkins(t *testing.T, dir string) int {
	t.Helper()
	out := r.mustZit(t, dir, "timeline", "-n", "100", "-F", "oneline")
	n := 0
	for _, line := range strings.Split(out, "\n") {
		if strings.TrimSpace(line) != "" {
			n++
		}
	}
	return n
}

// The first push of an existing project into a repository created
// empty is authenticated against the pusher's project code, the store
// takes that code, and every cache of it follows before the next push.
func TestAnEmptyRepositoryAdoptsTheFirstPushedProject(t *testing.T) {
	r := newFirstPushRig(t, "vault", "other")
	proj := r.project(t, 2)
	projCode, _ := r.state(t, proj)
	orig, n0 := r.state(t, r.dirs["vault"])
	if n0 != 0 || orig == projCode {
		t.Fatalf("FAIL: rig: vault holds %d artifacts, code %.8s", n0, orig)
	}

	if out, err := r.push(t, proj, "vault", "jan", r.token); err != nil {
		t.Fatalf("FAIL: the first push was refused: %v\n%s", err, out)
	}
	code, n1 := r.state(t, r.dirs["vault"])
	if code != projCode || n1 == 0 {
		t.Fatalf("FAIL: after the first push vault has code %.8s and %d artifacts; want %.8s and some", code, n1, projCode)
	}
	if got, _ := r.rg.deps.Repos.Get("vault"); got.Code != projCode {
		t.Errorf("FAIL: the registry entry still carries %.8s", got.Code)
	}
	if c := r.rg.codeOr("vault", ""); c != projCode {
		t.Errorf("FAIL: the code cache still carries %.8s", c)
	}

	// The second push is judged by the adopted code: a stale cache
	// fails here.
	r.commit(t, proj)
	if out, err := r.push(t, proj, "vault", "jan", r.token); err != nil {
		t.Fatalf("FAIL: the second push was refused: %v\n%s", err, out)
	}
	_, n2 := r.state(t, r.dirs["vault"])
	if n2 <= n1 {
		t.Errorf("FAIL: the second push stored nothing (%d then %d artifacts)", n1, n2)
	}

	// A fresh clone has the adopted code and all three check-ins.
	cl := filepath.Join(t.TempDir(), "clone")
	if out, err := r.zit(t, ".", "clone", r.base+"vault", cl, "--user", "jan", "--password", r.token); err != nil {
		t.Fatalf("FAIL: clone: %v\n%s", err, out)
	}
	if c, _ := r.state(t, cl); c != projCode {
		t.Errorf("FAIL: the clone has code %.8s, want %.8s", c, projCode)
	}
	if got, want := r.checkins(t, cl), r.checkins(t, proj); got != want {
		t.Errorf("FAIL: the clone has %d timeline lines, the pusher %d", got, want)
	}

	// A different project pushed to the now non-empty repository is
	// refused, and the store is as it was.
	r.fold(t, "vault")
	other := r.project(t, 1)
	claimsBefore := r.claims.Load()
	if out, err := r.push(t, other, "vault", "jan", r.token); err == nil {
		t.Errorf("FAIL: a different project was pushed into a non-empty repository:\n%s", out)
	}
	if c, n := r.state(t, r.dirs["vault"]); c != projCode || n != n2 {
		t.Errorf("FAIL: the refused push changed vault to %.8s, %d artifacts (was %d)", c, n, n2)
	}
	// ...and was stopped by the forge's own check, before zit was asked.
	// A forge that left it to zit would have claimed the code first.
	if got := r.claims.Load(); got != claimsBefore {
		t.Errorf("FAIL: a project code was claimed for a repository that is not empty")
	}
	if rec, _ := r.rg.odb.RepoCode("vault"); rec != projCode {
		t.Errorf("FAIL: vault's row records %.8s, want %.8s", rec, projCode)
	}

	// The same project is held by vault now, so another empty repository
	// does not take it: chat and the write lease are keyed by the code.
	otherOrig, _ := r.state(t, r.dirs["other"])
	taken, err := r.push(t, proj, "other", "jan", r.token)
	if err == nil {
		t.Errorf("FAIL: a project code another repository holds was adopted:\n%s", taken)
	}
	if bad, _ := r.push(t, proj, "other", "jan", "qat_wrong"); bad == taken {
		t.Errorf("FAIL: a proved caller whose project is already hosted is told what any bad credential is told")
	}
	if c, n := r.state(t, r.dirs["other"]); c != otherOrig || n != 0 {
		t.Errorf("FAIL: the refused push changed other to %.8s, %d artifacts", c, n)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A first push that does not verify is refused exactly as any other
// that does not, whoever it names, and leaves the empty store alone.
func TestAnUnverifiedFirstPushIsRefusedAlikeAndChangesNothing(t *testing.T) {
	r := newFirstPushRig(t, "empty", "full")
	// "full" holds a project already, so a push of another is judged
	// the ordinary way.
	seed := r.project(t, 1)
	if out, err := r.push(t, seed, "full", "jan", r.token); err != nil {
		t.Fatalf("FAIL: seeding: %v\n%s", err, out)
	}
	proj := r.project(t, 1)
	orig, _ := r.state(t, r.dirs["empty"])

	refusal := func(repo, user, token string) string {
		out, err := r.push(t, proj, repo, user, token)
		if err == nil {
			t.Fatalf("FAIL: %s as %s with %.6s... was accepted:\n%s", repo, user, token, out)
		}
		return strings.ReplaceAll(strings.TrimSpace(out), user, "USER")
	}
	// The wrong token for an account that exists, the right token for
	// an account that holds none, and a name that is no account.
	wrong := refusal("empty", "jan", "qat_wrong")
	if got := refusal("empty", "kim", r.token); got != wrong {
		t.Errorf("FAIL: an account without the token is told differently from a wrong token:\n%s\n%s", got, wrong)
	}
	if got := refusal("empty", "nobody", "qat_wrong"); got != wrong {
		t.Errorf("FAIL: a name that is no account is told differently:\n%s\n%s", got, wrong)
	}
	if got := refusal("full", "jan", "qat_wrong"); got != wrong {
		t.Errorf("FAIL: an empty repository refuses differently from a non-empty one:\n%s\n%s", got, wrong)
	}
	if c, n := r.state(t, r.dirs["empty"]); c != orig || n != 0 {
		t.Errorf("FAIL: refused pushes left empty at %.8s with %d artifacts", c, n)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A malformed project code in the card derives no secret to check, so
// it is refused like any unverified card and adopts nothing.
func TestAMalformedProjectCodeIsNeverAdopted(t *testing.T) {
	for _, code := range []string{"", "abc", strings.Repeat("g", 40), strings.ToUpper(strings.Repeat("a", 40)), strings.Repeat("a", 41)} {
		if xfer.ValidProjectCode(code) {
			t.Errorf("FAIL: %q taken for a project code", code)
		}
	}
	for _, code := range []string{strings.Repeat("a", 40), strings.Repeat("0f", 32)} {
		if !xfer.ValidProjectCode(code) {
			t.Errorf("FAIL: %q refused as a project code", code)
		}
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// The first contact must be a push: a pull or a sync of a project the
// empty repository does not hold is refused by zit, and changes nothing.
func TestAFirstPullOrSyncIsRefusedByDesign(t *testing.T) {
	r := newFirstPushRig(t, "empty")
	proj := r.project(t, 1)
	orig, _ := r.state(t, r.dirs["empty"])
	for _, verb := range []string{"pull", "sync"} {
		out, err := r.zit(t, proj, verb, r.base+"empty", "--user", "jan", "--password", r.token)
		if err == nil {
			t.Errorf("FAIL: a first %s was accepted:\n%s", verb, out)
		}
		if c, n := r.state(t, r.dirs["empty"]); c != orig || n != 0 {
			t.Errorf("FAIL: a first %s left empty at %.8s with %d artifacts", verb, c, n)
		}
	}
	// ...and the push that follows is accepted.
	if out, err := r.push(t, proj, "empty", "jan", r.token); err != nil {
		t.Errorf("FAIL: the push after the refused pull: %v\n%s", err, out)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// post sends a raw sync body and returns what came back, status and all.
func (r *firstPushRig) post(t *testing.T, repo, body string) string {
	t.Helper()
	resp, err := http.Post(r.base+repo, "application/x-fossil", bytes.NewReader([]byte(body)))
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()
	b, _ := io.ReadAll(resp.Body)
	return resp.Status + "\n" + string(b)
}

// A caller who has proved nothing starts nothing: a push naming another
// project, anonymous or with a login that does not verify, is put no
// question about adopting it, whether the repository is empty and public,
// private, or does not exist, and a private repository answers exactly as
// one that is not there.
func TestAnUnprovedFirstPushAsksNothingAndPrivateIsAbsent(t *testing.T) {
	r := newFirstPushRig(t, "pubempty", "vault")
	foreign := strings.Repeat("ab", 20)
	server := strings.Repeat("cd", 20)
	cards := map[string]string{
		"anonymous":  "push " + server + " " + foreign + "\n",
		"badlogin":   "login jan 0123456789abcdef " + strings.Repeat("0", 40) + "\npush " + server + " " + foreign + "\n",
		"nonmember":  "login nobody 0123456789abcdef " + strings.Repeat("1", 40) + "\npush " + server + " " + foreign + "\n",
		"mixedcodes": "login jan 0123456789abcdef " + strings.Repeat("2", 40) + "\npush " + server + " " + foreign + "\npush " + server + " " + server + "\n",
	}
	for name, body := range cards {
		got := map[string]string{}
		for _, repo := range []string{"pubempty", "vault", "ghost"} {
			got[repo] = r.post(t, repo, body)
		}
		if got["vault"] != got["ghost"] {
			t.Errorf("FAIL: %s: a private repository answers differently from one that is not there:\n%s\n%s", name, got["vault"], got["ghost"])
		}
	}
	if n := r.probes.Load(); n != 0 {
		t.Errorf("FAIL: %d questions were put to the forge for callers who proved nothing", n)
	}
	// The same through a real client with a wrong token.
	proj := r.project(t, 1)
	for _, repo := range []string{"pubempty", "vault", "ghost"} {
		r.push(t, proj, repo, "jan", "qat_wrong")
	}
	if n := r.probes.Load(); n != 0 {
		t.Errorf("FAIL: %d questions were put to the forge for a wrong token", n)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// The push is leased under the project code the forge holds when it
// arrives, and once the store has adopted another the lease moves to it
// and the old key is given up.
func TestTheLeaseMovesToTheAdoptedCode(t *testing.T) {
	r := newFirstPushRig(t, "vault")
	m := r.lease(t)
	orig, _ := r.state(t, r.dirs["vault"])
	proj := r.project(t, 2)
	adopted, _ := r.state(t, proj)

	if out, err := r.push(t, proj, "vault", "jan", r.token); err != nil {
		t.Fatalf("FAIL: the first push: %v\n%s", err, out)
	}
	r.mu.Lock()
	first := append([]string(nil), r.guarded...)
	r.mu.Unlock()
	if len(first) == 0 || first[0] != orig {
		t.Errorf("FAIL: the first push was leased under %v, want %.8s first", first, orig)
	}
	if m.Holds(orig) {
		t.Errorf("FAIL: the lease on the old code is still held")
	}
	r.commit(t, proj)
	if out, err := r.push(t, proj, "vault", "jan", r.token); err != nil {
		t.Fatalf("FAIL: the second push: %v\n%s", err, out)
	}
	r.mu.Lock()
	later := append([]string(nil), r.guarded[len(first):]...)
	r.mu.Unlock()
	if len(later) == 0 {
		t.Fatalf("FAIL: the second push took no lease")
	}
	for _, c := range later {
		if c != adopted {
			t.Errorf("FAIL: a later push was leased under %.8s, want %.8s", c, adopted)
		}
	}
	if !m.Holds(adopted) || m.Holds(orig) {
		t.Errorf("FAIL: holds adopted=%v old=%v", m.Holds(adopted), m.Holds(orig))
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A cache that did not follow the store is put right by the next push
// that names the store's real code, not left to refuse every push.
func TestAStaleCodeCacheHealsOnTheNextPush(t *testing.T) {
	r := newFirstPushRig(t, "vault")
	orig, _ := r.state(t, r.dirs["vault"])
	proj := r.project(t, 1)
	adopted, _ := r.state(t, proj)
	if out, err := r.push(t, proj, "vault", "jan", r.token); err != nil {
		t.Fatalf("FAIL: the first push: %v\n%s", err, out)
	}
	// As if the read after the adoption had failed: the caches say the
	// old code, the row says the claim.
	r.rg.codeMu.Lock()
	r.rg.codes["vault"] = orig
	r.rg.codeMu.Unlock()
	r.commit(t, proj)
	if out, err := r.push(t, proj, "vault", "jan", r.token); err != nil {
		t.Fatalf("FAIL: a push after a stale cache was refused: %v\n%s", err, out)
	}
	if c := r.rg.codeOr("vault", ""); c != adopted {
		t.Errorf("FAIL: the cache holds %.8s after the heal, want %.8s", c, adopted)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A store that cannot be read when a push is answered is read again by
// itself.
func TestACodeThatCannotBeReadIsReadAgain(t *testing.T) {
	r := newFirstPushRig(t, "vault")
	dir := filepath.Join(t.TempDir(), "later")
	if err := os.MkdirAll(dir, 0o755); err != nil {
		t.Fatal(err)
	}
	r.rg.codeMu.Lock()
	r.rg.codes["late"] = "stale"
	r.rg.codeMu.Unlock()
	if r.rg.refreshCode("late", dir) {
		t.Fatalf("FAIL: an unreadable store was reported read")
	}
	r.mustZit(t, dir, "init")
	want, _ := r.state(t, dir)
	deadline := time.Now().Add(15 * time.Second)
	for r.rg.codeOr("late", "") != want {
		if time.Now().After(deadline) {
			t.Fatalf("FAIL: the code was never read again")
		}
		time.Sleep(100 * time.Millisecond)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

type purgeRecorder struct {
	api.ChatPlane
	mu     sync.Mutex
	purged []string
}

func (p *purgeRecorder) PurgeChannel(code string) error {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.purged = append(p.purged, code)
	return nil
}

// What was keyed by the code the store had before adopting another goes
// with it: its chat channel and robot lines are erased, only for the old
// code, and a code another repository still has is left alone.
func TestAdoptionErasesWhatWasKeyedByTheOldCode(t *testing.T) {
	r := newFirstPushRig(t, "vault", "twin")
	chat := &purgeRecorder{}
	r.rg.deps.Chat = chat
	orig, _ := r.state(t, r.dirs["vault"])
	if err := r.rg.odb.RecordRobotLine(orig, 1, time.Now().UTC().Format(time.RFC3339Nano)); err != nil {
		t.Fatal(err)
	}
	proj := r.project(t, 1)
	if out, err := r.push(t, proj, "vault", "jan", r.token); err != nil {
		t.Fatalf("FAIL: push: %v\n%s", err, out)
	}
	chat.mu.Lock()
	purged := append([]string(nil), chat.purged...)
	chat.mu.Unlock()
	if len(purged) != 1 || purged[0] != orig {
		t.Errorf("FAIL: channels erased = %v, want only the old code", purged)
	}
	if n, err := r.rg.odb.RobotLines(orig, 0); err != nil || n != 0 {
		t.Errorf("FAIL: %d robot lines remain under the old code (%v)", n, err)
	}
	// A repository that still has the old code keeps its channel.
	r.rg.codeMu.Lock()
	r.rg.codes["twin"] = "kept"
	r.rg.codeMu.Unlock()
	chat.purged = nil
	r.rg.retire("vault", "kept")
	if len(chat.purged) != 0 {
		t.Errorf("FAIL: a channel another repository still has was erased")
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// signed frames rest as a request logged in as user with token, signing
// with the secret derived from project code (RFC ยง6.1-6.3), the way a
// sync client does; with a garbage token it is a card that does not
// verify anywhere.
func signed(code, user, token, rest string) string {
	nonce := sha1.Sum([]byte(rest))
	nonceHex := hex.EncodeToString(nonce[:])
	secret := xfer.SharedSecret(code, user, token)
	sig := sha1.Sum([]byte(nonceHex + secret))
	return "login " + user + " " + nonceHex + " " + hex.EncodeToString(sig[:]) + "\n" + rest
}

// A login that verifies only under the project code its own card names
// is no login: replayed against a private mirror, a private repository
// that holds something, or an empty one its account has no letter on, it
// is answered byte for byte as a card that does not verify, and claims
// nothing.
func TestALoginThatVerifiesOnlyUnderTheCardsCodeConfersNothing(t *testing.T) {
	r := newFirstPushRig(t, "mirror", "full", "nolet")
	seed := r.project(t, 1)
	for _, repo := range []string{"mirror", "full"} {
		// Each holds a different project, as a mirror and a repository
		// that has been pushed to do.
		p := r.project(t, 1)
		if out, err := r.push(t, p, repo, "jan", r.token); err != nil {
			t.Fatalf("FAIL: seeding %s: %v\n%s", repo, err, out)
		}
		r.fold(t, repo)
	}
	_ = seed
	r.front.SetWriteGate(func(repo string) (bool, string) { return repo != "mirror", "https://upstream.example/x" })
	claims := r.claims.Load()

	// The project of the captured request: a code no repository here has.
	foreign := strings.Repeat("ab", 20)
	server := strings.Repeat("cd", 20)
	rest := "push " + server + " " + foreign + "\npull " + server + " " + foreign + "\n"
	for _, repo := range []string{"mirror", "full", "nolet"} {
		replay := r.post(t, repo, signed(foreign, "jan", r.token, rest))
		unverified := r.post(t, repo, signed(foreign, "jan", "qat_not_the_token", rest))
		if replay != unverified {
			t.Errorf("FAIL: %s: a card verifying under its own project code was answered differently from one that verifies nowhere:\n%s\n---\n%s", repo, replay, unverified)
		}
		anon := r.post(t, repo, rest)
		if replay != anon && strings.Contains(replay, "igot") {
			t.Errorf("FAIL: %s: the replay was served a pull", repo)
		}
	}
	// Verified, with the letter, the forge looks at emptiness and finds
	// the two that hold something not empty; nothing is claimed.
	if got := r.claims.Load(); got != claims {
		t.Errorf("FAIL: %d codes were claimed for repositories that hold something or are not the account's", got-claims)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// Rows that cannot hold an unrecorded code do not stand in the way of a
// first push: a failed import is a record, an import still running has no
// store to compare yet.
func TestAFailedOrCloningRowDoesNotBlockAdoption(t *testing.T) {
	r := newFirstPushRig(t, "vault")
	for name, state := range map[string]string{"failedimp": oltp.Failed, "cloningimp": oltp.Cloning} {
		if err := r.rg.odb.AddRepo(name, t.TempDir(), "", oltp.Public); err != nil {
			t.Fatal(err)
		}
		if err := r.rg.odb.SetRepoState(name, state, "x"); err != nil {
			t.Fatal(err)
		}
	}
	proj := r.project(t, 1)
	projCode, _ := r.state(t, proj)
	if out, err := r.push(t, proj, "vault", "jan", r.token); err != nil {
		t.Fatalf("FAIL: with a failed and a cloning row present the first push was refused: %v\n%s", err, out)
	}
	if c, _ := r.state(t, r.dirs["vault"]); c != projCode {
		t.Errorf("FAIL: vault has code %.8s, want %.8s", c, projCode)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A Ready row with no recorded code is given the one its store has, so it
// stops being a possible holder of a code nobody can name.
func TestAReadyRowWithNoCodeIsBackfilled(t *testing.T) {
	r := newFirstPushRig(t, "vault")
	dir := t.TempDir()
	r.mustZit(t, dir, "init")
	want, _ := r.state(t, dir)
	if err := r.rg.odb.AddRepo("older", dir, "", oltp.Public); err != nil {
		t.Fatal(err)
	}
	if got, _ := r.rg.odb.RepoCode("older"); got != "" {
		t.Fatalf("FAIL: rig: older already records %q", got)
	}
	r.rg.backfillCodes()
	if got, _ := r.rg.odb.RepoCode("older"); got != want {
		t.Errorf("FAIL: older records %.8s after the backfill, want %.8s", got, want)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

// A shared project code is logged when either repository is private and
// not when both are public, and only once.
func TestASharedCodeWarnsOnlyWhenOneSidePrivate(t *testing.T) {
	r := newFirstPushRig(t, "a", "b")
	var buf bytes.Buffer
	log.SetOutput(&buf)
	t.Cleanup(func() { log.SetOutput(os.Stderr) })
	odb := r.rg.odb
	add := func(name, vis string) {
		if err := odb.AddRepo(name, t.TempDir(), "", vis); err != nil {
			t.Fatal(err)
		}
		if err := odb.SetRepoCode(name, "SHARED"); err != nil {
			t.Fatal(err)
		}
	}
	add("pub1", oltp.Public)
	add("pub2", oltp.Public)
	r.rg.warnSharedCode("pub2", "SHARED")
	if strings.Contains(buf.String(), "WARNING") {
		t.Errorf("FAIL: two public repositories sharing a code were warned about")
	}
	add("priv", oltp.Private)
	r.rg.warnSharedCode("priv", "SHARED")
	r.rg.warnSharedCode("priv", "SHARED")
	got := buf.String()
	if !strings.Contains(got, "priv") || !strings.Contains(got, "pub1") {
		t.Errorf("FAIL: no warning naming both repositories: %q", got)
	}
	if n := strings.Count(got, "WARNING: repositories priv (private) and pub1"); n != 1 {
		t.Errorf("FAIL: the pair priv/pub1 was logged %d times, want once", n)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}