package resolution import ( "go/ast" "go/parser" "go/token" "io/fs" "path/filepath" "strconv" "strings" "testing" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) func registryShelf() map[string]mcat.Manifest { return shelf(mcat.Manifest{Module: "gitea", Version: "1", Provides: mcat.FromAnywhere("npm-package-registry"), Claims: []mcat.Claim{{Name: "npm-package-registry", Scope: mcat.ScopeMesh}}}, mcat.Manifest{Module: "verdaccio", Version: "1", Provides: mcat.FromAnywhere("npm-package-registry")}, mcat.Manifest{Module: "builder", Version: "1", Requires: []string{"npm-package-registry"}}, ) } func twoRegistries() map[string][]mcat.Provider { return map[string][]mcat.Provider{"npm-package-registry": { {Node: "anchor", At: "anchor.internal", Module: "gitea"}, {Node: "archive", At: "archive.internal", Module: "verdaccio"}, }} } func giteaHoldsTheSeat() []mcat.Held { return []mcat.Held{{Claim: "npm-package-registry", Scope: mcat.ScopeMesh, Node: "anchor", Module: "gitea"}} } func TestTheSeatsHolderAnswersWhenSeveralProvide(t *testing.T) { // The whole point: a second registry beside the holder harms nothing, and nobody pins. got, err := Resolve(registryShelf(), []string{"builder"}, reachable(), mcat.World{Offered: twoRegistries(), Held: giteaHoldsTheSeat()}) if err != nil { t.Fatal(err) } if len(got.Needs) != 1 || got.Needs[0].From != "anchor" { t.Fatalf("the seat's holder did not answer: %v", got.Needs) } } func TestAPinStillWinsOverTheSeat(t *testing.T) { // A consumer coupled to one provider's contents has said so, and the seat does not overrule it. got, err := Resolve(registryShelf(), []string{"builder"}, reachable(), mcat.World{Offered: twoRegistries(), Held: giteaHoldsTheSeat(), Pinned: map[string]mcat.Chosen{"npm-package-registry": {Node: "archive", Module: "verdaccio"}}}) if err != nil { t.Fatal(err) } if len(got.Needs) != 1 || got.Needs[0].From != "archive" { t.Fatalf("the pin was overruled by the seat: %v", got.Needs) } } func TestWithTheSeatUnheldSeveralProvidersAreStillRefused(t *testing.T) { // No seat held is no choice made, and ADR 0009's rule stands: never guessed. _, err := Resolve(registryShelf(), []string{"builder"}, reachable(), mcat.World{Offered: twoRegistries()}) if err == nil { t.Fatal("one of two registries was picked with nobody holding the seat") } if !strings.Contains(err.Error(), "pin") { t.Fatalf("the refusal does not say how to choose: %v", err) } } func TestTheHolderIsTheModuleNotTheMachine(t *testing.T) { // Two modules on one machine could provide the same thing; only the one holding the seat // answers. A holder matched by node alone would send consumers to whichever came first. providers := []mcat.Provider{ {Node: "anchor", At: "anchor.internal", Module: "verdaccio"}, {Node: "anchor", At: "anchor.internal", Module: "gitea"}, } holder, held := HolderAmong("npm-package-registry", providers, giteaHoldsTheSeat()) if !held || holder.Module != "gitea" { t.Fatalf("the holder was not told apart from a neighbour: %+v", holder) } // A seat's verbs are its definition's, read from the bus (novox/hq ADR 0635 ยง1): the node-engine keeps no copy of // them, so no copy can drift from the seat as the seven it kept did (issue 1921). A copy is a list of the catalogue's // verbs written in the code; the guard proves it finds one, then finds none in the node-engine. t.Run("no seat's verbs are copied into the node-engine", func(t *testing.T) { copied := `package x import mcat "git.novox.be/novox/mesh-sdk/go/catalogue" func someVerbs() []mcat.Verb { return []mcat.Verb{{Name: "routes"}} } ` if got := verbCopies(t, "copied.go", copied); len(got) == 0 { t.Fatal("the guard finds no copy in a file that holds one, so it proves nothing") } var found []string root := filepath.Join("..", "..", "..") err := filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() && (d.Name() == "testdata" || strings.HasPrefix(d.Name(), ".")) && p != root { return filepath.SkipDir } if d.IsDir() || !strings.HasSuffix(p, ".go") || strings.HasSuffix(p, "_test.go") { return nil } found = append(found, verbCopies(t, p, nil)...) return nil }) if err != nil { t.Fatal(err) } if len(found) > 0 { t.Errorf("the node-engine copies a seat's verbs instead of reading the seat list from the bus: %v", found) } }) } // verbCopies is where one Go file writes the catalogue's verbs itself: a function answering a list of them, or a verb // written as a literal. func verbCopies(t *testing.T, name string, src any) []string { t.Helper() fset := token.NewFileSet() f, err := parser.ParseFile(fset, name, src, parser.SkipObjectResolution) if err != nil { t.Fatal(err) } alias := "" for _, imp := range f.Imports { if p, _ := strconv.Unquote(imp.Path.Value); p == "git.novox.be/novox/mesh-sdk/go/catalogue" { alias = "catalogue" if imp.Name != nil { alias = imp.Name.Name } } } if alias == "" { return nil } isVerb := func(e ast.Expr) bool { if a, ok := e.(*ast.ArrayType); ok { e = a.Elt } sel, ok := e.(*ast.SelectorExpr) if !ok || sel.Sel.Name != "Verb" { return false } x, ok := sel.X.(*ast.Ident) return ok && x.Name == alias } var out []string ast.Inspect(f, func(n ast.Node) bool { switch n := n.(type) { case *ast.FuncDecl: if n.Type.Results != nil { for _, r := range n.Type.Results.List { if isVerb(r.Type) { out = append(out, fset.Position(n.Pos()).String()+" "+n.Name.Name) } } } case *ast.CompositeLit: if n.Type != nil && isVerb(n.Type) { out = append(out, fset.Position(n.Pos()).String()) } } return true }) return out }