]> git.giacomolli.uk - onechat/blob - whatsapp_voice_test.go
Require consent and review for autosquash
[onechat] / whatsapp_voice_test.go
1 package main
2
3 import (
4 "errors"
5 "net/http"
6 "os"
7 "path/filepath"
8 "testing"
9 "testing/synctest"
10 "time"
11
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"
16 )
17
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())
27
28 restored := whatsappTestState(t, whatsapp.state.path)
29 require.Len(t, restored.Closed(), 1)
30
31 text := restored.Closed()[0].Messages[0].Text
32 require.Contains(t, text, "[voice ")
33 require.Contains(t, text, "A made-up voice transcript.")
34
35 files, err := os.ReadDir(whatsapp.inbox.voices.dir)
36 require.NoError(t, err)
37 require.Len(t, files, 1)
38
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)
45 }
46
47 func TestWhatsAppVoiceRoutingSurvivesRestartWithoutDuplicateEntries(t *testing.T) {
48 dir := t.TempDir()
49 server := newModelFake(t)
50 server.reply = func(w http.ResponseWriter, _ ModelFakeCall) {
51 modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5))
52 }
53
54 first := newDaemonMessageTest(t, dir, server)
55 whatsappTestSession(t, dir, true)
56
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
64
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())
69
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)
75 first.Close(t)
76
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())
83
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)
90 second.Start(t, 1)
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")
94 second.Close(t)
95
96 third := newDaemonMessageTest(t, dir, server)
97 require.Equal(t, 1, third.agents[1].memory.Len())
98 require.False(t, third.loops[1].waiting())
99 }
100
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
106
107 require.True(t, whatsapp.handle(whatsappVoiceTestEvent(whatsappTestAna)))
108
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())
116
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)
122 })
123 }
124
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))
131
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())
138 }
139
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{
143 PTT: new(true),
144 Mimetype: new("audio/ogg; codecs=opus"),
145 }}
146
147 return event
148 }