9 "github.com/stretchr/testify/require"
12 func TestWhatsAppBatchesFollowTheTimesOfTheMessages(t *testing.T) {
13 testCases := []struct {
19 name: "messages close together form one batch",
20 times: []time.Duration{0, 10 * time.Second, 30 * time.Second},
24 name: "a quiet chat starts a new batch",
25 times: []time.Duration{0, 10 * time.Second, 31 * time.Second},
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},
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),
45 require.NoError(t, err)
48 require.NoError(t, batches.CloseAll())
50 for _, batch := range batches.Closed() {
51 sizes = append(sizes, len(batch.Messages))
54 require.Equal(t, tc.sizes, sizes)
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(),
68 require.NoError(t, err)
69 require.Equal(t, index == whatsappBatchMessages-1, closed)
72 require.Len(t, batches.Closed(), 1)
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)),
83 }, arrival.Add(offset))
84 require.NoError(t, err)
87 wait, open := batches.Wait(arrival.Add(40 * time.Second))
89 require.Equal(t, 10*time.Second, wait)
91 closed, err := batches.CloseDue(arrival.Add(49 * time.Second))
92 require.NoError(t, err)
93 require.False(t, closed)
95 closed, err = batches.CloseDue(arrival.Add(50 * time.Second))
96 require.NoError(t, err)
97 require.True(t, closed)
99 _, open = batches.Wait(arrival.Add(50 * time.Second))
100 require.False(t, open)
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{
109 Time: time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC),
112 require.NoError(t, err)
115 info, err := os.Stat(path)
116 require.NoError(t, err)
117 require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
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})
125 require.NoError(t, again.Remove(closed[0].Number))
126 require.Empty(t, whatsappTestBatches(t, path).Closed())
129 func TestWhatsAppBatchesRefuseABrokenStateFile(t *testing.T) {
130 path := filepath.Join(t.TempDir(), whatsappStateFileName)
131 require.NoError(t, os.WriteFile(path, []byte("{"), 0o600))
133 _, err := openWhatsAppBatches(path, whatsappBatchQuiet, whatsappBatchWindow)
134 require.ErrorContains(t, err, path)
137 func TestWhatsAppBatchText(t *testing.T) {
138 at := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
139 testCases := []struct {
145 name: "a private chat with a message of another person starts a turn",
146 batch: WhatsAppBatch{
147 Chat: whatsappTestChat,
148 Messages: []WhatsAppMessage{
152 Text: "Are you at home?",
155 Time: at.Add(3 * time.Minute),
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,
164 name: "a private chat with messages of the user only starts no turn",
165 batch: WhatsAppBatch{
166 Chat: whatsappTestChat,
167 Messages: []WhatsAppMessage{
175 text: "[WhatsApp] Ana Souza, private chat 15550002222@s.whatsapp.net, Thursday 2026-01-01\n12:00 You: On my way.",
178 name: "a group with no mention of the user starts no turn",
179 batch: WhatsAppBatch{
180 Chat: whatsappTestGroup,
181 Messages: []WhatsAppMessage{
183 Sender: "Bruno Lima",
185 Text: "Who brings the cake?",
189 text: "[WhatsApp] Family, group 120363000000000001@g.us, Thursday 2026-01-01\n12:00 Bruno Lima: Who brings the cake?",
192 name: "a group with a mention of the user starts a turn",
193 batch: WhatsAppBatch{
194 Chat: whatsappTestGroup,
195 Messages: []WhatsAppMessage{
197 Sender: "Bruno Lima",
199 Text: "@you can you bring the cake?",
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,
207 name: "a group with a reply to the user starts a turn",
208 batch: WhatsAppBatch{
209 Chat: whatsappTestGroup,
210 Messages: []WhatsAppMessage{
212 Sender: "Bruno Lima",
219 text: "[WhatsApp] Family, group 120363000000000001@g.us, Thursday 2026-01-01\n12:00 Bruno Lima (replies to you): Thanks!\n" + whatsappBatchEmptyReply,
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))
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 {
235 batches, err := openWhatsAppBatches(path, whatsappBatchQuiet, whatsappBatchWindow)
236 require.NoError(t, err)