deltachat-rpc-client 2.26.0__tar.gz → 2.27.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.
Files changed (34) hide show
  1. {deltachat_rpc_client-2.26.0/src/deltachat_rpc_client.egg-info → deltachat_rpc_client-2.27.0}/PKG-INFO +1 -1
  2. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/pyproject.toml +1 -1
  3. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/account.py +10 -1
  4. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/deltachat.py +10 -1
  5. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/pytestplugin.py +8 -2
  6. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0/src/deltachat_rpc_client.egg-info}/PKG-INFO +1 -1
  7. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client.egg-info/SOURCES.txt +1 -0
  8. deltachat_rpc_client-2.27.0/tests/test_folders.py +538 -0
  9. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_iroh_webxdc.py +3 -3
  10. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_multidevice.py +35 -0
  11. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_securejoin.py +25 -28
  12. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_something.py +84 -94
  13. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/LICENSE +0 -0
  14. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/README.md +0 -0
  15. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/setup.cfg +0 -0
  16. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/__init__.py +0 -0
  17. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/_utils.py +0 -0
  18. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/chat.py +0 -0
  19. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/client.py +0 -0
  20. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/const.py +0 -0
  21. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/contact.py +0 -0
  22. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/events.py +0 -0
  23. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/message.py +0 -0
  24. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/py.typed +0 -0
  25. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client/rpc.py +0 -0
  26. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client.egg-info/dependency_links.txt +0 -0
  27. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client.egg-info/entry_points.txt +0 -0
  28. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/src/deltachat_rpc_client.egg-info/top_level.txt +0 -0
  29. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_account_events.py +0 -0
  30. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_calls.py +0 -0
  31. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_chatlist_events.py +0 -0
  32. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_key_transfer.py +0 -0
  33. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_vcard.py +0 -0
  34. {deltachat_rpc_client-2.26.0 → deltachat_rpc_client-2.27.0}/tests/test_webxdc.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deltachat-rpc-client
3
- Version: 2.26.0
3
+ Version: 2.27.0
4
4
  Summary: Python client for Delta Chat core JSON-RPC interface
5
5
  Classifier: Development Status :: 5 - Production/Stable
6
6
  Classifier: Intended Audience :: Developers
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deltachat-rpc-client"
7
- version = "2.26.0"
7
+ version = "2.27.0"
8
8
  description = "Python client for Delta Chat core JSON-RPC interface"
9
9
  classifiers = [
10
10
  "Development Status :: 5 - Production/Stable",
@@ -417,12 +417,21 @@ class Account:
417
417
  """Wait for messages noticed event and return it."""
418
418
  return self.wait_for_event(EventType.MSGS_NOTICED)
419
419
 
420
+ def wait_for_msg(self, event_type) -> Message:
421
+ """Wait for an event about the message.
422
+
423
+ Consumes all events before the matching event.
424
+ Returns a message corresponding to the msg_id field of the event.
425
+ """
426
+ event = self.wait_for_event(event_type)
427
+ return self.get_message_by_id(event.msg_id)
428
+
420
429
  def wait_for_incoming_msg(self):
421
430
  """Wait for incoming message and return it.
422
431
 
423
432
  Consumes all events before the next incoming message event.
424
433
  """
425
- return self.get_message_by_id(self.wait_for_incoming_msg_event().msg_id)
434
+ return self.wait_for_msg(EventType.INCOMING_MSG)
426
435
 
427
436
  def wait_for_securejoin_inviter_success(self):
428
437
  """Wait until SecureJoin process finishes successfully on the inviter side."""
@@ -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()
@@ -135,9 +135,15 @@ def rpc(tmp_path) -> AsyncGenerator:
135
135
 
136
136
 
137
137
  @pytest.fixture
138
- def acfactory(rpc) -> AsyncGenerator:
138
+ def dc(rpc) -> DeltaChat:
139
+ """Return account manager."""
140
+ return DeltaChat(rpc)
141
+
142
+
143
+ @pytest.fixture
144
+ def acfactory(dc) -> AsyncGenerator:
139
145
  """Return account factory fixture."""
140
- return ACFactory(DeltaChat(rpc))
146
+ return ACFactory(dc)
141
147
 
142
148
 
143
149
  @pytest.fixture
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deltachat-rpc-client
3
- Version: 2.26.0
3
+ Version: 2.27.0
4
4
  Summary: Python client for Delta Chat core JSON-RPC interface
5
5
  Classifier: Development Status :: 5 - Production/Stable
6
6
  Classifier: Intended Audience :: Developers
@@ -22,6 +22,7 @@ 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
@@ -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 "uid/validity change folder DeltaChat" 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
@@ -84,7 +84,7 @@ def test_realtime_sequentially(acfactory, path_to_webxdc):
84
84
 
85
85
  # share a webxdc app between ac1 and ac2
86
86
  ac1_webxdc_msg = acfactory.send_message(from_account=ac1, to_account=ac2, text="play", file=path_to_webxdc)
87
- ac2_webxdc_msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id)
87
+ ac2_webxdc_msg = ac2.wait_for_incoming_msg()
88
88
  snapshot = ac2_webxdc_msg.get_snapshot()
89
89
  assert snapshot.text == "play"
90
90
 
@@ -94,7 +94,7 @@ def test_realtime_sequentially(acfactory, path_to_webxdc):
94
94
  acfactory.send_message(from_account=ac1, to_account=ac2, text="ping1")
95
95
 
96
96
  log("waiting for incoming message on ac2")
97
- snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
97
+ snapshot = ac2.wait_for_incoming_msg().get_snapshot()
98
98
  assert snapshot.text == "ping1"
99
99
 
100
100
  log("sending ac2 -> ac1 realtime advertisement and additional message")
@@ -102,7 +102,7 @@ def test_realtime_sequentially(acfactory, path_to_webxdc):
102
102
  acfactory.send_message(from_account=ac2, to_account=ac1, text="ping2")
103
103
 
104
104
  log("waiting for incoming message on ac1")
105
- snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
105
+ snapshot = ac1.wait_for_incoming_msg().get_snapshot()
106
106
  assert snapshot.text == "ping2"
107
107
 
108
108
  log("sending realtime data ac1 -> ac2")
@@ -4,6 +4,41 @@ from deltachat_rpc_client import EventType
4
4
  from deltachat_rpc_client.const import MessageState
5
5
 
6
6
 
7
+ def test_bcc_self_delete_server_after_defaults(acfactory):
8
+ """Test default values for bcc_self and delete_server_after."""
9
+ ac = acfactory.get_online_account()
10
+
11
+ # Initially after getting online
12
+ # the setting bcc_self is set to 0 because there is only one device
13
+ # and delete_server_after is "1", meaning immediate deletion.
14
+ assert ac.get_config("bcc_self") == "0"
15
+ assert ac.get_config("delete_server_after") == "1"
16
+
17
+ # Setup a second device.
18
+ ac_clone = ac.clone()
19
+ ac_clone.bring_online()
20
+
21
+ # Second device setup
22
+ # enables bcc_self and changes default delete_server_after.
23
+ assert ac.get_config("bcc_self") == "1"
24
+ assert ac.get_config("delete_server_after") == "0"
25
+
26
+ assert ac_clone.get_config("bcc_self") == "1"
27
+ assert ac_clone.get_config("delete_server_after") == "0"
28
+
29
+ # Manually disabling bcc_self
30
+ # also restores the default for delete_server_after.
31
+ ac.set_config("bcc_self", "0")
32
+ assert ac.get_config("bcc_self") == "0"
33
+ assert ac.get_config("delete_server_after") == "1"
34
+
35
+ # Cloning the account again enables bcc_self
36
+ # even though it was manually disabled.
37
+ ac_clone = ac.clone()
38
+ assert ac.get_config("bcc_self") == "1"
39
+ assert ac.get_config("delete_server_after") == "0"
40
+
41
+
7
42
  def test_one_account_send_bcc_setting(acfactory, log, direct_imap):
8
43
  ac1, ac2 = acfactory.get_online_accounts(2)
9
44
  ac1_clone = ac1.clone()
@@ -86,7 +86,7 @@ def test_qr_securejoin(acfactory):
86
86
  alice_contact_bob_snapshot = alice_contact_bob.get_snapshot()
87
87
  assert alice_contact_bob_snapshot.is_verified
88
88
 
89
- snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
89
+ snapshot = bob.wait_for_incoming_msg().get_snapshot()
90
90
  assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr"))
91
91
 
92
92
  # Test that Bob verified Alice's profile.
@@ -140,15 +140,15 @@ def test_qr_securejoin_broadcast(acfactory, all_devices_online):
140
140
  return chat
141
141
 
142
142
  def wait_for_broadcast_messages(ac):
143
- chat = get_broadcast(ac)
143
+ snapshot1 = ac.wait_for_incoming_msg().get_snapshot()
144
+ assert snapshot1.text == "You joined the channel."
144
145
 
145
- snapshot = ac.wait_for_incoming_msg().get_snapshot()
146
- assert snapshot.text == "You joined the channel."
147
- assert snapshot.chat_id == chat.id
146
+ snapshot2 = ac.wait_for_incoming_msg().get_snapshot()
147
+ assert snapshot2.text == "Hello everyone!"
148
148
 
149
- snapshot = ac.wait_for_incoming_msg().get_snapshot()
150
- assert snapshot.text == "Hello everyone!"
151
- assert snapshot.chat_id == chat.id
149
+ chat = get_broadcast(ac)
150
+ assert snapshot1.chat_id == chat.id
151
+ assert snapshot2.chat_id == chat.id
152
152
 
153
153
  def check_account(ac, contact, inviter_side, please_wait_info_msg=False):
154
154
  # Check that the chat partner is verified.
@@ -255,7 +255,7 @@ def test_qr_securejoin_contact_request(acfactory) -> None:
255
255
  alice_chat_bob = alice_contact_bob.create_chat()
256
256
  alice_chat_bob.send_text("Hello!")
257
257
 
258
- snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
258
+ snapshot = bob.wait_for_incoming_msg().get_snapshot()
259
259
  assert snapshot.text == "Hello!"
260
260
  bob_chat_alice = snapshot.chat
261
261
  assert bob_chat_alice.get_basic_snapshot().is_contact_request
@@ -299,8 +299,7 @@ def test_qr_readreceipt(acfactory) -> None:
299
299
 
300
300
  logging.info("Bob and Charlie receive a group")
301
301
 
302
- bob_msg_id = bob.wait_for_incoming_msg_event().msg_id
303
- bob_message = bob.get_message_by_id(bob_msg_id)
302
+ bob_message = bob.wait_for_incoming_msg()
304
303
  bob_snapshot = bob_message.get_snapshot()
305
304
  assert bob_snapshot.text == "Hello"
306
305
 
@@ -311,8 +310,7 @@ def test_qr_readreceipt(acfactory) -> None:
311
310
 
312
311
  bob_out_message = bob_snapshot.chat.send_message(text="Hi from Bob!")
313
312
 
314
- charlie_msg_id = charlie.wait_for_incoming_msg_event().msg_id
315
- charlie_message = charlie.get_message_by_id(charlie_msg_id)
313
+ charlie_message = charlie.wait_for_incoming_msg()
316
314
  charlie_snapshot = charlie_message.get_snapshot()
317
315
  assert charlie_snapshot.text == "Hi from Bob!"
318
316
 
@@ -387,7 +385,7 @@ def test_verified_group_member_added_recovery(acfactory) -> None:
387
385
  ac3_contact_ac2 = ac3.create_contact(ac2)
388
386
  ac3_chat.remove_contact(ac3_contact_ac2_old)
389
387
 
390
- snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
388
+ snapshot = ac1.wait_for_incoming_msg().get_snapshot()
391
389
  assert "removed" in snapshot.text
392
390
 
393
391
  ac3_chat.add_contact(ac3_contact_ac2)
@@ -400,18 +398,17 @@ def test_verified_group_member_added_recovery(acfactory) -> None:
400
398
  logging.info("ac2 got event message: %s", snapshot.text)
401
399
  assert "added" in snapshot.text
402
400
 
403
- snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
401
+ snapshot = ac1.wait_for_incoming_msg().get_snapshot()
404
402
  assert "added" in snapshot.text
405
403
 
406
404
  chat = Chat(ac2, chat_id)
407
405
  chat.send_text("Works again!")
408
406
 
409
- msg_id = ac3.wait_for_incoming_msg_event().msg_id
410
- message = ac3.get_message_by_id(msg_id)
407
+ message = ac3.wait_for_incoming_msg()
411
408
  snapshot = message.get_snapshot()
412
409
  assert snapshot.text == "Works again!"
413
410
 
414
- snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
411
+ snapshot = ac1.wait_for_incoming_msg().get_snapshot()
415
412
  assert snapshot.text == "Works again!"
416
413
 
417
414
  ac1_contact_ac2 = ac1.create_contact(ac2)
@@ -447,7 +444,7 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
447
444
  # ensure ac1 can write and ac2 receives messages in verified chat
448
445
  ch1.send_text("ac1 says hello")
449
446
  while 1:
450
- snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
447
+ snapshot = ac2.wait_for_incoming_msg().get_snapshot()
451
448
  if snapshot.text == "ac1 says hello":
452
449
  break
453
450
 
@@ -468,7 +465,7 @@ def test_qr_join_chat_with_pending_bobstate_issue4894(acfactory):
468
465
  # ensure ac2 receives message in VG
469
466
  vg.send_text("hello")
470
467
  while 1:
471
- msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
468
+ msg = ac2.wait_for_incoming_msg().get_snapshot()
472
469
  if msg.text == "hello":
473
470
  break
474
471
 
@@ -505,7 +502,7 @@ def test_qr_new_group_unblocked(acfactory):
505
502
  ac2.wait_for_incoming_msg_event()
506
503
 
507
504
  ac1_new_chat.send_text("Hello!")
508
- ac2_msg = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
505
+ ac2_msg = ac2.wait_for_incoming_msg().get_snapshot()
509
506
  assert ac2_msg.text == "Hello!"
510
507
  assert ac2_msg.chat.get_basic_snapshot().is_contact_request
511
508
 
@@ -530,7 +527,7 @@ def test_aeap_flow_verified(acfactory):
530
527
 
531
528
  logging.info("receiving first message")
532
529
  ac2.wait_for_incoming_msg_event() # member added message
533
- msg_in_1 = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
530
+ msg_in_1 = ac2.wait_for_incoming_msg().get_snapshot()
534
531
  assert msg_in_1.text == msg_out.text
535
532
 
536
533
  logging.info("changing email account")
@@ -544,7 +541,7 @@ def test_aeap_flow_verified(acfactory):
544
541
  msg_out = chat.send_text("changed address").get_snapshot()
545
542
 
546
543
  logging.info("receiving second message")
547
- msg_in_2 = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id)
544
+ msg_in_2 = ac2.wait_for_incoming_msg()
548
545
  msg_in_2_snapshot = msg_in_2.get_snapshot()
549
546
  assert msg_in_2_snapshot.text == msg_out.text
550
547
  assert msg_in_2_snapshot.chat.id == msg_in_1.chat.id
@@ -576,7 +573,7 @@ def test_gossip_verification(acfactory) -> None:
576
573
  bob_group_chat.add_contact(bob_contact_carol)
577
574
  bob_group_chat.send_message(text="Hello Autocrypt group")
578
575
 
579
- snapshot = carol.get_message_by_id(carol.wait_for_incoming_msg_event().msg_id).get_snapshot()
576
+ snapshot = carol.wait_for_incoming_msg().get_snapshot()
580
577
  assert snapshot.text == "Hello Autocrypt group"
581
578
  assert snapshot.show_padlock
582
579
 
@@ -592,7 +589,7 @@ def test_gossip_verification(acfactory) -> None:
592
589
  bob_group_chat.add_contact(bob_contact_carol)
593
590
  bob_group_chat.send_message(text="Hello Securejoin group")
594
591
 
595
- snapshot = carol.get_message_by_id(carol.wait_for_incoming_msg_event().msg_id).get_snapshot()
592
+ snapshot = carol.wait_for_incoming_msg().get_snapshot()
596
593
  assert snapshot.text == "Hello Securejoin group"
597
594
  assert snapshot.show_padlock
598
595
 
@@ -620,7 +617,7 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
620
617
  ac1.wait_for_securejoin_joiner_success()
621
618
 
622
619
  # ac1 waits for member added message and creates a QR code.
623
- snapshot = ac1.get_message_by_id(ac1.wait_for_incoming_msg_event().msg_id).get_snapshot()
620
+ snapshot = ac1.wait_for_incoming_msg().get_snapshot()
624
621
  assert snapshot.text == "Member Me added by {}.".format(ac3.get_config("addr"))
625
622
  ac1_qr_code = snapshot.chat.get_qr_code()
626
623
 
@@ -657,7 +654,7 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
657
654
 
658
655
  # Wait for member added.
659
656
  logging.info("ac2 waits for member added message")
660
- snapshot = ac2.get_message_by_id(ac2.wait_for_incoming_msg_event().msg_id).get_snapshot()
657
+ snapshot = ac2.wait_for_incoming_msg().get_snapshot()
661
658
  assert snapshot.is_info
662
659
  ac2_chat = snapshot.chat
663
660
  assert len(ac2_chat.get_contacts()) == 3
@@ -679,7 +676,7 @@ def test_withdraw_securejoin_qr(acfactory):
679
676
 
680
677
  alice.clear_all_events()
681
678
 
682
- snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
679
+ snapshot = bob.wait_for_incoming_msg().get_snapshot()
683
680
  assert snapshot.text == "Member Me added by {}.".format(alice.get_config("addr"))
684
681
  bob_chat.leave()
685
682
 
@@ -352,9 +352,7 @@ def test_receive_imf_failure(acfactory) -> None:
352
352
  # The failed message doesn't break the IMAP loop.
353
353
  bob.set_config("fail_on_receiving_full_msg", "0")
354
354
  alice_chat_bob.send_text("Hello again!")
355
- event = bob.wait_for_incoming_msg_event()
356
- msg_id = event.msg_id
357
- message = bob.get_message_by_id(msg_id)
355
+ message = bob.wait_for_incoming_msg()
358
356
  snapshot = message.get_snapshot()
359
357
  assert snapshot.text == "Hello again!"
360
358
  assert snapshot.download_state == DownloadState.DONE
@@ -423,10 +421,7 @@ def test_is_bot(acfactory) -> None:
423
421
  alice.set_config("bot", "1")
424
422
  alice_chat_bob.send_text("Hello!")
425
423
 
426
- event = bob.wait_for_incoming_msg_event()
427
- message = bob.get_message_by_id(event.msg_id)
428
- snapshot = message.get_snapshot()
429
- assert snapshot.chat_id == event.chat_id
424
+ snapshot = bob.wait_for_incoming_msg().get_snapshot()
430
425
  assert snapshot.text == "Hello!"
431
426
  assert snapshot.is_bot
432
427
 
@@ -518,7 +513,7 @@ def test_import_export_keys(acfactory, tmp_path) -> None:
518
513
  alice_chat_bob = alice.create_chat(bob)
519
514
  alice_chat_bob.send_text("Hello Bob!")
520
515
 
521
- snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
516
+ snapshot = bob.wait_for_incoming_msg().get_snapshot()
522
517
  assert snapshot.text == "Hello Bob!"
523
518
 
524
519
  # Alice resetups account, but keeps the key.
@@ -530,7 +525,7 @@ def test_import_export_keys(acfactory, tmp_path) -> None:
530
525
 
531
526
  snapshot.chat.accept()
532
527
  snapshot.chat.send_text("Hello Alice!")
533
- snapshot = alice.get_message_by_id(alice.wait_for_incoming_msg_event().msg_id).get_snapshot()
528
+ snapshot = alice.wait_for_incoming_msg().get_snapshot()
534
529
  assert snapshot.text == "Hello Alice!"
535
530
  assert snapshot.show_padlock
536
531
 
@@ -575,18 +570,13 @@ def test_mdn_doesnt_break_autocrypt(acfactory) -> None:
575
570
 
576
571
  # Alice sends a message to Bob.
577
572
  alice_chat_bob.send_text("Hello Bob!")
578
- event = bob.wait_for_incoming_msg_event()
579
- msg_id = event.msg_id
580
- message = bob.get_message_by_id(msg_id)
581
- snapshot = message.get_snapshot()
573
+ snapshot = bob.wait_for_incoming_msg().get_snapshot()
582
574
 
583
575
  # Bob sends a message to Alice.
584
576
  bob_chat_alice = snapshot.chat
585
577
  bob_chat_alice.accept()
586
578
  bob_chat_alice.send_text("Hello Alice!")
587
- event = alice.wait_for_incoming_msg_event()
588
- msg_id = event.msg_id
589
- message = alice.get_message_by_id(msg_id)
579
+ message = alice.wait_for_incoming_msg()
590
580
  snapshot = message.get_snapshot()
591
581
  assert snapshot.show_padlock
592
582
 
@@ -596,10 +586,7 @@ def test_mdn_doesnt_break_autocrypt(acfactory) -> None:
596
586
 
597
587
  # Bob sends a message to Alice, it should also be encrypted.
598
588
  bob_chat_alice.send_text("Hi Alice!")
599
- event = alice.wait_for_incoming_msg_event()
600
- msg_id = event.msg_id
601
- message = alice.get_message_by_id(msg_id)
602
- snapshot = message.get_snapshot()
589
+ snapshot = alice.wait_for_incoming_msg().get_snapshot()
603
590
  assert snapshot.show_padlock
604
591
 
605
592
 
@@ -657,50 +644,6 @@ def test_reaction_to_partially_fetched_msg(acfactory, tmp_path):
657
644
  assert list(reactions.reactions_by_contact.values())[0] == [react_str]
658
645
 
659
646
 
660
- def test_reactions_for_a_reordering_move(acfactory, direct_imap):
661
- """When a batch of messages is moved from Inbox to DeltaChat folder with a single MOVE command,
662
- their UIDs may be reordered (e.g. Gmail is known for that) which led to that messages were
663
- processed by receive_imf in the wrong order, and, particularly, reactions were processed before
664
- messages they refer to and thus dropped.
665
- """
666
- (ac1,) = acfactory.get_online_accounts(1)
667
-
668
- addr, password = acfactory.get_credentials()
669
- ac2 = acfactory.get_unconfigured_account()
670
- ac2.add_or_update_transport({"addr": addr, "password": password})
671
- ac2.set_config("mvbox_move", "1")
672
- assert ac2.is_configured()
673
-
674
- ac2.bring_online()
675
- chat1 = acfactory.get_accepted_chat(ac1, ac2)
676
- ac2.stop_io()
677
-
678
- logging.info("sending message + reaction from ac1 to ac2")
679
- msg1 = chat1.send_text("hi")
680
- msg1.wait_until_delivered()
681
- # It's is sad, but messages must differ in their INTERNALDATEs to be processed in the correct
682
- # order by DC, and most (if not all) mail servers provide only seconds precision.
683
- time.sleep(1.1)
684
- react_str = "\N{THUMBS UP SIGN}"
685
- msg1.send_reaction(react_str).wait_until_delivered()
686
-
687
- logging.info("moving messages to ac2's DeltaChat folder in the reverse order")
688
- ac2_direct_imap = direct_imap(ac2)
689
- ac2_direct_imap.connect()
690
- for uid in sorted([m.uid for m in ac2_direct_imap.get_all_messages()], reverse=True):
691
- ac2_direct_imap.conn.move(uid, "DeltaChat")
692
-
693
- logging.info("receiving messages by ac2")
694
- ac2.start_io()
695
- msg2 = Message(ac2, ac2.wait_for_reactions_changed().msg_id)
696
- assert msg2.get_snapshot().text == msg1.get_snapshot().text
697
- reactions = msg2.get_reactions()
698
- contacts = [Contact(ac2, int(i)) for i in reactions.reactions_by_contact]
699
- assert len(contacts) == 1
700
- assert contacts[0].get_snapshot().address == ac1.get_config("addr")
701
- assert list(reactions.reactions_by_contact.values())[0] == [react_str]
702
-
703
-
704
647
  @pytest.mark.parametrize("n_accounts", [3, 2])
705
648
  def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
706
649
  download_limit = 300000
@@ -712,7 +655,7 @@ def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
712
655
  for account in others:
713
656
  chat = account.create_chat(alice)
714
657
  chat.send_text("Hello Alice!")
715
- assert alice.get_message_by_id(alice.wait_for_incoming_msg_event().msg_id).get_snapshot().text == "Hello Alice!"
658
+ assert alice.wait_for_incoming_msg().get_snapshot().text == "Hello Alice!"
716
659
 
717
660
  contact = alice.create_contact(account)
718
661
  alice_group.add_contact(contact)
@@ -722,7 +665,7 @@ def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
722
665
  bob.set_config("download_limit", str(download_limit))
723
666
 
724
667
  alice_group.send_text("hi")
725
- snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
668
+ snapshot = bob.wait_for_incoming_msg().get_snapshot()
726
669
  assert snapshot.text == "hi"
727
670
  bob_group = snapshot.chat
728
671
 
@@ -732,7 +675,7 @@ def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
732
675
  for i in range(10):
733
676
  logging.info("Sending message %s", i)
734
677
  alice_group.send_file(str(path))
735
- snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
678
+ snapshot = bob.wait_for_incoming_msg().get_snapshot()
736
679
  assert snapshot.download_state == DownloadState.AVAILABLE
737
680
  if n_accounts > 2:
738
681
  assert snapshot.chat == bob_group
@@ -759,8 +702,8 @@ def test_markseen_contact_request(acfactory):
759
702
  alice_chat_bob = alice.create_chat(bob)
760
703
  alice_chat_bob.send_text("Hello Bob!")
761
704
 
762
- message = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id)
763
- message2 = bob2.get_message_by_id(bob2.wait_for_incoming_msg_event().msg_id)
705
+ message = bob.wait_for_incoming_msg()
706
+ message2 = bob2.wait_for_incoming_msg()
764
707
  assert message2.get_snapshot().state == MessageState.IN_FRESH
765
708
 
766
709
  message.mark_seen()
@@ -782,7 +725,7 @@ def test_read_receipt(acfactory):
782
725
  msg = bob.wait_for_incoming_msg()
783
726
  msg.mark_seen()
784
727
 
785
- read_msg = alice.get_message_by_id(alice.wait_for_event(EventType.MSG_READ).msg_id)
728
+ read_msg = alice.wait_for_msg(EventType.MSG_READ)
786
729
  read_receipts = read_msg.get_read_receipts()
787
730
  assert len(read_receipts) == 1
788
731
  assert read_receipts[0].contact_id == alice_contact_bob.id
@@ -888,30 +831,6 @@ def test_get_all_accounts_deadlock(rpc):
888
831
  all_accounts()
889
832
 
890
833
 
891
- def test_delete_deltachat_folder(acfactory, direct_imap):
892
- """Test that DeltaChat folder is recreated if user deletes it manually."""
893
- ac1 = acfactory.new_configured_account()
894
- ac1.set_config("mvbox_move", "1")
895
- ac1.bring_online()
896
-
897
- ac1_direct_imap = direct_imap(ac1)
898
- ac1_direct_imap.conn.folder.delete("DeltaChat")
899
- assert "DeltaChat" not in ac1_direct_imap.list_folders()
900
-
901
- # Wait until new folder is created and UIDVALIDITY is updated.
902
- while True:
903
- event = ac1.wait_for_event()
904
- if event.kind == EventType.INFO and "uid/validity change folder DeltaChat" in event.msg:
905
- break
906
-
907
- ac2 = acfactory.get_online_account()
908
- ac2.create_chat(ac1).send_text("hello")
909
- msg = ac1.wait_for_incoming_msg().get_snapshot()
910
- assert msg.text == "hello"
911
-
912
- assert "DeltaChat" in ac1_direct_imap.list_folders()
913
-
914
-
915
834
  @pytest.mark.parametrize("all_devices_online", [True, False])
916
835
  def test_leave_broadcast(acfactory, all_devices_online):
917
836
  alice, bob = acfactory.get_online_accounts(2)
@@ -1012,3 +931,74 @@ def test_leave_broadcast(acfactory, all_devices_online):
1012
931
  bob2.wait_for_event(EventType.CHAT_MODIFIED)
1013
932
 
1014
933
  check_account(bob2, bob2.create_contact(alice), inviter_side=False)
934
+
935
+
936
+ def test_immediate_autodelete(acfactory, direct_imap, log):
937
+ ac1, ac2 = acfactory.get_online_accounts(2)
938
+
939
+ # "1" means delete immediately, while "0" means do not delete
940
+ ac2.set_config("delete_server_after", "1")
941
+
942
+ log.section("ac1: create chat with ac2")
943
+ chat1 = ac1.create_chat(ac2)
944
+ ac2.create_chat(ac1)
945
+
946
+ log.section("ac1: send message to ac2")
947
+ sent_msg = chat1.send_text("hello")
948
+
949
+ msg = ac2.wait_for_incoming_msg()
950
+ assert msg.get_snapshot().text == "hello"
951
+
952
+ log.section("ac2: wait for close/expunge on autodelete")
953
+ ac2.wait_for_event(EventType.IMAP_MESSAGE_DELETED)
954
+ while True:
955
+ event = ac2.wait_for_event()
956
+ if event.kind == EventType.INFO and "Close/expunge succeeded." in event.msg:
957
+ break
958
+
959
+ log.section("ac2: check that message was autodeleted on server")
960
+ ac2_direct_imap = direct_imap(ac2)
961
+ assert len(ac2_direct_imap.get_all_messages()) == 0
962
+
963
+ log.section("ac2: Mark deleted message as seen and check that read receipt arrives")
964
+ msg.mark_seen()
965
+ ev = ac1.wait_for_event(EventType.MSG_READ)
966
+ assert ev.chat_id == chat1.id
967
+ assert ev.msg_id == sent_msg.id
968
+
969
+
970
+ def test_background_fetch(acfactory, dc):
971
+ ac1, ac2 = acfactory.get_online_accounts(2)
972
+ ac1.stop_io()
973
+
974
+ ac1_chat = ac1.create_chat(ac2)
975
+
976
+ ac2_chat = ac2.create_chat(ac1)
977
+ ac2_chat.send_text("Hello!")
978
+
979
+ while True:
980
+ dc.background_fetch(300)
981
+ messages = ac1_chat.get_messages()
982
+ snapshot = messages[-1].get_snapshot()
983
+ if snapshot.text == "Hello!":
984
+ break
985
+
986
+ # Stopping background fetch immediately after starting
987
+ # does not result in any errors.
988
+ background_fetch_future = dc.background_fetch.future(300)
989
+ dc.stop_background_fetch()
990
+ background_fetch_future()
991
+
992
+ # Starting background fetch with zero timeout is ok,
993
+ # it should terminate immediately.
994
+ dc.background_fetch(0)
995
+
996
+ # Background fetch can still be used to send and receive messages.
997
+ ac2_chat.send_text("Hello again!")
998
+
999
+ while True:
1000
+ dc.background_fetch(300)
1001
+ messages = ac1_chat.get_messages()
1002
+ snapshot = messages[-1].get_snapshot()
1003
+ if snapshot.text == "Hello again!":
1004
+ break