mirror of
https://github.com/chatmail/core.git
synced 2026-04-26 01:46:34 +03:00
Allow Delta Chat core to work with chatmail servers running on underscore-prefixed domains (e.g. _alice.localchat) which use self-signed TLS certificates. This is mirroring related work on chatmail relays: https://github.com/chatmail/relay/pull/855 Underscore domains with self-signed TLS certs can be used by LXC test containers where obtaining real certificates is not practical. When the domain starts with '_', certificate verification is automatically relaxed for IMAP/SMTP connections, dcaccount QR code handling, and iroh relay endpoints. The Python test suite is adapted to also work against such underscore-domain servers, including cross-core tests with older Delta Chat versions. Note: this PR does not support HTTPS requests with underscore domains. They are not currently needed for working with LXC test containers. 14 files changed, +102/-31 lines (excluding Cargo.lock). Cargo.lock: +606/-11 lines from enabling iroh features needed for connecting to iroh relay endpoint on underscore domains. The added dependencies are unfortunate but best considered when finally upgrading to iroh 1.0 (tm).
389 lines
14 KiB
Python
389 lines
14 KiB
Python
import pytest
|
|
|
|
from deltachat_rpc_client import EventType
|
|
from deltachat_rpc_client.const import ChatType, DownloadState
|
|
from deltachat_rpc_client.rpc import JsonRpcError
|
|
|
|
|
|
def test_add_second_address(acfactory) -> None:
|
|
account = acfactory.new_configured_account()
|
|
assert len(account.list_transports()) == 1
|
|
|
|
# When the first transport is created,
|
|
# mvbox_move and only_fetch_mvbox should be disabled.
|
|
assert account.get_config("mvbox_move") == "0"
|
|
assert account.get_config("only_fetch_mvbox") == "0"
|
|
assert account.get_config("show_emails") == "2"
|
|
|
|
qr = acfactory.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"]
|
|
|
|
# Cannot delete the first address.
|
|
with pytest.raises(JsonRpcError):
|
|
account.delete_transport(first_addr)
|
|
|
|
account.delete_transport(second_addr)
|
|
assert len(account.list_transports()) == 2
|
|
|
|
# Enabling mvbox_move or only_fetch_mvbox
|
|
# is not allowed when multi-transport is enabled.
|
|
for option in ["mvbox_move", "only_fetch_mvbox"]:
|
|
with pytest.raises(JsonRpcError):
|
|
account.set_config(option, "1")
|
|
|
|
# show_emails does not matter for multi-relay, can be set to anything
|
|
account.set_config("show_emails", "0")
|
|
|
|
|
|
@pytest.mark.parametrize("key", ["mvbox_move", "only_fetch_mvbox"])
|
|
def test_no_second_transport_with_mvbox(acfactory, key) -> None:
|
|
"""Test that second transport cannot be configured if mvbox is used."""
|
|
account = acfactory.new_configured_account()
|
|
assert len(account.list_transports()) == 1
|
|
|
|
assert account.get_config("mvbox_move") == "0"
|
|
assert account.get_config("only_fetch_mvbox") == "0"
|
|
|
|
qr = acfactory.get_account_qr()
|
|
account.set_config(key, "1")
|
|
|
|
with pytest.raises(JsonRpcError):
|
|
account.add_transport_from_qr(qr)
|
|
|
|
|
|
def test_second_transport_without_classic_emails(acfactory) -> None:
|
|
"""Test that second transport can be configured if classic emails are not fetched."""
|
|
account = acfactory.new_configured_account()
|
|
assert len(account.list_transports()) == 1
|
|
|
|
assert account.get_config("show_emails") == "2"
|
|
|
|
qr = acfactory.get_account_qr()
|
|
account.set_config("show_emails", "0")
|
|
|
|
account.add_transport_from_qr(qr)
|
|
|
|
|
|
def test_change_address(acfactory) -> None:
|
|
"""Test Alice configuring a second transport and setting it as a primary one."""
|
|
alice, bob = acfactory.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 = acfactory.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
|
|
with pytest.raises(JsonRpcError):
|
|
alice.delete_transport(new_alice_addr)
|
|
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_download_on_demand(acfactory, data) -> None:
|
|
alice, bob = acfactory.get_online_accounts(2)
|
|
alice.set_config("download_limit", "1")
|
|
|
|
alice.stop_io()
|
|
qr = acfactory.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=data.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
|
|
|
|
|
|
@pytest.mark.parametrize("is_chatmail", ["0", "1"])
|
|
def test_mvbox_move_first_transport(acfactory, is_chatmail) -> None:
|
|
"""Test that mvbox_move is disabled by default even for non-chatmail accounts.
|
|
Disabling mvbox_move is required to be able to setup a second transport.
|
|
"""
|
|
account = acfactory.get_unconfigured_account()
|
|
|
|
account.set_config("fix_is_chatmail", "1")
|
|
account.set_config("is_chatmail", is_chatmail)
|
|
|
|
# The default value when the setting is unset is "1".
|
|
# This is not changed for compatibility with old databases
|
|
# imported from backups.
|
|
assert account.get_config("mvbox_move") == "1"
|
|
|
|
qr = acfactory.get_account_qr()
|
|
account.add_transport_from_qr(qr)
|
|
|
|
# Once the first transport is set up,
|
|
# mvbox_move is disabled.
|
|
assert account.get_config("mvbox_move") == "0"
|
|
assert account.get_config("is_chatmail") == is_chatmail
|
|
|
|
|
|
def test_reconfigure_transport(acfactory) -> None:
|
|
"""Test that reconfiguring the transport works
|
|
even if settings not supported for multi-transport
|
|
like mvbox_move are enabled."""
|
|
account = acfactory.get_online_account()
|
|
account.set_config("mvbox_move", "1")
|
|
|
|
[transport] = account.list_transports()
|
|
account.add_or_update_transport(transport)
|
|
|
|
# Reconfiguring the transport should not reset
|
|
# the settings as if when configuring the first transport.
|
|
assert account.get_config("mvbox_move") == "1"
|
|
|
|
|
|
def test_transport_synchronization(acfactory, 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
|
|
|
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
ac1_clone = ac1.clone()
|
|
ac1_clone.bring_online()
|
|
|
|
qr = acfactory.get_account_qr()
|
|
|
|
ac1.add_transport_from_qr(qr)
|
|
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
wait_for_io_started(ac1_clone)
|
|
assert len(ac1.list_transports()) == 2
|
|
assert len(ac1_clone.list_transports()) == 2
|
|
|
|
ac1_clone.add_transport_from_qr(qr)
|
|
ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
wait_for_io_started(ac1)
|
|
assert len(ac1.list_transports()) == 3
|
|
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"])
|
|
|
|
ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
wait_for_io_started(ac1)
|
|
[transport1, transport3] = ac1.list_transports()
|
|
|
|
log.section("ac1 changes the primary transport")
|
|
ac1.set_config("configured_addr", transport3["addr"])
|
|
|
|
# One event for updated `add_timestamp` of the new primary transport,
|
|
# one event for the `configured_addr` update.
|
|
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
[transport1, transport3] = ac1_clone.list_transports()
|
|
assert ac1_clone.get_config("configured_addr") == addr3
|
|
|
|
log.section("ac1 removes the first transport")
|
|
ac1.delete_transport(transport1["addr"])
|
|
|
|
ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
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(acfactory, log) -> None:
|
|
"""Test synchronization of new transport as primary between devices."""
|
|
ac1, bob = acfactory.get_online_accounts(2)
|
|
ac1_clone = ac1.clone()
|
|
ac1_clone.bring_online()
|
|
|
|
qr = acfactory.get_account_qr()
|
|
|
|
ac1.add_transport_from_qr(qr)
|
|
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_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
|
|
assert ac1_clone.get_config("configured_addr") == transport2["addr"]
|
|
|
|
log.section("ac1_clone receives a message via the new primary 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(acfactory) -> None:
|
|
alice, bob = acfactory.get_online_accounts(2)
|
|
|
|
bob_chat = bob.create_chat(alice)
|
|
|
|
qr = acfactory.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(acfactory) -> None:
|
|
"""Test transports limit."""
|
|
account = acfactory.get_online_account()
|
|
qr = acfactory.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)
|
|
|
|
# test that adding a transport after deleting one works again
|
|
account.add_transport_from_qr(qr)
|
|
|
|
|
|
def test_message_info_imap_urls(acfactory) -> None:
|
|
"""Test that message info contains IMAP URLs of where the message was received."""
|
|
alice, bob = acfactory.get_online_accounts(2)
|
|
|
|
qr = acfactory.get_account_qr()
|
|
for i in range(3):
|
|
alice.add_transport_from_qr(qr)
|
|
# Wait for all transports to go IDLE after adding each one.
|
|
for _ in range(i + 1):
|
|
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(acfactory, log) -> None:
|
|
"""Test that after removing the primary relay, Alice can still receive messages."""
|
|
alice, bob = acfactory.get_online_accounts(2)
|
|
qr = acfactory.get_account_qr()
|
|
|
|
alice.add_transport_from_qr(qr)
|
|
alice.bring_online()
|
|
|
|
bob_chat = bob.create_chat(alice)
|
|
alice.create_chat(bob)
|
|
|
|
log.section("Alice sets up second transport")
|
|
[transport1, transport2] = alice.list_transports()
|
|
alice.set_config("configured_addr", transport2["addr"])
|
|
|
|
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"])
|
|
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.create_chat(bob)
|