package main import ( "context" "encoding/json" "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" ) // A delivery group's gate is the composed check's, and its members' own checks run their repository's check alone // (novox/hq ADR 0403 clauses 1 and 7). Pure: no store, no bus. // A member of a group whose line says composed_gate is a composed member; one whose group says nothing of it (an older // mesh-delivery), one of a group of a single pull request and a lone pull request are not. func TestAMemberOfAComposedGroupIsKnownByTheGroupsLine(t *testing.T) { two := func(composed bool) groupsLine { l := group("feat/x", member("novox/mesh-host", 114, headHost, "main", true), member("novox/mesh-controller", 346, headController, "main", true)) l.ComposedGate = composed return l } p := link.PullUpdated{Owner: "novox", Repo: "mesh-host", Number: 114, Base: "main", Head: "feat/x", Commit: headHost} recheck := link.PullUpdated{Owner: "novox", Repo: "mesh-host", Number: 114, Base: "main", Commit: headHost} for name, c := range map[string]struct { p link.PullUpdated lines []groupsLine want bool }{ "composed": {p, []groupsLine{two(true)}, true}, "a recheck, by its number": {recheck, []groupsLine{two(true)}, true}, "an older mesh-delivery": {p, []groupsLine{two(false)}, false}, "another group": {p, []groupsLine{group("feat/y", member("novox/mesh-host", 1, headHost, "main", true))}, false}, "no group": {p, nil, false}, } { if got := composedMember(c.p, c.lines); got != c.want { t.Errorf("%s: composed %v, want %v", name, got, c.want) } } alone := group("feat/x", member("novox/mesh-host", 114, headHost, "main", true)) alone.ComposedGate = true if composedMember(p, []groupsLine{alone}) { t.Error("a group of a single pull request is composed") } } // A composed member's verdict never says its gate passed: the gate layer, and the verdict that is the gate's, read // "composed"; a failure or an error stays what it was. func TestAComposedMembersVerdictHasNoPassingGate(t *testing.T) { asked := &link.CheckRequest{Owner: "novox", Repo: "mesh-host", Number: 114, Composed: true} pass := link.BuildResult{ID: "b", Ref: headHost, Checked: asked, Check: &link.CheckOutcome{Verdict: "pass", Summary: "no module touched", Gate: &link.CheckLayer{Verdict: "pass", Summary: "no module touched"}, RepoCheck: &link.CheckLayer{Verdict: "pass", Summary: "ok"}}} c := checkedOf(pass) if c.Gate == nil || c.Gate.Verdict != "composed" || c.Verdict != "composed" || c.RepoCheck == nil || c.RepoCheck.Verdict != "pass" { t.Fatalf("the member's verdict says %+v gate %+v", c, c.Gate) } asked.Composed = false if c := checkedOf(pass); c.Gate.Verdict != "pass" || c.Verdict != "pass" { t.Fatalf("a member of no composed group has its own gate: %+v", c.Gate) } asked.Composed = true failing := pass failing.Check = &link.CheckOutcome{Verdict: "fail", Summary: "conflict", Gate: &link.CheckLayer{Verdict: "fail", Summary: "conflict"}, RepoCheck: &link.CheckLayer{Verdict: "fail", Summary: "conflict"}} if c := checkedOf(failing); c.Gate.Verdict != "fail" || c.Verdict != "fail" { t.Fatalf("a failure was read as composed: %+v", c.Gate) } broken := link.BuildResult{ID: "b", Ref: headHost, Checked: asked, Failed: "the facts snapshot cannot be read"} if c := checkedOf(broken); c.Verdict != "error" || c.Gate.Verdict != "error" { t.Fatalf("an error was read as composed: %+v", c) } } // The composed verdict on the bus carries every member, each with its trunk commit, for the forge's holder to set each // member's mesh/merge-gate from it. func TestTheComposedVerdictCarriesEveryMember(t *testing.T) { asked := &link.CheckRequest{Owner: "novox", Repo: "mesh-host", Number: 114, Group: "feat/x", Members: []link.GroupMember{{Owner: "novox", Repo: "mesh-controller", Number: 346, Ref: headController}}} c := checkedOf(link.BuildResult{ID: "g", Ref: headHost, Checked: asked, Check: &link.CheckOutcome{Verdict: "pass", Summary: "ok", Trunk: "aaaaaaaaaaaa", Members: []link.CheckedMember{{Owner: "novox", Repo: "mesh-controller", Commit: headController[:12], Trunk: "bbbbbbbbbbbb"}}}}) body, _ := json.Marshal(c) for _, want := range []string{`"group":"feat/x"`, `"members":[{"owner":"novox","repo":"mesh-host","number":114,"commit":"` + headHost + `","trunk":"aaaaaaaaaaaa"}`, `{"owner":"novox","repo":"mesh-controller","number":346,"commit":"` + headController + `","trunk":"bbbbbbbbbbbb"}`} { if !strings.Contains(string(body), want) { t.Fatalf("the composed verdict lacks %s:\n%s", want, body) } } } // Members whose manifests order their modules in a cycle fail the composed check, naming every module and edge, and no // ask is made of the build seat. func TestAGroupInACycleFailsItsComposedCheckWithoutABuild(t *testing.T) { members := []orderMember{ {ID: "novox/x@" + headHost[:12], Repository: "novox/x", Base: "main", Head: headHost, Number: 7, Paths: []string{"module.json"}}, {ID: "novox/y@" + headController[:12], Repository: "novox/y", Base: "main", Head: headController, Number: 9, Paths: []string{"module.json"}}, } reach := map[string]orderReach{members[0].ID: {Moved: []string{"mx"}}, members[1].ID: {Moved: []string{"my"}}} was, wasParent := manifestAt, parentOf t.Cleanup(func() { manifestAt, parentOf = was, wasParent }) parentOf = func(context.Context, string, string, string) (string, error) { return "", nil } manifestAt = func(_ context.Context, _, repo, _, ref string) (catalogue.Manifest, bool, error) { if ref == "main" { return catalogue.Manifest{}, false, nil // what the pull requests merge into holds no such manifest } if repo == "x" { return catalogue.Manifest{Module: "mx", DefinesSeats: []catalogue.SeatDeclaration{{Name: "s", Serves: []catalogue.Verb{{Name: "a"}}}}, Invokes: []string{"seat:s.b"}}, true, nil } return catalogue.Manifest{Module: "my", DefinesSeats: []catalogue.SeatDeclaration{{Name: "s", Serves: []catalogue.Verb{{Name: "b"}}}}, Invokes: []string{"seat:s.a"}}, true, nil } c, cyclic, err := cycleVerdict(context.Background(), "feat/cycle", members, reach, []inventory.Edge(nil), nil) if err != nil || !cyclic { t.Fatalf("no cycle found: %v", err) } if c.Verdict != "fail" || c.Group != "feat/cycle" || len(c.Members) != 2 || c.Gate == nil || c.Gate.Verdict != "fail" { t.Fatalf("the verdict is %+v", c) } for _, w := range []string{"mx", "my", "s.a", "s.b"} { if !strings.Contains(c.Summary, w) { t.Errorf("the cycle does not name %s: %s", w, c.Summary) } } // Members with no edge between them are no cycle. manifestsReadMu.Lock() clear(manifestsRead) manifestsReadMu.Unlock() manifestAt = func(context.Context, string, string, string, string) (catalogue.Manifest, bool, error) { return catalogue.Manifest{}, false, nil } if _, cyclic, err := cycleVerdict(context.Background(), "feat/cycle", members, reach, nil, nil); err != nil || cyclic { t.Fatalf("a cycle where there is none: %v %v", cyclic, err) } }