From d680c332edbe73550395e418da14dbed52560831 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 21 Sep 2026 23:34:40 +0200 Subject: [PATCH] fix: keep a message read status if a failure arrives later also simplifies tests to use a shared helper for receiving an mdn --- src/message.rs | 4 ++++ src/message/message_tests.rs | 22 +++++++++++++++++++ src/receive_imf/receive_imf_tests.rs | 32 ++++------------------------ src/test_utils.rs | 13 ++++++++++- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/message.rs b/src/message.rs index d1c058a3e..fc4cc1992 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1972,6 +1972,10 @@ pub(crate) async fn set_msg_failed( msg: &mut Message, error: &str, ) -> Result<()> { + if msg.state == MessageState::OutMdnRcvd { + info!(context, "{} was read, ignoring failure: {}", msg.id, error); + return Ok(()); + } if msg.state.can_fail() { msg.state = MessageState::OutFailed; warn!(context, "{} failed: {}", msg.id, error); diff --git a/src/message/message_tests.rs b/src/message/message_tests.rs index 41c579d4e..0d63b5cb9 100644 --- a/src/message/message_tests.rs +++ b/src/message/message_tests.rs @@ -454,6 +454,28 @@ async fn test_get_state() -> Result<()> { Ok(()) } +/// Tests that a failure reported after a read receipt leaves the message untouched. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_set_msg_failed_after_mdn() -> Result<()> { + let mut tcm = TestContextManager::new(); + let alice = &tcm.alice().await; + let bob = &tcm.bob().await; + let alice_chat = alice.create_chat(bob).await; + let sent = alice.send_text(alice_chat.id, "hi").await; + let bob_msg = bob.recv_msg(&sent).await; + alice.recv_mdn(bob, &bob_msg).await?; + let mut msg = sent.load_from_db().await; + assert_eq!(msg.state, MessageState::OutMdnRcvd); + + set_msg_failed(alice, &mut msg, "relay bounced").await?; + let msg = sent.load_from_db().await; + assert_eq!(msg.state, MessageState::OutMdnRcvd); + assert_eq!(msg.error(), None); + let chats = Chatlist::try_load(alice, 0, None, None).await?; + assert_eq!(chats.get_msg_id(0)?, Some(msg.id)); + Ok(()) +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_is_bot() -> Result<()> { let mut tcm = TestContextManager::new(); diff --git a/src/receive_imf/receive_imf_tests.rs b/src/receive_imf/receive_imf_tests.rs index 40afe5104..c130fbd0a 100644 --- a/src/receive_imf/receive_imf_tests.rs +++ b/src/receive_imf/receive_imf_tests.rs @@ -2723,20 +2723,8 @@ async fn test_read_receipts_dont_create_chats() -> Result<()> { let chats = Chatlist::try_load(&alice, 0, None, None).await?; assert_eq!(chats.len(), 0); - // Bob sends a read receipt. - let mdn_mimefactory = crate::mimefactory::MimeFactory::from_mdn( - &bob, - received_msg.from_id, - received_msg.rfc724_mid, - vec![], - ) - .await?; - let bob_addr = bob.get_primary_self_addr().await?; - let rendered_mdn = mdn_mimefactory.render(&bob, &bob_addr).await?; - let mdn_body = rendered_mdn.message; - - // Alice receives the read receipt. - receive_imf(&alice, mdn_body.as_bytes(), false).await?; + // Alice receives Bob's read receipt. + alice.recv_mdn(&bob, &received_msg).await?; // Chat should not pop up in the chatlist. let chats = Chatlist::try_load(&alice, 0, None, None).await?; @@ -2759,20 +2747,8 @@ async fn test_read_receipts_dont_unmark_bots() -> Result<()> { .await; let received_msg = bob.get_last_msg().await; - // Bob sends a read receipt. - let mdn_mimefactory = crate::mimefactory::MimeFactory::from_mdn( - bob, - received_msg.from_id, - received_msg.rfc724_mid, - vec![], - ) - .await?; - let bob_addr = bob.get_primary_self_addr().await?; - let rendered_mdn = mdn_mimefactory.render(bob, &bob_addr).await?; - let mdn_body = rendered_mdn.message; - - // Alice receives the read receipt. - receive_imf(alice, mdn_body.as_bytes(), false).await?; + // Alice receives Bob's read receipt. + alice.recv_mdn(bob, &received_msg).await?; let msg = alice.get_last_msg_in(alice_chat.id).await; assert_eq!(msg.state, MessageState::OutMdnRcvd); let ab_contact = alice.add_or_lookup_contact(bob).await; diff --git a/src/test_utils.rs b/src/test_utils.rs index b7c03ce69..457281570 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -35,7 +35,7 @@ use crate::context::Context; use crate::events::{Event, EventEmitter, EventType, Events}; use crate::key::{self, DcKey, self_fingerprint}; use crate::message::{Message, MessageState, MsgId}; -use crate::mimefactory; +use crate::mimefactory::{self, MimeFactory}; use crate::mimeparser::{MimeMessage, SystemMessage}; use crate::pgp::SeipdVersion; use crate::receive_imf::{ReceivedMsg, receive_imf}; @@ -850,6 +850,17 @@ ORDER BY id" assert_eq!(received.chat_id, ChatId::TRASH); } + /// Receives a read receipt from `reader`, who received `msg`. + pub async fn recv_mdn(&self, reader: &TestContext, msg: &Message) -> Result<()> { + let mdn = MimeFactory::from_mdn(reader, msg.from_id, msg.rfc724_mid.clone(), vec![]) + .await? + .render(reader, &reader.get_primary_self_addr().await?) + .await? + .message; + receive_imf(self, mdn.as_bytes(), false).await?; + Ok(()) + } + /// Gets the most recent message ID of a chat. /// /// Panics on errors or if the most recent message is a marker.