Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cca4174
remove: partial downloads
Simon-Laux Oct 30, 2025
fdcb222
remove partial download tests and fix issues in other tests
Simon-Laux Oct 30, 2025
7f3eabc
remove remaining partial messages tests
Simon-Laux Oct 30, 2025
2e2bc12
remove download limit validation, it does not really matter in the new
Simon-Laux Oct 30, 2025
902a0d7
fix clippy
Simon-Laux Oct 30, 2025
70d7e04
ignore that `partial_download_msg_body` is dead code
Simon-Laux Oct 30, 2025
2284d36
remove test for download limit validation
Simon-Laux Oct 30, 2025
53163ab
remove `test_download_on_demand`
Simon-Laux Oct 31, 2025
cc9fe5d
remove test `test_download_limit_chat_assignment`
Simon-Laux Oct 31, 2025
d1dc322
Remove superfluous parentheses.
Simon-Laux Oct 31, 2025
58ad664
fix the issue that broke the imap fetching
Simon-Laux Oct 31, 2025
014d80b
fix python lint errors
Simon-Laux Oct 31, 2025
a044436
remove cffi python test`test_webxdc_download_on_demand`
Simon-Laux Nov 1, 2025
28cca8b
fix python lint
Simon-Laux Nov 1, 2025
560e86b
fix rebase issue
Simon-Laux Nov 6, 2025
5a2ca7a
fix another rebase issue
Simon-Laux Nov 6, 2025
10c2016
fix lint of python test
Simon-Laux Nov 6, 2025
37b027a
reintroduce `Config::FailOnReceivingFullMsg` but renamed it to
Simon-Laux Nov 6, 2025
afa46c8
allow exclusion of `simulate_receive_imf_error` from get_info
Simon-Laux Nov 8, 2025
6d11e08
adapt comment
Simon-Laux Nov 12, 2025
05a1967
remove `StockMessage::PartialDownloadMsgBody`
Simon-Laux Nov 12, 2025
b70a41f
fix clippy issue
Simon-Laux Nov 14, 2025
971286b
restore test `test_downloadstate_values`
Simon-Laux Nov 14, 2025
af00375
restore test `test_webxdc_update_for_not_downloaded_instance` and rename
Simon-Laux Nov 14, 2025
ab1fdfa
bring back receiving half of `test_partial_receive_imf` and rename to
Simon-Laux Nov 14, 2025
f7d8812
remove `test_webxdc_update_for_not_yet_received_instance`
Simon-Laux Nov 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
remove partial download tests and fix issues in other tests
  • Loading branch information
Simon-Laux committed Nov 12, 2025
commit fdcb2227bf0e33f34c1c4ab4aca77b7fcfa162f3
54 changes: 0 additions & 54 deletions deltachat-rpc-client/tests/test_something.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,60 +604,6 @@ def test_mdn_doesnt_break_autocrypt(acfactory) -> None:
assert snapshot.show_padlock


def test_reaction_to_partially_fetched_msg(acfactory, tmp_path):
"""See https://github.com/deltachat/deltachat-core-rust/issues/3688 "Partially downloaded
messages are received out of order".

If the Inbox contains X small messages followed by Y large messages followed by Z small
messages, Delta Chat first downloaded a batch of X+Z messages, and then a batch of Y messages.

This bug was discovered by @Simon-Laux while testing reactions PR #3644 and can be reproduced
with online test as follows:
- Bob enables download limit and goes offline.
- Alice sends a large message to Bob and reacts to this message with a thumbs-up.
- Bob goes online
- Bob first processes a reaction message and throws it away because there is no corresponding
message, then processes a partially downloaded message.
- As a result, Bob does not see a reaction
"""
download_limit = 300000
ac1, ac2 = acfactory.get_online_accounts(2)
ac1_addr = ac1.get_config("addr")
chat = ac1.create_chat(ac2)
ac2.set_config("download_limit", str(download_limit))
ac2.stop_io()

logging.info("sending small+large messages from ac1 to ac2")
msgs = []
msgs.append(chat.send_text("hi"))
path = tmp_path / "large"
path.write_bytes(os.urandom(download_limit + 1))
msgs.append(chat.send_file(str(path)))
for m in msgs:
m.wait_until_delivered()

logging.info("sending a reaction to the large message from ac1 to ac2")
# TODO: Find the reason of an occasional message reordering on the server (so that the reaction
# has a lower UID than the previous message). W/a is to sleep for some time to let the reaction
# have a later INTERNALDATE.
time.sleep(1.1)
react_str = "\N{THUMBS UP SIGN}"
msgs.append(msgs[-1].send_reaction(react_str))
msgs[-1].wait_until_delivered()

ac2.start_io()

logging.info("wait for ac2 to receive a reaction")
msg2 = Message(ac2, ac2.wait_for_reactions_changed().msg_id)
assert msg2.get_sender_contact().get_snapshot().address == ac1_addr
assert msg2.get_snapshot().download_state == DownloadState.AVAILABLE
reactions = msg2.get_reactions()
contacts = [Contact(ac2, int(i)) for i in reactions.reactions_by_contact]
assert len(contacts) == 1
assert contacts[0].get_snapshot().address == ac1_addr
assert list(reactions.reactions_by_contact.values())[0] == [react_str]


def test_reactions_for_a_reordering_move(acfactory, direct_imap):
"""When a batch of messages is moved from Inbox to DeltaChat folder with a single MOVE command,
their UIDs may be reordered (e.g. Gmail is known for that) which led to that messages were
Expand Down
11 changes: 2 additions & 9 deletions src/calls/calls_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -652,20 +652,13 @@ async fn test_no_partial_calls() -> Result<()> {
Chat-Content: call-ended\n\
\n\
Call ended\n";
receive_imf_from_inbox(
alice,
"[email protected]",
imf_raw,
seen,
Some(imf_raw.len().try_into().unwrap()),
)
.await?;
receive_imf_from_inbox(alice, "[email protected]", imf_raw, seen).await?;

// The call is still not ended.
assert_eq!(call_state(alice, call_msg.id).await?, CallState::Alerting);

// Fully downloading the message ends the call.
receive_imf_from_inbox(alice, "[email protected]", imf_raw, seen, None)
receive_imf_from_inbox(alice, "[email protected]", imf_raw, seen)
.await
.context("Failed to fully download end call message")?;
assert_eq!(call_state(alice, call_msg.id).await?, CallState::Missed);
Expand Down
4 changes: 2 additions & 2 deletions src/chat/chat_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3130,7 +3130,7 @@ async fn test_broadcast_channel_protected_listid() -> Result<()> {
.await?
.grpid;

let parsed = mimeparser::MimeMessage::from_bytes(bob, sent.payload.as_bytes(), None).await?;
let parsed = mimeparser::MimeMessage::from_bytes(bob, sent.payload.as_bytes()).await?;
assert_eq!(
parsed.get_mailinglist_header().unwrap(),
format!("My Channel <{}>", alice_list_id)
Expand Down Expand Up @@ -3325,7 +3325,7 @@ async fn test_leave_broadcast_multidevice() -> Result<()> {
remove_contact_from_chat(bob0, bob_chat_id, ContactId::SELF).await?;

let leave_msg = bob0.pop_sent_msg().await;
let parsed = MimeMessage::from_bytes(bob1, leave_msg.payload().as_bytes(), None).await?;
let parsed = MimeMessage::from_bytes(bob1, leave_msg.payload().as_bytes()).await?;
assert_eq!(
parsed.parts[0].msg,
stock_str::msg_group_left_remote(bob0).await
Expand Down
233 changes: 2 additions & 231 deletions src/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,8 @@ mod tests {
use num_traits::FromPrimitive;

use super::*;
use crate::chat::{get_chat_msgs, send_msg};
use crate::ephemeral::Timer;
use crate::message::delete_msgs;
use crate::receive_imf::receive_imf_from_inbox;
use crate::test_utils::{E2EE_INFO_MSGS, TestContext, TestContextManager};
use crate::chat::send_msg;
use crate::test_utils::TestContext;

#[test]
fn test_downloadstate_values() {
Expand Down Expand Up @@ -294,230 +291,4 @@ mod tests {

Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_partial_receive_imf() -> Result<()> {
let t = TestContext::new_alice().await;

let header = "Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
From: [email protected]\n\
To: [email protected]\n\
Subject: foo\n\
Message-ID: <[email protected]>\n\
Chat-Version: 1.0\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\
Content-Type: text/plain";

receive_imf_from_inbox(
&t,
"[email protected]",
header.as_bytes(),
false,
Some(100000),
)
.await?;
let msg = t.get_last_msg().await;
assert_eq!(msg.download_state(), DownloadState::Available);
assert_eq!(msg.get_subject(), "foo");
assert!(
msg.get_text()
.contains(&stock_str::partial_download_msg_body(&t, 100000).await)
);

receive_imf_from_inbox(
&t,
"[email protected]",
format!("{header}\n\n100k text...").as_bytes(),
false,
None,
)
.await?;
let msg = t.get_last_msg().await;
assert_eq!(msg.download_state(), DownloadState::Done);
assert_eq!(msg.get_subject(), "foo");
assert_eq!(msg.get_text(), "100k text...");

Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_partial_download_and_ephemeral() -> Result<()> {
let t = TestContext::new_alice().await;
let chat_id = t
.create_chat_with_contact("bob", "[email protected]")
.await
.id;
chat_id
.set_ephemeral_timer(&t, Timer::Enabled { duration: 60 })
.await?;

// download message from bob partially, this must not change the ephemeral timer
receive_imf_from_inbox(
&t,
"[email protected]",
b"From: Bob <[email protected]>\n\
To: Alice <[email protected]>\n\
Chat-Version: 1.0\n\
Subject: subject\n\
Message-ID: <[email protected]>\n\
Date: Sun, 14 Nov 2021 00:10:00 +0000\
Content-Type: text/plain",
false,
Some(100000),
)
.await?;
assert_eq!(
chat_id.get_ephemeral_timer(&t).await?,
Timer::Enabled { duration: 60 }
);

Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_status_update_expands_to_nothing() -> Result<()> {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
let chat_id = alice.create_chat(&bob).await.id;

let file = alice.get_blobdir().join("minimal.xdc");
tokio::fs::write(&file, include_bytes!("../test-data/webxdc/minimal.xdc")).await?;
let mut instance = Message::new(Viewtype::File);
instance.set_file_and_deduplicate(&alice, &file, None, None)?;
let _sent1 = alice.send_msg(chat_id, &mut instance).await;

alice
.send_webxdc_status_update(instance.id, r#"{"payload":7}"#)
.await?;
alice.flush_status_updates().await?;
let sent2 = alice.pop_sent_msg().await;
let sent2_rfc724_mid = sent2.load_from_db().await.rfc724_mid;

// not downloading the status update results in an placeholder
receive_imf_from_inbox(
&bob,
&sent2_rfc724_mid,
sent2.payload().as_bytes(),
false,
Some(sent2.payload().len() as u32),
)
.await?;
let msg = bob.get_last_msg().await;
let chat_id = msg.chat_id;
assert_eq!(
get_chat_msgs(&bob, chat_id).await?.len(),
E2EE_INFO_MSGS + 1
);
assert_eq!(msg.download_state(), DownloadState::Available);

// downloading the status update afterwards expands to nothing and moves the placeholder to trash-chat
// (usually status updates are too small for not being downloaded directly)
receive_imf_from_inbox(
&bob,
&sent2_rfc724_mid,
sent2.payload().as_bytes(),
false,
None,
)
.await?;
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), E2EE_INFO_MSGS);
assert!(
Message::load_from_db_optional(&bob, msg.id)
.await?
.is_none()
);

Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_mdn_expands_to_nothing() -> Result<()> {
let bob = TestContext::new_bob().await;
let raw = b"Subject: Message opened\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\
Message-ID: <[email protected]>\n\
To: Alice <[email protected]>\n\
From: Bob <[email protected]>\n\
Content-Type: multipart/report; report-type=disposition-notification;\n\t\
boundary=\"kJBbU58X1xeWNHgBtTbMk80M5qnV4N\"\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\
Content-Type: text/plain; charset=utf-8\n\
\n\
bla\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N\n\
Content-Type: message/disposition-notification\n\
\n\
Reporting-UA: Delta Chat 1.88.0\n\
Original-Recipient: rfc822;[email protected]\n\
Final-Recipient: rfc822;[email protected]\n\
Original-Message-ID: <[email protected]>\n\
Disposition: manual-action/MDN-sent-automatically; displayed\n\
\n\
\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\
";

// not downloading the mdn results in an placeholder
receive_imf_from_inbox(&bob, "[email protected]", raw, false, Some(raw.len() as u32)).await?;
let msg = bob.get_last_msg().await;
let chat_id = msg.chat_id;
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), 1);
assert_eq!(msg.download_state(), DownloadState::Available);

// downloading the mdn afterwards expands to nothing and deletes the placeholder directly
// (usually mdn are too small for not being downloaded directly)
receive_imf_from_inbox(&bob, "[email protected]", raw, false, None).await?;
assert_eq!(get_chat_msgs(&bob, chat_id).await?.len(), 0);
assert!(
Message::load_from_db_optional(&bob, msg.id)
.await?
.is_none()
);

Ok(())
}

/// Tests that fully downloading the message
/// works even if the Message-ID already exists
/// in the database assigned to the trash chat.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_partial_download_trashed() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;

let imf_raw = b"From: Bob <[email protected]>\n\
To: Alice <[email protected]>\n\
Chat-Version: 1.0\n\
Subject: subject\n\
Message-ID: <[email protected]>\n\
Date: Sun, 14 Nov 2021 00:10:00 +0000\
Content-Type: text/plain";

// Download message from Bob partially.
let partial_received_msg =
receive_imf_from_inbox(alice, "[email protected]", imf_raw, false, Some(100000))
.await?
.unwrap();
assert_eq!(partial_received_msg.msg_ids.len(), 1);

// Delete the received message.
// Not it is still in the database,
// but in the trash chat.
delete_msgs(alice, &[partial_received_msg.msg_ids[0]]).await?;

// Fully download message after deletion.
let full_received_msg =
receive_imf_from_inbox(alice, "[email protected]", imf_raw, false, None).await?;

// The message does not reappear.
// However, `receive_imf` should not fail.
assert!(full_received_msg.is_none());

Ok(())
}
}
6 changes: 3 additions & 3 deletions src/mimefactory/mimefactory_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ async fn test_render_reply() {
"1.0"
);

let _mime_msg = MimeMessage::from_bytes(context, rendered_msg.message.as_bytes(), None)
let _mime_msg = MimeMessage::from_bytes(context, rendered_msg.message.as_bytes())
.await
.unwrap();
}
Expand Down Expand Up @@ -824,7 +824,7 @@ async fn test_protected_headers_directive() -> Result<()> {
assert!(msg.get_showpadlock());
assert!(sent.payload.contains("\r\nSubject: [...]\r\n"));

let mime = MimeMessage::from_bytes(&alice, sent.payload.as_bytes(), None).await?;
let mime = MimeMessage::from_bytes(&alice, sent.payload.as_bytes()).await?;
let mut payload = str::from_utf8(&mime.decoded_data)?.splitn(2, "\r\n\r\n");
let part = payload.next().unwrap();
assert_eq!(
Expand Down Expand Up @@ -854,7 +854,7 @@ async fn test_dont_remove_self() -> Result<()> {
.await;

println!("{}", sent.payload);
let mime_message = MimeMessage::from_bytes(alice, sent.payload.as_bytes(), None)
let mime_message = MimeMessage::from_bytes(alice, sent.payload.as_bytes())
.await
.unwrap();
assert!(!mime_message.header_exists(HeaderDef::ChatGroupPastMembers));
Expand Down
Loading