mirror of
https://github.com/chatmail/core.git
synced 2026-09-22 13:01:21 +03:00
Headers like From and Autocrypt are now added late, right before sending the message over SMTP. This way we advertise the latest list of transports and use the correct From address in the encrypted part even for messages queued while being offline. BCC-self recipients are also added late. For unencrypted messages we only want to send a copy to the sending address, but we don't know the sending address when queueing the message. Adding bcc-self recipients when dequeuing the message also makes it possible to send copies to updated list of relays.
405 lines
14 KiB
Python
405 lines
14 KiB
Python
import time
|
|
import urllib.parse
|
|
|
|
import pytest
|
|
|
|
from deltachat_rpc_client import EventType
|
|
from deltachat_rpc_client.const import ChatType, DownloadState
|
|
from deltachat_rpc_client.rpc import JsonRpcError
|
|
|
|
|
|
def alice_with_two_transports_and_bob(acf):
|
|
alice, bob = acf.get_online_accounts(2)
|
|
alice.add_transport_from_qr(acf.get_account_qr())
|
|
alice.bring_online()
|
|
return alice, alice.create_chat(bob), bob.create_chat(alice)
|
|
|
|
|
|
def messages_with_text(chat, text):
|
|
return [msg for msg in chat.get_messages() if msg.get_snapshot().text == text]
|
|
|
|
|
|
def wait_for_imap_message(imap):
|
|
while not imap.get_all_messages():
|
|
time.sleep(1)
|
|
|
|
|
|
def test_add_second_address(acf) -> None:
|
|
account = acf.new_configured_account()
|
|
assert len(account.list_transports()) == 1
|
|
|
|
qr = acf.get_account_qr()
|
|
account.add_transport_from_qr(qr)
|
|
assert len(account.list_transports()) == 2
|
|
|
|
account.add_transport_from_qr(qr)
|
|
assert len(account.list_transports()) == 3
|
|
|
|
first_addr = account.list_transports()[0]["addr"]
|
|
second_addr = account.list_transports()[1]["addr"]
|
|
third_addr = account.list_transports()[2]["addr"]
|
|
|
|
assert account.get_config("configured_addr") == first_addr
|
|
account.delete_transport(first_addr)
|
|
assert len(account.list_transports()) == 2
|
|
assert account.get_config("configured_addr") != first_addr
|
|
|
|
account.delete_transport(second_addr)
|
|
assert len(account.list_transports()) == 1
|
|
|
|
with pytest.raises(JsonRpcError):
|
|
account.delete_transport(third_addr)
|
|
|
|
|
|
def test_change_address(acf) -> None:
|
|
"""Test Alice configuring a second transport and setting it as a primary one."""
|
|
alice, bob = acf.get_online_accounts(2)
|
|
|
|
bob_addr = bob.get_config("configured_addr")
|
|
bob.create_chat(alice)
|
|
|
|
alice_chat_bob = alice.create_chat(bob)
|
|
alice_chat_bob.send_text("Hello!")
|
|
|
|
msg1 = bob.wait_for_incoming_msg().get_snapshot()
|
|
sender_addr1 = msg1.sender.get_snapshot().address
|
|
|
|
alice.stop_io()
|
|
old_alice_addr = alice.get_config("configured_addr")
|
|
alice_vcard = alice.self_contact.make_vcard()
|
|
assert old_alice_addr in alice_vcard
|
|
qr = acf.get_account_qr()
|
|
alice.add_transport_from_qr(qr)
|
|
new_alice_addr = alice.list_transports()[1]["addr"]
|
|
with pytest.raises(JsonRpcError):
|
|
# Cannot use the address that is not
|
|
# configured for any transport.
|
|
alice.set_config("configured_addr", bob_addr)
|
|
|
|
# Load old address so it is cached.
|
|
assert alice.get_config("configured_addr") == old_alice_addr
|
|
alice.set_config("configured_addr", new_alice_addr)
|
|
# Make sure that setting `configured_addr` invalidated the cache.
|
|
assert alice.get_config("configured_addr") == new_alice_addr
|
|
|
|
alice_vcard = alice.self_contact.make_vcard()
|
|
assert old_alice_addr not in alice_vcard
|
|
assert new_alice_addr in alice_vcard
|
|
alice.start_io()
|
|
|
|
alice_chat_bob.send_text("Hello again!")
|
|
|
|
msg2 = bob.wait_for_incoming_msg().get_snapshot()
|
|
sender_addr2 = msg2.sender.get_snapshot().address
|
|
|
|
assert msg1.sender == msg2.sender
|
|
assert sender_addr1 != sender_addr2
|
|
assert sender_addr1 == old_alice_addr
|
|
assert sender_addr2 == new_alice_addr
|
|
|
|
|
|
def test_remove_transport_keep_messages(acf) -> None:
|
|
"""Test that deleting current sending transport keeps queued messages."""
|
|
alice, bob = acf.get_online_accounts(2)
|
|
|
|
qr = acf.get_account_qr()
|
|
alice.add_transport_from_qr(qr)
|
|
|
|
alice.stop_io()
|
|
alice_chat_bob = alice.create_chat(bob)
|
|
alice_chat_bob.send_text("Hello!")
|
|
|
|
new_alice_addr = alice.list_transports()[1]["addr"]
|
|
alice.delete_transport(alice.list_transports()[0]["addr"])
|
|
alice.start_io()
|
|
|
|
bob_msg = bob.wait_for_incoming_msg().get_snapshot()
|
|
assert bob_msg.sender.get_snapshot().address == new_alice_addr
|
|
|
|
|
|
def test_download_on_demand(acf, rpcdata) -> None:
|
|
alice, bob = acf.get_online_accounts(2)
|
|
alice.set_config("download_limit", "1")
|
|
|
|
alice.stop_io()
|
|
qr = acf.get_account_qr()
|
|
alice.add_transport_from_qr(qr)
|
|
alice.start_io()
|
|
|
|
alice.create_chat(bob)
|
|
chat_bob_alice = bob.create_chat(alice)
|
|
chat_bob_alice.send_message(file=rpcdata.get_path("image/screenshot.jpg"))
|
|
msg = alice.wait_for_incoming_msg()
|
|
snapshot = msg.get_snapshot()
|
|
assert snapshot.download_state == DownloadState.AVAILABLE
|
|
chat_id = snapshot.chat_id
|
|
# Actually the message isn't available yet. Wait somehow for the post-message to arrive.
|
|
chat_bob_alice.send_message("Now you can download my previous message")
|
|
alice.wait_for_incoming_msg()
|
|
alice._rpc.download_full_message(alice.id, msg.id)
|
|
for dstate in [DownloadState.IN_PROGRESS, DownloadState.DONE]:
|
|
event = alice.wait_for_event(EventType.MSGS_CHANGED)
|
|
assert event.chat_id == chat_id
|
|
assert event.msg_id == msg.id
|
|
assert msg.get_snapshot().download_state == dstate
|
|
|
|
|
|
def test_reconfigure_transport(acf) -> None:
|
|
"""Test that reconfiguring the transport works."""
|
|
account = acf.get_online_account()
|
|
|
|
[transport] = account.list_transports()
|
|
account.add_or_update_transport(transport)
|
|
|
|
|
|
def test_transport_synchronization(acf, log) -> None:
|
|
"""Test synchronization of transports between devices."""
|
|
|
|
def wait_for_io_started(ac):
|
|
while True:
|
|
ev = ac.wait_for_event(EventType.INFO)
|
|
if "scheduler is running" in ev.msg:
|
|
return
|
|
|
|
def wait_transports(ac, n):
|
|
while len(ac.list_transports()) != n:
|
|
ac.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
|
|
ac1, ac2 = acf.get_online_accounts(2)
|
|
ac1_clone = ac1.clone()
|
|
ac1_clone.bring_online()
|
|
|
|
qr = acf.get_account_qr()
|
|
|
|
ac1.add_transport_from_qr(qr)
|
|
wait_transports(ac1_clone, 2)
|
|
wait_for_io_started(ac1_clone)
|
|
assert len(ac1.list_transports()) == 2
|
|
|
|
ac1_clone.add_transport_from_qr(qr)
|
|
wait_transports(ac1, 3)
|
|
wait_for_io_started(ac1)
|
|
assert len(ac1_clone.list_transports()) == 3
|
|
|
|
log.section("ac1 clone removes second transport")
|
|
[transport1, transport2, transport3] = ac1_clone.list_transports()
|
|
addr3 = transport3["addr"]
|
|
ac1_clone.delete_transport(transport2["addr"])
|
|
|
|
wait_transports(ac1, 2)
|
|
wait_for_io_started(ac1)
|
|
[transport1, transport3] = ac1.list_transports()
|
|
|
|
log.section("ac1 changes the sending transport")
|
|
ac1.set_config("configured_addr", transport3["addr"])
|
|
|
|
log.section("ac1 removes the first transport")
|
|
ac1.delete_transport(transport1["addr"])
|
|
|
|
wait_transports(ac1_clone, 1)
|
|
wait_for_io_started(ac1_clone)
|
|
[transport3] = ac1_clone.list_transports()
|
|
assert transport3["addr"] == addr3
|
|
assert ac1_clone.get_config("configured_addr") == addr3
|
|
|
|
ac2_chat = ac2.create_chat(ac1)
|
|
ac2_chat.send_text("Hello!")
|
|
|
|
assert ac1.wait_for_incoming_msg().get_snapshot().text == "Hello!"
|
|
assert ac1_clone.wait_for_incoming_msg().get_snapshot().text == "Hello!"
|
|
|
|
|
|
def test_transport_sync_new_as_primary(acf, log) -> None:
|
|
"""Test that a transport promoted on one device is usable on other devices."""
|
|
ac1, bob = acf.get_online_accounts(2)
|
|
ac1_clone = ac1.clone()
|
|
ac1_clone.bring_online()
|
|
|
|
qr = acf.get_account_qr()
|
|
|
|
ac1.add_transport_from_qr(qr)
|
|
ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
ac1_transports = ac1.list_transports()
|
|
assert len(ac1_transports) == 2
|
|
[transport1, transport2] = ac1_transports
|
|
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
assert len(ac1_clone.list_transports()) == 2
|
|
assert ac1_clone.get_config("configured_addr") == transport1["addr"]
|
|
|
|
log.section("ac1 changes the primary transport")
|
|
ac1.set_config("configured_addr", transport2["addr"])
|
|
ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
|
|
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
assert ac1_clone.get_config("configured_addr") == transport1["addr"]
|
|
|
|
log.section("ac1_clone receives a message via the new transport")
|
|
ac1_chat = ac1.create_chat(bob)
|
|
ac1_chat.send_text("Hello!")
|
|
bob_chat_id = bob.wait_for_incoming_msg_event().chat_id
|
|
bob_chat = bob.get_chat_by_id(bob_chat_id)
|
|
bob_chat.accept()
|
|
bob_chat.send_text("hello back")
|
|
assert ac1_clone.wait_for_incoming_msg().get_snapshot().text == "hello back"
|
|
|
|
|
|
def test_recognize_self_address(acf) -> None:
|
|
alice, bob = acf.get_online_accounts(2)
|
|
|
|
bob_chat = bob.create_chat(alice)
|
|
|
|
qr = acf.get_account_qr()
|
|
alice.add_transport_from_qr(qr)
|
|
|
|
new_alice_addr = alice.list_transports()[1]["addr"]
|
|
alice.set_config("configured_addr", new_alice_addr)
|
|
|
|
bob_chat.send_text("Hello!")
|
|
msg = alice.wait_for_incoming_msg().get_snapshot()
|
|
assert msg.chat == alice.create_chat(bob)
|
|
|
|
|
|
def test_transport_limit(acf) -> None:
|
|
"""Test transports limit."""
|
|
account = acf.get_online_account()
|
|
qr = acf.get_account_qr()
|
|
|
|
limit = 5
|
|
|
|
for _ in range(1, limit):
|
|
account.add_transport_from_qr(qr)
|
|
|
|
assert len(account.list_transports()) == limit
|
|
|
|
with pytest.raises(JsonRpcError):
|
|
account.add_transport_from_qr(qr)
|
|
|
|
second_addr = account.list_transports()[1]["addr"]
|
|
|
|
account.delete_transport(second_addr)
|
|
account.add_transport_from_qr(qr)
|
|
with pytest.raises(JsonRpcError):
|
|
account.add_transport_from_qr(qr)
|
|
|
|
|
|
def test_message_info_imap_urls(acf) -> None:
|
|
"""Test that message info contains IMAP URLs of where the message was received."""
|
|
alice, bob = acf.get_online_accounts(2)
|
|
|
|
qr = acf.get_account_qr()
|
|
for _ in range(3):
|
|
alice.add_transport_from_qr(qr)
|
|
# Wait for all transports to go IDLE after adding each one.
|
|
alice.bring_online()
|
|
|
|
# Enable multi-device mode so messages are not deleted immediately.
|
|
alice.set_config("bcc_self", "1")
|
|
|
|
# Bob creates chat, learning about Alice's currently selected transport.
|
|
# This is where he will send the message.
|
|
bob_chat = bob.create_chat(alice)
|
|
|
|
# Alice switches to another transport and removes the rest of the transports.
|
|
new_alice_addr = alice.list_transports()[1]["addr"]
|
|
alice.set_config("configured_addr", new_alice_addr)
|
|
removed_addrs = []
|
|
for transport in alice.list_transports():
|
|
if transport["addr"] != new_alice_addr:
|
|
alice.delete_transport(transport["addr"])
|
|
removed_addrs.append(transport["addr"])
|
|
alice.stop_io()
|
|
alice.start_io()
|
|
|
|
bob_chat.send_text("Hello!")
|
|
|
|
msg = alice.wait_for_incoming_msg()
|
|
msg_info = msg.get_info()
|
|
assert new_alice_addr in msg_info
|
|
for removed_addr in removed_addrs:
|
|
assert removed_addr not in msg_info
|
|
assert f"{new_alice_addr}/INBOX" in msg_info
|
|
|
|
|
|
def test_remove_primary_transport(acf, log) -> None:
|
|
"""Test that after removing the primary relay, Alice can still receive messages."""
|
|
alice, alice_chat, bob_chat = alice_with_two_transports_and_bob(acf)
|
|
|
|
log.section("Alice sets up second transport")
|
|
[transport1, transport2] = alice.list_transports()
|
|
|
|
bob_chat.send_text("Hello!")
|
|
msg1 = alice.wait_for_incoming_msg().get_snapshot()
|
|
assert msg1.text == "Hello!"
|
|
|
|
log.section("Alice removes the primary relay")
|
|
alice.delete_transport(transport1["addr"])
|
|
assert alice.get_config("configured_addr") == transport2["addr"]
|
|
alice.stop_io()
|
|
alice.start_io()
|
|
|
|
bob_chat.send_text("Hello again!")
|
|
msg2 = alice.wait_for_incoming_msg().get_snapshot()
|
|
assert msg2.text == "Hello again!"
|
|
assert msg2.chat.get_basic_snapshot().chat_type == ChatType.SINGLE
|
|
assert msg2.chat == alice_chat
|
|
|
|
|
|
def test_qr_works_after_removing_primary_transport(acf, log) -> None:
|
|
log.section("Alice setups an account and adds two additional relays")
|
|
alice = acf.new_configured_account()
|
|
relay_qr = acf.get_account_qr()
|
|
alice.add_transport_from_qr(relay_qr)
|
|
alice.add_transport_from_qr(relay_qr)
|
|
|
|
first_addr = alice.list_transports()[0]["addr"]
|
|
second_addr = alice.list_transports()[1]["addr"]
|
|
third_addr = alice.list_transports()[2]["addr"]
|
|
|
|
log.section("Alice creates a QR code")
|
|
chat_qr = alice.get_qr_code()
|
|
chat_qr_unquoted = urllib.parse.unquote(chat_qr)
|
|
assert f"&a={first_addr}" in chat_qr_unquoted
|
|
assert f"&r={third_addr},{second_addr}" in chat_qr_unquoted
|
|
|
|
log.section("Alice removes first and second transport")
|
|
alice.set_config("configured_addr", third_addr)
|
|
alice.delete_transport(first_addr)
|
|
alice.delete_transport(second_addr)
|
|
|
|
log.section("Bob scans the QR code, which still works")
|
|
alice.bring_online()
|
|
bob = acf.get_online_account()
|
|
bob.secure_join(chat_qr)
|
|
alice.wait_for_securejoin_inviter_success()
|
|
bob.wait_for_securejoin_joiner_success()
|
|
|
|
|
|
def test_background_fetch_from_second_transport(acf, direct_imap, dc):
|
|
alice, alice_chat, bob_chat = alice_with_two_transports_and_bob(acf)
|
|
[transport1, transport2] = alice.list_transports()
|
|
assert alice.get_config("configured_addr") == transport1["addr"]
|
|
|
|
alice.stop_io()
|
|
bob_chat.send_text("hello")
|
|
imap1 = direct_imap(alice, transport1["addr"], transport1["password"])
|
|
wait_for_imap_message(direct_imap(alice, transport2["addr"], transport2["password"]))
|
|
wait_for_imap_message(imap1)
|
|
|
|
# Leave the message on the second transport only.
|
|
imap1.delete("1:*")
|
|
|
|
dc.background_fetch(300)
|
|
assert len(messages_with_text(alice_chat, "hello")) == 1
|
|
|
|
|
|
def test_background_fetch_no_duplicates(acf, direct_imap, dc):
|
|
alice, alice_chat, bob_chat = alice_with_two_transports_and_bob(acf)
|
|
|
|
alice.stop_io()
|
|
bob_chat.send_text("hello")
|
|
for transport in alice.list_transports():
|
|
wait_for_imap_message(direct_imap(alice, transport["addr"], transport["password"]))
|
|
|
|
dc.background_fetch(300)
|
|
assert len(messages_with_text(alice_chat, "hello")) == 1
|