]> git.giacomolli.uk - onechat/blob - whatsapp_batch_test.go
Give the WhatsApp batches to the agent
[onechat] / whatsapp_batch_test.go
1 package main
2
3 import (
4         "os"
5         "path/filepath"
6         "testing"
7         "time"
8
9         "github.com/stretchr/testify/require"
10 )
11
12 func TestWhatsAppBatchesFollowTheTimesOfTheMessages(t *testing.T) {
13         testCases := []struct {
14                 name  string
15                 times []time.Duration
16                 sizes []int
17         }{
18                 {
19                         name:  "messages close together form one batch",
20                         times: []time.Duration{0, 10 * time.Second, 30 * time.Second},
21                         sizes: []int{3},
22                 },
23                 {
24                         name:  "a quiet chat starts a new batch",
25                         times: []time.Duration{0, 10 * time.Second, 31 * time.Second},
26                         sizes: []int{2, 1},
27                 },
28                 {
29                         name:  "a batch ends after two minutes",
30                         times: []time.Duration{0, 20 * time.Second, 40 * time.Second, 60 * time.Second, 80 * time.Second, 100 * time.Second, 120 * time.Second, 140 * time.Second},
31                         sizes: []int{7, 1},
32                 },
33         }
34
35         for _, tc := range testCases {
36                 t.Run(tc.name, func(t *testing.T) {
37                         batches := whatsappTestBatches(t, filepath.Join(t.TempDir(), whatsappStateFileName))
38                         start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
39                         for index, offset := range tc.times {
40                                 _, err := batches.Add(whatsappTestChat, WhatsAppMessage{
41                                         ID:   string(rune('a' + index)),
42                                         Time: start.Add(offset),
43                                         Text: "hi",
44                                 }, time.Now())
45                                 require.NoError(t, err)
46                         }
47
48                         require.NoError(t, batches.CloseAll())
49                         var sizes []int
50                         for _, batch := range batches.Closed() {
51                                 sizes = append(sizes, len(batch.Messages))
52                         }
53
54                         require.Equal(t, tc.sizes, sizes)
55                 })
56         }
57 }
58
59 func TestWhatsAppBatchClosesAtFiftyMessages(t *testing.T) {
60         batches := whatsappTestBatches(t, filepath.Join(t.TempDir(), whatsappStateFileName))
61         start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
62         for index := range whatsappBatchMessages {
63                 closed, err := batches.Add(whatsappTestChat, WhatsAppMessage{
64                         ID:   time.Duration(index).String(),
65                         Time: start,
66                         Text: "hi",
67                 }, time.Now())
68                 require.NoError(t, err)
69                 require.Equal(t, index == whatsappBatchMessages-1, closed)
70         }
71
72         require.Len(t, batches.Closed(), 1)
73 }
74
75 func TestWhatsAppBatchClosesInRealTime(t *testing.T) {
76         batches := whatsappTestBatches(t, filepath.Join(t.TempDir(), whatsappStateFileName))
77         arrival := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
78         for index, offset := range []time.Duration{0, 15 * time.Second, 30 * time.Second} {
79                 _, err := batches.Add(whatsappTestChat, WhatsAppMessage{
80                         ID:   string(rune('a' + index)),
81                         Time: arrival,
82                         Text: "hi",
83                 }, arrival.Add(offset))
84                 require.NoError(t, err)
85         }
86
87         wait, open := batches.Wait(arrival.Add(40 * time.Second))
88         require.True(t, open)
89         require.Equal(t, 10*time.Second, wait)
90
91         closed, err := batches.CloseDue(arrival.Add(49 * time.Second))
92         require.NoError(t, err)
93         require.False(t, closed)
94
95         closed, err = batches.CloseDue(arrival.Add(50 * time.Second))
96         require.NoError(t, err)
97         require.True(t, closed)
98
99         _, open = batches.Wait(arrival.Add(50 * time.Second))
100         require.False(t, open)
101 }
102
103 func TestWhatsAppBatchesSurviveARestart(t *testing.T) {
104         path := filepath.Join(t.TempDir(), whatsappDirName, whatsappStateFileName)
105         batches := whatsappTestBatches(t, path)
106         for _, id := range []string{"a", "a", "b"} {
107                 _, err := batches.Add(whatsappTestChat, WhatsAppMessage{
108                         ID:   id,
109                         Time: time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC),
110                         Text: "hi " + id,
111                 }, time.Now())
112                 require.NoError(t, err)
113         }
114
115         info, err := os.Stat(path)
116         require.NoError(t, err)
117         require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
118
119         again := whatsappTestBatches(t, path)
120         require.NoError(t, again.CloseAll())
121         closed := again.Closed()
122         require.Len(t, closed, 1)
123         require.Equal(t, []string{"hi a", "hi b"}, []string{closed[0].Messages[0].Text, closed[0].Messages[1].Text})
124
125         require.NoError(t, again.Remove(closed[0].Number))
126         require.Empty(t, whatsappTestBatches(t, path).Closed())
127 }
128
129 func TestWhatsAppBatchesRefuseABrokenStateFile(t *testing.T) {
130         path := filepath.Join(t.TempDir(), whatsappStateFileName)
131         require.NoError(t, os.WriteFile(path, []byte("{"), 0o600))
132
133         _, err := openWhatsAppBatches(path, whatsappBatchQuiet, whatsappBatchWindow)
134         require.ErrorContains(t, err, path)
135 }
136
137 func TestWhatsAppBatchText(t *testing.T) {
138         at := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
139         testCases := []struct {
140                 name  string
141                 batch WhatsAppBatch
142                 text  string
143         }{
144                 {
145                         name: "a private chat with a message of another person starts a turn",
146                         batch: WhatsAppBatch{
147                                 Chat: whatsappTestChat,
148                                 Messages: []WhatsAppMessage{
149                                         {
150                                                 Sender: "Ana Souza",
151                                                 Time:   at,
152                                                 Text:   "Are you at home?",
153                                         },
154                                         {
155                                                 Time: at.Add(3 * time.Minute),
156                                                 Text: "Yes",
157                                                 Mine: true,
158                                         },
159                                 },
160                         },
161                         text: "[WhatsApp] Ana Souza, private chat 15550002222@s.whatsapp.net, Thursday 2026-01-01\n12:00 Ana Souza: Are you at home?\n12:03 You: Yes\n" + whatsappBatchEmptyReply,
162                 },
163                 {
164                         name: "a private chat with messages of the user only starts no turn",
165                         batch: WhatsAppBatch{
166                                 Chat: whatsappTestChat,
167                                 Messages: []WhatsAppMessage{
168                                         {
169                                                 Time: at,
170                                                 Text: "On my way.",
171                                                 Mine: true,
172                                         },
173                                 },
174                         },
175                         text: "[WhatsApp] Ana Souza, private chat 15550002222@s.whatsapp.net, Thursday 2026-01-01\n12:00 You: On my way.",
176                 },
177                 {
178                         name: "a group with no mention of the user starts no turn",
179                         batch: WhatsAppBatch{
180                                 Chat: whatsappTestGroup,
181                                 Messages: []WhatsAppMessage{
182                                         {
183                                                 Sender: "Bruno Lima",
184                                                 Time:   at,
185                                                 Text:   "Who brings the cake?",
186                                         },
187                                 },
188                         },
189                         text: "[WhatsApp] Family, group 120363000000000001@g.us, Thursday 2026-01-01\n12:00 Bruno Lima: Who brings the cake?",
190                 },
191                 {
192                         name: "a group with a mention of the user starts a turn",
193                         batch: WhatsAppBatch{
194                                 Chat: whatsappTestGroup,
195                                 Messages: []WhatsAppMessage{
196                                         {
197                                                 Sender:  "Bruno Lima",
198                                                 Time:    at,
199                                                 Text:    "@you can you bring the cake?",
200                                                 Mention: true,
201                                         },
202                                 },
203                         },
204                         text: "[WhatsApp] Family, group 120363000000000001@g.us, Thursday 2026-01-01\n12:00 Bruno Lima (mentions you): @you can you bring the cake?\n" + whatsappBatchEmptyReply,
205                 },
206                 {
207                         name: "a group with a reply to the user starts a turn",
208                         batch: WhatsAppBatch{
209                                 Chat: whatsappTestGroup,
210                                 Messages: []WhatsAppMessage{
211                                         {
212                                                 Sender: "Bruno Lima",
213                                                 Time:   at,
214                                                 Text:   "Thanks!",
215                                                 Reply:  true,
216                                         },
217                                 },
218                         },
219                         text: "[WhatsApp] Family, group 120363000000000001@g.us, Thursday 2026-01-01\n12:00 Bruno Lima (replies to you): Thanks!\n" + whatsappBatchEmptyReply,
220                 },
221         }
222
223         for _, tc := range testCases {
224                 t.Run(tc.name, func(t *testing.T) {
225                         require.Equal(t, tc.text, tc.batch.Text(time.UTC))
226                 })
227         }
228 }
229
230 // whatsappTestBatches opens the batches of the state file at path with the
231 // times of the plan.
232 func whatsappTestBatches(t *testing.T, path string) *WhatsAppBatches {
233         t.Helper()
234
235         batches, err := openWhatsAppBatches(path, whatsappBatchQuiet, whatsappBatchWindow)
236         require.NoError(t, err)
237
238         return batches
239 }