package main import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/require" ) func TestRootRouteToolsPersistAndRemoveRoutes(t *testing.T) { dir := t.TempDir() server := newModelFake(t) test := newDaemonMessageTest(t, dir, server) root := test.loops[0] source := "whatsapp:" + whatsappTestFamily.String() arguments := `{"source":"` + source + `","agent":"Garden"}` require.Equal(t, "Route set.", root.tools.run(t.Context(), "route", arguments)) require.Equal(t, source+" -> Garden", root.tools.run(t.Context(), "routes", `{}`)) info, err := os.Stat(filepath.Join(root.agent.dir, "routes.json")) require.NoError(t, err) require.Equal(t, os.FileMode(0o600), info.Mode().Perm()) require.Contains(t, test.logs.String(), "route set kind=whatsapp name=Garden") require.NotContains(t, test.logs.String(), whatsappTestFamily.String()) test.Close(t) restored := newDaemonMessageTest(t, dir, server) require.Equal(t, source+" -> Garden", restored.loops[0].tools.run(t.Context(), "routes", `{}`)) require.Equal(t, "Route removed.", restored.loops[0].tools.run(t.Context(), "unroute", `{"source":"`+source+`"}`)) require.Equal(t, "No routes.", restored.loops[0].tools.run(t.Context(), "routes", `{}`)) require.Contains(t, restored.logs.String(), "route removed kind=whatsapp") restored.Close(t) empty := newDaemonMessageTest(t, dir, server) require.Equal(t, "No routes.", empty.loops[0].tools.run(t.Context(), "routes", `{}`)) require.Zero(t, server.Calls()) } func TestRoutesRefuseUnknownSourcesAndNonchildren(t *testing.T) { test := newDaemonMessageTest(t, t.TempDir(), newModelFake(t)) testCases := []struct { name string source string agent string cause string }{ { name: "unknown kind", source: "mail:inbox", agent: "Garden", cause: "unknown source kind", }, { name: "missing kind", source: whatsappTestFamily.String(), agent: "Garden", cause: "unknown source kind", }, { name: "empty chat", source: "whatsapp:", agent: "Garden", cause: "invalid WhatsApp source", }, { name: "unknown chat kind", source: "whatsapp:example@unknown", agent: "Garden", cause: "invalid WhatsApp source", }, { name: "root is not a child", source: "whatsapp:" + whatsappTestFamily.String(), agent: "OneChat", cause: "must be a child", }, { name: "unknown child", source: "whatsapp:" + whatsappTestFamily.String(), agent: "Other", cause: "must be a child", }, { name: "empty child", source: "whatsapp:" + whatsappTestFamily.String(), agent: "", cause: "must be a child", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { arguments := `{"source":"` + tc.source + `","agent":"` + tc.agent + `"}` require.Contains(t, test.loops[0].tools.run(t.Context(), "route", arguments), tc.cause) }) } require.Contains(t, test.loops[0].tools.run(t.Context(), "unroute", `{"source":"mail:inbox"}`), "unknown source kind") require.Equal(t, "No routes.", test.messages.routes.List()) } func TestRouteToolsBelongOnlyToRoot(t *testing.T) { test := newDaemonMessageTest(t, t.TempDir(), newModelFake(t)) for _, name := range []string{"route", "unroute", "routes"} { require.Contains(t, promptToolNames(test.agents[0].agent.Tools()), name) require.NotContains(t, promptToolNames(test.agents[1].agent.Tools()), name) require.NotContains(t, promptToolNames(promptSubagentTools()), name) require.Contains(t, test.loops[1].tools.run(t.Context(), name, `{}`), "unknown tool") } require.NotContains(t, promptChild("Garden", "OneChat", "Keep plans.", "/CHILD/instructions.md", ""), "Propose routing a source") } func TestWhatsAppStatusListsRoutes(t *testing.T) { whatsapp, _, _ := whatsappStartTest(t, t.TempDir(), newTestClock()) test := newDaemonMessageTest(t, t.TempDir(), newModelFake(t)) whatsapp.routes = test.messages.routes require.NoError(t, test.messages.routes.Set("whatsapp:"+whatsappTestFamily.String(), "Garden")) var answer string whatsapp.Command("status", func(text string) { answer = text }) require.Contains(t, answer, "WhatsApp is not linked.") require.Contains(t, answer, "WhatsApp routes:\nwhatsapp:"+whatsappTestFamily.String()+" -> Garden") } func TestInvalidRouteFileStopsStartup(t *testing.T) { testCases := []struct { name string text string }{ { name: "invalid JSON", text: "{", }, { name: "unknown source", text: `{"mail:inbox":"Garden"}`, }, { name: "empty destination", text: `{"whatsapp:example@lid":""}`, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "routes.json") require.NoError(t, os.WriteFile(path, []byte(tc.text), 0o600)) _, err := openRoutes(path, discardLog()) require.Error(t, err) }) } }