diff --git a/python/doc/conf.py b/python/doc/conf.py index 108a39bba..8ac98584f 100644 --- a/python/doc/conf.py +++ b/python/doc/conf.py @@ -288,10 +288,6 @@ intersphinx_mapping = {'http://docs.python.org/': None} autodoc_member_order = "bysource" # always document __init__ functions def skip(app, what, name, obj, skip, options): - import attr - if name == "__init__": - if not hasattr(obj.im_class, "__attrs_attrs__"): - return False return skip def setup(app): diff --git a/python/setup.py b/python/setup.py index b0e94339d..7d401b2a5 100644 --- a/python/setup.py +++ b/python/setup.py @@ -17,7 +17,7 @@ def main(): description='Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat', long_description=long_description, author='holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors', - install_requires=['cffi>=1.0.0', 'attrs', 'six'], + install_requires=['cffi>=1.0.0', 'six'], packages=setuptools.find_packages('src'), package_dir={'': 'src'}, cffi_modules=['src/deltachat/_build.py:ffibuilder'], diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index dd1d7da08..6080239c9 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -69,6 +69,10 @@ class Account(object): d[key.lower()] = value return d + def get_blob_dir(self): + """ return blob directory for this account. """ + return from_dc_charpointer(lib.dc_get_blobdir(self._dc_context)) + def set_config(self, name, value): """ set configuration values. @@ -142,15 +146,6 @@ class Account(object): self.check_is_configured() return Contact(self._dc_context, const.DC_CONTACT_ID_SELF) - def create_message(self, view_type): - """ create a new non persistent message. - - :param view_type: a string specifying "text", "video", - "image", "audio" or "file". - :returns: :class:`deltachat.message.Message` instance. - """ - return Message.new(self._dc_context, view_type) - def create_contact(self, email, name=None): """ create a (new) Contact. If there already is a Contact with that e-mail address, it is unblocked and its name is @@ -212,7 +207,7 @@ class Account(object): assert isinstance(contact, int) contact_id = contact chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id) - return Chat(self._dc_context, chat_id) + return Chat(self, chat_id) def create_chat_by_message(self, message): """ create or get an existing chat object for the @@ -229,7 +224,7 @@ class Account(object): assert isinstance(message, int) msg_id = message chat_id = lib.dc_create_chat_by_msg_id(self._dc_context, msg_id) - return Chat(self._dc_context, chat_id) + return Chat(self, chat_id) def create_group_chat(self, name, verified=False): """ create a new group chat object. @@ -241,7 +236,7 @@ class Account(object): """ bytes_name = name.encode("utf8") chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name) - return Chat(self._dc_context, chat_id) + return Chat(self, chat_id) def get_chats(self): """ return list of chats. @@ -257,15 +252,15 @@ class Account(object): chatlist = [] for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)): chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i) - chatlist.append(Chat(self._dc_context, chat_id)) + chatlist.append(Chat(self, chat_id)) return chatlist def get_deaddrop_chat(self): - return Chat(self._dc_context, const.DC_CHAT_ID_DEADDROP) + return Chat(self, const.DC_CHAT_ID_DEADDROP) def get_message_by_id(self, msg_id): """ return Message instance. """ - return Message.from_db(self._dc_context, msg_id) + return Message.from_db(self, msg_id) def mark_seen_messages(self, messages): """ mark the given set of messages as seen. diff --git a/python/src/deltachat/chatting.py b/python/src/deltachat/chatting.py index 5f65662ab..f63924c9e 100644 --- a/python/src/deltachat/chatting.py +++ b/python/src/deltachat/chatting.py @@ -1,24 +1,30 @@ """ chatting related objects: Contact, Chat, Message. """ -import os - +import mimetypes from . import props from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array from .capi import lib, ffi from . import const -import attr -from attr import validators as v from .message import Message -@attr.s class Contact(object): """ Delta-Chat Contact. You obtain instances of it through :class:`deltachat.account.Account`. """ - _dc_context = attr.ib(validator=v.instance_of(ffi.CData)) - id = attr.ib(validator=v.instance_of(int)) + def __init__(self, dc_context, id): + self._dc_context = dc_context + self.id = id + + def __eq__(self, other): + return self._dc_context == other._dc_context and self.id == other.id + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "".format(self.id, self.addr, self._dc_context) @property def _dc_contact(self): @@ -46,14 +52,26 @@ class Contact(object): return lib.dc_contact_is_verified(self._dc_contact) -@attr.s class Chat(object): """ Chat object which manages members and through which you can send and retrieve messages. You obtain instances of it through :class:`deltachat.account.Account`. """ - _dc_context = attr.ib(validator=v.instance_of(ffi.CData)) - id = attr.ib(validator=v.instance_of(int)) + + def __init__(self, account, id): + self.account = account + self._dc_context = account._dc_context + self.id = id + + def __eq__(self, other): + return self.id == getattr(other, "id", None) and \ + self._dc_context == getattr(other, "_dc_context", None) + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "".format(self.id, self.get_name(), self._dc_context) @property def _dc_chat(self): @@ -126,7 +144,7 @@ class Chat(object): msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg) if msg_id == 0: raise ValueError("message could not be send, does chat exist?") - return Message.from_db(self._dc_context, msg_id) + return Message.from_db(self.account, msg_id) def send_file(self, path, mime_type="application/octet-stream"): """ send a file and return the resulting Message instance. @@ -136,14 +154,8 @@ class Chat(object): :raises ValueError: if message can not be send/chat does not exist. :returns: the resulting :class:`deltachat.message.Message` instance """ - path = as_dc_charpointer(path) - mtype = as_dc_charpointer(mime_type) - msg = Message.new(self._dc_context, "file") - msg.set_file(path, mtype) - msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg) - if msg_id == 0: - raise ValueError("message could not be send, does chat exist?") - return Message.from_db(self._dc_context, msg_id) + msg = self.prepare_message_file(path=path, mime_type=mime_type) + return self.send_prepared(msg) def send_image(self, path): """ send an image message and return the resulting Message instance. @@ -152,14 +164,22 @@ class Chat(object): :raises ValueError: if message can not be send/chat does not exist. :returns: the resulting :class:`deltachat.message.Message` instance """ - if not os.path.exists(path): - raise ValueError("path does not exist: {!r}".format(path)) - msg = Message.new(self._dc_context, "image") - msg.set_file(path) - msg_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg) - return Message.from_db(self._dc_context, msg_id) + mime_type = mimetypes.guess_type(path)[0] + msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image") + return self.send_prepared(msg) - def prepare_file(self, path, mime_type=None, view_type="file"): + def prepare_message(self, msg): + """ create a new message. + + :param msg: the message to be prepared. + :returns: :class:`deltachat.message.Message` instance. + """ + msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg) + if msg_id == 0: + raise ValueError("message could not be prepared") + return Message.from_db(self.account, msg_id) + + def prepare_message_file(self, path, mime_type=None, view_type="file"): """ prepare a message for sending and return the resulting Message instance. To actually send the message, call :meth:`send_prepared`. @@ -171,14 +191,9 @@ class Chat(object): :raises ValueError: if message can not be prepared/chat does not exist. :returns: the resulting :class:`Message` instance """ - path = as_dc_charpointer(path) - mtype = as_dc_charpointer(mime_type) - msg = Message.new(self._dc_context, view_type) - msg.set_file(path, mtype) - msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg) - if msg_id == 0: - raise ValueError("message could not be prepared, does chat exist?") - return Message.from_db(self._dc_context, msg_id) + msg = Message.new(self.account, view_type) + msg.set_file(path, mime_type) + return self.prepare_message(msg) def send_prepared(self, message): """ send a previously prepared message. @@ -191,7 +206,30 @@ class Chat(object): msg_id = lib.dc_send_msg(self._dc_context, 0, message._dc_msg) if msg_id == 0: raise ValueError("message could not be sent") - return Message.from_db(self._dc_context, msg_id) + return Message.from_db(self.account, msg_id) + + def set_draft(self, message): + """ set message as draft. + + :param message: a :class:`Message` instance + :returns: None + """ + if message is None: + lib.dc_set_draft(self._dc_context, self.id, ffi.NULL) + else: + lib.dc_set_draft(self._dc_context, self.id, message._dc_msg) + + def get_draft(self): + """ get draft message for this chat. + + :param message: a :class:`Message` instance + :returns: Message object or None (if no draft available) + """ + x = lib.dc_get_draft(self._dc_context, self.id) + if x == ffi.NULL: + return None + dc_msg = ffi.gc(x, lib.dc_msg_unref) + return Message.from_dc_msg(self.account, dc_msg) def get_messages(self): """ return list of messages in this chat. @@ -202,7 +240,7 @@ class Chat(object): lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0), lib.dc_array_unref ) - return list(iter_array(dc_array, lambda x: Message.from_db(self._dc_context, x))) + return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x))) def count_fresh_messages(self): """ return number of fresh messages in this chat. diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py index 87d4e9445..715b138ee 100644 --- a/python/src/deltachat/message.py +++ b/python/src/deltachat/message.py @@ -1,46 +1,64 @@ """ chatting related objects: Contact, Chat, Message. """ import os +import shutil from . import props from .cutil import from_dc_charpointer, as_dc_charpointer from .capi import lib, ffi from . import const from datetime import datetime -import attr -from attr import validators as v -@attr.s class Message(object): """ Message object. You obtain instances of it through :class:`deltachat.account.Account` or :class:`deltachat.chatting.Chat`. """ - _dc_context = attr.ib(validator=v.instance_of(ffi.CData)) - try: - id = attr.ib(validator=v.instance_of((int, long))) - except NameError: # py35 - id = attr.ib(validator=v.instance_of(int)) + def __init__(self, account, id=None, dc_msg=None): + self.account = account + self._dc_context = account._dc_context + if dc_msg is not None: + self._cache_dc_msg = self._dc_msg_volatile = dc_msg + id = lib.dc_msg_get_id(dc_msg) + assert id is not None + self.id = id + assert isinstance(self._dc_context, ffi.CData) + assert int(id) >= 0 + + def __eq__(self, other): + return self.account == other.account and self.id == other.id + + def __repr__(self): + return "".format(self.id, self._dc_context) @property def _dc_msg(self): if self.id > 0: - return ffi.gc( - lib.dc_get_msg(self._dc_context, self.id), - lib.dc_msg_unref - ) + if not hasattr(self, "_cache_dc_msg"): + self._cache_dc_msg = ffi.gc( + lib.dc_get_msg(self._dc_context, self.id), + lib.dc_msg_unref + ) + return self._cache_dc_msg return self._dc_msg_volatile @classmethod - def from_db(cls, _dc_context, id): + def from_db(cls, account, id): + assert hasattr(account, "_dc_context") assert id > 0 - return cls(_dc_context, id) + return cls(account, id) @classmethod - def new(cls, dc_context, view_type): - """ create a non-persistent method. """ - msg = cls(dc_context, 0) + def from_dc_msg(cls, account, dc_msg): + assert hasattr(account, "_dc_context") + return cls(account, dc_msg=dc_msg) + + @classmethod + def new(cls, account, view_type): + """ create a non-persistent message. """ + dc_context = account._dc_context + msg = cls(account=account, id=0) view_type_code = MessageType.get_typecode(view_type) msg._dc_msg_volatile = ffi.gc( lib.dc_msg_new(dc_context, view_type_code), @@ -62,7 +80,9 @@ class Message(object): def set_text(self, text): """set text of this message. """ - return lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text)) + assert self.id > 0, "message not prepared" + assert self.get_state().is_out_preparing() + lib.dc_msg_set_text(self._dc_msg, as_dc_charpointer(text)) @props.with_doc def filename(self): @@ -70,9 +90,23 @@ class Message(object): return from_dc_charpointer(lib.dc_msg_get_file(self._dc_msg)) def set_file(self, path, mime_type=None): - """set file for this message. """ - mtype = ffi.NULL if mime_type is None else mime_type - assert os.path.exists(path) + """set file for this message from path and mime_type. """ + mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type) + if not os.path.exists(path): + raise ValueError("path does not exist: {!r}".format(path)) + blobdir = self.account.get_blob_dir() + if not path.startswith(blobdir): + for i in range(50): + ext = "" if i == 0 else "-" + str(i) + dest = os.path.join(blobdir, os.path.basename(path) + ext) + if os.path.exists(dest): + continue + shutil.copyfile(path, dest) + break + else: + raise ValueError("could not create blobdir-path for {}".format(path)) + path = dest + assert path.startswith(blobdir), path lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype) @props.with_doc @@ -91,12 +125,20 @@ class Message(object): :returns: a :class:`deltachat.message.MessageType` instance. """ + assert self.id > 0 return MessageType(lib.dc_msg_get_viewtype(self._dc_msg)) def is_setup_message(self): """ return True if this message is a setup message. """ return lib.dc_msg_is_setupmessage(self._dc_msg) + def get_message_info(self): + """ Return informational text for a single message. + + The text is multiline and may contain eg. the raw text of the message. + """ + return from_dc_charpointer(lib.dc_get_msg_info(self._dc_context, self.id)) + def continue_key_transfer(self, setup_code): """ extract key and use it as primary key for this account. """ lib.dc_continue_key_transfer(self._dc_context, self.id, as_dc_charpointer(setup_code)) @@ -144,7 +186,7 @@ class Message(object): """ from .chatting import Chat chat_id = lib.dc_msg_get_chat_id(self._dc_msg) - return Chat(self._dc_context, chat_id) + return Chat(self.account, chat_id) def get_sender_contact(self): """return the contact of who wrote the message. @@ -156,10 +198,8 @@ class Message(object): return Contact(self._dc_context, contact_id) -@attr.s class MessageType(object): """ DeltaChat message type, with is_* methods. """ - _type = attr.ib(validator=v.instance_of(int)) _mapping = { const.DC_MSG_TEXT: 'text', const.DC_MSG_IMAGE: 'image', @@ -169,6 +209,12 @@ class MessageType(object): const.DC_MSG_FILE: 'file' } + def __init__(self, _type): + self._type = _type + + def __eq__(self, other): + return self._type == getattr(other, "_type", None) + @classmethod def get_typecode(cls, view_type): for code, value in cls._mapping.items(): @@ -206,15 +252,24 @@ class MessageType(object): return self._type == const.DC_MSG_FILE -@attr.s class MessageState(object): """ Current Message In/Out state, updated on each call of is_* methods. """ - message = attr.ib(validator=v.instance_of(Message)) + def __init__(self, message): + self.message = message + + def __eq__(self, other): + return self.message == getattr(other, "message", None) @property def _msgstate(self): - return lib.dc_msg_get_state(self.message._dc_msg) + if self.message.id == 0: + return lib.dc_msg_get_state(self.message._dc_msg) + dc_msg = ffi.gc( + lib.dc_get_msg(self.message._dc_context, self.message.id), + lib.dc_msg_unref + ) + return lib.dc_msg_get_state(dc_msg) def is_in_fresh(self): """ return True if Message is incoming fresh message (un-noticed). diff --git a/python/tests/test_account.py b/python/tests/test_account.py index fc7956377..66c046100 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -2,11 +2,12 @@ from __future__ import print_function import pytest import os from deltachat import const, Account +from deltachat.message import Message from datetime import datetime, timedelta from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection -class TestOfflineAccount: +class TestOfflineAccountBasic: def test_wrong_db(self, tmpdir): p = tmpdir.join("hello.db") p.write("123") @@ -57,9 +58,15 @@ class TestOfflineAccount: with pytest.raises(KeyError): ac1.get_config("123123") + +class TestOfflineContact: def test_contact_attr(self, acfactory): ac1 = acfactory.get_configured_offline_account() contact1 = ac1.create_contact(email="some1@hello.com", name="some1") + contact2 = ac1.create_contact(email="some1@hello.com", name="some1") + str(contact1) + repr(contact1) + assert contact1 == contact2 assert contact1.id assert contact1.addr == "some1@hello.com" assert contact1.display_name == "some1" @@ -89,26 +96,38 @@ class TestOfflineAccount: chat.send_text("one messae") assert not ac1.delete_contact(contact1) - def test_chat(self, acfactory): - ac1 = acfactory.get_configured_offline_account() + +class TestOfflineChat: + @pytest.fixture + def ac1(self, acfactory): + return acfactory.get_configured_offline_account() + + @pytest.fixture + def chat1(self, ac1): contact1 = ac1.create_contact("some1@hello.com", name="some1") chat = ac1.create_chat_by_contact(contact1) assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id + return chat + def test_display(self, chat1): + str(chat1) + repr(chat1) + + def test_chat_idempotent(self, chat1, ac1): + contact1 = chat1.get_contacts()[0] chat2 = ac1.create_chat_by_contact(contact1.id) - assert chat2.id == chat.id - assert chat2.get_name() == chat.get_name() - assert chat == chat2 - assert not (chat != chat2) + assert chat2.id == chat1.id + assert chat2.get_name() == chat1.get_name() + assert chat1 == chat2 + assert not (chat1 != chat2) for ichat in ac1.get_chats(): - if ichat.id == chat.id: + if ichat.id == chat1.id: break else: pytest.fail("could not find chat") - def test_group_chat_creation(self, acfactory): - ac1 = acfactory.get_configured_offline_account() + def test_group_chat_creation(self, ac1): contact1 = ac1.create_contact("some1@hello.com", name="some1") contact2 = ac1.create_contact("some2@hello.com", name="some2") chat = ac1.create_group_chat(name="title1") @@ -121,29 +140,46 @@ class TestOfflineAccount: chat.set_name("title2") assert chat.get_name() == "title2" - def test_delete_and_send_fails(self, acfactory): - ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact("some1@hello.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) - chat.delete() + def test_delete_and_send_fails(self, ac1, chat1): + chat1.delete() ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") with pytest.raises(ValueError): - chat.send_text("msg1") + chat1.send_text("msg1") - def test_create_message(self, acfactory): - ac1 = acfactory.get_configured_offline_account() - message = ac1.create_message("text") - assert message.id == 0 - assert message._dc_msg is message._dc_msg - message.set_text("hello") - assert message.text == "hello" - assert message.id == 0 + def test_prepare_message_and_send(self, ac1, chat1): + msg = chat1.prepare_message(Message.new(chat1.account, "text")) + msg.set_text("hello world") + assert msg.text == "hello world" + assert msg.id > 0 + msg = chat1.send_prepared(msg) + assert "Sent" in msg.get_message_info() + str(msg) + repr(msg) + assert msg == ac1.get_message_by_id(msg.id) - def test_message(self, acfactory): - ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact("some1@hello.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) - msg = chat.send_text("msg1") + def test_prepare_file(self, ac1, chat1): + blobdir = ac1.get_blob_dir() + p = os.path.join(blobdir, "somedata.txt") + with open(p, "w") as f: + f.write("some data") + message = chat1.prepare_message_file(p) + assert message.id > 0 + message.set_text("hello world") + assert message.get_state().is_out_preparing() + assert message.text == "hello world" + msg = chat1.send_prepared(message) + s = msg.get_message_info() + assert "Sent" in s + + def test_message_eq_contains(self, chat1): + msg = chat1.send_text("msg1") + assert msg in chat1.get_messages() + assert not (msg not in chat1.get_messages()) + str(msg) + repr(msg) + + def test_message_send_text(self, chat1): + msg = chat1.send_text("msg1") assert msg assert msg.view_type.is_text() assert msg.view_type.name == "text" @@ -161,23 +197,17 @@ class TestOfflineAccount: assert not msg_state.is_out_delivered() assert not msg_state.is_out_mdn_received() - def test_create_chat_by_mssage_id(self, acfactory): - ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact("some1@hello.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) - msg = chat.send_text("msg1") - assert chat == ac1.create_chat_by_message(msg) - assert chat == ac1.create_chat_by_message(msg.id) + def test_create_chat_by_message_id(self, ac1, chat1): + msg = chat1.send_text("msg1") + assert chat1 == ac1.create_chat_by_message(msg) + assert chat1 == ac1.create_chat_by_message(msg.id) - def test_message_image(self, acfactory, data, lp): - ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact("some1@hello.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) + def test_message_image(self, chat1, data, lp): with pytest.raises(ValueError): - chat.send_image(path="notexists") + chat1.send_image(path="notexists") fn = data.get_path("d.png") lp.sec("sending image") - msg = chat.send_image(fn) + msg = chat1.send_image(fn) assert msg.view_type.name == "image" assert msg assert msg.id > 0 @@ -189,13 +219,10 @@ class TestOfflineAccount: ("text/plain", "text/plain"), ("image/png", "image/png"), ]) - def test_message_file(self, acfactory, data, lp, typein, typeout): - ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact("some1@hello.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) + def test_message_file(self, ac1, chat1, data, lp, typein, typeout): lp.sec("sending file") fn = data.get_path("r.txt") - msg = chat.send_file(fn, typein) + msg = chat1.send_file(fn, typein) assert msg assert msg.id > 0 assert msg.view_type.name == "file" @@ -203,6 +230,9 @@ class TestOfflineAccount: assert os.path.exists(msg.filename) assert msg.filename.endswith(msg.basename) assert msg.filemime == typeout + msg2 = chat1.send_file(fn, typein) + assert msg2 != msg + assert msg2.filename != msg.filename def test_create_chat_mismatch(self, acfactory): ac1 = acfactory.get_configured_offline_account() @@ -215,12 +245,9 @@ class TestOfflineAccount: with pytest.raises(ValueError): ac2.create_chat_by_message(msg) - def test_chat_message_distinctions(self, acfactory): - ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact("some1@hello.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) + def test_chat_message_distinctions(self, ac1, chat1): past1s = datetime.utcnow() - timedelta(seconds=1) - msg = chat.send_text("msg1") + msg = chat1.send_text("msg1") ts = msg.time_sent assert msg.time_received is None assert ts.strftime("Y") @@ -228,8 +255,7 @@ class TestOfflineAccount: contact = msg.get_sender_contact() assert contact == ac1.get_self_contact() - def test_basic_configure_ok_addr_setting_forbidden(self, acfactory): - ac1 = acfactory.get_configured_offline_account() + def test_basic_configure_ok_addr_setting_forbidden(self, ac1): assert ac1.get_config("mail_pw") assert ac1.is_configured() with pytest.raises(ValueError): @@ -268,11 +294,21 @@ class TestOfflineAccount: assert messages[0].text == "msg1" assert os.path.exists(messages[1].filename) - def test_ac_setup_message_fails(self, acfactory): - ac1 = acfactory.get_configured_offline_account() + def test_ac_setup_message_fails(self, ac1): with pytest.raises(RuntimeError): ac1.initiate_key_transfer() + def test_set_get_draft(self, chat1): + msg = Message.new(chat1.account, "text") + msg1 = chat1.prepare_message(msg) + msg1.set_text("hello") + chat1.set_draft(msg1) + msg1.set_text("obsolete") + msg2 = chat1.get_draft() + assert msg2.text == "hello" + chat1.set_draft(None) + assert chat1.get_draft() is None + class TestOnlineAccount: def test_one_account_init(self, acfactory): @@ -293,7 +329,7 @@ class TestOnlineAccount: ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED") assert ev[1] == msg_out.id - def test_two_acocunts_send_receive(self, acfactory): + def test_two_accounts_send_receive(self, acfactory): ac1 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account() c2 = ac1.create_contact(email=ac2.get_config("addr")) @@ -390,7 +426,6 @@ class TestOnlineAccount: lp.step("1") ac1._evlogger.get_matching("DC_EVENT_MSG_READ") lp.step("2") - # ac1._evlogger.get_info_matching("Message marked as seen") assert msg_out.get_state().is_out_mdn_received() def test_saved_mime_on_received_message(self, acfactory, lp): @@ -473,7 +508,7 @@ class TestOnlineAccount: wait_configuration_progress(ac1, 1000) assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] setup_code = ac1.initiate_key_transfer() - ac2._evlogger.set_timeout(10) + ac2._evlogger.set_timeout(30) ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED") msg = ac2.get_message_by_id(ev[2]) assert msg.is_setup_message() diff --git a/python/tests/test_increation.py b/python/tests/test_increation.py index b7181e2b4..24c829614 100644 --- a/python/tests/test_increation.py +++ b/python/tests/test_increation.py @@ -1,6 +1,4 @@ from __future__ import print_function -import os -import shutil from filecmp import cmp from deltachat import const from conftest import wait_configuration_progress, wait_msgs_changed @@ -13,17 +11,14 @@ class TestInCreation: wait_configuration_progress(ac1, 1000) wait_configuration_progress(ac2, 1000) - blobdir = ac1.get_blobdir() - c2 = ac1.create_contact(email=ac2.get_config("addr")) chat = ac1.create_chat_by_contact(c2) assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL wait_msgs_changed(ac1, 0, 0) # why no chat id? lp.sec("create a message with a file in creation") - path = os.path.join(blobdir, "d.png") - open(path, 'a').close() - prepared_original = chat.prepare_file(path) + path = data.get_path("d.png") + prepared_original = chat.prepare_message_file(path) assert prepared_original.get_state().is_out_preparing() wait_msgs_changed(ac1, chat.id, prepared_original.id) @@ -37,11 +32,11 @@ class TestInCreation: forwarded_id = wait_msgs_changed(ac1, chat2.id) if forwarded_id == 0: forwarded_id = wait_msgs_changed(ac1, chat2.id) + assert forwarded_id forwarded_msg = ac1.get_message_by_id(forwarded_id) assert forwarded_msg.get_state().is_out_preparing() lp.sec("finish creating the file and send it") - shutil.copy(data.get_path("d.png"), path) sent_original = chat.send_prepared(prepared_original) assert sent_original.id == prepared_original.id state = sent_original.get_state() @@ -61,11 +56,13 @@ class TestInCreation: assert ev[1] == chat2.id assert ev[2] == forwarded_id - lp.sec("wait for both messages to arrive") + lp.sec("wait1 for original or forwarded messages to arrive") ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL received_original = ac2.get_message_by_id(ev1[2]) assert cmp(received_original.filename, path, False) + + lp.sec("wait2 for original or forwarded messages to arrive") ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL assert ev2[1] != ev1[1] diff --git a/python/tox.ini b/python/tox.ini index 49ce8d78e..bd350c5d3 100644 --- a/python/tox.ini +++ b/python/tox.ini @@ -8,7 +8,7 @@ envlist = [testenv] commands = - pytest -rsXx {posargs:tests} + pytest -v -rsXx {posargs:tests} python tests/package_wheels.py {toxworkdir}/wheelhouse passenv = TRAVIS