Files
mesh-sdk/go/catalogue/keyed_test.go
T

135 lines
6.5 KiB
Go

package catalogue
import (
"reflect"
"slices"
"strings"
"testing"
)
// The one rule replays the live cases of 2026-10-30 (issue 774): what the assigner's rule 8 waits for is what the
// composition keys, judged from the manifests alone.
func TestAwaitedIsWhatTheCompositionKeys(t *testing.T) {
built := &Handed{Source: "store://t", Digest: "sha256:t", Templates: map[string]string{"c": "c"}}
offer := func(module, name string, scope string, helper bool, serves bool) Manifest {
o := Offer{Name: name, Scope: scope}
if helper {
o.AnsweredBy = AnsweredByHelper
}
m := Manifest{Module: module, Provides: []Offer{o}}
if serves {
m.Serves = map[string]map[string]any{name: {"port": 1}}
}
return m
}
gitea, distribution := offer("gitea", "forge-account", ScopeMesh, false, false), offer("distribution", "artifact-store", ScopeMesh, false, false)
goTools, nodeJS := offer("go", "go-toolchain", ScopeNode, false, false), offer("nodejs", "node-toolchain", ScopeNode, false, false)
keycloak, proxy := offer("keycloak", "oidc-client", ScopeMesh, false, false), offer("route-proxy", "route", ScopeMesh, true, false)
adapter := offer("route-adapter", "route", ScopeMesh, false, false)
ollama := offer("ollama", "llm", ScopeNode, false, true)
by := func(ms ...Manifest) func(string) []Manifest {
return func(p string) []Manifest {
var out []Manifest
for _, m := range ms {
if slices.Contains(m.Offers(), p) {
out = append(out, m)
}
}
return out
}
}
for _, c := range []struct {
name string
consumer Manifest
providers func(string) []Manifest
want []string
}{
{"a handed builder waits for what binds, not for a toolchain on its own machine (mesh-build-go on ace)",
Manifest{Module: "mesh-build-go", Moved: true, Handed: built,
Requires: []string{"forge-account", "go-toolchain", "artifact-store", "node-tools"}},
by(gitea, distribution, goTools), []string{"artifact-store", "forge-account"}},
{"node-toolchain alike (mesh-build-node)",
Manifest{Module: "mesh-build-node", Moved: true, Handed: built, Requires: []string{"node-toolchain", "forge-account"}},
by(gitea, nodeJS), []string{"forge-account"}},
{"a module not handed waits for its route, which the helper answers, and not for its login the controller still makes (grafana on ace)",
Manifest{Module: "grafana", Requires: []string{"route", "oidc-client", "influxdb-api"},
OptionalRequires: map[string]bool{"influxdb-api": true}},
by(keycloak, proxy), []string{"route"}},
{"where providers disagree on the helper, nothing is waited for that might never be keyed",
Manifest{Module: "grafana", Requires: []string{"route"}}, by(proxy, adapter), nil},
{"a moved module with no template is not handed (a held stub, issue 1126)",
Manifest{Module: "bot", Moved: true, Handed: &Handed{}, Requires: []string{"oidc-client", "route"}},
by(keycloak, proxy), []string{"route"}},
{"a provision on its own machine that serves facts binds, and is keyed for a handed consumer",
Manifest{Module: "chat", Moved: true, Handed: built, Requires: []string{"llm", "llm"}}, by(ollama), []string{"llm"}},
} {
if got := Awaited(c.consumer, c.providers); !reflect.DeepEqual(got, c.want) {
t.Errorf("%s: awaited %v, want %v", c.name, got, c.want)
}
}
}
// A module not handed has a plan for what a provider's helper answers of it, and for nothing else (issue 774: grafana
// had no plan, so no route key, and route-proxy served no route for it); a need the controller still answers stays out.
func TestAModuleNotHandedHasAPlanForWhatAHelperAnswers(t *testing.T) {
consumer := notifierConsumer(t, false, false)
consumer.Requires = append(consumer.Requires, "route")
consumer.Contributes = map[string]map[string]any{"route": {"host": "notify.example.org"}}
node := workstation()
node.At = "workstation.internal"
proxy := Manifest{Module: "proxy", Version: "1", Provides: []Offer{{Name: "route", Scope: ScopeMesh, AnsweredBy: AnsweredByHelper}}}
store := Manifest{Module: "store", Version: "1", Provides: []Offer{{Name: "postgres-database", Scope: ScopeMesh,
Credential: &OfferCredential{Form: CredentialFormPair}}}, Serves: map[string]map[string]any{"postgres-database": {"port": 5432}}}
w := twoStores("anchor")
w.Offered["route"] = []Provider{{Node: "anchor", At: "anchor.internal", Module: "proxy"}}
r, err := Resolve(shelf(consumer, proxy, store), []string{consumer.Module}, node, w)
if err != nil {
t.Fatal(err)
}
r.Account = "op"
for i, n := range r.Needs {
// As the plan sets it (compose's answeredByHelper).
r.Needs[i].AnsweredByHelper = n.Name == "route"
}
_, before := composeWith(t, r, Rendering{})
c, got := composeWith(t, r, handingTo("store:5000"))
if got != before {
t.Fatalf("the plan changed what is composed:\nbefore: %s\nnow: %s", before, got)
}
plan, ok := c.Plans["notify"]
if !ok || len(plan.Provisions) != 1 || plan.Provisions[0].Provision != "route" || plan.Provisions[0].ProviderModule != "proxy" {
t.Fatalf("the plan is %+v (%v), want the route alone", plan, ok)
}
if plan.Templates != (PlanTemplates{}) || plan.Files != nil || plan.Secrets != nil ||
!strings.Contains(plan.Provisions[0].As, "notify") || plan.Provisions[0].Contributes["host"] != "notify.example.org" {
t.Fatalf("the plan names %+v", plan)
}
// And it hands nothing: Plan.Hands, the one rule every reader of a plan asks, by each part a plan can name.
if plan.Hands() {
t.Fatalf("a plan naming the route alone hands its module: %+v", plan)
}
for _, c := range []struct {
name string
plan Plan
hands bool
}{
{"empty", Plan{}, false},
{"provisions alone", Plan{Provisions: plan.Provisions}, false},
{"a digest without an archive", Plan{Templates: PlanTemplates{Digest: "sha256:1"}}, false},
{"templates alone", Plan{Templates: PlanTemplates{Source: "notify.tar", Digest: "sha256:1"}}, true},
{"files alone", Plan{Files: []PlanFile{{ID: "conf", Path: "/etc/notify.conf"}}}, true},
{"secrets alone", Plan{Secrets: []PlanSecret{{Name: "admin"}}}, true},
{"items alone", Plan{Items: []PlanItem{{Collection: "routes", Template: "route.tmpl"}}}, true},
{"secrets and provisions", Plan{Secrets: []PlanSecret{{Name: "admin"}}, Provisions: plan.Provisions}, true},
} {
if got := c.plan.Hands(); got != c.hands {
t.Errorf("%s: Hands() is %v, want %v", c.name, got, c.hands)
}
}
if want := Awaited(consumer, func(p string) []Manifest {
return map[string][]Manifest{"route": {proxy}, "postgres-database": {store}}[p]
}); !reflect.DeepEqual(want, []string{"route"}) {
t.Fatalf("the assigner waits for %v, and the plan keys the route alone", want)
}
}