package main import ( "reflect" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/inventory" ) // **The planner over edges the store derives**, not edges written by hand: modules registered, builds // recorded with what they stood on and which repositories they read, the relation answered by // inventory.Dependencies (dependenciesOf over the records), and the merge planned by reachOfMerge — the // path a real merge takes, short of the bus. // // **The repository rows are CURRENT BEHAVIOUR, documented — not the rule the operator states** // (novox/hq issue 338, and the decision pending on it): a build that read a repository gives its module a // packages edge to every module built from that repository, and mergeCandidates moves it on any merge to // that repository, whatever the files. So a change to C alone, or to a README, moves the module that // packages C's repository. ADR 0238 §3 records exactly that today ("a repository a recipe names"); the // expectations marked 338 change with that decision. func TestASharedRepositoryIsPlannedFromTheRecordsAsItIsToday(t *testing.T) { inv := inventory.ForTest(t) ctx := t.Context() asked := time.Now().Add(-time.Hour) register := func(m catalogue.Manifest, repository, path string, against []string, read []inventory.ReadRepository) { t.Helper() if err := inv.RegisterModule(ctx, m, inventory.Source{Repository: repository, Seat: "git", Path: path, Ref: "main", BuiltFrom: "old", Asked: asked}); err != nil { t.Fatal(err) } if err := inv.RecordBuild(ctx, inventory.Build{ID: "build-" + m.Module, Repository: repository, Ref: "main", Module: m.Module, Commit: "old", On: "builder", Path: path, Against: against, Read: read, Asked: asked}); err != nil { t.Fatal(err) } } controllerRead := []inventory.ReadRepository{{Repository: "novox/mesh-controller", Ref: "main"}} agent := catalogue.Manifest{Module: "build-agent", Version: "1", Claims: []catalogue.Claim{{Name: "node-build-agent", Scope: catalogue.ScopeNode}}} // The shape of issue 338. register(catalogue.Manifest{Module: "mesh-controller", Version: "1"}, "novox/mesh-controller", "", nil, nil) register(agent, "novox/mesh-catalog", "modules/build-agent", nil, controllerRead) register(catalogue.Manifest{Module: "route-proxy", Version: "1"}, "novox/mesh-catalog", "modules/route-proxy", nil, controllerRead) register(catalogue.Manifest{Module: "gitea", Version: "1"}, "novox/mesh-catalog", "modules/gitea", nil, nil) // A, B and C in one repository; D built against A's artifact, E declaring B, P packaging the repository. for _, n := range []string{"a", "b", "c"} { register(catalogue.Manifest{Module: n, Version: "1"}, "novox/one", "modules/"+n, nil, nil) } register(catalogue.Manifest{Module: "d", Version: "1"}, "novox/two", "d", []string{catalogue.ArtifactStoreScheme + "a/runtime@sha256:" + strings.Repeat("0", 64)}, nil) register(catalogue.Manifest{Module: "e", Version: "1", Build: &catalogue.Build{ On: []catalogue.BuildsOn{{Arg: "BASE", Module: "b", Artifact: "runtime"}}}}, "novox/two", "e", nil, nil) register(catalogue.Manifest{Module: "p", Version: "1"}, "novox/two", "p", nil, []inventory.ReadRepository{{Repository: "novox/one", Ref: "main"}}) entries, err := inv.Catalogued(ctx) if err != nil { t.Fatal(err) } read, err := inv.ReadRepositories(ctx) if err != nil { t.Fatal(err) } edges, err := inv.Dependencies(ctx) if err != nil { t.Fatal(err) } // The edges the hand-written rows of TestASharedRepositoryMovesWhatPackagesItAsItDoesToday use are // the ones derived here. var shared []inventory.Edge in338 := map[string]bool{"mesh-controller": true, "build-agent": true, "route-proxy": true, "gitea": true} for _, e := range edges { if in338[e.From] && in338[e.To] { shared = append(shared, e) } } if !reflect.DeepEqual(shared, sharedRepositoryEdges) { t.Errorf("derived %v\nthe hand-written rows use %v", shared, sharedRepositoryEdges) } // Each kind derived from its record: built against (stands-on), build.on (declared), read (packages). for _, want := range []inventory.Edge{ dep("d", inventory.EdgeStandsOn, "a"), dep("e", inventory.EdgeDeclared, "b"), dep("p", inventory.EdgePackages, "a"), dep("p", inventory.EdgePackages, "b"), dep("p", inventory.EdgePackages, "c"), dep("d", inventory.EdgeBuiltBy, "build-agent"), } { found := false for _, e := range edges { found = found || e == want } if !found { t.Errorf("no %s %s %s derived: %v", want.From, want.Kind, want.To, edges) } } for _, c := range []struct { what, repo string paths []string want string issue338 bool }{ {"A and B changed, C untouched: D after A, E after B; P packages their repository", "one", []string{"modules/a/x.go", "modules/b/x.go"}, "a,b,p | d,e", false}, {"C alone: C, and P, which packages C's repository", "one", []string{"modules/c/x.go"}, "c,p", true}, {"a README of the repository P packages: P moves, nothing built from it does", "one", []string{"README.md"}, "p", true}, {"the dependent's repository: D alone", "two", []string{"d/main.go"}, "d", false}, {"a README of the controller's repository: all three, three tiers", "mesh-controller", []string{"README.md"}, "mesh-controller | build-agent | route-proxy", true}, {"the route proxy's directory in the catalogue: it alone", "mesh-catalog", []string{"modules/route-proxy/module.json"}, "route-proxy", false}, {"the build agent's directory: it alone, nothing it builds", "mesh-catalog", []string{"modules/build-agent/module.json"}, "build-agent", false}, } { _, got := planMerge(t, c.repo, c.paths, entries, read, edges) if got != c.want { tag := "" if c.issue338 { tag = " (current behaviour, issue 338)" } t.Errorf("%s: planned %q, wanted %q%s", c.what, got, c.want, tag) } } }