feat: Group and broadcast channel descriptions (#7829)

fix https://github.com/chatmail/core/issues/7766

Implementation notes:

- Descriptions are only sent with member additions, when the description
is changed, and when promoting a previously-unpromoted group, in order
not to waste bandwith.
- Descriptions are not loaded everytime a chat object is loaded, because
they are only needed for the profile. Instead, they are in their own
table, and can be loaded with their own JsonRPC call.

---------

Co-authored-by: iequidoo <117991069+iequidoo@users.noreply.github.com>
This commit is contained in:
Hocuri
2026-02-10 22:28:12 +01:00
committed by GitHub
parent c475882727
commit 3fdda6f3b8
15 changed files with 411 additions and 24 deletions

View File

@@ -3156,6 +3156,119 @@ async fn test_broadcasts_name_and_avatar() -> Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_chat_description_basic() {
test_chat_description("", false).await.unwrap()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_chat_description_unpromoted_description() {
test_chat_description("Unpromoted description in the beginning", false)
.await
.unwrap()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_chat_description_qr() {
test_chat_description("", true).await.unwrap()
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_chat_description_unpromoted_description_qr() {
test_chat_description("Unpromoted description in the beginning", true)
.await
.unwrap()
}
async fn test_chat_description(initial_description: &str, join_via_qr: bool) -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let alice2 = &tcm.alice().await;
let bob = &tcm.bob().await;
alice.set_config_bool(Config::SyncMsgs, true).await?;
alice2.set_config_bool(Config::SyncMsgs, true).await?;
tcm.section("Create a group chat, and add Bob");
let alice_chat_id = create_group(alice, "My Group").await?;
if !initial_description.is_empty() {
set_chat_description(alice, alice_chat_id, initial_description).await?;
}
sync(alice, alice2).await;
let alice2_chat_id = get_chat_id_by_grpid(
alice2,
&Chat::load_from_db(alice, alice_chat_id).await?.grpid,
)
.await?
.unwrap()
.0;
assert_eq!(
get_chat_description(alice2, alice2_chat_id).await?,
initial_description
);
let bob_chat_id = if join_via_qr {
let qr = get_securejoin_qr(alice, Some(alice_chat_id)).await.unwrap();
tcm.exec_securejoin_qr(bob, alice, &qr).await
} else {
let alice_bob_id = alice.add_or_lookup_contact_id(bob).await;
add_contact_to_chat(alice, alice_chat_id, alice_bob_id).await?;
let sent = alice.send_text(alice_chat_id, "promoting the group").await;
bob.recv_msg(&sent).await.chat_id
};
assert_eq!(
get_chat_description(bob, bob_chat_id).await?,
initial_description
);
for description in ["This is a cool group", "", "ä ẟ 😂"] {
tcm.section(&format!(
"Alice sets the chat description to '{description}'"
));
set_chat_description(alice, alice_chat_id, description).await?;
let sent = alice.pop_sent_msg().await;
assert_eq!(
sent.load_from_db().await.text,
"You changed the chat description."
);
tcm.section("Bob receives the description change");
let rcvd = bob.recv_msg(&sent).await;
assert_eq!(rcvd.get_info_type(), SystemMessage::GroupDescriptionChanged);
assert_eq!(rcvd.text, "Chat description changed by alice@example.org.");
assert_eq!(get_chat_description(bob, rcvd.chat_id).await?, description);
tcm.section("Check Alice's second device");
alice2.recv_msg(&sent).await;
let alice2_chat_id = get_chat_id_by_grpid(
alice2,
&Chat::load_from_db(alice, alice_chat_id).await?.grpid,
)
.await?
.unwrap()
.0;
assert_eq!(
get_chat_description(alice2, alice2_chat_id).await?,
description
);
}
tcm.section("Alice calls set_chat_description() without actually changing the description");
set_chat_description(alice, alice_chat_id, "ä ẟ 😂").await?;
assert!(
alice
.pop_sent_msg_opt(Duration::from_secs(0))
.await
.is_none()
);
Ok(())
}
/// Tests that directly after broadcast-securejoin,
/// the brodacast is shown correctly on both devices.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]