update python API (#3394)

This commit is contained in:
Asiel Díaz Benítez
2022-06-04 12:12:38 -04:00
committed by GitHub
parent 0d1d1a25da
commit 4f02c811a3
6 changed files with 304 additions and 54 deletions

View File

@@ -11,6 +11,24 @@
- do not reset our database if imported backup cannot be decrypted #3397 - do not reset our database if imported backup cannot be decrypted #3397
- node: remove `npx` from build script, this broke flathub build #3396 - node: remove `npx` from build script, this broke flathub build #3396
### API-Changes
- python: added optional `closed` parameter to `Account` constructor #3394
- python: added optional `passphrase` parameter to `Account.export_all()` and `Account.import_all()` #3394
- python: added `Account.open()` #3394
- python: added `Chat.is_single()` #3394
- python: added `Chat.is_mailinglist()` #3394
- python: added `Chat.is_broadcast()` #3394
- python: added `Chat.is_multiuser()` #3394
- python: added `Chat.is_self_talk()` #3394
- python: added `Chat.is_device_talk()` #3394
- python: added `Chat.is_pinned()` #3394
- python: added `Chat.pin()` #3394
- python: added `Chat.unpin()` #3394
- python: added `Chat.archive()` #3394
- python: added `Chat.unarchive()` #3394
- python: added `Message.get_summarytext()` #3394
- python: added optional `closed` parameter to `ACFactory.get_unconfigured_account()` (pytest plugin) #3394
- python: added optional `passphrase` parameter to `ACFactory.get_pseudo_configured_account()` (pytest plugin) #3394
## 1.85.0 ## 1.85.0

View File

@@ -62,12 +62,15 @@ class Account(object):
MissingCredentials = MissingCredentials MissingCredentials = MissingCredentials
def __init__(self, db_path, os_name=None, logging=True) -> None: def __init__(self, db_path, os_name=None, logging=True, closed=False) -> None:
"""initialize account object. """initialize account object.
:param db_path: a path to the account database. The database :param db_path: a path to the account database. The database
will be created if it doesn't exist. will be created if it doesn't exist.
:param os_name: this will be put to the X-Mailer header in outgoing messages :param os_name: [Deprecated]
:param logging: enable logging for this account
:param closed: set to True to avoid automatically opening the account
after creation.
""" """
# initialize per-account plugin system # initialize per-account plugin system
self._pm = hookspec.PerAccount._make_plugin_manager() self._pm = hookspec.PerAccount._make_plugin_manager()
@@ -80,7 +83,7 @@ class Account(object):
db_path = db_path.encode("utf8") db_path = db_path.encode("utf8")
self._dc_context = ffi.gc( self._dc_context = ffi.gc(
lib.dc_context_new(as_dc_charpointer(os_name), db_path, ffi.NULL), lib.dc_context_new_closed(db_path) if closed else lib.dc_context_new(ffi.NULL, db_path, ffi.NULL),
lib.dc_context_unref, lib.dc_context_unref,
) )
if self._dc_context == ffi.NULL: if self._dc_context == ffi.NULL:
@@ -92,6 +95,17 @@ class Account(object):
hook = hookspec.Global._get_plugin_manager().hook hook = hookspec.Global._get_plugin_manager().hook
hook.dc_account_init(account=self) hook.dc_account_init(account=self)
def open(self, passphrase: Optional[str] = None) -> bool:
"""Open the account's database with the given passphrase.
This can only be used on a closed account. If the account is new, this
operation sets the database passphrase. For existing databases the passphrase
should be the one used to encrypt the database the first time.
:returns: True if the database is opened with this passphrase, False if the
passphrase is incorrect or an error occurred.
"""
return bool(lib.dc_context_open(self._dc_context, as_dc_charpointer(passphrase)))
def disable_logging(self) -> None: def disable_logging(self) -> None:
"""disable logging.""" """disable logging."""
self._logging = False self._logging = False
@@ -209,13 +223,13 @@ class Account(object):
:returns: True if account is configured. :returns: True if account is configured.
""" """
return True if lib.dc_is_configured(self._dc_context) else False return bool(lib.dc_is_configured(self._dc_context))
def is_open(self) -> bool: def is_open(self) -> bool:
"""Determine if account is open """Determine if account is open
:returns True if account is open.""" :returns True if account is open."""
return True if lib.dc_context_is_open(self._dc_context) else False return bool(lib.dc_context_is_open(self._dc_context))
def set_avatar(self, img_path: Optional[str]) -> None: def set_avatar(self, img_path: Optional[str]) -> None:
"""Set self avatar. """Set self avatar.
@@ -461,21 +475,24 @@ class Account(object):
""" """
return self._export(path, imex_cmd=const.DC_IMEX_EXPORT_SELF_KEYS) return self._export(path, imex_cmd=const.DC_IMEX_EXPORT_SELF_KEYS)
def export_all(self, path): def export_all(self, path: str, passphrase: Optional[str] = None) -> str:
"""return new file containing a backup of all database state """Export a backup of all database state (chats, contacts, keys, media, ...).
(chats, contacts, keys, media, ...). The file is created in the
the `path` directory. :param path: the directory where the backup will be stored.
:param passphrase: the backup will be encrypted with the passphrase, if it is
None or empty string, the backup is not encrypted.
:returns: path to the created backup.
Note that the account has to be stopped; call stop_io() if necessary. Note that the account has to be stopped; call stop_io() if necessary.
""" """
export_files = self._export(path, const.DC_IMEX_EXPORT_BACKUP) export_files = self._export(path, const.DC_IMEX_EXPORT_BACKUP, passphrase)
if len(export_files) != 1: if len(export_files) != 1:
raise RuntimeError("found more than one new file") raise RuntimeError("found more than one new file")
return export_files[0] return export_files[0]
def _export(self, path, imex_cmd): def _export(self, path: str, imex_cmd: int, passphrase: Optional[str] = None) -> list:
with self.temp_plugin(ImexTracker()) as imex_tracker: with self.temp_plugin(ImexTracker()) as imex_tracker:
self.imex(path, imex_cmd) self.imex(path, imex_cmd, passphrase)
return imex_tracker.wait_finish() return imex_tracker.wait_finish()
def import_self_keys(self, path): def import_self_keys(self, path):
@@ -487,21 +504,23 @@ class Account(object):
""" """
self._import(path, imex_cmd=const.DC_IMEX_IMPORT_SELF_KEYS) self._import(path, imex_cmd=const.DC_IMEX_IMPORT_SELF_KEYS)
def import_all(self, path): def import_all(self, path: str, passphrase: Optional[str] = None) -> None:
"""import delta chat state from the specified backup `path` (a file). """Import Delta Chat state from the specified backup file.
The account must be in unconfigured state for import to attempted. The account must be in unconfigured state for import to attempted.
:param path: path to the backup file.
:param passphrase: if not None or empty, the backup will be opened with the passphrase.
""" """
assert not self.is_configured(), "cannot import into configured account" assert not self.is_configured(), "cannot import into configured account"
self._import(path, imex_cmd=const.DC_IMEX_IMPORT_BACKUP) self._import(path, imex_cmd=const.DC_IMEX_IMPORT_BACKUP, passphrase=passphrase)
def _import(self, path, imex_cmd): def _import(self, path: str, imex_cmd: int, passphrase: Optional[str] = None) -> None:
with self.temp_plugin(ImexTracker()) as imex_tracker: with self.temp_plugin(ImexTracker()) as imex_tracker:
self.imex(path, imex_cmd) self.imex(path, imex_cmd, passphrase)
imex_tracker.wait_finish() imex_tracker.wait_finish()
def imex(self, path, imex_cmd): def imex(self, path: str, imex_cmd: int, passphrase: Optional[str] = None) -> None:
lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL) lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), as_dc_charpointer(passphrase))
def initiate_key_transfer(self) -> str: def initiate_key_transfer(self) -> str:
"""return setup code after a Autocrypt setup message """return setup code after a Autocrypt setup message

View File

@@ -65,27 +65,65 @@ class Chat(object):
# ------ chat status/metadata API ------------------------------ # ------ chat status/metadata API ------------------------------
def is_group(self) -> bool: def is_group(self) -> bool:
"""return true if this chat is a group chat. """Return True if this chat is a group chat.
:returns: True if chat is a group-chat, false otherwise :returns: True if chat is a group-chat, False otherwise
""" """
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP
def is_single(self) -> bool:
"""Return True if this chat is a single/direct chat, False otherwise"""
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_SINGLE
def is_mailinglist(self) -> bool:
"""Return True if this chat is a mailing list, False otherwise"""
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_MAILINGLIST
def is_broadcast(self) -> bool:
"""Return True if this chat is a broadcast list, False otherwise"""
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_BROADCAST
def is_multiuser(self) -> bool:
"""Return True if this chat is a multi-user chat (group, mailing list or broadcast), False otherwise"""
return lib.dc_chat_get_type(self._dc_chat) in (
const.DC_CHAT_TYPE_GROUP,
const.DC_CHAT_TYPE_MAILINGLIST,
const.DC_CHAT_TYPE_BROADCAST,
)
def is_self_talk(self) -> bool:
"""Return True if this chat is the self-chat (a.k.a. "Saved Messages"), False otherwise"""
return bool(lib.dc_chat_is_self_talk(self._dc_chat))
def is_device_talk(self) -> bool:
"""Returns True if this chat is the "Device Messages" chat, False otherwise"""
return bool(lib.dc_chat_is_device_talk(self._dc_chat))
def is_muted(self) -> bool: def is_muted(self) -> bool:
"""return true if this chat is muted. """return true if this chat is muted.
:returns: True if chat is muted, False otherwise. :returns: True if chat is muted, False otherwise.
""" """
return lib.dc_chat_is_muted(self._dc_chat) return bool(lib.dc_chat_is_muted(self._dc_chat))
def is_contact_request(self): def is_pinned(self) -> bool:
"""Return True if this chat is pinned, False otherwise"""
return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_PINNED
def is_archived(self) -> bool:
"""Return True if this chat is archived, False otherwise.
:returns: True if archived, False otherwise
"""
return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_ARCHIVED
def is_contact_request(self) -> bool:
"""return True if this chat is a contact request chat. """return True if this chat is a contact request chat.
:returns: True if chat is a contact request chat, False otherwise. :returns: True if chat is a contact request chat, False otherwise.
""" """
return lib.dc_chat_is_contact_request(self._dc_chat) return bool(lib.dc_chat_is_contact_request(self._dc_chat))
def is_promoted(self): def is_promoted(self) -> bool:
"""return True if this chat is promoted, i.e. """return True if this chat is promoted, i.e.
the member contacts are aware of their membership, the member contacts are aware of their membership,
have been sent messages. have been sent messages.
@@ -100,14 +138,14 @@ class Chat(object):
:returns: True if the chat is writable, False otherwise :returns: True if the chat is writable, False otherwise
""" """
return lib.dc_chat_can_send(self._dc_chat) return bool(lib.dc_chat_can_send(self._dc_chat))
def is_protected(self) -> bool: def is_protected(self) -> bool:
"""return True if this chat is a protected chat. """return True if this chat is a protected chat.
:returns: True if chat is protected, False otherwise. :returns: True if chat is protected, False otherwise.
""" """
return lib.dc_chat_is_protected(self._dc_chat) return bool(lib.dc_chat_is_protected(self._dc_chat))
def get_name(self) -> Optional[str]: def get_name(self) -> Optional[str]:
"""return name of this chat. """return name of this chat.
@@ -125,6 +163,18 @@ class Chat(object):
name = as_dc_charpointer(name) name = as_dc_charpointer(name)
return bool(lib.dc_set_chat_name(self.account._dc_context, self.id, name)) return bool(lib.dc_set_chat_name(self.account._dc_context, self.id, name))
def get_color(self):
"""return the color of the chat.
:returns: color as 0x00rrggbb
"""
return lib.dc_chat_get_color(self._dc_chat)
def get_summary(self):
"""return dictionary with summary information."""
dc_res = lib.dc_chat_get_info_json(self.account._dc_context, self.id)
s = from_dc_charpointer(dc_res)
return json.loads(s)
def mute(self, duration: Optional[int] = None) -> None: def mute(self, duration: Optional[int] = None) -> None:
"""mutes the chat """mutes the chat
@@ -148,6 +198,24 @@ class Chat(object):
if not bool(ret): if not bool(ret):
raise ValueError("Failed to unmute chat") raise ValueError("Failed to unmute chat")
def pin(self) -> None:
"""Pin the chat."""
lib.dc_set_chat_visibility(self.account._dc_context, self.id, const.DC_CHAT_VISIBILITY_PINNED)
def unpin(self) -> None:
"""Unpin the chat."""
if self.is_pinned():
lib.dc_set_chat_visibility(self.account._dc_context, self.id, const.DC_CHAT_VISIBILITY_NORMAL)
def archive(self) -> None:
"""Archive the chat."""
lib.dc_set_chat_visibility(self.account._dc_context, self.id, const.DC_CHAT_VISIBILITY_ARCHIVED)
def unarchive(self) -> None:
"""Unarchive the chat."""
if self.is_archived():
lib.dc_set_chat_visibility(self.account._dc_context, self.id, const.DC_CHAT_VISIBILITY_NORMAL)
def get_mute_duration(self) -> int: def get_mute_duration(self) -> int:
"""Returns the number of seconds until the mute of this chat is lifted. """Returns the number of seconds until the mute of this chat is lifted.
@@ -364,12 +432,6 @@ class Chat(object):
""" """
return lib.dc_marknoticed_chat(self.account._dc_context, self.id) return lib.dc_marknoticed_chat(self.account._dc_context, self.id)
def get_summary(self):
"""return dictionary with summary information."""
dc_res = lib.dc_chat_get_info_json(self.account._dc_context, self.id)
s = from_dc_charpointer(dc_res)
return json.loads(s)
# ------ group management API ------------------------------ # ------ group management API ------------------------------
def add_contact(self, obj): def add_contact(self, obj):
@@ -460,12 +522,6 @@ class Chat(object):
return None return None
return from_dc_charpointer(dc_res) return from_dc_charpointer(dc_res)
def get_color(self):
"""return the color of the chat.
:returns: color as 0x00rrggbb
"""
return lib.dc_chat_get_color(self._dc_chat)
# ------ location streaming API ------------------------------ # ------ location streaming API ------------------------------
def is_sending_locations(self): def is_sending_locations(self):
@@ -474,12 +530,6 @@ class Chat(object):
""" """
return lib.dc_is_sending_locations_to_chat(self.account._dc_context, self.id) return lib.dc_is_sending_locations_to_chat(self.account._dc_context, self.id)
def is_archived(self):
"""return True if this chat is archived.
:returns: True if archived.
"""
return lib.dc_chat_get_visibility(self._dc_chat) == const.DC_CHAT_VISIBILITY_ARCHIVED
def enable_sending_locations(self, seconds): def enable_sending_locations(self, seconds):
"""enable sending locations for this chat. """enable sending locations for this chat.

View File

@@ -159,6 +159,10 @@ class Message(object):
""" """
return from_dc_charpointer(lib.dc_get_msg_info(self.account._dc_context, self.id)) return from_dc_charpointer(lib.dc_get_msg_info(self.account._dc_context, self.id))
def get_summarytext(self, width: int) -> str:
"""Get a message summary as a single line of text. Typically used for notifications."""
return from_dc_charpointer(lib.dc_msg_get_summarytext(self._dc_msg, width))
def continue_key_transfer(self, setup_code): def continue_key_transfer(self, setup_code):
"""extract key and use it as primary key for this account.""" """extract key and use it as primary key for this account."""
res = lib.dc_continue_key_transfer(self.account._dc_context, self.id, as_dc_charpointer(setup_code)) res = lib.dc_continue_key_transfer(self.account._dc_context, self.id, as_dc_charpointer(setup_code))

View File

@@ -11,7 +11,7 @@ import threading
import time import time
import weakref import weakref
from queue import Queue from queue import Queue
from typing import Callable, List from typing import Callable, List, Optional
import pytest import pytest
import requests import requests
@@ -429,16 +429,16 @@ class ACFactory:
if addr in self.testprocess._addr2files: if addr in self.testprocess._addr2files:
return self._getaccount(addr) return self._getaccount(addr)
def get_unconfigured_account(self): def get_unconfigured_account(self, closed=False):
return self._getaccount() return self._getaccount(closed=closed)
def _getaccount(self, try_cache_addr=None): def _getaccount(self, try_cache_addr=None, closed=False):
logid = "ac{}".format(len(self._accounts) + 1) logid = "ac{}".format(len(self._accounts) + 1)
# we need to use fixed database basename for maybe_cache_* functions to work # we need to use fixed database basename for maybe_cache_* functions to work
path = self.tmpdir.mkdir(logid).join("dc.db") path = self.tmpdir.mkdir(logid).join("dc.db")
if try_cache_addr: if try_cache_addr:
self.testprocess.cache_maybe_retrieve_configured_db_files(try_cache_addr, path) self.testprocess.cache_maybe_retrieve_configured_db_files(try_cache_addr, path)
ac = Account(path.strpath, logging=self._logging) ac = Account(path.strpath, logging=self._logging, closed=closed)
ac._logid = logid # later instantiated FFIEventLogger needs this ac._logid = logid # later instantiated FFIEventLogger needs this
ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac)) ac._evtracker = ac.add_account_plugin(FFIEventTracker(ac))
if self.pytestconfig.getoption("--debug-setup"): if self.pytestconfig.getoption("--debug-setup"):
@@ -467,9 +467,11 @@ class ACFactory:
else: else:
print("WARN: could not use preconfigured keys for {!r}".format(addr)) print("WARN: could not use preconfigured keys for {!r}".format(addr))
def get_pseudo_configured_account(self): def get_pseudo_configured_account(self, passphrase: Optional[str] = None) -> Account:
# do a pseudo-configured account # do a pseudo-configured account
ac = self.get_unconfigured_account() ac = self.get_unconfigured_account(closed=bool(passphrase))
if passphrase:
ac.open(passphrase)
acname = ac._logid acname = ac._logid
addr = "{}@offline.org".format(acname) addr = "{}@offline.org".format(acname)
ac.update_config( ac.update_config(

View File

@@ -11,6 +11,7 @@ from deltachat.capi import ffi, lib
from deltachat.cutil import iter_array from deltachat.cutil import iter_array
from deltachat.hookspec import account_hookimpl from deltachat.hookspec import account_hookimpl
from deltachat.message import Message from deltachat.message import Message
from deltachat.tracker import ImexFailed
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -496,7 +497,7 @@ class TestOfflineChat:
with pytest.raises(ValueError): with pytest.raises(ValueError):
ac1.set_config("addr", "123@example.org") ac1.set_config("addr", "123@example.org")
def test_import_export_one_contact(self, acfactory, tmpdir): def test_import_export_on_unencrypted_acct(self, acfactory, tmpdir):
backupdir = tmpdir.mkdir("backup") backupdir = tmpdir.mkdir("backup")
ac1 = acfactory.get_pseudo_configured_account() ac1 = acfactory.get_pseudo_configured_account()
chat = ac1.create_contact("some1 <some1@example.org>").create_chat() chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
@@ -525,6 +526,162 @@ class TestOfflineChat:
assert messages[0].text == "msg1" assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename) assert os.path.exists(messages[1].filename)
def test_import_export_on_encrypted_acct(self, acfactory, tmpdir):
passphrase1 = "passphrase1"
passphrase2 = "passphrase2"
backupdir = tmpdir.mkdir("backup")
ac1 = acfactory.get_pseudo_configured_account(passphrase=passphrase1)
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
# send a text message
msg = chat.send_text("msg1")
# send a binary file
bin = tmpdir.join("some.bin")
with bin.open("w") as f:
f.write("\00123" * 10000)
msg = chat.send_file(bin.strpath)
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
assert not backupdir.listdir()
ac1.stop_io()
path = ac1.export_all(backupdir.strpath)
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account(closed=True)
ac2.open(passphrase2)
ac2.import_all(path)
# check data integrity
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
contact2 = contacts[0]
assert contact2.addr == "some1@example.org"
chat2 = contact2.create_chat()
messages = chat2.get_messages()
assert len(messages) == 2
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
ac2.shutdown()
# check that passphrase is not lost after import:
ac2 = Account(ac2.db_path, logging=ac2._logging, closed=True)
ac2.open(passphrase2)
# check data integrity
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
contact2 = contacts[0]
assert contact2.addr == "some1@example.org"
chat2 = contact2.create_chat()
messages = chat2.get_messages()
assert len(messages) == 2
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
def test_import_export_with_passphrase(self, acfactory, tmpdir):
passphrase = "test_passphrase"
wrong_passphrase = "wrong_passprase"
backupdir = tmpdir.mkdir("backup")
ac1 = acfactory.get_pseudo_configured_account()
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
# send a text message
msg = chat.send_text("msg1")
# send a binary file
bin = tmpdir.join("some.bin")
with bin.open("w") as f:
f.write("\00123" * 10000)
msg = chat.send_file(bin.strpath)
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
assert not backupdir.listdir()
ac1.stop_io()
path = ac1.export_all(backupdir.strpath, passphrase)
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account()
with pytest.raises(ImexFailed):
ac2.import_all(path, wrong_passphrase)
ac2.import_all(path, passphrase)
# check data integrity
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
contact2 = contacts[0]
assert contact2.addr == "some1@example.org"
chat2 = contact2.create_chat()
messages = chat2.get_messages()
assert len(messages) == 2
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
def test_import_encrypted_bak_into_encrypted_acct(self, acfactory, tmpdir):
"""
Test that account passphrase isn't lost if backup failed to be imported.
See https://github.com/deltachat/deltachat-core-rust/issues/3379
"""
acct_passphrase = "passphrase1"
bak_passphrase = "passphrase2"
wrong_passphrase = "wrong_passprase"
backupdir = tmpdir.mkdir("backup")
ac1 = acfactory.get_pseudo_configured_account()
chat = ac1.create_contact("some1 <some1@example.org>").create_chat()
# send a text message
msg = chat.send_text("msg1")
# send a binary file
bin = tmpdir.join("some.bin")
with bin.open("w") as f:
f.write("\00123" * 10000)
msg = chat.send_file(bin.strpath)
contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact()
assert not backupdir.listdir()
ac1.stop_io()
path = ac1.export_all(backupdir.strpath, bak_passphrase)
assert os.path.exists(path)
ac2 = acfactory.get_unconfigured_account(closed=True)
ac2.open(acct_passphrase)
with pytest.raises(ImexFailed):
ac2.import_all(path, wrong_passphrase)
ac2.import_all(path, bak_passphrase)
# check data integrity
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
contact2 = contacts[0]
assert contact2.addr == "some1@example.org"
chat2 = contact2.create_chat()
messages = chat2.get_messages()
assert len(messages) == 2
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
ac2.shutdown()
# check that passphrase is not lost after import
ac2 = Account(ac2.db_path, logging=ac2._logging, closed=True)
ac2.open(acct_passphrase)
# check data integrity
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
contact2 = contacts[0]
assert contact2.addr == "some1@example.org"
chat2 = contact2.create_chat()
messages = chat2.get_messages()
assert len(messages) == 2
assert messages[0].text == "msg1"
assert os.path.exists(messages[1].filename)
def test_set_get_draft(self, chat1): def test_set_get_draft(self, chat1):
msg = Message.new_empty(chat1.account, "text") msg = Message.new_empty(chat1.account, "text")
msg1 = chat1.prepare_message(msg) msg1 = chat1.prepare_message(msg)