diff --git a/cmd/mesh-controller/group_wait.go b/cmd/mesh-controller/group_wait.go index 12cc63a3..1be26410 100644 --- a/cmd/mesh-controller/group_wait.go +++ b/cmd/mesh-controller/group_wait.go @@ -8,6 +8,8 @@ import ( "strings" "time" + "github.com/nats-io/nats.go" + "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/link" @@ -60,13 +62,16 @@ var groupsAsked = func(ctx context.Context, held bool) ([]groupsLine, error) { if !held { return nil, fmt.Errorf("no holder of %s is on record to say it", catalogue.DeliverySeat) } - serving := servingBus.Load() - if serving == nil { - return nil, errors.New("this controller is not on the bus") - } - asking, cancel := context.WithTimeout(ctx, groupAskWithin) - defer cancel() - raw, err := askDeliveryOwner(asking, serving.Conn(), "groups", map[string]any{}) + // On the bus as this process is: the serving controller's own connection, lent, or — in a verb that runs as a command + // of its own, such as a recheck's `delivery check` — one dialled for the moment (onTheBus). + var raw json.RawMessage + err := onTheBus(func(conn *nats.Conn) error { + asking, cancel := context.WithTimeout(ctx, groupAskWithin) + defer cancel() + var err error + raw, err = askDeliveryOwner(asking, conn, "groups", map[string]any{}) + return err + }) if err != nil { if errors.Is(err, link.ErrNothingServes) { return nil, fmt.Errorf("nothing serves %s.groups", catalogue.DeliverySeat) diff --git a/cmd/mesh-controller/replay867_test.go b/cmd/mesh-controller/replay867_test.go index 9db90bd9..ac6b2add 100644 --- a/cmd/mesh-controller/replay867_test.go +++ b/cmd/mesh-controller/replay867_test.go @@ -67,6 +67,7 @@ type theIssue867 struct { js interface{ Conn() *nats.Conn } judgeCache string registry string + groups string // what mesh-delivery's groups answers } func r867Git(t *testing.T, dir string, args ...string) string { @@ -172,13 +173,14 @@ func theGroupOfIssue867(t *testing.T) *theIssue867 { if verb != "groups" { t.Fatalf("asked the owner %s", verb) } - return json.RawMessage(`[{"id":"feat/867","members":["` + root + `/mesh-host@` + r.hostHead[:12] + ` (checked)","` + - root + `/mesh-controller@` + r.controllerHead[:12] + ` (checked)"],"member_details":[` + - `{"id":"novox/mesh-host@` + r.hostHead[:12] + `","repository":"novox/mesh-host","state":"checked","number":114,"head":"` + - r.hostHead + `","base":"main","open":true},` + - `{"id":"novox/mesh-controller@` + r.controllerHead[:12] + `","repository":"novox/mesh-controller","state":"checked","number":346,"head":"` + - r.controllerHead + `","base":"main","open":true}]}]`), nil + return json.RawMessage(r.groups), nil } + r.groups = (`[{"id":"feat/867","members":["` + root + `/mesh-host@` + r.hostHead[:12] + ` (checked)","` + + root + `/mesh-controller@` + r.controllerHead[:12] + ` (checked)"],"member_details":[` + + `{"id":"novox/mesh-host@` + r.hostHead[:12] + `","repository":"novox/mesh-host","state":"checked","number":114,"head":"` + + r.hostHead + `","base":"main","open":true},` + + `{"id":"novox/mesh-controller@` + r.controllerHead[:12] + `","repository":"novox/mesh-controller","state":"checked","number":346,"head":"` + + r.controllerHead + `","base":"main","open":true}]}]`) r.registry = r867Registry(t) r867Runtime(t) return r @@ -301,3 +303,44 @@ exit 1 t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH")) t.Setenv("GOTOOLCHAIN", "local") } + +// A recheck runs as a command of its own (`delivery check` with one member), where the controller is not the serving +// one and holds no connection: it dials the bus for the moment and asks mesh-delivery's `groups` as the daemon does. +// This drives the verb on a test bus with a fake mesh-delivery answering `groups`, with no stub of the seam between. +func TestTheRecheckVerbAsksMeshDeliveryOnABusOfItsOwn(t *testing.T) { + realAsk := askDeliveryOwner + r := theGroupOfIssue867(t) + askDeliveryOwner = realAsk + js, _ := aBuildQueue(t) + if servingBus.Load() != nil { + t.Fatal("this test is of a process that is not the serving controller") + } + asked := 0 + wrapped, _ := json.Marshal(map[string]any{"content": []map[string]any{{"type": "text", "text": r.groups}}}) + sub, err := js.Conn().Subscribe(link.SeatToolSubject(catalogue.DeliverySeat, "groups"), func(m *nats.Msg) { + asked++ + body, _ := json.Marshal(link.Answer{Result: wrapped}) + _ = m.Respond(body) + }) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = sub.Unsubscribe() }) + member := `[{"id":"novox/mesh-host@` + r.hostHead[:12] + `","repository":"novox/mesh-host","base":"main","head":"` + + r.hostHead + `","number":114,"paths":["validate/more.go"],"module_dirs_said":true}]` + if err := deliveryCommand(t.Context(), []string{"check", "--members", member}); err != nil { + t.Fatal(err) + } + q, err := link.ReadQueue(t.Context(), js, link.TheBuildMachine) + if err != nil || len(q.Asks) == 0 { + t.Fatalf("nothing was asked of the build seat: %v", err) + } + c := q.Asks[len(q.Asks)-1].Request.Check + if c.SiblingsUnknown != "" || len(c.Siblings) != 1 || c.Siblings[0].Repository != "novox/mesh-controller" || + c.Siblings[0].Number != 346 || c.Siblings[0].Head != r.controllerHead { + t.Fatalf("the recheck learned siblings %+v, unknown %q", c.Siblings, c.SiblingsUnknown) + } + if asked != 1 { + t.Fatalf("mesh-delivery was asked %d times", asked) + } +} diff --git a/testdata/store-tests b/testdata/store-tests index 055495ac..ebafe258 100644 --- a/testdata/store-tests +++ b/testdata/store-tests @@ -81,3 +81,4 @@ ./cmd/mesh-controller TestARecordedMoveThatCannotBeJudgedWaits ./cmd/mesh-controller TestReplay867 ./cmd/mesh-controller TestReplay867Recheck +./cmd/mesh-controller TestTheRecheckVerbAsksMeshDeliveryOnABusOfItsOwn