From 34a434f07c3c7f571bb7c33560c47a7d18c0860e Mon Sep 17 00:00:00 2001 From: iequidoo Date: Fri, 10 Nov 2023 23:32:29 -0300 Subject: [PATCH] refactor: Move chat-related code from `sync` to `chat` module - Reduce cross-module dependencies. - Stop bloating the `sync` module while implementing synchronisation of more entities. - Now there's the only `ChatId` :) --- src/chat.rs | 240 ++++++++++++++++++++++++++++++++++++++++++------- src/contact.rs | 6 +- src/sync.rs | 212 ++++--------------------------------------- 3 files changed, 230 insertions(+), 228 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 40388fc92..fc26c606e 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -39,7 +39,7 @@ use crate::receive_imf::ReceivedMsg; use crate::smtp::send_msg_to_smtp; use crate::sql; use crate::stock_str; -use crate::sync::{self, ChatAction, Sync::*, SyncData}; +use crate::sync::{self, Sync::*, SyncData}; use crate::tools::{ buf_compress, create_id, create_outgoing_rfc724_mid, create_smeared_timestamp, create_smeared_timestamps, get_abs_path, gm2local_offset, improve_single_line_input, @@ -400,7 +400,7 @@ impl ChatId { if sync.into() { // NB: For a 1:1 chat this currently triggers `Contact::block()` on other devices. - chat.sync(context, ChatAction::Block) + chat.sync(context, SyncAction::Block) .await .log_err(context) .ok(); @@ -421,7 +421,7 @@ impl ChatId { // TODO: For a 1:1 chat this currently triggers `Contact::unblock()` on other devices. // Maybe we should unblock the contact locally too, this would also resolve discrepancy // with `block()` which also blocks the contact. - chat.sync(context, ChatAction::Unblock) + chat.sync(context, SyncAction::Unblock) .await .log_err(context) .ok(); @@ -472,7 +472,7 @@ impl ChatId { } if sync.into() { - chat.sync(context, ChatAction::Accept) + chat.sync(context, SyncAction::Accept) .await .log_err(context) .ok(); @@ -637,7 +637,7 @@ impl ChatId { if sync.into() { let chat = Chat::load_from_db(context, self).await?; - chat.sync(context, ChatAction::SetVisibility(visibility)) + chat.sync(context, SyncAction::SetVisibility(visibility)) .await .log_err(context) .ok(); @@ -1932,18 +1932,18 @@ impl Chat { Ok(msg.id) } - /// Sends a `ChatAction` synchronising chat contacts to other devices. + /// Sends a `SyncAction` synchronising chat contacts to other devices. pub(crate) async fn sync_contacts(&self, context: &Context) -> Result<()> { let mut addrs = Vec::new(); for contact_id in get_chat_contacts(context, self.id).await? { let contact = Contact::get_by_id(context, contact_id).await?; addrs.push(contact.get_addr().to_string()); } - self.sync(context, ChatAction::SetContacts(addrs)).await + self.sync(context, SyncAction::SetContacts(addrs)).await } /// Returns chat id for the purpose of synchronisation across devices. - async fn get_sync_id(&self, context: &Context) -> Result> { + async fn get_sync_id(&self, context: &Context) -> Result> { match self.typ { Chattype::Single => { let mut r = None; @@ -1955,7 +1955,7 @@ impl Chat { return Ok(None); } let contact = Contact::get_by_id(context, contact_id).await?; - r = Some(sync::ChatId::ContactAddr(contact.get_addr().to_string())); + r = Some(SyncId::ContactAddr(contact.get_addr().to_string())); } Ok(r) } @@ -1963,13 +1963,13 @@ impl Chat { if self.grpid.is_empty() { return Ok(None); } - Ok(Some(sync::ChatId::Grpid(self.grpid.clone()))) + Ok(Some(SyncId::Grpid(self.grpid.clone()))) } } } /// Synchronises a chat action to other devices. - pub(crate) async fn sync(&self, context: &Context, action: ChatAction) -> Result<()> { + pub(crate) async fn sync(&self, context: &Context, action: SyncAction) -> Result<()> { if let Some(id) = self.get_sync_id(context).await? { sync(context, id, action).await?; } @@ -1977,7 +1977,7 @@ impl Chat { } } -async fn sync(context: &Context, id: sync::ChatId, action: ChatAction) -> Result<()> { +async fn sync(context: &Context, id: SyncId, action: SyncAction) -> Result<()> { context .add_sync_item(SyncData::AlterChat { id, action }) .await?; @@ -3290,8 +3290,8 @@ async fn create_broadcast_list_ex( context.emit_msgs_changed_without_ids(); if sync.into() { - let id = sync::ChatId::Grpid(grpid); - let action = ChatAction::CreateBroadcast(chat_name); + let id = SyncId::Grpid(grpid); + let action = SyncAction::CreateBroadcast(chat_name); self::sync(context, id, action).await.log_err(context).ok(); } Ok(chat_id) @@ -3568,7 +3568,7 @@ pub(crate) async fn set_muted_ex( context.emit_event(EventType::ChatModified(chat_id)); if sync.into() { let chat = Chat::load_from_db(context, chat_id).await?; - chat.sync(context, ChatAction::SetMuted(duration)) + chat.sync(context, SyncAction::SetMuted(duration)) .await .log_err(context) .ok(); @@ -4221,15 +4221,34 @@ async fn set_contacts_by_addrs(context: &Context, id: ChatId, addrs: &[String]) Ok(()) } +/// A cross-device chat id used for synchronisation. +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub(crate) enum SyncId { + ContactAddr(String), + Grpid(String), + // NOTE: Ad-hoc groups lack an identifier that can be used across devices so + // block/mute/etc. actions on them are not synchronized to other devices. +} + +/// An action synchronised to other devices. +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub(crate) enum SyncAction { + Block, + Unblock, + Accept, + SetVisibility(ChatVisibility), + SetMuted(MuteDuration), + /// Create broadcast list with the given name. + CreateBroadcast(String), + /// Set chat contacts by their addresses. + SetContacts(Vec), +} + impl Context { /// Executes [`SyncData::AlterChat`] item sent by other device. - pub(crate) async fn sync_alter_chat( - &self, - id: &sync::ChatId, - action: &ChatAction, - ) -> Result<()> { + pub(crate) async fn sync_alter_chat(&self, id: &SyncId, action: &SyncAction) -> Result<()> { let chat_id = match id { - sync::ChatId::ContactAddr(addr) => { + SyncId::ContactAddr(addr) => { let Some(contact_id) = Contact::lookup_id_by_addr_ex(self, addr, Origin::Unknown, None).await? else { @@ -4237,10 +4256,10 @@ impl Context { return Ok(()); }; match action { - ChatAction::Block => { + SyncAction::Block => { return contact::set_blocked(self, Nosync, contact_id, true).await } - ChatAction::Unblock => { + SyncAction::Unblock => { return contact::set_blocked(self, Nosync, contact_id, false).await } _ => (), @@ -4251,8 +4270,8 @@ impl Context { }; chat_id } - sync::ChatId::Grpid(grpid) => { - if let ChatAction::CreateBroadcast(name) = action { + SyncId::Grpid(grpid) => { + if let SyncAction::CreateBroadcast(name) = action { create_broadcast_list_ex(self, Nosync, grpid.clone(), name.clone()).await?; return Ok(()); } @@ -4264,15 +4283,15 @@ impl Context { } }; match action { - ChatAction::Block => chat_id.block_ex(self, Nosync).await, - ChatAction::Unblock => chat_id.unblock_ex(self, Nosync).await, - ChatAction::Accept => chat_id.accept_ex(self, Nosync).await, - ChatAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await, - ChatAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await, - ChatAction::CreateBroadcast(_) => { + SyncAction::Block => chat_id.block_ex(self, Nosync).await, + SyncAction::Unblock => chat_id.unblock_ex(self, Nosync).await, + SyncAction::Accept => chat_id.accept_ex(self, Nosync).await, + SyncAction::SetVisibility(v) => chat_id.set_visibility_ex(self, Nosync, *v).await, + SyncAction::SetMuted(duration) => set_muted_ex(self, Nosync, chat_id, *duration).await, + SyncAction::CreateBroadcast(_) => { Err(anyhow!("sync_alter_chat({id:?}, {action:?}): Bad request.")) } - ChatAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await, + SyncAction::SetContacts(addrs) => set_contacts_by_addrs(self, chat_id, addrs).await, } } } @@ -4286,6 +4305,7 @@ mod tests { use crate::message::delete_msgs; use crate::receive_imf::receive_imf; use crate::test_utils::{TestContext, TestContextManager}; + use strum::IntoEnumIterator; use tokio::fs; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -6764,4 +6784,160 @@ mod tests { ); Ok(()) } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_sync_alter_chat() -> Result<()> { + let alices = [ + TestContext::new_alice().await, + TestContext::new_alice().await, + ]; + for a in &alices { + a.set_config_bool(Config::SyncMsgs, true).await?; + } + let bob = TestContext::new_bob().await; + + let ba_chat = bob.create_chat(&alices[0]).await; + let sent_msg = bob.send_text(ba_chat.id, "hi").await; + let a0b_chat_id = alices[0].recv_msg(&sent_msg).await.chat_id; + alices[1].recv_msg(&sent_msg).await; + let ab_contact_ids = [ + alices[0].add_or_lookup_contact(&bob).await.id, + alices[1].add_or_lookup_contact(&bob).await.id, + ]; + + async fn sync(alices: &[TestContext]) -> Result<()> { + let sync_msg = alices.get(0).unwrap().pop_sent_msg().await; + alices.get(1).unwrap().recv_msg(&sync_msg).await; + Ok(()) + } + + assert_eq!(alices[1].get_chat(&bob).await.blocked, Blocked::Request); + a0b_chat_id.accept(&alices[0]).await?; + sync(&alices).await?; + assert_eq!(alices[1].get_chat(&bob).await.blocked, Blocked::Not); + a0b_chat_id.block(&alices[0]).await?; + sync(&alices).await?; + assert_eq!(alices[1].get_chat(&bob).await.blocked, Blocked::Yes); + a0b_chat_id.unblock(&alices[0]).await?; + sync(&alices).await?; + assert_eq!(alices[1].get_chat(&bob).await.blocked, Blocked::Not); + + // Unblocking a 1:1 chat doesn't unblock the contact currently. + Contact::unblock(&alices[0], ab_contact_ids[0]).await?; + + assert!(!alices[1].add_or_lookup_contact(&bob).await.is_blocked()); + Contact::block(&alices[0], ab_contact_ids[0]).await?; + sync(&alices).await?; + assert!(alices[1].add_or_lookup_contact(&bob).await.is_blocked()); + Contact::unblock(&alices[0], ab_contact_ids[0]).await?; + sync(&alices).await?; + assert!(!alices[1].add_or_lookup_contact(&bob).await.is_blocked()); + + // Test accepting and blocking groups. This way we test: + // - Group chats synchronisation. + // - That blocking a group deletes it on other devices. + let fiona = TestContext::new_fiona().await; + let fiona_grp_chat_id = fiona + .create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&alices[0]]) + .await; + let sent_msg = fiona.send_text(fiona_grp_chat_id, "hi").await; + let a0_grp_chat_id = alices[0].recv_msg(&sent_msg).await.chat_id; + let a1_grp_chat_id = alices[1].recv_msg(&sent_msg).await.chat_id; + let a1_grp_chat = Chat::load_from_db(&alices[1], a1_grp_chat_id).await?; + assert_eq!(a1_grp_chat.blocked, Blocked::Request); + a0_grp_chat_id.accept(&alices[0]).await?; + sync(&alices).await?; + let a1_grp_chat = Chat::load_from_db(&alices[1], a1_grp_chat_id).await?; + assert_eq!(a1_grp_chat.blocked, Blocked::Not); + a0_grp_chat_id.block(&alices[0]).await?; + sync(&alices).await?; + assert!(Chat::load_from_db(&alices[1], a1_grp_chat_id) + .await + .is_err()); + assert!( + !alices[1] + .sql + .exists("SELECT COUNT(*) FROM chats WHERE id=?", (a1_grp_chat_id,)) + .await? + ); + + // Test syncing of chat visibility on a self-chat. This way we test: + // - Self-chat synchronisation. + // - That sync messages don't unarchive the self-chat. + let a0self_chat_id = alices[0].get_self_chat().await.id; + assert_eq!( + alices[1].get_self_chat().await.get_visibility(), + ChatVisibility::Normal + ); + let mut visibilities = + ChatVisibility::iter().chain(std::iter::once(ChatVisibility::Normal)); + visibilities.next(); + for v in visibilities { + a0self_chat_id.set_visibility(&alices[0], v).await?; + sync(&alices).await?; + for a in &alices { + assert_eq!(a.get_self_chat().await.get_visibility(), v); + } + } + + assert_eq!( + alices[1].get_chat(&bob).await.mute_duration, + MuteDuration::NotMuted + ); + let mute_durations = [ + MuteDuration::Forever, + MuteDuration::Until(SystemTime::now() + Duration::from_secs(42)), + MuteDuration::NotMuted, + ]; + for m in mute_durations { + set_muted(&alices[0], a0b_chat_id, m).await?; + sync(&alices).await?; + let m = match m { + MuteDuration::Until(time) => MuteDuration::Until( + SystemTime::UNIX_EPOCH + + Duration::from_secs( + time.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(), + ), + ), + _ => m, + }; + assert_eq!(alices[1].get_chat(&bob).await.mute_duration, m); + } + + let a0_broadcast_id = create_broadcast_list(&alices[0]).await?; + let a0_broadcast_chat = Chat::load_from_db(&alices[0], a0_broadcast_id).await?; + set_chat_name(&alices[0], a0_broadcast_id, "Broadcast list 42").await?; + sync(&alices).await?; + let a1_broadcast_id = get_chat_id_by_grpid(&alices[1], &a0_broadcast_chat.grpid) + .await? + .unwrap() + .0; + let a1_broadcast_chat = Chat::load_from_db(&alices[1], a1_broadcast_id).await?; + assert_eq!(a1_broadcast_chat.get_type(), Chattype::Broadcast); + // TODO: Implement synchronisation of `set_chat_name()`. + // assert_eq!(a1_broadcast_chat.get_name(), "Broadcast list 42"); + assert!(get_chat_contacts(&alices[1], a1_broadcast_id) + .await? + .is_empty()); + add_contact_to_chat(&alices[0], a0_broadcast_id, ab_contact_ids[0]).await?; + sync(&alices).await?; + assert_eq!( + get_chat_contacts(&alices[1], a1_broadcast_id).await?, + vec![ab_contact_ids[1]] + ); + let sent_msg = alices[1].send_text(a1_broadcast_id, "hi").await; + let msg = bob.recv_msg(&sent_msg).await; + let chat = Chat::load_from_db(&bob, msg.chat_id).await?; + assert_eq!(chat.get_type(), Chattype::Mailinglist); + // TODO: It doesn't work now for some reason, `msg.chat_id == DC_CHAT_ID_TRASH`. + // let msg = alices[0].recv_msg(&sent_msg).await; + // assert_eq!(msg.chat_id, a0_broadcast_id); + remove_contact_from_chat(&alices[0], a0_broadcast_id, ab_contact_ids[0]).await?; + sync(&alices).await?; + assert!(get_chat_contacts(&alices[1], a1_broadcast_id) + .await? + .is_empty()); + + Ok(()) + } } diff --git a/src/contact.rs b/src/contact.rs index 21d16426d..151159145 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -1480,12 +1480,12 @@ WHERE type=? AND id IN ( if sync.into() { let action = match new_blocking { - true => sync::ChatAction::Block, - false => sync::ChatAction::Unblock, + true => chat::SyncAction::Block, + false => chat::SyncAction::Unblock, }; context .add_sync_item(SyncData::AlterChat { - id: sync::ChatId::ContactAddr(contact.addr.clone()), + id: chat::SyncId::ContactAddr(contact.addr.clone()), action, }) .await?; diff --git a/src/sync.rs b/src/sync.rs index 0f5174772..4d3e60e40 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -5,7 +5,7 @@ use lettre_email::mime::{self}; use lettre_email::PartBuilder; use serde::{Deserialize, Serialize}; -use crate::chat::{self, Chat, ChatVisibility}; +use crate::chat::{self, Chat, ChatId}; use crate::config::Config; use crate::constants::Blocked; use crate::contact::ContactId; @@ -42,32 +42,14 @@ pub(crate) struct QrTokenData { pub(crate) grpid: Option, } -#[derive(Debug, Serialize, Deserialize, PartialEq)] -pub(crate) enum ChatId { - ContactAddr(String), - Grpid(String), - // NOTE: Ad-hoc groups lack an identifier that can be used across devices so - // block/mute/etc. actions on them are not synchronized to other devices. -} - -#[derive(Debug, Serialize, Deserialize, PartialEq)] -pub(crate) enum ChatAction { - Block, - Unblock, - Accept, - SetVisibility(ChatVisibility), - SetMuted(chat::MuteDuration), - /// Create broadcast list with the given name. - CreateBroadcast(String), - /// Set chat contacts by their addresses. - SetContacts(Vec), -} - #[derive(Debug, Serialize, Deserialize)] pub(crate) enum SyncData { AddQrToken(QrTokenData), DeleteQrToken(QrTokenData), - AlterChat { id: ChatId, action: ChatAction }, + AlterChat { + id: chat::SyncId, + action: chat::SyncAction, + }, } #[derive(Debug, Serialize, Deserialize)] @@ -109,7 +91,7 @@ impl Context { /// Adds most recent qr-code tokens for a given chat to the list of items to be synced. /// If device synchronization is disabled, /// no tokens exist or the chat is unpromoted, the function does nothing. - pub(crate) async fn sync_qr_code_tokens(&self, chat_id: Option) -> Result<()> { + pub(crate) async fn sync_qr_code_tokens(&self, chat_id: Option) -> Result<()> { if !self.get_config_bool(Config::SyncMsgs).await? { return Ok(()); } @@ -160,7 +142,7 @@ impl Context { pub async fn send_sync_msg(&self) -> Result> { if let Some((json, ids)) = self.build_sync_json().await? { let chat_id = - chat::ChatId::create_for_contact_with_blocked(self, ContactId::SELF, Blocked::Yes) + ChatId::create_for_contact_with_blocked(self, ContactId::SELF, Blocked::Yes) .await?; let mut msg = Message { chat_id, @@ -300,12 +282,10 @@ mod tests { use std::time::{Duration, SystemTime}; use anyhow::bail; - use strum::IntoEnumIterator; use super::*; - use crate::chat::{Chat, ProtectionStatus}; + use crate::chat::Chat; use crate::chatlist::Chatlist; - use crate::constants::Chattype; use crate::contact::{Contact, Origin}; use crate::test_utils::TestContext; use crate::token::Namespace; @@ -328,13 +308,13 @@ mod tests { assert!(t.build_sync_json().await?.is_none()); - // Having one test on `SyncData::AlterChat` is sufficient here as `ChatAction::SetMuted` - // introduces enums inside items and `SystemTime`. Let's avoid in-depth testing of the - // serialiser here which is an external crate. + // Having one test on `SyncData::AlterChat` is sufficient here as + // `chat::SyncAction::SetMuted` introduces enums inside items and `SystemTime`. Let's avoid + // in-depth testing of the serialiser here which is an external crate. t.add_sync_item_with_timestamp( SyncData::AlterChat { - id: ChatId::ContactAddr("bob@example.net".to_string()), - action: ChatAction::SetMuted(chat::MuteDuration::Until( + id: chat::SyncId::ContactAddr("bob@example.net".to_string()), + action: chat::SyncAction::SetMuted(chat::MuteDuration::Until( SystemTime::UNIX_EPOCH + Duration::from_millis(42999), )), }, @@ -447,10 +427,13 @@ mod tests { let AlterChat { id, action } = &sync_items.items.get(0).unwrap().data else { bail!("bad item"); }; - assert_eq!(*id, ChatId::ContactAddr("bob@example.net".to_string())); + assert_eq!( + *id, + chat::SyncId::ContactAddr("bob@example.net".to_string()) + ); assert_eq!( *action, - ChatAction::SetMuted(chat::MuteDuration::Until( + chat::SyncAction::SetMuted(chat::MuteDuration::Until( SystemTime::UNIX_EPOCH + Duration::from_millis(42999) )) ); @@ -555,7 +538,7 @@ mod tests { // check that the used self-talk is not visible to the user // but that creation will still work (in this case, the chat is empty) assert_eq!(Chatlist::try_load(&alice, 0, None, None).await?.len(), 0); - let chat_id = chat::ChatId::create_for_contact(&alice, ContactId::SELF).await?; + let chat_id = ChatId::create_for_contact(&alice, ContactId::SELF).await?; let chat = Chat::load_from_db(&alice, chat_id).await?; assert!(chat.is_self_talk()); assert_eq!(Chatlist::try_load(&alice, 0, None, None).await?.len(), 1); @@ -578,161 +561,4 @@ mod tests { Ok(()) } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_alter_chat() -> Result<()> { - let alices = [ - TestContext::new_alice().await, - TestContext::new_alice().await, - ]; - for a in &alices { - a.set_config_bool(Config::SyncMsgs, true).await?; - } - let bob = TestContext::new_bob().await; - - let ba_chat = bob.create_chat(&alices[0]).await; - let sent_msg = bob.send_text(ba_chat.id, "hi").await; - let a0b_chat_id = alices[0].recv_msg(&sent_msg).await.chat_id; - alices[1].recv_msg(&sent_msg).await; - let ab_contact_ids = [ - alices[0].add_or_lookup_contact(&bob).await.id, - alices[1].add_or_lookup_contact(&bob).await.id, - ]; - - async fn sync(alices: &[TestContext]) -> Result<()> { - let sync_msg = alices.get(0).unwrap().pop_sent_msg().await; - alices.get(1).unwrap().recv_msg(&sync_msg).await; - Ok(()) - } - - assert_eq!(alices[1].get_chat(&bob).await.blocked, Blocked::Request); - a0b_chat_id.accept(&alices[0]).await?; - sync(&alices).await?; - assert_eq!(alices[1].get_chat(&bob).await.blocked, Blocked::Not); - a0b_chat_id.block(&alices[0]).await?; - sync(&alices).await?; - assert_eq!(alices[1].get_chat(&bob).await.blocked, Blocked::Yes); - a0b_chat_id.unblock(&alices[0]).await?; - sync(&alices).await?; - assert_eq!(alices[1].get_chat(&bob).await.blocked, Blocked::Not); - - // Unblocking a 1:1 chat doesn't unblock the contact currently. - Contact::unblock(&alices[0], ab_contact_ids[0]).await?; - - assert!(!alices[1].add_or_lookup_contact(&bob).await.is_blocked()); - Contact::block(&alices[0], ab_contact_ids[0]).await?; - sync(&alices).await?; - assert!(alices[1].add_or_lookup_contact(&bob).await.is_blocked()); - Contact::unblock(&alices[0], ab_contact_ids[0]).await?; - sync(&alices).await?; - assert!(!alices[1].add_or_lookup_contact(&bob).await.is_blocked()); - - // Test accepting and blocking groups. This way we test: - // - Group chats synchronisation. - // - That blocking a group deletes it on other devices. - let fiona = TestContext::new_fiona().await; - let fiona_grp_chat_id = fiona - .create_group_with_members(ProtectionStatus::Unprotected, "grp", &[&alices[0]]) - .await; - let sent_msg = fiona.send_text(fiona_grp_chat_id, "hi").await; - let a0_grp_chat_id = alices[0].recv_msg(&sent_msg).await.chat_id; - let a1_grp_chat_id = alices[1].recv_msg(&sent_msg).await.chat_id; - let a1_grp_chat = Chat::load_from_db(&alices[1], a1_grp_chat_id).await?; - assert_eq!(a1_grp_chat.blocked, Blocked::Request); - a0_grp_chat_id.accept(&alices[0]).await?; - sync(&alices).await?; - let a1_grp_chat = Chat::load_from_db(&alices[1], a1_grp_chat_id).await?; - assert_eq!(a1_grp_chat.blocked, Blocked::Not); - a0_grp_chat_id.block(&alices[0]).await?; - sync(&alices).await?; - assert!(Chat::load_from_db(&alices[1], a1_grp_chat_id) - .await - .is_err()); - assert!( - !alices[1] - .sql - .exists("SELECT COUNT(*) FROM chats WHERE id=?", (a1_grp_chat_id,)) - .await? - ); - - // Test syncing of chat visibility on a self-chat. This way we test: - // - Self-chat synchronisation. - // - That sync messages don't unarchive the self-chat. - let a0self_chat_id = alices[0].get_self_chat().await.id; - assert_eq!( - alices[1].get_self_chat().await.get_visibility(), - ChatVisibility::Normal - ); - let mut visibilities = - ChatVisibility::iter().chain(std::iter::once(ChatVisibility::Normal)); - visibilities.next(); - for v in visibilities { - a0self_chat_id.set_visibility(&alices[0], v).await?; - sync(&alices).await?; - for a in &alices { - assert_eq!(a.get_self_chat().await.get_visibility(), v); - } - } - - use chat::MuteDuration; - assert_eq!( - alices[1].get_chat(&bob).await.mute_duration, - MuteDuration::NotMuted - ); - let mute_durations = [ - MuteDuration::Forever, - MuteDuration::Until(SystemTime::now() + Duration::from_secs(42)), - MuteDuration::NotMuted, - ]; - for m in mute_durations { - chat::set_muted(&alices[0], a0b_chat_id, m).await?; - sync(&alices).await?; - let m = match m { - MuteDuration::Until(time) => MuteDuration::Until( - SystemTime::UNIX_EPOCH - + Duration::from_secs( - time.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(), - ), - ), - _ => m, - }; - assert_eq!(alices[1].get_chat(&bob).await.mute_duration, m); - } - - let a0_broadcast_id = chat::create_broadcast_list(&alices[0]).await?; - let a0_broadcast_chat = Chat::load_from_db(&alices[0], a0_broadcast_id).await?; - chat::set_chat_name(&alices[0], a0_broadcast_id, "Broadcast list 42").await?; - sync(&alices).await?; - let a1_broadcast_id = chat::get_chat_id_by_grpid(&alices[1], &a0_broadcast_chat.grpid) - .await? - .unwrap() - .0; - let a1_broadcast_chat = Chat::load_from_db(&alices[1], a1_broadcast_id).await?; - assert_eq!(a1_broadcast_chat.get_type(), Chattype::Broadcast); - // TODO: Implement synchronisation of `chat::set_chat_name()`. - // assert_eq!(a1_broadcast_chat.get_name(), "Broadcast list 42"); - assert!(chat::get_chat_contacts(&alices[1], a1_broadcast_id) - .await? - .is_empty()); - chat::add_contact_to_chat(&alices[0], a0_broadcast_id, ab_contact_ids[0]).await?; - sync(&alices).await?; - assert_eq!( - chat::get_chat_contacts(&alices[1], a1_broadcast_id).await?, - vec![ab_contact_ids[1]] - ); - let sent_msg = alices[1].send_text(a1_broadcast_id, "hi").await; - let msg = bob.recv_msg(&sent_msg).await; - let chat = Chat::load_from_db(&bob, msg.chat_id).await?; - assert_eq!(chat.get_type(), Chattype::Mailinglist); - // TODO: It doesn't work now for some reason, `msg.chat_id == DC_CHAT_ID_TRASH`. - // let msg = alices[0].recv_msg(&sent_msg).await; - // assert_eq!(msg.chat_id, a0_broadcast_id); - chat::remove_contact_from_chat(&alices[0], a0_broadcast_id, ab_contact_ids[0]).await?; - sync(&alices).await?; - assert!(chat::get_chat_contacts(&alices[1], a1_broadcast_id) - .await? - .is_empty()); - - Ok(()) - } }