deltachat-rpc-client 1.157.2__tar.gz → 1.158.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 (31) hide show
  1. {deltachat_rpc_client-1.157.2/src/deltachat_rpc_client.egg-info → deltachat_rpc_client-1.158.0}/PKG-INFO +3 -2
  2. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/pyproject.toml +9 -1
  3. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/account.py +28 -37
  4. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/pytestplugin.py +59 -20
  5. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0/src/deltachat_rpc_client.egg-info}/PKG-INFO +3 -2
  6. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/tests/test_account_events.py +3 -2
  7. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/tests/test_chatlist_events.py +3 -6
  8. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/tests/test_iroh_webxdc.py +6 -15
  9. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/tests/test_key_transfer.py +3 -0
  10. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/tests/test_securejoin.py +17 -29
  11. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/tests/test_something.py +149 -87
  12. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/tests/test_webxdc.py +3 -9
  13. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/LICENSE +0 -0
  14. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/README.md +0 -0
  15. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/setup.cfg +0 -0
  16. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/__init__.py +0 -0
  17. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/_utils.py +0 -0
  18. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/chat.py +0 -0
  19. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/client.py +0 -0
  20. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/const.py +0 -0
  21. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/contact.py +0 -0
  22. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/deltachat.py +0 -0
  23. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/events.py +0 -0
  24. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/message.py +0 -0
  25. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/py.typed +0 -0
  26. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client/rpc.py +0 -0
  27. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client.egg-info/SOURCES.txt +0 -0
  28. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client.egg-info/dependency_links.txt +0 -0
  29. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client.egg-info/entry_points.txt +0 -0
  30. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/src/deltachat_rpc_client.egg-info/top_level.txt +0 -0
  31. {deltachat_rpc_client-1.157.2 → deltachat_rpc_client-1.158.0}/tests/test_vcard.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: deltachat-rpc-client
3
- Version: 1.157.2
3
+ Version: 1.158.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
@@ -19,6 +19,7 @@ Classifier: Topic :: Communications :: Email
19
19
  Requires-Python: >=3.8
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
+ Dynamic: license-file
22
23
 
23
24
  # Delta Chat RPC python client
24
25
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deltachat-rpc-client"
7
- version = "1.157.2"
7
+ version = "1.158.0"
8
8
  description = "Python client for Delta Chat core JSON-RPC interface"
9
9
  classifiers = [
10
10
  "Development Status :: 5 - Production/Stable",
@@ -70,3 +70,11 @@ line-length = 120
70
70
 
71
71
  [tool.isort]
72
72
  profile = "black"
73
+
74
+ [dependency-groups]
75
+ dev = [
76
+ "imap-tools",
77
+ "pytest",
78
+ "pytest-timeout",
79
+ "pytest-xdist",
80
+ ]
@@ -1,8 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from pathlib import Path
5
- from tempfile import TemporaryDirectory
6
4
  from typing import TYPE_CHECKING, Optional, Union
7
5
  from warnings import warn
8
6
 
@@ -28,9 +26,12 @@ class Account:
28
26
  def _rpc(self) -> "Rpc":
29
27
  return self.manager.rpc
30
28
 
31
- def wait_for_event(self) -> AttrDict:
29
+ def wait_for_event(self, event_type=None) -> AttrDict:
32
30
  """Wait until the next event and return it."""
33
- return AttrDict(self._rpc.wait_for_event(self.id))
31
+ while True:
32
+ next_event = AttrDict(self._rpc.wait_for_event(self.id))
33
+ if event_type is None or next_event.kind == event_type:
34
+ return next_event
34
35
 
35
36
  def clear_all_events(self):
36
37
  """Removes all queued-up events for a given account. Useful for tests."""
@@ -41,14 +42,14 @@ class Account:
41
42
  self._rpc.remove_account(self.id)
42
43
 
43
44
  def clone(self) -> "Account":
44
- """Clone given account."""
45
- with TemporaryDirectory() as tmp_dir:
46
- tmp_path = Path(tmp_dir)
47
- self.export_backup(tmp_path)
48
- files = list(tmp_path.glob("*.tar"))
49
- new_account = self.manager.add_account()
50
- new_account.import_backup(files[0])
51
- return new_account
45
+ """Clone given account.
46
+ This uses backup-transfer via iroh, i.e. the 'Add second device' feature."""
47
+ future = self._rpc.provide_backup.future(self.id)
48
+ qr = self._rpc.get_backup_qr(self.id)
49
+ new_account = self.manager.add_account()
50
+ new_account._rpc.get_backup(new_account.id, qr)
51
+ future()
52
+ return new_account
52
53
 
53
54
  def start_io(self) -> None:
54
55
  """Start the account I/O."""
@@ -112,12 +113,9 @@ class Account:
112
113
  def bring_online(self):
113
114
  """Start I/O and wait until IMAP becomes IDLE."""
114
115
  self.start_io()
115
- while True:
116
- event = self.wait_for_event()
117
- if event.kind == EventType.IMAP_INBOX_IDLE:
118
- break
116
+ self.wait_for_event(EventType.IMAP_INBOX_IDLE)
119
117
 
120
- def create_contact(self, obj: Union[int, str, Contact], name: Optional[str] = None) -> Contact:
118
+ def create_contact(self, obj: Union[int, str, Contact, "Account"], name: Optional[str] = None) -> Contact:
121
119
  """Create a new Contact or return an existing one.
122
120
 
123
121
  Calling this method will always result in the same
@@ -125,9 +123,15 @@ class Account:
125
123
  with that e-mail address, it is unblocked and its display
126
124
  name is updated if specified.
127
125
 
128
- :param obj: email-address or contact id.
126
+ :param obj: email-address, contact id or account.
129
127
  :param name: (optional) display name for this contact.
130
128
  """
129
+ if isinstance(obj, Account):
130
+ vcard = obj.self_contact.make_vcard()
131
+ [contact] = self.import_vcard(vcard)
132
+ if name:
133
+ contact.set_name(name)
134
+ return contact
131
135
  if isinstance(obj, int):
132
136
  obj = Contact(self, obj)
133
137
  if isinstance(obj, Contact):
@@ -148,9 +152,8 @@ class Account:
148
152
  return [Contact(self, contact_id) for contact_id in contact_ids]
149
153
 
150
154
  def create_chat(self, account: "Account") -> Chat:
151
- vcard = account.self_contact.make_vcard()
152
- [contact] = self.import_vcard(vcard)
153
- return contact.create_chat()
155
+ """Create a 1:1 chat with another account."""
156
+ return self.create_contact(account).create_chat()
154
157
 
155
158
  def get_device_chat(self) -> Chat:
156
159
  """Return device chat."""
@@ -334,24 +337,15 @@ class Account:
334
337
 
335
338
  def wait_for_incoming_msg_event(self):
336
339
  """Wait for incoming message event and return it."""
337
- while True:
338
- event = self.wait_for_event()
339
- if event.kind == EventType.INCOMING_MSG:
340
- return event
340
+ return self.wait_for_event(EventType.INCOMING_MSG)
341
341
 
342
342
  def wait_for_msgs_changed_event(self):
343
343
  """Wait for messages changed event and return it."""
344
- while True:
345
- event = self.wait_for_event()
346
- if event.kind == EventType.MSGS_CHANGED:
347
- return event
344
+ return self.wait_for_event(EventType.MSGS_CHANGED)
348
345
 
349
346
  def wait_for_msgs_noticed_event(self):
350
347
  """Wait for messages noticed event and return it."""
351
- while True:
352
- event = self.wait_for_event()
353
- if event.kind == EventType.MSGS_NOTICED:
354
- return event
348
+ return self.wait_for_event(EventType.MSGS_NOTICED)
355
349
 
356
350
  def wait_for_incoming_msg(self):
357
351
  """Wait for incoming message and return it.
@@ -372,10 +366,7 @@ class Account:
372
366
  break
373
367
 
374
368
  def wait_for_reactions_changed(self):
375
- while True:
376
- event = self.wait_for_event()
377
- if event.kind == EventType.REACTIONS_CHANGED:
378
- return event
369
+ return self.wait_for_event(EventType.REACTIONS_CHANGED)
379
370
 
380
371
  def get_fresh_messages_in_arrival_order(self) -> list[Message]:
381
372
  """Return fresh messages list sorted in the order of their arrival, with ascending IDs."""
@@ -4,6 +4,7 @@ import os
4
4
  import random
5
5
  from typing import AsyncGenerator, Optional
6
6
 
7
+ import py
7
8
  import pytest
8
9
 
9
10
  from . import Account, AttrDict, Bot, Chat, Client, DeltaChat, EventType, Message
@@ -11,14 +12,6 @@ from ._utils import futuremethod
11
12
  from .rpc import Rpc
12
13
 
13
14
 
14
- def get_temp_credentials() -> dict:
15
- domain = os.getenv("CHATMAIL_DOMAIN")
16
- username = "ci-" + "".join(random.choice("2345789acdefghjkmnpqrstuvwxyz") for i in range(6))
17
- password = f"{username}${username}"
18
- addr = f"{username}@{domain}"
19
- return {"email": addr, "password": password}
20
-
21
-
22
15
  class ACFactory:
23
16
  def __init__(self, deltachat: DeltaChat) -> None:
24
17
  self.deltachat = deltachat
@@ -31,26 +24,25 @@ class ACFactory:
31
24
  def get_unconfigured_bot(self) -> Bot:
32
25
  return Bot(self.get_unconfigured_account())
33
26
 
34
- def new_preconfigured_account(self) -> Account:
35
- """Make a new account with configuration options set, but configuration not started."""
36
- credentials = get_temp_credentials()
37
- account = self.get_unconfigured_account()
38
- account.set_config("addr", credentials["email"])
39
- account.set_config("mail_pw", credentials["password"])
40
- assert not account.is_configured()
41
- return account
27
+ def get_credentials(self) -> (str, str):
28
+ domain = os.getenv("CHATMAIL_DOMAIN")
29
+ username = "ci-" + "".join(random.choice("2345789acdefghjkmnpqrstuvwxyz") for i in range(6))
30
+ return f"{username}@{domain}", f"{username}${username}"
42
31
 
43
32
  @futuremethod
44
33
  def new_configured_account(self):
45
- account = self.new_preconfigured_account()
46
- yield account.configure.future()
34
+ addr, password = self.get_credentials()
35
+ account = self.get_unconfigured_account()
36
+ params = {"addr": addr, "password": password}
37
+ yield account._rpc.add_transport.future(account.id, params)
38
+
47
39
  assert account.is_configured()
48
40
  return account
49
41
 
50
42
  def new_configured_bot(self) -> Bot:
51
- credentials = get_temp_credentials()
43
+ addr, password = self.get_credentials()
52
44
  bot = self.get_unconfigured_bot()
53
- bot.configure(credentials["email"], credentials["password"])
45
+ bot.configure(addr, password)
54
46
  return bot
55
47
 
56
48
  @futuremethod
@@ -124,3 +116,50 @@ def rpc(tmp_path) -> AsyncGenerator:
124
116
  @pytest.fixture
125
117
  def acfactory(rpc) -> AsyncGenerator:
126
118
  return ACFactory(DeltaChat(rpc))
119
+
120
+
121
+ @pytest.fixture
122
+ def data():
123
+ """Test data."""
124
+
125
+ class Data:
126
+ def __init__(self) -> None:
127
+ for path in reversed(py.path.local(__file__).parts()):
128
+ datadir = path.join("test-data")
129
+ if datadir.isdir():
130
+ self.path = datadir
131
+ return
132
+ raise Exception("Data path cannot be found")
133
+
134
+ def get_path(self, bn):
135
+ """return path of file or None if it doesn't exist."""
136
+ fn = os.path.join(self.path, *bn.split("/"))
137
+ assert os.path.exists(fn)
138
+ return fn
139
+
140
+ def read_path(self, bn, mode="r"):
141
+ fn = self.get_path(bn)
142
+ if fn is not None:
143
+ with open(fn, mode) as f:
144
+ return f.read()
145
+ return None
146
+
147
+ return Data()
148
+
149
+
150
+ @pytest.fixture
151
+ def log():
152
+ """Log printer fixture."""
153
+
154
+ class Printer:
155
+ def section(self, msg: str) -> None:
156
+ print()
157
+ print("=" * 10, msg, "=" * 10)
158
+
159
+ def step(self, msg: str) -> None:
160
+ print("-" * 5, "step " + msg, "-" * 5)
161
+
162
+ def indent(self, msg: str) -> None:
163
+ print(" " + msg)
164
+
165
+ return Printer()
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: deltachat-rpc-client
3
- Version: 1.157.2
3
+ Version: 1.158.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
@@ -19,6 +19,7 @@ Classifier: Topic :: Communications :: Email
19
19
  Requires-Python: >=3.8
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
+ Dynamic: license-file
22
23
 
23
24
  # Delta Chat RPC python client
24
25
 
@@ -13,10 +13,11 @@ def test_event_on_configuration(acfactory: ACFactory) -> None:
13
13
  Test if ACCOUNTS_ITEM_CHANGED event is emitted on configure
14
14
  """
15
15
 
16
- account = acfactory.new_preconfigured_account()
16
+ addr, password = acfactory.get_credentials()
17
+ account = acfactory.get_unconfigured_account()
17
18
  account.clear_all_events()
18
19
  assert not account.is_configured()
19
- future = account.configure.future()
20
+ future = account._rpc.add_transport.future(account.id, {"addr": addr, "password": password})
20
21
  while True:
21
22
  event = account.wait_for_event()
22
23
  if event.kind == EventType.ACCOUNTS_ITEM_CHANGED:
@@ -48,8 +48,7 @@ def test_delivery_status(acfactory: ACFactory) -> None:
48
48
  """
49
49
  alice, bob = acfactory.get_online_accounts(2)
50
50
 
51
- bob_addr = bob.get_config("addr")
52
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
51
+ alice_contact_bob = alice.create_contact(bob, "Bob")
53
52
  alice_chat_bob = alice_contact_bob.create_chat()
54
53
 
55
54
  alice.clear_all_events()
@@ -119,8 +118,7 @@ def test_download_on_demand(acfactory: ACFactory) -> None:
119
118
  """
120
119
  alice, bob = acfactory.get_online_accounts(2)
121
120
 
122
- bob_addr = bob.get_config("addr")
123
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
121
+ alice_contact_bob = alice.create_contact(bob, "Bob")
124
122
  alice_chat_bob = alice_contact_bob.create_chat()
125
123
  alice_chat_bob.send_text("hi")
126
124
 
@@ -150,8 +148,7 @@ def test_download_on_demand(acfactory: ACFactory) -> None:
150
148
  def get_multi_account_test_setup(acfactory: ACFactory) -> [Account, Account, Account]:
151
149
  alice, bob = acfactory.get_online_accounts(2)
152
150
 
153
- bob_addr = bob.get_config("addr")
154
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
151
+ alice_contact_bob = alice.create_contact(bob, "Bob")
155
152
  alice_chat_bob = alice_contact_bob.create_chat()
156
153
  alice_chat_bob.send_text("hi")
157
154
 
@@ -175,17 +175,11 @@ def test_no_duplicate_messages(acfactory, path_to_webxdc):
175
175
 
176
176
  threading.Thread(target=thread_run, daemon=True).start()
177
177
 
178
- while 1:
179
- event = ac2.wait_for_event()
180
- if event.kind == EventType.WEBXDC_REALTIME_DATA:
181
- n = int(bytes(event.data).decode())
182
- break
178
+ event = ac2.wait_for_event(EventType.WEBXDC_REALTIME_DATA)
179
+ n = int(bytes(event.data).decode())
183
180
 
184
- while 1:
185
- event = ac2.wait_for_event()
186
- if event.kind == EventType.WEBXDC_REALTIME_DATA:
187
- assert int(bytes(event.data).decode()) > n
188
- break
181
+ event = ac2.wait_for_event(EventType.WEBXDC_REALTIME_DATA)
182
+ assert int(bytes(event.data).decode()) > n
189
183
 
190
184
 
191
185
  def test_no_reordering(acfactory, path_to_webxdc):
@@ -229,8 +223,5 @@ def test_advertisement_after_chatting(acfactory, path_to_webxdc):
229
223
  ac2_hello_msg_snapshot.chat.accept()
230
224
 
231
225
  ac2_webxdc_msg.send_webxdc_realtime_advertisement()
232
- while 1:
233
- event = ac1.wait_for_event()
234
- if event.kind == EventType.WEBXDC_REALTIME_ADVERTISEMENT_RECEIVED:
235
- assert event.msg_id == ac1_webxdc_msg.id
236
- break
226
+ event = ac1.wait_for_event(EventType.WEBXDC_REALTIME_ADVERTISEMENT_RECEIVED)
227
+ assert event.msg_id == ac1_webxdc_msg.id
@@ -21,6 +21,7 @@ def test_autocrypt_setup_message_key_transfer(acfactory):
21
21
  alice2.set_config("addr", alice1.get_config("addr"))
22
22
  alice2.set_config("mail_pw", alice1.get_config("mail_pw"))
23
23
  alice2.configure()
24
+ alice2.bring_online()
24
25
 
25
26
  setup_code = alice1.initiate_autocrypt_key_transfer()
26
27
  msg = wait_for_autocrypt_setup_message(alice2)
@@ -34,10 +35,12 @@ def test_autocrypt_setup_message_key_transfer(acfactory):
34
35
 
35
36
  def test_ac_setup_message_twice(acfactory):
36
37
  alice1 = acfactory.get_online_account()
38
+
37
39
  alice2 = acfactory.get_unconfigured_account()
38
40
  alice2.set_config("addr", alice1.get_config("addr"))
39
41
  alice2.set_config("mail_pw", alice1.get_config("mail_pw"))
40
42
  alice2.configure()
43
+ alice2.bring_online()
41
44
 
42
45
  # Send the first Autocrypt Setup Message and ignore it.
43
46
  _setup_code = alice1.initiate_autocrypt_key_transfer()
@@ -76,17 +76,11 @@ def test_qr_securejoin(acfactory, protect):
76
76
  bob.secure_join(qr_code)
77
77
 
78
78
  # Alice deletes "vg-request".
79
- while True:
80
- event = alice.wait_for_event()
81
- if event["kind"] == "ImapMessageDeleted":
82
- break
79
+ alice.wait_for_event(EventType.IMAP_MESSAGE_DELETED)
83
80
  alice.wait_for_securejoin_inviter_success()
84
81
  # Bob deletes "vg-auth-required", Alice deletes "vg-request-with-auth".
85
82
  for ac in [alice, bob]:
86
- while True:
87
- event = ac.wait_for_event()
88
- if event["kind"] == "ImapMessageDeleted":
89
- break
83
+ ac.wait_for_event(EventType.IMAP_MESSAGE_DELETED)
90
84
  bob.wait_for_securejoin_joiner_success()
91
85
 
92
86
  # Test that Alice verified Bob's profile.
@@ -123,8 +117,7 @@ def test_qr_securejoin_contact_request(acfactory) -> None:
123
117
  """Alice invites Bob to a group when Bob's chat with Alice is in a contact request mode."""
124
118
  alice, bob = acfactory.get_online_accounts(2)
125
119
 
126
- bob_addr = bob.get_config("addr")
127
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
120
+ alice_contact_bob = alice.create_contact(bob, "Bob")
128
121
  alice_chat_bob = alice_contact_bob.create_chat()
129
122
  alice_chat_bob.send_text("Hello!")
130
123
 
@@ -161,11 +154,8 @@ def test_qr_readreceipt(acfactory) -> None:
161
154
  logging.info("Alice creates a verified group")
162
155
  group = alice.create_group("Group", protect=True)
163
156
 
164
- bob_addr = bob.get_config("addr")
165
- charlie_addr = charlie.get_config("addr")
166
-
167
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
168
- alice_contact_charlie = alice.create_contact(charlie_addr, "Charlie")
157
+ alice_contact_bob = alice.create_contact(bob, "Bob")
158
+ alice_contact_charlie = alice.create_contact(charlie, "Charlie")
169
159
 
170
160
  group.add_contact(alice_contact_bob)
171
161
  group.add_contact(alice_contact_charlie)
@@ -192,7 +182,7 @@ def test_qr_readreceipt(acfactory) -> None:
192
182
  charlie_snapshot = charlie_message.get_snapshot()
193
183
  assert charlie_snapshot.text == "Hi from Bob!"
194
184
 
195
- bob_contact_charlie = bob.create_contact(charlie_addr, "Charlie")
185
+ bob_contact_charlie = bob.create_contact(charlie, "Charlie")
196
186
  assert not bob.get_chat_by_contact(bob_contact_charlie)
197
187
 
198
188
  logging.info("Charlie reads Bob's message")
@@ -463,12 +453,12 @@ def test_qr_new_group_unblocked(acfactory):
463
453
  assert ac2_msg.chat.get_basic_snapshot().is_contact_request
464
454
 
465
455
 
456
+ @pytest.mark.skip(reason="AEAP is disabled for now")
466
457
  def test_aeap_flow_verified(acfactory):
467
458
  """Test that a new address is added to a contact when it changes its address."""
468
459
  ac1, ac2 = acfactory.get_online_accounts(2)
469
460
 
470
- # ac1new is only used to get a new address.
471
- ac1new = acfactory.new_preconfigured_account()
461
+ addr, password = acfactory.get_credentials()
472
462
 
473
463
  logging.info("ac1: create verified-group QR, ac2 scans and joins")
474
464
  chat = ac1.create_group("hello", protect=True)
@@ -488,8 +478,8 @@ def test_aeap_flow_verified(acfactory):
488
478
  assert msg_in_1.text == msg_out.text
489
479
 
490
480
  logging.info("changing email account")
491
- ac1.set_config("addr", ac1new.get_config("addr"))
492
- ac1.set_config("mail_pw", ac1new.get_config("mail_pw"))
481
+ ac1.set_config("addr", addr)
482
+ ac1.set_config("mail_pw", password)
493
483
  ac1.stop_io()
494
484
  ac1.configure()
495
485
  ac1.start_io()
@@ -502,11 +492,9 @@ def test_aeap_flow_verified(acfactory):
502
492
  msg_in_2_snapshot = msg_in_2.get_snapshot()
503
493
  assert msg_in_2_snapshot.text == msg_out.text
504
494
  assert msg_in_2_snapshot.chat.id == msg_in_1.chat.id
505
- assert msg_in_2.get_sender_contact().get_snapshot().address == ac1new.get_config("addr")
495
+ assert msg_in_2.get_sender_contact().get_snapshot().address == addr
506
496
  assert len(msg_in_2_snapshot.chat.get_contacts()) == 2
507
- assert ac1new.get_config("addr") in [
508
- contact.get_snapshot().address for contact in msg_in_2_snapshot.chat.get_contacts()
509
- ]
497
+ assert addr in [contact.get_snapshot().address for contact in msg_in_2_snapshot.chat.get_contacts()]
510
498
 
511
499
 
512
500
  def test_gossip_verification(acfactory) -> None:
@@ -522,9 +510,9 @@ def test_gossip_verification(acfactory) -> None:
522
510
  bob.secure_join(qr_code)
523
511
  bob.wait_for_securejoin_joiner_success()
524
512
 
525
- bob_contact_alice = bob.create_contact(alice.get_config("addr"), "Alice")
526
- bob_contact_carol = bob.create_contact(carol.get_config("addr"), "Carol")
527
- carol_contact_alice = carol.create_contact(alice.get_config("addr"), "Alice")
513
+ bob_contact_alice = bob.create_contact(alice, "Alice")
514
+ bob_contact_carol = bob.create_contact(carol, "Carol")
515
+ carol_contact_alice = carol.create_contact(alice, "Alice")
528
516
 
529
517
  logging.info("Bob creates an Autocrypt group")
530
518
  bob_group_chat = bob.create_group("Autocrypt Group")
@@ -584,7 +572,7 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
584
572
  ac2.wait_for_securejoin_joiner_success()
585
573
 
586
574
  # ac1 is verified for ac2.
587
- ac2_contact_ac1 = ac2.create_contact(ac1.get_config("addr"), "")
575
+ ac2_contact_ac1 = ac2.create_contact(ac1, "")
588
576
  assert ac2_contact_ac1.get_snapshot().is_verified
589
577
 
590
578
  # ac1 resetups the account.
@@ -599,7 +587,7 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
599
587
  # header sent by old ac1.
600
588
  while True:
601
589
  # ac1 sends a message to ac2.
602
- ac1_contact_ac2 = ac1.create_contact(ac2.get_config("addr"), "")
590
+ ac1_contact_ac2 = ac1.create_contact(ac2, "")
603
591
  ac1_chat_ac2 = ac1_contact_ac2.create_chat()
604
592
  ac1_chat_ac2.send_text("Hello!")
605
593
 
@@ -61,61 +61,83 @@ def test_acfactory(acfactory) -> None:
61
61
 
62
62
 
63
63
  def test_configure_starttls(acfactory) -> None:
64
- account = acfactory.new_preconfigured_account()
65
-
66
- # Use STARTTLS
67
- account.set_config("mail_security", "2")
68
- account.set_config("send_security", "2")
69
- account.configure()
64
+ addr, password = acfactory.get_credentials()
65
+ account = acfactory.get_unconfigured_account()
66
+ account._rpc.add_transport(
67
+ account.id,
68
+ {
69
+ "addr": addr,
70
+ "password": password,
71
+ "imapSecurity": "starttls",
72
+ "smtpSecurity": "starttls",
73
+ },
74
+ )
70
75
  assert account.is_configured()
71
76
 
72
77
 
73
78
  def test_configure_ip(acfactory) -> None:
74
- account = acfactory.new_preconfigured_account()
75
-
76
- domain = account.get_config("addr").rsplit("@")[-1]
77
- ip_address = socket.gethostbyname(domain)
79
+ addr, password = acfactory.get_credentials()
80
+ account = acfactory.get_unconfigured_account()
81
+ ip_address = socket.gethostbyname(addr.rsplit("@")[-1])
78
82
 
79
- # This should fail TLS check.
80
- account.set_config("mail_server", ip_address)
81
83
  with pytest.raises(JsonRpcError):
82
- account.configure()
84
+ account._rpc.add_transport(
85
+ account.id,
86
+ {
87
+ "addr": addr,
88
+ "password": password,
89
+ # This should fail TLS check.
90
+ "imapServer": ip_address,
91
+ },
92
+ )
83
93
 
84
94
 
85
95
  def test_configure_alternative_port(acfactory) -> None:
86
96
  """Test that configuration with alternative port 443 works."""
87
- account = acfactory.new_preconfigured_account()
88
-
89
- account.set_config("mail_port", "443")
90
- account.set_config("send_port", "443")
91
-
92
- account.configure()
93
-
94
-
95
- def test_configure_username(acfactory) -> None:
96
- account = acfactory.new_preconfigured_account()
97
+ addr, password = acfactory.get_credentials()
98
+ account = acfactory.get_unconfigured_account()
99
+ account._rpc.add_transport(
100
+ account.id,
101
+ {
102
+ "addr": addr,
103
+ "password": password,
104
+ "imapPort": 443,
105
+ "smtpPort": 443,
106
+ },
107
+ )
108
+ assert account.is_configured()
97
109
 
98
- addr = account.get_config("addr")
99
- account.set_config("mail_user", addr)
100
- account.configure()
101
110
 
102
- assert account.get_config("configured_mail_user") == addr
111
+ def test_list_transports(acfactory) -> None:
112
+ addr, password = acfactory.get_credentials()
113
+ account = acfactory.get_unconfigured_account()
114
+ account._rpc.add_transport(
115
+ account.id,
116
+ {
117
+ "addr": addr,
118
+ "password": password,
119
+ "imapUser": addr,
120
+ },
121
+ )
122
+ transports = account._rpc.list_transports(account.id)
123
+ assert len(transports) == 1
124
+ params = transports[0]
125
+ assert params["addr"] == addr
126
+ assert params["password"] == password
127
+ assert params["imapUser"] == addr
103
128
 
104
129
 
105
130
  def test_account(acfactory) -> None:
106
131
  alice, bob = acfactory.get_online_accounts(2)
107
132
 
108
133
  bob_addr = bob.get_config("addr")
109
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
134
+ alice_contact_bob = alice.create_contact(bob, "Bob")
110
135
  alice_chat_bob = alice_contact_bob.create_chat()
111
136
  alice_chat_bob.send_text("Hello!")
112
137
 
113
- while True:
114
- event = bob.wait_for_event()
115
- if event.kind == EventType.INCOMING_MSG:
116
- chat_id = event.chat_id
117
- msg_id = event.msg_id
118
- break
138
+ event = bob.wait_for_incoming_msg_event()
139
+ chat_id = event.chat_id
140
+ msg_id = event.msg_id
119
141
 
120
142
  message = bob.get_message_by_id(msg_id)
121
143
  snapshot = message.get_snapshot()
@@ -174,8 +196,7 @@ def test_account(acfactory) -> None:
174
196
  def test_chat(acfactory) -> None:
175
197
  alice, bob = acfactory.get_online_accounts(2)
176
198
 
177
- bob_addr = bob.get_config("addr")
178
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
199
+ alice_contact_bob = alice.create_contact(bob, "Bob")
179
200
  alice_chat_bob = alice_contact_bob.create_chat()
180
201
  alice_chat_bob.send_text("Hello!")
181
202
 
@@ -241,7 +262,7 @@ def test_contact(acfactory) -> None:
241
262
  alice, bob = acfactory.get_online_accounts(2)
242
263
 
243
264
  bob_addr = bob.get_config("addr")
244
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
265
+ alice_contact_bob = alice.create_contact(bob, "Bob")
245
266
 
246
267
  assert alice_contact_bob == alice.get_contact_by_id(alice_contact_bob.id)
247
268
  assert repr(alice_contact_bob)
@@ -258,8 +279,7 @@ def test_contact(acfactory) -> None:
258
279
  def test_message(acfactory) -> None:
259
280
  alice, bob = acfactory.get_online_accounts(2)
260
281
 
261
- bob_addr = bob.get_config("addr")
262
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
282
+ alice_contact_bob = alice.create_contact(bob, "Bob")
263
283
  alice_chat_bob = alice_contact_bob.create_chat()
264
284
  alice_chat_bob.send_text("Hello!")
265
285
 
@@ -287,13 +307,37 @@ def test_message(acfactory) -> None:
287
307
  assert reactions == snapshot.reactions
288
308
 
289
309
 
310
+ def test_selfavatar_sync(acfactory, data, log) -> None:
311
+ alice = acfactory.get_online_account()
312
+
313
+ log.section("Alice adds a second device")
314
+ alice2 = alice.clone()
315
+
316
+ log.section("Second device goes online")
317
+ alice2.start_io()
318
+
319
+ log.section("First device changes avatar")
320
+ image = data.get_path("image/avatar1000x1000.jpg")
321
+ alice.set_config("selfavatar", image)
322
+ avatar_config = alice.get_config("selfavatar")
323
+ avatar_hash = os.path.basename(avatar_config)
324
+ print("Info: avatar hash is ", avatar_hash)
325
+
326
+ log.section("First device receives avatar change")
327
+ alice2.wait_for_event(EventType.SELFAVATAR_CHANGED)
328
+ avatar_config2 = alice2.get_config("selfavatar")
329
+ avatar_hash2 = os.path.basename(avatar_config2)
330
+ print("Info: avatar hash on second device is ", avatar_hash2)
331
+ assert avatar_hash == avatar_hash2
332
+ assert avatar_config != avatar_config2
333
+
334
+
290
335
  def test_reaction_seen_on_another_dev(acfactory) -> None:
291
336
  alice, bob = acfactory.get_online_accounts(2)
292
337
  alice2 = alice.clone()
293
338
  alice2.start_io()
294
339
 
295
- bob_addr = bob.get_config("addr")
296
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
340
+ alice_contact_bob = alice.create_contact(bob, "Bob")
297
341
  alice_chat_bob = alice_contact_bob.create_chat()
298
342
  alice_chat_bob.send_text("Hello!")
299
343
 
@@ -305,20 +349,12 @@ def test_reaction_seen_on_another_dev(acfactory) -> None:
305
349
  snapshot.chat.accept()
306
350
  message.send_reaction("😎")
307
351
  for a in [alice, alice2]:
308
- while True:
309
- event = a.wait_for_event()
310
- if event.kind == EventType.INCOMING_REACTION:
311
- break
352
+ a.wait_for_event(EventType.INCOMING_REACTION)
312
353
 
313
354
  alice2.clear_all_events()
314
355
  alice_chat_bob.mark_noticed()
315
- while True:
316
- event = alice2.wait_for_event()
317
- if event.kind == EventType.MSGS_NOTICED:
318
- chat_id = event.chat_id
319
- break
320
- alice2_contact_bob = alice2.get_contact_by_addr(bob_addr)
321
- alice2_chat_bob = alice2_contact_bob.create_chat()
356
+ chat_id = alice2.wait_for_event(EventType.MSGS_NOTICED).chat_id
357
+ alice2_chat_bob = alice2.create_chat(bob)
322
358
  assert chat_id == alice2_chat_bob.id
323
359
 
324
360
 
@@ -326,24 +362,19 @@ def test_is_bot(acfactory) -> None:
326
362
  """Test that we can recognize messages submitted by bots."""
327
363
  alice, bob = acfactory.get_online_accounts(2)
328
364
 
329
- bob_addr = bob.get_config("addr")
330
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
365
+ alice_contact_bob = alice.create_contact(bob, "Bob")
331
366
  alice_chat_bob = alice_contact_bob.create_chat()
332
367
 
333
368
  # Alice becomes a bot.
334
369
  alice.set_config("bot", "1")
335
370
  alice_chat_bob.send_text("Hello!")
336
371
 
337
- while True:
338
- event = bob.wait_for_event()
339
- if event.kind == EventType.INCOMING_MSG:
340
- msg_id = event.msg_id
341
- message = bob.get_message_by_id(msg_id)
342
- snapshot = message.get_snapshot()
343
- assert snapshot.chat_id == event.chat_id
344
- assert snapshot.text == "Hello!"
345
- assert snapshot.is_bot
346
- break
372
+ event = bob.wait_for_incoming_msg_event()
373
+ message = bob.get_message_by_id(event.msg_id)
374
+ snapshot = message.get_snapshot()
375
+ assert snapshot.chat_id == event.chat_id
376
+ assert snapshot.text == "Hello!"
377
+ assert snapshot.is_bot
347
378
 
348
379
 
349
380
  def test_bot(acfactory) -> None:
@@ -390,9 +421,11 @@ def test_wait_next_messages(acfactory) -> None:
390
421
  alice = acfactory.new_configured_account()
391
422
 
392
423
  # Create a bot account so it does not receive device messages in the beginning.
393
- bot = acfactory.new_preconfigured_account()
424
+ addr, password = acfactory.get_credentials()
425
+ bot = acfactory.get_unconfigured_account()
394
426
  bot.set_config("bot", "1")
395
- bot.configure()
427
+ bot._rpc.add_transport(bot.id, {"addr": addr, "password": password})
428
+ assert bot.is_configured()
396
429
 
397
430
  # There are no old messages and the call returns immediately.
398
431
  assert not bot.wait_next_messages()
@@ -401,8 +434,7 @@ def test_wait_next_messages(acfactory) -> None:
401
434
  # Bot starts waiting for messages.
402
435
  next_messages_task = executor.submit(bot.wait_next_messages)
403
436
 
404
- bot_addr = bot.get_config("addr")
405
- alice_contact_bot = alice.create_contact(bot_addr, "Bot")
437
+ alice_contact_bot = alice.create_contact(bot, "Bot")
406
438
  alice_chat_bot = alice_contact_bot.create_chat()
407
439
  alice_chat_bot.send_text("Hello!")
408
440
 
@@ -426,9 +458,7 @@ def test_import_export_backup(acfactory, tmp_path) -> None:
426
458
  def test_import_export_keys(acfactory, tmp_path) -> None:
427
459
  alice, bob = acfactory.get_online_accounts(2)
428
460
 
429
- bob_addr = bob.get_config("addr")
430
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
431
- alice_chat_bob = alice_contact_bob.create_chat()
461
+ alice_chat_bob = alice.create_chat(bob)
432
462
  alice_chat_bob.send_text("Hello Bob!")
433
463
 
434
464
  snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
@@ -478,9 +508,7 @@ def test_provider_info(rpc) -> None:
478
508
  def test_mdn_doesnt_break_autocrypt(acfactory) -> None:
479
509
  alice, bob = acfactory.get_online_accounts(2)
480
510
 
481
- bob_addr = bob.get_config("addr")
482
-
483
- alice_contact_bob = alice.create_contact(bob_addr, "Bob")
511
+ alice_contact_bob = alice.create_contact(bob, "Bob")
484
512
 
485
513
  # Bob creates chat manually so chat with Alice is accepted.
486
514
  alice_chat_bob = alice_contact_bob.create_chat()
@@ -504,10 +532,7 @@ def test_mdn_doesnt_break_autocrypt(acfactory) -> None:
504
532
 
505
533
  # Alice reads Bob's message.
506
534
  message.mark_seen()
507
- while True:
508
- event = bob.wait_for_event()
509
- if event.kind == EventType.MSG_READ:
510
- break
535
+ bob.wait_for_event(EventType.MSG_READ)
511
536
 
512
537
  # Bob sends a message to Alice, it should also be encrypted.
513
538
  bob_chat_alice.send_text("Hi Alice!")
@@ -579,9 +604,13 @@ def test_reactions_for_a_reordering_move(acfactory, direct_imap):
579
604
  messages they refer to and thus dropped.
580
605
  """
581
606
  (ac1,) = acfactory.get_online_accounts(1)
582
- ac2 = acfactory.new_preconfigured_account()
583
- ac2.configure()
607
+
608
+ addr, password = acfactory.get_credentials()
609
+ ac2 = acfactory.get_unconfigured_account()
610
+ ac2._rpc.add_transport(ac2.id, {"addr": addr, "password": password})
584
611
  ac2.set_config("mvbox_move", "1")
612
+ assert ac2.is_configured()
613
+
585
614
  ac2.bring_online()
586
615
  chat1 = acfactory.get_accepted_chat(ac1, ac2)
587
616
  ac2.stop_io()
@@ -625,9 +654,7 @@ def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
625
654
  chat.send_text("Hello Alice!")
626
655
  assert alice.get_message_by_id(alice.wait_for_incoming_msg_event().msg_id).get_snapshot().text == "Hello Alice!"
627
656
 
628
- contact_addr = account.get_config("addr")
629
- contact = alice.create_contact(contact_addr, "")
630
-
657
+ contact = alice.create_contact(account)
631
658
  alice_group.add_contact(contact)
632
659
 
633
660
  if n_accounts == 2:
@@ -677,10 +704,7 @@ def test_markseen_contact_request(acfactory):
677
704
  assert message2.get_snapshot().state == MessageState.IN_FRESH
678
705
 
679
706
  message.mark_seen()
680
- while True:
681
- event = bob2.wait_for_event()
682
- if event.kind == EventType.MSGS_NOTICED:
683
- break
707
+ bob2.wait_for_event(EventType.MSGS_NOTICED)
684
708
  assert message2.get_snapshot().state == MessageState.IN_SEEN
685
709
 
686
710
 
@@ -728,6 +752,8 @@ def test_no_old_msg_is_fresh(acfactory):
728
752
  assert ac1.create_chat(ac2).get_fresh_message_count() == 1
729
753
  assert len(list(ac1.get_fresh_messages())) == 1
730
754
 
755
+ ac1.wait_for_event(EventType.IMAP_INBOX_IDLE)
756
+
731
757
  logging.info("Send a message from ac1_clone to ac2 and check that ac1 marks the first message as 'noticed'")
732
758
  ac1_clone_chat.send_text("Hi back")
733
759
  ev = ac1.wait_for_msgs_noticed_event()
@@ -735,3 +761,39 @@ def test_no_old_msg_is_fresh(acfactory):
735
761
  assert ev.chat_id == first_msg.get_snapshot().chat_id
736
762
  assert ac1.create_chat(ac2).get_fresh_message_count() == 0
737
763
  assert len(list(ac1.get_fresh_messages())) == 0
764
+
765
+
766
+ def test_rename_synchronization(acfactory):
767
+ """Test synchronization of contact renaming."""
768
+ alice, bob = acfactory.get_online_accounts(2)
769
+ alice2 = alice.clone()
770
+ alice2.bring_online()
771
+
772
+ bob.set_config("displayname", "Bob")
773
+ bob.create_chat(alice).send_text("Hello!")
774
+ alice_msg = alice.wait_for_incoming_msg().get_snapshot()
775
+ alice2_msg = alice2.wait_for_incoming_msg().get_snapshot()
776
+
777
+ assert alice2_msg.sender.get_snapshot().display_name == "Bob"
778
+ alice_msg.sender.set_name("Bobby")
779
+ alice2.wait_for_event(EventType.CONTACTS_CHANGED)
780
+ assert alice2_msg.sender.get_snapshot().display_name == "Bobby"
781
+
782
+
783
+ def test_rename_group(acfactory):
784
+ """Test renaming the group."""
785
+ alice, bob = acfactory.get_online_accounts(2)
786
+
787
+ alice_group = alice.create_group("Test group")
788
+ alice_contact_bob = alice.create_contact(bob)
789
+ alice_group.add_contact(alice_contact_bob)
790
+ alice_group.send_text("Hello!")
791
+
792
+ bob_msg = bob.wait_for_incoming_msg()
793
+ bob_chat = bob_msg.get_snapshot().chat
794
+ assert bob_chat.get_basic_snapshot().name == "Test group"
795
+
796
+ for name in ["Baz", "Foo bar", "Xyzzy"]:
797
+ alice_group.set_name(name)
798
+ bob.wait_for_incoming_msg_event()
799
+ assert bob_chat.get_basic_snapshot().name == name
@@ -1,6 +1,3 @@
1
- from deltachat_rpc_client import EventType
2
-
3
-
4
1
  def test_webxdc(acfactory) -> None:
5
2
  alice, bob = acfactory.get_online_accounts(2)
6
3
 
@@ -9,12 +6,9 @@ def test_webxdc(acfactory) -> None:
9
6
  alice_chat_bob = alice_contact_bob.create_chat()
10
7
  alice_chat_bob.send_message(text="Let's play chess!", file="../test-data/webxdc/chess.xdc")
11
8
 
12
- while True:
13
- event = bob.wait_for_event()
14
- if event.kind == EventType.INCOMING_MSG:
15
- bob_chat_alice = bob.get_chat_by_id(event.chat_id)
16
- message = bob.get_message_by_id(event.msg_id)
17
- break
9
+ event = bob.wait_for_incoming_msg_event()
10
+ bob_chat_alice = bob.get_chat_by_id(event.chat_id)
11
+ message = bob.get_message_by_id(event.msg_id)
18
12
 
19
13
  webxdc_info = message.get_webxdc_info()
20
14
  assert webxdc_info == {