Files
mesh-host/internal/compose/resolution/handed_providers_test.go
T
jschoubben d68e241724 Take the catalogue's composition rules into the node-engine
ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk
carries no composition rule. The catalogue functions only the
node-engine's composition reaches (Resolve, Resolution's Compose,
Declaration, DataItems, LeftOut, Rules and the rest, the settings,
placeholder and template refusals, the rendering helpers) are copied
with their tests into internal/compose/resolution; a method of a
mesh-sdk shape becomes a function taking it first. The composition
calls its own copies, and the captured catalogue its tests read lives
here now, shared by mesh-compose's tests.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-02 19:15:55 +01:00

112 lines
4.6 KiB
Go

package resolution
import (
"reflect"
"strings"
"testing"
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
)
// notifierConsumer is the notifier requiring a mesh-scoped database, marked moved when moved, with its credential
// kept under two local names (ADR 0094) when locals is set.
func notifierConsumer(t *testing.T, moved, locals bool) mcat.Manifest {
t.Helper()
raw := handedNotifier
if moved {
raw = notifierMoved()
}
add := `"requires":["postgres-database"],`
if locals {
add += `"secrets":{"postgres-database":{"hot":"/var/lib/notify/hot","cold":"/var/lib/notify/cold"}},`
}
raw = strings.Replace(raw, `"build":`, add+`"build":`, 1)
return held(t, raw, builtNotifier())
}
// twoStores is a mesh where two machines each run a store providing the database, the consumer pinned to one.
func twoStores(pin string) mcat.World {
return mcat.World{Offered: map[string][]mcat.Provider{"postgres-database": {
{Node: "anchor", At: "anchor.internal", Module: "store", Serves: map[string]any{"port": 5432}},
{Node: "home", At: "home.internal", Module: "store-two", Serves: map[string]any{"port": 5434}},
}}, Pinned: map[string]mcat.Chosen{"postgres-database": {Node: pin}}}
}
func consumerOn(t *testing.T, m mcat.Manifest, w mcat.World) mcat.Resolution {
t.Helper()
node := workstation()
node.At = "workstation.internal"
stores := shelf(m, mcat.Manifest{Module: "store", Version: "1", Provides: []mcat.Offer{{Name: "postgres-database", Scope: mcat.ScopeMesh,
Credential: &mcat.OfferCredential{Form: mcat.CredentialFormPair}}},
Serves: map[string]map[string]any{"postgres-database": {"port": 5432}}}, mcat.Manifest{Module: "store-two", Version: "1", Provides: []mcat.Offer{{Name: "postgres-database", Scope: mcat.ScopeMesh, Credential: &mcat.OfferCredential{Form: mcat.CredentialFormPair}}},
Serves: map[string]map[string]any{"postgres-database": {"port": 5434}}})
r, err := Resolve(stores, []string{m.Module}, node, w)
if err != nil {
t.Fatal(err)
}
r.Account = "op"
return r
}
// A consumer's plan entry carries the names the mesh composed for it through that provision (issue 792): what its
// binding file names as `${bound:<provision>:name|internal-name|name-<local>|internal-name-<local>}`, so a moved
// provider's helper copies them into the consumer's entry and the client fills the same text. A consumer
// contributing no label has the entry it had.
func TestAHandedModulesPlanEntryCarriesTheNamesComposedForIt(t *testing.T) {
m := notifierConsumer(t, true, false)
m.ContributesMany = map[string]map[string]map[string]any{
"postgres-database": {"web": {"label": "git"}, "api": {"label": "git-api"}}}
r := consumerOn(t, m, twoStores("anchor"))
r.PublicDomain = "example.org"
c, _ := composeWith(t, r, handingTo("store:5000"))
got := c.Plans["notify"].Provisions
if len(got) != 1 {
t.Fatalf("the plan names %+v", got)
}
want := map[string]string{"name-web": "git.example.org", "name-api": "git-api.example.org"}
own, err := resolutionOwnNames(r, m, "postgres-database", nil)
if err != nil {
t.Fatal(err)
}
file := map[string]string{}
withOwnNames(file, own)
for k, v := range file {
if strings.HasPrefix(k, "internal-") {
want[k] = v
}
}
if !reflect.DeepEqual(got[0].Names, want) {
t.Fatalf("the entry carries %v, the binding file names %v", got[0].Names, want)
}
}
// A plan entry names the tool of an offer whose credential the provider's software issues (issue 928), so the vault
// asks that tool for each holder; an offer of any other form names none.
func TestAHandedModulesPlanNamesTheCredentialToolOfASoftwareIssuedOffer(t *testing.T) {
for credential, want := range map[string]string{
``: "",
`,"credential":{"own":"admin"}`: "",
`,"credential":{"issued-by":"outside"}`: "",
`,"credential":{"issued-by":"software","tool":"gitea_agent_token"}`: "gitea_agent_token",
} {
raw := `{"module":"store","version":"1","provides":[{"name":"postgres-database","scope":"mesh"` +
credential + `}],"serves":{"postgres-database":{"port":5432}}`
if strings.Contains(credential, `"own"`) {
raw += `,"own-secrets":{"admin":{"path":"/var/lib/store/admin","taken":"at-start"}}`
}
node := workstation()
node.At = "workstation.internal"
consumer := notifierConsumer(t, true, false)
r, err := Resolve(shelf(consumer, mustParse(t, raw+`}`)), []string{consumer.Module}, node, twoStores("anchor"))
if err != nil {
t.Fatal(err)
}
r.Account = "op"
c, _ := composeWith(t, r, handingTo("store:5000"))
got := c.Plans["notify"].Provisions
if len(got) != 1 || got[0].CredentialTool != want {
t.Errorf("an offer with %q: the plan names %+v, want the tool %q", credential, got, want)
}
}
}