Main reverted G's half merge (#189, reverted by8afc3ebin #191) and then took the reader release's S1-S5, which copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's headf30dee54only by those and the release's two tests (objects cap option, seat states and stream kinds). Builds: design 47 G (the milestone's sdk part, as atf30dee54) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
72 lines
2.7 KiB
Go
72 lines
2.7 KiB
Go
//go:build integration
|
|
|
|
package process
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
|
"git.novox.be/novox/mesh-sdk/go/testbus"
|
|
)
|
|
|
|
// Design 47 G A-291: a process receives a seat verb's chunked answer through one helper: it subscribes to the call's chunks
|
|
// before it asks, acknowledges each, and answers the whole body (CallChunks into a writer, CallFile as the member file);
|
|
// an answer accept refuses takes no chunk.
|
|
func TestAProcessReceivesAChunkedSeatAnswerWhole(t *testing.T) {
|
|
url := testbus.URL(t)
|
|
holder, err := nats.Connect(url)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer holder.Close()
|
|
body := []byte(strings.Repeat("0123456789", 2*buscalls.SeatChunkBytes/10+7)) // three chunks
|
|
of := int64((len(body) + buscalls.SeatChunkBytes - 1) / buscalls.SeatChunkBytes)
|
|
sub, err := holder.Subscribe("mesh.seat.node-build-go.call.output.ace.>", func(m *nats.Msg) {
|
|
var in struct{ Ask, Call string }
|
|
_ = json.Unmarshal(m.Data, &in)
|
|
answer, _ := json.Marshal(map[string]any{"result": map[string]any{"ask": in.Ask, "chunks": of}})
|
|
_ = m.Respond(answer)
|
|
go func() {
|
|
_ = buscalls.SeatChunks{Seat: "node-build-go", Verb: "output", Call: in.Call}.Send(holder, body, buscalls.SeatAckWithin, t.Logf)
|
|
}()
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = sub.Unsubscribe() }()
|
|
_ = holder.Flush()
|
|
nc, err := nats.Connect(url)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
c := &Conn{nc: nc, account: Account{User: "novox.mesh-delivery-checker"}}
|
|
defer c.Close()
|
|
var got bytes.Buffer
|
|
answer, err := c.CallChunks(context.Background(), "seat:node-build-go.output@ace", "c1", map[string]any{"ask": "a1", "call": "c1"}, nil, &got)
|
|
if err != nil || !bytes.Equal(got.Bytes(), body) || !strings.Contains(string(answer), `"ask":"a1"`) {
|
|
t.Fatalf("answered %s and %d bytes (%v), want the verb's answer and the whole %d bytes", answer, got.Len(), err, len(body))
|
|
}
|
|
raw, err := c.CallFile("seat:node-build-go.output@ace", map[string]any{"ask": "a2", "call": "c2"})
|
|
var file struct{ Ask, File string }
|
|
if err != nil || json.Unmarshal(raw, &file) != nil || file.Ask != "a2" {
|
|
t.Fatalf("CallFile answered %.80s, %v", raw, err)
|
|
}
|
|
if whole, _ := base64.StdEncoding.DecodeString(file.File); !bytes.Equal(whole, body) {
|
|
t.Fatalf("CallFile's file holds %d bytes, want %d", len(whole), len(body))
|
|
}
|
|
refused := errors.New("too large for this disk")
|
|
got.Reset()
|
|
if _, err := c.CallChunks(context.Background(), "seat:node-build-go.output@ace", "c3", map[string]any{"ask": "a3", "call": "c3"},
|
|
func(json.RawMessage) error { return refused }, &got); !errors.Is(err, refused) || got.Len() != 0 {
|
|
t.Fatalf("an answer accept refused: %v, %d bytes taken", err, got.Len())
|
|
}
|
|
}
|