package api
import (
"path/filepath"
"testing"
"craftdesign.group/quarry/internal/artifact"
"craftdesign.group/quarry/internal/index"
)
// checksByTarget answers required checks per target branch, as the real
// rules do: trunk's rule names checks, a feature branch has none.
type checksByTarget map[string][]string
func (c checksByTarget) RequiredChecks(_, target string) []string { return c[target] }
// An open landing's badge is its required checks' on the merge, not its
// branch's own run: green only when every one has passed, red when one
// failed, waiting while one has not answered.
func TestALandingIsGreenOnlyWhenItsRequiredChecksAre(t *testing.T) {
const tip = "f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1f1"
const trunk = "t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0"
pair := "pair:" + tip + ":" + trunk
run := func(id, pipeline, status string) CIRun {
return CIRun{RunID: id, Checkin: pair, Pair: pair, Workflow: "w." + id, Pipeline: pipeline, Status: status}
}
for _, c := range []struct {
name string
runs []CIRun
required []string
tip string
want string
}{
{"both pass", []CIRun{run("q", "ci/quarry.json", "completed"), run("r", "ci/race.json", "completed")}, []string{"quarry", "race"}, "ci-pass", "ci-pass"},
{"one running", []CIRun{run("q", "ci/quarry.json", "running"), run("r", "ci/race.json", "completed")}, []string{"quarry", "race"}, "ci-pass", "ci-waiting"},
{"none yet", nil, []string{"quarry", "race"}, "ci-pass", "ci-waiting"},
{"one failed", []CIRun{run("q", "ci/quarry.json", "failed"), run("r", "ci/race.json", "running")}, []string{"quarry", "race"}, "ci-pass", "ci-fail"},
{"the branch failed", []CIRun{run("q", "ci/quarry.json", "completed")}, []string{"quarry"}, "ci-fail", "ci-fail"},
{"nothing required", nil, nil, "ci-pass", "ci-pass"},
} {
ix := openLandingIndex(t, "feat", tip)
l, open, err := ix.OpenLandingOn("feat")
if err != nil || !open {
t.Fatalf("no open landing: %v", err)
}
d := Deps{Repos: NewRepos(map[string]*Repo{"test": {Index: ix}}),
CI: pushedStub{pair: map[string][]CIRun{pair: c.runs}, latestPair: map[string]string{tip: pair}},
Checks: checksStub{required: c.required}}
if got := d.gatedVerdict("test", l, c.tip); got != c.want {
t.Errorf("FAIL: %s: the badge is %q, want %q", c.name, got, c.want)
}
}
}
// A RUNG OF A STACK IS JUDGED ON THE CHECKS OF THE BRANCH THE STACK
// LANDS ON, NOT ON ITS OWN TARGET'S.
//
// !1293 targeted !1292's branch, which no rule names, so its badge was
// its branch's own run: "pass", in the list and on the page, while the
// stack it was armed with waited on its race check.
func TestARungOfAStackWaitsOnTheBaseBranchsChecks(t *testing.T) {
const (
trunk = "t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0t0"
baseTip = "b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1"
topTip = "c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2"
landA = "a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0"
landB = "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
)
ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { ix.Close() })
manifest := func(hash, date, branch string) index.Parsed {
return index.Parsed{Hash: hash, Art: &artifact.Artifact{Type: artifact.Manifest, Date: date, User: "ada", Comment: "work",
Tags: []artifact.Tag{{Name: "*branch", Target: "*", Value: branch}}}}
}
landing := func(hash, date, branch, target string) index.Parsed {
return index.Parsed{Hash: hash, Art: &artifact.Artifact{Type: artifact.Forum, ThreadTitle: "land " + branch, Date: date, User: "ada",
Body: []byte("zit-landing: 1\nbranch: " + branch + "\ntarget: " + target + "\n\nready\n")}}
}
if err := ix.FoldNow([]index.Parsed{
manifest(trunk, "2026-01-01T08:00:00", "trunk"),
manifest(baseTip, "2026-01-01T09:00:00", "base"),
manifest(topTip, "2026-01-01T09:30:00", "top"),
landing(landA, "2026-01-01T10:00:00", "base", "trunk"),
landing(landB, "2026-01-01T10:30:00", "top", "base"),
}); err != nil {
t.Fatal(err)
}
top, open, err := ix.OpenLandingOn("top")
if err != nil || !open {
t.Fatalf("no open landing on top: %v", err)
}
pair := "pair:" + topTip + ":" + baseTip
run := func(id, pipeline, status string) CIRun {
return CIRun{RunID: id, Checkin: pair, Pair: pair, Workflow: "w." + id, Pipeline: pipeline, Status: status}
}
for _, c := range []struct {
name string
runs []CIRun
want string
}{
{"race still running", []CIRun{run("q", "ci/quarry.json", "completed"), run("r", "ci/race.json", "running")}, "ci-waiting"},
{"both pass", []CIRun{run("q", "ci/quarry.json", "completed"), run("r", "ci/race.json", "completed")}, "ci-pass"},
{"race failed", []CIRun{run("q", "ci/quarry.json", "completed"), run("r", "ci/race.json", "failed")}, "ci-fail"},
} {
d := Deps{Repos: NewRepos(map[string]*Repo{"test": {Index: ix}}),
CI: pushedStub{pair: map[string][]CIRun{pair: c.runs}, latestPair: map[string]string{topTip: pair}},
Checks: checksByTarget{"trunk": {"quarry", "race"}}}
if got := d.gatedVerdict("test", top, "ci-pass"); got != c.want {
t.Errorf("FAIL: %s: the rung's badge is %q, want %q", c.name, got, c.want)
}
}
// A landing on a branch nothing proposes, with no rule, keeps its tip's verdict.
d := Deps{Repos: NewRepos(map[string]*Repo{"test": {Index: ix}}), Checks: checksByTarget{}}
if got := d.gatedVerdict("test", top, "ci-pass"); got != "ci-pass" {
t.Errorf("FAIL: with no rule anywhere the badge is %q, want the tip's", got)
}
t.Log("ok")
}