package main import ( "errors" "net/http" "os" "path/filepath" "testing" "testing/synctest" "time" "github.com/stretchr/testify/require" "go.mau.fi/whatsmeow/proto/waE2E" "go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types/events" ) func TestWhatsAppVoiceNotesAreDurableBeforeAcknowledgement(t *testing.T) { whatsapp, fake := whatsappStartTestLinked(t) whatsapp.client = fake _, whatsapp.inbox.voice = voiceTestInput(t, voiceTestScript) audio := []byte("made-up WhatsApp voice") fake.downloadData = audio event := whatsappVoiceTestEvent(whatsappTestAna) require.True(t, whatsapp.handle(event)) require.NoError(t, whatsapp.state.CloseAll()) restored := whatsappTestState(t, whatsapp.state.path) require.Len(t, restored.Closed(), 1) text := restored.Closed()[0].Messages[0].Text require.Contains(t, text, "[voice ") require.Contains(t, text, "A made-up voice transcript.") files, err := os.ReadDir(whatsapp.inbox.voices.dir) require.NoError(t, err) require.Len(t, files, 1) stored, err := os.ReadFile(filepath.Join(whatsapp.inbox.voices.dir, files[0].Name())) require.NoError(t, err) require.Equal(t, audio, stored) require.True(t, whatsapp.handle(event)) require.NoError(t, whatsapp.state.CloseAll()) require.Len(t, whatsapp.state.Closed()[0].Messages, 1) } func TestWhatsAppVoiceRoutingSurvivesRestartWithoutDuplicateEntries(t *testing.T) { dir := t.TempDir() server := newModelFake(t) server.reply = func(w http.ResponseWriter, _ ModelFakeCall) { modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("")}, 100, 0, 5)) } first := newDaemonMessageTest(t, dir, server) whatsappTestSession(t, dir, true) whatsapp, fake, logs := whatsappStartTest(t, dir, newTestClock()) whatsapp.client = fake _, whatsapp.inbox.voice = voiceTestInput(t, voiceTestScript) fake.downloadData = []byte("made-up routed voice") event := whatsappVoiceTestEvent(whatsappTestAna) event.Info.Chat = whatsappTestFamily event.Info.IsGroup = true whatsapp.groups.set(whatsappTestFamily.String(), "Garden plans") require.NoError(t, first.messages.routes.Set("whatsapp:"+whatsappTestFamily.String(), "Garden")) require.True(t, whatsapp.handle(event)) require.NoError(t, whatsapp.state.CloseAll()) batch := whatsapp.state.Closed()[0] text := batch.Text(time.UTC) routed, err := first.messages.RouteWhatsApp(batch, text) require.NoError(t, err) require.True(t, routed) first.Close(t) second := newDaemonMessageTest(t, dir, server) restored := whatsappRouteTestInbox(t, dir) require.NoError(t, restored.deliver(t.Context(), second.loops[0], WhatsAppLink{}, true)) require.Empty(t, restored.state.Closed()) require.Zero(t, second.agents[0].memory.Len()) require.Equal(t, 1, second.agents[1].memory.Len()) message, err := second.agents[1].memory.Message(0) require.NoError(t, err) require.Equal(t, text, message.Text) require.Contains(t, text, "[voice ") require.Contains(t, text, "A made-up voice transcript.") require.Equal(t, whatsapp.inbox.voices.dir, second.agents[1].agent.Voices().dir) second.Start(t, 1) second.events[1].WaitFor(t, "idle", 1) require.Contains(t, string(server.Call(0).raw), "A made-up voice transcript.") require.NotContains(t, logs.String()+second.logs.String(), "A made-up voice transcript") second.Close(t) third := newDaemonMessageTest(t, dir, server) require.Equal(t, 1, third.agents[1].memory.Len()) require.False(t, third.loops[1].waiting()) } func TestWhatsAppVoiceFailuresKeepLaterMessagesMoving(t *testing.T) { synctest.Test(t, func(t *testing.T) { whatsapp, fake := whatsappStartTestLinked(t) whatsapp.client = fake fake.downloadWait = 3 * time.Minute require.True(t, whatsapp.handle(whatsappVoiceTestEvent(whatsappTestAna))) fake.downloadWait = 0 fake.downloadErr = errors.New("secret URL and key") event := whatsappVoiceTestEvent(whatsappTestAna) event.Info.ID = "failed" require.True(t, whatsapp.handle(event)) require.True(t, whatsapp.handle(whatsappTestMessage(whatsappTestAna, whatsappTestAna, "next", "Next text.", event.Info.Timestamp))) require.NoError(t, whatsapp.state.CloseAll()) messages := whatsapp.state.Closed()[0].Messages require.Len(t, messages, 3) require.Equal(t, "(voice not downloaded)", messages[0].Text) require.Equal(t, "(voice not downloaded)", messages[1].Text) require.Equal(t, "Next text.", messages[2].Text) }) } func TestWhatsAppSkipsViewOnceAndNonVoiceAudio(t *testing.T) { whatsapp, fake := whatsappStartTestLinked(t) whatsapp.client = fake event := whatsappVoiceTestEvent(whatsappTestAna) event.Message.AudioMessage.ViewOnce = new(true) require.True(t, whatsapp.handle(event)) event.Message.AudioMessage.ViewOnce = new(false) event.Message.AudioMessage.PTT = new(false) require.True(t, whatsapp.handle(event)) require.Empty(t, fake.Downloads()) require.NoError(t, whatsapp.state.CloseAll()) require.Empty(t, whatsapp.state.Closed()) } func whatsappVoiceTestEvent(sender types.JID) *events.Message { event := whatsappTestMessage(whatsappTestAna, sender, "voice", "", time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)) event.Message = &waE2E.Message{AudioMessage: &waE2E.AudioMessage{ PTT: new(true), Mimetype: new("audio/ogg; codecs=opus"), }} return event }