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) }