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
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.
102 lines
3.7 KiB
Go
102 lines
3.7 KiB
Go
package link
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"github.com/novox/mesh-controller/internal/testbus"
|
|
)
|
|
|
|
// An answer too large for one message is recorded as paged, and a call's record says so: `calls` no
|
|
// longer reads "answered" for an answer nobody received (novox/hq issue 314).
|
|
func TestAPagedAnswerIsSaidInCalls(t *testing.T) {
|
|
l, a := NewCallLog(), newAnswers(t)
|
|
big := strings.Repeat("x", 5000)
|
|
l.serveCallWithin("mesh-controller", "conditions", nil, "_INBOX.operator.ABCDEFGHIJKLMNOPQRSTUV",
|
|
func(context.Context, json.RawMessage) (any, error) { return big, nil }, a.respond, 2048, nil)
|
|
got := a.only()
|
|
said, _ := got["error"].(string)
|
|
paged, _ := got["paged"].(map[string]any)
|
|
if !strings.Contains(said, "more than the bus carries") || paged == nil || paged["parts"].(float64) < 3 {
|
|
t.Fatalf("answered %v", got)
|
|
}
|
|
recent, _ := l.Recent()
|
|
if len(recent) != 1 || recent[0].Paged == "" || strings.Contains(string(recent[0].Answer), "in full") {
|
|
t.Fatalf("kept %+v", recent[0])
|
|
}
|
|
}
|
|
|
|
// An answer the bus refuses anyway is said to its caller and to `calls`, never only to the journal.
|
|
func TestAnAnswerTheBusRefusesIsSaid(t *testing.T) {
|
|
l := NewCallLog()
|
|
var sent [][]byte
|
|
respond := func(body []byte) error {
|
|
if len(body) > 1000 {
|
|
return nats.ErrMaxPayload
|
|
}
|
|
sent = append(sent, body)
|
|
return nil
|
|
}
|
|
l.serveCallWithin("mesh-controller", "status", nil, "_INBOX.x.1",
|
|
func(context.Context, json.RawMessage) (any, error) { return strings.Repeat("y", 4000), nil }, respond, 0, nil)
|
|
if len(sent) != 1 || !strings.Contains(string(sent[0]), "would not carry it") {
|
|
t.Fatalf("the caller was told %q", sent)
|
|
}
|
|
recent, _ := l.Recent()
|
|
if recent[0].Refused == "" {
|
|
t.Fatalf("calls does not say the answer was not sent: %+v", recent[0])
|
|
}
|
|
}
|
|
|
|
// A part goes only to the caller its answer was paged to, on the verb that answered it.
|
|
func TestAPartGoesOnlyToItsCaller(t *testing.T) {
|
|
conn, err := nats.Connect(testbus.URL(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
l := NewCallLog()
|
|
c := l.begin("mesh-controller", "status", nil, "_INBOX.operator.ABCDEFGHIJKLMNOPQRSTUV")
|
|
l.page(c, []byte(strings.Repeat("z", 5000)), 2048)
|
|
sub, err := conn.Subscribe("pages.test", func(m *nats.Msg) { l.servePage("mesh-controller", "status", m, nil) })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer sub.Unsubscribe()
|
|
_, err = askPart(context.Background(), conn, "pages.test", Paged{Call: c.ID, Parts: 3}, 0)
|
|
if err == nil || !strings.Contains(err.Error(), "another caller") {
|
|
t.Fatalf("a part was handed to a caller it was not paged to: %v", err)
|
|
}
|
|
_, err = askPart(context.Background(), conn, "pages.test", Paged{Call: "call-0-0", Parts: 3}, 0)
|
|
if err == nil || !strings.Contains(err.Error(), "not held here") {
|
|
t.Fatalf("a part of nothing held: %v", err)
|
|
}
|
|
}
|
|
|
|
// A part that cannot be had fails the whole answer, naming it — never a shorter answer.
|
|
func TestAMissingPartFailsTheWhole(t *testing.T) {
|
|
conn, err := nats.Connect(testbus.URL(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
sub, err := conn.Subscribe("pages.none", func(m *nats.Msg) { _ = m.Respond([]byte(`{"error":"gone"}`)) })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer sub.Unsubscribe()
|
|
first, _ := json.Marshal(map[string]any{"error": "too large", "paged": Paged{Call: "call-1-1", Bytes: 10, Parts: 2}})
|
|
_, err = Whole(context.Background(), conn, "pages.none", first)
|
|
if err == nil || !strings.Contains(err.Error(), "part 0") || !strings.Contains(err.Error(), "gone") {
|
|
t.Fatalf("a missing part read as %v", err)
|
|
}
|
|
if plain, err := Whole(context.Background(), conn, "pages.none", []byte(`{"result":1}`)); err != nil ||
|
|
string(plain) != `{"result":1}` {
|
|
t.Fatalf("an answer that fits was changed: %s %v", plain, err)
|
|
}
|
|
}
|