Files
mesh-controller/internal/link/replay314_test.go
T
jochen 175b28ee42
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group fix/314-a-large-answer-is-paged-not-lost delivered: every member is delivered
Page an answer larger than one message of the bus, and keep overviews brief (hq issue 314)
The client library refuses to send a reply over the bus's max_payload, and the
controller only logged it: conditions and status answered nobody for hours on
2026-10-08 while calls said each was answered in 130 ms, and the operator's
channel read nothing. An answer too large is now held under its call and paged
to the caller that asks, on the same subject; a caller that does not page is
told in words, and calls says it. The overviews no longer carry every finding:
conditions and status list each condition with its newest evidence, doctor at
most twenty findings a probe (probe= gives one whole), and the JSON overviews
are sent once, as data, instead of twice.
2026-10-08 12:19:16 +02:00

81 lines
3.1 KiB
Go

package link
import (
"bytes"
"context"
"encoding/json"
"strings"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-controller/internal/testbus"
)
// novox/hq issue 314, replayed with only what the link had before its fix, so it can be laid over the
// older commit. On 2026-10-08 the controller's `conditions` answered 689 stalled deliveries, each an
// open condition with its evidence, and `status` led with them: more than the bus carries in one message
// (its max_payload, a mebibyte). The client library refused the answer before it left the controller —
// `nats: maximum payload exceeded`, in the controller's journal only — and every caller, the console and
// the operator's channel among them, waited out its timeout and read that the controller did not answer,
// while `calls` said each call answered in 130 ms. An answer larger than one message arrives whole to a
// caller that pages, and is said, in one message that fits, to one that does not — never silence.
func TestReplay314(t *testing.T) {
conn, err := nats.Connect(testbus.URL(t))
if err != nil {
t.Fatal(err)
}
defer conn.Close()
limit := conn.MaxPayload()
// Three times what one message carries: the size of the answers of that morning.
type finding struct {
Key, Summary string
}
var large []finding
for len(large)*200 < int(3*limit) {
large = append(large, finding{Key: "delivery.d-" + strings.Repeat("7", 8) + ".stalled",
Summary: strings.Repeat("held past its bound ", 9)})
}
stop, err := OverNATS{Conn: conn}.ServeSeatTools("replay-314", map[string]ToolHandler{
"conditions": func(context.Context, json.RawMessage) (any, error) { return large, nil },
}, nil)
if err != nil {
t.Fatal(err)
}
defer stop()
t.Run("a caller of the controller's own reads it whole", func(t *testing.T) {
answer, err := AskMeshSeatTool(context.Background(), conn, "replay-314", "conditions", map[string]any{},
5*time.Second)
if err != nil {
t.Fatalf("an answer of about %d bytes did not arrive: %v", 3*limit, err)
}
if answer.Error != "" {
t.Fatalf("answered an error: %.300s", answer.Error)
}
var got []finding
if err := json.Unmarshal(answer.Result, &got); err != nil || len(got) != len(large) {
t.Fatalf("answered %d findings of %d (%v)", len(got), len(large), err)
}
})
t.Run("a caller that does not page is told, not left waiting", func(t *testing.T) {
reply, err := conn.Request(SeatToolSubject("replay-314", "conditions"), []byte(`{}`), 5*time.Second)
if err != nil {
t.Fatalf("no answer at all: %v — the caller waits out its timeout and reads silence", err)
}
if int64(len(reply.Data)) > limit {
t.Fatalf("an answer of %d bytes, more than the bus carries", len(reply.Data))
}
var r struct {
Error string `json:"error"`
}
if json.Unmarshal(reply.Data, &r) != nil || !strings.Contains(r.Error, "more than the bus carries") ||
!bytes.Contains(reply.Data, []byte("ask for less")) && !bytes.Contains(reply.Data, []byte("Ask for less")) {
t.Fatalf("the answer does not say it was too large and what to do: %.400s", reply.Data)
}
})
}