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::name|internal-name|name-|internal-name-}`, 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) } } }