package check import ( "context" "encoding/json" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/delivery" ) type fakeState map[string][]byte func (f fakeState) read() StateRead { return StateRead{ Keys: func() ([]string, error) { var k []string for key := range f { k = append(k, key) } return k, nil }, Get: func(key string) (json.RawMessage, error) { if v, ok := f[key]; ok { return json.RawMessage(v), nil } return nil, nil }, } } // readerOf is a reader over a verdict the checker wrote through its own code and the candidate it judged. func readerOf(t *testing.T) (Reader, string) { t.Helper() r := newRig(t, passJudge()) cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1), pullOf(hqRepo, 3, head2)) r.ch.Candidate(context.Background(), "feat%2Fx", cand) r.ch.Wait() ckey, _ := delivery.CandidatesKey("feat/x") verdicts, kept := fakeState{}, fakeState{} for k, v := range r.verdicts.vals { verdicts[k] = v } if _, err := (Kept{Store: r.verdicts}).Keep(context.Background(), "unit", digA, keptValue("m/a", 3)); err != nil { t.Fatal(err) } kept["unit."+digA] = r.verdicts.vals["unit."+digA] return Reader{VerdictState: verdicts.read(), KeptState: kept.read(), CandidateState: fakeState{ckey: cand}.read()}, keyOf(t, "feat/x", cand) } func TestTheVerdictToolsAnswerTheStoredVerdictWithTheFullReportOfEachPullRequest(t *testing.T) { // N9/AC1, 1686 rd, key := readerOf(t) vs, err := rd.Verdicts("") if err != nil || len(vs) != 1 || vs[0].Key != key { t.Fatalf("verdicts = %+v, %v", vs, err) } if _, err := delivery.DecodeVerdict(vs[0].Verdict); err != nil { t.Errorf("the verdict is not as stored: %v", err) } if got := vs[0].Reports["novox/mesh-delivery#7"]; !strings.HasPrefix(got, "pass 111111111111 on aaaaaaaaaaaa") || !strings.Contains(got, "composition not yet run") { t.Errorf("reports = %v", vs[0].Reports) } if only, _ := rd.Verdicts("feat/other"); len(only) != 0 { t.Errorf("another group's verdicts = %+v", only) } } // 1887/A10: an hq pull request is answered as any other, its report its verdict's (ADR 0527 §2), never a note that a script // judges it. func TestAnHqPullRequestIsAnsweredAsAnyOther(t *testing.T) { rd, _ := readerOf(t) hq, err := rd.Pull(hqRepo, 3) if err != nil || hq.Note != "" || hq.Verdict == nil { t.Errorf("hq pull = %+v, %v", hq, err) } p, err := rd.Pull(repoA, 7) if err != nil || p.Note != "" || !strings.HasPrefix(p.Reports["novox/mesh-delivery#7"], "pass ") { t.Errorf("pull = %+v, %v", p, err) } if _, err := rd.Pull(repoA, 99); err == nil { t.Error("a pull request no candidate holds was answered") } } func TestTheKeptToolAnswersTheKeysAsStoredByLevelAndByDigest(t *testing.T) { // N9/AC1 rd, _ := readerOf(t) all, err := rd.Kept("", "") if err != nil || len(all) != 1 { t.Fatalf("kept = %v, %v", all, err) } one, err := rd.Kept("unit", digA) if err != nil || len(one) != 1 { t.Fatalf("one kept = %v, %v", one, err) } for _, raw := range one { if k, err := delivery.DecodeKept(raw); err != nil || k.Package != "m/a" { t.Errorf("kept value = %+v, %v", k, err) } } if none, _ := rd.Kept("integration", ""); len(none) != 0 { t.Errorf("integration = %v", none) } if _, err := rd.Kept("unit", digB); err == nil { t.Error("a digest nothing kept was answered") } } // 1680 live acceptance: checker_previews shows the preview key of an open pull request, as stored, narrowed by repository and head. func TestThePreviewsToolShowsTheStoredPreviewOfAPullRequest(t *testing.T) { pv := delivery.Preview{Repository: repoA, Head: head1, Trunk: head2, Merge: "m1", Modules: map[string]delivery.PreviewModule{}} data, err := delivery.EncodePreview(pv) if err != nil { t.Fatal(err) } key, _ := delivery.PreviewKey(repoA, head1, head2) rd := Reader{PreviewState: fakeState{key: data, "junk": []byte("{}")}.read()} got, bad, err := rd.Previews(repoA, head1[:8]) if err != nil || len(got) != 1 || got[0].Key != key || got[0].Head != head1 || got[0].Trunk != head2 || string(got[0].Preview) != string(data) { t.Fatalf("previews = %+v, %v", got, err) } if len(bad) != 1 || bad[0] != "junk" { t.Errorf("unreadable = %v", bad) } if got, _, _ := rd.Previews("novox/other", ""); len(got) != 0 { t.Errorf("another repository's = %+v", got) } if _, _, err := (Reader{}).Previews("", ""); err == nil { t.Error("answered with no state to read") } } // checker_status reads the steps in flight by node and the candidates without a verdict; since R4g nothing holds the checker // and it has no capacity of its own (ADR 0525 §4, §7; 1685 closed by the hold's deletion). func TestTheStatusToolReadsRunningSteps(t *testing.T) { running, _ := delivery.EncodeStep(delivery.Step{ID: "abc1", Tool: "build_go_test", Node: "shanks", State: delivery.StepRunning, Started: time.Date(2026, 10, 22, 15, 0, 0, 0, time.UTC)}) passed, _ := delivery.EncodeStep(delivery.Step{ID: "abc2", Tool: "build_go_test", Node: "ace", State: delivery.StepPassed}) cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) ckey, _ := delivery.CandidatesKey("feat/x") rd := Reader{ StepStates: map[string]StateRead{"mesh-build-go": fakeState{"shanks.abc1": running, "ace.abc2": passed}.read()}, CandidateState: fakeState{ckey: cand}.read(), VerdictState: fakeState{}.read(), HaveState: fakeState{"shanks.mesh-build-go": haveOf(t, delivery.PhaseRunning)}.read(), Now: func() time.Time { return time.Date(2026, 10, 22, 15, 10, 0, 0, time.UTC) }, } st, err := rd.Status() if err != nil { t.Fatal(err) } if len(st.Running) != 1 || st.Running[0].Node != "shanks" || st.RunningByNode["shanks"] != 1 || st.RunningByNode["ace"] != 0 { t.Errorf("running = %+v %v", st.Running, st.RunningByNode) } if len(st.Unjudged) != 1 || st.Unjudged[0] != "feat/x" { t.Errorf("unjudged = %v", st.Unjudged) } // What cannot be read is said, not guessed. st, err = Reader{}.Status() if err != nil || len(st.Notes) == 0 { t.Errorf("empty status = %+v, %v", st, err) } } func haveOf(t *testing.T, phase delivery.Phase) []byte { t.Helper() raw, err := delivery.EncodeHave(delivery.Have{Phase: phase, Health: delivery.HealthOK}) if err != nil { t.Fatal(err) } return raw } func stepOf(t *testing.T, id, node string, started time.Time) []byte { t.Helper() raw, err := delivery.EncodeStep(delivery.Step{ID: id, Tool: "build_go_fetch", Node: node, State: delivery.StepRunning, Started: started}) if err != nil { t.Fatal(err) } return raw } // 1890: a key a build module left running (it stopped, restarted or was unassigned before it wrote the step's end) holds // no place in the checker, whose wait for it ended at the step bound; the status says it is left running and why, and // counts only the steps that can still run. func TestTheStatusCountsOnlyStepsThatCanStillRunAndSaysWhyTheOthersAreLeft(t *testing.T) { // 1890/AC2, AC3 now := time.Date(2026, 10, 23, 7, 0, 0, 0, time.UTC) rd := Reader{ StepStates: map[string]StateRead{ "mesh-build-go": fakeState{ "g14.fresh1": stepOf(t, "fresh1", "g14", now.Add(-5*time.Minute)), "g14.old1": stepOf(t, "old1", "g14", now.Add(-33*time.Hour)), "ace.gone1": stepOf(t, "gone1", "ace", now.Add(-2*time.Minute)), "shanks.down1": stepOf(t, "down1", "shanks", now.Add(-time.Minute)), }.read(), }, HaveState: fakeState{ "g14.mesh-build-go": haveOf(t, delivery.PhaseRunning), "shanks.mesh-build-go": haveOf(t, delivery.PhaseFailed), }.read(), StepBound: 30 * time.Minute, Now: func() time.Time { return now }, } st, err := rd.Status() if err != nil { t.Fatal(err) } if len(st.Running) != 1 || st.Running[0].ID != "fresh1" || len(st.RunningByNode) != 1 || st.RunningByNode["g14"] != 1 { t.Fatalf("running = %+v by node %v", st.Running, st.RunningByNode) } why := map[string]string{} for _, l := range st.LeftRunning { why[l.ID] = l.Why } if len(why) != 3 { t.Fatalf("left running = %+v", st.LeftRunning) } for id, want := range map[string]string{"old1": "past the step bound of 30m0s", "gone1": "mesh-build-go is not on ace", "down1": "mesh-build-go on shanks is failed"} { if !strings.Contains(why[id], want) { t.Errorf("why %s = %q, want it to say %q", id, why[id], want) } } } // 1887/A10: a group of hq's pull requests alone is judged by the checker since R4g, so without a verdict it is unjudged. func TestTheStatusCountsAnHQOnlyGroupAsUnjudged(t *testing.T) { cand := candidateOf(t, "pb-x", pullOf(hqRepo, 3, head2)) ckey, _ := delivery.CandidatesKey("pb-x") st, err := Reader{CandidateState: fakeState{ckey: cand}.read(), VerdictState: fakeState{}.read()}.Status() if err != nil { t.Fatal(err) } if len(st.Unjudged) != 1 || st.Unjudged[0] != "pb-x" { t.Errorf("unjudged = %v, want pb-x", st.Unjudged) } }