Files
chatmail-core/src/contact/contact_tests.rs
iequidoo dea1b414db feat: Case-insensitive search for non-ASCII chat and contact names (#7477)
This makes `Contact::get_all()` and `Chatlist::try_load()` case-insensitive for non-ASCII chat and
contact names as well. The same approach as in f6f4ccc6ea "feat:
Case-insensitive search for non-ASCII messages (#5052)" is used: `chats.name_normalized` and
`contacts.name_normalized` colums are added which store lowercased/normalized names (for a contact,
if the name is unset, it's a normalized authname). If a normalized name is the same as the
chat/contact name, it's not stored to reduce the db size. A db migration is added for 10000 random
chats and the same number of the most recently seen contacts, for users it will probably migrate all
chats/contacts and for bots which may have more data it's not important.
2025-12-05 05:11:29 -03:00

1390 lines
51 KiB
Rust

use deltachat_contact_tools::{addr_cmp, may_be_valid_addr};
use super::*;
use crate::chat::{Chat, get_chat_contacts, send_text_msg};
use crate::chatlist::Chatlist;
use crate::receive_imf::receive_imf;
use crate::test_utils::{self, TestContext, TestContextManager, TimeShiftFalsePositiveNote, sync};
#[test]
fn test_contact_id_values() {
// Some FFI users need to have the values of these fixed, how naughty. But let's
// make sure we don't modify them anyway.
assert_eq!(ContactId::UNDEFINED.to_u32(), 0);
assert_eq!(ContactId::SELF.to_u32(), 1);
assert_eq!(ContactId::INFO.to_u32(), 2);
assert_eq!(ContactId::DEVICE.to_u32(), 5);
assert_eq!(ContactId::LAST_SPECIAL.to_u32(), 9);
}
#[test]
fn test_may_be_valid_addr() {
assert_eq!(may_be_valid_addr(""), false);
assert_eq!(may_be_valid_addr("user@domain.tld"), true);
assert_eq!(may_be_valid_addr("uuu"), false);
assert_eq!(may_be_valid_addr("dd.tt"), false);
assert_eq!(may_be_valid_addr("tt.dd@uu"), true);
assert_eq!(may_be_valid_addr("u@d"), true);
assert_eq!(may_be_valid_addr("u@d."), false);
assert_eq!(may_be_valid_addr("u@d.t"), true);
assert_eq!(may_be_valid_addr("u@d.tt"), true);
assert_eq!(may_be_valid_addr("u@.tt"), true);
assert_eq!(may_be_valid_addr("@d.tt"), false);
assert_eq!(may_be_valid_addr("<da@d.tt"), false);
assert_eq!(may_be_valid_addr("sk <@d.tt>"), false);
assert_eq!(may_be_valid_addr("as@sd.de>"), false);
assert_eq!(may_be_valid_addr("ask dkl@dd.tt"), false);
assert_eq!(may_be_valid_addr("user@domain.tld."), false);
}
#[test]
fn test_normalize_addr() {
assert_eq!(addr_normalize("mailto:john@doe.com"), "john@doe.com");
assert_eq!(addr_normalize(" hello@world.com "), "hello@world.com");
assert_eq!(addr_normalize("John@Doe.com"), "john@doe.com");
}
#[test]
fn test_split_address_book() {
let book = "Name one\nAddress one\nName two\nAddress two\nrest name";
let list = split_address_book(book);
assert_eq!(
list,
vec![("Name one", "Address one"), ("Name two", "Address two")]
)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_contacts() -> Result<()> {
let mut tcm = TestContextManager::new();
let context = tcm.bob().await;
let alice = tcm.alice().await;
alice
.set_config(Config::Displayname, Some("MyNameIsΔ"))
.await?;
// Alice is not in the contacts yet.
let contacts = Contact::get_all(&context.ctx, 0, Some("Alice")).await?;
assert_eq!(contacts.len(), 0);
let contacts = Contact::get_all(&context.ctx, 0, Some("MyNameIsΔ")).await?;
assert_eq!(contacts.len(), 0);
let claire_id = Contact::create(&context, "Δ-someone", "claire@example.org").await?;
let dave_id = Contact::create(&context, "", "dave@example.org").await?;
let id = context.add_or_lookup_contact_id(&alice).await;
assert_ne!(id, ContactId::UNDEFINED);
let contact = Contact::get_by_id(&context, id).await.unwrap();
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_authname(), "MyNameIsΔ");
assert_eq!(contact.get_display_name(), "MyNameIsΔ");
// Search by name.
let contacts = Contact::get_all(&context, 0, Some("myname")).await?;
assert_eq!(contacts.len(), 1);
assert_eq!(contacts.first(), Some(&id));
// Search by address.
let contacts = Contact::get_all(&context, 0, Some("alice@example.org")).await?;
assert_eq!(contacts.len(), 1);
assert_eq!(contacts.first(), Some(&id));
let contacts = Contact::get_all(&context, 0, Some("Foobar")).await?;
assert_eq!(contacts.len(), 0);
// Set Alice name manually.
id.set_name(&context, "Δ-someone").await?;
let contact = Contact::get_by_id(&context.ctx, id).await.unwrap();
assert_eq!(contact.get_name(), "Δ-someone");
assert_eq!(contact.get_authname(), "MyNameIsΔ");
assert_eq!(contact.get_display_name(), "Δ-someone");
// Not searchable by authname, because it is not displayed.
let contacts = Contact::get_all(&context, 0, Some("MyName")).await?;
assert_eq!(contacts.len(), 0);
for add_self in [0, constants::DC_GCL_ADD_SELF] {
info!(&context, "add_self={add_self}");
// Search key-contacts by display name (same as manually set name).
let contacts = Contact::get_all(&context.ctx, add_self, Some("Δ-someone")).await?;
assert_eq!(contacts, vec![id]);
let contacts = Contact::get_all(&context.ctx, add_self, Some("δ-someon")).await?;
assert_eq!(contacts, vec![id]);
// Get all key-contacts.
let contacts = Contact::get_all(&context, add_self, None).await?;
match add_self {
0 => assert_eq!(contacts, vec![id]),
_ => assert_eq!(contacts, vec![id, ContactId::SELF]),
}
}
// Search address-contacts by display name.
let contacts = Contact::get_all(&context, constants::DC_GCL_ADDRESS, Some("Δ-someone")).await?;
assert_eq!(contacts, vec![claire_id]);
// Get all address-contacts. Newer contacts go first.
let contacts = Contact::get_all(&context, constants::DC_GCL_ADDRESS, None).await?;
assert_eq!(contacts, vec![dave_id, claire_id]);
let contacts = Contact::get_all(
&context,
constants::DC_GCL_ADDRESS | constants::DC_GCL_ADD_SELF,
None,
)
.await?;
assert_eq!(contacts, vec![dave_id, claire_id, ContactId::SELF]);
// Reset the user-provided name for Alice.
id.set_name(&context, "").await?;
let contact = Contact::get_by_id(&context.ctx, id).await.unwrap();
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_authname(), "MyNameIsΔ");
assert_eq!(contact.get_display_name(), "MyNameIsΔ");
let contacts = Contact::get_all(&context, 0, Some("MyName")).await?;
assert_eq!(contacts.len(), 1);
let contacts = Contact::get_all(&context, 0, Some("δ")).await?;
assert_eq!(contacts.len(), 1);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_is_self_addr() -> Result<()> {
let t = TestContext::new().await;
assert_eq!(t.is_self_addr("me@me.org").await?, false);
t.configure_addr("you@you.net").await;
assert_eq!(t.is_self_addr("me@me.org").await?, false);
assert_eq!(t.is_self_addr("you@you.net").await?, true);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_add_or_lookup() {
// add some contacts, this also tests add_address_book()
let t = TestContext::new_alice().await;
let book = concat!(
" Name one \n one@eins.org \n",
"Name two\ntwo@deux.net\n",
"Invalid\n+1234567890\n", // invalid, should be ignored
"\nthree@drei.sam\n",
"Name two\ntwo@deux.net\n", // should not be added again
"\nWonderland, Alice <alice@w.de>\n",
);
assert_eq!(Contact::add_address_book(&t, book).await.unwrap(), 4);
// check first added contact, this modifies authname because it is empty
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"bla foo",
&ContactAddress::new("one@eins.org").unwrap(),
Origin::IncomingUnknownTo,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_id(), contact_id);
assert_eq!(contact.get_name(), "Name one");
assert_eq!(contact.get_authname(), "bla foo");
assert_eq!(contact.get_display_name(), "Name one");
assert_eq!(contact.get_addr(), "one@eins.org");
assert_eq!(contact.get_name_n_addr(), "Name one (one@eins.org)");
// modify first added contact
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t,
"Real one",
&ContactAddress::new(" one@eins.org ").unwrap(),
Origin::ManuallyCreated,
)
.await
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "Real one");
assert_eq!(contact.get_addr(), "one@eins.org");
assert!(!contact.is_blocked());
// check third added contact (contact without name)
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"",
&ContactAddress::new("three@drei.sam").unwrap(),
Origin::IncomingUnknownTo,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "three@drei.sam");
assert_eq!(contact.get_addr(), "three@drei.sam");
assert_eq!(contact.get_name_n_addr(), "three@drei.sam");
// add name to third contact from incoming message (this becomes authorized name)
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t,
"m. serious",
&ContactAddress::new("three@drei.sam").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_name_n_addr(), "m. serious (three@drei.sam)");
assert!(!contact.is_blocked());
// manually edit name of third contact (does not changed authorized name)
let (contact_id_test, sth_modified) = Contact::add_or_lookup(
&t,
"schnucki",
&ContactAddress::new("three@drei.sam").unwrap(),
Origin::ManuallyCreated,
)
.await
.unwrap();
assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "m. serious");
assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)");
assert!(!contact.is_blocked());
// Fourth contact:
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"",
&ContactAddress::new("alice@w.de").unwrap(),
Origin::IncomingUnknownTo,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::None);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "Wonderland, Alice");
assert_eq!(contact.get_display_name(), "Wonderland, Alice");
assert_eq!(contact.get_addr(), "alice@w.de");
assert_eq!(contact.get_name_n_addr(), "Wonderland, Alice (alice@w.de)");
// check SELF
let contact = Contact::get_by_id(&t, ContactId::SELF).await.unwrap();
assert_eq!(contact.get_name(), stock_str::self_msg(&t).await);
assert_eq!(contact.get_addr(), "alice@example.org");
assert!(!contact.is_blocked());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_contact_name_changes() -> Result<()> {
let t = TestContext::new_alice().await;
// first message creates contact and one-to-one-chat without name set
receive_imf(
&t,
b"From: f@example.org\n\
To: alice@example.org\n\
Subject: foo\n\
Message-ID: <1234-1@example.org>\n\
Chat-Version: 1.0\n\
Date: Sun, 29 May 2022 08:37:57 +0000\n\
\n\
hello one\n",
false,
)
.await?;
let chat_id = t.get_last_msg().await.get_chat_id();
chat_id.accept(&t).await?;
assert_eq!(Chat::load_from_db(&t, chat_id).await?.name, "f@example.org");
let chatlist = Chatlist::try_load(&t, 0, Some("f@example.org"), None).await?;
assert_eq!(chatlist.len(), 1);
let contacts = get_chat_contacts(&t, chat_id).await?;
let contact_id = contacts.first().unwrap();
let contact = Contact::get_by_id(&t, *contact_id).await?;
assert_eq!(contact.get_authname(), "");
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "f@example.org");
assert_eq!(contact.get_name_n_addr(), "f@example.org");
let contacts = Contact::get_all(&t, 0, Some("f@example.org")).await?;
assert_eq!(contacts.len(), 0);
// second message inits the name
receive_imf(
&t,
b"From: Flobbyfoo <f@example.org>\n\
To: alice@example.org\n\
Subject: foo\n\
Message-ID: <1234-2@example.org>\n\
Chat-Version: 1.0\n\
Date: Sun, 29 May 2022 08:38:57 +0000\n\
\n\
hello two\n",
false,
)
.await?;
let chat_id = t.get_last_msg().await.get_chat_id();
assert_eq!(Chat::load_from_db(&t, chat_id).await?.name, "Flobbyfoo");
let chatlist = Chatlist::try_load(&t, 0, Some("flobbyfoo"), None).await?;
assert_eq!(chatlist.len(), 1);
let contact = Contact::get_by_id(&t, *contact_id).await?;
assert_eq!(contact.get_authname(), "Flobbyfoo");
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "Flobbyfoo");
assert_eq!(contact.get_name_n_addr(), "Flobbyfoo (f@example.org)");
let contacts = Contact::get_all(&t, 0, Some("f@example.org")).await?;
assert_eq!(contacts.len(), 0);
let contacts = Contact::get_all(&t, 0, Some("flobbyfoo")).await?;
assert_eq!(contacts.len(), 0);
// third message changes the name
receive_imf(
&t,
b"From: Foo Flobby <f@example.org>\n\
To: alice@example.org\n\
Subject: foo\n\
Message-ID: <1234-3@example.org>\n\
Chat-Version: 1.0\n\
Date: Sun, 29 May 2022 08:39:57 +0000\n\
\n\
hello three\n",
false,
)
.await?;
let chat_id = t.get_last_msg().await.get_chat_id();
assert_eq!(Chat::load_from_db(&t, chat_id).await?.name, "Foo Flobby");
let chatlist = Chatlist::try_load(&t, 0, Some("Flobbyfoo"), None).await?;
assert_eq!(chatlist.len(), 0);
let chatlist = Chatlist::try_load(&t, 0, Some("Foo Flobby"), None).await?;
assert_eq!(chatlist.len(), 1);
let contact = Contact::get_by_id(&t, *contact_id).await?;
assert_eq!(contact.get_authname(), "Foo Flobby");
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "Foo Flobby");
assert_eq!(contact.get_name_n_addr(), "Foo Flobby (f@example.org)");
let contacts = Contact::get_all(&t, 0, Some("f@example.org")).await?;
assert_eq!(contacts.len(), 0);
let contacts = Contact::get_all(&t, 0, Some("flobbyfoo")).await?;
assert_eq!(contacts.len(), 0);
let contacts = Contact::get_all(&t, 0, Some("Foo Flobby")).await?;
assert_eq!(contacts.len(), 0);
// change name manually
let test_id = Contact::create(&t, "Falk", "f@example.org").await?;
assert_eq!(*contact_id, test_id);
assert_eq!(Chat::load_from_db(&t, chat_id).await?.name, "Falk");
let chatlist = Chatlist::try_load(&t, 0, Some("Falk"), None).await?;
assert_eq!(chatlist.len(), 1);
let contact = Contact::get_by_id(&t, *contact_id).await?;
assert_eq!(contact.get_authname(), "Foo Flobby");
assert_eq!(contact.get_name(), "Falk");
assert_eq!(contact.get_display_name(), "Falk");
assert_eq!(contact.get_name_n_addr(), "Falk (f@example.org)");
let contacts = Contact::get_all(&t, 0, Some("f@example.org")).await?;
assert_eq!(contacts.len(), 0);
let contacts = Contact::get_all(&t, 0, Some("falk")).await?;
assert_eq!(contacts.len(), 0);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_delete() -> Result<()> {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
assert!(Contact::delete(&alice, ContactId::SELF).await.is_err());
// Create Bob contact
let contact_id = alice.add_or_lookup_contact_id(&bob).await;
let chat = alice.create_chat(&bob).await;
assert_eq!(
Contact::get_all(&alice, 0, Some("bob@example.net"))
.await?
.len(),
1
);
// If a contact has ongoing chats, contact is only hidden on deletion
Contact::delete(&alice, contact_id).await?;
let contact = Contact::get_by_id(&alice, contact_id).await?;
assert_eq!(contact.origin, Origin::Hidden);
assert_eq!(
Contact::get_all(&alice, 0, Some("bob@example.net"))
.await?
.len(),
0
);
// Delete chat.
chat.get_id().delete(&alice).await?;
// Can delete contact physically now
Contact::delete(&alice, contact_id).await?;
assert!(Contact::get_by_id(&alice, contact_id).await.is_err());
assert_eq!(
Contact::get_all(&alice, 0, Some("bob@example.net"))
.await?
.len(),
0
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_delete_and_recreate_contact() -> Result<()> {
let mut tcm = TestContextManager::new();
let t = tcm.alice().await;
let bob = tcm.bob().await;
// test recreation after physical deletion
let contact_id1 = t.add_or_lookup_contact_id(&bob).await;
assert_eq!(
Contact::get_all(&t, 0, Some("bob@example.net"))
.await?
.len(),
1
);
Contact::delete(&t, contact_id1).await?;
assert!(Contact::get_by_id(&t, contact_id1).await.is_err());
assert_eq!(
Contact::get_all(&t, 0, Some("bob@example.net"))
.await?
.len(),
0
);
let contact_id2 = t.add_or_lookup_contact_id(&bob).await;
assert_ne!(contact_id2, contact_id1);
assert_eq!(
Contact::get_all(&t, 0, Some("bob@example.net"))
.await?
.len(),
1
);
// test recreation after hiding
t.create_chat(&bob).await;
Contact::delete(&t, contact_id2).await?;
let contact = Contact::get_by_id(&t, contact_id2).await?;
assert_eq!(contact.origin, Origin::Hidden);
assert_eq!(
Contact::get_all(&t, 0, Some("bob@example.net"))
.await?
.len(),
0
);
let contact_id3 = t.add_or_lookup_contact_id(&bob).await;
let contact = Contact::get_by_id(&t, contact_id3).await?;
assert_eq!(contact.origin, Origin::CreateChat);
assert_eq!(contact_id3, contact_id2);
assert_eq!(
Contact::get_all(&t, 0, Some("bob@example.net"))
.await?
.len(),
1
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_remote_authnames() {
let t = TestContext::new().await;
// incoming mail `From: bob1 <bob@example.org>` - this should init authname
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"bob1",
&ContactAddress::new("bob@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::Created);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "bob1");
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "bob1");
// incoming mail `From: bob2 <bob@example.org>` - this should update authname
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"bob2",
&ContactAddress::new("bob@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "bob2");
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "bob2");
// manually edit name to "bob3" - authname should be still be "bob2" as given in `From:` above
let contact_id = Contact::create(&t, "bob3", "bob@example.org")
.await
.unwrap();
assert!(!contact_id.is_special());
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "bob2");
assert_eq!(contact.get_name(), "bob3");
assert_eq!(contact.get_display_name(), "bob3");
// incoming mail `From: bob4 <bob@example.org>` - this should update authname, manually given name is still "bob3"
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"bob4",
&ContactAddress::new("bob@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
assert!(!contact_id.is_special());
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "bob4");
assert_eq!(contact.get_name(), "bob3");
assert_eq!(contact.get_display_name(), "bob3");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_remote_authnames_create_empty() {
let t = TestContext::new().await;
// manually create "claire@example.org" without a given name
let contact_id = Contact::create(&t, "", "claire@example.org").await.unwrap();
assert!(!contact_id.is_special());
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "");
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "claire@example.org");
// incoming mail `From: claire1 <claire@example.org>` - this should update authname
let (contact_id_same, sth_modified) = Contact::add_or_lookup(
&t,
"claire1",
&ContactAddress::new("claire@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "claire1");
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "claire1");
// incoming mail `From: claire2 <claire@example.org>` - this should update authname
let (contact_id_same, sth_modified) = Contact::add_or_lookup(
&t,
"claire2",
&ContactAddress::new("claire@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "claire2");
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "claire2");
}
/// Regression test.
///
/// In the past, "Not Bob" name was stuck until "Bob" changed the name to "Not Bob" and back in
/// the "From:" field or user set the name to empty string manually.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_remote_authnames_update_to() -> Result<()> {
let t = TestContext::new().await;
// Incoming message from Bob.
let (contact_id, sth_modified) = Contact::add_or_lookup(
&t,
"Bob",
&ContactAddress::new("bob@example.org")?,
Origin::IncomingUnknownFrom,
)
.await?;
assert_eq!(sth_modified, Modifier::Created);
let contact = Contact::get_by_id(&t, contact_id).await?;
assert_eq!(contact.get_display_name(), "Bob");
// Incoming message from someone else with "Not Bob" <bob@example.org> in the "To:" field.
let (contact_id_same, sth_modified) = Contact::add_or_lookup(
&t,
"Not Bob",
&ContactAddress::new("bob@example.org")?,
Origin::IncomingUnknownTo,
)
.await?;
assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified);
let contact = Contact::get_by_id(&t, contact_id).await?;
assert_eq!(contact.get_display_name(), "Not Bob");
// Incoming message from Bob, changing the name back.
let (contact_id_same, sth_modified) = Contact::add_or_lookup(
&t,
"Bob",
&ContactAddress::new("bob@example.org")?,
Origin::IncomingUnknownFrom,
)
.await?;
assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified); // This was None until the bugfix
let contact = Contact::get_by_id(&t, contact_id).await?;
assert_eq!(contact.get_display_name(), "Bob");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_remote_authnames_edit_empty() {
let t = TestContext::new().await;
// manually create "dave@example.org"
let contact_id = Contact::create(&t, "dave1", "dave@example.org")
.await
.unwrap();
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "");
assert_eq!(contact.get_name(), "dave1");
assert_eq!(contact.get_display_name(), "dave1");
// incoming mail `From: dave2 <dave@example.org>` - this should update authname
Contact::add_or_lookup(
&t,
"dave2",
&ContactAddress::new("dave@example.org").unwrap(),
Origin::IncomingUnknownFrom,
)
.await
.unwrap();
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "dave2");
assert_eq!(contact.get_name(), "dave1");
assert_eq!(contact.get_display_name(), "dave1");
// manually clear the name
Contact::create(&t, "", "dave@example.org").await.unwrap();
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "dave2");
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "dave2");
}
#[test]
fn test_addr_cmp() {
assert!(addr_cmp("AA@AA.ORG", "aa@aa.ORG"));
assert!(addr_cmp(" aa@aa.ORG ", "AA@AA.ORG"));
assert!(addr_cmp(" mailto:AA@AA.ORG", "Aa@Aa.orG"));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_name_in_address() {
let t = TestContext::new().await;
let contact_id = Contact::create(&t, "", "<dave@example.org>").await.unwrap();
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_addr(), "dave@example.org");
let contact_id = Contact::create(&t, "", "Mueller, Dave <dave@example.org>")
.await
.unwrap();
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "Mueller, Dave");
assert_eq!(contact.get_addr(), "dave@example.org");
let contact_id = Contact::create(&t, "name1", "name2 <dave@example.org>")
.await
.unwrap();
let contact = Contact::get_by_id(&t, contact_id).await.unwrap();
assert_eq!(contact.get_name(), "name1");
assert_eq!(contact.get_addr(), "dave@example.org");
assert!(
Contact::create(&t, "", "<dskjfdslk@sadklj.dk")
.await
.is_err()
);
assert!(
Contact::create(&t, "", "<dskjf>dslk@sadklj.dk>")
.await
.is_err()
);
assert!(Contact::create(&t, "", "dskjfdslksadklj.dk").await.is_err());
assert!(
Contact::create(&t, "", "dskjfdslk@sadklj.dk>")
.await
.is_err()
);
assert!(Contact::create(&t, "", "dskjf dslk@d.e").await.is_err());
assert!(
Contact::create(&t, "", "<dskjf dslk@sadklj.dk")
.await
.is_err()
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_lookup_id_by_addr() {
let t = TestContext::new().await;
let id = Contact::lookup_id_by_addr(&t.ctx, "the.other@example.net", Origin::Unknown)
.await
.unwrap();
assert!(id.is_none());
let other_id = Contact::create(&t.ctx, "The Other", "the.other@example.net")
.await
.unwrap();
let id = Contact::lookup_id_by_addr(&t.ctx, "the.other@example.net", Origin::Unknown)
.await
.unwrap();
assert_eq!(id, Some(other_id));
let alice = TestContext::new_alice().await;
let id = Contact::lookup_id_by_addr(&alice.ctx, "alice@example.org", Origin::Unknown)
.await
.unwrap();
assert_eq!(id, Some(ContactId::SELF));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_contact_get_color() -> Result<()> {
let t = TestContext::new().await;
let contact_id = Contact::create(&t, "name", "name@example.net").await?;
let color1 = Contact::get_by_id(&t, contact_id).await?.get_color();
assert_eq!(color1, 0x4844e2);
let t = TestContext::new().await;
let contact_id = Contact::create(&t, "prename name", "name@example.net").await?;
let color2 = Contact::get_by_id(&t, contact_id).await?.get_color();
assert_eq!(color2, color1);
let t = TestContext::new().await;
let contact_id = Contact::create(&t, "Name", "nAme@exAmple.NET").await?;
let color3 = Contact::get_by_id(&t, contact_id).await?.get_color();
assert_eq!(color3, color1);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_self_color() -> Result<()> {
let mut tcm = TestContextManager::new();
let t = &tcm.unconfigured().await;
t.configure_addr("alice@example.org").await;
assert!(t.is_configured().await?);
let self_contact = Contact::get_by_id(t, ContactId::SELF).await?;
let color = self_contact.get_color();
assert_eq!(color, 0x808080);
let color = self_contact.get_or_gen_color(t).await?;
assert_ne!(color, 0x808080);
let color1 = self_contact.get_or_gen_color(t).await?;
assert_eq!(color1, color);
let bob = &tcm.bob().await;
assert_eq!(bob.add_or_lookup_contact(t).await.get_color(), color);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_contact_get_encrinfo() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
// Return error for special IDs
let encrinfo = Contact::get_encrinfo(alice, ContactId::SELF).await;
assert!(encrinfo.is_err());
let encrinfo = Contact::get_encrinfo(alice, ContactId::DEVICE).await;
assert!(encrinfo.is_err());
let address_contact_bob_id = alice.add_or_lookup_address_contact_id(bob).await;
let encrinfo = Contact::get_encrinfo(alice, address_contact_bob_id).await?;
assert_eq!(encrinfo, "No encryption");
let contact = Contact::get_by_id(alice, address_contact_bob_id).await?;
assert!(!contact.e2ee_avail(alice).await?);
let contact_bob_id = alice.add_or_lookup_contact_id(bob).await;
let encrinfo = Contact::get_encrinfo(alice, contact_bob_id).await?;
assert_eq!(
encrinfo,
"End-to-end encryption available.
Fingerprints:
Me (alice@example.org):
2E6F A2CB 23B5 32D7 2863
4B58 64B0 8F61 A9ED 9443
bob@example.net (bob@example.net):
CCCB 5AA9 F6E1 141C 9431
65F1 DB18 B18C BCF7 0487"
);
let contact = Contact::get_by_id(alice, contact_bob_id).await?;
assert!(contact.e2ee_avail(alice).await?);
alice.sql.execute("DELETE FROM public_keys", ()).await?;
let encrinfo = Contact::get_encrinfo(alice, contact_bob_id).await?;
assert_eq!(
encrinfo,
"No encryption.
Fingerprints:
Me (alice@example.org):
2E6F A2CB 23B5 32D7 2863
4B58 64B0 8F61 A9ED 9443
bob@example.net (bob@example.net):
CCCB 5AA9 F6E1 141C 9431
65F1 DB18 B18C BCF7 0487"
);
let contact = Contact::get_by_id(alice, contact_bob_id).await?;
assert!(!contact.e2ee_avail(alice).await?);
Ok(())
}
/// Tests that self-status is not synchronized from outgoing messages.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_synchronize_status() -> Result<()> {
let mut tcm = TestContextManager::new();
// Alice has two devices.
let alice1 = &tcm.alice().await;
let alice2 = &tcm.alice().await;
// Bob has one device.
let bob = &tcm.bob().await;
let default_status = alice1.get_config(Config::Selfstatus).await?;
alice1
.set_config(Config::Selfstatus, Some("New status"))
.await?;
let chat = alice1.create_email_chat(bob).await;
// Alice sends an unencrypted message to Bob from the first device.
send_text_msg(alice1, chat.id, "Hello".to_string()).await?;
let sent_msg = alice1.pop_sent_msg().await;
// Alice's second devices receives a copy of outgoing message.
alice2.recv_msg(&sent_msg).await;
assert_eq!(alice2.get_config(Config::Selfstatus).await?, default_status);
// Alice sends encrypted message.
let chat = alice1.create_chat(bob).await;
send_text_msg(alice1, chat.id, "Hello".to_string()).await?;
let sent_msg = alice1.pop_sent_msg().await;
// Alice's second devices receives a copy of second outgoing message.
alice2.recv_msg(&sent_msg).await;
assert_eq!(alice2.get_config(Config::Selfstatus).await?, default_status);
Ok(())
}
/// Tests that DC_EVENT_SELFAVATAR_CHANGED is emitted on avatar changes.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_selfavatar_changed_event() -> Result<()> {
let mut tcm = TestContextManager::new();
// Alice has two devices.
let alice1 = &tcm.alice().await;
let alice2 = &tcm.alice().await;
for a in [alice1, alice2] {
a.set_config_bool(Config::SyncMsgs, true).await?;
}
assert_eq!(alice1.get_config(Config::Selfavatar).await?, None);
let avatar_src = alice1.get_blobdir().join("avatar.png");
tokio::fs::write(&avatar_src, test_utils::AVATAR_900x900_BYTES).await?;
alice1
.set_config(Config::Selfavatar, Some(avatar_src.to_str().unwrap()))
.await?;
alice1
.evtracker
.get_matching(|e| matches!(e, EventType::SelfavatarChanged))
.await;
sync(alice1, alice2).await;
// Alice's second device applies the selfavatar.
assert!(alice2.get_config(Config::Selfavatar).await?.is_some());
alice2
.evtracker
.get_matching(|e| matches!(e, EventType::SelfavatarChanged))
.await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_last_seen() -> Result<()> {
let alice = TestContext::new_alice().await;
let (contact_id, _) = Contact::add_or_lookup(
&alice,
"Bob",
&ContactAddress::new("bob@example.net")?,
Origin::ManuallyCreated,
)
.await?;
let contact = Contact::get_by_id(&alice, contact_id).await?;
assert_eq!(contact.last_seen(), 0);
let mime = br#"Subject: Hello
Message-ID: message@example.net
To: Alice <alice@example.org>
From: Bob <bob@example.net>
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
Chat-Version: 1.0
Date: Sun, 22 Mar 2020 22:37:55 +0000
Hi."#;
receive_imf(&alice, mime, false).await?;
let msg = alice.get_last_msg().await;
let timestamp = msg.get_timestamp();
assert!(timestamp > 0);
let contact = Contact::get_by_id(&alice, contact_id).await?;
assert_eq!(contact.last_seen(), timestamp);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_was_seen_recently() -> Result<()> {
let _n = TimeShiftFalsePositiveNote;
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let chat = alice.create_chat(&bob).await;
let sent_msg = alice.send_text(chat.id, "moin").await;
let chat = bob.create_chat(&alice).await;
let contacts = chat::get_chat_contacts(&bob, chat.id).await?;
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
assert!(!contact.was_seen_recently());
bob.recv_msg(&sent_msg).await;
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
assert!(contact.was_seen_recently());
let self_contact = Contact::get_by_id(&bob, ContactId::SELF).await?;
assert!(!self_contact.was_seen_recently());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_was_seen_recently_event() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let recently_seen_loop = RecentlySeenLoop::new(bob.ctx.clone());
let chat = bob.create_chat(&alice).await;
let contacts = chat::get_chat_contacts(&bob, chat.id).await?;
for _ in 0..2 {
let chat = alice.create_chat(&bob).await;
let sent_msg = alice.send_text(chat.id, "moin").await;
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
assert!(!contact.was_seen_recently());
bob.evtracker.clear_events();
bob.recv_msg(&sent_msg).await;
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
assert!(contact.was_seen_recently());
bob.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged { .. }))
.await;
recently_seen_loop
.interrupt(contact.id, contact.last_seen)
.await;
// Wait for `was_seen_recently()` to turn off.
bob.evtracker.clear_events();
SystemTime::shift(Duration::from_secs(SEEN_RECENTLY_SECONDS as u64 * 2));
recently_seen_loop.interrupt(ContactId::UNDEFINED, 0).await;
let contact = Contact::get_by_id(&bob, *contacts.first().unwrap()).await?;
assert!(!contact.was_seen_recently());
bob.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged { .. }))
.await;
}
Ok(())
}
async fn test_lookup_id_by_addr_recent_ex(accept_unencrypted_chat: bool) -> Result<()> {
let mut tcm = TestContextManager::new();
let bob = &tcm.bob().await;
let raw = include_bytes!("../../test-data/message/thunderbird_with_autocrypt.eml");
assert!(std::str::from_utf8(raw)?.contains("Date: Thu, 24 Nov 2022 20:05:57 +0100"));
let received_msg = receive_imf(bob, raw, false).await?.unwrap();
received_msg.chat_id.accept(bob).await?;
let raw = r#"From: Alice <alice@example.org>
To: bob@example.net
Message-ID: message$TIME@example.org
Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no
Date: Thu, 24 Nov 2022 $TIME +0100
Hi"#
.to_string();
for (time, is_key_contact) in [("20:05:57", true), ("20:05:58", !accept_unencrypted_chat)] {
let raw = raw.replace("$TIME", time);
let received_msg = receive_imf(bob, raw.as_bytes(), false).await?.unwrap();
if accept_unencrypted_chat {
received_msg.chat_id.accept(bob).await?;
}
let contact_id = Contact::lookup_id_by_addr(bob, "alice@example.org", Origin::Unknown)
.await?
.unwrap();
let contact = Contact::get_by_id(bob, contact_id).await?;
assert_eq!(contact.is_key_contact(), is_key_contact);
}
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_lookup_id_by_addr_recent() -> Result<()> {
let accept_unencrypted_chat = true;
test_lookup_id_by_addr_recent_ex(accept_unencrypted_chat).await
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_lookup_id_by_addr_recent_accepted() -> Result<()> {
let accept_unencrypted_chat = false;
test_lookup_id_by_addr_recent_ex(accept_unencrypted_chat).await
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_verified_by_none() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let contact_id = Contact::create(&alice, "Bob", "bob@example.net").await?;
let contact = Contact::get_by_id(&alice, contact_id).await?;
assert!(contact.get_verifier_id(&alice).await?.is_none());
// Receive a message from Bob to save the public key.
let chat = bob.create_chat(&alice).await;
let sent_msg = bob.send_text(chat.id, "moin").await;
alice.recv_msg(&sent_msg).await;
let contact = Contact::get_by_id(&alice, contact_id).await?;
assert!(contact.get_verifier_id(&alice).await?.is_none());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sync_create() -> Result<()> {
let alice0 = &TestContext::new_alice().await;
let alice1 = &TestContext::new_alice().await;
for a in [alice0, alice1] {
a.set_config_bool(Config::SyncMsgs, true).await?;
}
Contact::create(alice0, "Bob", "bob@example.net").await?;
test_utils::sync(alice0, alice1).await;
let a1b_contact_id =
Contact::lookup_id_by_addr(alice1, "bob@example.net", Origin::ManuallyCreated)
.await?
.unwrap();
let a1b_contact = Contact::get_by_id(alice1, a1b_contact_id).await?;
assert_eq!(a1b_contact.name, "Bob");
assert_eq!(a1b_contact.is_key_contact(), false);
Contact::create(alice0, "Bob Renamed", "bob@example.net").await?;
test_utils::sync(alice0, alice1).await;
let id = Contact::lookup_id_by_addr(alice1, "bob@example.net", Origin::ManuallyCreated)
.await?
.unwrap();
assert_eq!(id, a1b_contact_id);
let a1b_contact = Contact::get_by_id(alice1, a1b_contact_id).await?;
assert_eq!(a1b_contact.name, "Bob Renamed");
assert_eq!(a1b_contact.is_key_contact(), false);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_make_n_import_vcard() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
bob.set_config(Config::Displayname, Some("Bob")).await?;
bob.set_config(Config::Selfstatus, Some("It's me, bob"))
.await?;
let avatar_path = bob.dir.path().join("avatar.png");
let avatar_bytes = include_bytes!("../../test-data/image/avatar64x64.png");
let avatar_base64 = base64::engine::general_purpose::STANDARD.encode(avatar_bytes);
tokio::fs::write(&avatar_path, avatar_bytes).await?;
bob.set_config(Config::Selfavatar, Some(avatar_path.to_str().unwrap()))
.await?;
let bob_addr = bob.get_config(Config::Addr).await?.unwrap();
let bob_biography = bob.get_config(Config::Selfstatus).await?.unwrap();
let chat = bob.create_chat(alice).await;
let sent_msg = bob.send_text(chat.id, "moin").await;
let bob_id = alice.recv_msg(&sent_msg).await.from_id;
let bob_contact = Contact::get_by_id(alice, bob_id).await?;
let key_base64 = bob_contact.public_key(alice).await?.unwrap().to_base64();
let fiona_id = Contact::create(alice, "Fiona", "fiona@example.net").await?;
assert_eq!(make_vcard(alice, &[]).await?, "".to_string());
let t0 = time();
let vcard = make_vcard(alice, &[bob_id, fiona_id]).await?;
let t1 = time();
// Just test that it's parsed as expected, `deltachat_contact_tools` crate has tests on the
// exact format.
let contacts = contact_tools::parse_vcard(&vcard);
assert_eq!(contacts.len(), 2);
assert_eq!(contacts[0].addr, bob_addr);
assert_eq!(contacts[0].authname, "Bob".to_string());
assert_eq!(*contacts[0].key.as_ref().unwrap(), key_base64);
assert_eq!(*contacts[0].profile_image.as_ref().unwrap(), avatar_base64);
assert_eq!(*contacts[0].biography.as_ref().unwrap(), bob_biography);
let timestamp = *contacts[0].timestamp.as_ref().unwrap();
assert!(t0 <= timestamp && timestamp <= t1);
assert_eq!(contacts[1].addr, "fiona@example.net".to_string());
assert_eq!(contacts[1].authname, "".to_string());
assert_eq!(contacts[1].key, None);
assert_eq!(contacts[1].profile_image, None);
assert_eq!(contacts[1].biography, None);
let timestamp = *contacts[1].timestamp.as_ref().unwrap();
assert!(t0 <= timestamp && timestamp <= t1);
let alice = &TestContext::new_alice().await;
alice.evtracker.clear_events();
let contact_ids = import_vcard(alice, &vcard).await?;
assert_eq!(contact_ids.len(), 2);
for _ in 0..contact_ids.len() {
alice
.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged(Some(_))))
.await;
}
let vcard = make_vcard(alice, &[contact_ids[0], contact_ids[1]]).await?;
// This should be the same vCard except timestamps, check that roughly.
let contacts = contact_tools::parse_vcard(&vcard);
assert_eq!(contacts.len(), 2);
assert_eq!(contacts[0].addr, bob_addr);
assert_eq!(contacts[0].authname, "Bob".to_string());
assert_eq!(*contacts[0].key.as_ref().unwrap(), key_base64);
assert_eq!(*contacts[0].profile_image.as_ref().unwrap(), avatar_base64);
assert_eq!(*contacts[0].biography.as_ref().unwrap(), bob_biography);
assert!(contacts[0].timestamp.is_ok());
assert_eq!(contacts[1].addr, "fiona@example.net".to_string());
let chat_id = ChatId::create_for_contact(alice, contact_ids[0]).await?;
let sent_msg = alice.send_text(chat_id, "moin").await;
let msg = bob.recv_msg(&sent_msg).await;
assert!(msg.get_showpadlock());
// Bob only actually imports Fiona, though `ContactId::SELF` is also returned.
bob.evtracker.clear_events();
let contact_ids = import_vcard(bob, &vcard).await?;
bob.emit_event(EventType::Test);
assert_eq!(contact_ids.len(), 2);
assert_eq!(contact_ids[0], ContactId::SELF);
let ev = bob
.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged { .. }))
.await;
assert_eq!(ev, EventType::ContactsChanged(Some(contact_ids[1])));
let ev = bob
.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged { .. } | EventType::Test))
.await;
assert_eq!(ev, EventType::Test);
let vcard = make_vcard(bob, &[contact_ids[1]]).await?;
let contacts = contact_tools::parse_vcard(&vcard);
assert_eq!(contacts.len(), 1);
assert_eq!(contacts[0].addr, "fiona@example.net");
assert_eq!(contacts[0].authname, "".to_string());
assert_eq!(contacts[0].key, None);
assert_eq!(contacts[0].profile_image, None);
assert_eq!(contacts[0].biography, None);
assert!(contacts[0].timestamp.is_ok());
Ok(())
}
/// Tests importing a vCard with the same email address,
/// but a new key.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_import_vcard_key_change() -> Result<()> {
let alice = &TestContext::new_alice().await;
let bob = &TestContext::new_bob().await;
let bob_addr = &bob.get_config(Config::Addr).await?.unwrap();
bob.set_config(Config::Displayname, Some("Bob")).await?;
let vcard = make_vcard(bob, &[ContactId::SELF]).await?;
alice.evtracker.clear_events();
let alice_bob_id = import_vcard(alice, &vcard).await?[0];
let ev = alice
.evtracker
.get_matching(|evt| matches!(evt, EventType::ContactsChanged { .. }))
.await;
assert_eq!(ev, EventType::ContactsChanged(Some(alice_bob_id)));
let chat_id = ChatId::create_for_contact(alice, alice_bob_id).await?;
let sent_msg = alice.send_text(chat_id, "moin").await;
let msg = bob.recv_msg(&sent_msg).await;
assert!(msg.get_showpadlock());
let bob1 = &TestContext::new().await;
bob1.configure_addr(bob_addr).await;
bob1.set_config(Config::Displayname, Some("New Bob"))
.await?;
let avatar_path = bob1.dir.path().join("avatar.png");
let avatar_bytes = include_bytes!("../../test-data/image/avatar64x64.png");
tokio::fs::write(&avatar_path, avatar_bytes).await?;
bob1.set_config(Config::Selfavatar, Some(avatar_path.to_str().unwrap()))
.await?;
SystemTime::shift(Duration::from_secs(1));
let vcard1 = make_vcard(bob1, &[ContactId::SELF]).await?;
let alice_bob_id1 = import_vcard(alice, &vcard1).await?[0];
assert_ne!(alice_bob_id1, alice_bob_id);
let alice_bob_contact = Contact::get_by_id(alice, alice_bob_id).await?;
assert_eq!(alice_bob_contact.get_authname(), "Bob");
assert_eq!(alice_bob_contact.get_profile_image(alice).await?, None);
let alice_bob_contact1 = Contact::get_by_id(alice, alice_bob_id1).await?;
assert_eq!(alice_bob_contact1.get_authname(), "New Bob");
assert!(alice_bob_contact1.get_profile_image(alice).await?.is_some());
// Last message is still the same,
// no new messages are added.
let msg = alice.get_last_msg_in(chat_id).await;
assert_eq!(msg.get_text(), "moin");
let chat_id1 = ChatId::create_for_contact(alice, alice_bob_id1).await?;
let sent_msg = alice.send_text(chat_id1, "moin").await;
let msg = bob1.recv_msg(&sent_msg).await;
assert!(msg.get_showpadlock());
// The old vCard is imported, but doesn't change Bob's key for Alice.
import_vcard(alice, &vcard).await?.first().unwrap();
let sent_msg = alice.send_text(chat_id, "moin").await;
let msg = bob.recv_msg(&sent_msg).await;
assert!(msg.get_showpadlock());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_self_is_verified() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let contact = Contact::get_by_id(&alice, ContactId::SELF).await?;
assert_eq!(contact.is_verified(&alice).await?, true);
assert!(contact.get_verifier_id(&alice).await?.is_none());
assert!(contact.is_key_contact());
Ok(())
}
/// Tests that importing a vCard with a key creates a key-contact.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_vcard_creates_key_contact() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let vcard = make_vcard(bob, &[ContactId::SELF]).await?;
let contact_ids = import_vcard(alice, &vcard).await?;
assert_eq!(contact_ids.len(), 1);
let contact_id = contact_ids.first().unwrap();
let contact = Contact::get_by_id(alice, *contact_id).await?;
assert!(contact.is_key_contact());
Ok(())
}
/// Tests changing display name by sending a message.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_name_changes() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
alice
.set_config(Config::Displayname, Some("Alice Revision 1"))
.await?;
let alice_bob_chat = alice.create_chat(bob).await;
let sent_msg = alice.send_text(alice_bob_chat.id, "Hello").await;
let bob_alice_id = bob.recv_msg(&sent_msg).await.from_id;
let bob_alice_contact = Contact::get_by_id(bob, bob_alice_id).await?;
assert_eq!(bob_alice_contact.get_display_name(), "Alice Revision 1");
alice
.set_config(Config::Displayname, Some("Alice Revision 2"))
.await?;
let sent_msg = alice.send_text(alice_bob_chat.id, "Hello").await;
bob.recv_msg(&sent_msg).await;
let bob_alice_contact = Contact::get_by_id(bob, bob_alice_id).await?;
assert_eq!(bob_alice_contact.get_display_name(), "Alice Revision 2");
// Explicitly rename contact to "Renamed".
bob.evtracker.clear_events();
bob_alice_contact.id.set_name(bob, "Renamed").await?;
let event = bob
.evtracker
.get_matching(|e| matches!(e, EventType::ContactsChanged { .. }))
.await;
assert_eq!(
event,
EventType::ContactsChanged(Some(bob_alice_contact.id))
);
let bob_alice_contact = Contact::get_by_id(bob, bob_alice_id).await?;
assert_eq!(bob_alice_contact.get_display_name(), "Renamed");
// Alice also renames self into "Renamed".
alice
.set_config(Config::Displayname, Some("Renamed"))
.await?;
let sent_msg = alice.send_text(alice_bob_chat.id, "Hello").await;
bob.recv_msg(&sent_msg).await;
let bob_alice_contact = Contact::get_by_id(bob, bob_alice_id).await?;
assert_eq!(bob_alice_contact.get_display_name(), "Renamed");
// Contact name was set to "Renamed" explicitly before,
// so it should not be changed.
alice
.set_config(Config::Displayname, Some("Renamed again"))
.await?;
let sent_msg = alice.send_text(alice_bob_chat.id, "Hello").await;
bob.recv_msg(&sent_msg).await;
let bob_alice_contact = Contact::get_by_id(bob, bob_alice_id).await?;
assert_eq!(bob_alice_contact.get_display_name(), "Renamed");
Ok(())
}