Compare commits

...

2 Commits

Author SHA1 Message Date
iequidoo
e14f01f866 fix: Send pre-message after successful sending of post-message (#8063)
Also, send the message text in the post-message too, we can't support the workaround for duplicated
message text shown in ancient versions anymore, otherwise newer versions won't show the message text
at all.

On the receiving side, don't download a post-message w/o known pre-message if the post-message
exceeds DownloadLimit to save user's traffic.

This isn't easy to test in Rust though because the `smtp` sending code requires an SMTP connection,
so it's only tested (by already existing tests) that messages are queued in the right order.

Another problem is that the `smtp` sending logic doesn't try to send any messages following a
failed-to-send message until the retry limit is reached, so if there's smth wrong with a
post-message, other unrelated messages are delayed, but this problem has already existed.
2026-06-25 22:24:01 -03:00
iequidoo
3a36b0beb9 fix: ChatId::delete_ex(): Interrupt inbox loop
Messages are deleted from the inbox loop, so it should be interrupted. The SMTP doesn't need to be
interrupted explicitly however, chat::sync() already does this internally.
2026-06-25 14:45:45 -03:00
14 changed files with 115 additions and 190 deletions

View File

@@ -251,12 +251,7 @@ def test_realtime_large_webxdc(acfactory, path_to_large_webxdc):
ac1_ac2_chat = ac1.create_chat(ac2)
ac1_webxdc_msg = ac1_ac2_chat.send_message(text="realtime check", file=path_to_large_webxdc)
# Receive pre-message.
ac2_webxdc_msg = ac2.wait_for_incoming_msg()
# Receive post-message.
ac2_webxdc_msg = ac2.wait_for_msg(EventType.MSGS_CHANGED)
ac2_webxdc_msg.send_webxdc_realtime_advertisement()
event = ac1.wait_for_event(EventType.WEBXDC_REALTIME_ADVERTISEMENT_RECEIVED)
assert event.msg_id == ac1_webxdc_msg.id

View File

@@ -926,10 +926,6 @@ def test_delete_fully_downloaded_msg(acfactory, tmp_path, direct_imap):
msg = bob.wait_for_incoming_msg()
msg_snapshot = msg.get_snapshot()
assert msg_snapshot.download_state == DownloadState.AVAILABLE
msgs_changed_event = bob.wait_for_msgs_changed_event()
assert msgs_changed_event.msg_id == msg.id
msg_snapshot = msg.get_snapshot()
assert msg_snapshot.download_state == DownloadState.DONE
bob_direct_imap = direct_imap(bob)
@@ -960,10 +956,6 @@ def test_imap_autodelete_fully_downloaded_msg(acfactory, tmp_path, direct_imap):
msg = bob.wait_for_incoming_msg()
msg_snapshot = msg.get_snapshot()
assert msg_snapshot.download_state == DownloadState.AVAILABLE
msgs_changed_event = bob.wait_for_msgs_changed_event()
assert msgs_changed_event.msg_id == msg.id
msg_snapshot = msg.get_snapshot()
assert msg_snapshot.download_state == DownloadState.DONE
bob_direct_imap = direct_imap(bob)
@@ -1443,7 +1435,5 @@ def test_large_message(acfactory) -> None:
)
msg = bob.wait_for_incoming_msg()
msgs_changed_event = bob.wait_for_msgs_changed_event()
assert msg.id == msgs_changed_event.msg_id
snapshot = msg.get_snapshot()
assert snapshot.text == "Hello World, this message is bigger than 5 bytes"

View File

@@ -704,8 +704,7 @@ SELECT id, rfc724_mid, pre_rfc724_mid, timestamp, ?, 1 FROM msgs WHERE chat_id=?
context
.set_config_internal(Config::LastHousekeeping, None)
.await?;
context.scheduler.interrupt_smtp().await;
context.scheduler.interrupt_inbox().await;
Ok(())
}
@@ -2987,15 +2986,6 @@ WHERE id=?
)?;
for recipients_chunk in recipients.chunks(chunk_size) {
let recipients_chunk = recipients_chunk.join(" ");
if let Some(pre_msg) = &rendered_pre_msg {
let row_id = stmt.execute((
&pre_msg.rfc724_mid,
&recipients_chunk,
&pre_msg.message,
msg.id,
))?;
row_ids.push(row_id.try_into()?);
}
let row_id = stmt.execute((
&rendered_msg.rfc724_mid,
&recipients_chunk,
@@ -3003,6 +2993,16 @@ WHERE id=?
msg.id,
))?;
row_ids.push(row_id.try_into()?);
let Some(pre_msg) = &rendered_pre_msg else {
continue;
};
let row_id = stmt.execute((
&pre_msg.rfc724_mid,
&recipients_chunk,
&pre_msg.message,
msg.id,
))?;
row_ids.push(row_id.try_into()?);
}
Ok(row_ids)
};

View File

@@ -160,7 +160,6 @@ pub(crate) async fn download_msg(
let Some((server_uid, server_folder, msg_transport_id)) = row else {
// No IMAP record found, we don't know the UID and folder.
delete_from_available_post_msgs(context, &rfc724_mid).await?;
return Err(anyhow!(
"IMAP location for {rfc724_mid:?} post-message is unknown"
));
@@ -234,31 +233,6 @@ async fn set_state_to_failure(context: &Context, rfc724_mid: &str) -> Result<()>
Ok(())
}
async fn available_post_msgs_contains_rfc724_mid(
context: &Context,
rfc724_mid: &str,
) -> Result<bool> {
Ok(context
.sql
.query_get_value::<String>(
"SELECT rfc724_mid FROM available_post_msgs WHERE rfc724_mid=?",
(&rfc724_mid,),
)
.await?
.is_some())
}
async fn delete_from_available_post_msgs(context: &Context, rfc724_mid: &str) -> Result<()> {
context
.sql
.execute(
"DELETE FROM available_post_msgs WHERE rfc724_mid=?",
(&rfc724_mid,),
)
.await?;
Ok(())
}
async fn delete_from_downloads(context: &Context, rfc724_mid: &str) -> Result<()> {
context
.sql
@@ -286,7 +260,6 @@ pub(crate) async fn download_msgs(context: &Context, session: &mut Session) -> R
let res = download_msg(context, rfc724_mid.clone(), session).await;
if let Ok(Some(())) = res {
delete_from_downloads(context, rfc724_mid).await?;
delete_from_available_post_msgs(context, rfc724_mid).await?;
}
if let Err(err) = res {
warn!(
@@ -300,18 +273,13 @@ pub(crate) async fn download_msgs(context: &Context, session: &mut Session) -> R
"{rfc724_mid} download failed and there is no downloaded pre-message."
);
delete_from_downloads(context, rfc724_mid).await?;
} else if available_post_msgs_contains_rfc724_mid(context, rfc724_mid).await? {
} else {
warn!(
context,
"{rfc724_mid} is in available_post_msgs table but we failed to fetch it,
so set the message to DownloadState::Failure - probably it was deleted on the server in the meantime"
"Failed to fetch {rfc724_mid}, setting DownloadState to Failure."
);
set_state_to_failure(context, rfc724_mid).await?;
delete_from_downloads(context, rfc724_mid).await?;
delete_from_available_post_msgs(context, rfc724_mid).await?;
} else {
// leave the message in DownloadState::InProgress;
// it will be downloaded once it arrives.
}
}
}
@@ -319,44 +287,6 @@ pub(crate) async fn download_msgs(context: &Context, session: &mut Session) -> R
Ok(())
}
/// Downloads known post-messages without pre-messages
/// in order to guard against lost pre-messages.
pub(crate) async fn download_known_post_messages_without_pre_message(
context: &Context,
session: &mut Session,
) -> Result<()> {
let rfc724_mids = context
.sql
.query_map_vec("SELECT rfc724_mid FROM available_post_msgs", (), |row| {
let rfc724_mid: String = row.get(0)?;
Ok(rfc724_mid)
})
.await?;
for rfc724_mid in &rfc724_mids {
if msg_is_downloaded_for(context, rfc724_mid).await? {
delete_from_available_post_msgs(context, rfc724_mid).await?;
continue;
}
// Download the Post-Message unconditionally,
// because the Pre-Message got lost.
// The message may be in the wrong order,
// but at least we have it at all.
let res = download_msg(context, rfc724_mid.clone(), session).await;
if let Ok(Some(())) = res {
delete_from_available_post_msgs(context, rfc724_mid).await?;
}
if let Err(err) = res {
warn!(
context,
"download_known_post_messages_without_pre_message: Failed to download message rfc724_mid={rfc724_mid}: {:#}.",
err
);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use num_traits::FromPrimitive;

View File

@@ -607,7 +607,6 @@ impl Imap {
let _fetch_msgs_lock_guard = context.fetch_msgs_mutex.lock().await;
let mut uids_fetch: Vec<u32> = Vec::new();
let mut available_post_msgs: Vec<String> = Vec::new();
let mut download_later: Vec<String> = Vec::new();
let mut uid_message_ids = BTreeMap::new();
let mut largest_uid_skipped = None;
@@ -695,8 +694,6 @@ impl Imap {
.is_some()
{
info!(context, "{message_id:?} is a post-message.");
available_post_msgs.push(message_id.clone());
let is_bot = context.get_config_bool(Config::Bot).await?;
if is_bot && download_limit.is_none_or(|download_limit| size <= download_limit)
{
@@ -792,18 +789,8 @@ impl Imap {
chat::mark_old_messages_as_noticed(context, received_msgs).await?;
if fetch_res.is_ok() {
info!(
context,
"available_post_msgs: {}, download_later: {}.",
available_post_msgs.len(),
download_later.len(),
);
info!(context, "download_later: {}.", download_later.len(),);
let trans_fn = |t: &mut rusqlite::Transaction| {
let mut stmt = t.prepare("INSERT OR IGNORE INTO available_post_msgs VALUES (?)")?;
for rfc724_mid in available_post_msgs {
stmt.execute((rfc724_mid,))
.context("INSERT OR IGNORE INTO available_post_msgs")?;
}
let mut stmt =
t.prepare("INSERT OR IGNORE INTO download (rfc724_mid, msg_id) VALUES (?,0)")?;
for rfc724_mid in download_later {

View File

@@ -1723,10 +1723,6 @@ pub async fn delete_msgs_ex(
"DELETE FROM download WHERE rfc724_mid=?",
(&msg.rfc724_mid,),
)?;
trans.execute(
"DELETE FROM available_post_msgs WHERE rfc724_mid=?",
(&msg.rfc724_mid,),
)?;
Ok(())
};
if let Err(e) = context.sql.transaction(update_db).await {

View File

@@ -1702,23 +1702,19 @@ impl MimeFactory {
let footer = if is_reaction { "" } else { &self.selfstatus };
let message_text = if self.pre_message_mode == PreMessageMode::Post {
"".to_string()
} else {
format!(
"{}{}{}{}{}{}",
fwdhint.unwrap_or_default(),
quoted_text.unwrap_or_default(),
escape_message_footer_marks(final_text),
if !final_text.is_empty() && !footer.is_empty() {
"\r\n\r\n"
} else {
""
},
if !footer.is_empty() { "-- \r\n" } else { "" },
footer
)
};
let message_text = format!(
"{}{}{}{}{}{}",
fwdhint.unwrap_or_default(),
quoted_text.unwrap_or_default(),
escape_message_footer_marks(final_text),
if !final_text.is_empty() && !footer.is_empty() {
"\r\n\r\n"
} else {
""
},
if !footer.is_empty() { "-- \r\n" } else { "" },
footer
);
let mut main_part = MimePart::new("text/plain", message_text);
if is_reaction {

View File

@@ -533,19 +533,10 @@ pub(crate) async fn receive_imf_inner(
"Receiving message {rfc724_mid_orig:?}, seen={seen}...",
);
// These checks must be done before processing of SecureJoin and other special messages.
if mime_parser.pre_message == mimeparser::PreMessageMode::Post {
// Post-Message just replaces the attachment and modifies Params, not the whole message.
// This is done in the `handle_post_message` method.
} else if let Some(msg_id) = message::rfc724_mid_exists(context, rfc724_mid_orig).await? {
info!(
context,
"Message {rfc724_mid} is already in some chat or deleted."
);
if mime_parser.incoming {
return Ok(None);
}
let existing_msg_id = message::rfc724_mid_exists(context, rfc724_mid_orig).await?;
if let Some(msg_id) = existing_msg_id
&& !mime_parser.incoming
{
// It sometimes happens that a slow server (usually a classical email server)
// receives a message via SMTP,
// but then the connection to the server dies before it sends the OK response.
@@ -566,6 +557,16 @@ pub(crate) async fn receive_imf_inner(
if !msg_has_pending_smtp_job(context, msg_id).await? {
msg_id.set_delivered(context).await?;
}
}
// These checks must be done before processing of SecureJoin and other special messages.
if mime_parser.pre_message == mimeparser::PreMessageMode::Post {
// Post-Message just replaces the attachment and modifies Params, not the whole message.
// This is done in the `update_from_post_msg` method.
} else if existing_msg_id.is_some() {
info!(
context,
"Message {rfc724_mid} is already in some chat or deleted."
);
return Ok(None);
}
@@ -2088,7 +2089,7 @@ async fn add_parts(
}
handle_edit_delete(context, mime_parser, from_id, &mime_headers).await?;
handle_post_message(context, mime_parser, from_id, state).await?;
update_from_post_msg(context, mime_parser, from_id, state).await?;
if mime_parser.is_system_message == SystemMessage::CallAccepted
|| mime_parser.is_system_message == SystemMessage::CallEnded
@@ -2282,8 +2283,7 @@ INSERT INTO msgs
// Maybe set logging xdc and add gossip topics for webxdcs.
for (part, msg_id) in mime_parser.parts.iter().zip(&created_db_entries) {
if mime_parser.pre_message != PreMessageMode::Post
&& part.typ == Viewtype::Webxdc
if part.typ == Viewtype::Webxdc
&& let Some(topic) = mime_parser.get_header(HeaderDef::IrohGossipTopic)
{
let topic = iroh_topic_from_str(topic)?;
@@ -2433,7 +2433,7 @@ async fn handle_edit_delete(
Ok(())
}
async fn handle_post_message(
async fn update_from_post_msg(
context: &Context,
mime_parser: &MimeMessage,
from_id: ContactId,
@@ -2451,7 +2451,7 @@ async fn handle_post_message(
let Some(msg_id) = message::rfc724_mid_exists(context, &rfc724_mid).await? else {
warn!(
context,
"handle_post_message: {rfc724_mid}: Database entry does not exist."
"update_from_post_msg: {rfc724_mid}: Database entry does not exist."
);
return Ok(());
};
@@ -2459,7 +2459,7 @@ async fn handle_post_message(
// else: message is processed like a normal message
warn!(
context,
"handle_post_message: {rfc724_mid}: Pre-message was not downloaded yet so treat as normal message."
"update_from_post_msg: {rfc724_mid}: Pre-message was not downloaded yet so treat as normal message."
);
return Ok(());
};
@@ -2470,7 +2470,7 @@ async fn handle_post_message(
// Do nothing if safety checks fail, the worst case is the message modifies the chat if the
// sender is a member.
if from_id != original_msg.from_id {
warn!(context, "handle_post_message: {rfc724_mid}: Bad sender.");
warn!(context, "update_from_post_msg: {rfc724_mid}: Bad sender.");
return Ok(());
}
let post_msg_showpadlock = part
@@ -2478,14 +2478,17 @@ async fn handle_post_message(
.get_bool(Param::GuaranteeE2ee)
.unwrap_or_default();
if !post_msg_showpadlock && original_msg.get_showpadlock() {
warn!(context, "handle_post_message: {rfc724_mid}: Not encrypted.");
warn!(
context,
"update_from_post_msg: {rfc724_mid}: Not encrypted."
);
return Ok(());
}
if !part.typ.has_file() {
warn!(
context,
"handle_post_message: {rfc724_mid}: First mime part's message-viewtype has no file."
"update_from_post_msg: {rfc724_mid}: First mime part's message-viewtype has no file."
);
return Ok(());
}
@@ -2516,7 +2519,10 @@ WHERE id=?
part.typ,
part.bytes as isize,
part.error.as_deref().unwrap_or_default(),
state,
match mime_parser.incoming {
true => state,
false => MessageState::Undefined,
},
DownloadState::Done as u32,
original_msg.id,
),

View File

@@ -5696,27 +5696,27 @@ async fn test_mark_message_as_delivered_only_after_sent_out_fully() -> Result<()
.await
.unwrap();
let (pre_msg_id, pre_msg_payload) = first_row_in_smtp_queue(alice).await;
assert_eq!(msg_id, pre_msg_id);
assert!(pre_msg_payload.len() < file_bytes.len());
assert_eq!(msg_id.get_state(alice).await?, MessageState::OutPending);
// Alice receives her own pre-message because of bcc_self
// This should not yet mark the message as delivered,
// because not everything was sent,
// but it does remove the pre-message from the SMTP queue
receive_imf(alice, pre_msg_payload.as_bytes(), false).await?;
assert_eq!(msg_id.get_state(alice).await?, MessageState::OutPending);
let (post_msg_id, post_msg_payload) = first_row_in_smtp_queue(alice).await;
assert_eq!(msg_id, post_msg_id);
assert!(post_msg_payload.len() > file_bytes.len());
assert_eq!(msg_id.get_state(alice).await?, MessageState::OutPending);
// Alice receives her own post-message because of bcc_self
// This should not yet mark the message as delivered,
// because not everything was sent,
// but it does remove the post-message from the SMTP queue.
receive_imf(alice, post_msg_payload.as_bytes(), false).await?;
assert_eq!(msg_id.get_state(alice).await?, MessageState::OutPending);
let (pre_msg_id, pre_msg_payload) = first_row_in_smtp_queue(alice).await;
assert_eq!(msg_id, pre_msg_id);
assert!(pre_msg_payload.len() < file_bytes.len());
assert_eq!(msg_id.get_state(alice).await?, MessageState::OutPending);
// Alice receives her own pre-message because of bcc_self
// This should now mark the message as delivered,
// because everything was sent by now.
receive_imf(alice, post_msg_payload.as_bytes(), false).await?;
receive_imf(alice, pre_msg_payload.as_bytes(), false).await?;
assert_eq!(msg_id.get_state(alice).await?, MessageState::OutDelivered);
Ok(())

View File

@@ -14,7 +14,7 @@ pub(crate) use self::connectivity::ConnectivityStore;
use crate::config::Config;
use crate::contact::{ContactId, RecentlySeenLoop};
use crate::context::Context;
use crate::download::{download_known_post_messages_without_pre_message, download_msgs};
use crate::download::download_msgs;
use crate::ephemeral;
use crate::events::EventType;
use crate::imap::{Imap, session::Session};
@@ -486,7 +486,6 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, mut session: Session)
.await
.context("fetch_move_delete")?;
download_known_post_messages_without_pre_message(ctx, &mut session).await?;
download_msgs(ctx, &mut session)
.await
.context("download_msgs")?;

View File

@@ -379,7 +379,7 @@ pub(crate) async fn send_msg_to_smtp(
if retries > 6 {
context
.sql
.execute("DELETE FROM smtp WHERE id=?", (rowid,))
.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))
.await
.context("Failed to remove message with exceeded retry limit from smtp table")?;
if let Some(mut msg) = Message::load_from_db_optional(context, msg_id).await? {
@@ -457,7 +457,7 @@ pub(crate) async fn send_msg_to_smtp(
}
context
.sql
.execute("DELETE FROM smtp WHERE id=?", (rowid,))
.execute("DELETE FROM smtp WHERE msg_id=?", (msg_id,))
.await?;
}
};

View File

@@ -737,12 +737,14 @@ ORDER BY id"
})
}
/// Returns `SentMessage` instances representing `smtp` rows for the given message. Returned
/// items go in reverse order for historical reasons.
pub async fn get_smtp_rows_for_msg<'a>(&'a self, msg_id: MsgId) -> Vec<SentMessage<'a>> {
let sent_msgs = self
.ctx
.sql
.query_map_vec(
"SELECT id, msg_id, mime, recipients FROM smtp WHERE msg_id=?",
"SELECT id, msg_id, mime, recipients FROM smtp WHERE msg_id=? ORDER BY id DESC",
(msg_id,),
|row| {
let _id: MsgId = row.get(0)?;
@@ -1070,9 +1072,14 @@ ORDER BY id"
/// This is not hooked up to any SMTP-IMAP pipeline, so the other account must call
/// [`TestContext::recv_msg`] with the returned [`SentMessage`] if it wants to receive
/// the message.
///
/// Removes SMTP jobs existed before and marks the corresponding messages as delivered, as
/// tracking of these jobs is probably already lost by the test code.
pub async fn send_msg(&self, chat_id: ChatId, msg: &mut Message) -> SentMessage<'_> {
while self.pop_sent_msg_opt().await.is_some() {}
let msg_id = chat::send_msg(self, chat_id, msg).await.unwrap();
let res = self.pop_sent_msg().await;
let rev_order = false;
let res = self.pop_sent_msg_ex(rev_order).await.unwrap();
assert_eq!(
res.sender_msg_id, msg_id,
"Apparently the message was not actually sent out"

View File

@@ -105,7 +105,7 @@ async fn test_receive_both() -> Result<()> {
assert_eq!(msg.text, "test".to_owned());
forward_msgs(alice, &[alice_msg_id], alice_chat_id).await?;
let rev_order = false;
let rev_order = true;
let msg = bob
.recv_msg(&alice.pop_sent_msg_ex(rev_order).await.unwrap())
.await;

View File

@@ -257,21 +257,21 @@ async fn test_lost_pre_msg() -> Result<()> {
let _pre_msg = alice.pop_sent_msg().await;
let msg = bob.recv_msg(&full_msg).await;
assert_eq!(msg.download_state, DownloadState::Done);
assert_eq!(msg.text, "");
assert_eq!(msg.text, "populate");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_pre_msg_mdn_before_sending_full() -> Result<()> {
pre_msg_mdn_before_sending_full("").await
async fn test_post_msg_mdn_before_sending_pre_msg() -> Result<()> {
post_msg_mdn_before_sending_pre_msg("").await
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_pre_msg_mdn_before_sending_full_with_text() -> Result<()> {
pre_msg_mdn_before_sending_full("text").await
async fn test_post_msg_mdn_before_sending_pre_msg_with_text() -> Result<()> {
post_msg_mdn_before_sending_pre_msg("text").await
}
async fn pre_msg_mdn_before_sending_full(text: &str) -> Result<()> {
async fn post_msg_mdn_before_sending_pre_msg(text: &str) -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
@@ -283,11 +283,11 @@ async fn pre_msg_mdn_before_sending_full(text: &str) -> Result<()> {
msg.set_text(text.to_string());
chat::send_msg(alice, alice_chat_id, &mut msg).await?;
let rev_order = false;
let pre_msg = alice.pop_sent_msg_ex(rev_order).await.unwrap();
let post_msg = alice.pop_sent_msg_ex(rev_order).await.unwrap();
let alice_msg_id = msg.id;
let msg = bob.recv_msg(&pre_msg).await;
assert_eq!(msg.download_state, DownloadState::Available);
let msg = bob.recv_msg(&post_msg).await;
assert_eq!(msg.download_state, DownloadState::Done);
assert_eq!(msg.id.get_state(bob).await?, MessageState::InFresh);
assert_eq!(msg.text, text);
assert!(msg.param.get_bool(Param::WantsMdn).unwrap_or_default());
@@ -308,11 +308,30 @@ async fn pre_msg_mdn_before_sending_full(text: &str) -> Result<()> {
MessageState::OutPending
);
let _full_msg = alice.pop_sent_msg().await;
// Bob's other device receives the pre-message first.
let bob = &tcm.bob().await;
let pre_msg = alice.pop_sent_msg().await;
assert_eq!(
alice_msg_id.get_state(alice).await?,
MessageState::OutDelivered
);
let msg = bob.recv_msg(&pre_msg).await;
assert_eq!(msg.download_state, DownloadState::Available);
assert_eq!(msg.id.get_state(bob).await?, MessageState::InFresh);
assert_eq!(msg.text, text);
assert!(msg.param.get_bool(Param::WantsMdn).unwrap_or_default());
msg.chat_id.accept(bob).await?;
markseen_msgs(bob, vec![msg.id]).await?;
assert_eq!(msg.id.get_state(bob).await?, MessageState::InSeen);
assert_eq!(
bob.sql
.count(
"SELECT COUNT(*) FROM smtp_mdns WHERE from_id=?",
(msg.from_id,)
)
.await?,
1
);
Ok(())
}
@@ -613,8 +632,8 @@ async fn test_webxdc_updates_in_post_message_after_pre_message() -> Result<()> {
.await?;
send_msg(alice, alice_chat_id, &mut alice_instance).await?;
let post_message = alice.pop_sent_msg().await;
let pre_message = alice.pop_sent_msg().await;
let post_message = alice.pop_sent_msg().await;
let bob_instance = bob.recv_msg(&pre_message).await;
assert_eq!(bob_instance.download_state, DownloadState::Available);
@@ -659,8 +678,8 @@ async fn test_large_webxdc_without_text() -> Result<()> {
.await?;
send_msg(bob, bob_chat_id, &mut bob_instance).await?;
let post_message = bob.pop_sent_msg().await;
let pre_message = bob.pop_sent_msg().await;
let post_message = bob.pop_sent_msg().await;
tcm.section("Alice receives a pre-message");
let alice_instance = alice.recv_msg(&pre_message).await;
@@ -704,8 +723,8 @@ async fn test_webxdc_updates_in_post_message_after_deleted_pre_message() -> Resu
.await?;
send_msg(alice, alice_chat_id, &mut alice_instance).await?;
let post_message = alice.pop_sent_msg().await;
let pre_message = alice.pop_sent_msg().await;
let post_message = alice.pop_sent_msg().await;
let bob_instance = bob.recv_msg(&pre_message).await;
assert_eq!(bob_instance.download_state, DownloadState::Available);
@@ -746,8 +765,8 @@ async fn test_webxdc_updates_in_post_message_without_pre_message() -> Result<()>
.await?;
send_msg(alice, alice_chat_id, &mut alice_instance).await?;
let post_message = alice.pop_sent_msg().await;
let pre_message = alice.pop_sent_msg().await;
let post_message = alice.pop_sent_msg().await;
// Bob receives post-message first.
let bob_instance = bob.recv_msg(&post_message).await;