mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 09:26:29 +03:00
test: Add python test test_qr_securejoin_broadcast, and fix some small bugs I found on the way
This commit is contained in:
@@ -439,6 +439,12 @@ class Account:
|
|||||||
"""Wait for reaction change event."""
|
"""Wait for reaction change event."""
|
||||||
return self.wait_for_event(EventType.REACTIONS_CHANGED)
|
return self.wait_for_event(EventType.REACTIONS_CHANGED)
|
||||||
|
|
||||||
|
def wait_for_imap_inbox_idle(self):
|
||||||
|
"""Wait until all messages are fetched,
|
||||||
|
and the IMAP loop enters IDLE mode.
|
||||||
|
"""
|
||||||
|
self.wait_for_event(EventType.IMAP_INBOX_IDLE)
|
||||||
|
|
||||||
def get_fresh_messages_in_arrival_order(self) -> list[Message]:
|
def get_fresh_messages_in_arrival_order(self) -> list[Message]:
|
||||||
"""Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
|
"""Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
|
||||||
warn(
|
warn(
|
||||||
|
|||||||
@@ -112,6 +112,98 @@ def test_qr_securejoin(acfactory, protect):
|
|||||||
fiona.wait_for_securejoin_joiner_success()
|
fiona.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("all_devices_online", [True, False])
|
||||||
|
def test_qr_securejoin_broadcast(acfactory, all_devices_online):
|
||||||
|
alice, bob, fiona = acfactory.get_online_accounts(3)
|
||||||
|
|
||||||
|
alice2 = alice.clone()
|
||||||
|
bob2 = bob.clone()
|
||||||
|
|
||||||
|
if all_devices_online:
|
||||||
|
alice2.start_io()
|
||||||
|
bob2.start_io()
|
||||||
|
|
||||||
|
logging.info("Alice creates a broadcast")
|
||||||
|
alice_chat = alice.create_broadcast("Broadcast channel for everyone!")
|
||||||
|
|
||||||
|
logging.info("Bob joins the broadcast")
|
||||||
|
|
||||||
|
qr_code = alice_chat.get_qr_code()
|
||||||
|
bob.secure_join(qr_code)
|
||||||
|
alice.wait_for_securejoin_inviter_success()
|
||||||
|
bob.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
|
snapshot = bob.wait_for_incoming_msg().get_snapshot()
|
||||||
|
assert snapshot.text == f"Member Me added by {alice.get_config('addr')}."
|
||||||
|
|
||||||
|
alice_chat.send_text("Hello everyone!")
|
||||||
|
snapshot = bob.wait_for_incoming_msg().get_snapshot()
|
||||||
|
assert snapshot.text == "Hello everyone!"
|
||||||
|
|
||||||
|
def check_account(ac, contact, inviter_side, please_wait_info_msg=False):
|
||||||
|
# Check that the chat partner is verified.
|
||||||
|
contact_snapshot = contact.get_snapshot()
|
||||||
|
assert contact_snapshot.is_verified
|
||||||
|
|
||||||
|
chat = ac.get_chatlist()[0]
|
||||||
|
chat_msgs = chat.get_messages()
|
||||||
|
|
||||||
|
if please_wait_info_msg:
|
||||||
|
first_msg = chat_msgs.pop(0).get_snapshot()
|
||||||
|
assert first_msg.text == "Establishing guaranteed end-to-end encryption, please wait…"
|
||||||
|
assert first_msg.is_info
|
||||||
|
|
||||||
|
encrypted_msg = chat_msgs[0].get_snapshot()
|
||||||
|
assert encrypted_msg.text == "Messages are end-to-end encrypted."
|
||||||
|
assert encrypted_msg.is_info
|
||||||
|
|
||||||
|
member_added_msg = chat_msgs[1].get_snapshot()
|
||||||
|
if inviter_side:
|
||||||
|
assert member_added_msg.text == f"Member {contact_snapshot.display_name} added."
|
||||||
|
else:
|
||||||
|
assert member_added_msg.text == f"Member Me added by {contact_snapshot.display_name}."
|
||||||
|
assert member_added_msg.is_info
|
||||||
|
|
||||||
|
hello_msg = chat_msgs[2].get_snapshot()
|
||||||
|
assert hello_msg.text == "Hello everyone!"
|
||||||
|
assert not hello_msg.is_info
|
||||||
|
|
||||||
|
assert len(chat_msgs) == 3
|
||||||
|
|
||||||
|
chat_snapshot = chat.get_basic_snapshot() # TODO or get_full_snapshot()
|
||||||
|
assert chat_snapshot.is_encrypted
|
||||||
|
|
||||||
|
# TODO check more things
|
||||||
|
|
||||||
|
check_account(alice, alice.create_contact(bob), inviter_side=True)
|
||||||
|
check_account(bob, bob.create_contact(alice), inviter_side=False, please_wait_info_msg=True)
|
||||||
|
|
||||||
|
logging.info("===================== Test Alice's second device =====================")
|
||||||
|
|
||||||
|
# Start second Alice device, if it wasn't started already.
|
||||||
|
alice2.start_io()
|
||||||
|
alice2.wait_for_securejoin_inviter_success()
|
||||||
|
alice2.wait_for_imap_inbox_idle()
|
||||||
|
check_account(alice2, alice2.create_contact(bob), inviter_side=True)
|
||||||
|
|
||||||
|
logging.info("===================== Test Bob's second device =====================")
|
||||||
|
|
||||||
|
# Start second Bob device, if it wasn't started already.
|
||||||
|
bob2.start_io()
|
||||||
|
bob2.wait_for_securejoin_joiner_success()
|
||||||
|
bob2.wait_for_imap_inbox_idle()
|
||||||
|
check_account(bob2, bob2.create_contact(alice), inviter_side=False)
|
||||||
|
|
||||||
|
# The QR code token is synced, so alice2 must be able to handle join requests.
|
||||||
|
logging.info("Fiona joins the group via alice2")
|
||||||
|
alice.stop_io()
|
||||||
|
fiona.secure_join(qr_code)
|
||||||
|
alice2.wait_for_securejoin_inviter_success()
|
||||||
|
fiona.wait_for_securejoin_joiner_success()
|
||||||
|
|
||||||
|
# TODO test that Fiona is in the channel correctly
|
||||||
|
|
||||||
|
|
||||||
def test_qr_securejoin_contact_request(acfactory) -> None:
|
def test_qr_securejoin_contact_request(acfactory) -> None:
|
||||||
"""Alice invites Bob to a group when Bob's chat with Alice is in a contact request mode."""
|
"""Alice invites Bob to a group when Bob's chat with Alice is in a contact request mode."""
|
||||||
alice, bob = acfactory.get_online_accounts(2)
|
alice, bob = acfactory.get_online_accounts(2)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ envlist =
|
|||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands =
|
commands =
|
||||||
pytest -n6 {posargs}
|
pytest {posargs}
|
||||||
setenv =
|
setenv =
|
||||||
# Avoid stack overflow when Rust core is built without optimizations.
|
# Avoid stack overflow when Rust core is built without optimizations.
|
||||||
RUST_MIN_STACK=8388608
|
RUST_MIN_STACK=8388608
|
||||||
@@ -25,6 +25,6 @@ commands =
|
|||||||
ruff check src/ examples/ tests/
|
ruff check src/ examples/ tests/
|
||||||
|
|
||||||
[pytest]
|
[pytest]
|
||||||
timeout = 300
|
timeout = 30
|
||||||
log_cli = true
|
log_cli = true
|
||||||
log_level = debug
|
log_level = debug
|
||||||
|
|||||||
38
src/chat.rs
38
src/chat.rs
@@ -601,11 +601,23 @@ impl ChatId {
|
|||||||
|| chat.is_device_talk()
|
|| chat.is_device_talk()
|
||||||
|| chat.is_self_talk()
|
|| chat.is_self_talk()
|
||||||
|| (!chat.can_send(context).await? && !chat.is_contact_request())
|
|| (!chat.can_send(context).await? && !chat.is_contact_request())
|
||||||
|
// For chattype InBrodacast, the info message is added when the member-added message is received
|
||||||
|
// by directly calling add_encrypted_msg():
|
||||||
|
|| chat.typ == Chattype::InBroadcast
|
||||||
|| chat.blocked == Blocked::Yes
|
|| chat.blocked == Blocked::Yes
|
||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.add_encrypted_msg(context, timestamp_sort).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn add_encrypted_msg(
|
||||||
|
self,
|
||||||
|
context: &Context,
|
||||||
|
timestamp_sort: i64,
|
||||||
|
) -> Result<()> {
|
||||||
let text = stock_str::messages_e2e_encrypted(context).await;
|
let text = stock_str::messages_e2e_encrypted(context).await;
|
||||||
add_info_msg_with_cmd(
|
add_info_msg_with_cmd(
|
||||||
context,
|
context,
|
||||||
@@ -3817,10 +3829,17 @@ pub(crate) async fn create_broadcast_ex(
|
|||||||
chat_name: String,
|
chat_name: String,
|
||||||
secret: String,
|
secret: String,
|
||||||
) -> Result<ChatId> {
|
) -> Result<ChatId> {
|
||||||
let row_id = {
|
let chat_name = sanitize_single_line(&chat_name);
|
||||||
|
if chat_name.is_empty() {
|
||||||
|
bail!("Invalid broadcast channel name: {chat_name}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let timestamp = create_smeared_timestamp(context);
|
||||||
|
let chat_id = {
|
||||||
let chat_name = &chat_name;
|
let chat_name = &chat_name;
|
||||||
let grpid = &grpid;
|
let grpid = &grpid;
|
||||||
let trans_fn = |t: &mut rusqlite::Transaction| {
|
let trans_fn = |t: &mut rusqlite::Transaction| -> Result<u32> {
|
||||||
|
// TODO it's not needed to lookup an existing broadcast here
|
||||||
let cnt = t.execute("UPDATE chats SET name=? WHERE grpid=?", (chat_name, grpid))?;
|
let cnt = t.execute("UPDATE chats SET name=? WHERE grpid=?", (chat_name, grpid))?;
|
||||||
ensure!(cnt <= 1, "{cnt} chats exist with grpid {grpid}");
|
ensure!(cnt <= 1, "{cnt} chats exist with grpid {grpid}");
|
||||||
if cnt == 1 {
|
if cnt == 1 {
|
||||||
@@ -3828,7 +3847,7 @@ pub(crate) async fn create_broadcast_ex(
|
|||||||
"SELECT id FROM chats WHERE grpid=? AND type=?",
|
"SELECT id FROM chats WHERE grpid=? AND type=?",
|
||||||
(grpid, Chattype::OutBroadcast),
|
(grpid, Chattype::OutBroadcast),
|
||||||
|row| {
|
|row| {
|
||||||
let id: isize = row.get(0)?;
|
let id: u32 = row.get(0)?;
|
||||||
Ok(id)
|
Ok(id)
|
||||||
},
|
},
|
||||||
)?);
|
)?);
|
||||||
@@ -3837,23 +3856,20 @@ pub(crate) async fn create_broadcast_ex(
|
|||||||
"INSERT INTO chats \
|
"INSERT INTO chats \
|
||||||
(type, name, grpid, created_timestamp) \
|
(type, name, grpid, created_timestamp) \
|
||||||
VALUES(?, ?, ?, ?);",
|
VALUES(?, ?, ?, ?);",
|
||||||
(
|
(Chattype::OutBroadcast, &chat_name, &grpid, timestamp),
|
||||||
Chattype::OutBroadcast,
|
|
||||||
&chat_name,
|
|
||||||
&grpid,
|
|
||||||
create_smeared_timestamp(context),
|
|
||||||
),
|
|
||||||
)?;
|
)?;
|
||||||
let chat_id = t.last_insert_rowid();
|
let chat_id = t.last_insert_rowid();
|
||||||
t.execute(SQL_INSERT_BROADCAST_SECRET, (chat_id, &secret))?;
|
t.execute(SQL_INSERT_BROADCAST_SECRET, (chat_id, &secret))?;
|
||||||
Ok(t.last_insert_rowid().try_into()?)
|
Ok(chat_id.try_into()?)
|
||||||
};
|
};
|
||||||
context.sql.transaction(trans_fn).await?
|
context.sql.transaction(trans_fn).await?
|
||||||
};
|
};
|
||||||
let chat_id = ChatId::new(u32::try_from(row_id)?);
|
let chat_id = ChatId::new(chat_id);
|
||||||
|
chat_id.maybe_add_encrypted_msg(context, timestamp).await?;
|
||||||
|
|
||||||
context.emit_msgs_changed_without_ids();
|
context.emit_msgs_changed_without_ids();
|
||||||
chatlist_events::emit_chatlist_changed(context);
|
chatlist_events::emit_chatlist_changed(context);
|
||||||
|
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||||
|
|
||||||
if sync.into() {
|
if sync.into() {
|
||||||
let id = SyncId::Grpid(grpid);
|
let id = SyncId::Grpid(grpid);
|
||||||
|
|||||||
@@ -3571,6 +3571,9 @@ async fn apply_in_broadcast_changes(
|
|||||||
info!(context, "No-op broadcast 'Member added' message (TRASH)");
|
info!(context, "No-op broadcast 'Member added' message (TRASH)");
|
||||||
msg = "".to_string();
|
msg = "".to_string();
|
||||||
} else {
|
} else {
|
||||||
|
chat.id
|
||||||
|
.add_encrypted_msg(context, mime_parser.timestamp_sent)
|
||||||
|
.await?;
|
||||||
msg = stock_str::msg_add_member_local(context, ContactId::SELF, from_id).await;
|
msg = stock_str::msg_add_member_local(context, ContactId::SELF, from_id).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user