package resolution import ( "strings" "testing" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) func issuerShelf() map[string]mcat.Manifest { return shelf(mcat.Manifest{Module: "public-acme", Version: "1", Provides: mcat.FromAnywhere("acme-ca"), Serves: map[string]map[string]any{"acme-ca": {"at": "acme-v02.api.letsencrypt.org"}}}, mcat.Manifest{Module: "step-ca", Version: "1", Provides: mcat.FromAnywhere("acme-ca"), Serves: map[string]map[string]any{"acme-ca": {"at": "novox.internal"}}}, mcat.Manifest{Module: "route-proxy", Version: "1", Requires: []string{"acme-ca"}}, ) } func twoIssuersOnOneNode() map[string][]mcat.Provider { return map[string][]mcat.Provider{"acme-ca": { {Node: "novox", At: "novox.internal", Module: "public-acme", Serves: map[string]any{"at": "acme-v02.api.letsencrypt.org"}}, {Node: "novox", At: "novox.internal", Module: "step-ca", Serves: map[string]any{"at": "novox.internal"}}, }} } func TestTwoProvidersOnOneNodeAreRefusedWithBothNamed(t *testing.T) { // The refusal must name the pair, because a node alone cannot tell them apart. _, err := Resolve(issuerShelf(), []string{"route-proxy"}, reachable(), mcat.World{Offered: twoIssuersOnOneNode()}) if err == nil { t.Fatal("two providers on one node were resolved by picking") } for _, want := range []string{"novox/public-acme", "novox/step-ca", " "} { if !strings.Contains(err.Error(), want) { t.Fatalf("the refusal does not say %q: %v", want, err) } } } func TestAPinNamesTheModule(t *testing.T) { got, err := Resolve(issuerShelf(), []string{"route-proxy"}, reachable(), mcat.World{Offered: twoIssuersOnOneNode(), Pinned: map[string]mcat.Chosen{"acme-ca": {Node: "novox", Module: "public-acme"}}}) if err != nil { t.Fatal(err) } if len(got.Needs) != 1 || got.Needs[0].From != "novox" || got.Needs[0].Serves["at"] != "acme-v02.api.letsencrypt.org" { t.Fatalf("the named module was not the one taken: %+v", got.Needs) } } func TestARecordNamingOnlyTheNodeIsRefusedWhenThatNodeAnswersTwice(t *testing.T) { // A pin from before the module was asked for. It once took the last one listed — a coin flip. _, err := Resolve(issuerShelf(), []string{"route-proxy"}, reachable(), mcat.World{Offered: twoIssuersOnOneNode(), Pinned: map[string]mcat.Chosen{"acme-ca": {Node: "novox"}}}) if err == nil { t.Fatal("a node that answers twice was resolved by picking") } if !strings.Contains(err.Error(), "provides it 2 times") || !strings.Contains(err.Error(), "pin workstation acme-ca novox ") { t.Fatalf("the refusal does not ask for the module: %v", err) } } func TestAPinNamingAModuleThatDoesNotProvideItIsRefused(t *testing.T) { _, err := Resolve(issuerShelf(), []string{"route-proxy"}, reachable(), mcat.World{Offered: twoIssuersOnOneNode(), Pinned: map[string]mcat.Chosen{"acme-ca": {Node: "novox", Module: "gitea"}}}) if err == nil || !strings.Contains(err.Error(), "novox/gitea does not provide it") { t.Fatalf("a module that does not provide it was not refused by name: %v", err) } } func TestTwoProvidersBesideTheConsumerAreRefusedUntilOneIsNamed(t *testing.T) { // The same ambiguity on the consumer's own machine. This was settled by a map walk — random, // per plan — which is how novox's own proxy got its issuer. _, err := Resolve(issuerShelf(), []string{"route-proxy", "public-acme", "step-ca"}, reachable(), mcat.World{}) if err == nil { t.Fatal("two providers beside the consumer were resolved by picking") } if !strings.Contains(err.Error(), "workstation provides \"acme-ca\" 2 times") || !strings.Contains(err.Error(), "public-acme, step-ca") { t.Fatalf("the refusal does not list them: %v", err) } } func TestAPinSettlesTwoProvidersBesideTheConsumer(t *testing.T) { got, err := Resolve(issuerShelf(), []string{"route-proxy", "public-acme", "step-ca"}, reachable(), mcat.World{Pinned: map[string]mcat.Chosen{"acme-ca": {Node: "workstation", Module: "public-acme"}}}) if err != nil { t.Fatal(err) } var found bool for _, n := range got.Needs { if n.Name == "acme-ca" { found = true if n.Serves["at"] != "acme-v02.api.letsencrypt.org" { t.Fatalf("the named module was not the one taken: %+v", n) } } } if !found { t.Fatalf("no need for acme-ca was created: %+v", got.Needs) } } func TestTheFirstPassDoesNotRefuseTwoProvidersBesideTheConsumer(t *testing.T) { // The first pass answers only what a node offers. Refused there, the node vanishes from every // other node's world — and the whole mesh loses its vault for an ambiguity one machine has to // settle. The second pass is where it is refused, and the test above proves it is. if _, err := Resolve(issuerShelf(), []string{"route-proxy", "public-acme", "step-ca"}, reachable(), mcat.World{Unchecked: true}); err != nil { t.Fatalf("the first pass refused what only the second may: %v", err) } }