]> git.giacomolli.uk - onechat/blob - routes_test.go
Separate multiline Go statements
[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
92 for _, tc := range testCases {
93 t.Run(tc.name, func(t *testing.T) {
94 arguments := `{"source":"` + tc.source + `","agent":"` + tc.agent + `"}`
95 require.Contains(t, test.loops[0].tools.run(t.Context(), "route", arguments), tc.cause)
96 })
97 }
98
99 require.Contains(t, test.loops[0].tools.run(t.Context(), "unroute", `{"source":"mail:inbox"}`), "unknown source kind")
100 require.Equal(t, "No routes.", test.messages.routes.List())
101 }
102
103 func TestRouteToolsBelongOnlyToRoot(t *testing.T) {
104 test := newDaemonMessageTest(t, t.TempDir(), newModelFake(t))
105 for _, name := range []string{"route", "unroute", "routes"} {
106 require.Contains(t, promptToolNames(test.agents[0].agent.Tools()), name)
107 require.NotContains(t, promptToolNames(test.agents[1].agent.Tools()), name)
108 require.NotContains(t, promptToolNames(promptSubagentTools()), name)
109 require.Contains(t, test.loops[1].tools.run(t.Context(), name, `{}`), "unknown tool")
110 }
111
112 require.NotContains(t, promptChild("Garden", "OneChat", "Keep plans.", "/CHILD/instructions.md", ""), "Propose routing a source")
113 }
114
115 func TestWhatsAppStatusListsRoutes(t *testing.T) {
116 whatsapp, _, _ := whatsappStartTest(t, t.TempDir(), newTestClock())
117 test := newDaemonMessageTest(t, t.TempDir(), newModelFake(t))
118 whatsapp.routes = test.messages.routes
119 require.NoError(t, test.messages.routes.Set("whatsapp:"+whatsappTestFamily.String(), "Garden"))
120
121 var answer string
122
123 whatsapp.Command("status", func(text string) {
124 answer = text
125 })
126
127 require.Contains(t, answer, "WhatsApp is not linked.")
128 require.Contains(t, answer, "WhatsApp routes:\nwhatsapp:"+whatsappTestFamily.String()+" -> Garden")
129 }
130
131 func TestInvalidRouteFileStopsStartup(t *testing.T) {
132 testCases := []struct {
133 name string
134 text string
135 }{
136 {
137 name: "invalid JSON",
138 text: "{",
139 },
140 {
141 name: "unknown source",
142 text: `{"mail:inbox":"Garden"}`,
143 },
144 {
145 name: "empty destination",
146 text: `{"whatsapp:example@lid":""}`,
147 },
148 }
149
150 for _, tc := range testCases {
151 t.Run(tc.name, func(t *testing.T) {
152 path := filepath.Join(t.TempDir(), "routes.json")
153 require.NoError(t, os.WriteFile(path, []byte(tc.text), 0o600))
154
155 _, err := openRoutes(path, discardLog())
156 require.Error(t, err)
157 })
158 }
159 }