529 lines
21 KiB
Go
529 lines
21 KiB
Go
//go:build integration
|
|
|
|
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
snapshot "github.com/novox/mesh-controller/internal/facts"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
func catalogueMesh(t *testing.T) (snapshot.Facts, map[string]string) {
|
|
t.Helper()
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
manifests := map[string]string{
|
|
"objects": `{"module":"objects","version":"1",
|
|
"provides":[{"name":"s3-bucket","scope":"mesh","identity":{"max":20,"in":"an S3 access key"}}],
|
|
"receives":{"s3-bucket":"/var/lib/mesh/objects/mesh.json"}}`,
|
|
"resolver": `{"module":"resolver","version":"1",
|
|
"provides":[{"name":"wildcard-resolution","scope":"mesh","identity":false}]}`,
|
|
"networkmanager": `{"module":"networkmanager","version":"1","requires":["wildcard-resolution"]}`,
|
|
"album": `{"module":"album","version":"1","requires":["s3-bucket"]}`,
|
|
"lemurs": `{"module":"lemurs","version":"1","capabilities":["systemd"],
|
|
"resources":[{"id":"service","type":"service","unit":"lemurs.service","state":"running","boot":"enabled"}]}`,
|
|
}
|
|
for name, raw := range manifests {
|
|
m, err := catalogue.ParseManifest([]byte(raw))
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", name, err)
|
|
}
|
|
if err := open.inventory.RegisterModule(ctx, m, inventory.Source{Repository: "novox/mesh-catalog",
|
|
Path: "modules/" + name, BuiltFrom: "c0ffee"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, a := range [][2]string{{"anchor", "objects"}, {"anchor", "resolver"}, {"laptop", "networkmanager"},
|
|
{"laptop", "album"}, {"laptop", "lemurs"}} {
|
|
if _, err := assign(ctx, open, a[0], a[1]); err != nil {
|
|
t.Fatalf("assign %s %s: %v", a[0], a[1], err)
|
|
}
|
|
}
|
|
f, err := gatherFacts(ctx, open, "2.11.17")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return f, manifests
|
|
}
|
|
|
|
func gateJudged(t *testing.T, f snapshot.Facts, tree string, changed ...string) mergeVerdict {
|
|
t.Helper()
|
|
admin := os.Getenv("MESH_TEST_POSTGRES")
|
|
in := mergeCheckInput{facts: f, admin: admin, changed: changed}
|
|
if tree != "" {
|
|
in.repository, in.tree = "novox/mesh-catalog", tree
|
|
}
|
|
v, err := judgeChange(t.Context(), in)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func TestTheMeshAsItIsPassesTheGate(t *testing.T) {
|
|
f, manifests := catalogueMesh(t)
|
|
for _, m := range f.Machines {
|
|
if !m.Declaration.Composes {
|
|
t.Fatalf("the mesh itself does not compose: %+v", m.Declaration)
|
|
}
|
|
}
|
|
v := gateJudged(t, f, aTree(t, manifests))
|
|
if v.Verdict != "pass" {
|
|
t.Fatalf("an unchanged catalogue does not pass:\n%s", v.Report())
|
|
}
|
|
for _, m := range v.Machines {
|
|
if !m.Base.Composes || !m.Change.Composes {
|
|
t.Errorf("%s does not compose in the gate's store as it does on the mesh: %+v / %+v", m.Described, m.Base, m.Change)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIssue263AnIdentityARealMachineNameOverflowsFailsThePullRequest(t *testing.T) {
|
|
f, manifests := catalogueMesh(t)
|
|
tree := aTree(t, withEdit(manifests, "networkmanager",
|
|
`{"module":"networkmanager","version":"1","requires":["wildcard-resolution","s3-bucket"]}`))
|
|
v := gateJudged(t, f, tree)
|
|
if v.Verdict != "fail" {
|
|
t.Fatalf("the overflow passed the gate:\n%s", v.Report())
|
|
}
|
|
report := v.Report()
|
|
if !strings.Contains(report, "networkmanager") || !strings.Contains(report, "s3-bucket") {
|
|
t.Errorf("the refusal does not name the module and the provision:\n%s", report)
|
|
}
|
|
}
|
|
|
|
func TestIssue236AManifestTheNodeEngineRefusesFailsThePullRequest(t *testing.T) {
|
|
f, manifests := catalogueMesh(t)
|
|
broken := `{"module":"lemurs","version":"1","capabilities":["systemd"],
|
|
"resources":[{"id":"service","type":"service","unit":"lemurs.service","boot":"enabled"}]}`
|
|
v := gateJudged(t, f, aTree(t, withEdit(manifests, "lemurs", broken)))
|
|
if v.Verdict != "fail" || !strings.Contains(v.Report(), "lemurs") {
|
|
t.Fatalf("a service the node-engine refuses passed the gate:\n%s", v.Report())
|
|
}
|
|
laptop := snapshot.Pseudonym("machine", "laptop")
|
|
if !strings.Contains(v.Report(), laptop) {
|
|
t.Errorf("the refusal does not name the machine it would be refused on:\n%s", v.Report())
|
|
}
|
|
|
|
// And as a new module, which no machine runs: tried on one that could.
|
|
newcomer := strings.ReplaceAll(broken, `"lemurs"`, `"greeter"`)
|
|
newcomer = strings.ReplaceAll(newcomer, "lemurs.service", "greeter.service")
|
|
v = gateJudged(t, f, aTree(t, withEdit(manifests, "greeter", newcomer)))
|
|
if v.Verdict != "fail" || !strings.Contains(v.Report(), "greeter, assigned to") {
|
|
t.Fatalf("a new module the node-engine would refuse passed the gate:\n%s", v.Report())
|
|
}
|
|
}
|
|
|
|
func TestAManifestTheRunningControllerCannotReadFailsThePullRequest(t *testing.T) {
|
|
f, manifests := catalogueMesh(t)
|
|
v := gateJudged(t, f, aTree(t, withEdit(manifests, "album",
|
|
`{"module":"album","version":"1","requires":["s3-bucket"],"a-field-of-a-newer-controller":true}`)))
|
|
if v.Verdict != "fail" || !strings.Contains(v.Report(), "refuses it") {
|
|
t.Fatalf("a manifest this controller cannot read passed:\n%s", v.Report())
|
|
}
|
|
}
|
|
|
|
func TestAModuleAMachineRunsRemovedFromItsSourceFails(t *testing.T) {
|
|
f, manifests := catalogueMesh(t)
|
|
v := gateJudged(t, f, aTree(t, withEdit(manifests, "album", "")))
|
|
if v.Verdict != "fail" || !strings.Contains(v.Report(), "album is removed") {
|
|
t.Fatalf("removing a module a machine runs passed:\n%s", v.Report())
|
|
}
|
|
}
|
|
|
|
func TestIssue278AWideRebuildIsSaidBeforeTheMerge(t *testing.T) {
|
|
f, manifests := catalogueMesh(t)
|
|
was := wideRebuild
|
|
wideRebuild = 2
|
|
t.Cleanup(func() { wideRebuild = was })
|
|
for _, file := range []string{"modules/showcase/index.ts", "merge-check.sh", "README.md"} {
|
|
v := gateJudged(t, f, aTree(t, manifests), file)
|
|
if v.Width == nil || len(v.Width.Modules) != 0 || len(v.Width.Unread) != 1 || v.Verdict != "pass" {
|
|
t.Fatalf("%s, read by no build, reads %+v, %s", file, v.Width, v.Verdict)
|
|
}
|
|
if !strings.Contains(v.Report(), "read by no module's build") {
|
|
t.Errorf("why %s rebuilds nothing is not said:\n%s", file, v.Report())
|
|
}
|
|
}
|
|
v := gateJudged(t, f, aTree(t, manifests), "modules/album/x.ts", "modules/objects/x.go", "modules/resolver/x.go")
|
|
if v.Width == nil || len(v.Width.Modules) < 3 || v.Verdict != "warning" {
|
|
t.Fatalf("a rebuild wider than the bound is not warned of: %+v, %s", v.Width, v.Verdict)
|
|
}
|
|
// With the reference module's definition in the tree, its directory is a module, held or not.
|
|
withShowcase := withEdit(manifests, "showcase", `{"module":"showcase","version":"1"}`)
|
|
v = gateJudged(t, f, aTree(t, withShowcase), "modules/showcase/index.ts")
|
|
if v.Width == nil || len(v.Width.Modules) != 0 || len(v.Width.Unread) != 0 {
|
|
t.Fatalf("a file of a module nobody holds reads %+v", v.Width)
|
|
}
|
|
v = gateJudged(t, f, aTree(t, manifests), "modules/album/module.json")
|
|
if v.Width == nil || strings.Join(v.Width.Modules, ",") != "album" || v.Verdict != "pass" {
|
|
t.Fatalf("a change to one module's directory reads %+v, %s", v.Width, v.Verdict)
|
|
}
|
|
}
|
|
|
|
func TestADeliveryGroupsHeadsAreComposedTogether(t *testing.T) {
|
|
f, manifests := catalogueMeshWithAnApp(t)
|
|
needsTheApp := withEdit(manifests, "album", `{"module":"album","version":"1","requires":["s3-bucket","app-api"]}`)
|
|
catTree := aTree(t, needsTheApp)
|
|
alone := gateJudged(t, f, catTree, "modules/album/module.json")
|
|
if alone.Verdict != "fail" {
|
|
t.Fatalf("a requirement nothing provides passed alone:\n%s", alone.Report())
|
|
}
|
|
app := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(app, "module.json"), []byte(`{"module":"app","version":"1",
|
|
"provides":[{"name":"app-api","scope":"mesh","identity":false}]}`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
in := mergeCheckInput{facts: f, admin: os.Getenv("MESH_TEST_POSTGRES"), repository: "novox/mesh-catalog",
|
|
tree: catTree, changed: []string{"modules/album/module.json"},
|
|
group: []groupChange{{Repository: "novox/app", Tree: app, Changed: []string{"module.json"}}}}
|
|
together, err := judgeChange(t.Context(), in)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if together.Verdict == "fail" {
|
|
t.Fatalf("the group composed together fails:\n%s", together.Report())
|
|
}
|
|
if together.Modules["app"] != "changed" || together.Modules["album"] != "changed" {
|
|
t.Fatalf("the group's heads were not both laid over the mesh: %v", together.Modules)
|
|
}
|
|
}
|
|
|
|
func catalogueMeshWithAnApp(t *testing.T) (snapshot.Facts, map[string]string) {
|
|
t.Helper()
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
manifests := map[string]string{
|
|
"objects": `{"module":"objects","version":"1",
|
|
"provides":[{"name":"s3-bucket","scope":"mesh","identity":{"max":20,"in":"an S3 access key"}}],
|
|
"receives":{"s3-bucket":"/var/lib/mesh/objects/mesh.json"}}`,
|
|
"album": `{"module":"album","version":"1","requires":["s3-bucket"]}`,
|
|
}
|
|
for name, raw := range manifests {
|
|
m, err := catalogue.ParseManifest([]byte(raw))
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", name, err)
|
|
}
|
|
if err := open.inventory.RegisterModule(ctx, m, inventory.Source{Repository: "novox/mesh-catalog",
|
|
Path: "modules/" + name, BuiltFrom: "c0ffee"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := open.inventory.RegisterModule(ctx, catalogue.Manifest{Module: "app", Version: "1"},
|
|
inventory.Source{Repository: "novox/app", BuiltFrom: "c0ffee"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, a := range [][2]string{{"anchor", "objects"}, {"anchor", "app"}, {"laptop", "album"}} {
|
|
if _, err := assign(ctx, open, a[0], a[1]); err != nil {
|
|
t.Fatalf("assign %s %s: %v", a[0], a[1], err)
|
|
}
|
|
}
|
|
f, err := gatherFacts(ctx, open, "2.11.17")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return f, manifests
|
|
}
|
|
|
|
func busMesh(t *testing.T) snapshot.Facts {
|
|
t.Helper()
|
|
open := aMesh(t)
|
|
ctx := t.Context()
|
|
m, err := catalogue.ParseManifest([]byte(`{"module":"speaker","version":"1",
|
|
"own-secrets":{"broker":"/var/lib/speaker/broker"}}`))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := open.inventory.RegisterModule(ctx, m, inventory.Source{Repository: "novox/mesh-catalog",
|
|
Path: "modules/speaker", BuiltFrom: "c0ffee"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := assign(ctx, open, "laptop", "speaker"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
user := broker.Principal{Kind: broker.KindModule, Node: "laptop", Module: "speaker"}.Username()
|
|
password, err := open.inventory.MintBusPassword(ctx, inventory.BusUser{Username: user, Kind: inventory.BusModule,
|
|
Node: "laptop", Module: "speaker"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := open.inventory.AcceptSecretForModule(ctx, "laptop", "speaker", "broker",
|
|
`{"user":"`+user+`","password":"`+password+`"}`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
f, err := gatherFacts(ctx, open, "2.11.17")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, mc := range f.Machines {
|
|
if !mc.Declaration.Composes {
|
|
t.Fatalf("the mesh itself does not compose: %+v", mc.Declaration)
|
|
}
|
|
}
|
|
return f
|
|
}
|
|
|
|
func TestIssue285AModuleOnTheBusComposesInTheGate(t *testing.T) {
|
|
f := busMesh(t)
|
|
v := gateJudged(t, f, "")
|
|
if v.Verdict != "pass" {
|
|
t.Fatalf("the mesh as it is does not pass:\n%s", v.Report())
|
|
}
|
|
for _, m := range v.Machines {
|
|
if !m.Base.Composes {
|
|
t.Errorf("%s composes on the mesh and not in the gate: %v", m.Described, m.Base.Problems)
|
|
}
|
|
}
|
|
if !strings.Contains(v.Summary, "2 of 2 compose") {
|
|
t.Errorf("the summary does not say every machine composes: %s", v.Summary)
|
|
}
|
|
}
|
|
|
|
func TestIssue285AMachineTheGateCannotRaiseIsAnErrorNeverAPass(t *testing.T) {
|
|
f := busMesh(t)
|
|
for i := range f.Machines {
|
|
// A fact the snapshot does not carry: the module's credential, gone from the laptop's.
|
|
var kept []snapshot.Accepted
|
|
for _, a := range f.Machines[i].Accepted {
|
|
if a.Name != "broker" {
|
|
kept = append(kept, a)
|
|
}
|
|
}
|
|
f.Machines[i].Accepted = kept
|
|
}
|
|
v := gateJudged(t, f, "")
|
|
if v.Verdict != "error" {
|
|
t.Fatalf("a gate whose mesh does not compose said %s:\n%s", v.Verdict, v.Report())
|
|
}
|
|
if len(v.Errors) != 1 || !strings.Contains(v.Errors[0], "speaker") {
|
|
t.Errorf("the error does not name what could not be raised: %v", v.Errors)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
func TestADeliveryGroupMovingAModuleMachinesRunPassesTheGate(t *testing.T) {
|
|
f, manifests := catalogueMeshWithAnApp(t)
|
|
catTree := aTree(t, withEdit(manifests, "album", ""))
|
|
app := t.TempDir()
|
|
// The app module is built from this repository's root, and stays there: only album moves.
|
|
if err := os.WriteFile(filepath.Join(app, "module.json"), []byte(`{"module":"app","version":"1"}`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
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())
|
|
}
|
|
}
|
|
|
|
func TestTheGateRefusesAnArtifactContextNamingAnotherRepository(t *testing.T) {
|
|
withContext := func(repo string) string {
|
|
return `{"module":"proxy","version":"1","build":{"artifacts":[{"name":"server","kind":"image","from":"Dockerfile",` +
|
|
`"context":{"seat":"git","repository":"` + repo + `","ref":"main"}}]}}`
|
|
}
|
|
judge := func(repo string) []string {
|
|
t.Helper()
|
|
tree := aTree(t, map[string]string{"proxy": withContext(repo)})
|
|
_, failures, err := shelfWithChange(map[string]catalogue.Manifest{}, map[string]inventory.Source{},
|
|
snapshot.Facts{}, "novox/mesh-controller", tree, map[string]string{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return failures
|
|
}
|
|
const want = "proxy builds artifact server from novox/mesh-catalog: a module lives whole in one repository (novox/hq ADR 0420 §1)"
|
|
failures := judge("novox/mesh-catalog")
|
|
if len(failures) != 1 || !strings.Contains(failures[0], want) {
|
|
t.Errorf("a context naming another repository: %v, want it to say %q", failures, want)
|
|
}
|
|
for _, f := range judge("novox/mesh-controller") {
|
|
if strings.Contains(f, "ADR 0420 §1") {
|
|
t.Errorf("a context naming the module's own repository was refused: %s", f)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAMemberJudgedAloneSeesAMoveThroughTheClonesBeside(t *testing.T) {
|
|
const catalogueRepo, controllerRepo = "novox/mesh-catalog", "novox/mesh-controller"
|
|
raw := `{"module":"mover","version":"1"}`
|
|
sources := map[string]inventory.Source{"mover": {Repository: "http://forge.internal:20000/" + catalogueRepo + ".git",
|
|
Path: "modules/mover", BuiltFrom: "c0ffee"}}
|
|
// A tree is written into dir: defining mover, or holding only another module.
|
|
write := func(dir string, defines bool) string {
|
|
name, body := "other", `{"module":"other","version":"1"}`
|
|
if defines {
|
|
name, body = "mover", raw
|
|
}
|
|
if err := os.MkdirAll(filepath.Join(dir, "modules", name), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "modules", name, "module.json"), []byte(body), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return dir
|
|
}
|
|
defining := func() string { return write(t.TempDir(), true) }
|
|
without := func() string { return write(t.TempDir(), false) }
|
|
// beside lays out a MESH_CHECK_BESIDE directory holding the named repositories' trees, each defining mover or not.
|
|
beside := func(trees map[string]bool) string {
|
|
root := t.TempDir()
|
|
for name, defines := range trees {
|
|
write(filepath.Join(root, name), defines)
|
|
}
|
|
return root
|
|
}
|
|
moves := func(repository, tree, besideRoot string) map[string]moveOfModule {
|
|
t.Helper()
|
|
got, err := movesOfChange(mergeCheckInput{repository: repository, tree: tree, beside: besideRoot}, sources)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return got
|
|
}
|
|
// The controller's member: defines mover; the catalogue beside has removed it.
|
|
if got := moves(controllerRepo, defining(), beside(map[string]bool{"mesh-catalog": false})); got["mover"].To != controllerRepo {
|
|
t.Errorf("the controller's change defining mover, the catalogue beside without it: moves %v", got)
|
|
}
|
|
// The sources the gate then judges with: built from the controller, the definition is not a second one (issue 239);
|
|
// unmoved, it still is.
|
|
judge := func(at map[string]inventory.Source) []string {
|
|
t.Helper()
|
|
_, failures, err := shelfWithChange(map[string]catalogue.Manifest{}, at, snapshot.Facts{}, controllerRepo, defining(),
|
|
map[string]string{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return failures
|
|
}
|
|
mv := moves(controllerRepo, defining(), beside(map[string]bool{"mesh-catalog": false}))["mover"]
|
|
moved := map[string]inventory.Source{"mover": {Repository: mv.To, Path: mv.Dir, BuiltFrom: "c0ffee"}}
|
|
if f := judge(moved); len(f) != 0 {
|
|
t.Errorf("a definition of a module the catalogue beside removed was refused: %v", f)
|
|
}
|
|
if f := judge(sources); len(f) != 1 || !strings.Contains(f[0], "issue 239") {
|
|
t.Errorf("with the catalogue beside at main, the definition: %v, want the issue 239 refusal", f)
|
|
}
|
|
if got := moves(controllerRepo, defining(), beside(map[string]bool{"mesh-catalog": true})); len(got) != 0 {
|
|
t.Errorf("the catalogue beside at main still defines mover, yet moves %v", got)
|
|
}
|
|
// The catalogue's member: removes mover; the controller beside defines it.
|
|
if got := moves(catalogueRepo, without(), beside(map[string]bool{"mesh-controller": true})); got["mover"].To != controllerRepo {
|
|
t.Errorf("the catalogue's change removing mover, the controller beside defining it: moves %v", got)
|
|
}
|
|
if got := moves(catalogueRepo, without(), beside(map[string]bool{"mesh-controller": false})); len(got) != 0 {
|
|
t.Errorf("the controller beside at main does not define mover, yet moves %v", got)
|
|
}
|
|
if got := moves(catalogueRepo, without(), ""); len(got) != 0 {
|
|
t.Errorf("nothing beside, yet moves %v", got)
|
|
}
|
|
}
|