package provide import ( "encoding/json" "os" "strings" "testing" "time" "github.com/novox/mesh-tools/node-tools/internal/bus" mt "github.com/novox/mesh-tools/node-tools/internal/meshtest" ) // claude-code's facts.json template, as the catalogue keeps it (modules/claude-code/files/facts.json). const factsTemplate = `{ "node": "${machine:name}", "console": "http://127.0.0.1:${bound:mcp-endpoint:port}/mcp" } ` // factsToday is that file as the controller composes it today, from the port node-tools serves. const factsToday = `{ "node": "anchor", "console": "http://127.0.0.1:4270/mcp" } ` // fill is what the consumer's client does with the template: the machine's name, and each ${bound::} // from the provider's entry. func fill(t *testing.T, template string, entry map[string]any) string { t.Helper() values, _ := entry["values"].(map[string]any) out := strings.ReplaceAll(template, "${machine:name}", "anchor") for key, v := range values { raw, _ := json.Marshal(v) out = strings.ReplaceAll(out, "${bound:mcp-endpoint:"+key+"}", string(raw)) } return out } func TestTheRuntimeAnswersAHandedConsumersMcpEndpointThroughTheHelper(t *testing.T) { mesh := mt.New(t) bound := mesh.Bucket(t, "node-tools_bound") assignments := mesh.Bucket(t, "mesh-controller_assignments") mem := mt.MembershipOf("node-tools", "anchor", false, nil) mem.State = []bus.StateIssued{ {Name: "bound", Bucket: "node-tools_bound", Writes: true}, {Name: "mesh-controller.assignments", Bucket: "mesh-controller_assignments"}, } mesh.Issue(t, mem) // claude-code, handed on this machine, is bound to node-tools here for mcp-endpoint (issue 788's plan entry). plan := `{"node":"anchor","module":"claude-code","provisioning":{"provisions":[{"provision":"mcp-endpoint",` + `"provider":"anchor","provider-module":"node-tools","at":"127.0.0.1","from":"anchor","as":"mesh_anchor_claude_code"}]}}` if _, err := assignments.Put("anchor.claude-code", []byte(plan)); err != nil { t.Fatal(err) } conn, err := bus.Connect(bus.Credential{URL: mt.URL(t), Module: "node-tools", Node: "anchor"}) if err != nil { t.Fatal(err) } conn.Logf = func(string, ...any) {} t.Cleanup(conn.Close) conn.Follow("node-tools") said := &mt.Logs{} t.Cleanup(Start(conn, "node-tools", "anchor", 20*time.Millisecond, said.Logf)) var entry map[string]any mt.Until(t, func() error { kept, err := bound.Get("anchor.claude-code.mcp-endpoint") if err != nil { return err } return json.Unmarshal(kept.Value(), &entry) }) values, _ := entry["values"].(map[string]any) for key, want := range map[string]any{"port": float64(4270), "at": "127.0.0.1", "from": "anchor"} { if values[key] != want { t.Errorf("the entry's %s is %v, want %v (entry %v)", key, values[key], want, entry) } } if entry["as"] != "mesh_anchor_claude_code" { t.Errorf("the entry's as is %v", entry["as"]) } if c, has := entry["credential"]; has { t.Errorf("an offer with no credential names one: %v", c) } if got := fill(t, factsTemplate, entry); got != factsToday { t.Errorf("claude-code's facts.json fills to\n%s\nwant today's\n%s", got, factsToday) } } // The port the offer serves is the port the manifest serves and the console listens on. func TestTheOfferServesThePortTheManifestServes(t *testing.T) { raw, err := os.ReadFile("../../module.json") if err != nil { t.Fatal(err) } var m struct { Serves map[string]map[string]any `json:"serves"` Moved bool `json:"moved"` Provides []struct { Name string `json:"name"` AnsweredBy string `json:"answered-by"` } `json:"provides"` Resources []struct { ID string `json:"id"` } `json:"resources"` Build struct { Artifacts []struct { Name string `json:"name"` Env map[string]string `json:"env"` } `json:"artifacts"` } `json:"build"` } if err := json.Unmarshal(raw, &m); err != nil { t.Fatal(err) } if got := m.Serves[Provision]["port"]; got != float64(ServedPort) { t.Errorf("the manifest serves %v for %s, the offer %d", got, Provision, ServedPort) } if m.Moved { t.Error("node-tools is marked moved: that also hands the runtime's bus account to the vault (issue 791 round 1)") } if len(m.Provides) != 1 || m.Provides[0].Name != Provision || m.Provides[0].AnsweredBy != "helper" { t.Errorf("node-tools does not say its %s is answered by the helper: %+v", Provision, m.Provides) } for _, r := range m.Resources { if r.ID == "consumers" { t.Error("the manifest still carries a consumers file the helper does not need") } } for _, a := range m.Build.Artifacts { if a.Name == "files" || len(a.Env) > 0 { t.Errorf("the build still carries %+v, which only a moved module needs", a) } } } // Only the machine's runtime is the provider: a process on another credential, or with no machine, answers nobody. func TestOnlyTheMachinesRuntimeStartsTheHelper(t *testing.T) { said := &mt.Logs{} for _, c := range []struct{ module, node string }{{"claude-code", "anchor"}, {"node-tools", ""}} { Start(nil, c.module, c.node, time.Second, said.Logf)() } }