mesh/merge-gate pass: builds mesh-controller → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/529-a-waiting-walk-is-answered-or-named delivered: every member is delivered
A hand start there would skip its group's order; point at it only for a walk nothing holds, and say why nothing holds it.
103 lines
4.7 KiB
Go
103 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// novox/hq issue 529: a walk waiting because its delivery is held for a person said only "its walk waits for
|
|
// mesh-delivery's word — `plans go … --why …` starts it by hand", pointing a reader at a hand start instead of at
|
|
// the person's decision. The line names what mesh-delivery says holds the walk: the held delivery, its reason and
|
|
// `mesh-delivery.release`; or the delivery not at its turn.
|
|
|
|
func waitingWalk(now time.Time) inventory.Plan {
|
|
return inventory.Plan{ID: "plan-1791793277221379309", Repository: "novox/mesh-sdk", Branch: "main",
|
|
Commit: "9747f3e1b1ac", State: inventory.PlanBuilding, Tiers: [][]string{{"mesh-sdk"}},
|
|
Created: now.Add(-9 * time.Minute), Modules: map[string]*inventory.PlanModule{},
|
|
Delivery: &inventory.PlanDelivery{Awaits: "mesh-delivery"}}
|
|
}
|
|
|
|
func TestAWalkWaitingOnAHeldDeliveryNamesTheHold(t *testing.T) {
|
|
now := time.Date(2026, 10, 12, 8, 35, 0, 0, time.UTC)
|
|
p := waitingWalk(now)
|
|
held := "novox/mesh-sdk@d41d255f28ad held since 2026-10-12T08:23:00Z: its group adr-0307-state-history's composed " +
|
|
"check did not pass for the heads that merged — a person decides: `mesh-delivery.release` (id " +
|
|
"novox/mesh-sdk@d41d255f28ad, why) lets it go, `mesh-delivery.stop` ends it"
|
|
waits := walkWaits{p.ID: {{ID: "novox/mesh-sdk@d41d255f28ad", State: "held", Walk: p.ID, Says: held}}}
|
|
line := planLineSaid(p, now, pauseView{}, tierAtLeast, nil, waits)
|
|
for _, want := range []string{"held for a person", "composed check did not pass", "mesh-delivery.release"} {
|
|
if !strings.Contains(line, want) {
|
|
t.Fatalf("the line does not say %q: %q", want, line)
|
|
}
|
|
}
|
|
if strings.Contains(line, "starts it by hand") {
|
|
t.Fatalf("a held walk's line points at a hand start: %q", line)
|
|
}
|
|
// Told nothing — an older mesh-delivery, or none answering — it reads as before.
|
|
if line := planLineSaid(p, now, pauseView{}, tierAtLeast, nil, nil); !strings.Contains(line, "starts it by hand") {
|
|
t.Fatalf("told nothing, the line reads %q", line)
|
|
}
|
|
}
|
|
|
|
func TestAWalkWaitingOnADeliveryNotAtItsTurnSaysSo(t *testing.T) {
|
|
now := time.Date(2026, 10, 12, 8, 57, 0, 0, time.UTC)
|
|
p := waitingWalk(now)
|
|
waits := walkWaits{p.ID: {{ID: "novox/mesh-sdk@9747f3e1b1ac", State: "published", Walk: p.ID,
|
|
Says: "not let go yet: novox/mesh-tools@3f0c1a6cec18 (published) carried by it not at its turn"}}}
|
|
line := planLineSaid(p, now, pauseView{}, tierAtLeast, nil, waits)
|
|
if !strings.Contains(line, "not at its turn") || !strings.Contains(line, "novox/mesh-tools@3f0c1a6cec18") {
|
|
t.Fatalf("the line reads %q", line)
|
|
}
|
|
// No hand start offered: it would skip the group's order (review of issue 529).
|
|
if strings.Contains(line, "plans go") {
|
|
t.Fatalf("a walk not at its turn points at a hand start: %q", line)
|
|
}
|
|
}
|
|
|
|
// A walk nothing holds — the nzbgeek case, its merges' deliveries ended as nothing-to-move — is said so, and the hand
|
|
// start is the way on.
|
|
func TestAWalkHeldByNothingSaysWhyAndTheHandStart(t *testing.T) {
|
|
now := time.Date(2026, 10, 11, 20, 30, 0, 0, time.UTC)
|
|
p := waitingWalk(now)
|
|
waits := walkWaits{p.ID: {{ID: "novox/mesh-media-catalog@cdcdcdcd0000", State: "unheld", Walk: p.ID,
|
|
Says: "held by nothing: its merges' deliveries ended as nothing-to-move (novox/mesh-media-catalog@abababab0000), " +
|
|
"yet the walk moves nzbgeek, nzbget — nothing gives it its word"}}}
|
|
line := planLineSaid(p, now, pauseView{}, tierAtLeast, nil, waits)
|
|
if !strings.Contains(line, "ended as nothing-to-move") || !strings.Contains(line, "plans go "+p.ID) {
|
|
t.Fatalf("the line reads %q", line)
|
|
}
|
|
}
|
|
|
|
// What holds each waiting walk is asked of mesh-delivery's `stalled` with `walks`, a verb the controller's grant
|
|
// already names; only lines naming a walk are kept, by walk.
|
|
func TestWhatHoldsTheWaitingWalksIsAskedOfTheDeliveryOwner(t *testing.T) {
|
|
was := askDeliveryOwner
|
|
defer func() { askDeliveryOwner = was }()
|
|
var asked map[string]any
|
|
askDeliveryOwner = func(_ context.Context, _ *nats.Conn, verb string, args map[string]any) (json.RawMessage, error) {
|
|
if verb != "stalled" {
|
|
t.Fatalf("asked %s", verb)
|
|
}
|
|
asked = args
|
|
return json.Marshal([]stalledLine{{ID: "novox/a@1", State: "held", Walk: "plan-1", Says: "held: why"},
|
|
{ID: "novox/b@2", State: "held", Says: "a delivery past its bound, no walk"}})
|
|
}
|
|
got := walksWaiting(t.Context(), nil, true)
|
|
if asked["walks"] != true {
|
|
t.Fatalf("stalled was asked with %v", asked)
|
|
}
|
|
if len(got) != 1 || len(got["plan-1"]) != 1 || got["plan-1"][0].Says != "held: why" {
|
|
t.Fatalf("the waiting walks are %+v", got)
|
|
}
|
|
if got := walksWaiting(t.Context(), nil, false); got != nil {
|
|
t.Fatalf("with no holder on record it asked: %+v", got)
|
|
}
|
|
}
|