From ca9285e80b9fa2df82b135b3684f933d05222f69 Mon Sep 17 00:00:00 2001 From: Matěj Cepl Date: Sun, 22 Apr 2018 02:03:06 +0200 Subject: Add some more tests. --- archive_folder.py | 56 ++++++++++----------------- test/msg_test.eml | 98 +++++++++++++++++++++++++++++++++++++++++++++++ test/test_email_server.py | 82 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 35 deletions(-) create mode 100644 test/msg_test.eml diff --git a/archive_folder.py b/archive_folder.py index 744fcba..4b86707 100755 --- a/archive_folder.py +++ b/archive_folder.py @@ -1,11 +1,6 @@ #!/usr/bin/env python3.6 # note http://docs.python.org/lib/module-doctest.html # resp. file:///usr/share/doc/python-docs-*/html/lib/module-doctest.html -# FIXME -# read https://www.toptal.com/python/an-introduction-to-mocking-in-python -# & https://blog.fugue.co/2016-02-11-python-mocking-101.html -# for unittest.mock tutorials -# for testing import argparse import collections import configparser @@ -17,6 +12,7 @@ import locale import logging import imaplib import os +import re import sys from datetime import date, timedelta @@ -34,6 +30,7 @@ class MessageError(IOError): pass Capas = collections.namedtuple('Capas', ['MOVE', 'UIDPLUS']) +SEP_RE = re.compile(r'\s+"([/.])"\s+') class Message(object): @@ -69,14 +66,11 @@ class Message(object): class Folder(object): def __init__(self, box, fld_name, create=False): self.box = box - log.debug('fld_name = %s', fld_name) self.fld_name = fld_name self.selected = False self.__create_missing = create - self.__folder_sep = self.__get_separator() + self.folder_sep = self.__get_separator() - log.debug('create_missing = %s', self.__create_missing) - log.debug('list_folder = %s', self.__list_folder) if self.__create_missing and self.__list_folder() is None: self.__create_folder() @@ -88,13 +82,11 @@ class Folder(object): return self def __create_folder(self): - sep = self.__folder_sep + sep = self.folder_sep split_name = self.fld_name.split(sep) - log.debug('split_name = %s', split_name) target = '' for part in split_name: - log.debug('part = %s', part) - target += '{}{}'.format(part, self.__folder_sep) + target += '{}{}'.format(part, self.folder_sep) if self.__list_folder(target) is None: self.box.create(target) self.box.subscribe(target) @@ -117,14 +109,12 @@ class Folder(object): """ data = self.__list_folder() - log.debug('data = %s (%s)', data, type(data)) - if data is None: data = self.__list_folder('""', '""') - data = data.decode().split(' ') - if len(data) == 3: - return data[1].strip(' "') + parse_data = SEP_RE.search(data.decode()) + if parse_data is not None: + return parse_data.group(1) else: raise ServerError('Cannot find folder separator from %s' % data) @@ -143,26 +133,24 @@ class Folder(object): return self.__emails_search('BEFORE', before_str) def get_archive_folder(self, msg, aroot): - return self.__folder_sep.join((aroot, msg.date.strftime("%Y"), self.fld_name)) + return self.folder_sep.join((aroot, msg.date.strftime("%Y"), self.fld_name)) def move_messages(self, messages): assert self.selected == False, 'Target folder should not be selected.' if self.box.features_present.MOVE: - ok, data = self.box.uid('MOVE', - '%s %s' % (messages, self.fld_name)) + ok, data = self.box.uid('MOVE', messages, self.fld_name) log.debug('MOVE ok = %s, data = %s', ok, data) if ok != 'OK': raise FolderError('Cannot move messages to folder %s' % self.fld_name) elif self.box.features_present.UIDPLUS: - ok, data = self.box.uid('COPY', - '%s %s' % (messages, self.fld_name)) + ok, data = self.box.uid('COPY', messages, self.fld_name) log.debug('COPY ok = %s, data = %s', ok, data) if ok != 'OK': raise FolderError('Cannot copy messages to folder %s' % self.fld_name) ok, data = self.box.uid('STORE', - r'+FLAGS.SILENT (\DELETED) %s' % messages) + r'+FLAGS.SILENT (\DELETED)', messages) log.debug('STORE ok = %s, data = %s', ok, data) if ok != 'OK': raise FolderError('Cannot delete messages-') @@ -171,14 +159,13 @@ class Folder(object): if ok != 'OK': raise FolderError('Cannot expunge messages.') else: - ok, data = self.box.uid('COPY', - '%s %s' % (messages, self.fld_name)) + ok, data = self.box.uid('COPY', messages, self.fld_name) log.debug('COPY ok = %s, data = %s', ok, data) if ok != 'OK': raise FolderError('Cannot copy messages to folder %s' % self.fld_name) ok, data = self.box.uid('STORE', - r'+FLAGS.SILENT (\DELETED) %s' % messages) + r'+FLAGS.SILENT (\DELETED)', messages) log.debug('STORE ok = %s, data = %s', ok, data) if ok != 'OK': raise FolderError('Cannot delete messages-') @@ -191,12 +178,11 @@ class EmailServer(object): else config['general']['account'] self.cfg = dict(config.items(acc_name)) self.archive_root = archive_root - self.__box = self.__login(**self.cfg) + self.box = self.__login(**self.cfg) def __login(self, host='localhost', username=None, password=None, ssl=None): box = imaplib.IMAP4_SSL(host=host) - res = box.login(username, password) - ok, data = res + ok, data = box.login(username, password) if ok != 'OK': raise ServerError('Cannot login with credentials %s' % str((host, username, password,))) @@ -204,9 +190,9 @@ class EmailServer(object): ok, data = box.capability() capas = data[0].decode() box.features_present = Capas._make(['MOVE' in capas, 'UIDPLUS' in capas]) + return box @staticmethod - @functools.lru_cache() def get_config(): # In case the configuration file is missing, only empty list will be # returned @@ -223,7 +209,7 @@ class EmailServer(object): :param: before_date """ copy_cache = {} - fld = Folder(self.__box, folder_name).select() + fld = Folder(self.box, folder_name).select() before_str = before_date.strftime('%d-%b-%Y') for msg in fld.emails_before(before_str): arch_folder = fld.get_archive_folder(msg, self.archive_root) @@ -242,8 +228,8 @@ class EmailServer(object): msg_ids = ','.join([x.uid for x in copy_cache[arch_dir]]) log.debug('arch_dir = %s, msgs = %s', arch_dir, msg_ids) - dir = Folder(self.__box, arch_dir, create=True) - dir.move_messages(msg_ids) + dir = Folder(self.box, arch_dir, create=True) + # dir.move_messages(msg_ids) def __enter__(self): return self @@ -251,7 +237,7 @@ class EmailServer(object): def __exit__(self, *args): if args != (None, None, None): log.warning('args = %s', args) - self.__box.close() + self.box.close() if __name__ == '__main__': diff --git a/test/msg_test.eml b/test/msg_test.eml new file mode 100644 index 0000000..f9dc8e6 --- /dev/null +++ b/test/msg_test.eml @@ -0,0 +1,98 @@ +From ???@??? Wed Jul 18 17:27:27 2001 +Return-Path: +Delivered-To: cepl@surfbest.net +Received: from bigfoot.com (litemail.bigfoot.com [208.156.39.208]) + by server12.safepages.com (Postfix) with SMTP id 5DA8C136099 + for ; Wed, 18 Jul 2001 15:54:52 +0000 (GMT) +Received: from ci.egroups.com ([64.211.240.235]) + by BFLITEMAIL1.bigfoot.com (LiteMail v3.01(BFLITEMAIL1)) with SMTP id 18Jul2001_BFLITEMAIL1_13769_126340112; + Wed, 18 Jul 2001 12:00:18 -0400 EST +X-eGroups-Return:sentto-3642313-6-995471638-cepl=bigfoot.com@returns.onelist.com +Received: from[10.1.4.56] by ci.egroups.com with NNFMP; 18 Jul 2001 15:53:58 -0000 +X-Sender:frederic.amblard@CLERMONT.cemagref.fr +X-Apparently-To:dynnet@yahoogroups.com +Received: (EGP: mail-7_2_0); 18 Jul 2001 15:53:57-0000 +Received: (qmail 59967 invoked from network); 18 Jul 2001 15:53:32-0000 +Received: from unknown (10.1.10.26) by l10.egroups.com with QMQP; 18 Jul2001 15:53:32 -0000 +Received: from unknown (HELO lassolas.clermont.cemagref.fr)(195.221.117.5) by mta1 with SMTP; 18 Jul 2001 15:53:32 -0000 +Received: bylassolas.clermont.cemagref.fr with Internet Mail Service (5.5.2448.0) id; Wed, 18 Jul 2001 17:53:32 +0200 +Message-ID: +To:"'dynnet@yahoogroups.com'" +X-Mailer: Internet MailService (5.5.2448.0) +From: Amblard Frederic +MIME-Version: 1.0 +Mailing-List: listdynnet@yahoogroups.com; contact dynnet-owner@yahoogroups.com +Delivered-To:mailing list dynnet@yahoogroups.com +Precedence: bulk +List-Unsubscribe: +Date: Wed, 18 Jul 2001 17:53:30+0200 +Reply-To: dynnet@yahoogroups.com +Subject: TR: [dynnet] Evolution ofnetworks a temptative of bibliography +Content-Type: text/plain;charset=windows-1252 +Content-Transfer-Encoding: quoted-printable +X-PMFLAGS:34078848 0 1 P52F70.CNM +Content-Length: 1633 +Lines: 61 + +Thank you Scottand please forgive my mistake, it is sometimes hard to +follow the life ofworking papers ...=20 +in addition here comes the link to the book:=20 +http://www.amazon.com/exec/obidos/ASIN/354041522X/qid=3D995471342/sr=3D1-1/= +ref=3Ds +c_b_1/104-8956980-7775125 +andto the one published by Rosaria Conte, Rainer Hegselmann and Pietro +Terna forthe article of Tom Snijders:=20 +http://www.amazon.com/exec/obidos/ASIN/3540633294/qid=3D995471480/sr=3D1-1/= +ref=3Ds +c_b_1/104-8956980-7775125 + +ATB +Fred=20 + +-----Messaged'origine----- +De : Scott Moss [mailto:s.moss@mmu.ac.uk] +Envoy=E9 : mercredi18 juillet 2001 17:45 +=C0 : Frederic Amblard +Objet : Re: [dynnet] Evolutionof networks a temptative of bibliography + + +Hi Frederic + +The Axtellpaper has been published in the Springer LNAI series. The +author, title anddate are the same but the location is Scott Moss and +Paul Davidsson (eds),Multi-Agent-Based Simulation, Lecture Notes in +Artificial Intelligence No. 1979,pp. 33-48. + +yours, +Scott + +Amblard Frederic wrote: + +> Axtell,R., Effects of Interaction Topology and Activation Regime in +> SeveralMulti-agent Systems. 2000, Center on Social and Economic +>Dynamics.(http://www.brookings.edu/ES/dynamics/papers/) + +-- +ProfessorScott Moss +Director +Centre for Policy Modelling +Manchester MetropolitanUniversity +Aytoun Building +Manchester M1 3GH +UNITEDKINGDOM + +telephone: +44 (0)161 247 3886 +fax: +44 (0)161 2476802 + +http://www.cpm.mmu.ac.uk/~scott + + +To unsubscribe from thisgroup, send an emailto: +dynnet-unsubscribe@yahoogroups.com + +=20 + +Your use of Yahoo!Groups is subject to http://docs.yahoo.com/info/terms/=20 + + + diff --git a/test/test_email_server.py b/test/test_email_server.py index 8ff8788..737dd68 100644 --- a/test/test_email_server.py +++ b/test/test_email_server.py @@ -1,3 +1,4 @@ +import datetime import logging import unittest from unittest import mock @@ -6,6 +7,13 @@ import archive_folder log = logging.getLogger('test') + +def create_test_message(): + with open('test/msg_test.eml', 'rb') as msg_file: + msg_raw = msg_file.read() + return [(b'13 (UID 13 RFC822 {3776}', msg_raw), b')'] + + class TestEmailServer(unittest.TestCase): @mock.patch.object(archive_folder.imaplib, 'IMAP4_SSL', autospec=True) @mock.patch.object(archive_folder.configparser, 'ConfigParser', autospec=True) @@ -25,3 +33,77 @@ class TestEmailServer(unittest.TestCase): mock_imapobj().login.assert_called_once_with('fakeuser', 'veryverysecret') mock_imapobj().capability.assert_called_once_with() self.assertEqual(mock_imapobj().features_present, (False, True)) + self.assertIsInstance(box, archive_folder.EmailServer) + + +class TestFolder(unittest.TestCase): + @mock.patch.object(archive_folder.imaplib, 'IMAP4_SSL', autospec=True) + def test_folder_initialization(self, mock_imapobj): + mock_imapobj().list.return_value = ('OK', + [b'(\\HasNoChildren) "/" Work/sociology-junk']) + box = archive_folder.imaplib.IMAP4_SSL() + folder = archive_folder.Folder(box, 'INBOX/FakeFolder') + mock_imapobj().list.assert_called_once_with('INBOX/FakeFolder', '*') + self.assertIsInstance(folder, archive_folder.Folder) + self.assertEqual(folder.folder_sep, '/') + self.assertFalse(folder.selected) + + @mock.patch.object(archive_folder.imaplib, 'IMAP4_SSL', autospec=True) + def test_folder_select(self, mock_imapobj): + mock_imapobj().list.return_value = ('OK', + [b'(\\HasNoChildren) "/" Work/sociology-junk']) + mock_imapobj().select.return_value = ('OK', 'FAKE success') + box = archive_folder.imaplib.IMAP4_SSL() + folder = archive_folder.Folder(box, 'INBOX/FakeFolder').select() + mock_imapobj().list.assert_called_once_with('INBOX/FakeFolder', '*') + mock_imapobj().select.assert_called_once_with('INBOX/FakeFolder') + self.assertIsInstance(folder, archive_folder.Folder) + self.assertEqual(folder.folder_sep, '/') + self.assertTrue(folder.selected) + + @mock.patch.object(archive_folder.imaplib, 'IMAP4_SSL', autospec=True) + def test_folder_emails_before(self, mock_imapobj): + def fake_uid(cmd, param): + if cmd == 'FETCH': + return ('OK', create_test_message()) + elif cmd == 'SEARCH': + return ('OK', [b'13']) + + mock_imapobj().list.return_value = ('OK', + [b'(\\HasNoChildren) "/" Work/sociology-junk']) + mock_imapobj().select.return_value = ('OK', 'FAKE success') + mock_imapobj().uid.side_effect = fake_uid + box = archive_folder.imaplib.IMAP4_SSL() + folder = archive_folder.Folder(box, 'INBOX/FakeFolder').select() + msgs = folder.emails_before('25-Feb-1948') + self.assertIsInstance(msgs[0], archive_folder.Message) + + @mock.patch.object(archive_folder.imaplib, 'IMAP4_SSL', autospec=True) + def test_folder_get_archive_folder(self, mock_imapobj): + mock_imapobj().list.return_value = ('OK', + [b'(\\HasNoChildren) "/" Work/sociology-junk']) + mock_imapobj().select.return_value = ('OK', 'FAKE success') + box = archive_folder.imaplib.IMAP4_SSL() + folder = archive_folder.Folder(box, 'INBOX/FakeFolder').select() + + mock_imapobj().uid.return_value = ('OK', create_test_message()) + box = archive_folder.imaplib.IMAP4_SSL() + msg = archive_folder.Message(box, '10') + + arch_fld = folder.get_archive_folder(msg, 'FakeArchive') + self.assertEqual(arch_fld, 'FakeArchive/2001/INBOX/FakeFolder') + + +class TestMessage(unittest.TestCase): + @mock.patch.object(archive_folder.imaplib, 'IMAP4_SSL', autospec=True) + def test_message_initialization(self, mock_imapobj): + mock_imapobj().uid.return_value = ('OK', create_test_message()) + box = archive_folder.imaplib.IMAP4_SSL() + msg = archive_folder.Message(box, '10') + mock_imapobj().uid.assert_called_once_with('FETCH', '10 (RFC822)') + self.assertIsInstance(msg, archive_folder.Message) + self.assertEqual(msg.uid, '10') + self.assertEqual(msg.subject, + 'TR: [dynnet] Evolution ofnetworks a temptative of bibliography') + self.assertEqual(msg.date, datetime.datetime(2001, 7, 18, 17, 53, 30, + tzinfo=datetime.timezone(datetime.timedelta(0, 7200)))) -- cgit