]> git.giacomolli.uk - onechat/blob - routes_test.go
Reflow comments over the reflow width
[onechat] / routes_test.go
1 package main
2
3 import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9 )
10
11 func TestRootRouteToolsPersistAndRemoveRoutes(t *testing.T) {
12 dir := t.TempDir()
13 server := newModelFake(t)
14 test := newDaemonMessageTest(t, dir, server)
15 root := test.loops[0]
16 source := "whatsapp:" + whatsappTestFamily.String()
17 arguments := `{"source":"` + source + `","agent":"Garden"}`
18 require.Equal(t, "Route set.", root.tools.run(t.Context(), "route", arguments))
19 require.Equal(t, source+" -> Garden", root.tools.run(t.Context(), "routes", `{}`))
20
21 info, err := os.Stat(filepath.Join(root.agent.dir, "routes.json"))
22 require.NoError(t, err)
23 require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
24 require.Contains(t, test.logs.String(), "route set kind=whatsapp name=Garden")
25 require.NotContains(t, test.logs.String(), whatsappTestFamily.String())
26 test.Close(t)
27
28 restored := newDaemonMessageTest(t, dir, server)
29 require.Equal(t, source+" -> Garden", restored.loops[0].tools.run(t.Context(), "routes", `{}`))
30 require.Equal(t, "Route removed.", restored.loops[0].tools.run(t.Context(), "unroute", `{"source":"`+source+`"}`))
31 require.Equal(t, "No routes.", restored.loops[0].tools.run(t.Context(), "routes", `{}`))
32 require.Contains(t, restored.logs.String(), "route removed kind=whatsapp")
33 restored.Close(t)
34
35 empty := newDaemonMessageTest(t, dir, server)
36 require.Equal(t, "No routes.", empty.loops[0].tools.run(t.Context(), "routes", `{}`))
37 require.Zero(t, server.Calls())
38 }
39
40 func TestRoutesRefuseUnknownSourcesAndNonchildren(t *testing.T) {
41 test := newDaemonMessageTest(t, t.TempDir(), newModelFake(t))
42 testCases := []struct {
43 name string
44 source string
45 agent string
46 cause string
47 }{
48 {
49 name: "unknown kind",
50 source: "mail:inbox",
51 agent: "Garden",
52 cause: "unknown source kind",
53 },
54 {
55 name: "missing kind",
56 source: whatsappTestFamily.String(),
57 agent: "Garden",
58 cause: "unknown source kind",
59 },
60 {
61 name: "empty chat",
62 source: "whatsapp:",
63 agent: "Garden",
64 cause: "invalid WhatsApp source",
65 },
66 {
67 name: "unknown chat kind",
68 source: "whatsapp:example@unknown",
69 agent: "Garden",
70 cause: "invalid WhatsApp source",
71 },
72 {
73 name: "root is not a child",
74 source: "whatsapp:" + whatsappTestFamily.String(),
75 agent: "OneChat",
76 cause: "must be a child",
77 },
78 {
79 name: "unknown child",
80 source: "whatsapp:" + whatsappTestFamily.String(),
81 agent: "Other",
82 cause: "must be a child",
83 },
84 {
85 name: "empty child",
86 source: "whatsapp:" + whatsappTestFamily.String(),
87 agent: "",
88 cause: "must be a child",
89 },
90 }
91 for _, tc := range testCases {
92 t.Run(tc.name, func(t *testing.T) {
93 arguments := `{"source":"` + tc.source + `","agent":"` + tc.agent + `"}`
94 require.Contains(t, test.loops[0].tools.run(t.Context(), "route", arguments), tc.cause)
95 })
96 }
97
98 require.Contains(t, test.loops[0].tools.run(t.Context(), "unroute", `{"source":"mail:inbox"}`), "unknown source kind")
99 require.Equal(t, "No routes.", test.messages.routes.List())
100 }
101
102 func TestRouteToolsBelongOnlyToRoot(t *testing.T) {
103 test := newDaemonMessageTest(t, t.TempDir(), newModelFake(t))
104 for _, name := range []string{"route", "unroute", "routes"} {
105 require.Contains(t, promptToolNames(test.agents[0].agent.Tools()), name)
106 require.NotContains(t, promptToolNames(test.agents[1].agent.Tools()), name)
107 require.NotContains(t, promptToolNames(promptSubagentTools()), name)
108 require.Contains(t, test.loops[1].tools.run(t.Context(), name, `{}`), "unknown tool")
109 }
110
111 require.NotContains(t, promptChild("Garden", "OneChat", "Keep plans.", "/CHILD/instructions.md", ""), "Propose routing a source")
112 }
113
114 func TestWhatsAppStatusListsRoutes(t *testing.T) {
115 whatsapp, _, _ := whatsappStartTest(t, t.TempDir(), newTestClock())
116 test := newDaemonMessageTest(t, t.TempDir(), newModelFake(t))
117 whatsapp.routes = test.messages.routes
118 require.NoError(t, test.messages.routes.Set("whatsapp:"+whatsappTestFamily.String(), "Garden"))
119
120 var answer string
121
122 whatsapp.Command("status", func(text string) {
123 answer = text
124 })
125 require.Contains(t, answer, "WhatsApp is not linked.")
126 require.Contains(t, answer, "WhatsApp routes:\nwhatsapp:"+whatsappTestFamily.String()+" -> Garden")
127 }
128
129 func TestInvalidRouteFileStopsStartup(t *testing.T) {
130 testCases := []struct {
131 name string
132 text string
133 }{
134 {
135 name: "invalid JSON",
136 text: "{",
137 },
138 {
139 name: "unknown source",
140 text: `{"mail:inbox":"Garden"}`,
141 },
142 {
143 name: "empty destination",
144 text: `{"whatsapp:example@lid":""}`,
145 },
146 }
147 for _, tc := range testCases {
148 t.Run(tc.name, func(t *testing.T) {
149 path := filepath.Join(t.TempDir(), "routes.json")
150 require.NoError(t, os.WriteFile(path, []byte(tc.text), 0o600))
151
152 _, err := openRoutes(path, discardLog())
153 require.Error(t, err)
154 })
155 }
156 }