]> git.giacomolli.uk - onechat/blob - whatsapp_test.go
Give the WhatsApp batches to the agent
[onechat] / whatsapp_test.go
1 package main
2
3 import (
4         "errors"
5         "net/http"
6         "os"
7         "path/filepath"
8         "strings"
9         "testing"
10         "time"
11
12         "github.com/stretchr/testify/require"
13         "go.mau.fi/whatsmeow"
14         "go.mau.fi/whatsmeow/proto/waE2E"
15         "go.mau.fi/whatsmeow/proto/waWeb"
16         "go.mau.fi/whatsmeow/types"
17         "go.mau.fi/whatsmeow/types/events"
18 )
19
20 func TestWhatsAppStatusFollowsTheConnection(t *testing.T) {
21         dir := t.TempDir()
22         whatsappTestSession(t, dir, true)
23         clock := newTestClock()
24         whatsapp, fake, _ := whatsappStartTest(t, dir, clock)
25         daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
26
27         <-fake.connects
28         require.Equal(t, "WhatsApp is linked and not connected yet.", whatsapp.Status())
29
30         require.True(t, fake.Emit(&events.Connected{}))
31         require.Equal(t, "WhatsApp is linked and connected since 2026-01-01 12:00.", whatsapp.Status())
32
33         clock.now = clock.now.Add(20 * time.Minute)
34         require.True(t, fake.Emit(&events.Disconnected{}))
35         clock.now = clock.now.Add(time.Minute)
36         require.True(t, fake.Emit(&events.Disconnected{}))
37         require.Equal(t, "WhatsApp is linked and disconnected since 2026-01-01 12:20. The last connection started at 2026-01-01 12:00.", whatsapp.Status())
38 }
39
40 func TestWhatsAppStatusOfAFirstConnectionThatFailed(t *testing.T) {
41         dir := t.TempDir()
42         whatsappTestSession(t, dir, true)
43         whatsapp, fake, _ := whatsappStartTest(t, dir, newTestClock())
44         daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
45
46         <-fake.connects
47         require.True(t, fake.Emit(&events.Disconnected{}))
48         require.Equal(t, "WhatsApp is linked and disconnected since 2026-01-01 12:00.", whatsapp.Status())
49 }
50
51 func TestWhatsAppIsNotLinkedWithoutALinkedDevice(t *testing.T) {
52         testCases := []struct {
53                 name    string
54                 session func(t *testing.T, dir string)
55         }{
56                 {
57                         name: "no session file",
58                         session: func(t *testing.T, dir string) {
59                         },
60                 },
61                 {
62                         name: "a session of a pairing that did not end",
63                         session: func(t *testing.T, dir string) {
64                                 whatsappTestSession(t, dir, false)
65                         },
66                 },
67         }
68
69         for _, tc := range testCases {
70                 t.Run(tc.name, func(t *testing.T) {
71                         dir := t.TempDir()
72                         tc.session(t, dir)
73                         whatsapp, _, _ := whatsappStartTest(t, dir, newTestClock())
74                         require.Equal(t, "WhatsApp is not linked. Run /whatsapp pair PHONE to link it.", whatsapp.Status())
75                 })
76         }
77 }
78
79 func TestWhatsAppRefusesASessionThatOthersCanRead(t *testing.T) {
80         dir := t.TempDir()
81         whatsappTestSession(t, dir, true)
82         require.NoError(t, os.Chmod(filepath.Join(dir, whatsappDirName, whatsappSessionFileName), 0o644))
83
84         _, err := openWhatsApp(t.Context(), dir, time.Now, whatsappDiscardLog(), whatsappNewClient)
85         require.EqualError(t, err, "whatsapp/session.db holds the WhatsApp session, so its mode must be 0600")
86 }
87
88 func TestWhatsAppCommandsAnswerOnlyTheClientThatAsked(t *testing.T) {
89         server := newModelFake(t)
90         telegram := newTelegramFake(t)
91         bot, _ := telegramStartTestBot(t, telegram)
92         socket := daemonStartTestWithBot(t, server, bot)
93         asking := terminalStartTestClient(t, socket)
94         other := terminalStartTestClient(t, socket)
95         asking.WaitFor(t, "[connected to OneChat]")
96         other.WaitFor(t, "[connected to OneChat]")
97
98         asking.Type("/whatsapp status")
99         asking.WaitFor(t, "WhatsApp is not linked.")
100         asking.Type(" /whatsapp  unknown ")
101         asking.WaitFor(t, whatsappUsage)
102         asking.Type("/whatsapp pair 0123")
103         asking.WaitFor(t, "PHONE must be the number of your WhatsApp account")
104
105         telegram.Add(TelegramUpdate{
106                 UpdateID: 1,
107                 Message:  telegramOwnerMessage("/whatsapp status"),
108         })
109
110         telegramWaitForSent(t, telegram, "WhatsApp is not linked. Run /whatsapp pair PHONE to link it.")
111         require.Len(t, telegram.Sent(), 1)
112         require.Equal(t, "[connected to OneChat]\n", other.Output())
113         require.Zero(t, server.Calls())
114 }
115
116 func TestWhatsAppPairsWithACode(t *testing.T) {
117         dir := t.TempDir()
118         whatsapp, fake, _ := whatsappStartTest(t, dir, newTestClock())
119         server := newModelFake(t)
120         socket, memory := daemonStartTestWithWhatsApp(t, server, nil, whatsapp)
121         asking := terminalStartTestClient(t, socket)
122         other := terminalStartTestClient(t, socket)
123         asking.WaitFor(t, "[connected to OneChat]")
124         other.WaitFor(t, "[connected to OneChat]")
125
126         asking.Type("/whatsapp pair +1 555-000-1111")
127         items := <-fake.pairings
128         items <- whatsmeow.QRChannelItem{
129                 Event: whatsmeow.QRChannelEventCode,
130         }
131
132         asking.WaitFor(t, whatsappPairSteps+"\n"+whatsappTestCode+"\n")
133         require.True(t, fake.Emit(&events.PairSuccess{
134                 ID:  types.NewADJID(whatsappTestNumber, 0, 1),
135                 LID: types.NewADJID(whatsappTestLID, 0, 1),
136         }))
137         items <- whatsmeow.QRChannelSuccess
138         asking.WaitFor(t, "WhatsApp is linked to +15550001111.")
139
140         require.Equal(t, []string{whatsappTestNumber}, fake.Phones())
141         require.Equal(t, "WhatsApp is linked and not connected yet.", whatsapp.Status())
142         require.Equal(t, "[connected to OneChat]\n", other.Output())
143         require.Zero(t, server.Calls())
144         require.Equal(t, []string{"WhatsApp is linked."}, whatsappTestLogTexts(t, memory))
145
146         info, err := os.Stat(filepath.Join(dir, whatsappDirName))
147         require.NoError(t, err)
148         require.Equal(t, os.FileMode(0o700), info.Mode().Perm())
149
150         info, err = os.Stat(filepath.Join(dir, whatsappDirName, whatsappSessionFileName))
151         require.NoError(t, err)
152         require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
153 }
154
155 func TestWhatsAppPairingCodeIsATelegramMessageAlone(t *testing.T) {
156         whatsapp, fake, _ := whatsappStartTest(t, t.TempDir(), newTestClock())
157         telegram := newTelegramFake(t)
158         bot, _ := telegramStartTestBot(t, telegram)
159         daemonStartTestWithWhatsApp(t, newModelFake(t), bot, whatsapp)
160
161         telegram.Add(TelegramUpdate{
162                 UpdateID: 1,
163                 Message:  telegramOwnerMessage("/whatsapp pair 15550001111"),
164         })
165
166         items := <-fake.pairings
167         items <- whatsmeow.QRChannelItem{
168                 Event: whatsmeow.QRChannelEventCode,
169         }
170
171         telegramWaitForSent(t, telegram, whatsappPairSteps, whatsappTestCode)
172 }
173
174 func TestWhatsAppPairingEnds(t *testing.T) {
175         testCases := []struct {
176                 name   string
177                 end    func(items chan whatsmeow.QRChannelItem)
178                 answer string
179         }{
180                 {
181                         name: "the code expires",
182                         end: func(items chan whatsmeow.QRChannelItem) {
183                                 items <- whatsmeow.QRChannelTimeout
184                         },
185                         answer: whatsappPairExpired,
186                 },
187                 {
188                         name: "the library closes the pairing",
189                         end: func(items chan whatsmeow.QRChannelItem) {
190                                 close(items)
191                         },
192                         answer: whatsappPairExpired,
193                 },
194                 {
195                         name: "the pairing fails",
196                         end: func(items chan whatsmeow.QRChannelItem) {
197                                 items <- whatsmeow.QRChannelItem{
198                                         Event: whatsmeow.QRChannelEventError,
199                                         Error: errors.New("bad handshake"),
200                                 }
201                         },
202                         answer: "The pairing failed: bad handshake. Run /whatsapp pair PHONE again.",
203                 },
204         }
205
206         for _, tc := range testCases {
207                 t.Run(tc.name, func(t *testing.T) {
208                         whatsapp, fake, _ := whatsappStartTest(t, t.TempDir(), newTestClock())
209                         socket, _ := daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
210                         client := terminalStartTestClient(t, socket)
211                         client.Type("/whatsapp pair 15550001111")
212
213                         items := <-fake.pairings
214                         items <- whatsmeow.QRChannelItem{
215                                 Event: whatsmeow.QRChannelEventCode,
216                         }
217
218                         client.WaitFor(t, whatsappTestCode)
219                         tc.end(items)
220                         client.WaitFor(t, tc.answer)
221                         require.Equal(t, "WhatsApp is not linked. Run /whatsapp pair PHONE to link it.", whatsapp.Status())
222                 })
223         }
224 }
225
226 func TestWhatsAppNewPairingEndsTheOneBefore(t *testing.T) {
227         whatsapp, fake, _ := whatsappStartTest(t, t.TempDir(), newTestClock())
228         socket, _ := daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
229         client := terminalStartTestClient(t, socket)
230
231         client.Type("/whatsapp pair 15550001111")
232         <-fake.pairings
233         client.Type("/whatsapp pair 15550002222")
234         items := <-fake.pairings
235         items <- whatsmeow.QRChannelItem{
236                 Event: whatsmeow.QRChannelEventCode,
237         }
238
239         client.WaitFor(t, whatsappTestCode)
240         require.Equal(t, []string{"15550002222"}, fake.Phones())
241         require.NotContains(t, client.Output(), "did not start the pairing")
242 }
243
244 func TestWhatsAppPairWhenLinked(t *testing.T) {
245         dir := t.TempDir()
246         whatsappTestSession(t, dir, true)
247         whatsapp, _, _ := whatsappStartTest(t, dir, newTestClock())
248         socket, _ := daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
249         client := terminalStartTestClient(t, socket)
250
251         client.Type("/whatsapp pair 15550001111")
252         client.WaitFor(t, "WhatsApp is linked already. Run /whatsapp unlink first.")
253 }
254
255 func TestWhatsAppUnlink(t *testing.T) {
256         testCases := []struct {
257                 name      string
258                 logoutErr error
259                 answer    string
260         }{
261                 {
262                         name:   "with a logout",
263                         answer: "WhatsApp is unlinked.",
264                 },
265                 {
266                         name:      "with no logout",
267                         logoutErr: errors.New("not connected"),
268                         answer:    "WhatsApp is unlinked here, but the phone did not get the logout. In WhatsApp on your phone, open Linked devices and remove the device.",
269                 },
270         }
271
272         for _, tc := range testCases {
273                 t.Run(tc.name, func(t *testing.T) {
274                         dir := t.TempDir()
275                         whatsappTestSession(t, dir, true)
276                         whatsapp, fake, _ := whatsappStartTest(t, dir, newTestClock())
277                         fake.logoutErr = tc.logoutErr
278                         socket, memory := daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
279                         client := terminalStartTestClient(t, socket)
280                         <-fake.connects
281
282                         client.Type("/whatsapp unlink")
283                         client.WaitFor(t, tc.answer)
284                         require.Equal(t, 1, fake.Logouts())
285                         require.NoFileExists(t, filepath.Join(dir, whatsappDirName, whatsappSessionFileName))
286                         require.Equal(t, "WhatsApp is not linked. Run /whatsapp pair PHONE to link it.", whatsapp.Status())
287                         require.Equal(t, []string{"WhatsApp is unlinked."}, whatsappTestLogTexts(t, memory))
288
289                         client.Type("/whatsapp unlink")
290                         client.WaitFor(t, "WhatsApp is not linked.\n")
291                 })
292         }
293 }
294
295 func TestWhatsAppLogoutFromThePhone(t *testing.T) {
296         dir := t.TempDir()
297         whatsappTestSession(t, dir, true)
298         whatsapp, fake, _ := whatsappStartTest(t, dir, newTestClock())
299         telegram := newTelegramFake(t)
300         bot, _ := telegramStartTestBot(t, telegram)
301         socket, memory := daemonStartTestWithWhatsApp(t, newModelFake(t), bot, whatsapp)
302         client := terminalStartTestClient(t, socket)
303         client.WaitFor(t, "[connected to OneChat]")
304         <-fake.connects
305
306         require.True(t, fake.Emit(&events.LoggedOut{}))
307         notice := "WhatsApp was unlinked from the phone. Run /whatsapp pair PHONE to link it again."
308         client.WaitFor(t, notice)
309         telegramWaitForSent(t, telegram, notice)
310         require.NoFileExists(t, filepath.Join(dir, whatsappDirName, whatsappSessionFileName))
311         require.Equal(t, "WhatsApp is not linked. Run /whatsapp pair PHONE to link it.", whatsapp.Status())
312         require.Equal(t, []string{"WhatsApp was unlinked from the phone."}, whatsappTestLogTexts(t, memory))
313 }
314
315 func TestWhatsAppLongDisconnectionGivesOneNotice(t *testing.T) {
316         dir := t.TempDir()
317         whatsappTestSession(t, dir, true)
318         clock := newTestClock()
319         whatsapp, fake, _ := whatsappStartTest(t, dir, clock)
320         whatsapp.noticeDelay = 50 * time.Millisecond
321         socket, _ := daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
322         client := terminalStartTestClient(t, socket)
323         client.WaitFor(t, "[connected to OneChat]")
324         <-fake.connects
325
326         require.True(t, fake.Emit(&events.Connected{}))
327         require.True(t, fake.Emit(&events.Disconnected{}))
328         require.True(t, fake.Emit(&events.Connected{}))
329         time.Sleep(100 * time.Millisecond)
330         require.Equal(t, "[connected to OneChat]\n", client.Output())
331
332         clock.now = clock.now.Add(time.Hour)
333         require.True(t, fake.Emit(&events.Disconnected{}))
334         require.True(t, fake.Emit(&events.Disconnected{}))
335         client.WaitFor(t, "WhatsApp is disconnected since 2026-01-01 13:00. OneChat tries again.\n")
336         require.True(t, fake.Emit(&events.Disconnected{}))
337         time.Sleep(100 * time.Millisecond)
338
339         require.True(t, fake.Emit(&events.Connected{}))
340         client.WaitFor(t, "WhatsApp is connected again.\n")
341         require.Equal(t, 1, strings.Count(client.Output(), "WhatsApp is disconnected since"))
342 }
343
344 func TestWhatsAppDisconnectionWithNoRetryGivesANoticeAtOnce(t *testing.T) {
345         dir := t.TempDir()
346         whatsappTestSession(t, dir, true)
347         whatsapp, fake, _ := whatsappStartTest(t, dir, newTestClock())
348         socket, _ := daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
349         client := terminalStartTestClient(t, socket)
350         client.WaitFor(t, "[connected to OneChat]")
351         <-fake.connects
352
353         require.True(t, fake.Emit(&events.StreamReplaced{}))
354         client.WaitFor(t, "WhatsApp is disconnected and does not connect again by itself: stream replaced. Restart the daemon to try again.")
355 }
356
357 func TestWhatsAppLogsNoOwnNumber(t *testing.T) {
358         dir := t.TempDir()
359         whatsappTestSession(t, dir, true)
360         whatsapp, fake, logs := whatsappStartTest(t, dir, newTestClock())
361         fake.connectErr = errors.New("no route for " + whatsappTestNumber + "@s.whatsapp.net and " + whatsappTestLID + "@lid")
362         daemonStartTestWithWhatsApp(t, newModelFake(t), nil, whatsapp)
363         <-fake.connects
364
365         require.Eventually(t, func() bool {
366                 return logs.String() != ""
367         }, 10*time.Second, time.Millisecond)
368         require.Equal(t, "connect: no route for OWN@s.whatsapp.net and OWN@lid\n", logs.String())
369 }
370
371 func TestWhatsAppPhone(t *testing.T) {
372         testCases := []struct {
373                 name  string
374                 phone string
375                 valid bool
376         }{
377                 {
378                         name:  "a number with a plus and dashes",
379                         phone: "+1-555-000-1111",
380                         valid: true,
381                 },
382                 {
383                         name:  "a number with no country code",
384                         phone: "0115550001111",
385                         valid: false,
386                 },
387                 {
388                         name:  "a short number",
389                         phone: "123456",
390                         valid: false,
391                 },
392                 {
393                         name:  "a long number",
394                         phone: "1234567890123456",
395                         valid: false,
396                 },
397                 {
398                         name:  "a number with letters",
399                         phone: "1555ABC1111",
400                         valid: false,
401                 },
402         }
403
404         for _, tc := range testCases {
405                 t.Run(tc.name, func(t *testing.T) {
406                         _, valid := whatsappPhone(tc.phone)
407                         require.Equal(t, tc.valid, valid)
408                 })
409         }
410 }
411
412 func TestWhatsAppCommandArgs(t *testing.T) {
413         testCases := []struct {
414                 name  string
415                 line  string
416                 args  string
417                 found bool
418         }{
419                 {
420                         name:  "a command with its arguments",
421                         line:  " /whatsapp  pair +1 555 ",
422                         args:  "pair +1 555",
423                         found: true,
424                 },
425                 {
426                         name:  "the command alone",
427                         line:  "/whatsapp",
428                         args:  "",
429                         found: true,
430                 },
431                 {
432                         name:  "a word that starts with the command",
433                         line:  "/whatsappstatus",
434                         args:  "",
435                         found: false,
436                 },
437                 {
438                         name:  "a message that names the command",
439                         line:  "what does /whatsapp status do?",
440                         args:  "",
441                         found: false,
442                 },
443         }
444
445         for _, tc := range testCases {
446                 t.Run(tc.name, func(t *testing.T) {
447                         args, found := whatsappCommandArgs(tc.line)
448                         require.Equal(t, tc.args, args)
449                         require.Equal(t, tc.found, found)
450                 })
451         }
452 }
453
454 // whatsappTestLogTexts returns the texts of the messages in the log of memory,
455 // after the log holds at least one message.
456 func whatsappTestLogTexts(t *testing.T, memory *Memory) []string {
457         t.Helper()
458
459         require.Eventually(t, func() bool {
460                 return memory.Len() > 0
461         }, 10*time.Second, time.Millisecond)
462
463         var texts []string
464         for id := range memory.Len() {
465                 message, err := memory.Message(id)
466                 require.NoError(t, err)
467
468                 texts = append(texts, message.Text)
469         }
470
471         return texts
472 }
473
474 func TestWhatsAppPrivateBatchStartsATurnWithAnEmptyReply(t *testing.T) {
475         whatsapp, fake := whatsappStartTestLinked(t)
476         require.NoError(t, whatsapp.device.Contacts.PutContactName(t.Context(), whatsappTestAna, "Ana", "Ana Souza"))
477
478         server := newModelFake(t)
479         server.reply = func(w http.ResponseWriter, call ModelFakeCall) {
480                 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
481         }
482
483         telegram := newTelegramFake(t)
484         bot, _ := telegramStartTestBot(t, telegram)
485         socket, memory := daemonStartTestWithWhatsApp(t, server, bot, whatsapp)
486         client := terminalStartTestClient(t, socket)
487         client.WaitFor(t, "[connected to OneChat]")
488         <-fake.connects
489
490         at := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
491         require.True(t, fake.Emit(whatsappTestMessage(whatsappTestAna, whatsappTestAna, "m1", "Are you at home?", at)))
492         require.True(t, fake.Emit(whatsappTestMessage(whatsappTestAna, whatsappTestOwn, "m2", "Yes", at)))
493
494         text := "[WhatsApp] Ana Souza, private chat 15550002222@s.whatsapp.net, Thursday 2026-01-01\n12:00 Ana Souza: Are you at home?\n12:00 You: Yes\n" + whatsappBatchEmptyReply
495         client.WaitFor(t, text+"\n[working]\n[done]\n")
496         require.Equal(t, 1, server.Calls())
497         require.Contains(t, string(server.Call(0).raw), "Are you at home?")
498         require.Equal(t, []string{text}, whatsappTestLogTexts(t, memory))
499         require.Empty(t, telegram.Sent())
500         require.Eventually(t, func() bool {
501                 return len(whatsapp.batches.Closed()) == 0
502         }, 10*time.Second, time.Millisecond)
503 }
504
505 func TestWhatsAppGroupTurns(t *testing.T) {
506         testCases := []struct {
507                 name    string
508                 message func(event *events.Message)
509                 line    string
510                 turn    bool
511         }{
512                 {
513                         name: "plain",
514                         message: func(event *events.Message) {
515                         },
516                         line: "12:00 Bruno Lima: Who brings the cake?",
517                 },
518                 {
519                         name: "mention",
520                         message: func(event *events.Message) {
521                                 event.Message = &waE2E.Message{
522                                         ExtendedTextMessage: &waE2E.ExtendedTextMessage{
523                                                 Text: new("@" + whatsappTestLID + " can you bring the cake? Call " + whatsappTestNumber + "."),
524                                                 ContextInfo: &waE2E.ContextInfo{
525                                                         MentionedJID: []string{whatsappTestLID + "@lid"},
526                                                 },
527                                         },
528                                 }
529                         },
530                         line: "12:00 Bruno Lima (mentions you): @you can you bring the cake? Call OWN.",
531                         turn: true,
532                 },
533                 {
534                         name: "reply",
535                         message: func(event *events.Message) {
536                                 event.Message = &waE2E.Message{
537                                         ExtendedTextMessage: &waE2E.ExtendedTextMessage{
538                                                 Text: new("Who brings the cake?"),
539                                                 ContextInfo: &waE2E.ContextInfo{
540                                                         Participant: new(whatsappTestOwn.String()),
541                                                 },
542                                         },
543                                 }
544                         },
545                         line: "12:00 Bruno Lima (replies to you): Who brings the cake?",
546                         turn: true,
547                 },
548         }
549
550         for _, tc := range testCases {
551                 t.Run(tc.name, func(t *testing.T) {
552                         whatsapp, fake := whatsappStartTestLinked(t)
553                         fake.groups = []*types.GroupInfo{
554                                 {
555                                         JID: whatsappTestFamily,
556                                         GroupName: types.GroupName{
557                                                 Name: "Family",
558                                         },
559                                 },
560                         }
561
562                         server := newModelFake(t)
563                         socket, memory := daemonStartTestWithWhatsApp(t, server, nil, whatsapp)
564                         client := terminalStartTestClient(t, socket)
565                         client.WaitFor(t, "[connected to OneChat]")
566                         <-fake.connects
567
568                         event := whatsappTestMessage(whatsappTestFamily, whatsappTestBruno, "m1", "Who brings the cake?", time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC))
569                         event.Info.PushName = "Bruno Lima"
570                         tc.message(event)
571                         require.True(t, fake.Emit(event))
572
573                         text := "[WhatsApp] Family, group 120363000000000001@g.us, Thursday 2026-01-01\n" + tc.line
574                         if tc.turn {
575                                 text += "\n" + whatsappBatchEmptyReply
576                                 client.WaitFor(t, "[done]")
577                                 require.Equal(t, 1, server.Calls())
578                         } else {
579                                 client.WaitFor(t, text)
580                                 time.Sleep(100 * time.Millisecond)
581                                 require.Zero(t, server.Calls())
582                         }
583
584                         require.Equal(t, text, whatsappTestLogTexts(t, memory)[0])
585                 })
586         }
587 }
588
589 func TestWhatsAppTakesOnlyText(t *testing.T) {
590         at := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
591         testCases := []struct {
592                 name   string
593                 change func(event *events.Message)
594         }{
595                 {
596                         name: "an edit",
597                         change: func(event *events.Message) {
598                                 event.IsEdit = true
599                         },
600                 },
601                 {
602                         name: "old history",
603                         change: func(event *events.Message) {
604                                 event.SourceWebMsg = &waWeb.WebMessageInfo{}
605                         },
606                 },
607                 {
608                         name: "a view-once message",
609                         change: func(event *events.Message) {
610                                 event.IsViewOnce = true
611                         },
612                 },
613                 {
614                         name: "a disappearing message",
615                         change: func(event *events.Message) {
616                                 event.IsEphemeral = true
617                         },
618                 },
619                 {
620                         name: "the chat of the user with itself",
621                         change: func(event *events.Message) {
622                                 event.Info.Chat = whatsappTestOwn
623                         },
624                 },
625                 {
626                         name: "a status broadcast",
627                         change: func(event *events.Message) {
628                                 event.Info.Chat = types.StatusBroadcastJID
629                         },
630                 },
631                 {
632                         name: "a photo with a caption",
633                         change: func(event *events.Message) {
634                                 event.Message = &waE2E.Message{
635                                         ImageMessage: &waE2E.ImageMessage{
636                                                 Caption: new("look"),
637                                         },
638                                 }
639                         },
640                 },
641                 {
642                         name: "a text of spaces",
643                         change: func(event *events.Message) {
644                                 event.Message.Conversation = new("  ")
645                         },
646                 },
647         }
648
649         for _, tc := range testCases {
650                 t.Run(tc.name, func(t *testing.T) {
651                         whatsapp, _ := whatsappStartTestLinked(t)
652                         event := whatsappTestMessage(whatsappTestAna, whatsappTestAna, "m1", "hi", at)
653                         tc.change(event)
654                         require.True(t, whatsapp.handle(event))
655                         require.NoError(t, whatsapp.batches.CloseAll())
656                         require.Empty(t, whatsapp.batches.Closed())
657                 })
658         }
659 }
660
661 // whatsappStartTestLinked opens a WhatsApp with a linked device in a temporary
662 // directory, with a quiet time of 50 ms for its batches.
663 func whatsappStartTestLinked(t *testing.T) (*WhatsApp, *WhatsAppFake) {
664         t.Helper()
665
666         dir := t.TempDir()
667         whatsappTestSession(t, dir, true)
668         whatsapp, fake, _ := whatsappStartTest(t, dir, newTestClock())
669         whatsapp.batches.quiet = 50 * time.Millisecond
670
671         return whatsapp, fake
672 }