mesh/delivery merged: source.main moved to novox/mesh-sdk@7ac4493ce9ab
mesh/delivery-group group fix/1945-ask-again-and-why merged: every member is merged
mesh/check pass e0d1de047585 on 3db051be57ca | unit 1642 run, 752 kept, 2 skipped | integration 1710 run, 803 kept, 2 skipped | over bound: unit, inte…
The delivery builder exited twice on 2026-10-25 for a bucket the bus refused at start, though it could build without it: a grant reaches the bus with a delivery, so a refusal at start is often only a grant not there yet. EssentialBucket stops a start naming the bucket and its grant; OptionalBucket and NotWatched say what is not heard and go on. Here so the seven delivery parts take one copy, not seven.
117 lines
4.4 KiB
Go
117 lines
4.4 KiB
Go
package process
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
)
|
|
|
|
// 1171/AC2: given an account file broker-<process id> in the controller's form {url, fingerprint, node,
|
|
// module, user, password, claims}, when the process package reads it, then it yields those fields and
|
|
// tolerates claims.
|
|
func TestReadAccountYieldsEveryFieldAndToleratesClaims(t *testing.T) {
|
|
path := writeAccount(t, map[string]any{
|
|
"url": "nats://novox:4222", "fingerprint": "sha256:ab", "node": "novox", "module": "mesh-mail",
|
|
"user": "novox.mesh-mail.send", "password": "secret-pw",
|
|
"claims": []map[string]any{{"seat": "operator-channel", "verbs": []string{"tell"}}},
|
|
})
|
|
a, err := ReadAccount(path)
|
|
if err != nil {
|
|
t.Fatalf("a well-formed account file was refused: %v", err)
|
|
}
|
|
if a.URL != "nats://novox:4222" || a.Fingerprint != "sha256:ab" || a.Node != "novox" ||
|
|
a.Module != "mesh-mail" || a.User != "novox.mesh-mail.send" || a.Password != "secret-pw" {
|
|
t.Fatalf("the account read back: %+v", a)
|
|
}
|
|
}
|
|
|
|
// 1171/AC2: a process account carries no claims at all (heldProcessAccount); reading it is not refused for
|
|
// the field being absent.
|
|
func TestReadAccountToleratesNoClaimsAtAll(t *testing.T) {
|
|
path := writeAccount(t, map[string]any{
|
|
"url": "nats://novox:4222", "node": "novox", "module": "mesh-mail",
|
|
"user": "novox.mesh-mail.send", "password": "secret-pw",
|
|
})
|
|
a, err := ReadAccount(path)
|
|
if err != nil {
|
|
t.Fatalf("a process account with no claims was refused: %v", err)
|
|
}
|
|
if a.User != "novox.mesh-mail.send" {
|
|
t.Fatalf("the account read back: %+v", a)
|
|
}
|
|
}
|
|
|
|
// 1171/AC2: a file missing url, user or password is refused, naming the field; the password never appears
|
|
// in the error even when it is the field present and another is missing.
|
|
func TestReadAccountRefusesAFileMissingAField(t *testing.T) {
|
|
t.Run("1945: a refused bucket stops a start only when the part cannot work without it", func(t *testing.T) {
|
|
refusal := errors.New("nats: permissions violation for publish to $JS.API.STREAM.INFO.KV_mesh-delivery-gate_source")
|
|
for _, c := range []struct {
|
|
name string
|
|
err error
|
|
}{{"refused", refusal}, {"not answered", context.DeadlineExceeded}} {
|
|
var said []string
|
|
logf := func(f string, a ...any) { said = append(said, fmt.Sprintf(f, a...)) }
|
|
if kv := OptionalBucket(context.Background(), refusingBus{c.err}, "b", "b is not heard", logf); kv != nil {
|
|
t.Errorf("%s: an optional bucket answered %v", c.name, kv)
|
|
}
|
|
NotWatched(logf, "b", c.err, "b is not heard")
|
|
if len(said) != 2 || !strings.Contains(said[0], c.err.Error()) || !strings.Contains(said[1], "cannot be watched") {
|
|
t.Errorf("%s: said %q", c.name, said)
|
|
}
|
|
if _, err := EssentialBucket(context.Background(), refusingBus{c.err}, "builder", "b", "build"); err == nil ||
|
|
!strings.Contains(err.Error(), "grant of b") || !strings.Contains(err.Error(), "the builder cannot build") {
|
|
t.Errorf("%s: an essential bucket: %v", c.name, err)
|
|
}
|
|
}
|
|
})
|
|
|
|
for _, tc := range []struct {
|
|
field string
|
|
values map[string]any
|
|
}{
|
|
{"url", map[string]any{"node": "novox", "module": "mesh-mail", "user": "novox.mesh-mail.send", "password": "secret-pw"}},
|
|
{"user", map[string]any{"url": "nats://novox:4222", "node": "novox", "module": "mesh-mail", "password": "secret-pw"}},
|
|
{"password", map[string]any{"url": "nats://novox:4222", "node": "novox", "module": "mesh-mail", "user": "novox.mesh-mail.send"}},
|
|
} {
|
|
t.Run(tc.field, func(t *testing.T) {
|
|
path := writeAccount(t, tc.values)
|
|
_, err := ReadAccount(path)
|
|
if err == nil {
|
|
t.Fatalf("an account file missing %s was accepted", tc.field)
|
|
}
|
|
if !strings.Contains(err.Error(), tc.field) {
|
|
t.Errorf("the error %q does not name the missing field %s", err.Error(), tc.field)
|
|
}
|
|
if strings.Contains(err.Error(), "secret-pw") {
|
|
t.Errorf("the password appears in the error: %q", err.Error())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func writeAccount(t *testing.T, fields map[string]any) string {
|
|
t.Helper()
|
|
b, err := json.Marshal(fields)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(t.TempDir(), "broker-send")
|
|
if err := os.WriteFile(path, b, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
// refusingBus refuses every bucket with err.
|
|
type refusingBus struct{ err error }
|
|
|
|
func (r refusingBus) KeyValue(context.Context, string) (jetstream.KeyValue, error) { return nil, r.err }
|