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
112 lines
4.6 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|