deltachat-rpc-client 2.26.0__tar.gz → 2.28.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deltachat_rpc_client-2.26.0/src/deltachat_rpc_client.egg-info → deltachat_rpc_client-2.28.0}/PKG-INFO +1 -1
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/pyproject.toml +1 -1
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/account.py +14 -1
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/deltachat.py +10 -1
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/pytestplugin.py +16 -4
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0/src/deltachat_rpc_client.egg-info}/PKG-INFO +1 -1
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client.egg-info/SOURCES.txt +2 -0
- deltachat_rpc_client-2.28.0/tests/test_folders.py +538 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_iroh_webxdc.py +3 -3
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_multidevice.py +35 -0
- deltachat_rpc_client-2.28.0/tests/test_multitransport.py +158 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_securejoin.py +33 -36
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_something.py +91 -100
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/LICENSE +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/README.md +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/setup.cfg +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/__init__.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/_utils.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/chat.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/client.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/const.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/contact.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/events.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/message.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/py.typed +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/rpc.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client.egg-info/dependency_links.txt +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client.egg-info/entry_points.txt +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client.egg-info/top_level.txt +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_account_events.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_calls.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_chatlist_events.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_key_transfer.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_vcard.py +0 -0
- {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/tests/test_webxdc.py +0 -0
{deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/account.py
RENAMED
|
@@ -130,6 +130,10 @@ class Account:
|
|
|
130
130
|
"""Add a new transport using a QR code."""
|
|
131
131
|
yield self._rpc.add_transport_from_qr.future(self.id, qr)
|
|
132
132
|
|
|
133
|
+
def delete_transport(self, addr: str):
|
|
134
|
+
"""Delete a transport."""
|
|
135
|
+
self._rpc.delete_transport(self.id, addr)
|
|
136
|
+
|
|
133
137
|
@futuremethod
|
|
134
138
|
def list_transports(self):
|
|
135
139
|
"""Return the list of all email accounts that are used as a transport in the current profile."""
|
|
@@ -417,12 +421,21 @@ class Account:
|
|
|
417
421
|
"""Wait for messages noticed event and return it."""
|
|
418
422
|
return self.wait_for_event(EventType.MSGS_NOTICED)
|
|
419
423
|
|
|
424
|
+
def wait_for_msg(self, event_type) -> Message:
|
|
425
|
+
"""Wait for an event about the message.
|
|
426
|
+
|
|
427
|
+
Consumes all events before the matching event.
|
|
428
|
+
Returns a message corresponding to the msg_id field of the event.
|
|
429
|
+
"""
|
|
430
|
+
event = self.wait_for_event(event_type)
|
|
431
|
+
return self.get_message_by_id(event.msg_id)
|
|
432
|
+
|
|
420
433
|
def wait_for_incoming_msg(self):
|
|
421
434
|
"""Wait for incoming message and return it.
|
|
422
435
|
|
|
423
436
|
Consumes all events before the next incoming message event.
|
|
424
437
|
"""
|
|
425
|
-
return self.
|
|
438
|
+
return self.wait_for_msg(EventType.INCOMING_MSG)
|
|
426
439
|
|
|
427
440
|
def wait_for_securejoin_inviter_success(self):
|
|
428
441
|
"""Wait until SecureJoin process finishes successfully on the inviter side."""
|
{deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/deltachat.py
RENAMED
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
from ._utils import AttrDict
|
|
7
|
+
from ._utils import AttrDict, futuremethod
|
|
8
8
|
from .account import Account
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
@@ -39,6 +39,15 @@ class DeltaChat:
|
|
|
39
39
|
"""Stop the I/O of all accounts."""
|
|
40
40
|
self.rpc.stop_io_for_all_accounts()
|
|
41
41
|
|
|
42
|
+
@futuremethod
|
|
43
|
+
def background_fetch(self, timeout_in_seconds: int) -> None:
|
|
44
|
+
"""Run background fetch for all accounts."""
|
|
45
|
+
yield self.rpc.background_fetch.future(timeout_in_seconds)
|
|
46
|
+
|
|
47
|
+
def stop_background_fetch(self) -> None:
|
|
48
|
+
"""Stop ongoing background fetch."""
|
|
49
|
+
self.rpc.stop_background_fetch()
|
|
50
|
+
|
|
42
51
|
def maybe_network(self) -> None:
|
|
43
52
|
"""Indicate that the network conditions might have changed."""
|
|
44
53
|
self.rpc.maybe_network()
|
{deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.28.0}/src/deltachat_rpc_client/pytestplugin.py
RENAMED
|
@@ -40,12 +40,17 @@ class ACFactory:
|
|
|
40
40
|
username = "ci-" + "".join(random.choice("2345789acdefghjkmnpqrstuvwxyz") for i in range(6))
|
|
41
41
|
return f"{username}@{domain}", f"{username}${username}"
|
|
42
42
|
|
|
43
|
+
def get_account_qr(self):
|
|
44
|
+
"""Return "dcaccount:" QR code for testing chatmail relay."""
|
|
45
|
+
domain = os.getenv("CHATMAIL_DOMAIN")
|
|
46
|
+
return f"dcaccount:{domain}"
|
|
47
|
+
|
|
43
48
|
@futuremethod
|
|
44
49
|
def new_configured_account(self):
|
|
45
50
|
"""Create a new configured account."""
|
|
46
51
|
account = self.get_unconfigured_account()
|
|
47
|
-
|
|
48
|
-
yield account.add_transport_from_qr.future(
|
|
52
|
+
qr = self.get_account_qr()
|
|
53
|
+
yield account.add_transport_from_qr.future(qr)
|
|
49
54
|
|
|
50
55
|
assert account.is_configured()
|
|
51
56
|
return account
|
|
@@ -77,6 +82,7 @@ class ACFactory:
|
|
|
77
82
|
ac_clone = self.get_unconfigured_account()
|
|
78
83
|
for transport in transports:
|
|
79
84
|
ac_clone.add_or_update_transport(transport)
|
|
85
|
+
ac_clone.bring_online()
|
|
80
86
|
return ac_clone
|
|
81
87
|
|
|
82
88
|
def get_accepted_chat(self, ac1: Account, ac2: Account) -> Chat:
|
|
@@ -135,9 +141,15 @@ def rpc(tmp_path) -> AsyncGenerator:
|
|
|
135
141
|
|
|
136
142
|
|
|
137
143
|
@pytest.fixture
|
|
138
|
-
def
|
|
144
|
+
def dc(rpc) -> DeltaChat:
|
|
145
|
+
"""Return account manager."""
|
|
146
|
+
return DeltaChat(rpc)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@pytest.fixture
|
|
150
|
+
def acfactory(dc) -> AsyncGenerator:
|
|
139
151
|
"""Return account factory fixture."""
|
|
140
|
-
return ACFactory(
|
|
152
|
+
return ACFactory(dc)
|
|
141
153
|
|
|
142
154
|
|
|
143
155
|
@pytest.fixture
|
|
@@ -22,9 +22,11 @@ src/deltachat_rpc_client.egg-info/top_level.txt
|
|
|
22
22
|
tests/test_account_events.py
|
|
23
23
|
tests/test_calls.py
|
|
24
24
|
tests/test_chatlist_events.py
|
|
25
|
+
tests/test_folders.py
|
|
25
26
|
tests/test_iroh_webxdc.py
|
|
26
27
|
tests/test_key_transfer.py
|
|
27
28
|
tests/test_multidevice.py
|
|
29
|
+
tests/test_multitransport.py
|
|
28
30
|
tests/test_securejoin.py
|
|
29
31
|
tests/test_something.py
|
|
30
32
|
tests/test_vcard.py
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from imap_tools import AND, U
|
|
7
|
+
|
|
8
|
+
from deltachat_rpc_client import Contact, EventType, Message
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_move_works(acfactory):
|
|
12
|
+
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
13
|
+
ac2.set_config("mvbox_move", "1")
|
|
14
|
+
ac2.bring_online()
|
|
15
|
+
|
|
16
|
+
chat = ac1.create_chat(ac2)
|
|
17
|
+
chat.send_text("message1")
|
|
18
|
+
|
|
19
|
+
# Message is moved to the movebox
|
|
20
|
+
ac2.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
|
21
|
+
|
|
22
|
+
# Message is downloaded
|
|
23
|
+
msg = ac2.wait_for_incoming_msg().get_snapshot()
|
|
24
|
+
assert msg.text == "message1"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_move_avoids_loop(acfactory, direct_imap):
|
|
28
|
+
"""Test that the message is only moved from INBOX to DeltaChat.
|
|
29
|
+
|
|
30
|
+
This is to avoid busy loop if moved message reappears in the Inbox
|
|
31
|
+
or some scanned folder later.
|
|
32
|
+
For example, this happens on servers that alias `INBOX.DeltaChat` to `DeltaChat` folder,
|
|
33
|
+
so the message moved to `DeltaChat` appears as a new message in the `INBOX.DeltaChat` folder.
|
|
34
|
+
We do not want to move this message from `INBOX.DeltaChat` to `DeltaChat` again.
|
|
35
|
+
"""
|
|
36
|
+
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
37
|
+
ac2.set_config("mvbox_move", "1")
|
|
38
|
+
ac2.set_config("delete_server_after", "0")
|
|
39
|
+
ac2.bring_online()
|
|
40
|
+
|
|
41
|
+
# Create INBOX.DeltaChat folder and make sure
|
|
42
|
+
# it is detected by full folder scan.
|
|
43
|
+
ac2_direct_imap = direct_imap(ac2)
|
|
44
|
+
ac2_direct_imap.create_folder("INBOX.DeltaChat")
|
|
45
|
+
ac2.stop_io()
|
|
46
|
+
ac2.start_io()
|
|
47
|
+
|
|
48
|
+
while True:
|
|
49
|
+
event = ac2.wait_for_event()
|
|
50
|
+
# Wait until the end of folder scan.
|
|
51
|
+
if event.kind == EventType.INFO and "Found folders:" in event.msg:
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
ac1_chat = acfactory.get_accepted_chat(ac1, ac2)
|
|
55
|
+
ac1_chat.send_text("Message 1")
|
|
56
|
+
|
|
57
|
+
# Message is moved to the DeltaChat folder and downloaded.
|
|
58
|
+
ac2_msg1 = ac2.wait_for_incoming_msg().get_snapshot()
|
|
59
|
+
assert ac2_msg1.text == "Message 1"
|
|
60
|
+
|
|
61
|
+
# Move the message to the INBOX.DeltaChat again.
|
|
62
|
+
# We assume that test server uses "." as the delimiter.
|
|
63
|
+
ac2_direct_imap.select_folder("DeltaChat")
|
|
64
|
+
ac2_direct_imap.conn.move(["*"], "INBOX.DeltaChat")
|
|
65
|
+
|
|
66
|
+
ac1_chat.send_text("Message 2")
|
|
67
|
+
ac2_msg2 = ac2.wait_for_incoming_msg().get_snapshot()
|
|
68
|
+
assert ac2_msg2.text == "Message 2"
|
|
69
|
+
|
|
70
|
+
# Stop and start I/O to trigger folder scan.
|
|
71
|
+
ac2.stop_io()
|
|
72
|
+
ac2.start_io()
|
|
73
|
+
while True:
|
|
74
|
+
event = ac2.wait_for_event()
|
|
75
|
+
# Wait until the end of folder scan.
|
|
76
|
+
if event.kind == EventType.INFO and "Found folders:" in event.msg:
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
# Check that Message 1 is still in the INBOX.DeltaChat folder
|
|
80
|
+
# and Message 2 is in the DeltaChat folder.
|
|
81
|
+
ac2_direct_imap.select_folder("INBOX")
|
|
82
|
+
assert len(ac2_direct_imap.get_all_messages()) == 0
|
|
83
|
+
ac2_direct_imap.select_folder("DeltaChat")
|
|
84
|
+
assert len(ac2_direct_imap.get_all_messages()) == 1
|
|
85
|
+
ac2_direct_imap.select_folder("INBOX.DeltaChat")
|
|
86
|
+
assert len(ac2_direct_imap.get_all_messages()) == 1
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_reactions_for_a_reordering_move(acfactory, direct_imap):
|
|
90
|
+
"""When a batch of messages is moved from Inbox to DeltaChat folder with a single MOVE command,
|
|
91
|
+
their UIDs may be reordered (e.g. Gmail is known for that) which led to that messages were
|
|
92
|
+
processed by receive_imf in the wrong order, and, particularly, reactions were processed before
|
|
93
|
+
messages they refer to and thus dropped.
|
|
94
|
+
"""
|
|
95
|
+
(ac1,) = acfactory.get_online_accounts(1)
|
|
96
|
+
|
|
97
|
+
addr, password = acfactory.get_credentials()
|
|
98
|
+
ac2 = acfactory.get_unconfigured_account()
|
|
99
|
+
ac2.add_or_update_transport({"addr": addr, "password": password})
|
|
100
|
+
ac2.set_config("mvbox_move", "1")
|
|
101
|
+
assert ac2.is_configured()
|
|
102
|
+
|
|
103
|
+
ac2.bring_online()
|
|
104
|
+
chat1 = acfactory.get_accepted_chat(ac1, ac2)
|
|
105
|
+
ac2.stop_io()
|
|
106
|
+
|
|
107
|
+
logging.info("sending message + reaction from ac1 to ac2")
|
|
108
|
+
msg1 = chat1.send_text("hi")
|
|
109
|
+
msg1.wait_until_delivered()
|
|
110
|
+
# It's is sad, but messages must differ in their INTERNALDATEs to be processed in the correct
|
|
111
|
+
# order by DC, and most (if not all) mail servers provide only seconds precision.
|
|
112
|
+
time.sleep(1.1)
|
|
113
|
+
react_str = "\N{THUMBS UP SIGN}"
|
|
114
|
+
msg1.send_reaction(react_str).wait_until_delivered()
|
|
115
|
+
|
|
116
|
+
logging.info("moving messages to ac2's DeltaChat folder in the reverse order")
|
|
117
|
+
ac2_direct_imap = direct_imap(ac2)
|
|
118
|
+
ac2_direct_imap.connect()
|
|
119
|
+
for uid in sorted([m.uid for m in ac2_direct_imap.get_all_messages()], reverse=True):
|
|
120
|
+
ac2_direct_imap.conn.move(uid, "DeltaChat")
|
|
121
|
+
|
|
122
|
+
logging.info("receiving messages by ac2")
|
|
123
|
+
ac2.start_io()
|
|
124
|
+
msg2 = Message(ac2, ac2.wait_for_reactions_changed().msg_id)
|
|
125
|
+
assert msg2.get_snapshot().text == msg1.get_snapshot().text
|
|
126
|
+
reactions = msg2.get_reactions()
|
|
127
|
+
contacts = [Contact(ac2, int(i)) for i in reactions.reactions_by_contact]
|
|
128
|
+
assert len(contacts) == 1
|
|
129
|
+
assert contacts[0].get_snapshot().address == ac1.get_config("addr")
|
|
130
|
+
assert list(reactions.reactions_by_contact.values())[0] == [react_str]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_delete_deltachat_folder(acfactory, direct_imap):
|
|
134
|
+
"""Test that DeltaChat folder is recreated if user deletes it manually."""
|
|
135
|
+
ac1 = acfactory.new_configured_account()
|
|
136
|
+
ac1.set_config("mvbox_move", "1")
|
|
137
|
+
ac1.bring_online()
|
|
138
|
+
|
|
139
|
+
ac1_direct_imap = direct_imap(ac1)
|
|
140
|
+
ac1_direct_imap.conn.folder.delete("DeltaChat")
|
|
141
|
+
assert "DeltaChat" not in ac1_direct_imap.list_folders()
|
|
142
|
+
|
|
143
|
+
# Wait until new folder is created and UIDVALIDITY is updated.
|
|
144
|
+
while True:
|
|
145
|
+
event = ac1.wait_for_event()
|
|
146
|
+
if event.kind == EventType.INFO and "transport 1: UID validity for folder DeltaChat changed from " in event.msg:
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
ac2 = acfactory.get_online_account()
|
|
150
|
+
ac2.create_chat(ac1).send_text("hello")
|
|
151
|
+
msg = ac1.wait_for_incoming_msg().get_snapshot()
|
|
152
|
+
assert msg.text == "hello"
|
|
153
|
+
|
|
154
|
+
assert "DeltaChat" in ac1_direct_imap.list_folders()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_dont_show_emails(acfactory, direct_imap, log):
|
|
158
|
+
"""Most mailboxes have a "Drafts" folder where constantly new emails appear but we don't actually want to show them.
|
|
159
|
+
So: If it's outgoing AND there is no Received header, then ignore the email.
|
|
160
|
+
|
|
161
|
+
If the draft email is sent out and received later (i.e. it's in "Inbox"), it must be shown.
|
|
162
|
+
|
|
163
|
+
Also, test that unknown emails in the Spam folder are not shown."""
|
|
164
|
+
ac1 = acfactory.new_configured_account()
|
|
165
|
+
ac1.stop_io()
|
|
166
|
+
ac1.set_config("show_emails", "2")
|
|
167
|
+
|
|
168
|
+
ac1.create_contact("alice@example.org").create_chat()
|
|
169
|
+
|
|
170
|
+
ac1_direct_imap = direct_imap(ac1)
|
|
171
|
+
ac1_direct_imap.create_folder("Drafts")
|
|
172
|
+
ac1_direct_imap.create_folder("Spam")
|
|
173
|
+
ac1_direct_imap.create_folder("Junk")
|
|
174
|
+
|
|
175
|
+
# Learn UID validity for all folders.
|
|
176
|
+
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
|
177
|
+
ac1.start_io()
|
|
178
|
+
ac1.wait_for_event(EventType.IMAP_INBOX_IDLE)
|
|
179
|
+
ac1.stop_io()
|
|
180
|
+
|
|
181
|
+
ac1_direct_imap.append(
|
|
182
|
+
"Drafts",
|
|
183
|
+
"""
|
|
184
|
+
From: ac1 <{}>
|
|
185
|
+
Subject: subj
|
|
186
|
+
To: alice@example.org
|
|
187
|
+
Message-ID: <aepiors@example.org>
|
|
188
|
+
Content-Type: text/plain; charset=utf-8
|
|
189
|
+
|
|
190
|
+
message in Drafts received later
|
|
191
|
+
""".format(
|
|
192
|
+
ac1.get_config("configured_addr"),
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
ac1_direct_imap.append(
|
|
196
|
+
"Spam",
|
|
197
|
+
"""
|
|
198
|
+
From: unknown.address@junk.org
|
|
199
|
+
Subject: subj
|
|
200
|
+
To: {}
|
|
201
|
+
Message-ID: <spam.message@junk.org>
|
|
202
|
+
Content-Type: text/plain; charset=utf-8
|
|
203
|
+
|
|
204
|
+
Unknown message in Spam
|
|
205
|
+
""".format(
|
|
206
|
+
ac1.get_config("configured_addr"),
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
ac1_direct_imap.append(
|
|
210
|
+
"Spam",
|
|
211
|
+
"""
|
|
212
|
+
From: unknown.address@junk.org, unkwnown.add@junk.org
|
|
213
|
+
Subject: subj
|
|
214
|
+
To: {}
|
|
215
|
+
Message-ID: <spam.message2@junk.org>
|
|
216
|
+
Content-Type: text/plain; charset=utf-8
|
|
217
|
+
|
|
218
|
+
Unknown & malformed message in Spam
|
|
219
|
+
""".format(
|
|
220
|
+
ac1.get_config("configured_addr"),
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
ac1_direct_imap.append(
|
|
224
|
+
"Spam",
|
|
225
|
+
"""
|
|
226
|
+
From: delta<address: inbox@nhroy.com>
|
|
227
|
+
Subject: subj
|
|
228
|
+
To: {}
|
|
229
|
+
Message-ID: <spam.message99@junk.org>
|
|
230
|
+
Content-Type: text/plain; charset=utf-8
|
|
231
|
+
|
|
232
|
+
Unknown & malformed message in Spam
|
|
233
|
+
""".format(
|
|
234
|
+
ac1.get_config("configured_addr"),
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
ac1_direct_imap.append(
|
|
238
|
+
"Spam",
|
|
239
|
+
"""
|
|
240
|
+
From: alice@example.org
|
|
241
|
+
Subject: subj
|
|
242
|
+
To: {}
|
|
243
|
+
Message-ID: <spam.message3@junk.org>
|
|
244
|
+
Content-Type: text/plain; charset=utf-8
|
|
245
|
+
|
|
246
|
+
Actually interesting message in Spam
|
|
247
|
+
""".format(
|
|
248
|
+
ac1.get_config("configured_addr"),
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
ac1_direct_imap.append(
|
|
252
|
+
"Junk",
|
|
253
|
+
"""
|
|
254
|
+
From: unknown.address@junk.org
|
|
255
|
+
Subject: subj
|
|
256
|
+
To: {}
|
|
257
|
+
Message-ID: <spam.message@junk.org>
|
|
258
|
+
Content-Type: text/plain; charset=utf-8
|
|
259
|
+
|
|
260
|
+
Unknown message in Junk
|
|
261
|
+
""".format(
|
|
262
|
+
ac1.get_config("configured_addr"),
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
|
267
|
+
log.section("All prepared, now let DC find the message")
|
|
268
|
+
ac1.start_io()
|
|
269
|
+
|
|
270
|
+
# Wait until each folder was scanned, this is necessary for this test to test what it should test:
|
|
271
|
+
ac1.wait_for_event(EventType.IMAP_INBOX_IDLE)
|
|
272
|
+
|
|
273
|
+
fresh_msgs = list(ac1.get_fresh_messages())
|
|
274
|
+
msg = fresh_msgs[0].get_snapshot()
|
|
275
|
+
chat_msgs = msg.chat.get_messages()
|
|
276
|
+
assert len(chat_msgs) == 1
|
|
277
|
+
assert msg.text == "subj – Actually interesting message in Spam"
|
|
278
|
+
|
|
279
|
+
assert not any("unknown.address" in c.get_full_snapshot().name for c in ac1.get_chatlist())
|
|
280
|
+
ac1_direct_imap.select_folder("Spam")
|
|
281
|
+
assert ac1_direct_imap.get_uid_by_message_id("spam.message@junk.org")
|
|
282
|
+
|
|
283
|
+
ac1.stop_io()
|
|
284
|
+
log.section("'Send out' the draft by moving it to Inbox, and wait for DC to display it this time")
|
|
285
|
+
ac1_direct_imap.select_folder("Drafts")
|
|
286
|
+
uid = ac1_direct_imap.get_uid_by_message_id("aepiors@example.org")
|
|
287
|
+
ac1_direct_imap.conn.move(uid, "Inbox")
|
|
288
|
+
|
|
289
|
+
ac1.start_io()
|
|
290
|
+
event = ac1.wait_for_event(EventType.MSGS_CHANGED)
|
|
291
|
+
msg2 = Message(ac1, event.msg_id).get_snapshot()
|
|
292
|
+
|
|
293
|
+
assert msg2.text == "subj – message in Drafts received later"
|
|
294
|
+
assert len(msg.chat.get_messages()) == 2
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_move_works_on_self_sent(acfactory):
|
|
298
|
+
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
299
|
+
|
|
300
|
+
# Enable movebox and wait until it is created.
|
|
301
|
+
ac1.set_config("mvbox_move", "1")
|
|
302
|
+
ac1.set_config("bcc_self", "1")
|
|
303
|
+
ac1.bring_online()
|
|
304
|
+
|
|
305
|
+
chat = ac1.create_chat(ac2)
|
|
306
|
+
chat.send_text("message1")
|
|
307
|
+
ac1.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
|
308
|
+
chat.send_text("message2")
|
|
309
|
+
ac1.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
|
310
|
+
chat.send_text("message3")
|
|
311
|
+
ac1.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def test_moved_markseen(acfactory, direct_imap):
|
|
315
|
+
"""Test that message already moved to DeltaChat folder is marked as seen."""
|
|
316
|
+
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
317
|
+
ac2.set_config("mvbox_move", "1")
|
|
318
|
+
ac2.set_config("delete_server_after", "0")
|
|
319
|
+
ac2.set_config("sync_msgs", "0") # Do not send a sync message when accepting a contact request.
|
|
320
|
+
ac2.bring_online()
|
|
321
|
+
|
|
322
|
+
ac2.stop_io()
|
|
323
|
+
ac2_direct_imap = direct_imap(ac2)
|
|
324
|
+
with ac2_direct_imap.idle() as idle2:
|
|
325
|
+
ac1.create_chat(ac2).send_text("Hello!")
|
|
326
|
+
idle2.wait_for_new_message()
|
|
327
|
+
|
|
328
|
+
# Emulate moving of the message to DeltaChat folder by Sieve rule.
|
|
329
|
+
ac2_direct_imap.conn.move(["*"], "DeltaChat")
|
|
330
|
+
ac2_direct_imap.select_folder("DeltaChat")
|
|
331
|
+
assert len(list(ac2_direct_imap.conn.fetch("*", mark_seen=False))) == 1
|
|
332
|
+
|
|
333
|
+
with ac2_direct_imap.idle() as idle2:
|
|
334
|
+
ac2.start_io()
|
|
335
|
+
|
|
336
|
+
ev = ac2.wait_for_event(EventType.MSGS_CHANGED)
|
|
337
|
+
msg = ac2.get_message_by_id(ev.msg_id)
|
|
338
|
+
assert msg.get_snapshot().text == "Messages are end-to-end encrypted."
|
|
339
|
+
|
|
340
|
+
ev = ac2.wait_for_event(EventType.INCOMING_MSG)
|
|
341
|
+
msg = ac2.get_message_by_id(ev.msg_id)
|
|
342
|
+
chat = ac2.get_chat_by_id(ev.chat_id)
|
|
343
|
+
|
|
344
|
+
# Accept the contact request.
|
|
345
|
+
chat.accept()
|
|
346
|
+
msg.mark_seen()
|
|
347
|
+
idle2.wait_for_seen()
|
|
348
|
+
|
|
349
|
+
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True, uid=U(1, "*")), mark_seen=False))) == 1
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@pytest.mark.parametrize("mvbox_move", [True, False])
|
|
353
|
+
def test_markseen_message_and_mdn(acfactory, direct_imap, mvbox_move):
|
|
354
|
+
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
355
|
+
|
|
356
|
+
for ac in ac1, ac2:
|
|
357
|
+
ac.set_config("delete_server_after", "0")
|
|
358
|
+
if mvbox_move:
|
|
359
|
+
ac.set_config("mvbox_move", "1")
|
|
360
|
+
ac.bring_online()
|
|
361
|
+
|
|
362
|
+
# Do not send BCC to self, we only want to test MDN on ac1.
|
|
363
|
+
ac1.set_config("bcc_self", "0")
|
|
364
|
+
|
|
365
|
+
acfactory.get_accepted_chat(ac1, ac2).send_text("hi")
|
|
366
|
+
msg = ac2.wait_for_incoming_msg()
|
|
367
|
+
msg.mark_seen()
|
|
368
|
+
|
|
369
|
+
if mvbox_move:
|
|
370
|
+
rex = re.compile("Marked messages [0-9]+ in folder DeltaChat as seen.")
|
|
371
|
+
else:
|
|
372
|
+
rex = re.compile("Marked messages [0-9]+ in folder INBOX as seen.")
|
|
373
|
+
|
|
374
|
+
for ac in ac1, ac2:
|
|
375
|
+
while True:
|
|
376
|
+
event = ac.wait_for_event()
|
|
377
|
+
if event.kind == EventType.INFO and rex.search(event.msg):
|
|
378
|
+
break
|
|
379
|
+
|
|
380
|
+
folder = "mvbox" if mvbox_move else "inbox"
|
|
381
|
+
ac1_direct_imap = direct_imap(ac1)
|
|
382
|
+
ac2_direct_imap = direct_imap(ac2)
|
|
383
|
+
|
|
384
|
+
ac1_direct_imap.select_config_folder(folder)
|
|
385
|
+
ac2_direct_imap.select_config_folder(folder)
|
|
386
|
+
|
|
387
|
+
# Check that the mdn is marked as seen
|
|
388
|
+
assert len(list(ac1_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1
|
|
389
|
+
# Check original message is marked as seen
|
|
390
|
+
assert len(list(ac2_direct_imap.conn.fetch(AND(seen=True), mark_seen=False))) == 1
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def test_mvbox_and_trash(acfactory, direct_imap, log):
|
|
394
|
+
log.section("ac1: start with mvbox")
|
|
395
|
+
ac1 = acfactory.get_online_account()
|
|
396
|
+
ac1.set_config("mvbox_move", "1")
|
|
397
|
+
ac1.bring_online()
|
|
398
|
+
|
|
399
|
+
log.section("ac2: start without a mvbox")
|
|
400
|
+
ac2 = acfactory.get_online_account()
|
|
401
|
+
|
|
402
|
+
log.section("ac1: create trash")
|
|
403
|
+
ac1_direct_imap = direct_imap(ac1)
|
|
404
|
+
ac1_direct_imap.create_folder("Trash")
|
|
405
|
+
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
|
406
|
+
ac1.stop_io()
|
|
407
|
+
ac1.start_io()
|
|
408
|
+
|
|
409
|
+
log.section("ac1: send message and wait for ac2 to receive it")
|
|
410
|
+
acfactory.get_accepted_chat(ac1, ac2).send_text("message1")
|
|
411
|
+
assert ac2.wait_for_incoming_msg().get_snapshot().text == "message1"
|
|
412
|
+
|
|
413
|
+
assert ac1.get_config("configured_mvbox_folder") == "DeltaChat"
|
|
414
|
+
while ac1.get_config("configured_trash_folder") != "Trash":
|
|
415
|
+
ac1.wait_for_event(EventType.CONNECTIVITY_CHANGED)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@pytest.mark.parametrize(
|
|
419
|
+
("folder", "move", "expected_destination"),
|
|
420
|
+
[
|
|
421
|
+
(
|
|
422
|
+
"xyz",
|
|
423
|
+
False,
|
|
424
|
+
"xyz",
|
|
425
|
+
), # Test that emails aren't found in a random folder
|
|
426
|
+
(
|
|
427
|
+
"xyz",
|
|
428
|
+
True,
|
|
429
|
+
"xyz",
|
|
430
|
+
), # ...emails are found in a random folder and downloaded without moving
|
|
431
|
+
(
|
|
432
|
+
"Spam",
|
|
433
|
+
False,
|
|
434
|
+
"INBOX",
|
|
435
|
+
), # ...emails are moved from the spam folder to the Inbox
|
|
436
|
+
],
|
|
437
|
+
)
|
|
438
|
+
# Testrun.org does not support the CREATE-SPECIAL-USE capability, which means that we can't create a folder with
|
|
439
|
+
# the "\Junk" flag (see https://tools.ietf.org/html/rfc6154). So, we can't test spam folder detection by flag.
|
|
440
|
+
def test_scan_folders(acfactory, log, direct_imap, folder, move, expected_destination):
|
|
441
|
+
"""Delta Chat periodically scans all folders for new messages to make sure we don't miss any."""
|
|
442
|
+
variant = folder + "-" + str(move) + "-" + expected_destination
|
|
443
|
+
log.section("Testing variant " + variant)
|
|
444
|
+
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
445
|
+
ac1.set_config("delete_server_after", "0")
|
|
446
|
+
if move:
|
|
447
|
+
ac1.set_config("mvbox_move", "1")
|
|
448
|
+
ac1.bring_online()
|
|
449
|
+
|
|
450
|
+
ac1.stop_io()
|
|
451
|
+
ac1_direct_imap = direct_imap(ac1)
|
|
452
|
+
ac1_direct_imap.create_folder(folder)
|
|
453
|
+
|
|
454
|
+
# Wait until each folder was selected once and we are IDLEing:
|
|
455
|
+
ac1.start_io()
|
|
456
|
+
ac1.bring_online()
|
|
457
|
+
|
|
458
|
+
ac1.stop_io()
|
|
459
|
+
assert folder in ac1_direct_imap.list_folders()
|
|
460
|
+
|
|
461
|
+
log.section("Send a message from ac2 to ac1 and manually move it to `folder`")
|
|
462
|
+
ac1_direct_imap.select_config_folder("inbox")
|
|
463
|
+
with ac1_direct_imap.idle() as idle1:
|
|
464
|
+
acfactory.get_accepted_chat(ac2, ac1).send_text("hello")
|
|
465
|
+
idle1.wait_for_new_message()
|
|
466
|
+
ac1_direct_imap.conn.move(["*"], folder) # "*" means "biggest UID in mailbox"
|
|
467
|
+
|
|
468
|
+
log.section("start_io() and see if DeltaChat finds the message (" + variant + ")")
|
|
469
|
+
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
|
470
|
+
ac1.start_io()
|
|
471
|
+
chat = ac1.create_chat(ac2)
|
|
472
|
+
n_msgs = 1 # "Messages are end-to-end encrypted."
|
|
473
|
+
if folder == "Spam":
|
|
474
|
+
msg = ac1.wait_for_incoming_msg().get_snapshot()
|
|
475
|
+
assert msg.text == "hello"
|
|
476
|
+
n_msgs += 1
|
|
477
|
+
else:
|
|
478
|
+
ac1.wait_for_event(EventType.IMAP_INBOX_IDLE)
|
|
479
|
+
assert len(chat.get_messages()) == n_msgs
|
|
480
|
+
|
|
481
|
+
# The message has reached its destination.
|
|
482
|
+
ac1_direct_imap.select_folder(expected_destination)
|
|
483
|
+
assert len(ac1_direct_imap.get_all_messages()) == 1
|
|
484
|
+
if folder != expected_destination:
|
|
485
|
+
ac1_direct_imap.select_folder(folder)
|
|
486
|
+
assert len(ac1_direct_imap.get_all_messages()) == 0
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def test_trash_multiple_messages(acfactory, direct_imap, log):
|
|
490
|
+
ac1, ac2 = acfactory.get_online_accounts(2)
|
|
491
|
+
ac2.stop_io()
|
|
492
|
+
|
|
493
|
+
# Create the Trash folder on IMAP server and configure deletion to it. There was a bug that if
|
|
494
|
+
# Trash wasn't configured initially, it can't be configured later, let's check this.
|
|
495
|
+
log.section("Creating trash folder")
|
|
496
|
+
ac2_direct_imap = direct_imap(ac2)
|
|
497
|
+
ac2_direct_imap.create_folder("Trash")
|
|
498
|
+
ac2.set_config("delete_server_after", "0")
|
|
499
|
+
ac2.set_config("sync_msgs", "0")
|
|
500
|
+
ac2.set_config("delete_to_trash", "1")
|
|
501
|
+
|
|
502
|
+
log.section("Check that Trash can be configured initially as well")
|
|
503
|
+
ac3 = ac2.clone()
|
|
504
|
+
ac3.bring_online()
|
|
505
|
+
assert ac3.get_config("configured_trash_folder")
|
|
506
|
+
ac3.stop_io()
|
|
507
|
+
|
|
508
|
+
ac2.start_io()
|
|
509
|
+
chat12 = acfactory.get_accepted_chat(ac1, ac2)
|
|
510
|
+
|
|
511
|
+
log.section("ac1: sending 3 messages")
|
|
512
|
+
texts = ["first", "second", "third"]
|
|
513
|
+
for text in texts:
|
|
514
|
+
chat12.send_text(text)
|
|
515
|
+
|
|
516
|
+
log.section("ac2: waiting for all messages on the other side")
|
|
517
|
+
to_delete = []
|
|
518
|
+
for text in texts:
|
|
519
|
+
msg = ac2.wait_for_incoming_msg().get_snapshot()
|
|
520
|
+
assert msg.text in texts
|
|
521
|
+
if text != "second":
|
|
522
|
+
to_delete.append(msg)
|
|
523
|
+
# ac2 has received some messages, this is impossible w/o the trash folder configured, let's
|
|
524
|
+
# check the configuration.
|
|
525
|
+
assert ac2.get_config("configured_trash_folder") == "Trash"
|
|
526
|
+
|
|
527
|
+
log.section("ac2: deleting all messages except second")
|
|
528
|
+
assert len(to_delete) == len(texts) - 1
|
|
529
|
+
ac2.delete_messages(to_delete)
|
|
530
|
+
|
|
531
|
+
log.section("ac2: test that only one message is left")
|
|
532
|
+
while 1:
|
|
533
|
+
ac2.wait_for_event(EventType.IMAP_MESSAGE_MOVED)
|
|
534
|
+
ac2_direct_imap.select_config_folder("inbox")
|
|
535
|
+
nr_msgs = len(ac2_direct_imap.get_all_messages())
|
|
536
|
+
assert nr_msgs > 0
|
|
537
|
+
if nr_msgs == 1:
|
|
538
|
+
break
|