Compare commits

..

10 Commits

Author SHA1 Message Date
link2xt
600e2804e1 test: remove timeout from pop_sent_msg_ex()
Arbitrary timeouts often result in flaky tests,
especially if CI runners may be paused.
In this case however timeout was not used by any tests
and only slowed down the tests in cases where
no message is expected but non-zero timeout is set.
2026-06-13 17:20:14 +02:00
holger krekel
6cd5b21a26 fix: don't send or process webxdc status updates in pre-messages
it makes no sense to send or receive status updates in pre-messages for large webxdc attachments because they can't be processed anyway.
2026-06-12 17:01:46 +02:00
dependabot[bot]
87c1fb2118 chore(deps): bump EmbarkStudios/cargo-deny-action from 2.0.19 to 2.0.20
Bumps [EmbarkStudios/cargo-deny-action](https://github.com/embarkstudios/cargo-deny-action) from 2.0.19 to 2.0.20.
- [Release notes](https://github.com/embarkstudios/cargo-deny-action/releases)
- [Commits](a531616d8c...bb137d7af7)

---
updated-dependencies:
- dependency-name: EmbarkStudios/cargo-deny-action
  dependency-version: 2.0.20
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-12 09:58:44 +00:00
link2xt
e8b94781a5 fix: do not trash pre-messages without text but with a webxdc update
Webxdc updates go to pre-message and when the message has no text,
the whole pre-message is trashed if it has a webxdc update.
The solution is not to trash pre-message in this case so preview is displayed.
2026-06-11 15:10:14 +00:00
dependabot[bot]
07231f28ae chore(deps): bump taiki-e/install-action from 2.79.10 to 2.81.1
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.79.10 to 2.81.1.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](60ae4ce63c...e49978b799)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.81.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-11 14:31:02 +00:00
iequidoo
64f0d2352c docs(STYLE.md): Require to list columns explicitly in INSERT statements 2026-06-09 12:34:38 -03:00
link2xt
93bf3d6ebb chore: bump version to 2.52.0-dev 2026-06-09 02:18:40 +02:00
link2xt
f05336f793 chore(release): prepare for 2.52.0 2026-06-09 00:40:07 +02:00
link2xt
8df028e9a8 build(nix): fix windows cross-compilation 2026-06-08 17:17:10 +00:00
link2xt
40309ce857 chore: add exception for unmaintained proc-macro-error2 to deny.toml 2026-06-08 17:16:10 +00:00
28 changed files with 194 additions and 124 deletions

View File

@@ -62,7 +62,7 @@ jobs:
with:
show-progress: false
persist-credentials: false
- uses: EmbarkStudios/cargo-deny-action@a531616d8ce3b9177443e48a1159bc945a099823
- uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe
with:
arguments: --workspace --all-features --locked
command: check
@@ -146,7 +146,7 @@ jobs:
cache-bin: false
- name: Install nextest
uses: taiki-e/install-action@60ae4ce63c7aeb6e96d7f572c1ec7fafbb17ca80
uses: taiki-e/install-action@e49978b799e49ff429d162b7a30601a569ab6538
with:
tool: nextest

View File

@@ -1,5 +1,37 @@
# Changelog
## [2.52.0] - 2026-06-09
### Fixes
- Update the channel title after joining if the QR code included a wrong title ([#8260](https://github.com/chatmail/core/pull/8260)).
- Don't send removal message to contact that hasn't been a chat member ([#8298](https://github.com/chatmail/core/pull/8298)).
### Features / Changes
- Add cryptography-related statistics (`number_of_transports`, `key_version`, `key_algorithm`, `pubkey_size`, `number_of_keys`) ([#8293](https://github.com/chatmail/core/pull/8293), [#8297](https://github.com/chatmail/core/pull/8297)).
- Add IMAP folder to `Context::get_info()` ([#8285](https://github.com/chatmail/core/pull/8285)).
### Miscellaneous Tasks
- Update preloaded DNS cache.
- Use default aws-lc-rs cryptography provider for rustls.
- Add exception for unmaintained proc-macro-error2 to deny.toml.
- cargo: bump `pin-project` from 1.1.11 to 1.1.13.
- cargo: bump `tokio` from 1.52.1 to 1.52.3.
- cargo: bump `log` from 0.4.29 to 0.4.30.
- cargo: bump `serde_json` from 1.0.149 to 1.0.150.
- deps: bump EmbarkStudios/cargo-deny-action from 2.0.18 to 2.0.19.
- deps: bump taiki-e/install-action from 2.79.2 to 2.79.10.
### Build system
- nix: fix windows cross-compilation by adding pthreads includes.
### Refactor
- Remove support for building "source" packages for deltachat-rpc-server.
## [2.51.0] - 2026-05-29
### Features / Changes
@@ -8297,3 +8329,4 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
[2.49.0]: https://github.com/chatmail/core/compare/v2.48.0..v2.49.0
[2.50.0]: https://github.com/chatmail/core/compare/v2.49.0..v2.50.0
[2.51.0]: https://github.com/chatmail/core/compare/v2.50.0..v2.51.0
[2.52.0]: https://github.com/chatmail/core/compare/v2.51.0..v2.52.0

10
Cargo.lock generated
View File

@@ -1350,7 +1350,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "2.51.0-dev"
version = "2.52.0-dev"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -1459,7 +1459,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "2.51.0-dev"
version = "2.52.0-dev"
dependencies = [
"anyhow",
"async-channel 2.5.0",
@@ -1480,7 +1480,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "2.51.0-dev"
version = "2.52.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -1496,7 +1496,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "2.51.0-dev"
version = "2.52.0-dev"
dependencies = [
"anyhow",
"deltachat",
@@ -1525,7 +1525,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "2.51.0-dev"
version = "2.52.0-dev"
dependencies = [
"anyhow",
"deltachat",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "2.51.0-dev"
version = "2.52.0-dev"
edition = "2024"
license = "MPL-2.0"
rust-version = "1.89"

View File

@@ -59,6 +59,13 @@ If column is already declared without `NOT NULL`, use `IFNULL` function to provi
Use `HAVING COUNT(*) > 0` clause
to [prevent aggregate functions such as `MIN` and `MAX` from returning `NULL`](https://stackoverflow.com/questions/66527856/aggregate-functions-max-etc-return-null-instead-of-no-rows).
List columns explicitly in `INSERT` statements:
```
INSERT OR IGNORE INTO download (rfc724_mid, msg_id) VALUES (?,0);
```
Otherwise if a new column with default value is added in a future DB version, an upgraded DB can't
be used with the old code, e.g. after transferring a DB from a device running a newer version.
Don't delete unused columns too early, but maybe after several months/releases, unused columns are
still used by older versions, so deleting them breaks downgrading the core or importing a backup in
an older version. Also don't change the column type, consider adding a new column with another name

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "2.51.0-dev"
version = "2.52.0-dev"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "2.51.0-dev"
version = "2.52.0-dev"
description = "DeltaChat JSON-RPC API"
edition = "2021"
license = "MPL-2.0"

View File

@@ -54,5 +54,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "2.51.0-dev"
"version": "2.52.0-dev"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "2.51.0-dev"
version = "2.52.0-dev"
license = "MPL-2.0"
edition = "2021"
repository = "https://github.com/chatmail/core"

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat-rpc-client"
version = "2.51.0-dev"
version = "2.52.0-dev"
license = "MPL-2.0"
description = "Python client for Delta Chat core JSON-RPC interface"
classifiers = [

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "2.51.0-dev"
version = "2.52.0-dev"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"

View File

@@ -15,5 +15,5 @@
},
"type": "module",
"types": "index.d.ts",
"version": "2.51.0-dev"
"version": "2.52.0-dev"
}

View File

@@ -18,6 +18,11 @@ ignore = [
# this should be fixed by upgrading to iroh 1.0 once it is released.
"RUSTSEC-2025-0134",
# Unmaintained proc-macro-error2
# Transitive dependency of typescript-type-def 0.5.13.
# <https://rustsec.org/advisories/RUSTSEC-2026-0173>
"RUSTSEC-2026-0173",
# rustls-webpki v0.102.8
# We cannot upgrade to >=0.103.10 because
# it is a transitive dependency of iroh 0.35.0

View File

@@ -147,6 +147,7 @@
CARGO_BUILD_TARGET = rustTarget;
TARGET_CC = "${pkgsWin64.stdenv.cc}/bin/${pkgsWin64.stdenv.cc.targetPrefix}cc";
CFLAGS_x86_64_pc_windows_gnu = "-I${pkgsWin64.windows.pthreads}/include";
CARGO_BUILD_RUSTFLAGS = [
"-C"
"linker=${TARGET_CC}"
@@ -203,6 +204,7 @@
src = pkgs.lib.cleanSource ./.;
nativeBuildInputs = [
pkgs.perl # Needed to build vendored OpenSSL.
pkgs.nasm # aws-lc-sys requires it
];
depsBuildBuild = [
winCC
@@ -215,6 +217,7 @@
CARGO_BUILD_TARGET = rustTarget;
TARGET_CC = "${winCC}/bin/${winCC.targetPrefix}cc";
CFLAGS_i686_pc_windows_gnu = "-I${pkgsWin32.windows.pthreads}/include";
CARGO_BUILD_RUSTFLAGS = [
"-C"
"linker=${TARGET_CC}"

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "deltachat"
version = "2.51.0-dev"
version = "2.52.0-dev"
license = "MPL-2.0"
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
readme = "README.rst"

View File

@@ -1 +1 @@
2026-05-29
2026-06-09

View File

@@ -3738,19 +3738,17 @@ pub(crate) async fn update_chat_contacts_table(
id: ChatId,
contacts: &BTreeSet<ContactId>,
) -> Result<()> {
// See add_to_chat_contacts_table() for reasoning.
let limit = cmp::max(time().saturating_add(TIMESTAMP_SENT_TOLERANCE), timestamp);
context
.sql
.transaction(move |transaction| {
// Bump `remove_timestamp` even for members from `contacts`.
// Bump `remove_timestamp` to at least `now`
// even for members from `contacts`.
// We add members from `contacts` back below.
transaction.execute(
"UPDATE chats_contacts SET
add_timestamp=MIN(add_timestamp, ?1),
remove_timestamp=MAX(MIN(remove_timestamp,?1), MIN(add_timestamp,?1)+1, ?)
"UPDATE chats_contacts
SET remove_timestamp=MAX(add_timestamp+1, ?)
WHERE chat_id=?",
(limit, timestamp, id),
(timestamp, id),
)?;
if !contacts.is_empty() {
@@ -3762,8 +3760,9 @@ pub(crate) async fn update_chat_contacts_table(
)?;
for contact_id in contacts {
// We bumped `remove_timestamp` for existing rows above,
// so on conflict it is enough to set `add_timestamp = remove_timestamp`.
// We bumped `add_timestamp` for existing rows above,
// so on conflict it is enough to set `add_timestamp = remove_timestamp`
// and this guarantees that `add_timestamp` is no less than `timestamp`.
statement.execute((id, contact_id, timestamp))?;
}
}
@@ -3780,24 +3779,17 @@ pub(crate) async fn add_to_chat_contacts_table(
chat_id: ChatId,
contact_ids: &[ContactId],
) -> Result<()> {
// Our clock may be slow, so limit stored timestamps with `timestamp` if it's bigger. This way
// we only cap remote timestamps if, in addition, remote changes arrive reordered or we do local
// changes. Also allow some tolerance, moreover, previous removals might lend time from the
// future.
let limit = cmp::max(time().saturating_add(TIMESTAMP_SENT_TOLERANCE), timestamp);
context
.sql
.transaction(move |transaction| {
let mut add_statement = transaction.prepare(
"INSERT INTO chats_contacts (chat_id, contact_id, add_timestamp) VALUES(?1, ?2, ?3)
ON CONFLICT (chat_id, contact_id)
DO UPDATE SET
remove_timestamp=MIN(remove_timestamp, ?4),
add_timestamp=MIN(MAX(add_timestamp,remove_timestamp,?3), ?4)",
DO UPDATE SET add_timestamp=MAX(remove_timestamp, ?3)",
)?;
for contact_id in contact_ids {
add_statement.execute((chat_id, contact_id, timestamp, limit))?;
add_statement.execute((chat_id, contact_id, timestamp))?;
}
Ok(())
})
@@ -3816,16 +3808,13 @@ pub(crate) async fn remove_from_chat_contacts_table(
contact_id: ContactId,
) -> Result<bool> {
let now = time();
// See add_to_chat_contacts_table() for reasoning.
let limit = now.saturating_add(TIMESTAMP_SENT_TOLERANCE);
let is_past_member = context
.sql
.execute(
"UPDATE chats_contacts SET
add_timestamp=MIN(add_timestamp, ?1),
remove_timestamp=MAX(MIN(remove_timestamp,?1), MIN(add_timestamp,?1)+1, ?)
"UPDATE chats_contacts
SET remove_timestamp=MAX(add_timestamp+1, ?)
WHERE chat_id=? AND contact_id=?",
(limit, now, chat_id, contact_id),
(now, chat_id, contact_id),
)
.await?
> 0;

View File

@@ -1281,7 +1281,7 @@ async fn test_marknoticed_all_chats() -> Result<()> {
tcm.section("bob: receive messages, accept all chats and send a reply to each messsage");
while let Some(sent_msg) = alice.pop_sent_msg_opt(Duration::default()).await {
while let Some(sent_msg) = alice.pop_sent_msg_opt().await {
let bob_message = bob.recv_msg(&sent_msg).await;
let bob_chat_id = bob_message.chat_id;
bob_chat_id.accept(bob).await?;
@@ -1289,7 +1289,7 @@ async fn test_marknoticed_all_chats() -> Result<()> {
}
tcm.section("alice: receive replies from bob");
while let Some(sent_msg) = bob.pop_sent_msg_opt(Duration::default()).await {
while let Some(sent_msg) = bob.pop_sent_msg_opt().await {
alice.recv_msg(&sent_msg).await;
}
// ensure chats have unread messages
@@ -2815,7 +2815,7 @@ async fn test_cant_remove_nonmember() -> Result<()> {
let alice_charlie_id = alice.add_or_lookup_contact_id(charlie).await;
remove_contact_from_chat(alice, alice_broadcast_id, alice_charlie_id).await?;
assert!(alice.pop_sent_msg_opt(Duration::ZERO).await.is_none());
assert!(alice.pop_sent_msg_opt().await.is_none());
assert!(!remove_from_chat_contacts_table(alice, alice_broadcast_id, alice_charlie_id).await?);
assert!(
!remove_from_chat_contacts_table_without_trace(alice, alice_broadcast_id, alice_charlie_id)
@@ -3064,10 +3064,7 @@ async fn test_broadcast_resend_to_new_member() -> Result<()> {
}
for i in 0..N_MSGS_TO_NEW_BROADCAST_MEMBER {
let rev_order = false;
let resent_msg = alice
.pop_sent_msg_ex(rev_order, Duration::ZERO)
.await
.unwrap();
let resent_msg = alice.pop_sent_msg_ex(rev_order).await.unwrap();
let fiona_msg = fiona.recv_msg(&resent_msg).await;
assert_eq!(fiona_msg.chat_id, fiona_bc_id);
assert_eq!(fiona_msg.text, (i + 1).to_string());
@@ -3084,7 +3081,7 @@ async fn test_broadcast_resend_to_new_member() -> Result<()> {
);
bob.recv_msg_trash(&resent_msg).await;
}
assert!(alice.pop_sent_msg_opt(Duration::ZERO).await.is_none());
assert!(alice.pop_sent_msg_opt().await.is_none());
Ok(())
}
@@ -3547,12 +3544,7 @@ async fn test_chat_description(
tcm.section("Alice calls set_chat_description() without actually changing the description");
set_chat_description(alice, alice_chat_id, "ä ẟ 😂").await?;
assert!(
alice
.pop_sent_msg_opt(Duration::from_secs(0))
.await
.is_none()
);
assert!(alice.pop_sent_msg_opt().await.is_none());
Ok(())
}
@@ -3577,12 +3569,7 @@ async fn test_setting_empty_chat_description() -> Result<()> {
let _hi = alice.send_text(alice_chat_id, "hi").await;
set_chat_description(alice, alice_chat_id, "").await?;
assert!(
alice
.pop_sent_msg_opt(Duration::from_secs(0))
.await
.is_none()
);
assert!(alice.pop_sent_msg_opt().await.is_none());
Ok(())
}

View File

@@ -172,7 +172,7 @@ async fn test_ephemeral_unpromoted() -> Result<()> {
chat_id
.set_ephemeral_timer(&alice, Timer::Enabled { duration: 60 })
.await?;
let sent = alice.pop_sent_msg_opt(Duration::from_secs(1)).await;
let sent = alice.pop_sent_msg_opt().await;
assert!(sent.is_none());
assert_eq!(
chat_id.get_ephemeral_timer(&alice).await?,
@@ -182,13 +182,13 @@ async fn test_ephemeral_unpromoted() -> Result<()> {
// Promote the group.
send_text_msg(&alice, chat_id, "hi!".to_string()).await?;
assert!(chat_id.is_promoted(&alice).await?);
let sent = alice.pop_sent_msg_opt(Duration::from_secs(1)).await;
let sent = alice.pop_sent_msg_opt().await;
assert!(sent.is_some());
chat_id
.set_ephemeral_timer(&alice.ctx, Timer::Disabled)
.await?;
let sent = alice.pop_sent_msg_opt(Duration::from_secs(1)).await;
let sent = alice.pop_sent_msg_opt().await;
assert!(sent.is_some());
assert_eq!(chat_id.get_ephemeral_timer(&alice).await?, Timer::Disabled);

View File

@@ -1813,14 +1813,15 @@ impl MimeFactory {
HeaderDef::IrohGossipTopic.get_headername(),
mail_builder::headers::raw::Raw::new(topic).into(),
));
if let (Some(json), _) = context
.render_webxdc_status_update_object(
msg.id,
StatusUpdateSerial::MIN,
StatusUpdateSerial::MAX,
None,
)
.await?
if !matches!(self.pre_message_mode, PreMessageMode::Pre { .. })
&& let (Some(json), _) = context
.render_webxdc_status_update_object(
msg.id,
StatusUpdateSerial::MIN,
StatusUpdateSerial::MAX,
None,
)
.await?
{
parts.push(context.build_status_update_part(&json));
}

View File

@@ -1,6 +1,5 @@
use mailparse::ParsedMail;
use std::mem;
use std::time::Duration;
use super::*;
use crate::{
@@ -1435,7 +1434,7 @@ async fn test_intended_recipient_fingerprint() -> Result<()> {
let chat_id = chat::create_group(t, "").await?;
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
assert!(t.pop_sent_msg_opt(Duration::ZERO).await.is_none());
assert!(t.pop_sent_msg_opt().await.is_none());
for (i, member) in members.iter().enumerate() {
let contact = t.add_or_lookup_contact(member).await;

View File

@@ -826,7 +826,9 @@ UPDATE config SET value=? WHERE keyname='configured_addr' AND value!=?1
}
}
if let Some(ref status_update) = mime_parser.webxdc_status_update {
if let Some(ref status_update) = mime_parser.webxdc_status_update
&& !matches!(mime_parser.pre_message, PreMessageMode::Pre { .. })
{
let can_info_msg;
let instance = if mime_parser
.parts
@@ -1215,6 +1217,8 @@ async fn decide_chat_assignment(
// Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them
info!(context, "Email is probably just a draft (TRASH).");
true
} else if matches!(mime_parser.pre_message, PreMessageMode::Pre { .. }) {
false
} else if mime_parser.webxdc_status_update.is_some() && mime_parser.parts.len() == 1 {
if let Some(part) = mime_parser.parts.first() {
if part.typ == Viewtype::Text && part.msg.is_empty() {

View File

@@ -2030,12 +2030,12 @@ async fn test_no_smtp_job_for_self_chat() -> Result<()> {
let chat_id = bob.get_self_chat().await.id;
let mut msg = Message::new_text("Happy birthday to me".to_string());
chat::send_msg(bob, chat_id, &mut msg).await?;
assert!(bob.pop_sent_msg_opt(Duration::ZERO).await.is_none());
assert!(bob.pop_sent_msg_opt().await.is_none());
bob.set_config_bool(Config::BccSelf, true).await?;
let mut msg = Message::new_text("Happy birthday to me".to_string());
chat::send_msg(bob, chat_id, &mut msg).await?;
assert!(bob.pop_sent_msg_opt(Duration::ZERO).await.is_some());
assert!(bob.pop_sent_msg_opt().await.is_some());
Ok(())
}

View File

@@ -950,7 +950,7 @@ async fn test_parallel_setup_contact(bob_deletes_fiona_contact: bool) -> Result<
Contact::delete(bob, bob_fiona_contact_id).await?;
bob.recv_msg_trash(&sent_fiona_vc_auth_required).await;
let sent = bob.pop_sent_msg_opt(Duration::ZERO).await;
let sent = bob.pop_sent_msg_opt().await;
assert!(sent.is_none());
} else {
bob.recv_msg_trash(&sent_fiona_vc_auth_required).await;
@@ -1235,7 +1235,7 @@ async fn test_rejoin_group() -> Result<()> {
assert_eq!(progress, 1000);
// Bob does not send any more messages by scanning the QR code.
assert!(bob.pop_sent_msg_opt(Duration::ZERO).await.is_none());
assert!(bob.pop_sent_msg_opt().await.is_none());
Ok(())
}

View File

@@ -8,7 +8,7 @@ use std::ops::{Deref, DerefMut};
use std::panic;
use std::path::Path;
use std::sync::{Arc, LazyLock};
use std::time::{Duration, Instant};
use std::time::Duration;
use anyhow::Result;
use async_channel::{self as channel, Receiver, Sender};
@@ -282,9 +282,9 @@ impl TestContextManager {
inviters: &[&TestContext],
qr: &str,
) -> ChatId {
assert!(joiner.pop_sent_msg_opt(Duration::ZERO).await.is_none());
assert!(joiner.pop_sent_msg_opt().await.is_none());
for inviter in inviters {
assert!(inviter.pop_sent_msg_opt(Duration::ZERO).await.is_none());
assert!(inviter.pop_sent_msg_opt().await.is_none());
}
let chat_id = join_securejoin(&joiner.ctx, qr).await.unwrap();
@@ -292,14 +292,14 @@ impl TestContextManager {
for _ in 0..2 {
let mut something_sent = false;
let rev_order = false;
if let Some(sent) = joiner.pop_sent_msg_ex(rev_order, Duration::ZERO).await {
if let Some(sent) = joiner.pop_sent_msg_ex(rev_order).await {
for inviter in inviters {
inviter.recv_msg_opt(&sent).await;
}
something_sent = true;
}
for inviter in inviters {
if let Some(sent) = inviter.pop_sent_msg_ex(rev_order, Duration::ZERO).await {
if let Some(sent) = inviter.pop_sent_msg_ex(rev_order).await {
joiner.recv_msg_opt(&sent).await;
something_sent = true;
}
@@ -638,22 +638,17 @@ impl TestContext {
///
/// Panics if there is no message or on any error.
pub async fn pop_sent_msg(&self) -> SentMessage<'_> {
self.pop_sent_msg_opt(Duration::from_secs(3))
self.pop_sent_msg_opt()
.await
.expect("no sent message found in jobs table")
}
pub async fn pop_sent_msg_opt(&self, timeout: Duration) -> Option<SentMessage<'_>> {
pub async fn pop_sent_msg_opt(&self) -> Option<SentMessage<'_>> {
let rev_order = true;
self.pop_sent_msg_ex(rev_order, timeout).await
self.pop_sent_msg_ex(rev_order).await
}
pub async fn pop_sent_msg_ex(
&self,
rev_order: bool,
timeout: Duration,
) -> Option<SentMessage<'_>> {
let start = Instant::now();
pub async fn pop_sent_msg_ex(&self, rev_order: bool) -> Option<SentMessage<'_>> {
let mut query = "
SELECT id, msg_id, mime, recipients
FROM smtp
@@ -662,28 +657,18 @@ ORDER BY id"
if rev_order {
query += " DESC";
}
let (rowid, msg_id, payload, recipients) = loop {
let row = self
.ctx
.sql
.query_row_optional(&query, (), |row| {
let rowid: i64 = row.get(0)?;
let msg_id: MsgId = row.get(1)?;
let mime: String = row.get(2)?;
let recipients: String = row.get(3)?;
Ok((rowid, msg_id, mime, recipients))
})
.await
.expect("query_row_optional failed");
if let Some(row) = row {
break row;
}
if start.elapsed() < timeout {
tokio::time::sleep(Duration::from_millis(100)).await;
} else {
return None;
}
};
let (rowid, msg_id, payload, recipients) = self
.ctx
.sql
.query_row_optional(&query, (), |row| {
let rowid: i64 = row.get(0)?;
let msg_id: MsgId = row.get(1)?;
let mime: String = row.get(2)?;
let recipients: String = row.get(3)?;
Ok((rowid, msg_id, mime, recipients))
})
.await
.expect("query_row_optional failed")?;
self.ctx
.sql
.execute("DELETE FROM smtp WHERE id=?;", (rowid,))

View File

@@ -1,5 +1,4 @@
//! Tests about forwarding and saving Pre-Messages
use std::time::Duration;
use anyhow::Result;
use pretty_assertions::assert_eq;
@@ -108,12 +107,7 @@ async fn test_receive_both() -> Result<()> {
forward_msgs(alice, &[alice_msg_id], alice_chat_id).await?;
let rev_order = false;
let msg = bob
.recv_msg(
&alice
.pop_sent_msg_ex(rev_order, Duration::ZERO)
.await
.unwrap(),
)
.recv_msg(&alice.pop_sent_msg_ex(rev_order).await.unwrap())
.await;
assert_eq!(msg.download_state(), DownloadState::Available);
assert_eq!(msg.is_forwarded(), true);

View File

@@ -534,6 +534,17 @@ async fn test_webxdc_updates_in_post_message_after_pre_message() -> Result<()> {
let alice_chat_id = alice.create_chat_id(bob).await;
// regression test where updates get assigned to an unrelated prior webxdc message
let mut unrelated_xdc = Message::new(Viewtype::Webxdc);
unrelated_xdc.set_file_from_bytes(
alice,
"first.xdc",
include_bytes!("../../../test-data/webxdc/minimal.xdc"),
None,
)?;
send_msg(alice, alice_chat_id, &mut unrelated_xdc).await?;
let bob_unrelated_webxdc = bob.recv_msg(&alice.pop_sent_msg().await).await;
let big_webxdc_app = big_webxdc_app().await?;
let mut alice_instance = Message::new(Viewtype::Webxdc);
@@ -552,6 +563,14 @@ async fn test_webxdc_updates_in_post_message_after_pre_message() -> Result<()> {
let bob_instance = bob.recv_msg(&pre_message).await;
assert_eq!(bob_instance.download_state, DownloadState::Available);
// don't accidentally assign updates from a pre-message to parent message
assert_eq!(
bob.get_webxdc_status_updates(bob_unrelated_webxdc.id, StatusUpdateSerial::new(0))
.await?,
"[]"
);
bob.recv_msg_trash(&post_message).await;
let bob_instance = Message::load_from_db(bob, bob_instance.id).await?;
assert_eq!(bob_instance.download_state, DownloadState::Done);
@@ -565,6 +584,51 @@ async fn test_webxdc_updates_in_post_message_after_pre_message() -> Result<()> {
Ok(())
}
/// Tests sending large webxdc without text.
///
/// This is a regression test, previously pre-message
/// was trashed when it had no text.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_large_webxdc_without_text() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
tcm.section("Bob sends large webxdc without attached text message.");
let bob_chat_id = bob.create_chat_id(alice).await;
let big_webxdc_app = big_webxdc_app().await?;
let mut bob_instance = Message::new(Viewtype::Webxdc);
bob_instance.set_file_from_bytes(bob, "test.xdc", &big_webxdc_app, None)?;
bob_chat_id.set_draft(bob, Some(&mut bob_instance)).await?;
bob.send_webxdc_status_update(bob_instance.id, r#"{"payload":42, "info":"i"}"#)
.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;
tcm.section("Alice receives a pre-message");
let alice_instance = alice.recv_msg(&pre_message).await;
assert_eq!(alice_instance.download_state, DownloadState::Available);
tcm.section("Alice receives a post-message");
alice.recv_msg_trash(&post_message).await;
let alice_instance = Message::load_from_db(alice, alice_instance.id).await?;
assert_eq!(alice_instance.download_state, DownloadState::Done);
let alice_file_path = alice_instance.get_file(alice).expect("No file");
tokio::fs::try_exists(alice_file_path).await?;
assert_eq!(
alice
.get_webxdc_status_updates(alice_instance.id, StatusUpdateSerial::new(0))
.await?,
r#"[{"payload":42,"info":"i","serial":1,"max_serial":1}]"#
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_webxdc_updates_in_post_message_after_deleted_pre_message() -> Result<()> {
let mut tcm = TestContextManager::new();

View File

@@ -145,7 +145,6 @@ mod tests {
use crate::message::{Message, Viewtype};
use crate::test_utils::TestContext;
use anyhow::Result;
use std::time::Duration;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_default_integrations_are_single_device() -> Result<()> {
@@ -158,7 +157,7 @@ mod tests {
t.set_webxdc_integration(file.to_str().unwrap()).await?;
// default integrations are shipped with the apps and should not be sent over the wire
let sent = t.pop_sent_msg_opt(Duration::from_secs(1)).await;
let sent = t.pop_sent_msg_opt().await;
assert!(sent.is_none());
Ok(())