One name per thing, per the HQ glossary: the module/container/image/binary/repo becomes mesh-controller, the seat the-controller, and the store+broker pair the foundation (embedded base bundles, default template and example lock renamed with their go:embed directives). No behaviour change — a pure vocabulary rename. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
134 lines
5.0 KiB
Go
134 lines
5.0 KiB
Go
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
|
|
}
|