package inventory import ( "context" "testing" "github.com/novox/mesh-controller/internal/catalogue" ) // A seat's holder is a row, changed as one act (novox/hq ADR 0131). What these pin is the shape of // that row's life: it needs an assignment to point at, it is replaced rather than added to, and it // goes when the assignment does — so a seat never points at something that is not running anywhere. func twoBrokersOnTwoNodes(t *testing.T) (*Inventory, context.Context) { t.Helper() old := catalogue.Manifest{Module: "old-broker", Version: "1", Provides: []catalogue.Offer{{Name: "mesh-bus", Scope: catalogue.ScopeMesh}}, Claims: []catalogue.Claim{{Name: "mesh-broker", Scope: catalogue.ScopeMesh}}} new := catalogue.Manifest{Module: "new-broker", Version: "1", Provides: []catalogue.Offer{{Name: "mesh-bus", Scope: catalogue.ScopeMesh}}, Claims: []catalogue.Claim{{Name: "mesh-broker", Scope: catalogue.ScopeMesh}}} inv, ctx := aMeshWith(t, old, new) // The holding references the seat's row, which `migrate` seeds on a real mesh. if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil { t.Fatal(err) } for _, n := range []string{"anchor", "laptop"} { if _, err := inv.AddNode(ctx, n); err != nil { t.Fatal(err) } } if _, err := inv.Assign(ctx, "anchor", "old-broker"); err != nil { t.Fatal(err) } if _, err := inv.Assign(ctx, "laptop", "new-broker"); err != nil { t.Fatal(err) } return inv, ctx } func TestAHandoverIsOneRowReplacedNotOneAdded(t *testing.T) { inv, ctx := twoBrokersOnTwoNodes(t) if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "anchor", "old-broker"); err != nil { t.Fatal(err) } if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "laptop", "new-broker"); err != nil { t.Fatal(err) } held, err := inv.Holdings(ctx) if err != nil { t.Fatal(err) } if len(held) != 1 || held[0].Node != "laptop" || held[0].Module != "new-broker" || held[0].Scope != catalogue.ScopeMesh { t.Fatalf("after a handover the seat is not held by exactly the new holder: %+v", held) } } func TestASeatCannotBeHandedToSomethingNotAssigned(t *testing.T) { inv, ctx := twoBrokersOnTwoNodes(t) // new-broker is assigned to laptop, not anchor. if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "anchor", "new-broker"); err == nil { t.Fatal("a seat was handed to a module not assigned where it was named") } } func TestUnassigningTheHolderTakesTheHoldingWithIt(t *testing.T) { inv, ctx := twoBrokersOnTwoNodes(t) if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "laptop", "new-broker"); err != nil { t.Fatal(err) } if err := inv.Unassign(ctx, "laptop", "new-broker"); err != nil { t.Fatal(err) } held, err := inv.Holdings(ctx) if err != nil { t.Fatal(err) } if len(held) != 0 { t.Fatalf("the holding outlived the assignment it pointed at: %+v", held) } } func TestAMachinesMembershipIsOneRowReplacedAndGoesWithTheMachine(t *testing.T) { inv, ctx := twoBrokersOnTwoNodes(t) if err := inv.PutBusMembership(ctx, "anchor", "first"); err != nil { t.Fatal(err) } if err := inv.PutBusMembership(ctx, "anchor", "second"); err != nil { t.Fatal(err) } got, err := inv.BusMemberships(ctx) if err != nil { t.Fatal(err) } if got["anchor"] != "second" || len(got) != 1 { t.Fatalf("a re-told membership did not replace the first: %v", got) } } // A replicated seat has several holders on record (novox/hq ADR 0223): each is added beside the // others, adding one twice changes nothing, a handover still leaves exactly one, and unassigning one // takes only its own row. func TestAReplicatedSeatHasSeveralHoldersOnRecord(t *testing.T) { resolver := catalogue.Manifest{Module: "resolver", Version: "1", Provides: []catalogue.Offer{{Name: "wildcard-resolution", Scope: catalogue.ScopeMesh}}, Claims: []catalogue.Claim{{Name: "mesh-dns-resolver", Scope: catalogue.ScopeMesh}}} inv, ctx := aMeshWith(t, resolver) if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil { t.Fatal(err) } for _, n := range []string{"anchor", "home"} { if _, err := inv.AddNode(ctx, n); err != nil { t.Fatal(err) } if _, err := inv.Assign(ctx, n, "resolver"); err != nil { t.Fatal(err) } } if err := inv.HoldSeat(ctx, "mesh-dns-resolver", catalogue.ScopeMesh, "anchor", "resolver"); err != nil { t.Fatal(err) } for range 2 { if err := inv.AddSeatHolder(ctx, "mesh-dns-resolver", catalogue.ScopeMesh, "home", "resolver"); err != nil { t.Fatal(err) } } held, err := inv.Holdings(ctx) if err != nil { t.Fatal(err) } if len(held) != 2 || held[0].Node != "anchor" || held[1].Node != "home" { t.Fatalf("the two holders are not both on record, once each: %+v", held) } if err := inv.Unassign(ctx, "home", "resolver"); err != nil { t.Fatal(err) } if held, _ = inv.Holdings(ctx); len(held) != 1 || held[0].Node != "anchor" { t.Fatalf("unassigning one holder took more or less than its own row: %+v", held) } if _, err := inv.Assign(ctx, "home", "resolver"); err != nil { t.Fatal(err) } if err := inv.AddSeatHolder(ctx, "mesh-dns-resolver", catalogue.ScopeMesh, "home", "resolver"); err != nil { t.Fatal(err) } if err := inv.HoldSeat(ctx, "mesh-dns-resolver", catalogue.ScopeMesh, "home", "resolver"); err != nil { t.Fatal(err) } if held, _ = inv.Holdings(ctx); len(held) != 1 || held[0].Node != "home" { t.Fatalf("a handover left other holders on record: %+v", held) } }