mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
This way, the statistics / self-reporting bot will be made into an opt-in regular sending of statistics, where you enable the setting once and then they will be sent automatically. The statistics will be sent to a bot, so that the user can see exactly which data is being sent, and how often. The chat will be archived and muted by default, so that it doesn't disturb the user. The collected statistics will focus on the public-key-verification that is performed while scanning a QR code. Later on, we can add more statistics to collect. **Context:** _This is just to give a rough idea; I would need to write a lot more than a few paragraphs in order to fully explain all the context here_. End-to-end encrypted messengers are generally susceptible to MitM attacks. In order to mitigate against this, messengers offer some way of verifying the chat partner's public key. However, numerous studies found that most popular messengers implement this public-key-verification in a way that is not understood by users, and therefore ineffective - [a 2021 "State of Knowledge" paper concludes:](https://dl.acm.org/doi/pdf/10.1145/3558482.3581773) > Based on our evaluation, we have determined that all current E2EE apps, particularly when operating in opportunistic E2EE mode, are incapable of repelling active man-in-the-middle (MitM) attacks. In addition, we find that none of the current E2EE apps provide better and more usable [public key verification] ceremonies, resulting in insecure E2EE communications against active MitM attacks. This is why Delta Chat tries to go a different route: When the user scans a QR code (regardless of whether the QR code creates a 1:1 chat, invites to a group, or subscribes to a broadcast channel), a public-key-verification is performed in the background, without the user even having to know about this. The statistics collected here are supposed to tell us whether Delta Chat succeeds to nudge the users into using QR codes in a way that is secure against MitM attacks. **Plan for statistics-sending:** - [x] Get this PR reviewed and merged (but don't make it available in the UI yet; if Android wants to make a release in the meantime, I will create a PR that removes the option there) - [x] Set the interval to 1 week again (right now, it's 1 minute for testing) - [ ] Write something for people who are interested in what exactly we count, and link to it (see `TODO[blog post]` in the code) - [ ] Prepare a short survey for participants - [ ] Fine-tune the texts at https://github.com/deltachat/deltachat-android/pull/3794, and get it reviewed and merged - [ ] After the next release, ask people to enable the statistics-sending
638 lines
21 KiB
Rust
638 lines
21 KiB
Rust
use anyhow::Context as _;
|
|
use strum::IntoEnumIterator;
|
|
use tempfile::tempdir;
|
|
|
|
use super::*;
|
|
use crate::chat::{Chat, MuteDuration, get_chat_contacts, get_chat_msgs, send_msg, set_muted};
|
|
use crate::chatlist::Chatlist;
|
|
use crate::constants::Chattype;
|
|
use crate::message::Message;
|
|
use crate::receive_imf::receive_imf;
|
|
use crate::test_utils::{E2EE_INFO_MSGS, TestContext};
|
|
use crate::tools::{SystemTime, create_outgoing_rfc724_mid};
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_wrong_db() -> Result<()> {
|
|
let tmp = tempfile::tempdir()?;
|
|
let dbfile = tmp.path().join("db.sqlite");
|
|
tokio::fs::write(&dbfile, b"123").await?;
|
|
let res = Context::new(&dbfile, 1, Events::new(), StockStrings::new()).await?;
|
|
|
|
// Broken database is indistinguishable from encrypted one.
|
|
assert_eq!(res.is_open().await, false);
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_get_fresh_msgs() {
|
|
let t = TestContext::new().await;
|
|
let fresh = t.get_fresh_msgs().await.unwrap();
|
|
assert!(fresh.is_empty())
|
|
}
|
|
|
|
async fn receive_msg(t: &TestContext, chat: &Chat) {
|
|
let members = get_chat_contacts(t, chat.id).await.unwrap();
|
|
let contact = Contact::get_by_id(t, *members.first().unwrap())
|
|
.await
|
|
.unwrap();
|
|
let msg = format!(
|
|
"From: {}\n\
|
|
To: alice@example.org\n\
|
|
Message-ID: <{}>\n\
|
|
Chat-Version: 1.0\n\
|
|
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
|
\n\
|
|
hello\n",
|
|
contact.get_addr(),
|
|
create_outgoing_rfc724_mid()
|
|
);
|
|
println!("{msg}");
|
|
receive_imf(t, msg.as_bytes(), false).await.unwrap();
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_get_fresh_msgs_and_muted_chats() {
|
|
// receive various mails in 3 chats
|
|
let t = TestContext::new_alice().await;
|
|
let bob = t.create_chat_with_contact("", "bob@g.it").await;
|
|
let claire = t.create_chat_with_contact("", "claire@g.it").await;
|
|
let dave = t.create_chat_with_contact("", "dave@g.it").await;
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 0);
|
|
|
|
receive_msg(&t, &bob).await;
|
|
assert_eq!(get_chat_msgs(&t, bob.id).await.unwrap().len(), 1);
|
|
assert_eq!(bob.id.get_fresh_msg_cnt(&t).await.unwrap(), 1);
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 1);
|
|
|
|
receive_msg(&t, &claire).await;
|
|
receive_msg(&t, &claire).await;
|
|
assert_eq!(get_chat_msgs(&t, claire.id).await.unwrap().len(), 2);
|
|
assert_eq!(claire.id.get_fresh_msg_cnt(&t).await.unwrap(), 2);
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 3);
|
|
|
|
receive_msg(&t, &dave).await;
|
|
receive_msg(&t, &dave).await;
|
|
receive_msg(&t, &dave).await;
|
|
assert_eq!(get_chat_msgs(&t, dave.id).await.unwrap().len(), 3);
|
|
assert_eq!(dave.id.get_fresh_msg_cnt(&t).await.unwrap(), 3);
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 6);
|
|
|
|
// mute one of the chats
|
|
set_muted(&t, claire.id, MuteDuration::Forever)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(claire.id.get_fresh_msg_cnt(&t).await.unwrap(), 2);
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 4); // muted claires messages are no longer counted
|
|
|
|
// receive more messages
|
|
receive_msg(&t, &bob).await;
|
|
receive_msg(&t, &claire).await;
|
|
receive_msg(&t, &dave).await;
|
|
assert_eq!(get_chat_msgs(&t, claire.id).await.unwrap().len(), 3);
|
|
assert_eq!(claire.id.get_fresh_msg_cnt(&t).await.unwrap(), 3);
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 6); // muted claire is not counted
|
|
|
|
// unmute claire again
|
|
set_muted(&t, claire.id, MuteDuration::NotMuted)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(claire.id.get_fresh_msg_cnt(&t).await.unwrap(), 3);
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 9); // claire is counted again
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_get_fresh_msgs_and_muted_until() {
|
|
let t = TestContext::new_alice().await;
|
|
let bob = t.create_chat_with_contact("", "bob@g.it").await;
|
|
receive_msg(&t, &bob).await;
|
|
assert_eq!(get_chat_msgs(&t, bob.id).await.unwrap().len(), 1);
|
|
|
|
// chat is unmuted by default, here and in the following assert(),
|
|
// we check mainly that the SQL-statements in is_muted() and get_fresh_msgs()
|
|
// have the same view to the database.
|
|
assert!(!bob.is_muted());
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 1);
|
|
|
|
// test get_fresh_msgs() with mute_until in the future
|
|
set_muted(
|
|
&t,
|
|
bob.id,
|
|
MuteDuration::Until(SystemTime::now() + Duration::from_secs(3600)),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
let bob = Chat::load_from_db(&t, bob.id).await.unwrap();
|
|
assert!(bob.is_muted());
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 0);
|
|
|
|
// to test get_fresh_msgs() with mute_until in the past,
|
|
// we need to modify the database directly
|
|
t.sql
|
|
.execute(
|
|
"UPDATE chats SET muted_until=? WHERE id=?;",
|
|
(time() - 3600, bob.id),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
let bob = Chat::load_from_db(&t, bob.id).await.unwrap();
|
|
assert!(!bob.is_muted());
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 1);
|
|
|
|
// test get_fresh_msgs() with "forever" mute_until
|
|
set_muted(&t, bob.id, MuteDuration::Forever).await.unwrap();
|
|
let bob = Chat::load_from_db(&t, bob.id).await.unwrap();
|
|
assert!(bob.is_muted());
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 0);
|
|
|
|
// to test get_fresh_msgs() with invalid mute_until (everything < -1),
|
|
// that results in "muted forever" by definition.
|
|
t.sql
|
|
.execute("UPDATE chats SET muted_until=-2 WHERE id=?;", (bob.id,))
|
|
.await
|
|
.unwrap();
|
|
let bob = Chat::load_from_db(&t, bob.id).await.unwrap();
|
|
assert!(!bob.is_muted());
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 1);
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_muted_context() -> Result<()> {
|
|
let t = TestContext::new_alice().await;
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 0);
|
|
t.set_config(Config::IsMuted, Some("1")).await?;
|
|
let chat = t.create_chat_with_contact("", "bob@g.it").await;
|
|
receive_msg(&t, &chat).await;
|
|
|
|
// muted contexts should still show dimmed badge counters eg. in the sidebars,
|
|
// (same as muted chats show dimmed badge counters in the chatlist)
|
|
// therefore the fresh messages count should not be affected.
|
|
assert_eq!(t.get_fresh_msgs().await.unwrap().len(), 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_blobdir_exists() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let dbfile = tmp.path().join("db.sqlite");
|
|
Context::new(&dbfile, 1, Events::new(), StockStrings::new())
|
|
.await
|
|
.unwrap();
|
|
let blobdir = tmp.path().join("db.sqlite-blobs");
|
|
assert!(blobdir.is_dir());
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_wrong_blogdir() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let dbfile = tmp.path().join("db.sqlite");
|
|
let blobdir = tmp.path().join("db.sqlite-blobs");
|
|
tokio::fs::write(&blobdir, b"123").await.unwrap();
|
|
let res = Context::new(&dbfile, 1, Events::new(), StockStrings::new()).await;
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_sqlite_parent_not_exists() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let subdir = tmp.path().join("subdir");
|
|
let dbfile = subdir.join("db.sqlite");
|
|
let dbfile2 = dbfile.clone();
|
|
Context::new(&dbfile, 1, Events::new(), StockStrings::new())
|
|
.await
|
|
.unwrap();
|
|
assert!(subdir.is_dir());
|
|
assert!(dbfile2.is_file());
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_with_empty_blobdir() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let dbfile = tmp.path().join("db.sqlite");
|
|
let blobdir = PathBuf::new();
|
|
let res = Context::with_blobdir(
|
|
dbfile,
|
|
blobdir,
|
|
1,
|
|
Events::new(),
|
|
StockStrings::new(),
|
|
Default::default(),
|
|
);
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_with_blobdir_not_exists() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let dbfile = tmp.path().join("db.sqlite");
|
|
let blobdir = tmp.path().join("blobs");
|
|
let res = Context::with_blobdir(
|
|
dbfile,
|
|
blobdir,
|
|
1,
|
|
Events::new(),
|
|
StockStrings::new(),
|
|
Default::default(),
|
|
);
|
|
assert!(res.is_err());
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn no_crashes_on_context_deref() {
|
|
let t = TestContext::new().await;
|
|
std::mem::drop(t);
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_get_info() {
|
|
let t = TestContext::new().await;
|
|
|
|
let info = t.get_info().await.unwrap();
|
|
assert!(info.contains_key("database_dir"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_info_no_context() {
|
|
let info = get_info();
|
|
assert!(info.contains_key("deltachat_core_version"));
|
|
assert!(!info.contains_key("database_dir"));
|
|
assert_eq!(info.get("level").unwrap(), "awesome");
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_get_info_completeness() {
|
|
// For easier debugging,
|
|
// get_info() shall return all important information configurable by the Config-values.
|
|
//
|
|
// There are exceptions for Config-values considered to be unimportant,
|
|
// too sensitive or summarized in another item.
|
|
let skip_from_get_info = vec![
|
|
"addr",
|
|
"displayname",
|
|
"imap_certificate_checks",
|
|
"mail_server",
|
|
"mail_user",
|
|
"mail_pw",
|
|
"mail_port",
|
|
"mail_security",
|
|
"notify_about_wrong_pw",
|
|
"selfstatus",
|
|
"send_server",
|
|
"send_user",
|
|
"send_pw",
|
|
"send_port",
|
|
"send_security",
|
|
"server_flags",
|
|
"skip_start_messages",
|
|
"smtp_certificate_checks",
|
|
"proxy_url", // May contain passwords, don't leak it to the logs.
|
|
"socks5_enabled", // SOCKS5 options are deprecated.
|
|
"socks5_host",
|
|
"socks5_port",
|
|
"socks5_user",
|
|
"socks5_password",
|
|
"key_id",
|
|
"webxdc_integration",
|
|
"device_token",
|
|
"encrypted_device_token",
|
|
"stats_last_update",
|
|
"stats_last_old_contact_id",
|
|
];
|
|
let t = TestContext::new().await;
|
|
let info = t.get_info().await.unwrap();
|
|
for key in Config::iter() {
|
|
let key: String = key.to_string();
|
|
if !skip_from_get_info.contains(&&*key)
|
|
&& !key.starts_with("configured")
|
|
&& !key.starts_with("sys.")
|
|
{
|
|
assert!(
|
|
info.contains_key(&*key),
|
|
"'{key}' missing in get_info() output"
|
|
);
|
|
}
|
|
|
|
if skip_from_get_info.contains(&&*key) {
|
|
assert!(
|
|
!info.contains_key(&*key),
|
|
"'{key}' should not be in get_info() output"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_search_msgs() -> Result<()> {
|
|
let alice = TestContext::new_alice().await;
|
|
let self_talk = ChatId::create_for_contact(&alice, ContactId::SELF).await?;
|
|
let chat = alice
|
|
.create_chat_with_contact("Bob", "bob@example.org")
|
|
.await;
|
|
|
|
// Global search finds nothing.
|
|
let res = alice.search_msgs(None, "foo").await?;
|
|
assert!(res.is_empty());
|
|
|
|
// Search in chat with Bob finds nothing.
|
|
let res = alice.search_msgs(Some(chat.id), "foo").await?;
|
|
assert!(res.is_empty());
|
|
|
|
// Add messages to chat with Bob.
|
|
let mut msg1 = Message::new_text("foobar".to_string());
|
|
send_msg(&alice, chat.id, &mut msg1).await?;
|
|
|
|
let mut msg2 = Message::new_text("barbaz".to_string());
|
|
send_msg(&alice, chat.id, &mut msg2).await?;
|
|
|
|
alice.send_text(chat.id, "Δ-Chat").await;
|
|
|
|
// Global search with a part of text finds the message.
|
|
let res = alice.search_msgs(None, "ob").await?;
|
|
assert_eq!(res.len(), 1);
|
|
|
|
// Global search for "bar" matches both "foobar" and "barbaz".
|
|
let res = alice.search_msgs(None, "bar").await?;
|
|
assert_eq!(res.len(), 2);
|
|
|
|
// Message added later is returned first.
|
|
assert_eq!(res.first(), Some(&msg2.id));
|
|
assert_eq!(res.get(1), Some(&msg1.id));
|
|
|
|
// Search is case-insensitive.
|
|
for chat_id in [None, Some(chat.id)] {
|
|
let res = alice.search_msgs(chat_id, "δ-chat").await?;
|
|
assert_eq!(res.len(), 1);
|
|
}
|
|
|
|
// Global search with longer text does not find any message.
|
|
let res = alice.search_msgs(None, "foobarbaz").await?;
|
|
assert!(res.is_empty());
|
|
|
|
// Search for random string finds nothing.
|
|
let res = alice.search_msgs(None, "abc").await?;
|
|
assert!(res.is_empty());
|
|
|
|
// Search in chat with Bob finds the message.
|
|
let res = alice.search_msgs(Some(chat.id), "foo").await?;
|
|
assert_eq!(res.len(), 1);
|
|
|
|
// Search in Saved Messages does not find the message.
|
|
let res = alice.search_msgs(Some(self_talk), "foo").await?;
|
|
assert!(res.is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_search_unaccepted_requests() -> Result<()> {
|
|
let t = TestContext::new_alice().await;
|
|
receive_imf(
|
|
&t,
|
|
b"From: BobBar <bob@example.org>\n\
|
|
To: alice@example.org\n\
|
|
Subject: foo\n\
|
|
Message-ID: <msg1234@example.org>\n\
|
|
Chat-Version: 1.0\n\
|
|
Date: Tue, 25 Oct 2022 13:37:00 +0000\n\
|
|
\n\
|
|
hello bob, foobar test!\n",
|
|
false,
|
|
)
|
|
.await?;
|
|
let chat_id = t.get_last_msg().await.get_chat_id();
|
|
let chat = Chat::load_from_db(&t, chat_id).await?;
|
|
assert_eq!(chat.get_type(), Chattype::Single);
|
|
assert!(chat.is_contact_request());
|
|
|
|
assert_eq!(Chatlist::try_load(&t, 0, None, None).await?.len(), 1);
|
|
assert_eq!(
|
|
Chatlist::try_load(&t, 0, Some("BobBar"), None).await?.len(),
|
|
1
|
|
);
|
|
assert_eq!(t.search_msgs(None, "foobar").await?.len(), 1);
|
|
assert_eq!(t.search_msgs(Some(chat_id), "foobar").await?.len(), 1);
|
|
|
|
chat_id.block(&t).await?;
|
|
|
|
assert_eq!(Chatlist::try_load(&t, 0, None, None).await?.len(), 0);
|
|
assert_eq!(
|
|
Chatlist::try_load(&t, 0, Some("BobBar"), None).await?.len(),
|
|
0
|
|
);
|
|
assert_eq!(t.search_msgs(None, "foobar").await?.len(), 0);
|
|
assert_eq!(t.search_msgs(Some(chat_id), "foobar").await?.len(), 0);
|
|
|
|
let contact_ids = get_chat_contacts(&t, chat_id).await?;
|
|
Contact::unblock(&t, *contact_ids.first().unwrap()).await?;
|
|
|
|
assert_eq!(Chatlist::try_load(&t, 0, None, None).await?.len(), 1);
|
|
assert_eq!(
|
|
Chatlist::try_load(&t, 0, Some("BobBar"), None).await?.len(),
|
|
1
|
|
);
|
|
assert_eq!(t.search_msgs(None, "foobar").await?.len(), 1);
|
|
assert_eq!(t.search_msgs(Some(chat_id), "foobar").await?.len(), 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_limit_search_msgs() -> Result<()> {
|
|
let alice = TestContext::new_alice().await;
|
|
let chat = alice
|
|
.create_chat_with_contact("Bob", "bob@example.org")
|
|
.await;
|
|
|
|
// Add 999 messages
|
|
let mut msg = Message::new_text("foobar".to_string());
|
|
for _ in 0..999 {
|
|
send_msg(&alice, chat.id, &mut msg).await?;
|
|
}
|
|
let res = alice.search_msgs(None, "foo").await?;
|
|
assert_eq!(res.len(), 999);
|
|
|
|
// Add one more message, no limit yet
|
|
send_msg(&alice, chat.id, &mut msg).await?;
|
|
let res = alice.search_msgs(None, "foo").await?;
|
|
assert_eq!(res.len(), 1000);
|
|
|
|
// Add one more message, that one is truncated then
|
|
send_msg(&alice, chat.id, &mut msg).await?;
|
|
let res = alice.search_msgs(None, "foo").await?;
|
|
assert_eq!(res.len(), 1000);
|
|
|
|
// In-chat should not be not limited
|
|
let res = alice.search_msgs(Some(chat.id), "foo").await?;
|
|
assert_eq!(res.len(), 1001);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_check_passphrase() -> Result<()> {
|
|
let dir = tempdir()?;
|
|
let dbfile = dir.path().join("db.sqlite");
|
|
|
|
let context = ContextBuilder::new(dbfile.clone())
|
|
.with_id(1)
|
|
.build()
|
|
.await
|
|
.context("failed to create context")?;
|
|
assert_eq!(context.open("foo".to_string()).await?, true);
|
|
assert_eq!(context.is_open().await, true);
|
|
drop(context);
|
|
|
|
let context = ContextBuilder::new(dbfile)
|
|
.with_id(2)
|
|
.build()
|
|
.await
|
|
.context("failed to create context")?;
|
|
assert_eq!(context.is_open().await, false);
|
|
assert_eq!(context.check_passphrase("bar".to_string()).await?, false);
|
|
assert_eq!(context.open("false".to_string()).await?, false);
|
|
assert_eq!(context.open("foo".to_string()).await?, true);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_context_change_passphrase() -> Result<()> {
|
|
let dir = tempdir()?;
|
|
let dbfile = dir.path().join("db.sqlite");
|
|
|
|
let context = ContextBuilder::new(dbfile)
|
|
.with_id(1)
|
|
.build()
|
|
.await
|
|
.context("failed to create context")?;
|
|
assert_eq!(context.open("foo".to_string()).await?, true);
|
|
assert_eq!(context.is_open().await, true);
|
|
|
|
context
|
|
.set_config(Config::Addr, Some("alice@example.org"))
|
|
.await?;
|
|
|
|
context
|
|
.change_passphrase("bar".to_string())
|
|
.await
|
|
.context("Failed to change passphrase")?;
|
|
|
|
assert_eq!(
|
|
context.get_config(Config::Addr).await?.unwrap(),
|
|
"alice@example.org"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_ongoing() -> Result<()> {
|
|
let context = TestContext::new().await;
|
|
|
|
// No ongoing process allocated.
|
|
assert!(context.shall_stop_ongoing().await);
|
|
|
|
let receiver = context.alloc_ongoing().await?;
|
|
|
|
// Cannot allocate another ongoing process while the first one is running.
|
|
assert!(context.alloc_ongoing().await.is_err());
|
|
|
|
// Stop signal is not sent yet.
|
|
assert!(receiver.try_recv().is_err());
|
|
|
|
assert!(!context.shall_stop_ongoing().await);
|
|
|
|
// Send the stop signal.
|
|
context.stop_ongoing().await;
|
|
|
|
// Receive stop signal.
|
|
receiver.recv().await?;
|
|
|
|
assert!(context.shall_stop_ongoing().await);
|
|
|
|
// Ongoing process is still running even though stop signal was received,
|
|
// so another one cannot be allocated.
|
|
assert!(context.alloc_ongoing().await.is_err());
|
|
|
|
context.free_ongoing().await;
|
|
|
|
// No ongoing process allocated, should have been stopped already.
|
|
assert!(context.shall_stop_ongoing().await);
|
|
|
|
// Another ongoing process can be allocated now.
|
|
let _receiver = context.alloc_ongoing().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_get_next_msgs() -> Result<()> {
|
|
let alice = TestContext::new_alice().await;
|
|
let bob = TestContext::new_bob().await;
|
|
|
|
let alice_chat = alice.create_chat(&bob).await;
|
|
|
|
assert_eq!(alice.get_next_msgs().await?.len(), E2EE_INFO_MSGS);
|
|
assert!(bob.get_next_msgs().await?.is_empty());
|
|
|
|
let sent_msg = alice.send_text(alice_chat.id, "Hi Bob").await;
|
|
let received_msg = bob.recv_msg(&sent_msg).await;
|
|
|
|
let bob_next_msg_ids = bob.get_next_msgs().await?;
|
|
assert_eq!(bob_next_msg_ids.len(), 1);
|
|
assert_eq!(bob_next_msg_ids.first(), Some(&received_msg.id));
|
|
|
|
bob.set_config_u32(Config::LastMsgId, received_msg.id.to_u32())
|
|
.await?;
|
|
assert!(bob.get_next_msgs().await?.is_empty());
|
|
|
|
// Next messages include self-sent messages.
|
|
let alice_next_msg_ids = alice.get_next_msgs().await?;
|
|
assert_eq!(alice_next_msg_ids.len(), 1);
|
|
assert_eq!(alice_next_msg_ids.first(), Some(&sent_msg.sender_msg_id));
|
|
|
|
alice
|
|
.set_config_u32(Config::LastMsgId, sent_msg.sender_msg_id.to_u32())
|
|
.await?;
|
|
assert!(alice.get_next_msgs().await?.is_empty());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_cache_is_cleared_when_io_is_started() -> Result<()> {
|
|
let alice = TestContext::new_alice().await;
|
|
assert_eq!(
|
|
alice.get_config(Config::ShowEmails).await?,
|
|
Some("2".to_string())
|
|
);
|
|
|
|
// Change the config circumventing the cache
|
|
// This simulates what the notification plugin on iOS might do
|
|
// because it runs in a different process
|
|
alice
|
|
.sql
|
|
.execute(
|
|
"INSERT OR REPLACE INTO config (keyname, value) VALUES ('show_emails', '0')",
|
|
(),
|
|
)
|
|
.await?;
|
|
|
|
// Alice's Delta Chat doesn't know about it yet:
|
|
assert_eq!(
|
|
alice.get_config(Config::ShowEmails).await?,
|
|
Some("2".to_string())
|
|
);
|
|
|
|
// Starting IO will fail of course because no server settings are configured,
|
|
// but it should invalidate the caches:
|
|
alice.start_io().await;
|
|
|
|
assert_eq!(
|
|
alice.get_config(Config::ShowEmails).await?,
|
|
Some("0".to_string())
|
|
);
|
|
|
|
Ok(())
|
|
}
|