Name the pulls buckets of the seat's claimants on the check, so the build agent reads them (issue 1212)

This commit is contained in:
2026-10-19 12:04:26 +02:00
parent da3c01089e
commit e29e855bdf
6 changed files with 233 additions and 23 deletions
+113
View File
@@ -0,0 +1,113 @@
package link
import (
"context"
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"github.com/nats-io/nats.go/jetstream"
"github.com/novox/mesh-controller/internal/broker"
)
// The build agent reads and watches the pull requests' keys in the buckets the check request names (novox/hq ADR 0437
// §3, issue 1212): the controller knows the seat's claimants, the build agent holds no catalogue.
// 1212/AC4: a request naming no bucket reads mesh-delivery's, as before; the field is optional on the wire.
func TestARequestNamingNoPullsBucketUsesMeshDeliverys(t *testing.T) {
if got := PullBucketsOf(&CheckRequest{}); !reflect.DeepEqual(got, []string{broker.PullsBucket}) {
t.Errorf("buckets %v", got)
}
if got := PullBucketsOf(nil); !reflect.DeepEqual(got, []string{broker.PullsBucket}) {
t.Errorf("buckets of no check %v", got)
}
named := &CheckRequest{Pulls: []string{"mesh-delivery-gate_pulls"}}
if got := PullBucketsOf(named); !reflect.DeepEqual(got, named.Pulls) {
t.Errorf("buckets %v", got)
}
raw := mustJSON(t, CheckRequest{Owner: "o"})
if contains(string(raw), "pulls") {
t.Errorf("an empty field is written: %s", raw)
}
}
// opened is a set of buckets by name, each holding keys; a bucket not in it does not exist.
func opened(buckets map[string]map[string]string, opens *[]string) func(context.Context, string) (func(context.Context, string) ([]byte, error), error) {
return func(_ context.Context, bucket string) (func(context.Context, string) ([]byte, error), error) {
*opens = append(*opens, bucket)
keys, ok := buckets[bucket]
if !ok {
return nil, jetstream.ErrBucketNotFound
}
return func(_ context.Context, key string) ([]byte, error) {
v, ok := keys[key]
if !ok {
return nil, jetstream.ErrKeyNotFound
}
return []byte(v), nil
}, nil
}
}
// 1212/AC3: the buckets are read in turn, the first holding the key answers, a missing bucket is passed over, and
// none holding it is no key.
func TestThePullKeyIsReadFromTheNamedBucketsInTurn(t *testing.T) {
key := PullKeyOf("novox", "a", 7)
both := map[string]map[string]string{
"mesh-delivery_pulls": {key: `{"head":"old"}`},
"mesh-delivery-gate_pulls": {key: `{"head":"new"}`},
}
var opens []string
got, found, err := readPullKeyIn(t.Context(), []string{"mesh-delivery-gate_pulls", "mesh-delivery_pulls"}, opened(both, &opens), "novox", "a", 7)
if err != nil || !found || got.Head != "new" || len(opens) != 1 {
t.Errorf("first holding answers: %+v %v %v, opened %v", got, found, err, opens)
}
opens = nil
only := map[string]map[string]string{"mesh-delivery_pulls": {key: `{"head":"old"}`}, "mesh-delivery-gate_pulls": {}}
got, found, err = readPullKeyIn(t.Context(), []string{"mesh-delivery-gate_pulls", "mesh-delivery_pulls"}, opened(only, &opens), "novox", "a", 7)
if err != nil || !found || got.Head != "old" || len(opens) != 2 {
t.Errorf("a bucket without the key is passed over: %+v %v %v, opened %v", got, found, err, opens)
}
opens = nil
got, found, err = readPullKeyIn(t.Context(), []string{"gone_pulls", "mesh-delivery_pulls"}, opened(only, &opens), "novox", "a", 7)
if err != nil || !found || got.Head != "old" {
t.Errorf("a missing bucket is passed over: %+v %v %v", got, found, err)
}
_, found, err = readPullKeyIn(t.Context(), []string{"gone_pulls", "mesh-delivery-gate_pulls"}, opened(only, &opens), "novox", "a", 8)
if err != nil || found {
t.Errorf("none holding it: found %v, %v", found, err)
}
boom := errors.New("bus away")
_, _, err = readPullKeyIn(t.Context(), []string{"x"}, func(context.Context, string) (func(context.Context, string) ([]byte, error), error) {
return nil, boom
}, "novox", "a", 7)
if !errors.Is(err, boom) {
t.Errorf("a real failure is an error: %v", err)
}
// The watch takes the first named bucket that exists.
opens = nil
bucket, err := firstBucketThatExists(t.Context(), []string{"gone_pulls", "mesh-delivery-gate_pulls", "mesh-delivery_pulls"}, func(_ context.Context, b string) error {
opens = append(opens, b)
if b == "gone_pulls" {
return jetstream.ErrBucketNotFound
}
return nil
})
if err != nil || bucket != "mesh-delivery-gate_pulls" || len(opens) != 2 {
t.Errorf("watch bucket %q %v, tried %v", bucket, err, opens)
}
}
func mustJSON(t *testing.T, v any) string {
t.Helper()
raw, err := json.Marshal(v)
if err != nil {
t.Fatal(err)
}
return string(raw)
}
func contains(s, sub string) bool { return strings.Contains(s, sub) }