]> git.giacomolli.uk - onechat/blob - whatsapp_voice_test.go
Order daemon and storage declarations
[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 first := newDaemonMessageTest(t, dir, server)
54 whatsappTestSession(t, dir, true)
55
56 whatsapp, fake, logs := whatsappStartTest(t, dir, newTestClock())
57 whatsapp.client = fake
58 _, whatsapp.inbox.voice = voiceTestInput(t, voiceTestScript)
59 fake.downloadData = []byte("made-up routed voice")
60 event := whatsappVoiceTestEvent(whatsappTestAna)
61 event.Info.Chat = whatsappTestFamily
62 event.Info.IsGroup = true
63
64 whatsapp.groups.set(whatsappTestFamily.String(), "Garden plans")
65 require.NoError(t, first.messages.routes.Set("whatsapp:"+whatsappTestFamily.String(), "Garden"))
66 require.True(t, whatsapp.handle(event))
67 require.NoError(t, whatsapp.state.CloseAll())
68
69 batch := whatsapp.state.Closed()[0]
70 text := batch.Text(time.UTC)
71 routed, err := first.messages.RouteWhatsApp(batch, text)
72 require.NoError(t, err)
73 require.True(t, routed)
74 first.Close(t)
75
76 second := newDaemonMessageTest(t, dir, server)
77 restored := whatsappRouteTestInbox(t, dir)
78 require.NoError(t, restored.deliver(t.Context(), second.loops[0], WhatsAppLink{}, true))
79 require.Empty(t, restored.state.Closed())
80 require.Zero(t, second.agents[0].memory.Len())
81 require.Equal(t, 1, second.agents[1].memory.Len())
82
83 message, err := second.agents[1].memory.Message(0)
84 require.NoError(t, err)
85 require.Equal(t, text, message.Text)
86 require.Contains(t, text, "[voice ")
87 require.Contains(t, text, "A made-up voice transcript.")
88 require.Equal(t, whatsapp.inbox.voices.dir, second.agents[1].agent.Voices().dir)
89 second.Start(t, 1)
90 second.events[1].WaitFor(t, "idle", 1)
91 require.Contains(t, string(server.Call(0).raw), "A made-up voice transcript.")
92 require.NotContains(t, logs.String()+second.logs.String(), "A made-up voice transcript")
93 second.Close(t)
94
95 third := newDaemonMessageTest(t, dir, server)
96 require.Equal(t, 1, third.agents[1].memory.Len())
97 require.False(t, third.loops[1].waiting())
98 }
99
100 func TestWhatsAppVoiceFailuresKeepLaterMessagesMoving(t *testing.T) {
101 synctest.Test(t, func(t *testing.T) {
102 whatsapp, fake := whatsappStartTestLinked(t)
103 whatsapp.client = fake
104 fake.downloadWait = 3 * time.Minute
105
106 require.True(t, whatsapp.handle(whatsappVoiceTestEvent(whatsappTestAna)))
107
108 fake.downloadWait = 0
109 fake.downloadErr = errors.New("secret URL and key")
110 event := whatsappVoiceTestEvent(whatsappTestAna)
111 event.Info.ID = "failed"
112 require.True(t, whatsapp.handle(event))
113 require.True(t, whatsapp.handle(whatsappTestMessage(whatsappTestAna, whatsappTestAna, "next", "Next text.", event.Info.Timestamp)))
114 require.NoError(t, whatsapp.state.CloseAll())
115
116 messages := whatsapp.state.Closed()[0].Messages
117 require.Len(t, messages, 3)
118 require.Equal(t, "(voice not downloaded)", messages[0].Text)
119 require.Equal(t, "(voice not downloaded)", messages[1].Text)
120 require.Equal(t, "Next text.", messages[2].Text)
121 })
122 }
123
124 func TestWhatsAppSkipsViewOnceAndNonVoiceAudio(t *testing.T) {
125 whatsapp, fake := whatsappStartTestLinked(t)
126 whatsapp.client = fake
127 event := whatsappVoiceTestEvent(whatsappTestAna)
128 event.Message.AudioMessage.ViewOnce = new(true)
129 require.True(t, whatsapp.handle(event))
130
131 event.Message.AudioMessage.ViewOnce = new(false)
132 event.Message.AudioMessage.PTT = new(false)
133 require.True(t, whatsapp.handle(event))
134 require.Empty(t, fake.Downloads())
135 require.NoError(t, whatsapp.state.CloseAll())
136 require.Empty(t, whatsapp.state.Closed())
137 }
138
139 func whatsappVoiceTestEvent(sender types.JID) *events.Message {
140 event := whatsappTestMessage(whatsappTestAna, sender, "voice", "", time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC))
141 event.Message = &waE2E.Message{AudioMessage: &waE2E.AudioMessage{
142 PTT: new(true),
143 Mimetype: new("audio/ogg; codecs=opus"),
144 }}
145
146 return event
147 }