1use anyhow::Result;
4use mail_builder::mime::MimePart;
5use serde::{Deserialize, Serialize};
6
7use crate::chat::{self, ChatId};
8use crate::config::Config;
9use crate::constants::Blocked;
10use crate::contact::ContactId;
11use crate::context::Context;
12use crate::log::{LogExt as _, warn};
13use crate::login_param::EnteredLoginParam;
14use crate::message::{Message, MsgId, Viewtype};
15use crate::mimeparser::SystemMessage;
16use crate::param::Param;
17use crate::sync::SyncData::{AddQrToken, AlterChat, DeleteQrToken};
18use crate::token::Namespace;
19use crate::tools::time;
20use crate::transport::{ConfiguredLoginParamJson, sync_transports};
21use crate::{message, stock_str, token};
22use std::collections::HashSet;
23
24#[derive(Debug, PartialEq)]
26pub(crate) enum Sync {
27 Nosync,
28 Sync,
29}
30
31impl From<Sync> for bool {
32 fn from(sync: Sync) -> bool {
33 match sync {
34 Sync::Nosync => false,
35 Sync::Sync => true,
36 }
37 }
38}
39
40impl From<bool> for Sync {
41 fn from(sync: bool) -> Sync {
42 match sync {
43 false => Sync::Nosync,
44 true => Sync::Sync,
45 }
46 }
47}
48
49#[derive(Debug, Serialize, Deserialize)]
50pub(crate) struct QrTokenData {
51 pub(crate) invitenumber: String,
52 pub(crate) auth: String,
53 pub(crate) grpid: Option<String>,
54}
55
56#[derive(Debug, Serialize, Deserialize)]
57pub(crate) struct TransportData {
58 pub(crate) configured: ConfiguredLoginParamJson,
60
61 pub(crate) entered: EnteredLoginParam,
65
66 pub(crate) timestamp: i64,
68
69 pub(crate) is_published: bool,
72}
73
74#[derive(Debug, Serialize, Deserialize)]
75pub(crate) struct RemovedTransportData {
76 pub(crate) addr: String,
78
79 pub(crate) timestamp: i64,
81}
82
83#[derive(Debug, Serialize, Deserialize)]
84pub(crate) enum SyncData {
85 AddQrToken(QrTokenData),
86 DeleteQrToken(QrTokenData),
87 AlterChat {
88 id: chat::SyncId,
89 action: chat::SyncAction,
90 },
91 Config {
92 key: Config,
93 val: String,
94 },
95 SaveMessage {
96 src: String, dest: String, },
99 DeleteMessages {
100 msgs: Vec<String>, },
102
103 Transports {
118 transports: Vec<TransportData>,
120
121 removed_transports: Vec<RemovedTransportData>,
123 },
124}
125
126#[derive(Debug, Serialize, Deserialize)]
127#[serde(untagged)]
128pub(crate) enum SyncDataOrUnknown {
129 SyncData(SyncData),
130 Unknown(serde_json::Value),
131}
132
133#[derive(Debug, Serialize, Deserialize)]
134pub(crate) struct SyncItem {
135 timestamp: i64,
136
137 data: SyncDataOrUnknown,
138}
139
140#[derive(Debug, Deserialize)]
141pub(crate) struct SyncItems {
142 items: Vec<SyncItem>,
143}
144
145impl From<SyncData> for SyncDataOrUnknown {
146 fn from(sync_data: SyncData) -> Self {
147 Self::SyncData(sync_data)
148 }
149}
150
151impl Context {
152 pub(crate) async fn add_sync_item(&self, data: SyncData) -> Result<()> {
157 self.add_sync_item_with_timestamp(data, time()).await
158 }
159
160 async fn add_sync_item_with_timestamp(&self, data: SyncData, timestamp: i64) -> Result<()> {
163 if !self.should_send_sync_msgs().await? {
164 return Ok(());
165 }
166
167 let item = SyncItem {
168 timestamp,
169 data: data.into(),
170 };
171 let item = serde_json::to_string(&item)?;
172 self.sql
173 .execute("INSERT INTO multi_device_sync (item) VALUES(?);", (item,))
174 .await?;
175
176 Ok(())
177 }
178
179 pub(crate) async fn sync_qr_code_tokens(&self, grpid: Option<&str>) -> Result<()> {
184 if !self.should_send_sync_msgs().await? {
185 return Ok(());
186 }
187 if let (Some(invitenumber), Some(auth)) = (
188 token::lookup(self, Namespace::InviteNumber, grpid).await?,
189 token::lookup(self, Namespace::Auth, grpid).await?,
190 ) {
191 self.add_sync_item(SyncData::AddQrToken(QrTokenData {
192 invitenumber,
193 auth,
194 grpid: grpid.map(|s| s.to_string()),
195 }))
196 .await?;
197 }
198 Ok(())
199 }
200
201 pub(crate) async fn sync_qr_code_token_deletion(
205 &self,
206 invitenumber: String,
207 auth: String,
208 ) -> Result<()> {
209 self.add_sync_item(SyncData::DeleteQrToken(QrTokenData {
210 invitenumber,
211 auth,
212 grpid: None,
213 }))
214 .await?;
215 self.scheduler.interrupt_smtp().await;
216 Ok(())
217 }
218
219 pub async fn send_sync_msg(&self) -> Result<Option<MsgId>> {
225 if let Some((json, ids)) = self.build_sync_json().await? {
226 let chat_id =
227 ChatId::create_for_contact_with_blocked(self, ContactId::SELF, Blocked::Yes)
228 .await?;
229 let mut msg = Message {
230 chat_id,
231 viewtype: Viewtype::Text,
232 text: stock_str::sync_msg_body(self),
233 hidden: true,
234 subject: stock_str::sync_msg_subject(self),
235 ..Default::default()
236 };
237 msg.param.set_cmd(SystemMessage::MultiDeviceSync);
238 msg.param.set(Param::Arg, json);
239 msg.param.set(Param::Arg2, ids);
240 msg.param.set_int(Param::GuaranteeE2ee, 1);
241 Ok(Some(chat::send_msg(self, chat_id, &mut msg).await?))
242 } else {
243 Ok(None)
244 }
245 }
246
247 pub(crate) async fn build_sync_json(&self) -> Result<Option<(String, String)>> {
250 let (ids, serialized) = self
251 .sql
252 .query_map(
253 "SELECT id, item FROM multi_device_sync ORDER BY id;",
254 (),
255 |row| {
256 let id: u32 = row.get(0)?;
257 let item: String = row.get(1)?;
258 Ok((id, item))
259 },
260 |rows| {
261 let mut ids = vec![];
262 let mut serialized = String::default();
263 for row in rows {
264 let (id, item) = row?;
265 ids.push(id);
266 if !serialized.is_empty() {
267 serialized.push_str(",\n");
268 }
269 serialized.push_str(&item);
270 }
271 Ok((ids, serialized))
272 },
273 )
274 .await?;
275
276 if ids.is_empty() {
277 Ok(None)
278 } else {
279 Ok(Some((
280 format!("{{\"items\":[\n{serialized}\n]}}"),
281 ids.iter()
282 .map(|x| x.to_string())
283 .collect::<Vec<String>>()
284 .join(","),
285 )))
286 }
287 }
288
289 pub(crate) fn build_sync_part(&self, json: String) -> MimePart<'static> {
290 MimePart::new("application/json", json).attachment("multi-device-sync.json")
291 }
292
293 pub(crate) fn parse_sync_items(&self, serialized: String) -> Result<SyncItems> {
296 let sync_items: SyncItems = serde_json::from_str(&serialized)?;
297 Ok(sync_items)
298 }
299
300 pub(crate) async fn execute_sync_items(&self, items: &SyncItems, timestamp_sent: i64) {
311 info!(self, "executing {} sync item(s)", items.items.len());
312 for item in &items.items {
313 let timestamp = std::cmp::min(item.timestamp, timestamp_sent);
318
319 match &item.data {
320 SyncDataOrUnknown::SyncData(data) => match data {
321 AddQrToken(token) => self.add_qr_token(token, timestamp).await,
322 DeleteQrToken(token) => self.delete_qr_token(token, timestamp).await,
323 AlterChat { id, action } => self.sync_alter_chat(id, action).await,
324 SyncData::Config { key, val } => self.sync_config(key, val).await,
325 SyncData::SaveMessage { src, dest } => self.save_message(src, dest).await,
326 SyncData::DeleteMessages { msgs } => self.sync_message_deletion(msgs).await,
327 SyncData::Transports {
328 transports,
329 removed_transports,
330 } => sync_transports(self, transports, removed_transports).await,
331 },
332 SyncDataOrUnknown::Unknown(data) => {
333 warn!(self, "Ignored unknown sync item: {data}.");
334 Ok(())
335 }
336 }
337 .log_err(self)
338 .ok();
339 }
340
341 if !items.items.is_empty() && !self.get_config_bool(Config::BccSelf).await.unwrap_or(true) {
344 self.set_config_ex(Sync::Nosync, Config::BccSelf, Some("1"))
345 .await
346 .log_err(self)
347 .ok();
348 }
349 }
350
351 async fn add_qr_token(&self, token: &QrTokenData, timestamp: i64) -> Result<()> {
352 let grpid = token.grpid.as_deref();
353 token::save(
354 self,
355 Namespace::InviteNumber,
356 grpid,
357 &token.invitenumber,
358 timestamp,
359 )
360 .await?;
361 token::save(self, Namespace::Auth, grpid, &token.auth, timestamp).await?;
362 Ok(())
363 }
364
365 async fn delete_qr_token(&self, token: &QrTokenData, timestamp: i64) -> Result<()> {
366 self.sql
367 .execute(
368 "DELETE FROM tokens
369 WHERE foreign_key IN
370 (SELECT foreign_key FROM tokens
371 WHERE token=? OR token=? AND timestamp <= ?)",
372 (&token.invitenumber, &token.auth, timestamp),
373 )
374 .await?;
375 Ok(())
376 }
377
378 async fn save_message(&self, src_rfc724_mid: &str, dest_rfc724_mid: &String) -> Result<()> {
379 if let Some(src_msg_id) = message::rfc724_mid_exists(self, src_rfc724_mid).await? {
380 chat::save_copy_in_self_talk(self, src_msg_id, dest_rfc724_mid).await?;
381 }
382 Ok(())
383 }
384
385 async fn sync_message_deletion(&self, msgs: &Vec<String>) -> Result<()> {
386 let mut modified_chat_ids = HashSet::new();
387 let mut msg_ids = Vec::new();
388 for rfc724_mid in msgs {
389 if let Some(msg_id) = message::rfc724_mid_exists(self, rfc724_mid).await? {
390 if let Some(msg) = Message::load_from_db_optional(self, msg_id).await? {
391 message::delete_msg_locally(self, &msg).await?;
392 msg_ids.push(msg.id);
393 modified_chat_ids.insert(msg.chat_id);
394 } else {
395 warn!(self, "Sync message delete: Database entry does not exist.");
396 }
397 } else {
398 warn!(self, "Sync message delete: {rfc724_mid:?} not found.");
399 }
400 }
401 message::delete_msgs_locally_done(self, &msg_ids, modified_chat_ids).await?;
402 Ok(())
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use std::time::Duration;
409
410 use anyhow::bail;
411
412 use super::*;
413 use crate::chat::{Chat, remove_contact_from_chat};
414 use crate::chatlist::Chatlist;
415 use crate::contact::{Contact, Origin};
416 use crate::securejoin::get_securejoin_qr;
417 use crate::test_utils::{self, TestContext, TestContextManager};
418 use crate::tools::SystemTime;
419
420 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
421 async fn test_config_sync_msgs() -> Result<()> {
422 let t = TestContext::new_alice().await;
423 assert_eq!(t.get_config_bool(Config::SyncMsgs).await?, false);
424 assert_eq!(t.get_config_bool(Config::BccSelf).await?, true);
425 assert_eq!(t.should_send_sync_msgs().await?, false);
426
427 t.set_config_bool(Config::SyncMsgs, true).await?;
428 assert_eq!(t.get_config_bool(Config::SyncMsgs).await?, true);
429 assert_eq!(t.get_config_bool(Config::BccSelf).await?, true);
430 assert_eq!(t.should_send_sync_msgs().await?, true);
431
432 t.set_config_bool(Config::BccSelf, false).await?;
433 assert_eq!(t.get_config_bool(Config::SyncMsgs).await?, true);
434 assert_eq!(t.get_config_bool(Config::BccSelf).await?, false);
435 assert_eq!(t.should_send_sync_msgs().await?, false);
436
437 t.set_config_bool(Config::SyncMsgs, false).await?;
438 assert_eq!(t.get_config_bool(Config::SyncMsgs).await?, false);
439 assert_eq!(t.get_config_bool(Config::BccSelf).await?, false);
440 assert_eq!(t.should_send_sync_msgs().await?, false);
441 Ok(())
442 }
443
444 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
445 async fn test_build_sync_json() -> Result<()> {
446 let t = TestContext::new_alice().await;
447 t.set_config_bool(Config::SyncMsgs, true).await?;
448
449 assert!(t.build_sync_json().await?.is_none());
450
451 t.add_sync_item_with_timestamp(
455 SyncData::AlterChat {
456 id: chat::SyncId::ContactAddr("bob@example.net".to_string()),
457 action: chat::SyncAction::SetMuted(chat::MuteDuration::Until(
458 SystemTime::UNIX_EPOCH + Duration::from_millis(42999),
459 )),
460 },
461 1631781315,
462 )
463 .await?;
464
465 t.add_sync_item_with_timestamp(
466 SyncData::AddQrToken(QrTokenData {
467 invitenumber: "testinvite".to_string(),
468 auth: "testauth".to_string(),
469 grpid: Some("group123".to_string()),
470 }),
471 1631781316,
472 )
473 .await?;
474 t.add_sync_item_with_timestamp(
475 SyncData::DeleteQrToken(QrTokenData {
476 invitenumber: "123!?\":.;{}".to_string(),
477 auth: "456".to_string(),
478 grpid: None,
479 }),
480 1631781317,
481 )
482 .await?;
483
484 let (serialized, ids) = t.build_sync_json().await?.unwrap();
485 assert_eq!(
486 serialized,
487 r#"{"items":[
488{"timestamp":1631781315,"data":{"AlterChat":{"id":{"ContactAddr":"bob@example.net"},"action":{"SetMuted":{"Until":{"secs_since_epoch":42,"nanos_since_epoch":999000000}}}}}},
489{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"testinvite","auth":"testauth","grpid":"group123"}}},
490{"timestamp":1631781317,"data":{"DeleteQrToken":{"invitenumber":"123!?\":.;{}","auth":"456","grpid":null}}}
491]}"#
492 );
493
494 assert!(t.build_sync_json().await?.is_some());
495 t.sql
496 .execute(
497 &format!("DELETE FROM multi_device_sync WHERE id IN ({ids})"),
498 (),
499 )
500 .await?;
501 assert!(t.build_sync_json().await?.is_none());
502
503 let sync_items = t.parse_sync_items(serialized)?;
504 assert_eq!(sync_items.items.len(), 3);
505
506 Ok(())
507 }
508
509 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
510 async fn test_build_sync_json_sync_msgs_off() -> Result<()> {
511 let t = TestContext::new_alice().await;
512 t.set_config_bool(Config::SyncMsgs, false).await?;
513 t.add_sync_item(SyncData::AddQrToken(QrTokenData {
514 invitenumber: "testinvite".to_string(),
515 auth: "testauth".to_string(),
516 grpid: Some("group123".to_string()),
517 }))
518 .await?;
519 assert!(t.build_sync_json().await?.is_none());
520 Ok(())
521 }
522
523 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
524 async fn test_parse_sync_items() -> Result<()> {
525 let t = TestContext::new_alice().await;
526
527 assert!(t.parse_sync_items(r#"{bad json}"#.to_string()).is_err());
528
529 assert!(t.parse_sync_items(r#"{"badname":[]}"#.to_string()).is_err());
530
531 for bad_item_example in [
532 r#"{"items":[{"timestamp":1631781316,"data":{"BadItem":{"invitenumber":"in","auth":"a","grpid":null}}}]}"#,
533 r#"{"items":[{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"in","auth":123}}}]}"#, r#"{"items":[{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"in","auth":true}}}]}"#, r#"{"items":[{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"in","auth":[]}}}]}"#, r#"{"items":[{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"in","auth":{}}}}]}"#, r#"{"items":[{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"in","grpid":null}}}]}"#, r#"{"items":[{"timestamp":1631781316,"data":{"AlterChat":{"id":{"ContactAddr":"bob@example.net"},"action":"Burn"}}}]}"#, ] {
540 let sync_items = t.parse_sync_items(bad_item_example.to_string()).unwrap();
541 assert_eq!(sync_items.items.len(), 1);
542 assert!(matches!(sync_items.items[0].timestamp, 1631781316));
543 assert!(matches!(
544 sync_items.items[0].data,
545 SyncDataOrUnknown::Unknown(_)
546 ));
547 }
548
549 let sync_items = t.parse_sync_items(
551 r#"{"items":[{"timestamp":1631781318,"data":{"AlterChat":{"id":{"ContactAddr":"bob@example.net"},"action":{"SetMuted":{"Until":{"secs_since_epoch":42,"nanos_since_epoch":999000000}}}}}}]}"#.to_string(),
552 )?;
553 assert_eq!(sync_items.items.len(), 1);
554 let SyncDataOrUnknown::SyncData(AlterChat { id, action }) =
555 &sync_items.items.first().unwrap().data
556 else {
557 bail!("bad item");
558 };
559 assert_eq!(
560 *id,
561 chat::SyncId::ContactAddr("bob@example.net".to_string())
562 );
563 assert_eq!(
564 *action,
565 chat::SyncAction::SetMuted(chat::MuteDuration::Until(
566 SystemTime::UNIX_EPOCH + Duration::from_millis(42999)
567 ))
568 );
569
570 assert_eq!(
572 t.parse_sync_items(r#"{"items":[]}"#.to_string())?
573 .items
574 .len(),
575 0
576 );
577
578 let sync_items = t
580 .parse_sync_items(
581 r#"{"items":[
582{"timestamp":1631781316,"data":{"DeleteQrToken":{"invitenumber":"in","auth":"yip","grpid":null}}},
583{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"in","auth":"yip","additional":123,"grpid":null}}}
584]}"#
585 .to_string(),
586 )
587 ?;
588 assert_eq!(sync_items.items.len(), 2);
589
590 let sync_items = t.parse_sync_items(
591 r#"{"items":[
592{"timestamp":1631781318,"data":{"AddQrToken":{"invitenumber":"in","auth":"yip","grpid":null}}}
593],"additional":"field"}"#
594 .to_string(),
595 )?;
596
597 assert_eq!(sync_items.items.len(), 1);
598 if let SyncDataOrUnknown::SyncData(AddQrToken(token)) =
599 &sync_items.items.first().unwrap().data
600 {
601 assert_eq!(token.invitenumber, "in");
602 assert_eq!(token.auth, "yip");
603 assert_eq!(token.grpid, None);
604 } else {
605 bail!("bad item");
606 }
607
608 let sync_items = t.parse_sync_items(
610 r#"{"items":[{"timestamp":1631781319,"data":{"AddQrToken":{"invitenumber":"in","auth":"a"}}}]}"#.to_string(),
611 )
612 ?;
613 assert_eq!(sync_items.items.len(), 1);
614
615 Ok(())
616 }
617
618 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
619 async fn test_execute_sync_items() -> Result<()> {
620 let t = TestContext::new_alice().await;
621
622 assert!(!token::exists(&t, Namespace::Auth, "yip-auth").await?);
623
624 let timestamp_sent = time();
625 let sync_items = t
626 .parse_sync_items(
627 r#"{"items":[
628{"timestamp":1631781315,"data":{"AlterChat":{"id":{"ContactAddr":"bob@example.net"},"action":"Block"}}},
629{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"yip-in","auth":"a"}}},
630{"timestamp":1631781316,"data":{"DeleteQrToken":{"invitenumber":"in","auth":"delete unexistent, shall continue"}}},
631{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"in","auth":"yip-auth"}}},
632{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"in","auth":"foo","grpid":"non-existent"}}},
633{"timestamp":1631781316,"data":{"AddQrToken":{"invitenumber":"in","auth":"directly deleted"}}},
634{"timestamp":1631781316,"data":{"DeleteQrToken":{"invitenumber":"in","auth":"directly deleted"}}}
635]}"#
636 .to_string(),
637 )
638 ?;
639 t.execute_sync_items(&sync_items, timestamp_sent).await;
640
641 assert!(
642 Contact::lookup_id_by_addr(&t, "bob@example.net", Origin::Unknown)
643 .await?
644 .is_none()
645 );
646 assert!(!token::exists(&t, Namespace::InviteNumber, "yip-in").await?);
647 assert!(!token::exists(&t, Namespace::Auth, "yip-auth").await?);
648 assert!(!token::exists(&t, Namespace::Auth, "non-existent").await?);
649 assert!(!token::exists(&t, Namespace::Auth, "directly deleted").await?);
650
651 Ok(())
652 }
653
654 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
655 async fn test_send_sync_msg() -> Result<()> {
656 let alice = TestContext::new_alice().await;
657 alice.set_config_bool(Config::SyncMsgs, true).await?;
658 alice
659 .add_sync_item(SyncData::AddQrToken(QrTokenData {
660 invitenumber: "in".to_string(),
661 auth: "testtoken".to_string(),
662 grpid: None,
663 }))
664 .await?;
665 let msg_id = alice.send_sync_msg().await?.unwrap();
666 let msg = Message::load_from_db(&alice, msg_id).await?;
667 let chat = Chat::load_from_db(&alice, msg.chat_id).await?;
668 assert!(chat.is_self_talk());
669
670 assert_eq!(Chatlist::try_load(&alice, 0, None, None).await?.len(), 0);
673 let chat_id = ChatId::create_for_contact(&alice, ContactId::SELF).await?;
674 let chat = Chat::load_from_db(&alice, chat_id).await?;
675 assert!(chat.is_self_talk());
676 assert_eq!(Chatlist::try_load(&alice, 0, None, None).await?.len(), 1);
677 let msgs = chat::get_chat_msgs(&alice, chat_id).await?;
678 assert_eq!(msgs.len(), 0);
679
680 let sent_msg = alice.pop_sent_msg().await;
683 let alice2 = TestContext::new_alice().await;
684 alice2.set_config_bool(Config::SyncMsgs, true).await?;
685 alice2.recv_msg_trash(&sent_msg).await;
686 assert!(token::exists(&alice2, token::Namespace::Auth, "testtoken").await?);
687 assert_eq!(Chatlist::try_load(&alice2, 0, None, None).await?.len(), 0);
688
689 let self_contact = alice2.add_or_lookup_contact(&alice2).await;
691 assert!(!self_contact.is_bot());
692
693 let bob = TestContext::new_bob().await;
695 bob.recv_msg_trash(&sent_msg).await;
696 assert!(!token::exists(&bob, token::Namespace::Auth, "testtoken").await?);
697
698 Ok(())
699 }
700
701 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
702 async fn test_send_sync_msg_enables_bccself() -> Result<()> {
703 for (chatmail, sync_message_sent) in
704 [(false, false), (false, true), (true, false), (true, true)]
705 {
706 let alice1 = TestContext::new_alice().await;
707 let alice2 = TestContext::new_alice().await;
708
709 alice1.set_config_bool(Config::SyncMsgs, true).await?;
712 alice2.set_config_bool(Config::SyncMsgs, true).await?;
713
714 alice1.set_config_bool(Config::IsChatmail, chatmail).await?;
715 alice2.set_config_bool(Config::IsChatmail, chatmail).await?;
716
717 alice1.set_config_bool(Config::BccSelf, true).await?;
718 alice2.set_config_bool(Config::BccSelf, false).await?;
719
720 let sent_msg = if sync_message_sent {
721 alice1
722 .add_sync_item(SyncData::AddQrToken(QrTokenData {
723 invitenumber: "in".to_string(),
724 auth: "testtoken".to_string(),
725 grpid: None,
726 }))
727 .await?;
728 alice1.send_sync_msg().await?.unwrap();
729 alice1.pop_sent_msg().await
730 } else {
731 let chat = alice1.get_self_chat().await;
732 alice1.send_text(chat.id, "Hi").await
733 };
734
735 assert_eq!(alice2.get_config_bool(Config::BccSelf).await?, false);
740
741 alice2.recv_msg_opt(&sent_msg).await;
742 assert_eq!(
743 alice2.get_config_bool(Config::BccSelf).await?,
744 sync_message_sent
749 );
750 }
751 Ok(())
752 }
753
754 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
755 async fn test_bot_no_sync_msgs() -> Result<()> {
756 let mut tcm = TestContextManager::new();
757 let alice = &tcm.alice().await;
758 let bob = &tcm.bob().await;
759 alice.set_config_bool(Config::SyncMsgs, true).await?;
760 let chat_id = alice.create_chat(bob).await.id;
761
762 chat::send_text_msg(alice, chat_id, "hi".to_string()).await?;
763 alice
764 .set_config(Config::Displayname, Some("Alice Human"))
765 .await?;
766 alice.send_sync_msg().await?;
767 alice.pop_sent_msg().await;
768 let msg = bob.recv_msg(&alice.pop_sent_msg().await).await;
769 assert_eq!(msg.text, "hi");
770
771 alice.set_config_bool(Config::Bot, true).await?;
772 chat::send_text_msg(alice, chat_id, "hi".to_string()).await?;
773 alice
774 .set_config(Config::Displayname, Some("Alice Bot"))
775 .await?;
776 let msg = bob.recv_msg(&alice.pop_sent_msg().await).await;
777 assert_eq!(msg.text, "hi");
778 Ok(())
779 }
780
781 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
782 async fn test_unpromoted_group_qr_sync() -> Result<()> {
783 let mut tcm = TestContextManager::new();
784 let alice = &tcm.alice().await;
785 alice.set_config_bool(Config::SyncMsgs, true).await?;
786 let alice_chatid = chat::create_group(alice, "the chat").await?;
787 let qr = get_securejoin_qr(alice, Some(alice_chatid)).await?;
788
789 let alice2 = &tcm.alice().await;
791 alice2.set_config_bool(Config::SyncMsgs, true).await?;
792 test_utils::sync(alice, alice2).await;
793
794 let bob = &tcm.bob().await;
795 tcm.exec_securejoin_qr(bob, alice, &qr).await;
796 assert!(alice.send_sync_msg().await?.is_none());
797
798 let alice_bob_id = alice.add_or_lookup_contact(bob).await.id;
800 remove_contact_from_chat(alice, alice_chatid, alice_bob_id).await?;
801 alice.pop_sent_msg().await;
802 let sent = alice
803 .send_text(alice_chatid, "Promoting group to another device")
804 .await;
805 alice2.recv_msg(&sent).await;
806
807 let fiona = &tcm.fiona().await;
808 tcm.exec_securejoin_qr(fiona, alice2, &qr).await;
809 let msg = fiona.get_last_msg().await;
810 assert_eq!(msg.text, "Member Me added by alice@example.org.");
811 Ok(())
812 }
813}