Skip to content
quarry Artifact [7e6d17b086]
Login

Artifact [7e6d17b086]

Artifact 7e6d17b08623138d98ec60dc9c00cd6d72ad437c57de2ebdecd1429a4a7535d1:


package api

import (
	"context"
	"encoding/json"
	"path/filepath"
	"slices"
	"testing"
	"time"

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

// AN INHERITED VERDICT IS THE CHECK-IN'S OWN (RFC 0009 §inheritance).
//
// checkin.get lists it among the check-in's checks with its state and
// the pair run it came from, and the required-checks rule a deploy
// gates on reads it as it reads a run: an inherited pass is met, an
// inherited fail is unmet.
func TestAnInheritedVerdictIsTheCheckinsOwn(t *testing.T) {
	ix := redTrunkIndex(t)
	runs := runsStub{runs: []CIRun{
		{RunID: "p-quarry", InheritedFrom: "p-quarry", Checkin: redTrunkTip, Workflow: "quarry.quarry.1", Pipeline: "ci/quarry.json", Status: "completed"},
		{RunID: "p-race", InheritedFrom: "p-race", Checkin: redTrunkTip, Workflow: "quarry.race.1", Pipeline: "ci/race.json", Status: "failed"},
	}}
	r := ops.NewRegistry()
	d := Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix}}), CI: runs, Checks: checksStub{required: []string{"quarry", "race"}}}
	if err := RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(context.Background(), "checkin.get", checkinGetIn{Repo: "test", Hash: redTrunkTip})
	if err != nil {
		t.Fatal(err)
	}
	b, _ := json.Marshal(got)
	var m struct {
		Checks []map[string]any `json:"checks"`
		Unmet  []string         `json:"unmet"`
	}
	if err := json.Unmarshal(b, &m); err != nil {
		t.Fatal(err)
	}
	from := map[string]any{}
	for _, c := range m.Checks {
		from[c["pipeline"].(string)] = c["inheritedFrom"]
	}
	if from["ci/quarry.json"] != "p-quarry" || from["ci/race.json"] != "p-race" {
		t.Fatalf("FAIL: checks name their sources %v; want p-quarry and p-race", from)
	}
	if len(m.Unmet) != 1 || m.Unmet[0] != "race" {
		t.Fatalf("FAIL: unmet = %v; want the inherited fail, race, and only it", m.Unmet)
	}
	t.Log("ok: inherited verdicts are listed with their source and judged as the check-in's own")
}

// pushedStub files runs under a check-in or under a pair, apart, and
// names the newest pair tested for a tip.
type pushedStub struct {
	runsStub
	tip, pair  map[string][]CIRun
	latestPair map[string]string
}

func (p pushedStub) CIRunsFor(_, c string) ([]CIRun, error)      { return p.tip[c], nil }
func (p pushedStub) CIRunsForPair(_, s string) ([]CIRun, error)  { return p.pair[s], nil }
func (p pushedStub) LatestPairFor(_, tip string) (string, error) { return p.latestPair[tip], nil }

// A PROPOSED BRANCH CHECK-IN'S CHECKS ARE ITS MERGE'S (RFC 0009 §once).
//
// A push to a branch with an open landing is tested as its merge with
// the landing's target, so the check-in's own runs are only what stayed
// on its tip; checkin.get lists that merge's runs beside them, and
// judges the branch's required checks on either. A merge that is not
// its landing's (the branch was never proposed, or it merged into
// another branch) is not the check-in's: its tip must answer for itself.
func TestAProposedCheckinShowsItsMergesChecks(t *testing.T) {
	const tip = "f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1"
	const trunk = "t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0"
	get := func(ix *index.Index, hash, pair string) checkinGetOut {
		t.Helper()
		stub := pushedStub{
			tip: map[string][]CIRun{hash: {{RunID: "d", Checkin: hash, Workflow: "w.deploy", Pipeline: "ci/deploy.yml", Status: "completed"}}},
			pair: map[string][]CIRun{pair: {
				{RunID: "q", Checkin: pair, Pair: pair, Workflow: "w.quarry", Pipeline: "ci/quarry.json", Status: "completed"},
				{RunID: "r", Checkin: pair, Pair: pair, Workflow: "w.race", Pipeline: "ci/race.json", Status: "failed"},
			}},
			latestPair: map[string]string{hash: pair},
		}
		r := ops.NewRegistry()
		d := Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix}}), CI: stub,
			Checks: checksStub{required: []string{"deploy", "quarry", "race"}}}
		if err := RegisterAll(r, d); err != nil {
			t.Fatal(err)
		}
		got, err := r.Invoke(context.Background(), "checkin.get", checkinGetIn{Repo: "test", Hash: hash})
		if err != nil {
			t.Fatal(err)
		}
		return got.(checkinGetOut)
	}
	out := get(openLandingIndex(t, "feat", tip), tip, "pair:"+tip+":"+trunk)
	if len(out.Checks) != 3 {
		t.Fatalf("FAIL: checks %+v; want the tip's deploy and the merge's two runs", out.Checks)
	}
	if len(out.Unmet) != 1 || out.Unmet[0] != "race" {
		t.Fatalf("FAIL: unmet = %v; want race, failed on the merge", out.Unmet)
	}
	t.Log("ok: the open landing's merge runs are the branch check-in's checks")

	out = get(openLandingIndex(t, "feat", tip), tip, "pair:"+tip+":"+redTrunkTip)
	if len(out.Checks) != 1 || len(out.Unmet) != 2 {
		t.Fatalf("FAIL: a merge into another tip read as checks %+v, unmet %v; want the tip's own only", out.Checks, out.Unmet)
	}
	out = get(redTrunkIndex(t), redFeatureTip, "pair:"+redFeatureTip+":"+redTrunkTip)
	if len(out.Checks) != 1 || len(out.Unmet) != 2 {
		t.Fatalf("FAIL: an unproposed tip read a merge's runs: checks %+v, unmet %v; want its own only", out.Checks, out.Unmet)
	}
	t.Log("ok: a merge that is not the open landing's is not the check-in's")
}

// A CLOSED LANDING'S MERGE IS STILL ITS TIP'S CHECKS.
//
// A push made while the landing was open was tested as its merge with
// the landing's target, and that verdict is the tip's (its tag, and the
// reason a closing landing does not test the tip again). Rejected, the
// landing no longer holds, but the merge was still the one the branch
// was proposed into: checkin.get lists its runs and judges the required
// checks on them, so checks, Required and the badge agree.
func TestAClosedLandingsMergeIsStillItsTipsChecks(t *testing.T) {
	const tip = "f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1"
	const trunk = "t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0"
	pair := "pair:" + tip + ":" + trunk
	ix := openLandingIndex(t, "feat", tip)
	if err := ix.FoldNow([]index.Parsed{{Hash: "bbbb2222bbbb2222bbbb2222bbbb2222",
		Art: &artifact.Artifact{Type: artifact.Forum, ThreadRoot: landingHash, Date: "2026-01-01T11:00:00", User: "carol",
			Body: []byte("zit-landing-state: rejected\n\nnot now\n")}}}); err != nil {
		t.Fatal(err)
	}
	if _, open, err := ix.OpenLandingOn("feat"); err != nil || open {
		t.Fatalf("FAIL: the landing is still open (%v)", err)
	}
	stub := pushedStub{
		pair: map[string][]CIRun{pair: {
			{RunID: "q", Checkin: pair, Pair: pair, Workflow: "w.quarry", Pipeline: "ci/quarry.json", Status: "completed"},
			{RunID: "r", Checkin: pair, Pair: pair, Workflow: "w.race", Pipeline: "ci/race.json", Status: "failed"},
		}},
		latestPair: map[string]string{tip: pair},
	}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix}}), CI: stub,
		Checks: checksStub{required: []string{"quarry", "race"}}}); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(context.Background(), "checkin.get", checkinGetIn{Repo: "test", Hash: tip})
	if err != nil {
		t.Fatal(err)
	}
	out := got.(checkinGetOut)
	if len(out.Checks) != 2 || out.CIRun != "q" {
		t.Fatalf("FAIL: checks %+v, ciRun %q; want the merge's two runs, q behind the badge", out.Checks, out.CIRun)
	}
	if !slices.Equal(out.Unmet, []string{"race"}) {
		t.Fatalf("FAIL: unmet = %v; want race, failed on the merge, and quarry met by it", out.Unmet)
	}
	t.Log("ok: a closed landing's merge stays its tip's checks")
}

// storedPairs answers checkin.get's pair questions from a real store.
type storedPairs struct {
	runsStub
	db *oltp.DB
}

func (s storedPairs) CIRunsFor(string, string) ([]CIRun, error) { return nil, nil }
func (s storedPairs) LatestPairFor(repo, tip string) (string, error) {
	return s.db.LatestPairFor(repo, tip)
}
func (s storedPairs) CIRunsForPair(repo, pair string) ([]CIRun, error) {
	rows, err := s.db.CIRunsForPair(repo, pair)
	out := make([]CIRun, 0, len(rows))
	for _, r := range rows {
		out = append(out, CIRun{RunID: r.RunID, Checkin: r.Checkin, Pair: r.Pair, Workflow: r.Workflow, Pipeline: r.Pipeline, Status: r.Status})
	}
	return out, err
}

// A MERGE THE CLOSING LANDING CANCELLED IS NOBODY'S VERDICT.
//
// The push's merge into trunk passed; trunk moved and the landing sweep
// merged the tip into the new trunk tip; the landing was rejected and
// closing cancelled that newer merge. The tip's verdict is still the
// push's merge (it carries the tag, and the closing did not test the
// tip again), so checkin.get reads its checks from it, not from the
// newer merge that holds only cancelled runs.
func TestACancelledNewerMergeLeavesThePushsVerdict(t *testing.T) {
	const tip = "f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1"
	const t1 = "t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0"
	const t2 = "t2t2t2t2t2t2t2t2t2t2t2t2t2t2t2t2t2t2t2t2"
	ix := openLandingIndex(t, "feat", tip)
	if err := ix.FoldNow([]index.Parsed{
		{Hash: t2, Art: &artifact.Artifact{Type: artifact.Manifest, Date: "2026-01-01T10:30:00", User: "ada", Comment: "trunk moves",
			Tags: []artifact.Tag{{Name: "*branch", Target: "*", Value: "trunk"}}}},
		{Hash: "bbbb2222bbbb2222bbbb2222bbbb2222", Art: &artifact.Artifact{Type: artifact.Forum, ThreadRoot: landingHash,
			Date: "2026-01-01T11:00:00", User: "carol", Body: []byte("zit-landing-state: rejected\n\nnot now\n")}},
	}); err != nil {
		t.Fatal(err)
	}
	db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	run := func(id, pair, status string) {
		t.Helper()
		if err := db.AddCIRun(oltp.CIRun{RunID: id, Repo: "test", Checkin: pair, Pair: pair, Workflow: "w.quarry", Pipeline: "ci/quarry.json", Status: "running"}); err != nil {
			t.Fatal(err)
		}
		if err := db.SetCIStatus(id, status); err != nil {
			t.Fatal(err)
		}
	}
	run("pp", "pair:"+tip+":"+t1, "completed")
	time.Sleep(1100 * time.Millisecond) // started is stamped to the second
	run("lp", "pair:"+tip+":"+t2, "cancelled")

	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: ix}}), CI: storedPairs{db: db},
		Checks: checksStub{required: []string{"quarry"}}}); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(context.Background(), "checkin.get", checkinGetIn{Repo: "test", Hash: tip})
	if err != nil {
		t.Fatal(err)
	}
	out := got.(checkinGetOut)
	if len(out.Checks) != 1 || out.Checks[0].RunID != "pp" || out.CIRun != "pp" {
		t.Fatalf("FAIL: checks %+v, ciRun %q; want the push's merge run pp", out.Checks, out.CIRun)
	}
	if len(out.Unmet) != 0 {
		t.Fatalf("FAIL: unmet = %v; want quarry met by the push's merge", out.Unmet)
	}
	t.Log("ok: a cancelled newer merge leaves the push's merge as the tip's verdict")
}

// A PROPOSED CHECK-IN'S RUN IS ITS MERGE'S when nothing ran on the tip
// itself: ciRun links the badge to the run behind it, as before.
func TestAProposedCheckinsRunIsItsMerges(t *testing.T) {
	const tip = "f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1"
	pair := "pair:" + tip + ":t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0"
	stub := pushedStub{
		pair:       map[string][]CIRun{pair: {{RunID: "q", Checkin: pair, Pair: pair, Workflow: "w.quarry", Pipeline: "ci/quarry.json", Status: "completed"}}},
		latestPair: map[string]string{tip: pair},
	}
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"test": {Index: openLandingIndex(t, "feat", tip)}}), CI: stub}); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(context.Background(), "checkin.get", checkinGetIn{Repo: "test", Hash: tip})
	if err != nil {
		t.Fatal(err)
	}
	if run := got.(checkinGetOut).CIRun; run != "q" {
		t.Fatalf("FAIL: ciRun = %q; want the merge's run q", run)
	}
	t.Log("ok: the merge's run is the check-in's")
}