package main import ( "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" ) // What an assignment says about the machines it was not about. // **An assignment that blocks another machine says so.** // // The shape found on a four-node raise: a module offering a mesh-scoped provision stops offering it // the moment its own node stops resolving, so an assignment to the provider's machine silently took // a provision away from every consumer elsewhere. Those consumers were then told *nothing in this // mesh provides it*, naming as the remedy a module that was already assigned — which read, on the // way back, as a version bump breaking provider recognition. It was neither the version nor the // provider: it was one machine's set of assignments, and nothing said so. // // novox/hq 04-ISSUES/017 names the general shape — an action succeeding into a state its own verify // rejects, because the action's test is not the test the verify uses. `assign` tested one node; the // verify is resolution over all of them. func TestAnAssignmentThatBlocksAnotherMachineSaysWhichAndWhy(t *testing.T) { open := aMesh(t) ctx := t.Context() // A provider on the hub and a consumer on the other machine, both resolving. register(t, open, catalogue.Manifest{Module: "step-ca", Version: "1", Provides: []catalogue.Offer{{Name: "acme-ca", Scope: catalogue.ScopeMesh}}, Serves: map[string]map[string]any{"acme-ca": {"path": "/acme/directory"}}}) register(t, open, catalogue.Manifest{Module: "route-proxy", Version: "1", Requires: []string{"acme-ca"}}) if _, err := assign(ctx, open, "anchor", "step-ca"); err != nil { t.Fatal(err) } said, err := assign(ctx, open, "laptop", "route-proxy") if err != nil { t.Fatalf("a mesh that should resolve did not: %v\n%s", err, said) } if strings.Contains(said, "cannot be worked out") { t.Fatalf("a well mesh was reported as blocked:\n%s", said) } // Now break the hub's own set, with nothing but the command a person has. one, two := rivals() register(t, open, one) register(t, open, two) if _, err := assign(ctx, open, "anchor", "rival-one"); err != nil { t.Fatal(err) } said, err = assign(ctx, open, "anchor", "rival-two") if err == nil { t.Fatal("an assignment that makes its own node incoherent was not reported at all") } // The node's own refusal is the error, as it always was. What is new is that the machines this // just took a provision away from are named in the same breath. if !strings.Contains(said, "laptop") { t.Fatalf("the machine this blocked is not named:\n%s\n\n%v", said, err) } if !strings.Contains(said, "acme-ca") { t.Fatalf("what laptop is now missing is not said:\n%s", said) } if !strings.Contains(said, "cannot be worked out as things stand") { t.Fatalf("the report does not say what state the mesh is in:\n%s", said) } // And the assignment is kept: it is what a person meant, and assignment is not an ordering. assigned, err := open.inventory.Assigned(ctx, "anchor") if err != nil { t.Fatal(err) } if !contains(assigned, "rival-two") { t.Fatalf("the assignment was not kept: %v", assigned) } } // A consumer assigned before its provider is refused for itself and kept, because assignment is not // an ordering — refusing the first half of a pair would make the order somebody types two commands // in part of the mesh's rules. func TestAConsumerAssignedBeforeItsProviderIsStillAssigned(t *testing.T) { open := aMesh(t) ctx := t.Context() register(t, open, catalogue.Manifest{Module: "route-proxy", Version: "1", Requires: []string{"acme-ca"}}) said, err := assign(ctx, open, "laptop", "route-proxy") if err == nil { t.Fatalf("a consumer with nothing to consume resolved:\n%s", said) } assigned, err := open.inventory.Assigned(ctx, "laptop") if err != nil { t.Fatal(err) } if !contains(assigned, "route-proxy") { t.Fatalf("assignment became an ordering: %v", assigned) } } // Unassigning is the ordinary way to stop providing something to another machine, and it reports // the same way for the same reason. func TestUnassigningAProviderNamesWhoIsNowBlocked(t *testing.T) { open := aMesh(t) ctx := t.Context() register(t, open, catalogue.Manifest{Module: "step-ca", Version: "1", Provides: []catalogue.Offer{{Name: "acme-ca", Scope: catalogue.ScopeMesh}}, Serves: map[string]map[string]any{"acme-ca": {"path": "/acme/directory"}}}) register(t, open, catalogue.Manifest{Module: "route-proxy", Version: "1", Requires: []string{"acme-ca"}}) if _, err := assign(ctx, open, "anchor", "step-ca"); err != nil { t.Fatal(err) } if _, err := assign(ctx, open, "laptop", "route-proxy"); err != nil { t.Fatal(err) } said, err := unassign(ctx, open, "anchor", "step-ca") if err != nil { t.Fatal(err) } if !strings.Contains(said, "laptop") || !strings.Contains(said, "acme-ca") { t.Fatalf("taking the provider away said nothing about who was consuming it:\n%s", said) } } func contains(all []string, one string) bool { for _, s := range all { if s == one { return true } } return false }