111 lines
5.7 KiB
Go
111 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/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.
|
|
|
|
// The heads of a group's members, by repository.
|
|
const (
|
|
headHost = "1111111111111111111111111111111111111111"
|
|
headController = "2222222222222222222222222222222222222222"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
}
|