//go:build integration package main import ( "encoding/json" "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" ) func TestAnOverflowingConsumerNeverRefusesItsProvidersMachine(t *testing.T) { open := aMesh(t) ctx := t.Context() register(t, open, catalogue.Manifest{Module: "objects", Version: "1", Provides: []catalogue.Offer{{Name: "s3-bucket", Scope: catalogue.ScopeMesh, Identity: &catalogue.OfferIdentity{Max: 20, In: "an S3 access key"}}}, Receives: map[string]string{"s3-bucket": "/var/lib/mesh/objects/mesh.json"}}) register(t, open, catalogue.Manifest{Module: "resolver", Version: "1", Provides: []catalogue.Offer{{Name: "wildcard-resolution", Scope: catalogue.ScopeMesh}}}) register(t, open, catalogue.Manifest{Module: "networkmanager", Version: "1", Requires: []string{"wildcard-resolution"}}) register(t, open, catalogue.Manifest{Module: "photoalbum", Version: "1", Requires: []string{"s3-bucket"}}) register(t, open, catalogue.Manifest{Module: "files", Version: "1", Requires: []string{"s3-bucket"}}) for _, a := range [][2]string{{"anchor", "objects"}, {"anchor", "resolver"}, {"laptop", "networkmanager"}, {"laptop", "photoalbum"}, {"laptop", "files"}} { if _, err := assign(ctx, open, a[0], a[1]); err != nil { t.Fatalf("assign %s %s: %v", a[0], a[1], err) } } // The consumer's machine resolves, and says which of its modules no provider will grant. consumer, _, err := planFor(ctx, open, "laptop") if err != nil { t.Fatal(err) } over := consumer.Overflowing() if len(over) != 1 || over[0].Module != "photoalbum" || over[0].Provision != "s3-bucket" { t.Fatalf("the consumer's side does not name exactly photoalbum: %+v", over) } // The provider's machine composes. Under ADR 0049's one bound this was a refusal naming // networkmanager, and no push to the provider could go through. plan, settings, err := planFor(ctx, open, "anchor") if err != nil { t.Fatal(err) } declared, err := declarationFor(ctx, open, "anchor", plan, settings) if err != nil { t.Fatalf("one consumer's identity refused its provider's whole machine: %v", err) } if len(declared.withheld) != 1 || declared.withheld[0].Identity != "mesh_laptop_photoalbum" { t.Fatalf("the overflowing consumer is not the one withheld: %+v", declared.withheld) } grants, _, _, err := grantsFor(ctx, open, "anchor") if err != nil { t.Fatal(err) } var keyless bool for _, g := range grants { keyless = keyless || g.Provision == "wildcard-resolution" && g.From == "networkmanager" } if !keyless { t.Fatalf("networkmanager, 26 characters, is not granted the keyless resolver provision: %+v", grants) } var granted []string for _, c := range declared.Received["objects"]["s3-bucket"] { granted = append(granted, c.From) } if strings.Join(granted, ",") != "files" { t.Fatalf("the object store grants %v; files and only files fit", granted) } said := printed(t, func() error { reportLeftOut("anchor", declared); return nil }) if !strings.Contains(said, `photoalbum on laptop requires s3-bucket from anchor`) || !strings.Contains(said, "left out of anchor's grants") { t.Fatalf("the push does not say whom it leaves out:\n%s", said) } // And `status` names it, and does not call the mesh well while it stands. asked, err := theThreeQuestions(ctx, open) if err != nil { t.Fatal(err) } if len(asked.overflowing) != 1 || asked.overflowing[0].Module != "photoalbum" { t.Fatalf("status does not carry the overflow: %+v", asked.overflowing) } if asked.well() { t.Fatal("a mesh with a consumer left out of its grants reads as well") } shown := printed(t, func() error { return printStatus(asked) }) if !strings.Contains(shown, "identified too long for a provision they require") || !strings.Contains(shown, "mesh_laptop_photoalbum") { t.Fatalf("status does not say it:\n%s", shown) } body, err := statusAsJSON(asked) if err != nil { t.Fatal(err) } var doc struct { Overflowing []catalogue.Overflow `json:"overflowing"` } if err := json.Unmarshal(body, &doc); err != nil || len(doc.Overflowing) != 1 || doc.Overflowing[0].Bound.Max != 20 { t.Fatalf("the document does not carry it: %v\n%s", err, body) } }