mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
Before, only replies to chat messages were moved to the mvbox because we're removing Chat-Version from outer headers, but there's no much sense in moving only replies and not moving original messages and MDNs. Instead, move all encrypted messages. Users should be informed about this in UIs, so if a user has another PGP-capable MUA, probably they should disable MvboxMove. Moreover, untying this logic from References and In-Reply-To allows to remove them from outer headers too, the "Header Protection for Cryptographically Protected Email" RFC even suggests such a behavior: https://datatracker.ietf.org/doc/html/rfc9788#name-offering-more-ambitious-hea.
326 lines
11 KiB
Rust
326 lines
11 KiB
Rust
use super::*;
|
|
use crate::test_utils::TestContext;
|
|
|
|
#[test]
|
|
fn test_get_folder_meaning_by_name() {
|
|
assert_eq!(get_folder_meaning_by_name("xxx"), FolderMeaning::Unknown);
|
|
assert_eq!(get_folder_meaning_by_name("SPAM"), FolderMeaning::Spam);
|
|
assert_eq!(get_folder_meaning_by_name("Trash"), FolderMeaning::Trash);
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_set_uid_next_validity() {
|
|
let t = TestContext::new_alice().await;
|
|
assert_eq!(get_uid_next(&t.ctx, "Inbox").await.unwrap(), 0);
|
|
assert_eq!(get_uidvalidity(&t.ctx, "Inbox").await.unwrap(), 0);
|
|
|
|
set_uidvalidity(&t.ctx, "Inbox", 7).await.unwrap();
|
|
assert_eq!(get_uidvalidity(&t.ctx, "Inbox").await.unwrap(), 7);
|
|
assert_eq!(get_uid_next(&t.ctx, "Inbox").await.unwrap(), 0);
|
|
|
|
set_uid_next(&t.ctx, "Inbox", 5).await.unwrap();
|
|
set_uidvalidity(&t.ctx, "Inbox", 6).await.unwrap();
|
|
assert_eq!(get_uid_next(&t.ctx, "Inbox").await.unwrap(), 5);
|
|
assert_eq!(get_uidvalidity(&t.ctx, "Inbox").await.unwrap(), 6);
|
|
}
|
|
|
|
#[test]
|
|
fn test_build_sequence_sets() {
|
|
assert_eq!(build_sequence_sets(&[]).unwrap(), vec![]);
|
|
|
|
let cases = vec![
|
|
(vec![1], "1"),
|
|
(vec![3291], "3291"),
|
|
(vec![1, 3, 5, 7, 9, 11], "1,3,5,7,9,11"),
|
|
(vec![1, 2, 3], "1:3"),
|
|
(vec![1, 4, 5, 6], "1,4:6"),
|
|
((1..=500).collect(), "1:500"),
|
|
(vec![3, 4, 8, 9, 10, 11, 39, 50, 2], "3:4,8:11,39,50,2"),
|
|
];
|
|
for (input, s) in cases {
|
|
assert_eq!(
|
|
build_sequence_sets(&input).unwrap(),
|
|
vec![(input, s.into())]
|
|
);
|
|
}
|
|
|
|
let has_number = |(uids, s): &(Vec<u32>, String), number| {
|
|
uids.contains(&number) && s.split(',').any(|n| n.parse::<u32>().unwrap() == number)
|
|
};
|
|
|
|
let numbers: Vec<_> = (2..=500).step_by(2).collect();
|
|
let result = build_sequence_sets(&numbers).unwrap();
|
|
for (_, set) in &result {
|
|
assert!(set.len() < 1010);
|
|
assert!(!set.ends_with(','));
|
|
assert!(!set.starts_with(','));
|
|
}
|
|
assert!(result.len() == 1); // these UIDs fit in one set
|
|
for &number in &numbers {
|
|
assert!(result.iter().any(|r| has_number(r, number)));
|
|
}
|
|
|
|
let numbers: Vec<_> = (1..=1000).step_by(3).collect();
|
|
let result = build_sequence_sets(&numbers).unwrap();
|
|
for (_, set) in &result {
|
|
assert!(set.len() < 1010);
|
|
assert!(!set.ends_with(','));
|
|
assert!(!set.starts_with(','));
|
|
}
|
|
let (last_uids, last_str) = result.last().unwrap();
|
|
assert_eq!(
|
|
last_uids.get((last_uids.len() - 2)..).unwrap(),
|
|
&[997, 1000]
|
|
);
|
|
assert!(last_str.ends_with("997,1000"));
|
|
assert!(result.len() == 2); // This time we need 2 sets
|
|
for &number in &numbers {
|
|
assert!(result.iter().any(|r| has_number(r, number)));
|
|
}
|
|
|
|
let numbers: Vec<_> = (30000000..=30002500).step_by(4).collect();
|
|
let result = build_sequence_sets(&numbers).unwrap();
|
|
for (_, set) in &result {
|
|
assert!(set.len() < 1010);
|
|
assert!(!set.ends_with(','));
|
|
assert!(!set.starts_with(','));
|
|
}
|
|
assert_eq!(result.len(), 6);
|
|
for &number in &numbers {
|
|
assert!(result.iter().any(|r| has_number(r, number)));
|
|
}
|
|
}
|
|
|
|
async fn check_target_folder_combination(
|
|
folder: &str,
|
|
mvbox_move: bool,
|
|
is_encrypted: bool,
|
|
expected_destination: &str,
|
|
accepted_chat: bool,
|
|
outgoing: bool,
|
|
setupmessage: bool,
|
|
) -> Result<()> {
|
|
println!(
|
|
"Testing: For folder {folder}, mvbox_move {mvbox_move}, is_encrypted {is_encrypted}, accepted {accepted_chat}, outgoing {outgoing}, setupmessage {setupmessage}"
|
|
);
|
|
|
|
let t = TestContext::new_alice().await;
|
|
t.ctx
|
|
.set_config(Config::ConfiguredMvboxFolder, Some("DeltaChat"))
|
|
.await?;
|
|
t.ctx
|
|
.set_config(Config::MvboxMove, Some(if mvbox_move { "1" } else { "0" }))
|
|
.await?;
|
|
|
|
if accepted_chat {
|
|
let contact_id = Contact::create(&t.ctx, "", "bob@example.net").await?;
|
|
ChatId::create_for_contact(&t.ctx, contact_id).await?;
|
|
}
|
|
let temp;
|
|
|
|
let bytes = if setupmessage {
|
|
include_bytes!("../../test-data/message/AutocryptSetupMessage.eml")
|
|
} else {
|
|
temp = format!(
|
|
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
|
{}\
|
|
Message-ID: <abc@example.com>\n\
|
|
{}\
|
|
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
|
|
\n\
|
|
hello\n",
|
|
if outgoing {
|
|
"From: alice@example.org\nTo: bob@example.net\n"
|
|
} else {
|
|
"From: bob@example.net\nTo: alice@example.org\n"
|
|
},
|
|
if is_encrypted {
|
|
"Subject: [...]\n\
|
|
Content-Type: multipart/mixed; boundary=\"someboundary\"\n"
|
|
} else {
|
|
"Subject: foo\n"
|
|
},
|
|
);
|
|
temp.as_bytes()
|
|
};
|
|
|
|
let (headers, _) = mailparse::parse_headers(bytes)?;
|
|
let actual = if let Some(config) =
|
|
target_folder_cfg(&t, folder, get_folder_meaning_by_name(folder), &headers).await?
|
|
{
|
|
t.get_config(config).await?
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let expected = if expected_destination == folder {
|
|
None
|
|
} else {
|
|
Some(expected_destination)
|
|
};
|
|
assert_eq!(
|
|
expected,
|
|
actual.as_deref(),
|
|
"For folder {folder}, mvbox_move {mvbox_move}, is_encrypted {is_encrypted}, accepted {accepted_chat}, outgoing {outgoing}, setupmessage {setupmessage}: expected {expected:?}, got {actual:?}"
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
// The tuples are (folder, mvbox_move, is_encrypted, expected_destination)
|
|
const COMBINATIONS_ACCEPTED_CHAT: &[(&str, bool, bool, &str)] = &[
|
|
("INBOX", false, false, "INBOX"),
|
|
("INBOX", false, true, "INBOX"),
|
|
("INBOX", true, false, "INBOX"),
|
|
("INBOX", true, true, "DeltaChat"),
|
|
("Spam", false, false, "INBOX"),
|
|
("Spam", false, true, "INBOX"),
|
|
// Move unencrypted emails in accepted chats from Spam to INBOX, not 100% sure on this, we could
|
|
// also not move unencrypted emails or, if mvbox_move=1, move them to DeltaChat.
|
|
("Spam", true, false, "INBOX"),
|
|
("Spam", true, true, "DeltaChat"),
|
|
];
|
|
|
|
// These are the same as above, but unencrypted messages in Spam stay in Spam.
|
|
const COMBINATIONS_REQUEST: &[(&str, bool, bool, &str)] = &[
|
|
("INBOX", false, false, "INBOX"),
|
|
("INBOX", false, true, "INBOX"),
|
|
("INBOX", true, false, "INBOX"),
|
|
("INBOX", true, true, "DeltaChat"),
|
|
("Spam", false, false, "Spam"),
|
|
("Spam", false, true, "INBOX"),
|
|
("Spam", true, false, "Spam"),
|
|
("Spam", true, true, "DeltaChat"),
|
|
];
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_target_folder_incoming_accepted() -> Result<()> {
|
|
for (folder, mvbox_move, is_encrypted, expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
|
check_target_folder_combination(
|
|
folder,
|
|
*mvbox_move,
|
|
*is_encrypted,
|
|
expected_destination,
|
|
true,
|
|
false,
|
|
false,
|
|
)
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_target_folder_incoming_request() -> Result<()> {
|
|
for (folder, mvbox_move, is_encrypted, expected_destination) in COMBINATIONS_REQUEST {
|
|
check_target_folder_combination(
|
|
folder,
|
|
*mvbox_move,
|
|
*is_encrypted,
|
|
expected_destination,
|
|
false,
|
|
false,
|
|
false,
|
|
)
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_target_folder_outgoing() -> Result<()> {
|
|
// Test outgoing emails
|
|
for (folder, mvbox_move, is_encrypted, expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
|
check_target_folder_combination(
|
|
folder,
|
|
*mvbox_move,
|
|
*is_encrypted,
|
|
expected_destination,
|
|
true,
|
|
true,
|
|
false,
|
|
)
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_target_folder_setupmsg() -> Result<()> {
|
|
// Test setupmessages
|
|
for (folder, mvbox_move, is_encrypted, _expected_destination) in COMBINATIONS_ACCEPTED_CHAT {
|
|
check_target_folder_combination(
|
|
folder,
|
|
*mvbox_move,
|
|
*is_encrypted,
|
|
if folder == &"Spam" { "INBOX" } else { folder }, // Never move setup messages, except if they are in "Spam"
|
|
false,
|
|
true,
|
|
true,
|
|
)
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn test_get_imap_search_command() -> Result<()> {
|
|
let t = TestContext::new_alice().await;
|
|
assert_eq!(
|
|
get_imap_self_sent_search_command(&t.ctx).await?,
|
|
r#"FROM "alice@example.org""#
|
|
);
|
|
|
|
t.ctx.set_primary_self_addr("alice@another.com").await?;
|
|
assert_eq!(
|
|
get_imap_self_sent_search_command(&t.ctx).await?,
|
|
r#"OR (FROM "alice@another.com") (FROM "alice@example.org")"#
|
|
);
|
|
|
|
t.ctx.set_primary_self_addr("alice@third.com").await?;
|
|
assert_eq!(
|
|
get_imap_self_sent_search_command(&t.ctx).await?,
|
|
r#"OR (OR (FROM "alice@third.com") (FROM "alice@another.com")) (FROM "alice@example.org")"#
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_uid_grouper() {
|
|
// Input: sequence of (rowid: i64, uid: u32, target: String)
|
|
// Output: sequence of (target: String, rowid_set: Vec<i64>, uid_set: String)
|
|
let grouper = UidGrouper::from([(1, 2, "INBOX".to_string())]);
|
|
let res: Vec<(String, Vec<i64>, String)> = grouper.into_iter().collect();
|
|
assert_eq!(res, vec![("INBOX".to_string(), vec![1], "2".to_string())]);
|
|
|
|
let grouper = UidGrouper::from([(1, 2, "INBOX".to_string()), (2, 3, "INBOX".to_string())]);
|
|
let res: Vec<(String, Vec<i64>, String)> = grouper.into_iter().collect();
|
|
assert_eq!(
|
|
res,
|
|
vec![("INBOX".to_string(), vec![1, 2], "2:3".to_string())]
|
|
);
|
|
|
|
let grouper = UidGrouper::from([
|
|
(1, 2, "INBOX".to_string()),
|
|
(2, 2, "INBOX".to_string()),
|
|
(3, 3, "INBOX".to_string()),
|
|
]);
|
|
let res: Vec<(String, Vec<i64>, String)> = grouper.into_iter().collect();
|
|
assert_eq!(
|
|
res,
|
|
vec![("INBOX".to_string(), vec![1, 2, 3], "2:3".to_string())]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_setmetadata_device_token() {
|
|
assert_eq!(
|
|
format_setmetadata("INBOX", "foobarbaz"),
|
|
"SETMETADATA \"INBOX\" (/private/devicetoken {9+}\r\nfoobarbaz)"
|
|
);
|
|
assert_eq!(
|
|
format_setmetadata("INBOX", "foo\r\nbar\r\nbaz\r\n"),
|
|
"SETMETADATA \"INBOX\" (/private/devicetoken {15+}\r\nfoo\r\nbar\r\nbaz\r\n)"
|
|
);
|
|
}
|