Files
mesh-sdk/go/process/account_test.go
T
jschoubben e0d1de0475
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…
process: a part opens what it cannot work without as essential, the rest as optional (issue 1945)
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.
2026-10-25 22:12:20 +01:00

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 }