package main import ( "encoding/json" "strings" "testing" "github.com/novox/mesh-controller/internal/broker" "github.com/novox/mesh-controller/internal/catalogue" ) // mailSenderWithProcess is mesh-mail's real manifest shape (novox/hq ADR 0436 §7, §8, the lead's fixture): // no own secret named `broker` at all — its tools run in the machine's tool runner — and the process "send" // declares bus, whose own secret broker-send is where module issue delivers its account (ADR 0436 §2), // owned by mesh-mail-send, the user resource the process declares, at mode 0600 as every own secret is. func mailSenderWithProcess() catalogue.Manifest { m, err := catalogue.ParseManifest([]byte(`{"module":"mesh-mail","version":"1","emits":["mail.send.sent"], "own-secrets":{"broker-send":{"path":"/var/lib/mesh-mail/broker-send","owner":"mesh-mail-send"}}, "resources":[{"id":"account","type":"user","name":"mesh-mail-send"}, {"id":"send","type":"process","run":["./mesh-mail","send"],"user":"mesh-mail-send", "bus":{"emits":["mail.send.sent"]}}]}`)) if err != nil { panic(err) } return m } // review round 1, finding 1 (1181): a module declaring no `broker` of its own at all — mesh-mail's real // shape, tools served by the tool runner — still passes mayIssue on its process's bus declaration alone. func TestAModuleWithNoBrokerAndOnlyAProcessMayBeIssued(t *testing.T) { if err := mayIssue(mailSenderWithProcess()); err != nil { t.Fatalf("a module with no broker of its own, whose process declares bus, was refused: %v", err) } } // 1181/AC1: a process with a bus declaration and no own secret broker- cannot be issued — the // same refusal mayIssue already gives a module with no `broker` (novox/hq ADR 0436 §2). func TestAProcessWithNoBrokerSecretCannotBeIssued(t *testing.T) { m := mailSenderWithProcess() delete(m.OwnSecrets, "broker-send") err := mayIssue(m) if err == nil { t.Fatal("a process with no broker- own secret was issued an account") } for _, want := range []string{"mesh-mail", "send", "broker-send", "own-secrets"} { if !strings.Contains(err.Error(), want) { t.Errorf("the refusal does not say %q: %v", want, err) } } if err := mayIssue(mailSenderWithProcess()); err != nil { t.Errorf("a process declaring its own broker- secret was refused: %v", err) } } // 1181/AC1: the account held for a process is the provisioner client's form — {url, fingerprint, node, // module, user, password} — named for the process, never the module's own account. func TestAProcesssHeldAccountIsTheProvisionersForm(t *testing.T) { user := broker.Principal{Kind: broker.KindProcess, Node: "novox", Module: "mesh-mail", Process: "send"}.Username() if user != "novox.mesh-mail.send" { t.Fatalf("the process's username: %q", user) } held, err := heldProcessAccount(broker.Broker{Fingerprint: "abc"}, "novox.example:4222", "novox", "mesh-mail", user, "a-password") if err != nil { t.Fatal(err) } var got map[string]any if err := json.Unmarshal(held, &got); err != nil { t.Fatal(err) } want := map[string]any{"url": "nats://novox.example:4222", "fingerprint": "abc", "node": "novox", "module": "mesh-mail", "user": "novox.mesh-mail.send", "password": "a-password"} for k, v := range want { if got[k] != v { t.Errorf("%s: %v, want %v", k, got[k], v) } } if len(got) != len(want) { t.Errorf("the held account carries %v; want exactly %v (never claims — a process holds no seat)", got, want) } } // 1181/AC1: composed, the process's own secret belongs to its own account at 0600 (novox/hq ADR 0436 §1, §2; // the owner mechanism is issue 1170's, proven generically there — this is mesh-mail's own shape). func TestAProcessesOwnSecretIsOwnedByItsOwnAccount(t *testing.T) { m := mailSenderWithProcess() r := catalogue.Resolution{Node: "novox", Modules: []catalogue.Manifest{m}} out, err := r.Declaration(catalogue.Rendering{ Needed: map[string]map[string]string{"mesh-mail": {"broker-send": "sealed-account"}}, }) if err != nil { t.Fatalf("composing the declaration: %v", err) } var found bool for _, res := range out { if res["type"] == "file" && res["path"] == "/var/lib/mesh-mail/broker-send" { found = true if got := res["owner"]; got != "mesh-mail-send" { t.Fatalf("broker-send's owner: %v; want mesh-mail-send", got) } } } if !found { t.Fatal("broker-send was not composed at all") } }