- properly support prepare-msg API and implement get_message_info()

- remove usage of "attr.s" across the code
- make msg.set_file() copy a file into blobdir if it isn't already
- regroup tests
- add set_draft/get_draft API
This commit is contained in:
holger krekel
2019-07-24 16:37:24 +02:00
parent c7ebf6de09
commit 9836e73683
8 changed files with 268 additions and 152 deletions

View File

@@ -288,10 +288,6 @@ intersphinx_mapping = {'http://docs.python.org/': None}
autodoc_member_order = "bysource" autodoc_member_order = "bysource"
# always document __init__ functions # always document __init__ functions
def skip(app, what, name, obj, skip, options): 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 return skip
def setup(app): def setup(app):

View File

@@ -17,7 +17,7 @@ def main():
description='Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat', description='Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat',
long_description=long_description, long_description=long_description,
author='holger krekel, Floris Bruynooghe, Bjoern Petersen and contributors', 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'), packages=setuptools.find_packages('src'),
package_dir={'': 'src'}, package_dir={'': 'src'},
cffi_modules=['src/deltachat/_build.py:ffibuilder'], cffi_modules=['src/deltachat/_build.py:ffibuilder'],

View File

@@ -69,6 +69,10 @@ class Account(object):
d[key.lower()] = value d[key.lower()] = value
return d 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): def set_config(self, name, value):
""" set configuration values. """ set configuration values.
@@ -142,15 +146,6 @@ class Account(object):
self.check_is_configured() self.check_is_configured()
return Contact(self._dc_context, const.DC_CONTACT_ID_SELF) 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): def create_contact(self, email, name=None):
""" create a (new) Contact. If there already is a Contact """ create a (new) Contact. If there already is a Contact
with that e-mail address, it is unblocked and its name is with that e-mail address, it is unblocked and its name is
@@ -212,7 +207,7 @@ class Account(object):
assert isinstance(contact, int) assert isinstance(contact, int)
contact_id = contact contact_id = contact
chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id) 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): def create_chat_by_message(self, message):
""" create or get an existing chat object for the """ create or get an existing chat object for the
@@ -229,7 +224,7 @@ class Account(object):
assert isinstance(message, int) assert isinstance(message, int)
msg_id = message msg_id = message
chat_id = lib.dc_create_chat_by_msg_id(self._dc_context, msg_id) 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): def create_group_chat(self, name, verified=False):
""" create a new group chat object. """ create a new group chat object.
@@ -241,7 +236,7 @@ class Account(object):
""" """
bytes_name = name.encode("utf8") bytes_name = name.encode("utf8")
chat_id = lib.dc_create_group_chat(self._dc_context, verified, bytes_name) 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): def get_chats(self):
""" return list of chats. """ return list of chats.
@@ -257,15 +252,15 @@ class Account(object):
chatlist = [] chatlist = []
for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)): for i in range(0, lib.dc_chatlist_get_cnt(dc_chatlist)):
chat_id = lib.dc_chatlist_get_chat_id(dc_chatlist, i) 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 return chatlist
def get_deaddrop_chat(self): 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): def get_message_by_id(self, msg_id):
""" return Message instance. """ """ 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): def mark_seen_messages(self, messages):
""" mark the given set of messages as seen. """ mark the given set of messages as seen.

View File

@@ -1,24 +1,30 @@
""" chatting related objects: Contact, Chat, Message. """ """ chatting related objects: Contact, Chat, Message. """
import os import mimetypes
from . import props from . import props
from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array
from .capi import lib, ffi from .capi import lib, ffi
from . import const from . import const
import attr
from attr import validators as v
from .message import Message from .message import Message
@attr.s
class Contact(object): class Contact(object):
""" Delta-Chat Contact. """ Delta-Chat Contact.
You obtain instances of it through :class:`deltachat.account.Account`. You obtain instances of it through :class:`deltachat.account.Account`.
""" """
_dc_context = attr.ib(validator=v.instance_of(ffi.CData)) def __init__(self, dc_context, id):
id = attr.ib(validator=v.instance_of(int)) 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 "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context)
@property @property
def _dc_contact(self): def _dc_contact(self):
@@ -46,14 +52,26 @@ class Contact(object):
return lib.dc_contact_is_verified(self._dc_contact) return lib.dc_contact_is_verified(self._dc_contact)
@attr.s
class Chat(object): class Chat(object):
""" Chat object which manages members and through which you can send and retrieve messages. """ Chat object which manages members and through which you can send and retrieve messages.
You obtain instances of it through :class:`deltachat.account.Account`. 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 "<Chat id={} name={} dc_context={}>".format(self.id, self.get_name(), self._dc_context)
@property @property
def _dc_chat(self): def _dc_chat(self):
@@ -126,7 +144,7 @@ class Chat(object):
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg) msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg)
if msg_id == 0: if msg_id == 0:
raise ValueError("message could not be send, does chat exist?") 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"): def send_file(self, path, mime_type="application/octet-stream"):
""" send a file and return the resulting Message instance. """ 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. :raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance :returns: the resulting :class:`deltachat.message.Message` instance
""" """
path = as_dc_charpointer(path) msg = self.prepare_message_file(path=path, mime_type=mime_type)
mtype = as_dc_charpointer(mime_type) return self.send_prepared(msg)
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)
def send_image(self, path): def send_image(self, path):
""" send an image message and return the resulting Message instance. """ 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. :raises ValueError: if message can not be send/chat does not exist.
:returns: the resulting :class:`deltachat.message.Message` instance :returns: the resulting :class:`deltachat.message.Message` instance
""" """
if not os.path.exists(path): mime_type = mimetypes.guess_type(path)[0]
raise ValueError("path does not exist: {!r}".format(path)) msg = self.prepare_message_file(path=path, mime_type=mime_type, view_type="image")
msg = Message.new(self._dc_context, "image") return self.send_prepared(msg)
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)
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. """ prepare a message for sending and return the resulting Message instance.
To actually send the message, call :meth:`send_prepared`. 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. :raises ValueError: if message can not be prepared/chat does not exist.
:returns: the resulting :class:`Message` instance :returns: the resulting :class:`Message` instance
""" """
path = as_dc_charpointer(path) msg = Message.new(self.account, view_type)
mtype = as_dc_charpointer(mime_type) msg.set_file(path, mime_type)
msg = Message.new(self._dc_context, view_type) return self.prepare_message(msg)
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)
def send_prepared(self, message): def send_prepared(self, message):
""" send a previously prepared 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) msg_id = lib.dc_send_msg(self._dc_context, 0, message._dc_msg)
if msg_id == 0: if msg_id == 0:
raise ValueError("message could not be sent") 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): def get_messages(self):
""" return list of messages in this chat. """ 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_get_chat_msgs(self._dc_context, self.id, 0, 0),
lib.dc_array_unref 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): def count_fresh_messages(self):
""" return number of fresh messages in this chat. """ return number of fresh messages in this chat.

View File

@@ -1,46 +1,64 @@
""" chatting related objects: Contact, Chat, Message. """ """ chatting related objects: Contact, Chat, Message. """
import os import os
import shutil
from . import props from . import props
from .cutil import from_dc_charpointer, as_dc_charpointer from .cutil import from_dc_charpointer, as_dc_charpointer
from .capi import lib, ffi from .capi import lib, ffi
from . import const from . import const
from datetime import datetime from datetime import datetime
import attr
from attr import validators as v
@attr.s
class Message(object): class Message(object):
""" Message object. """ Message object.
You obtain instances of it through :class:`deltachat.account.Account` or You obtain instances of it through :class:`deltachat.account.Account` or
:class:`deltachat.chatting.Chat`. :class:`deltachat.chatting.Chat`.
""" """
_dc_context = attr.ib(validator=v.instance_of(ffi.CData)) def __init__(self, account, id=None, dc_msg=None):
try: self.account = account
id = attr.ib(validator=v.instance_of((int, long))) self._dc_context = account._dc_context
except NameError: # py35 if dc_msg is not None:
id = attr.ib(validator=v.instance_of(int)) 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 "<Message id={} dc_context={}>".format(self.id, self._dc_context)
@property @property
def _dc_msg(self): def _dc_msg(self):
if self.id > 0: if self.id > 0:
return ffi.gc( if not hasattr(self, "_cache_dc_msg"):
lib.dc_get_msg(self._dc_context, self.id), self._cache_dc_msg = ffi.gc(
lib.dc_msg_unref lib.dc_get_msg(self._dc_context, self.id),
) lib.dc_msg_unref
)
return self._cache_dc_msg
return self._dc_msg_volatile return self._dc_msg_volatile
@classmethod @classmethod
def from_db(cls, _dc_context, id): def from_db(cls, account, id):
assert hasattr(account, "_dc_context")
assert id > 0 assert id > 0
return cls(_dc_context, id) return cls(account, id)
@classmethod @classmethod
def new(cls, dc_context, view_type): def from_dc_msg(cls, account, dc_msg):
""" create a non-persistent method. """ assert hasattr(account, "_dc_context")
msg = cls(dc_context, 0) 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) view_type_code = MessageType.get_typecode(view_type)
msg._dc_msg_volatile = ffi.gc( msg._dc_msg_volatile = ffi.gc(
lib.dc_msg_new(dc_context, view_type_code), lib.dc_msg_new(dc_context, view_type_code),
@@ -62,7 +80,9 @@ class Message(object):
def set_text(self, text): def set_text(self, text):
"""set text of this message. """ """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 @props.with_doc
def filename(self): def filename(self):
@@ -70,9 +90,23 @@ class Message(object):
return from_dc_charpointer(lib.dc_msg_get_file(self._dc_msg)) return from_dc_charpointer(lib.dc_msg_get_file(self._dc_msg))
def set_file(self, path, mime_type=None): def set_file(self, path, mime_type=None):
"""set file for this message. """ """set file for this message from path and mime_type. """
mtype = ffi.NULL if mime_type is None else mime_type mtype = ffi.NULL if mime_type is None else as_dc_charpointer(mime_type)
assert os.path.exists(path) 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) lib.dc_msg_set_file(self._dc_msg, as_dc_charpointer(path), mtype)
@props.with_doc @props.with_doc
@@ -91,12 +125,20 @@ class Message(object):
:returns: a :class:`deltachat.message.MessageType` instance. :returns: a :class:`deltachat.message.MessageType` instance.
""" """
assert self.id > 0
return MessageType(lib.dc_msg_get_viewtype(self._dc_msg)) return MessageType(lib.dc_msg_get_viewtype(self._dc_msg))
def is_setup_message(self): def is_setup_message(self):
""" return True if this message is a setup message. """ """ return True if this message is a setup message. """
return lib.dc_msg_is_setupmessage(self._dc_msg) 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): 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. """
lib.dc_continue_key_transfer(self._dc_context, self.id, as_dc_charpointer(setup_code)) 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 from .chatting import Chat
chat_id = lib.dc_msg_get_chat_id(self._dc_msg) 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): def get_sender_contact(self):
"""return the contact of who wrote the message. """return the contact of who wrote the message.
@@ -156,10 +198,8 @@ class Message(object):
return Contact(self._dc_context, contact_id) return Contact(self._dc_context, contact_id)
@attr.s
class MessageType(object): class MessageType(object):
""" DeltaChat message type, with is_* methods. """ """ DeltaChat message type, with is_* methods. """
_type = attr.ib(validator=v.instance_of(int))
_mapping = { _mapping = {
const.DC_MSG_TEXT: 'text', const.DC_MSG_TEXT: 'text',
const.DC_MSG_IMAGE: 'image', const.DC_MSG_IMAGE: 'image',
@@ -169,6 +209,12 @@ class MessageType(object):
const.DC_MSG_FILE: 'file' const.DC_MSG_FILE: 'file'
} }
def __init__(self, _type):
self._type = _type
def __eq__(self, other):
return self._type == getattr(other, "_type", None)
@classmethod @classmethod
def get_typecode(cls, view_type): def get_typecode(cls, view_type):
for code, value in cls._mapping.items(): for code, value in cls._mapping.items():
@@ -206,15 +252,24 @@ class MessageType(object):
return self._type == const.DC_MSG_FILE return self._type == const.DC_MSG_FILE
@attr.s
class MessageState(object): class MessageState(object):
""" Current Message In/Out state, updated on each call of is_* methods. """ 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 @property
def _msgstate(self): 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): def is_in_fresh(self):
""" return True if Message is incoming fresh message (un-noticed). """ return True if Message is incoming fresh message (un-noticed).

View File

@@ -2,11 +2,12 @@ from __future__ import print_function
import pytest import pytest
import os import os
from deltachat import const, Account from deltachat import const, Account
from deltachat.message import Message
from datetime import datetime, timedelta from datetime import datetime, timedelta
from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection
class TestOfflineAccount: class TestOfflineAccountBasic:
def test_wrong_db(self, tmpdir): def test_wrong_db(self, tmpdir):
p = tmpdir.join("hello.db") p = tmpdir.join("hello.db")
p.write("123") p.write("123")
@@ -57,9 +58,15 @@ class TestOfflineAccount:
with pytest.raises(KeyError): with pytest.raises(KeyError):
ac1.get_config("123123") ac1.get_config("123123")
class TestOfflineContact:
def test_contact_attr(self, acfactory): def test_contact_attr(self, acfactory):
ac1 = acfactory.get_configured_offline_account() ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact(email="some1@hello.com", name="some1") 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.id
assert contact1.addr == "some1@hello.com" assert contact1.addr == "some1@hello.com"
assert contact1.display_name == "some1" assert contact1.display_name == "some1"
@@ -89,26 +96,38 @@ class TestOfflineAccount:
chat.send_text("one messae") chat.send_text("one messae")
assert not ac1.delete_contact(contact1) 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") contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1) chat = ac1.create_chat_by_contact(contact1)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL, chat.id 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) chat2 = ac1.create_chat_by_contact(contact1.id)
assert chat2.id == chat.id assert chat2.id == chat1.id
assert chat2.get_name() == chat.get_name() assert chat2.get_name() == chat1.get_name()
assert chat == chat2 assert chat1 == chat2
assert not (chat != chat2) assert not (chat1 != chat2)
for ichat in ac1.get_chats(): for ichat in ac1.get_chats():
if ichat.id == chat.id: if ichat.id == chat1.id:
break break
else: else:
pytest.fail("could not find chat") pytest.fail("could not find chat")
def test_group_chat_creation(self, acfactory): def test_group_chat_creation(self, ac1):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1") contact1 = ac1.create_contact("some1@hello.com", name="some1")
contact2 = ac1.create_contact("some2@hello.com", name="some2") contact2 = ac1.create_contact("some2@hello.com", name="some2")
chat = ac1.create_group_chat(name="title1") chat = ac1.create_group_chat(name="title1")
@@ -121,29 +140,46 @@ class TestOfflineAccount:
chat.set_name("title2") chat.set_name("title2")
assert chat.get_name() == "title2" assert chat.get_name() == "title2"
def test_delete_and_send_fails(self, acfactory): def test_delete_and_send_fails(self, ac1, chat1):
ac1 = acfactory.get_configured_offline_account() chat1.delete()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.delete()
ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
with pytest.raises(ValueError): with pytest.raises(ValueError):
chat.send_text("msg1") chat1.send_text("msg1")
def test_create_message(self, acfactory): def test_prepare_message_and_send(self, ac1, chat1):
ac1 = acfactory.get_configured_offline_account() msg = chat1.prepare_message(Message.new(chat1.account, "text"))
message = ac1.create_message("text") msg.set_text("hello world")
assert message.id == 0 assert msg.text == "hello world"
assert message._dc_msg is message._dc_msg assert msg.id > 0
message.set_text("hello") msg = chat1.send_prepared(msg)
assert message.text == "hello" assert "Sent" in msg.get_message_info()
assert message.id == 0 str(msg)
repr(msg)
assert msg == ac1.get_message_by_id(msg.id)
def test_message(self, acfactory): def test_prepare_file(self, ac1, chat1):
ac1 = acfactory.get_configured_offline_account() blobdir = ac1.get_blob_dir()
contact1 = ac1.create_contact("some1@hello.com", name="some1") p = os.path.join(blobdir, "somedata.txt")
chat = ac1.create_chat_by_contact(contact1) with open(p, "w") as f:
msg = chat.send_text("msg1") 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
assert msg.view_type.is_text() assert msg.view_type.is_text()
assert msg.view_type.name == "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_delivered()
assert not msg_state.is_out_mdn_received() assert not msg_state.is_out_mdn_received()
def test_create_chat_by_mssage_id(self, acfactory): def test_create_chat_by_message_id(self, ac1, chat1):
ac1 = acfactory.get_configured_offline_account() msg = chat1.send_text("msg1")
contact1 = ac1.create_contact("some1@hello.com", name="some1") assert chat1 == ac1.create_chat_by_message(msg)
chat = ac1.create_chat_by_contact(contact1) assert chat1 == ac1.create_chat_by_message(msg.id)
msg = chat.send_text("msg1")
assert chat == ac1.create_chat_by_message(msg)
assert chat == ac1.create_chat_by_message(msg.id)
def test_message_image(self, acfactory, data, lp): def test_message_image(self, chat1, data, lp):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
with pytest.raises(ValueError): with pytest.raises(ValueError):
chat.send_image(path="notexists") chat1.send_image(path="notexists")
fn = data.get_path("d.png") fn = data.get_path("d.png")
lp.sec("sending image") lp.sec("sending image")
msg = chat.send_image(fn) msg = chat1.send_image(fn)
assert msg.view_type.name == "image" assert msg.view_type.name == "image"
assert msg assert msg
assert msg.id > 0 assert msg.id > 0
@@ -189,13 +219,10 @@ class TestOfflineAccount:
("text/plain", "text/plain"), ("text/plain", "text/plain"),
("image/png", "image/png"), ("image/png", "image/png"),
]) ])
def test_message_file(self, acfactory, data, lp, typein, typeout): def test_message_file(self, ac1, chat1, 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)
lp.sec("sending file") lp.sec("sending file")
fn = data.get_path("r.txt") fn = data.get_path("r.txt")
msg = chat.send_file(fn, typein) msg = chat1.send_file(fn, typein)
assert msg assert msg
assert msg.id > 0 assert msg.id > 0
assert msg.view_type.name == "file" assert msg.view_type.name == "file"
@@ -203,6 +230,9 @@ class TestOfflineAccount:
assert os.path.exists(msg.filename) assert os.path.exists(msg.filename)
assert msg.filename.endswith(msg.basename) assert msg.filename.endswith(msg.basename)
assert msg.filemime == typeout 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): def test_create_chat_mismatch(self, acfactory):
ac1 = acfactory.get_configured_offline_account() ac1 = acfactory.get_configured_offline_account()
@@ -215,12 +245,9 @@ class TestOfflineAccount:
with pytest.raises(ValueError): with pytest.raises(ValueError):
ac2.create_chat_by_message(msg) ac2.create_chat_by_message(msg)
def test_chat_message_distinctions(self, acfactory): def test_chat_message_distinctions(self, ac1, chat1):
ac1 = acfactory.get_configured_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
past1s = datetime.utcnow() - timedelta(seconds=1) past1s = datetime.utcnow() - timedelta(seconds=1)
msg = chat.send_text("msg1") msg = chat1.send_text("msg1")
ts = msg.time_sent ts = msg.time_sent
assert msg.time_received is None assert msg.time_received is None
assert ts.strftime("Y") assert ts.strftime("Y")
@@ -228,8 +255,7 @@ class TestOfflineAccount:
contact = msg.get_sender_contact() contact = msg.get_sender_contact()
assert contact == ac1.get_self_contact() assert contact == ac1.get_self_contact()
def test_basic_configure_ok_addr_setting_forbidden(self, acfactory): def test_basic_configure_ok_addr_setting_forbidden(self, ac1):
ac1 = acfactory.get_configured_offline_account()
assert ac1.get_config("mail_pw") assert ac1.get_config("mail_pw")
assert ac1.is_configured() assert ac1.is_configured()
with pytest.raises(ValueError): with pytest.raises(ValueError):
@@ -268,11 +294,21 @@ class TestOfflineAccount:
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_ac_setup_message_fails(self, acfactory): def test_ac_setup_message_fails(self, ac1):
ac1 = acfactory.get_configured_offline_account()
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
ac1.initiate_key_transfer() 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: class TestOnlineAccount:
def test_one_account_init(self, acfactory): 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") ev = ac1._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
assert ev[1] == msg_out.id 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() ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account()
c2 = ac1.create_contact(email=ac2.get_config("addr")) c2 = ac1.create_contact(email=ac2.get_config("addr"))
@@ -390,7 +426,6 @@ class TestOnlineAccount:
lp.step("1") lp.step("1")
ac1._evlogger.get_matching("DC_EVENT_MSG_READ") ac1._evlogger.get_matching("DC_EVENT_MSG_READ")
lp.step("2") lp.step("2")
# ac1._evlogger.get_info_matching("Message marked as seen")
assert msg_out.get_state().is_out_mdn_received() assert msg_out.get_state().is_out_mdn_received()
def test_saved_mime_on_received_message(self, acfactory, lp): def test_saved_mime_on_received_message(self, acfactory, lp):
@@ -473,7 +508,7 @@ class TestOnlineAccount:
wait_configuration_progress(ac1, 1000) wait_configuration_progress(ac1, 1000)
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
setup_code = ac1.initiate_key_transfer() 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") ev = ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
msg = ac2.get_message_by_id(ev[2]) msg = ac2.get_message_by_id(ev[2])
assert msg.is_setup_message() assert msg.is_setup_message()

View File

@@ -1,6 +1,4 @@
from __future__ import print_function from __future__ import print_function
import os
import shutil
from filecmp import cmp from filecmp import cmp
from deltachat import const from deltachat import const
from conftest import wait_configuration_progress, wait_msgs_changed from conftest import wait_configuration_progress, wait_msgs_changed
@@ -13,17 +11,14 @@ class TestInCreation:
wait_configuration_progress(ac1, 1000) wait_configuration_progress(ac1, 1000)
wait_configuration_progress(ac2, 1000) wait_configuration_progress(ac2, 1000)
blobdir = ac1.get_blobdir()
c2 = ac1.create_contact(email=ac2.get_config("addr")) c2 = ac1.create_contact(email=ac2.get_config("addr"))
chat = ac1.create_chat_by_contact(c2) chat = ac1.create_chat_by_contact(c2)
assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL
wait_msgs_changed(ac1, 0, 0) # why no chat id? wait_msgs_changed(ac1, 0, 0) # why no chat id?
lp.sec("create a message with a file in creation") lp.sec("create a message with a file in creation")
path = os.path.join(blobdir, "d.png") path = data.get_path("d.png")
open(path, 'a').close() prepared_original = chat.prepare_message_file(path)
prepared_original = chat.prepare_file(path)
assert prepared_original.get_state().is_out_preparing() assert prepared_original.get_state().is_out_preparing()
wait_msgs_changed(ac1, chat.id, prepared_original.id) wait_msgs_changed(ac1, chat.id, prepared_original.id)
@@ -37,11 +32,11 @@ class TestInCreation:
forwarded_id = wait_msgs_changed(ac1, chat2.id) forwarded_id = wait_msgs_changed(ac1, chat2.id)
if forwarded_id == 0: if forwarded_id == 0:
forwarded_id = wait_msgs_changed(ac1, chat2.id) forwarded_id = wait_msgs_changed(ac1, chat2.id)
assert forwarded_id
forwarded_msg = ac1.get_message_by_id(forwarded_id) forwarded_msg = ac1.get_message_by_id(forwarded_id)
assert forwarded_msg.get_state().is_out_preparing() assert forwarded_msg.get_state().is_out_preparing()
lp.sec("finish creating the file and send it") lp.sec("finish creating the file and send it")
shutil.copy(data.get_path("d.png"), path)
sent_original = chat.send_prepared(prepared_original) sent_original = chat.send_prepared(prepared_original)
assert sent_original.id == prepared_original.id assert sent_original.id == prepared_original.id
state = sent_original.get_state() state = sent_original.get_state()
@@ -61,11 +56,13 @@ class TestInCreation:
assert ev[1] == chat2.id assert ev[1] == chat2.id
assert ev[2] == forwarded_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") ev1 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL assert ev1[1] >= const.DC_CHAT_ID_LAST_SPECIAL
received_original = ac2.get_message_by_id(ev1[2]) received_original = ac2.get_message_by_id(ev1[2])
assert cmp(received_original.filename, path, False) 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") ev2 = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL assert ev2[1] >= const.DC_CHAT_ID_LAST_SPECIAL
assert ev2[1] != ev1[1] assert ev2[1] != ev1[1]

View File

@@ -8,7 +8,7 @@ envlist =
[testenv] [testenv]
commands = commands =
pytest -rsXx {posargs:tests} pytest -v -rsXx {posargs:tests}
python tests/package_wheels.py {toxworkdir}/wheelhouse python tests/package_wheels.py {toxworkdir}/wheelhouse
passenv = passenv =
TRAVIS TRAVIS