package catalogue import ( "strings" "testing" ) // The mesh's bus is one per mesh, read from the catalogue beside this checkout. // // **This is step 2's claim, and it is checked here rather than in a bed** (novox/hq ADR 0116): // adoption puts the NATS server into the `mesh-broker` seat on a mesh that is already running, // and the property that matters is that a second one anywhere is refused *when it is assigned*, // not discovered later as two servers holding different halves of the mesh's traffic. A second // bus is not a degraded mesh; it is two meshes that both believe they are the one. func TestASecondMeshBusAnywhereIsRefusedByName(t *testing.T) { nats := catalogueManifest(t, "nats") if _, err := Resolve(shelf(nats), []string{"nats"}, workstation(), World{}); err != nil { t.Fatalf("the bus alone does not resolve: %v", err) } elsewhere := World{Held: []Held{{Claim: "mesh-broker", Scope: ScopeMesh, Node: "anchor", Module: "nats"}}} other := workstation() other.Name = "laptop" _, err := Resolve(shelf(nats), []string{"nats"}, other, elsewhere) if err == nil { t.Fatal("a second bus was accepted on another machine") } if !strings.Contains(err.Error(), "mesh-broker") || !strings.Contains(err.Error(), "one per mesh") { t.Fatalf("refused without naming the seat: %v", err) } } // The seat is the server's role, not the product's name (novox/hq ADR 0079). A different // implementation of the bus claims the same seat, and the mesh refuses it for the same reason — // which is the property that lets the bus be replaced at all. func TestTheSeatRefusesADifferentBusToo(t *testing.T) { nats := catalogueManifest(t, "nats") held := World{Held: []Held{{Claim: "mesh-broker", Scope: ScopeMesh, Node: "anchor", Module: "some-other-broker"}}} other := workstation() other.Name = "laptop" if _, err := Resolve(shelf(nats), []string{"nats"}, other, held); err == nil { t.Fatal("the seat admitted a second holder because the module's name differed") } } // **The old broker claims the seat until the seat is handed over, and stands beside the new one // while it waits** (novox/hq ADR 0131, superseding the record this test used to pin). Whoever is on // record holds it; the other eligible claimant is neither refused nor holding. This is the shape the // handover needs: both brokers assigned, one bus, no moment with nobody in the seat. func TestTheOldBrokerStandsBesideTheNewOneUntilTheHandover(t *testing.T) { was := Seats() t.Cleanup(func() { UseSeats(was) }) UseSeats([]Seat{{Name: "mesh-broker", Scope: ScopeMesh, Delivers: "mesh-bus", Decision: "test"}}) lavinmq := catalogueManifest(t, "lavinmq") if !lavinmq.ClaimsSeat("mesh-broker") { t.Skip("the old broker no longer claims the seat: design 28 task 5.4 has removed it") } nats := catalogueManifest(t, "nats") onRecord := World{Holdings: []Held{{Claim: "mesh-broker", Scope: ScopeMesh, Node: "anchor", Module: "nats"}}} // The same machine runs both. Without the record this is two holders and refused; with it, the // recorded one holds and the other is silent. anchor := workstation() anchor.Name = "anchor" got, err := Resolve(shelf(lavinmq, nats), []string{"lavinmq", "nats"}, anchor, onRecord) if err != nil { t.Fatalf("the old broker beside the recorded holder was refused: %v", err) } var holders []string for _, h := range got.Claims { if h.Claim == "mesh-broker" { holders = append(holders, h.Module) } } if len(holders) != 1 || holders[0] != "nats" { t.Fatalf("the seat is held by %v, not by the holder on record alone", holders) } } // **A seat and the interface it delivers are different names, and renaming one must not rename // the other** (novox/hq ADR 0118). This nearly went wrong: the seats were renamed to the `mesh-*` // prefix, and a blanket search-and-replace also renamed `npm-package-registry` and `git` where // they are *provisions* — which a consumer requires and a provider offers. The tests failed with // "the package registry is served on ", which does not say "you renamed an interface". func TestRenamingASeatDidNotRenameTheInterfaceItDelivers(t *testing.T) { for _, pair := range []struct{ seat, delivers string }{ {"git", "git"}, {"npm-package-registry", "npm-package-registry"}, {"the-artifact-store", "artifact-store"}, {"mesh-store", "postgres-database"}, {"mesh-broker", "mesh-bus"}, } { s, known := SeatNamed(pair.seat) if !known { t.Fatalf("%q is not a seat", pair.seat) } if s.Delivers != pair.delivers { t.Errorf("the %s seat delivers %q, expected %q — renaming the seat moved the "+ "interface with it, and every consumer requiring it would stop resolving", pair.seat, s.Delivers, pair.delivers) } // **Three of these deliberately share a name with what they deliver**, and that is not an // incomplete rename. Renaming a seat that delivers a provision cascades to every consumer // requiring it, with a mesh-wide window where a holder stops resolving mid-flight — so the // trunk deferred exactly those three (novox/hq ADR 0121) while renaming the node-scoped ones. // What this test is for is the other direction: that renaming a seat never moves the // interface, which once produced "the package registry is served on ". } } // A manifest written against an old seat name is told what it became rather than refused as // unknown. **That map is the controller's store now, not this package** (novox/hq ADR 0122): a // rename is a row, so the courtesy survives a rename nobody recompiled for. Checked where the // table is read, not here, where there is no longer a hardcoded list to check against.