issue 1025: the merge gate judges a group's move of a module as a move (ADR 0420 §6)

This commit is contained in:
2026-10-17 15:43:28 +02:00
parent 21414307c8
commit 085b661936
2 changed files with 245 additions and 3 deletions
+121
View File
@@ -434,3 +434,124 @@ func sdkPinnedByGoMod(t *testing.T) string {
t.Fatalf("go.mod requires no %s", sdkModule)
return ""
}
// **A module one head of a group removes and another defines is a move** (novox/hq ADR 0420 §6, issue 1025):
// neither a second definition of one module name (issue 239) nor the removal of a module machines run
// (ADR 0236). Alone, each head is still refused. This lays the heads over the shelf as judgeChange does,
// which needs no database.
func TestAGroupsMoveOfAModuleIsNeitherTwoDefinitionsNorARemoval(t *testing.T) {
const catalogueRepo, controllerRepo = "novox/mesh-catalog", "novox/mesh-controller"
built := func(repo, path string) inventory.Source {
return inventory.Source{Repository: "http://forge.internal:20000/" + repo + ".git", Path: path, BuiltFrom: "c0ffee"}
}
sources := map[string]inventory.Source{"mover": built(catalogueRepo, "modules/mover")}
raw := `{"module":"mover","version":"1"}`
base := map[string]catalogue.Manifest{}
m, err := catalogue.ParseManifest([]byte(raw))
if err != nil {
t.Fatal(err)
}
base["mover"] = m
facts := snapshot.Facts{Machines: []snapshot.Machine{{Name: "anchor", Assigned: []string{"mover"}}}}
catalogueWithout := aTree(t, map[string]string{"other": `{"module":"other","version":"1"}`})
controllerWith := func() string {
dir := t.TempDir()
if err := os.MkdirAll(filepath.Join(dir, "modules", "mover"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "modules", "mover", "module.json"), []byte(raw), 0o644); err != nil {
t.Fatal(err)
}
return dir
}()
removes := groupChange{Repository: catalogueRepo, Tree: catalogueWithout, Changed: []string{"modules/mover/module.json"}}
defines := groupChange{Repository: controllerRepo, Tree: controllerWith, Changed: []string{"modules/mover/module.json"}}
lay := func(heads ...groupChange) (map[string]catalogue.Manifest, []string, map[string]string) {
t.Helper()
moves, err := movesInGroup(heads, sources)
if err != nil {
t.Fatal(err)
}
at := sources
if len(moves) > 0 {
at = map[string]inventory.Source{}
for k, v := range sources {
at[k] = v
}
for name, mv := range moves {
o := at[name]
o.Repository, o.Path = mv.To, mv.Dir
at[name] = o
}
}
shelf, said := base, map[string]string{}
var failures []string
for _, h := range heads {
var f []string
if shelf, f, err = shelfWithChange(shelf, at, facts, h.Repository, h.Tree, said, nil); err != nil {
t.Fatal(err)
}
failures = append(failures, f...)
}
for name := range moves {
said[name] = "moved"
}
return shelf, failures, said
}
for _, order := range [][]groupChange{{removes, defines}, {defines, removes}} {
shelf, failures, said := lay(order...)
if len(failures) > 0 {
t.Fatalf("a move of a module machines run, laid %s first, was refused: %v", order[0].Repository, failures)
}
if said["mover"] != "moved" {
t.Errorf("the move reads %v, not moved", said)
}
if _, held := shelf["mover"]; !held {
t.Errorf("the moved module is not on the shelf after laying %s first", order[0].Repository)
}
}
// Alone, a head is still refused: a second definition (issue 239), and a removal of what runs (ADR 0236).
if _, failures, _ := lay(defines); len(failures) != 1 || !strings.Contains(failures[0], "issue 239") {
t.Errorf("a definition of a module another repository builds, alone: %v", failures)
}
if _, failures, _ := lay(removes); len(failures) != 1 || !strings.Contains(failures[0], "ADR 0236") {
t.Errorf("the removal of a module machines run, alone: %v", failures)
}
}
// A group that moves a module machines run passes the gate whole, in either order of its heads, and says the
// module moved (novox/hq ADR 0420 §6, issue 1025); a head alone is still refused. Needs the test store.
func TestADeliveryGroupMovingAModuleMachinesRunPassesTheGate(t *testing.T) {
f, manifests := catalogueMeshWithAnApp(t)
catTree := aTree(t, withEdit(manifests, "album", ""))
app := t.TempDir()
if err := os.MkdirAll(filepath.Join(app, "modules", "album"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(app, "modules", "album", "module.json"), []byte(manifests["album"]), 0o644); err != nil {
t.Fatal(err)
}
removes := groupChange{Repository: "novox/mesh-catalog", Tree: catTree, Changed: []string{"modules/album/module.json"}}
defines := groupChange{Repository: "novox/app", Tree: app, Changed: []string{"modules/album/module.json"}}
judge := func(first groupChange, rest ...groupChange) mergeVerdict {
v, err := judgeChange(t.Context(), mergeCheckInput{facts: f, admin: os.Getenv("MESH_TEST_POSTGRES"),
repository: first.Repository, tree: first.Tree, changed: first.Changed, group: rest})
if err != nil {
t.Fatal(err)
}
return v
}
for _, v := range []mergeVerdict{judge(removes, defines), judge(defines, removes)} {
if v.Verdict == "fail" || v.Modules["album"] != "moved" {
t.Fatalf("a group moving a module machines run: %v\n%s", v.Modules, v.Report())
}
}
if v := judge(defines); v.Verdict != "fail" {
t.Errorf("a definition of a module another repository builds passed alone:\n%s", v.Report())
}
if v := judge(removes); v.Verdict != "fail" {
t.Errorf("the removal of a module machines run passed alone:\n%s", v.Report())
}
}