ADR 0571 clause 10: the grants part publishes memberships on mesh.assignment until the lease write and stops then. FollowMembership now reads and watches its own key of the state memberships and nothing else, and refuses to follow, naming the state, when it cannot read it (there is nothing else to follow, and a silent follow of nothing is the fault). A module's principal and the runtime lose their subscribe on mesh.assignment; the grants part's publish grant stays until the write (S21 stops the publishing). Tests: TestFollowMembershipHearsWhatIsIssuedToTheAccount (integration) edited: no state is a refusal, a membership put in the state is applied, one published on the subject is not; run locally against its embedded bus. Rows added to TestTheCopiedComposerComposesAsTheControllers: nobody subscribes mesh.assignment, and the controller's golden grant of it is the one row the copy now lacks. No new test function. (issue 774, 774/S5) Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
78 lines
2.4 KiB
Go
78 lines
2.4 KiB
Go
//go:build integration
|
|
|
|
package process
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
|
)
|
|
|
|
// A process connected pinned to a test bus follows the membership the grants part writes for its node and module in
|
|
// its state memberships, and reports what is not one (novox/hq issue 1633, AC2); it no longer hears mesh.assignment,
|
|
// where the grants part stops publishing at the lease write (ADR 0571 clause 10; issue 774, 774/S5/AC1). Without the
|
|
// state it refuses to follow, naming it.
|
|
func TestFollowMembershipHearsWhatIsIssuedToTheAccount(t *testing.T) {
|
|
url, fingerprint := selfSignedServer(t)
|
|
account := mailSenderAccount(t, url, fingerprint)
|
|
conn, err := Connect(account)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
got := make(chan membership.Membership, 4)
|
|
bad := make(chan error, 4)
|
|
follow := func() error {
|
|
return conn.FollowMembership(func(m membership.Membership) { got <- m }, func(e error) { bad <- e })
|
|
}
|
|
if err := follow(); err == nil || !strings.Contains(err.Error(), membership.GrantsBucket) {
|
|
t.Fatalf("followed with no state memberships: %v", err)
|
|
}
|
|
js, err := jetstream.New(conn.nc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx := context.Background()
|
|
kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: membership.GrantsBucket})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
key := membership.GrantsKey("novox", "mesh-mail")
|
|
if _, err := kv.Put(ctx, key, []byte("not json")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := follow(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Published on the subject, as the grants part did until the write: not heard.
|
|
if err := conn.nc.Publish(membership.Subject("novox", "mesh-mail"), []byte(`{"node":"novox","module":"mesh-mail","mesh":["10.0.0.9"]}`)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := kv.Put(ctx, key, membership.EncodeGrantsMembership(membership.Membership{Node: "novox", Module: "mesh-mail",
|
|
Mesh: []string{"10.0.0.1"}})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
select {
|
|
case m := <-got:
|
|
if m.Module != "mesh-mail" || len(m.Mesh) != 1 || m.Mesh[0] != "10.0.0.1" {
|
|
t.Fatalf("membership: %+v", m)
|
|
}
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("no membership followed")
|
|
}
|
|
select {
|
|
case m := <-got:
|
|
t.Errorf("a second membership was applied, from the subject: %+v", m)
|
|
case <-time.After(300 * time.Millisecond):
|
|
}
|
|
if len(bad) == 0 {
|
|
t.Error("the value that is not a membership was not reported")
|
|
}
|
|
}
|