]> git.giacomolli.uk - onechat/blob - routes_test.go
Import WhatsApp history into a child
[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 }