Skip to content
quarry Artifact [68925a3d5e]
Login

Artifact [68925a3d5e]

Artifact 68925a3d5e9fb187116fa7fb37778e9aef1fc21eab38d612f4e390f79166d8ef:


package api

import (
	"errors"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/events"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ingest"
	"craftdesign.group/quarry/internal/ops"
	"craftdesign.group/quarry/internal/zitcli"
)

// noChecks requires nothing anywhere: an ungated target.
type noChecks struct{}

func (noChecks) RequiredChecks(string, string) []string { return nil }

// strataStore is a real store with a two-rung strata, low onto trunk
// and high onto low, and an unrelated landing, other onto trunk; every
// branch touches its own file. Landing ids are keyed by branch.
func strataStore(t *testing.T) (dir string, ids map[string]string, ix *index.Index, pipeSync func() (int, error)) {
	t.Helper()
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	dir = t.TempDir()
	zrun(t, dir, "init", ".")
	zwrite(t, dir, "f.txt", "root\n")
	zrun(t, dir, "add", "f.txt")
	zrun(t, dir, "commit", "-m", "root", "--user", "u")
	ids = map[string]string{}
	for _, b := range []struct{ branch, from string }{{"low", "trunk"}, {"high", "low"}, {"other", "trunk"}} {
		zrun(t, dir, "update", b.from)
		zwrite(t, dir, b.branch+".txt", b.branch+"\n")
		zrun(t, dir, "add", b.branch+".txt")
		zrun(t, dir, "commit", "-m", b.branch, "--branch", b.branch, "--user", "u")
		id, err := zitcli.New(dir).LandingOpen(testToken, "dan", b.branch, b.from, "land "+b.branch, "")
		if err != nil {
			t.Fatal(err)
		}
		ids[b.branch] = id
	}
	ev, err := events.Start(filepath.Join(t.TempDir(), "nats"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(ev.Close)
	ix, err = index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	pipe := ingest.NewPipeline(zitcli.New(dir), ev, ix, "test", "n1")
	if _, err := pipe.Sync(); err != nil {
		t.Fatal(err)
	}
	return dir, ids, ix, pipe.Sync
}

// ageOf reads a landing's merge verdict the way every caller does.
func ageOf(t *testing.T, d Deps, ix *index.Index, id string) mergeAge {
	t.Helper()
	l, found, err := ix.LandingByPrefix(id)
	if err != nil || !found {
		t.Fatalf("no landing %s: %v", id, err)
	}
	return d.mergeAgeOf("test", l)
}

func isStaleRefusal(err error) bool {
	var ref *ops.Refusal
	return errors.As(err, &ref) && ref.Code == RefusalStaleVerdict && errors.Is(err, ops.ErrForbidden)
}

func TestAMergeTestedOnTheTargetAsItIsIsFresh(t *testing.T) {
	dir, ids, ix, pipeSync := premergeStore(t)
	root := ztip(t, dir, "trunk")
	ci := &mergeRuns{pairRuns: &pairRuns{runs: map[string][]CIRun{}}}
	ci.green(ztip(t, dir, "one"), root)
	d := Deps{Version: "v", Checks: mergeChecks{}, CI: ci, Repos: NewRepos(map[string]*Repo{
		"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}
	if a := ageOf(t, d, ix, ids[0]); a.Stale || a.Tested != root || a.Behind != 0 {
		t.Fatalf("FAIL: a merge tested on trunk's tip reads %+v, want fresh against %.12s", a, root)
	}
	if !t.Failed() {
		t.Log("ok: tested on the tip trunk has now is fresh")
	}
}

// dmestas's rule: a target that moved under a landing voids its merge
// verdict. Four landings accepted within a minute were each tested
// against the same old trunk; the second one here is that case.
func TestAnUnrelatedLandingMakesTheMergeStaleAndTheAcceptWaitsForARerun(t *testing.T) {
	dir, ids, ix, pipeSync := premergeStore(t)
	root := ztip(t, dir, "trunk")
	ci := &mergeRuns{pairRuns: &pairRuns{runs: map[string][]CIRun{}}}
	ci.green(ztip(t, dir, "one"), root)
	ci.green(ztip(t, dir, "two"), root)
	d := Deps{Version: "v", Checks: mergeChecks{}, CI: ci, Repos: NewRepos(map[string]*Repo{
		"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}

	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[0]}, "accept"); err != nil {
		t.Fatalf("FAIL: the first accept of a fresh green merge failed: %v", err)
	}
	a := ageOf(t, d, ix, ids[1])
	if !a.Stale || a.Tested != root || a.Behind < 1 {
		t.Fatalf("FAIL: after one landed, two reads %+v, want stale against %.12s and behind", a, root)
	}
	_, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[1]}, "accept")
	if !isStaleRefusal(err) {
		t.Fatalf("FAIL: an accept over a stale verdict answered %v, want the %s refusal", err, RefusalStaleVerdict)
	}
	// Overriding is the existing gate's: an owner or an admin, with a
	// reason. A plain committer's reason is not enough.
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[1], Override: "hotfix"}, "accept"); !errors.Is(err, ops.ErrForbidden) {
		t.Fatalf("FAIL: a committer overrode a stale verdict: %v", err)
	}
	// The re-run: the merge tested again with trunk as it is now.
	now := ztip(t, dir, "trunk")
	ci.green(ztip(t, dir, "two"), now)
	if a := ageOf(t, d, ix, ids[1]); a.Stale || a.Tested != now {
		t.Fatalf("FAIL: after the re-run two reads %+v, want fresh against %.12s", a, now)
	}
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[1]}, "accept"); err != nil {
		t.Fatalf("FAIL: the accept after a re-run was refused: %v", err)
	}
	if !t.Failed() {
		t.Log("ok: stale is refused, the re-run makes it fresh, and it lands")
	}
}

func TestAnAdminMayAcceptOverAStaleVerdictSayingWhy(t *testing.T) {
	dir, ids, ix, pipeSync := premergeStore(t)
	root := ztip(t, dir, "trunk")
	ci := &mergeRuns{pairRuns: &pairRuns{runs: map[string][]CIRun{}}}
	ci.green(ztip(t, dir, "one"), root)
	ci.green(ztip(t, dir, "two"), root)
	d := Deps{Version: "v", Checks: mergeChecks{}, CI: ci, Repos: NewRepos(map[string]*Repo{
		"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[0]}, "accept"); err != nil {
		t.Fatal(err)
	}
	if _, err := d.landingSay(as("dan", "ia"), landingSayIn{Repo: "test", ID: ids[1], Override: "release cut"}, "accept"); err != nil {
		t.Fatalf("FAIL: an admin's stated override of a stale verdict was refused: %v", err)
	}
	if !t.Failed() {
		t.Log("ok: the override the checks gate has applies to a stale verdict")
	}
}

// A target with no required checks has no gate to close: the accept
// lands and its merge message records which trunk was tested.
func TestAStaleVerdictOnAnUngatedTargetLandsAndSaysSo(t *testing.T) {
	dir, ids, ix, pipeSync := premergeStore(t)
	root := ztip(t, dir, "trunk")
	ci := &mergeRuns{pairRuns: &pairRuns{runs: map[string][]CIRun{}}}
	ci.green(ztip(t, dir, "one"), root)
	ci.green(ztip(t, dir, "two"), root)
	d := Deps{Version: "v", Checks: noChecks{}, CI: ci, Repos: NewRepos(map[string]*Repo{
		"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[0]}, "accept"); err != nil {
		t.Fatal(err)
	}
	if !ageOf(t, d, ix, ids[1]).Stale {
		t.Fatal("FAIL: two should read stale once one landed")
	}
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[1]}, "accept"); err != nil {
		t.Fatalf("FAIL: an ungated target refused a stale verdict: %v", err)
	}
	if out := zrun(t, dir, "artifact", ztip(t, dir, "trunk")); !strings.Contains(out, root[:12]) {
		t.Errorf("FAIL: the merge's message does not name the trunk its checks tested:\n%s", out)
	}
	if !t.Failed() {
		t.Log("ok: ungated, the stale accept lands and its record names the tested trunk")
	}
}

// A strata's upper rung was tested against the rung below. When that
// rung lands, trunk moved only by the strata's own work, which the
// upper rung's test already held: the verdict stands.
func TestAStrataRungStaysFreshWhenOnlyItsLowerRungLanded(t *testing.T) {
	dir, ids, ix, pipeSync := strataStore(t)
	root := ztip(t, dir, "trunk")
	ci := &mergeRuns{pairRuns: &pairRuns{runs: map[string][]CIRun{}}}
	ci.green(ztip(t, dir, "low"), root)
	ci.green(ztip(t, dir, "high"), ztip(t, dir, "low"))
	d := Deps{Version: "v", Checks: mergeChecks{}, CI: ci, Repos: NewRepos(map[string]*Repo{
		"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}
	if a := ageOf(t, d, ix, ids["high"]); a.Stale {
		t.Fatalf("FAIL: high reads %+v before anything landed, want fresh", a)
	}
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids["low"]}, "accept"); err != nil {
		t.Fatalf("FAIL: the lower rung's accept failed: %v", err)
	}
	if a := ageOf(t, d, ix, ids["high"]); a.Stale {
		t.Fatalf("FAIL: high reads %+v after only its lower rung landed, want fresh", a)
	}
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids["high"]}, "accept"); err != nil {
		t.Fatalf("FAIL: the upper rung's accept was refused: %v", err)
	}
	if !t.Failed() {
		t.Log("ok: the strata lands rung by rung on the verdicts it already had")
	}
}

// The same strata, but other landed on trunk too: the upper rung's
// test never met other's work, so its verdict is stale.
func TestAStrataRungGoesStaleWhenAnythingElseLandedToo(t *testing.T) {
	dir, ids, ix, pipeSync := strataStore(t)
	root := ztip(t, dir, "trunk")
	ci := &mergeRuns{pairRuns: &pairRuns{runs: map[string][]CIRun{}}}
	ci.green(ztip(t, dir, "low"), root)
	ci.green(ztip(t, dir, "other"), root)
	ci.green(ztip(t, dir, "high"), ztip(t, dir, "low"))
	d := Deps{Version: "v", Checks: mergeChecks{}, CI: ci, Repos: NewRepos(map[string]*Repo{
		"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids["low"]}, "accept"); err != nil {
		t.Fatal(err)
	}
	ci.green(ztip(t, dir, "other"), ztip(t, dir, "trunk"))
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids["other"]}, "accept"); err != nil {
		t.Fatal(err)
	}
	if a := ageOf(t, d, ix, ids["high"]); !a.Stale {
		t.Fatalf("FAIL: high reads %+v after other also landed, want stale", a)
	}
	// The strata walk accepts each rung through the same gate.
	r := ops.NewRegistry()
	if err := RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(as("dan", "i"), "landing.land", landingLandIn{Repo: "test", ID: ids["high"]})
	if err != nil {
		t.Fatal(err)
	}
	if out := got.(landingLandOut); out.Stopped != ids["high"] || len(out.Landed) != 0 {
		t.Fatalf("FAIL: the walk over a stale rung answered %+v, want it stopped at high", out)
	}
	if !t.Failed() {
		t.Log("ok: anything beside the strata's own rungs voids the upper rung's verdict")
	}
}

// landing.get and landing.list carry the state, so the page and an
// API client read the one answer the gate acts on.
func TestTheLandingReadsCarryTheStaleState(t *testing.T) {
	dir, ids, ix, pipeSync := premergeStore(t)
	root := ztip(t, dir, "trunk")
	ci := &mergeRuns{pairRuns: &pairRuns{runs: map[string][]CIRun{}}}
	ci.green(ztip(t, dir, "one"), root)
	ci.green(ztip(t, dir, "two"), root)
	d := Deps{Version: "v", Checks: mergeChecks{}, CI: ci, Repos: NewRepos(map[string]*Repo{
		"test": {Index: ix, Zit: zitcli.New(dir), Sync: pipeSync}})}
	r := ops.NewRegistry()
	if err := RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}
	if _, err := d.landingSay(as("dan", "i"), landingSayIn{Repo: "test", ID: ids[0]}, "accept"); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(as("dan", "i8"), "landing.get", landingGetIn{Repo: "test", ID: ids[1]})
	if err != nil {
		t.Fatal(err)
	}
	if g := got.(landingGetOut); !g.Stale || g.TestedAgainst != root {
		t.Errorf("FAIL: landing.get reads stale=%v tested=%.12s, want stale against the root", g.Stale, g.TestedAgainst)
	}
	list, err := r.Invoke(as("dan", "i8"), "landing.list", landingListIn{Repo: "test", State: "open"})
	if err != nil {
		t.Fatal(err)
	}
	for _, l := range list.(landingListOut).Landings {
		if l.ID == ids[1] && !l.Stale {
			t.Errorf("FAIL: landing.list does not mark two stale")
		}
	}
	if !t.Failed() {
		t.Log("ok: both reads say stale")
	}
}