From f1438dff6a3d1faebef87a8a23b998ce672db327 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 19 Oct 2026 04:51:14 +0200 Subject: [PATCH] Prove Conn offers no Serve by reflection, and build its AC3 test account through ReadAccount (issue 1171, review round 1 findings 3 and 4) --- go/process/conn_integration_test.go | 31 ++++++++++++++++++++---- go/process/no_serve_test.go | 37 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 go/process/no_serve_test.go diff --git a/go/process/conn_integration_test.go b/go/process/conn_integration_test.go index 40c52ee..2633219 100644 --- a/go/process/conn_integration_test.go +++ b/go/process/conn_integration_test.go @@ -11,7 +11,10 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/hex" + "encoding/json" "math/big" + "os" + "path/filepath" "testing" "time" @@ -61,16 +64,34 @@ func selfSignedServer(t *testing.T) (url, fingerprint string) { return s.ClientURL(), fingerprint } -func mailSenderAccount(url, fingerprint string) Account { - return Account{URL: url, Fingerprint: fingerprint, Node: "novox", Module: "mesh-mail", - User: "novox.mesh-mail.send", Password: "the-pw"} +// mailSenderAccount is the account novox.mesh-mail.send would be delivered as, the module's own secret +// broker-send: written to a file and read back through ReadAccount (1171/AC2), exactly as a process +// reads it, rather than built by hand. +func mailSenderAccount(t *testing.T, url, fingerprint string) Account { + t.Helper() + data, err := json.Marshal(map[string]any{ + "url": url, "fingerprint": fingerprint, "node": "novox", "module": "mesh-mail", + "user": "novox.mesh-mail.send", "password": "the-pw", + }) + if err != nil { + t.Fatal(err) + } + path := filepath.Join(t.TempDir(), "broker-send") + if err := os.WriteFile(path, data, 0o600); err != nil { + t.Fatal(err) + } + account, err := ReadAccount(path) + if err != nil { + t.Fatalf("ReadAccount on a well-formed file: %v", err) + } + return account } // 1171/AC3: given a test bus with TLS and the account composed for novox.mesh-mail.send, when the package // connects with the file, then it connects with the inbox prefix _INBOX.novox.mesh-mail.send. func TestConnectPinsTheFingerprintAndSetsTheInboxPrefix(t *testing.T) { url, fingerprint := selfSignedServer(t) - account := mailSenderAccount(url, fingerprint) + account := mailSenderAccount(t, url, fingerprint) conn, err := Connect(account) if err != nil { t.Fatalf("connecting with the right fingerprint was refused: %v", err) @@ -87,7 +108,7 @@ func TestConnectPinsTheFingerprintAndSetsTheInboxPrefix(t *testing.T) { // 1171/AC3: connecting with a fingerprint that is not the bus's own leaf certificate is refused. func TestConnectRefusesTheWrongFingerprint(t *testing.T) { url, _ := selfSignedServer(t) - account := mailSenderAccount(url, "sha256:0000000000000000000000000000000000000000000000000000000000000000") + account := mailSenderAccount(t, url, "sha256:0000000000000000000000000000000000000000000000000000000000000000") if _, err := Connect(account); err == nil { t.Fatal("connecting with the wrong fingerprint was not refused") } diff --git a/go/process/no_serve_test.go b/go/process/no_serve_test.go new file mode 100644 index 0000000..f7eb72f --- /dev/null +++ b/go/process/no_serve_test.go @@ -0,0 +1,37 @@ +package process + +import ( + "reflect" + "testing" + + "github.com/nats-io/nats.go" +) + +// 1171/AC3: it offers no Serve and no way to serve a tool — a process reaches the bus only as itself, and +// never answers a call. Checked by reflection over *Conn's whole method set and field list, so a later +// addition cannot bring one back without this test naming it. +func TestConnOffersNoServeAndExposesNoRawConnection(t *testing.T) { + ptrType := reflect.TypeOf(&Conn{}) + for _, forbidden := range []string{"Serve", "ServeOn", "Handle", "Subscribe", "Respond", "Answer"} { + if _, ok := ptrType.MethodByName(forbidden); ok { + t.Errorf("*Conn has a method %s: a process offers no way to serve a tool", forbidden) + } + } + + natsConnType := reflect.TypeOf(&nats.Conn{}) + structType := reflect.TypeOf(Conn{}) + for i := 0; i < structType.NumField(); i++ { + f := structType.Field(i) + if f.IsExported() && (f.Type == natsConnType || f.Type == natsConnType.Elem()) { + t.Errorf("Conn exposes its raw connection through the exported field %s", f.Name) + } + } + for i := 0; i < ptrType.NumMethod(); i++ { + m := ptrType.Method(i) + for o := 0; o < m.Type.NumOut(); o++ { + if m.Type.Out(o) == natsConnType || m.Type.Out(o) == natsConnType.Elem() { + t.Errorf("*Conn.%s returns the raw *nats.Conn", m.Name) + } + } + } +}