diff --git a/ci_scripts/old/run-python.sh b/ci_scripts/old/run-python.sh index d431507e0..81506fc21 100755 --- a/ci_scripts/old/run-python.sh +++ b/ci_scripts/old/run-python.sh @@ -46,6 +46,7 @@ if [ -n "$TESTS" ]; then tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr" tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr" unset DCC_PY_LIVECONFIG + unset DCC_NEW_TMP_EMAIL tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc tox --workdir "$TOXWORKDIR" -e auditwheels popd diff --git a/ci_scripts/remote_python_packaging.sh b/ci_scripts/remote_python_packaging.sh index 7a2db322a..c6042ace8 100755 --- a/ci_scripts/remote_python_packaging.sh +++ b/ci_scripts/remote_python_packaging.sh @@ -32,11 +32,11 @@ ssh $SSHTARGET bash -c "cat >$BUILDDIR/exec_docker_run" <<_HERE set +x -e cd $BUILDDIR export DCC_PY_LIVECONFIG=$DCC_PY_LIVECONFIG - + export DCC_NEW_TMP_EMAIL=$DCC_NEW_TMP_EMAIL set -x # run everything else inside docker - docker run -e DCC_PY_LIVECONFIG \ + docker run -e DCC_NEW_TMP_EMAIL -e DCC_PY_LIVECONFIG \ --rm -it -v \$(pwd):/mnt -w /mnt \ deltachat/coredeps ci_scripts/run_all.sh diff --git a/ci_scripts/remote_tests_python.sh b/ci_scripts/remote_tests_python.sh index 49ae24197..98ffac7a5 100755 --- a/ci_scripts/remote_tests_python.sh +++ b/ci_scripts/remote_tests_python.sh @@ -30,6 +30,7 @@ ssh $SSHTARGET <<_HERE export CARGO_TARGET_DIR=\`pwd\`/../target export TARGET=release export DCC_PY_LIVECONFIG=$DCC_PY_LIVECONFIG + export DCC_NEW_TMP_EMAIL=$DCC_NEW_TMP_EMAIL #we rely on tox/virtualenv being available in the host #rm -rf virtualenv venv diff --git a/ci_scripts/run_all.sh b/ci_scripts/run_all.sh index 0d32d4645..6565ceedb 100755 --- a/ci_scripts/run_all.sh +++ b/ci_scripts/run_all.sh @@ -37,7 +37,8 @@ mkdir -p $TOXWORKDIR # XXX we may switch on some live-tests on for better ensurances # Note that the independent remote_tests_python step does all kinds of # live-testing already. -unset DCC_PY_LIVECONFIG +unset DCC_PY_LIVECONFIG +unset DCC_NEW_TMP_EMAIL tox --workdir "$TOXWORKDIR" -e py35,py36,py37,py38,auditwheels popd diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 6e9d1f6ff..71c8322d3 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1366,6 +1366,80 @@ class TestGroupStressTests: # Message should be encrypted because keys of other members are gossiped assert msg.is_encrypted() + def test_synchronize_member_list_on_group_rejoin(self, acfactory, lp): + """ + Test that user recreates group member list when it joins the group again. + + ac1 creates a group with two other accounts: ac2 and ac3 + Then it removes ac2, removes ac3 and adds ac2 back. + ac2 did not see that ac3 is removed, so it should rebuild member list from scratch. + """ + lp.sec("creating and configuring five accounts") + accounts = [acfactory.get_online_configuring_account() for i in range(3)] + for acc in accounts: + wait_configuration_progress(acc, 1000) + ac1 = accounts.pop() + + lp.sec("ac1: setting up contacts with 2 other members") + contacts = [] + for acc, name in zip(accounts, ["ac2", "ac3"]): + contact = ac1.create_contact(acc.get_config("addr"), name=name) + contacts.append(contact) + + # make sure we accept the "hi" message + ac1.create_chat_by_contact(contact) + + # make sure the other side accepts our messages + c1 = acc.create_contact(ac1.get_config("addr"), "a member") + chat1 = acc.create_chat_by_contact(c1) + + # send a message to get the contact key via autocrypt header + chat1.send_text("hi") + msg = ac1.wait_next_incoming_message() + assert msg.text == "hi" + + ac2, ac3 = accounts + + lp.sec("ac1: creating group chat with 2 other members") + chat = ac1.create_group_chat("title1") + for contact in contacts: + chat.add_contact(contact) + assert not chat.is_promoted() + + lp.sec("ac1: send mesage to new group chat") + msg = chat.send_text("hello") + assert chat.is_promoted() + assert msg.is_encrypted() + + num_contacts = len(chat.get_contacts()) + assert num_contacts == 3 + + lp.sec("checking that the chat arrived correctly") + for ac in accounts: + msg = ac.wait_next_incoming_message() + assert msg.text == "hello" + print("chat is", msg.chat) + assert len(msg.chat.get_contacts()) == 3 + + lp.sec("ac1: removing ac2") + chat.remove_contact(contacts[0]) + + lp.sec("ac2: wait for a message about removal from the chat") + msg = ac2.wait_next_incoming_message() + + lp.sec("ac1: removing ac3") + chat.remove_contact(contacts[1]) + + lp.sec("ac1: adding ac2 back") + # Group is promoted, message is sent automatically + assert chat.is_promoted() + chat.add_contact(contacts[0]) + + lp.sec("ac2: check that ac3 is removed") + msg = ac2.wait_next_incoming_message() + + assert len(msg.chat.get_contacts()) == len(chat.get_contacts()) + class TestOnlineConfigureFails: def test_invalid_password(self, acfactory): diff --git a/python/tox.ini b/python/tox.ini index 5ad7caa7f..965607770 100644 --- a/python/tox.ini +++ b/python/tox.ini @@ -14,6 +14,7 @@ passenv = DCC_RS_DEV DCC_RS_TARGET DCC_PY_LIVECONFIG + DCC_NEW_TMP_EMAIL CARGO_TARGET_DIR RUSTC_WRAPPER deps = diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 688ee5611..4433c08bd 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -1094,6 +1094,18 @@ async fn create_or_lookup_group( // add members to group/check members if recreate_member_list { if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { + // Members could have been removed while we were + // absent. We can't use existing member list and need to + // start from scratch. + context + .sql + .execute( + "DELETE FROM chats_contacts WHERE chat_id=?;", + paramsv![chat_id], + ) + .await + .ok(); + chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await; } if from_id > DC_CONTACT_ID_LAST_SPECIAL