package main import ( "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" "github.com/novox/mesh-controller/internal/conditions" ) // novox/hq issue 273, ADR 0232: a consumer of a provision that keeps its data moves only by a pin. func storeManifests() []catalogue.Manifest { return []catalogue.Manifest{ {Module: "store", Version: "1", Provides: []catalogue.Offer{{Name: "postgres-database", Scope: catalogue.ScopeMesh}}, Claims: []catalogue.Claim{{Name: "mesh-store", Scope: catalogue.ScopeMesh}}, Serves: map[string]map[string]any{"postgres-database": {"port": 5432}}, Grants: map[string]string{"postgres-database": "/var/lib/mesh/store/grants"}}, {Module: "resolver", Version: "1", Provides: []catalogue.Offer{{Name: "wildcard-resolution", Scope: catalogue.ScopeMesh}}, Claims: []catalogue.Claim{{Name: "mesh-dns-resolver", Scope: catalogue.ScopeMesh}}}, {Module: "network", Version: "1", Requires: []string{"wildcard-resolution"}}, {Module: "board", Version: "1", Requires: []string{"postgres-database"}}, } } func need(t *testing.T, plan catalogue.Resolution, consumer, provision string) catalogue.Needed { t.Helper() for _, n := range plan.Needs { if n.For == consumer && n.Name == provision { return n } } t.Fatalf("no %s for %s: %+v", provision, consumer, plan.Needs) return catalogue.Needed{} } // The incident through the stores: the laptop runs its own store and a consumer of it, the anchor's // store holds the mesh's seat. The consumer stays beside its data, the resolver follows its seat, the // binding is recorded as sent, and a pin — only a pin — moves it, said before the push that carries it. // What one machine's resolution says against its record. func TestBindingFindingsSayAKeptMoveAndAMoveNothingAskedFor(t *testing.T) { home, anchor := catalogue.Chosen{Node: "home", Module: "store"}, catalogue.Chosen{Node: "anchor", Module: "store"} plan := catalogue.Resolution{Node: "laptop", Kept: []catalogue.KeptBinding{{Machine: "laptop", Consumer: "board", Provision: "postgres-database", Bound: home, Would: anchor}}, Needs: []catalogue.Needed{ {Name: "postgres-database", For: "board", From: "home", Module: "store", KeepsData: true}, {Name: "postgres-database", For: "game", From: "anchor", Module: "store", KeepsData: true}, {Name: "wildcard-resolution", For: "network", From: "anchor", Module: "resolver"}, }} bound := map[string]map[string]catalogue.Chosen{ "board": {"postgres-database": home}, "game": {"postgres-database": home}, "network": {"wildcard-resolution": {Node: "home", Module: "resolver"}}, } found := linted(bindingFindings(plan, bound, nil)) if len(found) != 2 { t.Fatalf("found %+v", found) } kept, moved := found[0], found[1] if kept.Kind != kindBindingKept || kept.Severity != conditions.Urgent || kept.Machine != "laptop" || !strings.Contains(kept.Summary, "would move board's postgres-database from home/store to anchor/store") || !strings.Contains(kept.Summary, "its data is on home/store") || !strings.Contains(kept.Summary, "`pin laptop postgres-database anchor store` to confirm a move (and move the data first)") { t.Errorf("kept: %+v", kept) } if moved.Kind != kindBindingMoved || moved.Severity != conditions.Urgent || !strings.Contains(moved.Summary, "game's postgres-database would be sent anchor/store") { t.Errorf("moved: %+v", moved) } if kept.Key() == moved.Key() { t.Error("two consumers, one condition") } }