deltachat-rpc-client 2.28.0__tar.gz → 2.30.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 (36) hide show
  1. {deltachat_rpc_client-2.28.0/src/deltachat_rpc_client.egg-info → deltachat_rpc_client-2.30.0}/PKG-INFO +12 -5
  2. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/README.md +9 -0
  3. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/pyproject.toml +4 -6
  4. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/const.py +1 -0
  5. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/message.py +4 -0
  6. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/pytestplugin.py +148 -0
  7. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/rpc.py +24 -49
  8. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0/src/deltachat_rpc_client.egg-info}/PKG-INFO +12 -5
  9. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client.egg-info/SOURCES.txt +1 -0
  10. deltachat_rpc_client-2.30.0/tests/test_cross_core.py +44 -0
  11. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_multitransport.py +45 -0
  12. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_something.py +20 -11
  13. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/LICENSE +0 -0
  14. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/setup.cfg +0 -0
  15. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/__init__.py +0 -0
  16. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/_utils.py +0 -0
  17. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/account.py +0 -0
  18. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/chat.py +0 -0
  19. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/client.py +0 -0
  20. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/contact.py +0 -0
  21. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/deltachat.py +0 -0
  22. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/events.py +0 -0
  23. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client/py.typed +0 -0
  24. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client.egg-info/dependency_links.txt +0 -0
  25. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client.egg-info/entry_points.txt +0 -0
  26. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/src/deltachat_rpc_client.egg-info/top_level.txt +0 -0
  27. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_account_events.py +0 -0
  28. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_calls.py +0 -0
  29. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_chatlist_events.py +0 -0
  30. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_folders.py +0 -0
  31. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_iroh_webxdc.py +0 -0
  32. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_key_transfer.py +0 -0
  33. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_multidevice.py +0 -0
  34. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_securejoin.py +0 -0
  35. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_vcard.py +0 -0
  36. {deltachat_rpc_client-2.28.0 → deltachat_rpc_client-2.30.0}/tests/test_webxdc.py +0 -0
@@ -1,15 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deltachat-rpc-client
3
- Version: 2.28.0
3
+ Version: 2.30.0
4
4
  Summary: Python client for Delta Chat core JSON-RPC interface
5
+ License-Expression: MPL-2.0
5
6
  Classifier: Development Status :: 5 - Production/Stable
6
7
  Classifier: Intended Audience :: Developers
7
- Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
8
8
  Classifier: Operating System :: POSIX :: Linux
9
9
  Classifier: Operating System :: MacOS :: MacOS X
10
10
  Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.8
12
- Classifier: Programming Language :: Python :: 3.9
13
11
  Classifier: Programming Language :: Python :: 3.10
14
12
  Classifier: Programming Language :: Python :: 3.11
15
13
  Classifier: Programming Language :: Python :: 3.12
@@ -17,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
17
15
  Classifier: Programming Language :: Python :: 3.14
18
16
  Classifier: Topic :: Communications :: Chat
19
17
  Classifier: Topic :: Communications :: Email
20
- Requires-Python: >=3.8
18
+ Requires-Python: >=3.10
21
19
  Description-Content-Type: text/markdown
22
20
  License-File: LICENSE
23
21
  Dynamic: license-file
@@ -54,6 +52,15 @@ $ pip install .
54
52
 
55
53
  Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
56
54
 
55
+
56
+ ## Activating current checkout of deltachat-rpc-client and -server for development
57
+
58
+ Go to root repository directory and run:
59
+ ```
60
+ $ scripts/make-rpc-testenv.sh
61
+ $ source venv/bin/activate
62
+ ```
63
+
57
64
  ## Using in REPL
58
65
 
59
66
  Setup a development environment:
@@ -30,6 +30,15 @@ $ pip install .
30
30
 
31
31
  Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
32
32
 
33
+
34
+ ## Activating current checkout of deltachat-rpc-client and -server for development
35
+
36
+ Go to root repository directory and run:
37
+ ```
38
+ $ scripts/make-rpc-testenv.sh
39
+ $ source venv/bin/activate
40
+ ```
41
+
33
42
  ## Using in REPL
34
43
 
35
44
  Setup a development environment:
@@ -1,20 +1,18 @@
1
1
  [build-system]
2
- requires = ["setuptools>=45"]
2
+ requires = ["setuptools>=77"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "deltachat-rpc-client"
7
- version = "2.28.0"
7
+ version = "2.30.0"
8
+ license = "MPL-2.0"
8
9
  description = "Python client for Delta Chat core JSON-RPC interface"
9
10
  classifiers = [
10
11
  "Development Status :: 5 - Production/Stable",
11
12
  "Intended Audience :: Developers",
12
- "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
13
13
  "Operating System :: POSIX :: Linux",
14
14
  "Operating System :: MacOS :: MacOS X",
15
15
  "Programming Language :: Python :: 3",
16
- "Programming Language :: Python :: 3.8",
17
- "Programming Language :: Python :: 3.9",
18
16
  "Programming Language :: Python :: 3.10",
19
17
  "Programming Language :: Python :: 3.11",
20
18
  "Programming Language :: Python :: 3.12",
@@ -24,7 +22,7 @@ classifiers = [
24
22
  "Topic :: Communications :: Email"
25
23
  ]
26
24
  readme = "README.md"
27
- requires-python = ">=3.8"
25
+ requires-python = ">=3.10"
28
26
 
29
27
  [tool.setuptools.package-data]
30
28
  deltachat_rpc_client = [
@@ -80,6 +80,7 @@ class EventType(str, Enum):
80
80
  CONFIG_SYNCED = "ConfigSynced"
81
81
  WEBXDC_REALTIME_DATA = "WebxdcRealtimeData"
82
82
  WEBXDC_REALTIME_ADVERTISEMENT_RECEIVED = "WebxdcRealtimeAdvertisementReceived"
83
+ TRANSPORTS_MODIFIED = "TransportsModified"
83
84
 
84
85
 
85
86
  class ChatId(IntEnum):
@@ -60,6 +60,10 @@ class Message:
60
60
  """Mark the message as seen."""
61
61
  self._rpc.markseen_msgs(self.account.id, [self.id])
62
62
 
63
+ def exists(self) -> bool:
64
+ """Return True if the message exists."""
65
+ return bool(self._rpc.get_existing_msg_ids(self.account.id, [self.id]))
66
+
63
67
  def continue_autocrypt_key_transfer(self, setup_code: str) -> None:
64
68
  """Continue the Autocrypt Setup Message key transfer.
65
69
 
@@ -3,9 +3,14 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
+ import pathlib
7
+ import platform
6
8
  import random
9
+ import subprocess
10
+ import sys
7
11
  from typing import AsyncGenerator, Optional
8
12
 
13
+ import execnet
9
14
  import py
10
15
  import pytest
11
16
 
@@ -20,6 +25,18 @@ Currently this is "End-to-end encryption available".
20
25
  """
21
26
 
22
27
 
28
+ def pytest_report_header():
29
+ for base in os.get_exec_path():
30
+ fn = pathlib.Path(base).joinpath(base, "deltachat-rpc-server")
31
+ if fn.exists():
32
+ proc = subprocess.Popen([str(fn), "--version"], stderr=subprocess.PIPE)
33
+ proc.wait()
34
+ version = proc.stderr.read().decode().strip()
35
+ return f"deltachat-rpc-server: {fn} [{version}]"
36
+
37
+ return None
38
+
39
+
23
40
  class ACFactory:
24
41
  """Test account factory."""
25
42
 
@@ -197,3 +214,134 @@ def log():
197
214
  print(" " + msg)
198
215
 
199
216
  return Printer()
217
+
218
+
219
+ #
220
+ # support for testing against different deltachat-rpc-server/clients
221
+ # installed into a temporary virtualenv and connected via 'execnet' channels
222
+ #
223
+
224
+
225
+ def find_path(venv, name):
226
+ is_windows = platform.system() == "Windows"
227
+ bin = venv / ("bin" if not is_windows else "Scripts")
228
+
229
+ tryadd = [""]
230
+ if is_windows:
231
+ tryadd += os.environ["PATHEXT"].split(os.pathsep)
232
+ for ext in tryadd:
233
+ p = bin.joinpath(name + ext)
234
+ if p.exists():
235
+ return str(p)
236
+
237
+ return None
238
+
239
+
240
+ @pytest.fixture(scope="session")
241
+ def get_core_python_env(tmp_path_factory):
242
+ """Return a factory to create virtualenv environments with rpc server/client packages
243
+ installed.
244
+
245
+ The factory takes a version and returns a (python_path, rpc_server_path) tuple
246
+ of the respective binaries in the virtualenv.
247
+ """
248
+
249
+ envs = {}
250
+
251
+ def get_versioned_venv(core_version):
252
+ venv = envs.get(core_version)
253
+ if not venv:
254
+ venv = tmp_path_factory.mktemp(f"temp-{core_version}")
255
+ subprocess.check_call([sys.executable, "-m", "venv", venv])
256
+
257
+ python = find_path(venv, "python")
258
+ pkgs = [f"deltachat-rpc-server=={core_version}", f"deltachat-rpc-client=={core_version}", "pytest"]
259
+ subprocess.check_call([python, "-m", "pip", "install"] + pkgs)
260
+
261
+ envs[core_version] = venv
262
+ python = find_path(venv, "python")
263
+ rpc_server_path = find_path(venv, "deltachat-rpc-server")
264
+ print(f"python={python}\nrpc_server={rpc_server_path}")
265
+ return python, rpc_server_path
266
+
267
+ return get_versioned_venv
268
+
269
+
270
+ @pytest.fixture
271
+ def alice_and_remote_bob(tmp_path, acfactory, get_core_python_env):
272
+ """return local Alice account, a contact to bob, and a remote 'eval' function for bob.
273
+
274
+ The 'eval' function allows to remote-execute arbitrary expressions
275
+ that can use the `bob` online account, and the `bob_contact_alice`.
276
+ """
277
+
278
+ def factory(core_version):
279
+ python, rpc_server_path = get_core_python_env(core_version)
280
+ gw = execnet.makegateway(f"popen//python={python}")
281
+
282
+ accounts_dir = str(tmp_path.joinpath("account1_venv1"))
283
+ channel = gw.remote_exec(remote_bob_loop)
284
+ cm = os.environ.get("CHATMAIL_DOMAIN")
285
+
286
+ # trigger getting an online account on bob's side
287
+ channel.send((accounts_dir, str(rpc_server_path), cm))
288
+
289
+ # meanwhile get a local alice account
290
+ alice = acfactory.get_online_account()
291
+ channel.send(alice.self_contact.make_vcard())
292
+
293
+ # wait for bob to have started
294
+ sysinfo = channel.receive()
295
+ assert sysinfo == f"v{core_version}"
296
+ bob_vcard = channel.receive()
297
+ [alice_contact_bob] = alice.import_vcard(bob_vcard)
298
+
299
+ def eval(eval_str):
300
+ channel.send(eval_str)
301
+ return channel.receive()
302
+
303
+ return alice, alice_contact_bob, eval
304
+
305
+ return factory
306
+
307
+
308
+ def remote_bob_loop(channel):
309
+ # This function executes with versioned
310
+ # deltachat-rpc-client/server packages
311
+ # installed into the virtualenv.
312
+ #
313
+ # The "channel" argument is a send/receive pipe
314
+ # to the process that runs the corresponding remote_exec(remote_bob_loop)
315
+
316
+ import os
317
+
318
+ from deltachat_rpc_client import DeltaChat, Rpc
319
+ from deltachat_rpc_client.pytestplugin import ACFactory
320
+
321
+ accounts_dir, rpc_server_path, chatmail_domain = channel.receive()
322
+ os.environ["CHATMAIL_DOMAIN"] = chatmail_domain
323
+
324
+ # older core versions don't support specifying rpc_server_path
325
+ # so we can't just pass `rpc_server_path` argument to Rpc constructor
326
+ basepath = os.path.dirname(rpc_server_path)
327
+ os.environ["PATH"] = os.pathsep.join([basepath, os.environ["PATH"]])
328
+ rpc = Rpc(accounts_dir=accounts_dir)
329
+
330
+ with rpc:
331
+ dc = DeltaChat(rpc)
332
+ channel.send(dc.rpc.get_system_info()["deltachat_core_version"])
333
+ acfactory = ACFactory(dc)
334
+ bob = acfactory.get_online_account()
335
+ alice_vcard = channel.receive()
336
+ [alice_contact] = bob.import_vcard(alice_vcard)
337
+ ns = {"bob": bob, "bob_contact_alice": alice_contact}
338
+ channel.send(bob.self_contact.make_vcard())
339
+
340
+ while 1:
341
+ eval_str = channel.receive()
342
+ res = eval(eval_str, ns)
343
+ try:
344
+ channel.send(res)
345
+ except Exception:
346
+ # some unserializable result
347
+ channel.send(None)
@@ -9,7 +9,7 @@ import os
9
9
  import subprocess
10
10
  import sys
11
11
  from queue import Empty, Queue
12
- from threading import Event, Thread
12
+ from threading import Thread
13
13
  from typing import Any, Iterator, Optional
14
14
 
15
15
 
@@ -17,25 +17,6 @@ class JsonRpcError(Exception):
17
17
  """JSON-RPC error."""
18
18
 
19
19
 
20
- class RpcFuture:
21
- """RPC future waiting for RPC call result."""
22
-
23
- def __init__(self, rpc: "Rpc", request_id: int, event: Event):
24
- self.rpc = rpc
25
- self.request_id = request_id
26
- self.event = event
27
-
28
- def __call__(self):
29
- """Wait for the future to return the result."""
30
- self.event.wait()
31
- response = self.rpc.request_results.pop(self.request_id)
32
- if "error" in response:
33
- raise JsonRpcError(response["error"])
34
- if "result" in response:
35
- return response["result"]
36
- return None
37
-
38
-
39
20
  class RpcMethod:
40
21
  """RPC method."""
41
22
 
@@ -57,20 +38,26 @@ class RpcMethod:
57
38
  "params": args,
58
39
  "id": request_id,
59
40
  }
60
- event = Event()
61
- self.rpc.request_events[request_id] = event
41
+ self.rpc.request_results[request_id] = queue = Queue()
62
42
  self.rpc.request_queue.put(request)
63
43
 
64
- return RpcFuture(self.rpc, request_id, event)
44
+ def rpc_future():
45
+ """Wait for the request to receive a result."""
46
+ response = queue.get()
47
+ if "error" in response:
48
+ raise JsonRpcError(response["error"])
49
+ return response.get("result", None)
50
+
51
+ return rpc_future
65
52
 
66
53
 
67
54
  class Rpc:
68
55
  """RPC client."""
69
56
 
70
- def __init__(self, accounts_dir: Optional[str] = None, **kwargs):
57
+ def __init__(self, accounts_dir: Optional[str] = None, rpc_server_path="deltachat-rpc-server", **kwargs):
71
58
  """Initialize RPC client.
72
59
 
73
- The given arguments will be passed to subprocess.Popen().
60
+ The 'kwargs' arguments will be passed to subprocess.Popen().
74
61
  """
75
62
  if accounts_dir:
76
63
  kwargs["env"] = {
@@ -79,13 +66,12 @@ class Rpc:
79
66
  }
80
67
 
81
68
  self._kwargs = kwargs
69
+ self.rpc_server_path = rpc_server_path
82
70
  self.process: subprocess.Popen
83
71
  self.id_iterator: Iterator[int]
84
72
  self.event_queues: dict[int, Queue]
85
- # Map from request ID to `threading.Event`.
86
- self.request_events: dict[int, Event]
87
- # Map from request ID to the result.
88
- self.request_results: dict[int, Any]
73
+ # Map from request ID to a Queue which provides a single result
74
+ self.request_results: dict[int, Queue]
89
75
  self.request_queue: Queue[Any]
90
76
  self.closing: bool
91
77
  self.reader_thread: Thread
@@ -94,27 +80,18 @@ class Rpc:
94
80
 
95
81
  def start(self) -> None:
96
82
  """Start RPC server subprocess."""
83
+ popen_kwargs = {"stdin": subprocess.PIPE, "stdout": subprocess.PIPE}
97
84
  if sys.version_info >= (3, 11):
98
- self.process = subprocess.Popen(
99
- "deltachat-rpc-server",
100
- stdin=subprocess.PIPE,
101
- stdout=subprocess.PIPE,
102
- # Prevent subprocess from capturing SIGINT.
103
- process_group=0,
104
- **self._kwargs,
105
- )
85
+ # Prevent subprocess from capturing SIGINT.
86
+ popen_kwargs["process_group"] = 0
106
87
  else:
107
- self.process = subprocess.Popen(
108
- "deltachat-rpc-server",
109
- stdin=subprocess.PIPE,
110
- stdout=subprocess.PIPE,
111
- # `process_group` is not supported before Python 3.11.
112
- preexec_fn=os.setpgrp, # noqa: PLW1509
113
- **self._kwargs,
114
- )
88
+ # `process_group` is not supported before Python 3.11.
89
+ popen_kwargs["preexec_fn"] = os.setpgrp # noqa: PLW1509
90
+
91
+ popen_kwargs.update(self._kwargs)
92
+ self.process = subprocess.Popen(self.rpc_server_path, **popen_kwargs)
115
93
  self.id_iterator = itertools.count(start=1)
116
94
  self.event_queues = {}
117
- self.request_events = {}
118
95
  self.request_results = {}
119
96
  self.request_queue = Queue()
120
97
  self.closing = False
@@ -149,9 +126,7 @@ class Rpc:
149
126
  response = json.loads(line)
150
127
  if "id" in response:
151
128
  response_id = response["id"]
152
- event = self.request_events.pop(response_id)
153
- self.request_results[response_id] = response
154
- event.set()
129
+ self.request_results.pop(response_id).put(response)
155
130
  else:
156
131
  logging.warning("Got a response without ID: %s", response)
157
132
  except Exception:
@@ -1,15 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deltachat-rpc-client
3
- Version: 2.28.0
3
+ Version: 2.30.0
4
4
  Summary: Python client for Delta Chat core JSON-RPC interface
5
+ License-Expression: MPL-2.0
5
6
  Classifier: Development Status :: 5 - Production/Stable
6
7
  Classifier: Intended Audience :: Developers
7
- Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
8
8
  Classifier: Operating System :: POSIX :: Linux
9
9
  Classifier: Operating System :: MacOS :: MacOS X
10
10
  Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.8
12
- Classifier: Programming Language :: Python :: 3.9
13
11
  Classifier: Programming Language :: Python :: 3.10
14
12
  Classifier: Programming Language :: Python :: 3.11
15
13
  Classifier: Programming Language :: Python :: 3.12
@@ -17,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
17
15
  Classifier: Programming Language :: Python :: 3.14
18
16
  Classifier: Topic :: Communications :: Chat
19
17
  Classifier: Topic :: Communications :: Email
20
- Requires-Python: >=3.8
18
+ Requires-Python: >=3.10
21
19
  Description-Content-Type: text/markdown
22
20
  License-File: LICENSE
23
21
  Dynamic: license-file
@@ -54,6 +52,15 @@ $ pip install .
54
52
 
55
53
  Additional arguments to `tox` are passed to pytest, e.g. `tox -- -s` does not capture test output.
56
54
 
55
+
56
+ ## Activating current checkout of deltachat-rpc-client and -server for development
57
+
58
+ Go to root repository directory and run:
59
+ ```
60
+ $ scripts/make-rpc-testenv.sh
61
+ $ source venv/bin/activate
62
+ ```
63
+
57
64
  ## Using in REPL
58
65
 
59
66
  Setup a development environment:
@@ -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_cross_core.py
25
26
  tests/test_folders.py
26
27
  tests/test_iroh_webxdc.py
27
28
  tests/test_key_transfer.py
@@ -0,0 +1,44 @@
1
+ import subprocess
2
+
3
+ import pytest
4
+
5
+ from deltachat_rpc_client import DeltaChat, Rpc
6
+
7
+
8
+ def test_install_venv_and_use_other_core(tmp_path, get_core_python_env):
9
+ python, rpc_server_path = get_core_python_env("2.24.0")
10
+ subprocess.check_call([python, "-m", "pip", "install", "deltachat-rpc-server==2.24.0"])
11
+ rpc = Rpc(accounts_dir=tmp_path.joinpath("accounts"), rpc_server_path=rpc_server_path)
12
+
13
+ with rpc:
14
+ dc = DeltaChat(rpc)
15
+ assert dc.rpc.get_system_info()["deltachat_core_version"] == "v2.24.0"
16
+
17
+
18
+ @pytest.mark.parametrize("version", ["2.24.0"])
19
+ def test_qr_setup_contact(alice_and_remote_bob, version) -> None:
20
+ """Test other-core Bob profile can do securejoin with Alice on current core."""
21
+ alice, alice_contact_bob, remote_eval = alice_and_remote_bob(version)
22
+
23
+ qr_code = alice.get_qr_code()
24
+ remote_eval(f"bob.secure_join({qr_code!r})")
25
+ alice.wait_for_securejoin_inviter_success()
26
+
27
+ # Test that Alice verified Bob's profile.
28
+ alice_contact_bob_snapshot = alice_contact_bob.get_snapshot()
29
+ assert alice_contact_bob_snapshot.is_verified
30
+
31
+ remote_eval("bob.wait_for_securejoin_joiner_success()")
32
+
33
+ # Test that Bob verified Alice's profile.
34
+ assert remote_eval("bob_contact_alice.get_snapshot().is_verified")
35
+
36
+
37
+ def test_send_and_receive_message(alice_and_remote_bob) -> None:
38
+ """Test other-core Bob profile can send a message to Alice on current core."""
39
+ alice, alice_contact_bob, remote_eval = alice_and_remote_bob("2.20.0")
40
+
41
+ remote_eval("bob_contact_alice.create_chat().send_text('hello')")
42
+
43
+ msg = alice.wait_for_incoming_msg()
44
+ assert msg.get_snapshot().text == "hello"
@@ -1,5 +1,6 @@
1
1
  import pytest
2
2
 
3
+ from deltachat_rpc_client import EventType
3
4
  from deltachat_rpc_client.rpc import JsonRpcError
4
5
 
5
6
 
@@ -156,3 +157,47 @@ def test_reconfigure_transport(acfactory) -> None:
156
157
  # Reconfiguring the transport should not reset
157
158
  # the settings as if when configuring the first transport.
158
159
  assert account.get_config("mvbox_move") == "1"
160
+
161
+
162
+ def test_transport_synchronization(acfactory, log) -> None:
163
+ """Test synchronization of transports between devices."""
164
+ ac1, ac2 = acfactory.get_online_accounts(2)
165
+ ac1_clone = ac1.clone()
166
+ ac1_clone.bring_online()
167
+
168
+ qr = acfactory.get_account_qr()
169
+
170
+ ac1.add_transport_from_qr(qr)
171
+ ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
172
+ assert len(ac1.list_transports()) == 2
173
+ assert len(ac1_clone.list_transports()) == 2
174
+
175
+ ac1_clone.add_transport_from_qr(qr)
176
+ ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
177
+ assert len(ac1.list_transports()) == 3
178
+ assert len(ac1_clone.list_transports()) == 3
179
+
180
+ log.section("ac1 clone removes second transport")
181
+ [transport1, transport2, transport3] = ac1_clone.list_transports()
182
+ addr3 = transport3["addr"]
183
+ ac1_clone.delete_transport(transport2["addr"])
184
+
185
+ ac1.wait_for_event(EventType.TRANSPORTS_MODIFIED)
186
+ [transport1, transport3] = ac1.list_transports()
187
+
188
+ log.section("ac1 changes the primary transport")
189
+ ac1.set_config("configured_addr", transport3["addr"])
190
+
191
+ log.section("ac1 removes the first transport")
192
+ ac1.delete_transport(transport1["addr"])
193
+
194
+ ac1_clone.wait_for_event(EventType.TRANSPORTS_MODIFIED)
195
+ [transport3] = ac1_clone.list_transports()
196
+ assert transport3["addr"] == addr3
197
+ assert ac1_clone.get_config("configured_addr") == addr3
198
+
199
+ ac2_chat = ac2.create_chat(ac1)
200
+ ac2_chat.send_text("Hello!")
201
+
202
+ assert ac1.wait_for_incoming_msg().get_snapshot().text == "Hello!"
203
+ assert ac1_clone.wait_for_incoming_msg().get_snapshot().text == "Hello!"
@@ -661,8 +661,6 @@ def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
661
661
  contact = alice.create_contact(account)
662
662
  alice_group.add_contact(contact)
663
663
 
664
- if n_accounts == 2:
665
- bob_chat_alice = bob.create_chat(alice)
666
664
  bob.set_config("download_limit", str(download_limit))
667
665
 
668
666
  alice_group.send_text("hi")
@@ -678,15 +676,7 @@ def test_download_limit_chat_assignment(acfactory, tmp_path, n_accounts):
678
676
  alice_group.send_file(str(path))
679
677
  snapshot = bob.wait_for_incoming_msg().get_snapshot()
680
678
  assert snapshot.download_state == DownloadState.AVAILABLE
681
- if n_accounts > 2:
682
- assert snapshot.chat == bob_group
683
- else:
684
- # Group contains only Alice and Bob,
685
- # so partially downloaded messages are
686
- # hard to distinguish from private replies to group messages.
687
- #
688
- # Message may be a private reply, so we assign it to 1:1 chat with Alice.
689
- assert snapshot.chat == bob_chat_alice
679
+ assert snapshot.chat == bob_group
690
680
 
691
681
 
692
682
  def test_markseen_contact_request(acfactory):
@@ -1003,3 +993,22 @@ def test_background_fetch(acfactory, dc):
1003
993
  snapshot = messages[-1].get_snapshot()
1004
994
  if snapshot.text == "Hello again!":
1005
995
  break
996
+
997
+
998
+ def test_message_exists(acfactory):
999
+ ac1, ac2 = acfactory.get_online_accounts(2)
1000
+ chat = ac1.create_chat(ac2)
1001
+ message1 = chat.send_text("Hello!")
1002
+ message2 = chat.send_text("Hello again!")
1003
+ assert message1.exists()
1004
+ assert message2.exists()
1005
+
1006
+ ac1.delete_messages([message1])
1007
+ assert not message1.exists()
1008
+ assert message2.exists()
1009
+
1010
+ # There is no error when checking if
1011
+ # the message exists for deleted account.
1012
+ ac1.remove()
1013
+ assert not message1.exists()
1014
+ assert not message2.exists()