This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses

View File

@@ -0,0 +1,8 @@
from .testable_imapclient import TestableIMAPClient as IMAPClient
from .util import unittest
class IMAPClientTest(unittest.TestCase):
def setUp(self):
self.client = IMAPClient()

View File

@@ -0,0 +1,34 @@
# Copyright (c) 2016, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from imapclient import IMAPClient
from .imapclient_test import IMAPClientTest
class TestPlainLogin(IMAPClientTest):
def assert_authenticate_call(self, expected_auth_string):
authenticate = self.client._imap.authenticate
self.assertEqual(authenticate.call_count, 1)
auth_type, auth_func = authenticate.call_args[0]
self.assertEqual(auth_type, "PLAIN")
self.assertEqual(auth_func(None), expected_auth_string)
def test_simple(self):
self.client._imap.authenticate.return_value = ('OK', [b'Success'])
result = self.client.plain_login("user", "secret")
self.assertEqual(result, b'Success')
self.assert_authenticate_call("\0user\0secret")
def test_fail(self):
self.client._imap.authenticate.return_value = ('NO', [b'Boom'])
self.assertRaises(IMAPClient.Error, self.client.plain_login, "user", "secret")
def test_with_authorization_identity(self):
self.client._imap.authenticate.return_value = ('OK', [b'Success'])
result = self.client.plain_login("user", "secret", "authid")
self.assertEqual(result, b'Success')
self.assert_authenticate_call("authid\0user\0secret")

View File

@@ -0,0 +1,88 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from datetime import datetime, date
from mock import patch
from ..datetime_util import (
datetime_to_INTERNALDATE,
datetime_to_native,
format_criteria_date,
parse_to_datetime,
)
from ..fixed_offset import FixedOffset
from .util import unittest
class TestParsing(unittest.TestCase):
def check_normalised_and_not(self, in_string, expected_datetime):
self.assertEqual(
parse_to_datetime(in_string),
datetime_to_native(expected_datetime)
)
self.assertEqual(
parse_to_datetime(in_string, normalise=False),
expected_datetime
)
def test_rfc822_style(self):
self.check_normalised_and_not(
b'Sun, 24 Mar 2013 22:06:10 +0200',
datetime(2013, 3, 24, 22, 6, 10, 0, FixedOffset(120))
)
def test_internaldate_style(self):
self.check_normalised_and_not(
b' 9-Feb-2007 17:08:08 -0430',
datetime(2007, 2, 9, 17, 8, 8, 0, FixedOffset(-4 * 60 - 30))
)
self.check_normalised_and_not(
b'19-Feb-2007 17:08:08 0400',
datetime(2007, 2, 19, 17, 8, 8, 0, FixedOffset(4 * 60))
)
def test_dots_for_time_separator(self):
# As reported in issue #154.
self.check_normalised_and_not(
b'Sat, 8 May 2010 16.03.09 +0200',
datetime(2010, 5, 8, 16, 3, 9, 0, FixedOffset(120))
)
self.check_normalised_and_not(
b'Tue, 18 May 2010 16.03.09 -0200',
datetime(2010, 5, 18, 16, 3, 9, 0, FixedOffset(-120))
)
self.check_normalised_and_not(
b'Wednesday,18 August 2010 16.03.09 -0200',
datetime(2010, 8, 18, 16, 3, 9, 0, FixedOffset(-120))
)
def test_invalid(self):
self.assertRaises(ValueError, parse_to_datetime, b'ABC')
class TestDatetimeToINTERNALDATE(unittest.TestCase):
def test_with_timezone(self):
dt = datetime(2009, 1, 2, 3, 4, 5, 0, FixedOffset(2 * 60 + 30))
self.assertEqual(datetime_to_INTERNALDATE(dt), '02-Jan-2009 03:04:05 +0230')
@patch('imapclient.datetime_util.FixedOffset.for_system')
def test_without_timezone(self, for_system):
dt = datetime(2009, 1, 2, 3, 4, 5, 0)
for_system.return_value = FixedOffset(-5 * 60)
self.assertEqual(datetime_to_INTERNALDATE(dt), '02-Jan-2009 03:04:05 -0500')
class TestCriteriaDateFormatting(unittest.TestCase):
def test_basic(self):
self.assertEqual(format_criteria_date(date(1996, 2, 22)), b'22-Feb-1996')
def test_single_digit_day(self):
self.assertEqual(format_criteria_date(date(1996, 4, 4)), b'04-Apr-1996')

View File

@@ -0,0 +1,67 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from datetime import timedelta
from mock import Mock, patch, DEFAULT
from imapclient.test.util import unittest
from imapclient.fixed_offset import FixedOffset
class TestFixedOffset(unittest.TestCase):
def _check(self, offset, expected_delta, expected_name):
self.assertEqual(offset.utcoffset(None), expected_delta)
self.assertEqual(offset.tzname(None), expected_name)
self.assertEqual(offset.dst(None), timedelta(0))
def test_GMT(self):
self._check(FixedOffset(0),
timedelta(0), '+0000')
def test_positive(self):
self._check(FixedOffset(30),
timedelta(minutes=30), '+0030')
self._check(FixedOffset(2 * 60),
timedelta(hours=2), '+0200')
self._check(FixedOffset(11 * 60 + 30),
timedelta(hours=11, minutes=30), '+1130')
def test_negative(self):
self._check(FixedOffset(-30),
timedelta(minutes=-30), '-0030')
self._check(FixedOffset(-2 * 60),
timedelta(hours=-2), '-0200')
self._check(FixedOffset(-11 * 60 - 30),
timedelta(minutes=(-11 * 60) - 30), '-1130')
@patch.multiple('imapclient.fixed_offset.time',
daylight=True, timezone=15 * 60 * 60, localtime=DEFAULT)
def test_for_system_DST_not_active(self, localtime):
localtime_mock = Mock()
localtime_mock.tm_isdst = False
localtime.return_value = localtime_mock
offset = FixedOffset.for_system()
self.assertEqual(offset.tzname(None), '-1500')
@patch.multiple('imapclient.fixed_offset.time',
daylight=True, altzone=15 * 60 * 60, localtime=DEFAULT)
def test_for_system_DST_active(self, localtime):
localtime_mock = Mock()
localtime_mock.tm_isdst = True
localtime.return_value = localtime_mock
offset = FixedOffset.for_system()
self.assertEqual(offset.tzname(None), '-1500')
@patch.multiple('imapclient.fixed_offset.time',
daylight=False, timezone=-15 * 60 * 60)
def test_for_system_no_DST(self):
offset = FixedOffset.for_system()
self.assertEqual(offset.tzname(None), '+1500')
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,62 @@
# Copyright (c) 2015, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from mock import Mock
from .imapclient_test import IMAPClientTest
class TestFolderStatus(IMAPClientTest):
def test_basic(self):
self.client._imap.status.return_value = (
'OK',
[b'foo (MESSAGES 3 RECENT 0 UIDNEXT 4 UIDVALIDITY 1435636895 UNSEEN 0)']
)
out = self.client.folder_status('foo')
self.client._imap.status.assert_called_once_with(
b'"foo"',
'(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)'
)
self.assertDictEqual(out, {
b'MESSAGES': 3,
b'RECENT': 0,
b'UIDNEXT': 4,
b'UIDVALIDITY': 1435636895,
b'UNSEEN': 0
})
def test_literal(self):
self.client._imap.status.return_value = (
'OK',
[(b'{3}', b'foo'), b' (UIDNEXT 4)']
)
out = self.client.folder_status('foo', ['UIDNEXT'])
self.client._imap.status.assert_called_once_with(b'"foo"', '(UIDNEXT)')
self.assertDictEqual(out, {b'UIDNEXT': 4})
def test_extra_response(self):
# In production, we've seen folder names containing spaces come back
# like this and be broken into two components in the tuple.
server_response = [b"My files (UIDNEXT 24369)"]
mock = Mock(return_value=server_response)
self.client._command_and_check = mock
resp = self.client.folder_status('My files', ['UIDNEXT'])
self.assertEqual(resp, {b'UIDNEXT': 24369})
# We've also seen the response contain mailboxes we didn't
# ask for. In all known cases, the desired mailbox is last.
server_response = [b"sent (UIDNEXT 123)\nINBOX (UIDNEXT 24369)"]
mock = Mock(return_value=server_response)
self.client._command_and_check = mock
resp = self.client.folder_status('INBOX', ['UIDNEXT'])
self.assertEqual(resp, {b'UIDNEXT': 24369})

View File

@@ -0,0 +1,50 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from six import text_type, binary_type, int2byte, unichr
from imapclient.imap_utf7 import decode, encode
from imapclient.test.util import unittest
class IMAP4UTF7TestCase(unittest.TestCase):
tests = [
['Foo', b'Foo'],
['Foo Bar', b'Foo Bar'],
['Stuff & Things', b'Stuff &- Things'],
['Hello world', b'Hello world'],
['Hello & world', b'Hello &- world'],
['Hello\xffworld', b'Hello&AP8-world'],
['\xff\xfe\xfd\xfc', b'&AP8A,gD9APw-'],
['~peter/mail/\u65e5\u672c\u8a9e/\u53f0\u5317',
b'~peter/mail/&ZeVnLIqe-/&U,BTFw-'], # example from RFC 2060
['\x00foo', b'&AAA-foo'],
]
def test_encode(self):
for (input, output) in self.tests:
encoded = encode(input)
self.assertIsInstance(encoded, binary_type)
self.assertEqual(encoded, output)
def test_decode(self):
for (input, output) in self.tests:
decoded = decode(output)
self.assertIsInstance(decoded, text_type)
self.assertEqual(input, decoded)
def test_printable_singletons(self):
"""
The IMAP4 modified UTF-7 implementation encodes all printable
characters which are in ASCII using the corresponding ASCII byte.
"""
# All printables represent themselves
for o in list(range(0x20, 0x26)) + list(range(0x27, 0x7f)):
self.assertEqual(int2byte(o), encode(unichr(o)))
self.assertEqual(unichr(o), decode(int2byte(o)))
self.assertEqual(encode('&'), b'&-')
self.assertEqual(encode('&'), b'&-')
self.assertEqual(decode(b'&-'), '&')

View File

@@ -0,0 +1,577 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
import itertools
import socket
import sys
from datetime import datetime
import six
from mock import patch, sentinel, Mock
from imapclient.fixed_offset import FixedOffset
from .testable_imapclient import TestableIMAPClient as IMAPClient
from .imapclient_test import IMAPClientTest
class TestListFolders(IMAPClientTest):
def test_list_folders(self):
self.client._imap._simple_command.return_value = ('OK', [b'something'])
self.client._imap._untagged_response.return_value = ('LIST', sentinel.folder_data)
self.client._proc_folder_list = Mock(return_value=sentinel.folder_list)
folders = self.client.list_folders('foo', 'bar')
self.client._imap._simple_command.assert_called_once_with(
'LIST', b'"foo"', b'"bar"')
self.assertEqual(self.client._proc_folder_list.call_args, ((sentinel.folder_data,), {}))
self.assertTrue(folders is sentinel.folder_list)
def test_list_sub_folders(self):
self.client._imap._simple_command.return_value = ('OK', [b'something'])
self.client._imap._untagged_response.return_value = ('LSUB', sentinel.folder_data)
self.client._proc_folder_list = Mock(return_value=sentinel.folder_list)
folders = self.client.list_sub_folders('foo', 'bar')
self.client._imap._simple_command.assert_called_once_with(
'LSUB', b'"foo"', b'"bar"')
self.assertEqual(self.client._proc_folder_list.call_args, ((sentinel.folder_data,), {}))
self.assertTrue(folders is sentinel.folder_list)
def test_list_folders_NO(self):
self.client._imap._simple_command.return_value = ('NO', [b'badness'])
self.assertRaises(IMAPClient.Error, self.client.list_folders)
def test_list_sub_folders_NO(self):
self.client._imap._simple_command.return_value = ('NO', [b'badness'])
self.assertRaises(IMAPClient.Error, self.client.list_folders)
def test_utf7_decoding(self):
self.client._imap._simple_command.return_value = ('OK', [b'something'])
self.client._imap._untagged_response.return_value = (
'LIST', [
b'(\\HasNoChildren) "/" "A"',
b'(\\HasNoChildren) "/" "Hello&AP8-world"',
])
folders = self.client.list_folders('foo', 'bar')
self.client._imap._simple_command.assert_called_once_with('LIST', b'"foo"', b'"bar"')
self.assertEqual(folders, [((b'\\HasNoChildren',), b'/', 'A'),
((b'\\HasNoChildren',), b'/', 'Hello\xffworld')])
def test_folder_encode_off(self):
self.client.folder_encode = False
self.client._imap._simple_command.return_value = ('OK', [b'something'])
self.client._imap._untagged_response.return_value = (
'LIST', [
b'(\\HasNoChildren) "/" "A"',
b'(\\HasNoChildren) "/" "Hello&AP8-world"',
])
folders = self.client.list_folders('foo', 'bar')
self.client._imap._simple_command.assert_called_once_with('LIST', '"foo"', '"bar"')
self.assertEqual(folders, [((b'\\HasNoChildren',), b'/', b'A'),
((b'\\HasNoChildren',), b'/', b'Hello&AP8-world')])
def test_simple(self):
folders = self.client._proc_folder_list([b'(\\HasNoChildren) "/" "A"',
b'(\\HasNoChildren) "/" "Foo Bar"',
])
self.assertEqual(folders, [((b'\\HasNoChildren',), b'/', 'A',),
((b'\\HasNoChildren',), b'/', 'Foo Bar')])
def test_without_quotes(self):
folders = self.client._proc_folder_list([b'(\\HasNoChildren) "/" A',
b'(\\HasNoChildren) "/" B',
b'(\\HasNoChildren) "/" C',
])
self.assertEqual(folders, [((b'\\HasNoChildren',), b'/', 'A'),
((b'\\HasNoChildren',), b'/', 'B'),
((b'\\HasNoChildren',), b'/', 'C')])
def test_unquoted_numeric_folder_name(self):
# Some IMAP implementations do this
folders = self.client._proc_folder_list([b'(\\HasNoChildren) "/" 123'])
self.assertEqual(folders, [((b'\\HasNoChildren',), b'/', '123')])
def test_unqouted_numeric_folder_name_parsed_as_long(self):
# big enough numeric values might get parsed as longs
folder_name = str(sys.maxsize + 1)
folders = self.client._proc_folder_list(
[b'(\\HasNoChildren) "/" ' + folder_name.encode('ascii')])
self.assertEqual(folders, [((b'\\HasNoChildren', ), b'/', folder_name)])
def test_mixed(self):
folders = self.client._proc_folder_list([b'(\\HasNoChildren) "/" Alpha',
b'(\\HasNoChildren) "/" "Foo Bar"',
b'(\\HasNoChildren) "/" C',
])
self.assertEqual(folders, [((b'\\HasNoChildren',), b'/', 'Alpha'),
((b'\\HasNoChildren',), b'/', 'Foo Bar'),
((b'\\HasNoChildren',), b'/', 'C')])
def test_funky_characters(self):
folders = self.client._proc_folder_list([(b'(\\NoInferiors \\UnMarked) "/" {5}', 'bang\xff'),
b'',
b'(\\HasNoChildren \\UnMarked) "/" "INBOX"'])
self.assertEqual(folders, [((b'\\NoInferiors', b'\\UnMarked'), b"/", 'bang\xff'),
((b'\\HasNoChildren', b'\\UnMarked'), b"/", 'INBOX')])
def test_quoted_specials(self):
folders = self.client._proc_folder_list([br'(\HasNoChildren) "/" "Test \"Folder\""',
br'(\HasNoChildren) "/" "Left\"Right"',
br'(\HasNoChildren) "/" "Left\\Right"',
br'(\HasNoChildren) "/" "\"Left Right\""',
br'(\HasNoChildren) "/" "\"Left\\Right\""',
])
self.assertEqual(folders, [((b'\\HasNoChildren',), b'/', 'Test "Folder"'),
((b'\\HasNoChildren',), b'/', 'Left\"Right'),
((b'\\HasNoChildren',), b'/', r'Left\Right'),
((b'\\HasNoChildren',), b'/', r'"Left Right"'),
((b'\\HasNoChildren',), b'/', r'"Left\Right"'),
])
def test_empty_response(self):
self.assertEqual(self.client._proc_folder_list([None]), [])
def test_blanks(self):
folders = self.client._proc_folder_list(['', None, br'(\HasNoChildren) "/" "last"'])
self.assertEqual(folders, [((br'\HasNoChildren',), b'/', 'last')])
class TestSelectFolder(IMAPClientTest):
def test_normal(self):
self.client._command_and_check = Mock()
self.client._imap.untagged_responses = {
b'exists': [b'3'],
b'FLAGS': [br"(\Flagged \Deleted abc [foo]/bar def)"],
b'HIGHESTMODSEQ': [b'127110'],
b'OK': [br"[PERMANENTFLAGS (\Flagged \Deleted abc [foo]/bar def \*)] Flags permitted.",
b'[UIDVALIDITY 631062293] UIDs valid.',
b'[UIDNEXT 1281] Predicted next UID.',
b'[HIGHESTMODSEQ 127110]'],
b'PERMANENTFLAGS': [br'(\Flagged \Deleted abc [foo'],
b'READ-WRITE': [b''],
b'RECENT': [b'0'],
b'UIDNEXT': [b'1281'],
b'UIDVALIDITY': [b'631062293'],
b'OTHER': [b'blah']
}
result = self.client.select_folder(b'folder_name', sentinel.readonly)
self.client._command_and_check.assert_called_once_with('select',
b'"folder_name"',
sentinel.readonly)
self.maxDiff = 99999
self.assertEqual(result, {
b'EXISTS': 3,
b'RECENT': 0,
b'UIDNEXT': 1281,
b'UIDVALIDITY': 631062293,
b'HIGHESTMODSEQ': 127110,
b'FLAGS': (br'\Flagged', br'\Deleted', b'abc', b'[foo]/bar', b'def'),
b'PERMANENTFLAGS': (br'\Flagged', br'\Deleted', b'abc', b'[foo]/bar', b'def', br'\*'),
b'READ-WRITE': True,
b'OTHER': [b'blah']
})
class TestAppend(IMAPClientTest):
def test_without_msg_time(self):
self.client._imap.append.return_value = ('OK', [b'Good'])
msg = 'hi'
self.client.append('foobar', msg, ['FLAG', 'WAVE'], None)
self.client._imap.append.assert_called_with(
b'"foobar"', '(FLAG WAVE)', None, b'hi')
@patch('imapclient.imapclient.datetime_to_INTERNALDATE')
def test_with_msg_time(self, datetime_to_INTERNALDATE):
datetime_to_INTERNALDATE.return_value = 'somedate'
self.client._imap.append.return_value = ('OK', [b'Good'])
msg = b'bye'
self.client.append('foobar', msg, ['FLAG', 'WAVE'],
datetime(2009, 4, 5, 11, 0, 5, 0, FixedOffset(2 * 60)))
self.assertTrue(datetime_to_INTERNALDATE.called)
self.client._imap.append.assert_called_with(
b'"foobar"', '(FLAG WAVE)', '"somedate"', msg)
class TestAclMethods(IMAPClientTest):
def test_getacl(self):
self.client._imap.getacl.return_value = ('OK', [b'INBOX Fred rwipslda Sally rwip'])
acl = self.client.getacl('INBOX')
self.assertSequenceEqual(acl, [(b'Fred', b'rwipslda'), (b'Sally', b'rwip')])
def test_setacl(self):
self.client._imap.setacl.return_value = ('OK', [b"SETACL done"])
response = self.client.setacl('folder', sentinel.who, sentinel.what)
self.client._imap.setacl.assert_called_with(b'"folder"',
sentinel.who,
sentinel.what)
self.assertEqual(response, b"SETACL done")
class TestIdleAndNoop(IMAPClientTest):
def assert_sock_calls(self, sock):
self.assertListEqual(sock.method_calls, [
('settimeout', (None,), {}),
('setblocking', (0,), {}),
('setblocking', (1,), {}),
])
def test_idle(self):
self.client._imap._command.return_value = sentinel.tag
self.client._imap._get_response.return_value = None
self.client.idle()
self.client._imap._command.assert_called_with('IDLE')
self.assertEqual(self.client._idle_tag, sentinel.tag)
@patch('imapclient.imapclient.select.select')
def test_idle_check_blocking(self, mock_select):
mock_sock = Mock()
self.client._imap.sock = self.client._imap.sslobj = mock_sock
mock_select.return_value = ([True], [], [])
counter = itertools.count()
def fake_get_line():
count = six.next(counter)
if count == 0:
return b'* 1 EXISTS'
elif count == 1:
return b'* 0 EXPUNGE'
else:
raise socket.timeout
self.client._imap._get_line = fake_get_line
responses = self.client.idle_check()
mock_select.assert_called_once_with([mock_sock], [], [], None)
self.assert_sock_calls(mock_sock)
self.assertListEqual([(1, b'EXISTS'), (0, b'EXPUNGE')], responses)
@patch('imapclient.imapclient.select.select')
def test_idle_check_timeout(self, mock_select):
mock_sock = Mock()
self.client._imap.sock = self.client._imap.sslobj = mock_sock
mock_select.return_value = ([], [], [])
responses = self.client.idle_check(timeout=0.5)
mock_select.assert_called_once_with([mock_sock], [], [], 0.5)
self.assert_sock_calls(mock_sock)
self.assertListEqual([], responses)
@patch('imapclient.imapclient.select.select')
def test_idle_check_with_data(self, mock_select):
mock_sock = Mock()
self.client._imap.sock = self.client._imap.sslobj = mock_sock
mock_select.return_value = ([True], [], [])
counter = itertools.count()
def fake_get_line():
count = six.next(counter)
if count == 0:
return b'* 99 EXISTS'
else:
raise socket.timeout
self.client._imap._get_line = fake_get_line
responses = self.client.idle_check()
mock_select.assert_called_once_with([mock_sock], [], [], None)
self.assert_sock_calls(mock_sock)
self.assertListEqual([(99, b'EXISTS')], responses)
def test_idle_done(self):
self.client._idle_tag = sentinel.tag
mockSend = Mock()
self.client._imap.send = mockSend
mockConsume = Mock(return_value=sentinel.out)
self.client._consume_until_tagged_response = mockConsume
result = self.client.idle_done()
mockSend.assert_called_with(b'DONE\r\n')
mockConsume.assert_called_with(sentinel.tag, 'IDLE')
self.assertEqual(result, sentinel.out)
def test_noop(self):
mockCommand = Mock(return_value=sentinel.tag)
self.client._imap._command = mockCommand
mockConsume = Mock(return_value=sentinel.out)
self.client._consume_until_tagged_response = mockConsume
result = self.client.noop()
mockCommand.assert_called_with('NOOP')
mockConsume.assert_called_with(sentinel.tag, 'NOOP')
self.assertEqual(result, sentinel.out)
def test_consume_until_tagged_response(self):
client = self.client
client._imap.tagged_commands = {sentinel.tag: None}
counter = itertools.count()
def fake_get_response():
count = six.next(counter)
if count == 0:
return b'* 99 EXISTS'
client._imap.tagged_commands[sentinel.tag] = ('OK', [b'Idle done'])
client._imap._get_response = fake_get_response
text, responses = client._consume_until_tagged_response(sentinel.tag, b'IDLE')
self.assertEqual(client._imap.tagged_commands, {})
self.assertEqual(text, b'Idle done')
self.assertListEqual([(99, b'EXISTS')], responses)
class TestDebugLogging(IMAPClientTest):
def test_default_is_stderr(self):
self.assertIs(self.client.log_file, sys.stderr)
def test_IMAP_is_patched(self):
log = six.StringIO()
self.client.log_file = log
self.client._log('one')
self.client._imap._mesg('two')
output = log.getvalue()
self.assertIn('one', output)
self.assertIn('two', output)
class TestTimeNormalisation(IMAPClientTest):
def test_default(self):
self.assertTrue(self.client.normalise_times)
@patch('imapclient.imapclient.parse_fetch_response')
def test_pass_through(self, parse_fetch_response):
self.client._imap._command_complete.return_value = ('OK', sentinel.data)
self.client._imap._untagged_response.return_value = ('OK', sentinel.fetch_data)
self.client.use_uid = sentinel.use_uid
def check(expected):
self.client.fetch(22, ['SOMETHING'])
parse_fetch_response.assert_called_with(sentinel.fetch_data,
expected,
sentinel.use_uid)
self.client.normalise_times = True
check(True)
self.client.normalise_times = False
check(False)
class TestNamespace(IMAPClientTest):
def set_return(self, value):
self.client._imap.namespace.return_value = ('OK', [value])
def test_simple(self):
self.set_return(b'(("FOO." "/")) NIL NIL')
self.assertEqual(self.client.namespace(), ((('FOO.', '/'),), None, None))
def test_folder_decoding(self):
self.set_return(b'(("&AP8-." "/")) NIL NIL')
self.assertEqual(self.client.namespace(), ((('\xff.', '/'),), None, None))
def test_without_folder_decoding(self):
self.set_return(b'(("&AP8-." "/")) NIL NIL')
self.client.folder_encode = False
self.assertEqual(self.client.namespace(), (((b'&AP8-.', '/'),), None, None))
def test_other_only(self):
self.set_return(b'NIL NIL (("" "."))')
self.assertEqual(self.client.namespace(), (None, None, (("", "."),)))
def test_complex(self):
self.set_return(b'(("" "/")) '
b'(("~" "/")) '
b'(("#shared/" "/") ("#public/" "/")("#ftp/" "/")("#news." "."))')
self.assertEqual(self.client.namespace(), (
(("", "/"),),
(("~", "/"),),
(("#shared/", "/"), ("#public/", "/"), ("#ftp/", "/"), ("#news.", ".")),
))
class TestCapabilities(IMAPClientTest):
def test_preauth(self):
self.client._imap.capabilities = ('FOO', 'BAR')
self.client._imap.untagged_responses = {}
self.assertEqual(self.client.capabilities(), (b'FOO', b'BAR'))
def test_server_returned_capability_after_auth(self):
self.client._imap.capabilities = (b'FOO',)
self.client._imap.untagged_responses = {'CAPABILITY': [b'FOO MORE']}
self.assertEqual(self.client._cached_capabilities, None)
self.assertEqual(self.client.capabilities(), (b'FOO', b'MORE'))
self.assertEqual(self.client._cached_capabilities, (b'FOO', b'MORE'))
self.assertEqual(self.client._imap.untagged_responses, {})
def test_caching(self):
self.client._imap.capabilities = ('FOO',)
self.client._imap.untagged_responses = {}
self.client._cached_capabilities = (b'FOO', b'MORE')
self.assertEqual(self.client.capabilities(), (b'FOO', b'MORE'))
def test_post_auth_request(self):
self.client._imap.capabilities = ('FOO',)
self.client._imap.untagged_responses = {}
self.client._imap.state = 'SELECTED'
self.client._imap.capability.return_value = ('OK', [b'FOO BAR'])
self.assertEqual(self.client.capabilities(), (b'FOO', b'BAR'))
self.assertEqual(self.client._cached_capabilities, (b'FOO', b'BAR'))
def test_with_starttls(self):
# Initial connection
self.client._imap.capabilities = ('FOO',)
self.client._imap.untagged_responses = {}
self.client._imap.state = 'NONAUTH'
self.assertEqual(self.client.capabilities(), (b'FOO',))
# Now do STARTTLS; capabilities change and should be reported.
self.client._starttls_done = True
self.client._imap.capability.return_value = ('OK', [b'FOO BAR'])
self.assertEqual(self.client.capabilities(), (b'FOO', b'BAR'))
# Login done; capabilities change again.
self.client._imap.state = 'AUTH'
self.client._imap.capability.return_value = ('OK', [b'FOO BAR QUX'])
self.assertEqual(self.client.capabilities(), (b'FOO', b'BAR', b'QUX'))
def test_has_capability(self):
self.client._cached_capabilities = (b'FOO', b'MORE')
self.assertTrue(self.client.has_capability(b'FOO'))
self.assertTrue(self.client.has_capability(b'foo'))
self.assertFalse(self.client.has_capability(b'BAR'))
self.assertTrue(self.client.has_capability('FOO'))
self.assertTrue(self.client.has_capability('foo'))
self.assertFalse(self.client.has_capability('BAR'))
class TestId(IMAPClientTest):
def test_id(self):
self.client._cached_capabilities = (b'ID',)
self.client._imap._simple_command.return_value = ('OK', [b'Success'])
self.client._imap._untagged_response.return_value = (
b'OK', [b'("name" "GImap" "vendor" "Google, Inc.")'])
id_response = self.client.id_({'name': 'IMAPClient'})
self.client._imap._simple_command.assert_called_with(
'ID', '("name" "IMAPClient")')
self.assertSequenceEqual(
id_response,
((b'name', b'GImap', b'vendor', b'Google, Inc.'),))
def test_no_support(self):
self.client._cached_capabilities = (b'IMAP4rev1',)
self.assertRaises(ValueError, self.client.id_)
def test_invalid_parameters(self):
self.assertRaises(TypeError, self.client.id_, 'bananarama')
class TestRawCommand(IMAPClientTest):
def setUp(self):
super(TestRawCommand, self).setUp()
self.client._imap._get_response.return_value = None
self.client._imap._command_complete.return_value = ('OK', ['done'])
def check(self, command, args, expected):
typ, data = self.client._raw_command(command, args)
self.assertEqual(typ, 'OK')
self.assertEqual(data, ['done'])
self.assertEqual(self.client._imap.sent, expected)
def test_plain(self):
self.check(b'search', [b'ALL'],
b'tag UID SEARCH ALL\r\n',
)
def test_not_uid(self):
self.client.use_uid = False
self.check(b'search', [b'ALL'],
b'tag SEARCH ALL\r\n',
)
def test_literal_at_end(self):
self.check(b'search', [b'TEXT', b'\xfe\xff'],
b'tag UID SEARCH TEXT {2}\r\n'
b'\xfe\xff\r\n'
)
def test_embedded_literal(self):
self.check(b'search', [b'TEXT', b'\xfe\xff', b'DELETED'],
b'tag UID SEARCH TEXT {2}\r\n'
b'\xfe\xff DELETED\r\n'
)
def test_multiple_literals(self):
self.check(b'search', [b'TEXT', b'\xfe\xff', b'TEXT', b'\xcc'],
b'tag UID SEARCH TEXT {2}\r\n'
b'\xfe\xff TEXT {1}\r\n'
b'\xcc\r\n'
)
def test_complex(self):
self.check(b'search', [b'FLAGGED', b'TEXT', b'\xfe\xff', b'TEXT', b'\xcc', b'TEXT', b'foo'],
b'tag UID SEARCH FLAGGED TEXT {2}\r\n'
b'\xfe\xff TEXT {1}\r\n'
b'\xcc TEXT foo\r\n'
)
def test_invalid_input_type(self):
self.assertRaises(ValueError, self.client._raw_command, 'foo', [])
self.assertRaises(ValueError, self.client._raw_command, u'foo', ['foo'])
def test_failed_continuation_wait(self):
self.client._imap._get_response.return_value = b'blah'
self.client._imap.tagged_commands['tag'] = ('NO', ['go away'])
expected_error = "unexpected response while waiting for continuation response: \(u?'NO', \[u?'go away'\]\)"
with self.assertRaisesRegex(IMAPClient.AbortError, expected_error):
self.client._raw_command(b'FOO', [b'\xff'])
class TestShutdown(IMAPClientTest):
def test_shutdown(self):
self.client.shutdown()
self.client._imap.shutdown.assert_called_once_with()

View File

@@ -0,0 +1,77 @@
# Copyright (c) 2015, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from mock import patch, sentinel, Mock
from imapclient.imapclient import IMAPClient
from imapclient.test.util import unittest
class TestInit(unittest.TestCase):
def setUp(self):
patcher = patch('imapclient.imapclient.imap4')
self.imap4 = patcher.start()
self.addCleanup(patcher.stop)
patcher = patch('imapclient.imapclient.tls')
self.tls = patcher.start()
self.addCleanup(patcher.stop)
patcher = patch('imapclient.imapclient.imaplib')
self.imaplib = patcher.start()
self.addCleanup(patcher.stop)
def test_plain(self):
fakeIMAP4 = Mock()
self.imap4.IMAP4WithTimeout.return_value = fakeIMAP4
imap = IMAPClient('1.2.3.4', timeout=sentinel.timeout)
self.assertEqual(imap._imap, fakeIMAP4)
self.imap4.IMAP4WithTimeout.assert_called_with(
'1.2.3.4', 143,
sentinel.timeout
)
self.assertEqual(imap.host, '1.2.3.4')
self.assertEqual(imap.port, 143)
self.assertEqual(imap.ssl, False)
self.assertEqual(imap.ssl_context, None)
self.assertEqual(imap.stream, False)
def test_SSL(self):
fakeIMAP4_TLS = Mock()
self.tls.IMAP4_TLS.return_value = fakeIMAP4_TLS
imap = IMAPClient('1.2.3.4', ssl=True, ssl_context=sentinel.context,
timeout=sentinel.timeout)
self.assertEqual(imap._imap, fakeIMAP4_TLS)
self.tls.IMAP4_TLS.assert_called_with(
'1.2.3.4', 993,
sentinel.context, sentinel.timeout)
self.assertEqual(imap.host, '1.2.3.4')
self.assertEqual(imap.port, 993)
self.assertEqual(imap.ssl, True)
self.assertEqual(imap.ssl_context, sentinel.context)
self.assertEqual(imap.stream, False)
def test_stream(self):
self.imaplib.IMAP4_stream.return_value = sentinel.IMAP4_stream
imap = IMAPClient('command', stream=True)
self.assertEqual(imap._imap, sentinel.IMAP4_stream)
self.imaplib.IMAP4_stream.assert_called_with('command')
self.assertEqual(imap.host, 'command')
self.assertEqual(imap.port, None)
self.assertEqual(imap.ssl, False)
self.assertEqual(imap.stream, True)
def test_ssl_and_stream_is_error(self):
self.assertRaises(ValueError, IMAPClient, 'command', ssl=True, stream=True)
def test_stream_and_port_is_error(self):
self.assertRaises(ValueError, IMAPClient, 'command', stream=True, port=123)

View File

@@ -0,0 +1,118 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from six import next
from imapclient.response_lexer import TokenSource
from imapclient.test.util import unittest
class TestTokenSource(unittest.TestCase):
def test_one_token(self):
self.check([b'abc'],
[b'abc'])
def test_simple_tokens(self):
self.check([b'abc 111 def'],
[b'abc', b'111', b'def'])
def test_multiple_inputs(self):
self.check([b'abc 111', b'def 222'],
[b'abc', b'111', b'def', b'222'])
def test_whitespace(self):
self.check([b'abc def'],
[b'abc', b'def'])
self.check([b' abc \t\t\r\r\n\n def '],
[b'abc', b'def'])
def test_quoted_strings(self):
self.check([b'"abc def"'],
[b'"abc def"'])
self.check([b'""'],
[b'""'])
self.check([b'111 "abc def" 222'],
[b'111', b'"abc def"', b'222'])
def test_unterminated_strings(self):
message = "No closing '\"'"
self.check_error([b'"'], message)
self.check_error([b'"aaa bbb'], message)
def test_escaping(self):
self.check([br'"aaa\"bbb"'],
[br'"aaa"bbb"'])
self.check([br'"aaa\\bbb"'],
[br'"aaa\bbb"'])
self.check([br'"aaa\\bbb \"\""'],
[br'"aaa\bbb """'])
def test_invalid_escape(self):
self.check([br'"aaa\Zbbb"'],
[br'"aaa\Zbbb"'])
def test_lists(self):
self.check([b'()'],
[b'(', b')'])
self.check([b'(aaa)'],
[b'(', b'aaa', b')'])
self.check([b'(aaa "bbb def" 123)'],
[b'(', b'aaa', b'"bbb def"', b'123', b')'])
self.check([b'(aaa)(bbb ccc)'],
[b'(', b'aaa', b')', b'(', b'bbb', b'ccc', b')'])
self.check([b'(aaa (bbb ccc))'],
[b'(', b'aaa', b'(', b'bbb', b'ccc', b')', b')'])
def test_square_brackets(self):
self.check([b'[aaa bbb]'],
[b'[aaa bbb]'])
self.check([b'aaa[bbb]'],
[b'aaa[bbb]'])
self.check([b'[bbb]aaa'],
[b'[bbb]aaa'])
self.check([b'aaa [bbb]'],
[b'aaa', b'[bbb]'])
def test_no_escaping_in_square_brackets(self):
self.check([br'[aaa\\bbb]'],
[br'[aaa\\bbb]'])
def test_unmatched_square_brackets(self):
message = "No closing ']'"
self.check_error([b'['], message)
self.check_error([b'[aaa bbb'], message)
def test_literal(self):
source = TokenSource([(b'abc {7}', b'foo bar'), b')'])
tokens = iter(source)
self.assertEqual(next(tokens), b'abc')
self.assertEqual(next(tokens), b'{7}')
self.assertEqual(source.current_literal, b'foo bar')
self.assertEqual(next(tokens), b')')
self.assertRaises(StopIteration, lambda: next(tokens))
def test_literals(self):
source = TokenSource([
(b'abc {7}', b'foo bar'),
(b'{5}', b'snafu'),
b')'])
tokens = iter(source)
self.assertEqual(next(tokens), b'abc')
self.assertEqual(next(tokens), b'{7}')
self.assertEqual(source.current_literal, b'foo bar')
self.assertEqual(next(tokens), b'{5}')
self.assertEqual(source.current_literal, b'snafu')
self.assertEqual(next(tokens), b')')
self.assertRaises(StopIteration, lambda: next(tokens))
def check(self, text_in, expected_out):
tokens = TokenSource(text_in)
self.assertSequenceEqual(list(tokens), expected_out)
def check_error(self, text_in, expected_message):
self.assertRaisesRegex(ValueError, expected_message,
lambda: list(TokenSource(text_in)))

View File

@@ -0,0 +1,503 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
'''
Unit tests for the FetchTokeniser and FetchParser classes
'''
from __future__ import unicode_literals
from datetime import datetime
from imapclient.datetime_util import datetime_to_native
from imapclient.fixed_offset import FixedOffset
from imapclient.response_parser import (
parse_response,
parse_message_list,
parse_fetch_response,
ParseError,
)
from imapclient.response_types import Envelope, Address
from imapclient.test.util import unittest
# TODO: test invalid dates and times
CRLF = b'\r\n'
class TestParseResponse(unittest.TestCase):
def test_unquoted(self):
self._test(b'FOO', b'FOO')
self._test(b'F.O:-O_0;', b'F.O:-O_0;')
self._test(br'\Seen', br'\Seen')
def test_string(self):
self._test(b'"TEST"', b'TEST')
def test_int(self):
self._test(b'45', 45)
def test_nil(self):
self._test(b'NIL', None)
def test_empty_tuple(self):
self._test(b'()', ())
def test_tuple(self):
self._test(b'(123 "foo" GeE)', (123, b'foo', b'GeE'))
def test_int_and_tuple(self):
self._test(b'1 (123 "foo")', (1, (123, b'foo')), wrap=False)
def test_nested_tuple(self):
self._test(b'(123 "foo" ("more" NIL) 66)',
(123, b"foo", (b"more", None), 66))
def test_deeper_nest_tuple(self):
self._test(b'(123 "foo" ((0 1 2) "more" NIL) 66)',
(123, b"foo", ((0, 1, 2), b"more", None), 66))
def test_complex_mixed(self):
self._test(b'((FOO "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 1152 23) '
b'("TEXT" "PLAIN" ("CHARSET" "US-ASCII" "NAME" "cc.diff") '
b'"<hi.there>" "foo" "BASE64" 4554 73) "MIXED")',
((b'FOO', b'PLAIN', (b'CHARSET', b'US-ASCII'), None, None, b'7BIT', 1152, 23),
(b'TEXT', b'PLAIN', (b'CHARSET', b'US-ASCII', b'NAME', b'cc.diff'),
b'<hi.there>', b'foo', b'BASE64', 4554, 73), b'MIXED'))
def test_envelopey(self):
self._test(b'(UID 5 ENVELOPE ("internal_date" "subject" '
b'(("name" NIL "address1" "domain1.com")) '
b'((NIL NIL "address2" "domain2.com")) '
b'(("name" NIL "address3" "domain3.com")) '
b'((NIL NIL "address4" "domain4.com")) '
b'NIL NIL "<reply-to-id>" "<msg_id>"))',
(b'UID',
5,
b'ENVELOPE',
(b'internal_date',
b'subject',
((b'name', None, b'address1', b'domain1.com'),),
((None, None, b'address2', b'domain2.com'),),
((b'name', None, b'address3', b'domain3.com'),),
((None, None, b'address4', b'domain4.com'),),
None,
None,
b'<reply-to-id>',
b'<msg_id>')))
def test_envelopey_quoted(self):
self._test(b'(UID 5 ENVELOPE ("internal_date" "subject with \\"quotes\\"" '
b'(("name" NIL "address1" "domain1.com")) '
b'((NIL NIL "address2" "domain2.com")) '
b'(("name" NIL "address3" "domain3.com")) '
b'((NIL NIL "address4" "domain4.com")) '
b'NIL NIL "<reply-to-id>" "<msg_id>"))',
(b'UID',
5,
b'ENVELOPE',
(b'internal_date',
b'subject with "quotes"',
((b'name', None, b'address1', b'domain1.com'),),
((None, None, b'address2', b'domain2.com'),),
((b'name', None, b'address3', b'domain3.com'),),
((None, None, b'address4', b'domain4.com'),),
None,
None,
b'<reply-to-id>',
b'<msg_id>')))
def test_literal(self):
literal_text = add_crlf(
b"012\n"
b"abc def XYZ\n"
)
self._test([(b'{18}', literal_text)], literal_text)
def test_literal_with_more(self):
literal_text = add_crlf(
b"012\n"
b"abc def XYZ\n"
)
response = [(b'(12 "foo" {18}', literal_text), b")"]
self._test(response, (12, b'foo', literal_text))
def test_quoted_specials(self):
self._test(br'"\"foo bar\""', b'"foo bar"')
self._test(br'"foo \"bar\""', b'foo "bar"')
self._test(br'"foo\\bar"', br'foo\bar')
def test_square_brackets(self):
self._test(b'foo[bar rrr]', b'foo[bar rrr]')
self._test(b'"foo[bar rrr]"', b'foo[bar rrr]')
self._test(b'[foo bar]def', b'[foo bar]def')
self._test(b'(foo [bar rrr])', (b'foo', b'[bar rrr]'))
self._test(b'(foo foo[bar rrr])', (b'foo', b'foo[bar rrr]'))
def test_incomplete_tuple(self):
self._test_parse_error(b'abc (1 2', 'Tuple incomplete before "\(1 2"')
def test_bad_literal(self):
self._test_parse_error([(b'{99}', b'abc')],
'Expecting literal of size 99, got 3')
def test_bad_quoting(self):
self._test_parse_error(b'"abc next', """No closing '"'""")
def _test(self, to_parse, expected, wrap=True):
if wrap:
# convenience - expected value should be wrapped in another tuple
expected = (expected,)
if not isinstance(to_parse, list):
to_parse = [to_parse]
output = parse_response(to_parse)
self.assertSequenceEqual(output, expected)
def _test_parse_error(self, to_parse, expected_msg):
if not isinstance(to_parse, list):
to_parse = [to_parse]
self.assertRaisesRegex(ParseError, expected_msg,
parse_response, to_parse)
class TestParseMessageList(unittest.TestCase):
def test_basic(self):
out = parse_message_list([b'1 2 3'])
self.assertSequenceEqual(out, [1, 2, 3])
self.assertEqual(out.modseq, None)
def test_one_id(self):
self.assertSequenceEqual(parse_message_list([b'4']), [4])
def test_modseq(self):
out = parse_message_list([b'1 2 3 (modseq 999)'])
self.assertSequenceEqual(out, [1, 2, 3])
self.assertEqual(out.modseq, 999)
def test_modseq_no_space(self):
out = parse_message_list([b'1 2 3(modseq 999)'])
self.assertSequenceEqual(out, [1, 2, 3])
self.assertEqual(out.modseq, 999)
def test_modseq_interleaved(self):
# Unlikely but test it anyway.
out = parse_message_list([b'1 2 (modseq 9) 3 4'])
self.assertSequenceEqual(out, [1, 2, 3, 4])
self.assertEqual(out.modseq, 9)
class TestParseFetchResponse(unittest.TestCase):
def test_basic(self):
self.assertEqual(parse_fetch_response([b'4 ()']), {4: {b'SEQ': 4}})
def test_none_special_case(self):
self.assertEqual(parse_fetch_response([None]), {})
def test_bad_msgid(self):
self.assertRaises(ParseError, parse_fetch_response, [b'abc ()'])
def test_bad_data(self):
self.assertRaises(ParseError, parse_fetch_response, [b'2 WHAT'])
def test_missing_data(self):
self.assertRaises(ParseError, parse_fetch_response, [b'2'])
def test_simple_pairs(self):
self.assertEqual(parse_fetch_response([b'23 (ABC 123 StUfF "hello")']),
{23: {b'ABC': 123,
b'STUFF': b'hello',
b'SEQ': 23}})
def test_odd_pairs(self):
self.assertRaises(ParseError, parse_fetch_response, [b'(ONE)'])
self.assertRaises(ParseError, parse_fetch_response, [b'(ONE TWO THREE)'])
def test_UID(self):
self.assertEqual(parse_fetch_response([b'23 (UID 76)']),
{76: {b'SEQ': 23}})
self.assertEqual(parse_fetch_response([b'23 (uiD 76)']),
{76: {b'SEQ': 23}})
def test_not_uid_is_key(self):
self.assertEqual(parse_fetch_response([b'23 (UID 76)'], uid_is_key=False),
{23: {b'UID': 76,
b'SEQ': 23}})
def test_bad_UID(self):
self.assertRaises(ParseError, parse_fetch_response, [b'(UID X)'])
def test_FLAGS(self):
self.assertEqual(parse_fetch_response([b'23 (FLAGS (\Seen Stuff))']),
{23: {b'SEQ': 23, b'FLAGS': (br'\Seen', b'Stuff')}})
def test_multiple_messages(self):
self.assertEqual(parse_fetch_response(
[b"2 (FLAGS (Foo Bar)) ",
b"7 (FLAGS (Baz Sneeve))"]),
{
2: {b'FLAGS': (b'Foo', b'Bar'), b'SEQ': 2},
7: {b'FLAGS': (b'Baz', b'Sneeve'), b'SEQ': 7},
})
def test_same_message_appearing_multiple_times(self):
# This can occur when server sends unsolicited FETCH responses
# (e.g. RFC 4551)
self.assertEqual(parse_fetch_response(
[b"2 (FLAGS (Foo Bar)) ",
b"2 (MODSEQ 4)"]),
{2: {b'FLAGS': (b'Foo', b'Bar'), b'SEQ': 2, b'MODSEQ': 4}})
def test_literals(self):
self.assertEqual(parse_fetch_response([(b'1 (RFC822.TEXT {4}', b'body'),
(b' RFC822 {21}', b'Subject: test\r\n\r\nbody'),
b')']),
{1: {b'RFC822.TEXT': b'body',
b'RFC822': b'Subject: test\r\n\r\nbody',
b'SEQ': 1}})
def test_literals_and_keys_with_square_brackets(self):
self.assertEqual(parse_fetch_response([(b'1 (BODY[TEXT] {11}', b'Hi there.\r\n'), b')']),
{1: {b'BODY[TEXT]': b'Hi there.\r\n',
b'SEQ': 1}})
def test_BODY_HEADER_FIELDS(self):
header_text = b'Subject: A subject\r\nFrom: Some one <someone@mail.com>\r\n\r\n'
self.assertEqual(parse_fetch_response(
[(b'123 (UID 31710 BODY[HEADER.FIELDS (from subject)] {57}', header_text), b')']),
{31710: {b'BODY[HEADER.FIELDS (FROM SUBJECT)]': header_text,
b'SEQ': 123}})
def test_BODY(self):
self.check_BODYish_single_part(b'BODY')
self.check_BODYish_multipart(b'BODY')
self.check_BODYish_nested_multipart(b'BODY')
def test_BODYSTRUCTURE(self):
self.check_BODYish_single_part(b'BODYSTRUCTURE')
self.check_BODYish_nested_multipart(b'BODYSTRUCTURE')
def check_BODYish_single_part(self, respType):
text = b'123 (UID 317 ' + respType + \
b'("TEXT" "PLAIN" ("CHARSET" "us-ascii") NIL NIL "7BIT" 16 1))'
parsed = parse_fetch_response([text])
self.assertEqual(parsed, {
317: {
respType: (b'TEXT', b'PLAIN', (b'CHARSET', b'us-ascii'), None, None, b'7BIT', 16, 1),
b'SEQ': 123
}
})
self.assertFalse(parsed[317][respType].is_multipart)
def check_BODYish_multipart(self, respType):
text = b'123 (UID 269 ' + respType + b' ' \
b'(("TEXT" "HTML" ("CHARSET" "us-ascii") NIL NIL "QUOTED-PRINTABLE" 55 3)' \
b'("TEXT" "PLAIN" ("CHARSET" "us-ascii") NIL NIL "7BIT" 26 1) "MIXED"))'
parsed = parse_fetch_response([text])
self.assertEqual(parsed, {
269: {
respType: ([(b'TEXT', b'HTML', (b'CHARSET', b'us-ascii'), None, None, b'QUOTED-PRINTABLE', 55, 3),
(b'TEXT', b'PLAIN', (b'CHARSET', b'us-ascii'), None, None, b'7BIT', 26, 1)],
b'MIXED'),
b'SEQ': 123}
})
self.assertTrue(parsed[269][respType].is_multipart)
def check_BODYish_nested_multipart(self, respType):
text = b'1 (' + respType + b'(' \
b'(' \
b'("text" "html" ("charset" "utf-8") NIL NIL "7bit" 97 3 NIL NIL NIL NIL)' \
b'("text" "plain" ("charset" "utf-8") NIL NIL "7bit" 62 3 NIL NIL NIL NIL)' \
b'"alternative" ("boundary" "===============8211050864078048428==") NIL NIL NIL' \
b')' \
b'("text" "plain" ("charset" "utf-8") NIL NIL "7bit" 16 1 NIL ("attachment" ("filename" "attachment.txt")) NIL NIL) ' \
b'"mixed" ("boundary" "===============0373402508605428821==") NIL NIL NIL))'
parsed = parse_fetch_response([text])
self.assertEqual(parsed, {1: {
respType: (
[
(
[
(b'text', b'html', (b'charset', b'utf-8'), None,
None, b'7bit', 97, 3, None, None, None, None),
(b'text', b'plain', (b'charset', b'utf-8'), None,
None, b'7bit', 62, 3, None, None, None, None)
], b'alternative', (b'boundary', b'===============8211050864078048428=='), None, None, None
),
(b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 16, 1,
None, (b'attachment', (b'filename', b'attachment.txt')), None, None)
], b'mixed', (b'boundary', b'===============0373402508605428821=='), None, None, None,
),
b'SEQ': 1,
}})
self.assertTrue(parsed[1][respType].is_multipart)
self.assertTrue(parsed[1][respType][0][0].is_multipart)
self.assertFalse(parsed[1][respType][0][0][0][0].is_multipart)
def test_partial_fetch(self):
body = b'01234567890123456789'
self.assertEqual(parse_fetch_response(
[(b'123 (UID 367 BODY[]<0> {20}', body), b')']),
{367: {b'BODY[]<0>': body,
b'SEQ': 123}})
def test_ENVELOPE(self):
envelope_str = (b'1 (ENVELOPE ( '
b'"Sun, 24 Mar 2013 22:06:10 +0200" '
b'"subject" '
b'(("name" NIL "address1" "domain1.com")) ' # from (name and address)
b'((NIL NIL "address2" "domain2.com")) ' # sender (just address)
b'(("name" NIL "address3" "domain3.com") NIL) ' # reply to
b'NIL' # to (no address)
b'((NIL NIL "address4" "domain4.com") ' # cc
b'("person" NIL "address4b" "domain4b.com")) '
b'NIL ' # bcc
b'"<reply-to-id>" '
b'"<msg_id>"))')
output = parse_fetch_response([envelope_str], normalise_times=False)
self.assertSequenceEqual(output[1][b'ENVELOPE'],
Envelope(
datetime(2013, 3, 24, 22, 6, 10, tzinfo=FixedOffset(120)),
b"subject",
(Address(b"name", None, b"address1", b"domain1.com"),),
(Address(None, None, b"address2", b"domain2.com"),),
(Address(b"name", None, b"address3", b"domain3.com"),),
None,
(Address(None, None, b"address4", b"domain4.com"),
Address(b"person", None, b"address4b", b"domain4b.com")),
None, b"<reply-to-id>", b"<msg_id>"
)
)
def test_ENVELOPE_with_no_date(self):
envelope_str = (
b'1 (ENVELOPE ( '
b'NIL '
b'"subject" '
b'NIL '
b'NIL '
b'NIL '
b'NIL '
b'NIL '
b'NIL '
b'"<reply-to-id>" '
b'"<msg_id>"))'
)
output = parse_fetch_response([envelope_str], normalise_times=False)
self.assertSequenceEqual(output[1][b'ENVELOPE'],
Envelope(
None,
b"subject",
None,
None,
None,
None,
None,
None,
b"<reply-to-id>", b"<msg_id>"
)
)
def test_ENVELOPE_with_invalid_date(self):
envelope_str = (b'1 (ENVELOPE ( '
b'"wtf" ' # bad date
b'"subject" '
b'NIL NIL NIL NIL NIL NIL '
b'"<reply-to-id>" "<msg_id>"))')
output = parse_fetch_response([envelope_str], normalise_times=False)
self.assertSequenceEqual(output[1][b'ENVELOPE'],
Envelope(
None,
b"subject",
None, None, None, None, None, None,
b"<reply-to-id>", b"<msg_id>",
)
)
def test_ENVELOPE_with_empty_addresses(self):
envelope_str = (b'1 (ENVELOPE ( '
b'NIL '
b'"subject" '
b'(("name" NIL "address1" "domain1.com") NIL) '
b'(NIL (NIL NIL "address2" "domain2.com")) '
b'(("name" NIL "address3" "domain3.com") NIL ("name" NIL "address3b" "domain3b.com")) '
b'NIL'
b'((NIL NIL "address4" "domain4.com") '
b'("person" NIL "address4b" "domain4b.com")) '
b'NIL "<reply-to-id>" "<msg_id>"))')
output = parse_fetch_response([envelope_str], normalise_times=False)
self.assertSequenceEqual(output[1][b'ENVELOPE'],
Envelope(
None,
b"subject",
(Address(b"name", None, b"address1", b"domain1.com"),),
(Address(None, None, b"address2", b"domain2.com"),),
(Address(b"name", None, b"address3", b"domain3.com"),
Address(b"name", None, b"address3b", b"domain3b.com")),
None,
(Address(None, None, b"address4", b"domain4.com"),
Address(b"person", None, b"address4b", b"domain4b.com")),
None, b"<reply-to-id>", b"<msg_id>"
)
)
def test_INTERNALDATE(self):
out = parse_fetch_response(
[b'1 (INTERNALDATE " 9-Feb-2007 17:08:08 -0430")'],
normalise_times=False
)
self.assertEqual(
out[1][b'INTERNALDATE'],
datetime(2007, 2, 9, 17, 8, 8, 0, FixedOffset(-4 * 60 - 30))
)
def test_INTERNALDATE_normalised(self):
output = parse_fetch_response([b'3 (INTERNALDATE " 9-Feb-2007 17:08:08 -0430")'])
dt = output[3][b'INTERNALDATE']
self.assertTrue(dt.tzinfo is None) # Returned date should be in local timezone
expected_dt = datetime_to_native(
datetime(2007, 2, 9, 17, 8, 8, 0, FixedOffset(-4 * 60 - 30)))
self.assertEqual(dt, expected_dt)
def test_mixed_types(self):
self.assertEqual(parse_fetch_response([(
b'1 (INTERNALDATE " 9-Feb-2007 17:08:08 +0100" RFC822 {21}',
b'Subject: test\r\n\r\nbody'
), b')']), {
1: {
b'INTERNALDATE': datetime_to_native(datetime(2007, 2, 9, 17, 8, 8, 0, FixedOffset(60))),
b'RFC822': b'Subject: test\r\n\r\nbody',
b'SEQ': 1
}
})
def test_Address_str(self):
self.assertEqual(str(Address(b"Mary Jane", None, b"mary", b"jane.org")),
"Mary Jane <mary@jane.org>")
self.assertEqual(str(Address("Mary Jane", None, "mary", "jane.org")),
"Mary Jane <mary@jane.org>")
self.assertEqual(str(Address("Anonymous", None, "undisclosed-recipients", None)),
"Anonymous <undisclosed-recipients>")
self.assertEqual(str(Address(None, None, None, "undisclosed-recipients")),
"undisclosed-recipients")
def add_crlf(text):
return CRLF.join(text.splitlines()) + CRLF

View File

@@ -0,0 +1,119 @@
# Copyright (c) 2015, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from datetime import date, datetime
from mock import Mock
from .imapclient_test import IMAPClientTest
class TestSearchBase(IMAPClientTest):
def setUp(self):
super(TestSearchBase, self).setUp()
self.client._raw_command_untagged = Mock()
self.client._raw_command_untagged.return_value = [b'1 2 44']
def check_call(self, expected_args):
self.client._raw_command_untagged.assert_called_once_with(
b'SEARCH', expected_args)
class TestSearch(TestSearchBase):
def test_bytes_criteria(self):
result = self.client.search([b'FOO', b'BAR'])
self.check_call([b'FOO', b'BAR'])
self.assertEqual(result, [1, 2, 44])
self.assertEqual(result.modseq, None)
def test_bytes_criteria_with_charset(self):
self.client.search([b'FOO', b'BAR'], 'utf-92')
self.check_call([b'CHARSET', b'utf-92', b'FOO', b'BAR'])
def test_unicode_criteria(self):
result = self.client.search(['FOO', 'BAR'])
# Default conversion using us-ascii.
self.check_call([b'FOO', b'BAR'])
self.assertEqual(result, [1, 2, 44])
self.assertEqual(result.modseq, None)
def test_unicode_criteria_with_charset(self):
self.client.search(['FOO', '\u2639'], 'utf-8')
# Default conversion using us-ascii.
self.check_call([b'CHARSET', b'utf-8', b'FOO', b'\xe2\x98\xb9'])
def test_with_date(self):
self.client.search(['SINCE', date(2005, 4, 3)])
self.check_call([b'SINCE', b'03-Apr-2005'])
def test_with_datetime(self):
self.client.search(['SINCE', datetime(2005, 4, 3, 2, 1, 0)])
self.check_call([b'SINCE', b'03-Apr-2005']) # Time part is ignored
def test_quoting(self):
self.client.search(['TEXT', 'foo bar'])
self.check_call([b'TEXT', b'"foo bar"'])
def test_no_results(self):
self.client._raw_command_untagged.return_value = [None]
result = self.client.search(['FOO'])
self.assertEqual(result, [])
self.assertEqual(result.modseq, None)
def test_modseq(self):
self.client._raw_command_untagged.return_value = [b'1 2 (MODSEQ 51101)']
result = self.client.search(['MODSEQ', '40000'])
self.check_call([b'MODSEQ', b'40000'])
self.assertEqual(result, [1, 2])
self.assertEqual(result.modseq, 51101)
def test_nested_empty(self):
self.assertRaises(ValueError, self.client.search, [[]])
def test_single(self):
self.client.search([['FOO']])
self.check_call([b'(FOO)'])
def test_nested(self):
self.client.search(['NOT', ['SUBJECT', 'topic', 'TO', 'some@email.com']])
self.check_call([b'NOT', b'(SUBJECT', b'topic', b'TO', b'some@email.com)'])
def test_nested_multiple(self):
self.client.search(['NOT', ['OR', ['A', 'x', 'B', 'y'], ['C', 'z']]])
self.check_call([b'NOT', b'(OR', b'(A', b'x', b'B', b'y)', b'(C', b'z))'])
def test_nested_tuple(self):
self.client.search(['NOT', ('SUBJECT', 'topic', 'TO', 'some@email.com')])
self.check_call([b'NOT', b'(SUBJECT', b'topic', b'TO', b'some@email.com)'])
class TestGmailSearch(TestSearchBase):
def test_bytes_query(self):
result = self.client.gmail_search(b'foo bar')
self.check_call([b'CHARSET', b'UTF-8', b'X-GM-RAW', b'"foo bar"'])
self.assertEqual(result, [1, 2, 44])
def test_bytes_query_with_charset(self):
result = self.client.gmail_search(b'foo bar', 'utf-42')
self.check_call([b'CHARSET', b'utf-42', b'X-GM-RAW', b'"foo bar"'])
self.assertEqual(result, [1, 2, 44])
def test_unicode_criteria_with_charset(self):
self.client.gmail_search('foo \u2639', 'utf-8')
self.check_call([b'CHARSET', b'utf-8', b'X-GM-RAW', b'"foo \xe2\x98\xb9"'])

View File

@@ -0,0 +1,42 @@
# Copyright (c) 2015, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from mock import Mock
from .imapclient_test import IMAPClientTest
class TestSort(IMAPClientTest):
def setUp(self):
super(TestSort, self).setUp()
self.client._cached_capabilities = (b'SORT',)
self.client._raw_command_untagged = Mock()
self.client._raw_command_untagged.return_value = b'9 8 7'
def check_call(self, expected_args):
self.client._raw_command_untagged.assert_called_once_with(
b'SORT', expected_args, unpack=True)
def test_no_support(self):
self.client._cached_capabilities = (b'BLAH',)
self.assertRaises(ValueError, self.client.sort, 'ARRIVAL')
def test_single_criteria(self):
ids = self.client.sort('arrival')
self.check_call([b'(ARRIVAL)', b'UTF-8', b'ALL'])
self.assertSequenceEqual(ids, [9, 8, 7])
def test_multiple_criteria(self):
self.client.sort(['arrival', b'SUBJECT'])
self.check_call([b'(ARRIVAL SUBJECT)', b'UTF-8', b'ALL'])
def test_all_args(self):
self.client.sort('arrival', ['TEXT', '\u261e'], 'UTF-7')
self.check_call([b'(ARRIVAL)', b'UTF-7', b'TEXT', b'+Jh4-'])

View File

@@ -0,0 +1,63 @@
# Copyright (c) 2015, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from mock import Mock, patch, sentinel
from ..imapclient import IMAPClient
from .imapclient_test import IMAPClientTest
class TestStarttls(IMAPClientTest):
def setUp(self):
super(TestStarttls, self).setUp()
patcher = patch("imapclient.imapclient.tls")
self.tls = patcher.start()
self.addCleanup(patcher.stop)
self.client._imap.sock = sentinel.old_sock
self.new_sock = Mock()
self.new_sock.makefile.return_value = sentinel.file
self.tls.wrap_socket.return_value = self.new_sock
self.client.host = sentinel.host
self.client.ssl = False
self.client._starttls_done = False
self.client._imap._simple_command.return_value = "OK", [b'start TLS negotiation']
def test_works(self):
resp = self.client.starttls(sentinel.ssl_context)
self.tls.wrap_socket.assert_called_once_with(
sentinel.old_sock,
sentinel.ssl_context,
sentinel.host,
)
self.new_sock.makefile.assert_called_once_with()
self.assertEqual(self.client._imap.file, sentinel.file)
self.assertEqual(resp, b'start TLS negotiation')
def test_command_fails(self):
self.client._imap._simple_command.return_value = "NO", [b'sorry']
with self.assertRaises(IMAPClient.Error) as raised:
self.client.starttls(sentinel.ssl_context)
self.assertEqual(str(raised.exception), "starttls failed: sorry")
def test_fails_if_called_twice(self):
self.client.starttls(sentinel.ssl_context)
self.assert_tls_already_established()
def test_fails_if_ssl_true(self):
self.client.ssl = True
self.assert_tls_already_established()
def assert_tls_already_established(self):
with self.assertRaises(IMAPClient.AbortError) as raised:
self.client.starttls(sentinel.ssl_context)
self.assertEqual(str(raised.exception), "TLS session already established")

View File

@@ -0,0 +1,130 @@
# Copyright (c) 2016, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
import six
from mock import patch, sentinel, Mock
from ..imapclient import DELETED, SEEN, ANSWERED, FLAGGED, DRAFT, RECENT
from .imapclient_test import IMAPClientTest
class TestFlagsConsts(IMAPClientTest):
def test_flags_are_bytes(self):
for flag in DELETED, SEEN, ANSWERED, FLAGGED, DRAFT, RECENT:
if not isinstance(flag, six.binary_type):
self.fail("%r flag is not bytes" % flag)
class TestFlags(IMAPClientTest):
def setUp(self):
super(TestFlags, self).setUp()
self.client._command_and_check = Mock()
def test_get(self):
with patch.object(self.client, 'fetch', autospec=True,
return_value={123: {b'FLAGS': [b'foo', b'bar']},
444: {b'FLAGS': [b'foo']}}):
out = self.client.get_flags(sentinel.messages)
self.client.fetch.assert_called_with(sentinel.messages, ['FLAGS'])
self.assertEqual(out, {123: [b'foo', b'bar'],
444: [b'foo']})
def test_set(self):
self.check(self.client.set_flags, b'FLAGS')
def test_add(self):
self.check(self.client.add_flags, b'+FLAGS')
def test_remove(self):
self.check(self.client.remove_flags, b'-FLAGS')
def check(self, meth, expected_command):
self._check(meth, expected_command)
self._check(meth, expected_command, silent=True)
def _check(self, meth, expected_command, silent=False):
if silent:
expected_command += b".SILENT"
cc = self.client._command_and_check
cc.return_value = [
b'11 (FLAGS (blah foo) UID 1)',
b'11 (UID 1 OTHER (dont))',
b'22 (FLAGS (foo) UID 2)',
b'22 (UID 2 OTHER (care))',
]
resp = meth([1, 2], 'foo', silent=silent)
cc.assert_called_once_with(
'store', b"1,2",
expected_command,
'(foo)',
uid=True)
if silent:
self.assertIsNone(resp)
else:
self.assertEqual(resp, {
1: (b'blah', b'foo'),
2: (b'foo',),
})
cc.reset_mock()
class TestGmailLabels(IMAPClientTest):
def setUp(self):
super(TestGmailLabels, self).setUp()
self.client._command_and_check = Mock()
def test_get(self):
with patch.object(self.client, 'fetch', autospec=True,
return_value={123: {b'X-GM-LABELS': [b'foo', b'bar']},
444: {b'X-GM-LABELS': [b'foo']}}):
out = self.client.get_gmail_labels(sentinel.messages)
self.client.fetch.assert_called_with(sentinel.messages, [b'X-GM-LABELS'])
self.assertEqual(out, {123: [b'foo', b'bar'],
444: [b'foo']})
def test_set(self):
self.check(self.client.set_gmail_labels, b'X-GM-LABELS')
def test_add(self):
self.check(self.client.add_gmail_labels, b'+X-GM-LABELS')
def test_remove(self):
self.check(self.client.remove_gmail_labels, b'-X-GM-LABELS')
def check(self, meth, expected_command):
self._check(meth, expected_command)
self._check(meth, expected_command, silent=True)
def _check(self, meth, expected_command, silent=False):
if silent:
expected_command += b".SILENT"
cc = self.client._command_and_check
cc.return_value = [
b'11 (X-GM-LABELS (blah "f\\"o\\"o") UID 1)',
b'22 (X-GM-LABELS ("f\\"o\\"o") UID 2)',
b'11 (UID 1 FLAGS (dont))',
b'22 (UID 2 FLAGS (care))',
]
resp = meth([1, 2], 'f"o"o', silent=silent)
cc.assert_called_once_with(
'store', b"1,2",
expected_command,
'("f\\"o\\"o")',
uid=True)
if silent:
self.assertIsNone(resp)
else:
self.assertEqual(resp, {
1: (b'blah', b'f"o"o'),
2: (b'f"o"o',),
})
cc.reset_mock()

View File

@@ -0,0 +1,43 @@
# Copyright (c) 2015, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from mock import Mock
from .imapclient_test import IMAPClientTest
class TestThread(IMAPClientTest):
def setUp(self):
super(TestThread, self).setUp()
self.client._cached_capabilities = (b'THREAD=REFERENCES',)
self.client._raw_command_untagged = Mock()
self.client._raw_command_untagged.return_value = [b'(1 2)(3)(4 5 6)']
def check_call(self, expected_args):
self.client._raw_command_untagged.assert_called_once_with(
b'THREAD', expected_args)
def test_no_thread_support(self):
self.client._cached_capabilities = (b'NOT-THREAD',)
self.assertRaises(ValueError, self.client.thread)
def test_unsupported_algorithm(self):
self.client._cached_capabilities = (b'THREAD=FOO',)
self.assertRaises(ValueError, self.client.thread)
def test_defaults(self):
threads = self.client.thread()
self.check_call([b'REFERENCES', b'UTF-8', b'ALL'])
self.assertSequenceEqual(threads, ((1, 2), (3,), (4, 5, 6)))
def test_all_args(self):
self.client._cached_capabilities = (b'THREAD=COTTON',)
self.client.thread('COTTON', ['TEXT', '\u261e'], 'UTF-7')
self.check_call([b'COTTON', b'UTF-7', b'TEXT', b'+Jh4-'])

View File

@@ -0,0 +1,159 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from imapclient.imapclient import (
join_message_ids,
_normalise_search_criteria,
normalise_text_list,
seq_to_parenstr,
seq_to_parenstr_upper,
)
from imapclient.test.util import unittest
class Test_normalise_text_list(unittest.TestCase):
def check(self, items, expected):
self.assertEqual(normalise_text_list(items), expected)
def test_unicode(self):
self.check('Foo', ['Foo'])
def test_binary(self):
self.check(b'FOO', ['FOO'])
def test_tuple(self):
self.check(('FOO', 'BAR'), ['FOO', 'BAR'])
def test_list(self):
self.check(['FOO', 'BAR'], ['FOO', 'BAR'])
def test_iter(self):
self.check(iter(['FOO', 'BAR']), ['FOO', 'BAR'])
def test_mixed_list(self):
self.check(['FOO', b'Bar'], ['FOO', 'Bar'])
class Test_seq_to_parenstr(unittest.TestCase):
def check(self, items, expected):
self.assertEqual(seq_to_parenstr(items), expected)
def test_unicode(self):
self.check('foO', '(foO)')
def test_binary(self):
self.check(b'Foo', '(Foo)')
def test_tuple(self):
self.check(('FOO', 'BAR'), '(FOO BAR)')
def test_list(self):
self.check(['FOO', 'BAR'], '(FOO BAR)')
def test_iter(self):
self.check(iter(['FOO', 'BAR']), '(FOO BAR)')
def test_mixed_list(self):
self.check(['foo', b'BAR'], '(foo BAR)')
class Test_seq_to_parenstr_upper(unittest.TestCase):
def check(self, items, expected):
self.assertEqual(seq_to_parenstr_upper(items), expected)
def test_unicode(self):
self.check('foO', '(FOO)')
def test_binary(self):
self.check(b'Foo', '(FOO)')
def test_tuple(self):
self.check(('foo', 'BAR'), '(FOO BAR)')
def test_list(self):
self.check(['FOO', 'bar'], '(FOO BAR)')
def test_iter(self):
self.check(iter(['FOO', 'BaR']), '(FOO BAR)')
def test_mixed_list(self):
self.check(['foo', b'BAR'], '(FOO BAR)')
class Test_join_message_ids(unittest.TestCase):
def check(self, items, expected):
self.assertEqual(join_message_ids(items), expected)
def test_int(self):
self.check(123, b'123')
def test_unicode(self):
self.check('123', b'123')
def test_unicode_non_numeric(self):
self.check('2:*', b'2:*')
def test_binary(self):
self.check(b'123', b'123')
def test_binary_non_numeric(self):
self.check(b'2:*', b'2:*')
def test_tuple(self):
self.check((123, 99), b'123,99')
def test_mixed_list(self):
self.check(['2:3', 123, b'44'], b'2:3,123,44')
def test_iter(self):
self.check(iter([123, 99]), b'123,99')
class Test_normalise_search_criteria(unittest.TestCase):
def check(self, criteria, charset, expected):
self.assertEqual(_normalise_search_criteria(criteria, charset), expected)
def test_list(self):
self.check(['FOO', '\u263a'], 'utf-8', [b'FOO', b'\xe2\x98\xba'])
def test_tuple(self):
self.check(('FOO', 'BAR'), None, [b'FOO', b'BAR'])
def test_mixed_list(self):
self.check(['FOO', b'BAR'], None, [b'FOO', b'BAR'])
def test_quoting(self):
self.check(['foo bar'], None, [b'"foo bar"'])
def test_ints(self):
self.check(['modseq', 500], None, [b'modseq', b'500'])
def test_unicode(self):
self.check('Foo', None, [b'Foo'])
def test_binary(self):
self.check(b'FOO', None, [b'FOO'])
def test_unicode_with_charset(self):
self.check('\u263a', 'UTF-8', [b'\xe2\x98\xba'])
def test_binary_with_charset(self):
# charset is unused when criteria is binary.
self.check(b'FOO', 'UTF-9', [b'FOO'])
def test_no_quoting_when_criteria_given_as_string(self):
self.check('foo bar', None, [b'foo bar'])
def test_None(self):
self.assertRaises(ValueError, _normalise_search_criteria, None, None)
def test_empty(self):
self.assertRaises(ValueError, _normalise_search_criteria, '', None)

View File

@@ -0,0 +1,26 @@
# Copyright (c) 2015, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from ..version import _imapclient_version_string
from .util import unittest
class TestVersionString(unittest.TestCase):
def test_dot_oh(self):
self.assertEqual(_imapclient_version_string((1, 0, 0, 'final')), '1.0.0')
def test_minor(self):
self.assertEqual(_imapclient_version_string((2, 1, 0, 'final')), '2.1.0')
def test_point_release(self):
self.assertEqual(_imapclient_version_string((1, 2, 3, 'final')), '1.2.3')
def test_alpha(self):
self.assertEqual(_imapclient_version_string((2, 1, 0, 'alpha')), '2.1.0-alpha')
def test_beta_point(self):
self.assertEqual(_imapclient_version_string((2, 1, 3, 'beta')), '2.1.3-beta')

View File

@@ -0,0 +1,34 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
from mock import Mock
from imapclient.imapclient import IMAPClient
class TestableIMAPClient(IMAPClient):
def __init__(self):
super(TestableIMAPClient, self).__init__('somehost')
def _create_IMAP4(self):
return MockIMAP4()
class MockIMAP4(Mock):
def __init__(self, *args, **kwargs):
super(Mock, self).__init__(*args, **kwargs)
self.use_uid = True
self.sent = b'' # Accumulates what was given to send()
self.tagged_commands = {}
self.debug = 0
self._starttls_done = False
def send(self, data):
self.sent += data
def _new_tag(self):
return 'tag'

View File

@@ -0,0 +1,32 @@
# Copyright (c) 2014, Menno Smits
# Released subject to the New BSD License
# Please see http://en.wikipedia.org/wiki/BSD_licenses
from __future__ import unicode_literals
def find_unittest2():
import unittest
if hasattr(unittest, 'skip') and hasattr(unittest, 'loader'):
return unittest # unittest from stdlib is unittest2, use that
try:
import unittest2 # try for a separately installed unittest2 package
except ImportError:
raise ImportError(
'unittest2 not installed and unittest in standard library is not unittest2')
else:
return unittest2
unittest = find_unittest2()
def patch_TestCase():
TestCase = unittest.TestCase
# Older versions of unittest2 don't have
# TestCase.assertRaisesRegex and newer version raises warnings
# when you use assertRaisesRegexp. This helps deal with the
# mismatch.
if not hasattr(TestCase, 'assertRaisesRegex'):
TestCase.assertRaisesRegex = TestCase.assertRaisesRegexp
patch_TestCase()