package api
import (
"context"
"encoding/json"
"testing"
"craftdesign.group/quarry/internal/index"
"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
// the open landing's (none is open, 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")
}