package main import ( "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/conditions" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" ) // A consumer may wait for a provision's credential only a person gives (novox/hq ADR 0337 §5–§7, issue 164). var requestsManifest = catalogue.Manifest{Module: "requests", Version: "1", Requires: []string{"tv-api", "db"}, Secrets: map[string]string{"tv-api": "/s/tv-api", "db": "/s/db"}} var tvKeyWait = inventory.Wait{Part: "the tv library", Provision: "tv-api", What: "the API key of tv"} func outsideFacts(given map[string]bool) operatorWaitFacts { return operatorWaitFacts{manifests: map[string]catalogue.Manifest{"requests": requestsManifest}, outside: map[string]outsideProvider{"tv-api": {Module: "tv", Machines: []string{"media"}}}, pairsGiven: map[string]map[string]bool{"requests@laptop": given}} } // §6: excused only when the module requires the provision, its provider says it is issued outside, and no value a // person gave is held for the pair under the keys both ends have now. func TestAWaitForAProvisionIsExcusedOnlyWhenTheStoreBearsItOut(t *testing.T) { for _, c := range []struct { name string w inventory.Wait f operatorWaitFacts says string }{ {"required, issued outside, not given", tvKeyWait, outsideFacts(nil), ""}, {"given", tvKeyWait, outsideFacts(map[string]bool{"tv-api": true}), "was given"}, {"not required", inventory.Wait{Part: "p", Provision: "music-api", What: "w"}, outsideFacts(nil), "does not require"}, {"a credential the mesh gives itself", inventory.Wait{Part: "p", Provision: "db", What: "w"}, outsideFacts(nil), "mesh gives itself"}, {"what was given cannot be read", tvKeyWait, operatorWaitFacts{ manifests: map[string]catalogue.Manifest{"requests": requestsManifest}, outside: map[string]outsideProvider{"tv-api": {Module: "tv"}}}, "could not be read"}, {"a provision and a secret", inventory.Wait{Part: "p", Provision: "tv-api", Secret: "x", What: "w"}, outsideFacts(nil), "one secret, one setting or one provision"}, } { err := checkWait("requests", "laptop", c.w, c.f) switch { case c.says == "" && err != nil: t.Errorf("%s: refused: %v", c.name, err) case c.says != "" && (err == nil || !strings.Contains(err.Error(), c.says)): t.Errorf("%s: %v; want a refusal saying %q", c.name, err, c.says) } } } // §7: an excused wait is the consumer's needs-operator condition, in plain words, its summary naming the one line // that gives the credential for every consumer of the provider; the wait as kept is not changed. func TestAWaitForAProvisionNamesTheOneAcceptForItsProvider(t *testing.T) { r := inventory.ResourceHealth{Module: "requests", Resource: "requests.app", Kind: "container", Target: "requests", State: link.StateWaiting, Check: "tool", Waits: []inventory.Wait{tvKeyWait}} kept := []inventory.ResourceHealth{r} got := checkWaiting("laptop", kept, outsideFacts(nil)) if got[0].State != link.StateWaiting { t.Fatalf("an excused wait for a provision was judged: %+v", got[0]) } if kept[0].Waits[0].Act != "" { t.Fatal("checking the wait changed the statement as kept") } waits, waiting := operatorWait("requests", got) if !waiting { t.Fatal("the module does not read as waiting for the operator") } o := needsOperatorObservation("requests", "laptop", waits, got) if !strings.Contains(o.Summary, "secret accept media tv tv-api") { t.Fatalf("the summary does not name the accept for the provider: %q", o.Summary) } if !strings.HasPrefix(o.Needs, "give the API key of tv") { t.Fatalf("the needs do not name the act: %q", o.Needs) } if why, ok := conditions.PlainWords(conditions.Words{Headline: o.Headline, Explanation: o.Explanation, Resolved: o.Resolved, Needs: o.Needs, Actions: o.Actions}, "laptop"); !ok { t.Fatalf("the condition is not in plain words: %s", why) } // A provider on several machines, or none: the line names its machine as a place to fill. if line := (outsideProvider{Module: "tv", Machines: []string{"a", "b"}}).acceptLine("tv-api"); line != "secret accept tv tv-api" { t.Fatalf("a provider on two machines reads %q", line) } } // §3: the consumers one accept reaches are those whose resolution answers the provision from that provider. func TestTheConsumersOfAProviderAreThoseItAnswers(t *testing.T) { plan := catalogue.Resolution{Needs: []catalogue.Needed{ {Name: "tv-api", From: "media", Module: "tv", For: "requests"}, {Name: "tv-api", From: "media", Module: "tv", For: "indexer", Local: "main"}, {Name: "tv-api", From: "other", Module: "tv", For: "elsewhere"}, {Name: "tv-api", From: "media", Module: "another-tv", For: "not-this-module"}, {Name: "db", From: "media", Module: "tv", For: "requests"}, {Name: "tv-api", ByRecord: true, For: "by-record"}, }} got := bindsFrom(plan, "laptop", "media", "tv", "tv-api") if len(got) != 2 || got[0].ConsumerModule != "indexer" || got[0].Local != "main" || got[1].ConsumerModule != "requests" { t.Fatalf("the consumers from tv on media are %+v", got) } said := acceptedSaid("media", "tv", "tv-api", inventory.ProvisionAccepted{ Sealed: []inventory.PairEnd{{Consumer: "laptop", ConsumerModule: "requests"}}, Unsealable: []inventory.PairEnd{{Consumer: "new", ConsumerModule: "indexer"}}}, []string{"down: refused"}) for _, want := range []string{"sealed to requests on laptop", "no sealing key", "down: refused"} { if !strings.Contains(said, want) { t.Errorf("what the accept says lacks %q:\n%s", want, said) } } }