12 "github.com/stretchr/testify/require"
13 "go.mau.fi/whatsmeow/proto/waE2E"
14 "go.mau.fi/whatsmeow/types"
15 "go.mau.fi/whatsmeow/types/events"
18 func TestWhatsAppVoiceNotesAreDurableBeforeAcknowledgement(t *testing.T) {
19 whatsapp, fake := whatsappStartTestLinked(t)
20 whatsapp.client = fake
21 _, whatsapp.inbox.voice = voiceTestInput(t, voiceTestScript)
22 audio := []byte("made-up WhatsApp voice")
23 fake.downloadData = audio
24 event := whatsappVoiceTestEvent(whatsappTestAna)
25 require.True(t, whatsapp.handle(event))
26 require.NoError(t, whatsapp.state.CloseAll())
28 restored := whatsappTestState(t, whatsapp.state.path)
29 require.Len(t, restored.Closed(), 1)
31 text := restored.Closed()[0].Messages[0].Text
32 require.Contains(t, text, "[voice ")
33 require.Contains(t, text, "A made-up voice transcript.")
35 files, err := os.ReadDir(whatsapp.inbox.voices.dir)
36 require.NoError(t, err)
37 require.Len(t, files, 1)
39 stored, err := os.ReadFile(filepath.Join(whatsapp.inbox.voices.dir, files[0].Name()))
40 require.NoError(t, err)
41 require.Equal(t, audio, stored)
42 require.True(t, whatsapp.handle(event))
43 require.NoError(t, whatsapp.state.CloseAll())
44 require.Len(t, whatsapp.state.Closed()[0].Messages, 1)
47 func TestWhatsAppVoiceRoutingSurvivesRestartWithoutDuplicateEntries(t *testing.T) {
49 server := newModelFake(t)
50 server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
51 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
54 first := newDaemonMessageTest(t, dir, server)
55 whatsappTestSession(t, dir, true)
57 whatsapp, fake, logs := whatsappStartTest(t, dir, newTestClock())
58 whatsapp.client = fake
59 _, whatsapp.inbox.voice = voiceTestInput(t, voiceTestScript)
60 fake.downloadData = []byte("made-up routed voice")
61 event := whatsappVoiceTestEvent(whatsappTestAna)
62 event.Info.Chat = whatsappTestFamily
63 event.Info.IsGroup = true
65 whatsapp.groups.set(whatsappTestFamily.String(), "Garden plans")
66 require.NoError(t, first.messages.routes.Set("whatsapp:"+whatsappTestFamily.String(), "Garden"))
67 require.True(t, whatsapp.handle(event))
68 require.NoError(t, whatsapp.state.CloseAll())
70 batch := whatsapp.state.Closed()[0]
71 text := batch.Text(time.UTC)
72 routed, err := first.messages.RouteWhatsApp(batch, text)
73 require.NoError(t, err)
74 require.True(t, routed)
77 second := newDaemonMessageTest(t, dir, server)
78 restored := whatsappRouteTestInbox(t, dir)
79 require.NoError(t, restored.deliver(t.Context(), second.loops[0], WhatsAppLink{}, true))
80 require.Empty(t, restored.state.Closed())
81 require.Zero(t, second.agents[0].memory.Len())
82 require.Equal(t, 1, second.agents[1].memory.Len())
84 message, err := second.agents[1].memory.Message(0)
85 require.NoError(t, err)
86 require.Equal(t, text, message.Text)
87 require.Contains(t, text, "[voice ")
88 require.Contains(t, text, "A made-up voice transcript.")
89 require.Equal(t, whatsapp.inbox.voices.dir, second.agents[1].agent.Voices().dir)
91 second.events[1].WaitFor(t, "idle", 1)
92 require.Contains(t, string(server.Call(0).raw), "A made-up voice transcript.")
93 require.NotContains(t, logs.String()+second.logs.String(), "A made-up voice transcript")
96 third := newDaemonMessageTest(t, dir, server)
97 require.Equal(t, 1, third.agents[1].memory.Len())
98 require.False(t, third.loops[1].waiting())
101 func TestWhatsAppVoiceFailuresKeepLaterMessagesMoving(t *testing.T) {
102 synctest.Test(t, func(t *testing.T) {
103 whatsapp, fake := whatsappStartTestLinked(t)
104 whatsapp.client = fake
105 fake.downloadWait = 3 * time.Minute
107 require.True(t, whatsapp.handle(whatsappVoiceTestEvent(whatsappTestAna)))
109 fake.downloadWait = 0
110 fake.downloadErr = errors.New("secret URL and key")
111 event := whatsappVoiceTestEvent(whatsappTestAna)
112 event.Info.ID = "failed"
113 require.True(t, whatsapp.handle(event))
114 require.True(t, whatsapp.handle(whatsappTestMessage(whatsappTestAna, whatsappTestAna, "next", "Next text.", event.Info.Timestamp)))
115 require.NoError(t, whatsapp.state.CloseAll())
117 messages := whatsapp.state.Closed()[0].Messages
118 require.Len(t, messages, 3)
119 require.Equal(t, "(voice not downloaded)", messages[0].Text)
120 require.Equal(t, "(voice not downloaded)", messages[1].Text)
121 require.Equal(t, "Next text.", messages[2].Text)
125 func TestWhatsAppSkipsViewOnceAndNonVoiceAudio(t *testing.T) {
126 whatsapp, fake := whatsappStartTestLinked(t)
127 whatsapp.client = fake
128 event := whatsappVoiceTestEvent(whatsappTestAna)
129 event.Message.AudioMessage.ViewOnce = new(true)
130 require.True(t, whatsapp.handle(event))
132 event.Message.AudioMessage.ViewOnce = new(false)
133 event.Message.AudioMessage.PTT = new(false)
134 require.True(t, whatsapp.handle(event))
135 require.Empty(t, fake.Downloads())
136 require.NoError(t, whatsapp.state.CloseAll())
137 require.Empty(t, whatsapp.state.Closed())
140 func whatsappVoiceTestEvent(sender types.JID) *events.Message {
141 event := whatsappTestMessage(whatsappTestAna, sender, "voice", "", time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC))
142 event.Message = &waE2E.Message{AudioMessage: &waE2E.AudioMessage{
144 Mimetype: new("audio/ogg; codecs=opus"),