package main import ( "bytes" "context" "errors" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/catalogue" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" ) func aBrokerClaiming(module string) catalogue.Manifest { return catalogue.Manifest{Module: module, Provides: []catalogue.Offer{{Name: "mesh-bus", Scope: catalogue.ScopeMesh}}, Claims: []catalogue.Claim{{Name: "mesh-broker", Scope: catalogue.ScopeMesh}}} } func withBusSeat(t *testing.T) { t.Helper() was := catalogue.Seats() t.Cleanup(func() { catalogue.UseSeats(was) }) catalogue.UseSeats([]catalogue.Seat{{Name: "mesh-broker", Scope: catalogue.ScopeMesh, Delivers: "mesh-bus", Decision: "test"}}) } // passesFor is what the composition names to pass on for these holdings and assignments (mesh-sdk's SeatsToPassOn), with the // lines it says. func passesFor(holdings []catalogue.Held, as []catalogue.Assignment) ([]catalogue.SeatPass, []string) { return catalogue.SeatsToPassOn(holdings, as) } // 1317/AC1: the send records the claimant as the holder and writes one hand act by the controller naming the seat, the former // holder and the new one. (Which seats to pass, and the holdings after it, are mesh-sdk's: go/compose rest_test.go // TestASeatWhoseHolderIsAssignedNowhereGoesToItsOnlyClaimant and TestAHolderOnRecordSettlesTheSeatInTheRestOfTheMesh.) func TestASendPassesTheSeatAndWritesOneHandActByTheController(t *testing.T) { withBusSeat(t) var passed []catalogue.SeatPass var acts []link.HandAct var said bytes.Buffer sp := seatPassing{say: &said, pass: func(_ context.Context, p catalogue.SeatPass) (bool, error) { passed = append(passed, p) return true, nil }, act: func(_ context.Context, p catalogue.SeatPass) error { acts = append(acts, seatPassHandAct(p)) return nil }, } holdings := []catalogue.Held{{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "novox", Module: "old-broker"}} passes, _ := passesFor(holdings, []catalogue.Assignment{{Node: "novox", Manifest: aBrokerClaiming("gate")}}) if !performSeatPasses(t.Context(), sp, passes) { t.Fatal("a pass was recorded and the send does not say it made something") } if len(passed) != 1 || len(acts) != 1 { t.Fatalf("passes=%d acts=%d, want one each", len(passed), len(acts)) } a := acts[0] text := strings.Join(append([]string{a.By, a.Verb, a.Why}, a.Args...), " ") for _, want := range []string{"the controller", "mesh-broker", "novox/old-broker", "novox/gate"} { if !strings.Contains(text, want) { t.Fatalf("the hand act does not name %q: %+v", want, a) } } } // A send that lost the compare-and-set writes no act, and a failed write leaves the record. func TestALostPassWritesNoActAndAFailedOneLeavesTheRecord(t *testing.T) { withBusSeat(t) holdings := []catalogue.Held{{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "novox", Module: "old-broker"}} as := []catalogue.Assignment{{Node: "novox", Manifest: aBrokerClaiming("gate")}} passes, _ := passesFor(holdings, as) lost := seatPassing{say: &bytes.Buffer{}, pass: func(context.Context, catalogue.SeatPass) (bool, error) { return false, nil }, act: func(context.Context, catalogue.SeatPass) error { t.Fatal("act without a pass"); return nil }, } if performSeatPasses(t.Context(), lost, passes) { t.Fatal("a lost pass is said to have made something") } var said bytes.Buffer sp := seatPassing{say: &said, pass: func(context.Context, catalogue.SeatPass) (bool, error) { return false, context.DeadlineExceeded }, act: func(context.Context, catalogue.SeatPass) error { t.Fatal("act"); return nil }, } if performSeatPasses(t.Context(), sp, passes) || !strings.Contains(said.String(), "could not be passed") { t.Fatalf("a failed pass is not said: %q", said.String()) } // And an act that cannot be written is said, with the record kept. var told bytes.Buffer kept := seatPassing{say: &told, pass: func(context.Context, catalogue.SeatPass) (bool, error) { return true, nil }, act: func(context.Context, catalogue.SeatPass) error { return errors.New("no bus") }, } if !performSeatPasses(t.Context(), kept, passes) || !strings.Contains(told.String(), "the hand act could NOT be recorded") { t.Fatalf("a failed act is not said: %q", told.String()) } } // A pass is the mesh working as ADR 0449 ยง4 decided, never a repair: two of them raise no healer-wanted and // count as no repair. func TestTwoSeatPassesAreNoRepairAndRaiseNoHealerWanted(t *testing.T) { withBusSeat(t) now := time.Date(2026, 10, 19, 20, 0, 0, 0, time.UTC) mk := func(at time.Time) link.HandAct { a := seatPassHandAct(catalogue.SeatPass{Seat: catalogue.Seat{Name: "mesh-broker"}, From: catalogue.Held{Node: "novox", Module: "old"}, To: catalogue.Held{Node: "novox", Module: "gate"}}) a.At = at return a } acts := []link.HandAct{mk(now.Add(-26 * time.Hour)), mk(now.Add(-time.Hour))} if r := repairs(acts); len(r) != 0 { t.Fatalf("a seat pass counts as a repair: %+v", r) } f := calm(now) f.handActs = acts if got := watchHandActs(f); len(got) != 0 { t.Fatalf("two passes raised %+v", got) } } // On the merge gate's throwaway mesh the pass is recorded in its store and no hand act is written: there is no bus to write // it to. func TestAThrowawayMeshWritesNoHandActForAPass(t *testing.T) { inv := &inventory.Inventory{} inv.MarkThrowaway() var said bytes.Buffer sp := seatPassingOn(inv, &said) if err := sp.act(t.Context(), catalogue.SeatPass{}); err != nil { t.Fatalf("the throwaway mesh tried to write the act: %v", err) } if said.Len() != 0 { t.Errorf("the throwaway composition said %q (a write was tried)", said.String()) } }