def add_emails(items=None, *, tag='\\All', fetch=True, parse=True): gmail.client() if items is None: items = [{}] gm_client.fetch = [('OK', [])] for item in items: gm_client.uid += 1 uid = gm_client.uid gid = 100 * uid raw = item.get('raw') if raw: msg = raw else: txt = item.get('txt', '42') msg = message.binary(txt) subj = item.get('subj') if not subj: subj = 'Subj %s' % uid msg.add_header('Subject', subj) date = item.get('date') if not date: date = gm_client.time + uid msg.add_header('Date', formatdate(date)) mid = item.get('mid') if not mid: mid = '<%s@mlr>' % uid msg.add_header('Message-ID', mid) in_reply_to = item.get('in_reply_to') if in_reply_to: msg.add_header('In-Reply-To', in_reply_to) refs = item.get('refs') if refs: msg.add_header('References', refs) msg = msg.as_bytes() flags = item.get('flags', '').encode() labels = item.get('labels', '').encode() res = gm_client.con.append(local.ALL, gmail.MAP_LABELS.get(tag), None, msg) if res[0] != 'OK': raise Exception(res) gm_client.fetch[0][1].extend([ (b'1 (X-GM-MSGID %d X-GM-THRID %d X-GM-LABELS (%s) UID %d ' b'INTERNALDATE "08-Jul-2017 09:08:30 +0000" FLAGS (%s) ' b'BODY[] {%d}' % (gid, gid, labels, uid, flags, len(msg)), msg), b')' ]) if fetch: gmail.fetch_folder(tag) if parse: local.parse()
def add_emails(items=None, *, tag='\\All', fetch=True, parse=True): if items is None: items = [{}] gm_client.fetch = [('OK', []), ('OK', [])] for item in items: add_email(item, tag) if fetch: remote.fetch_folder(tag=tag) if parse: local.parse()
def test_parsed_msg(clean_users, gm_client, load_file, latest): gm_client.add_emails([{'flags': '\\Flagged'}]) msg = latest() assert 'X-UID' in msg['body'] assert re.match('<\d+>', msg['body']['X-UID']) assert '\\Flagged' in msg['flags'] # `email.policy.default` is not working with long addresses. # Exits with: "segmentation fault (core dumped)" # when running in threads. gm_client.add_emails([{ 'txt': 'some text' }, { 'raw': load_file('msg-header-with-long-addresses.txt') }]) msg = latest()['body'] assert msg['to'].startswith('primary discussion list') # should be decoding of headers during parsing gm_client.add_emails([{ 'raw': load_file('msg-header-with-encoding.txt') }], parse=False) local.parse(batch=1) msg = latest(raw=True)['body'].decode() expect = '\r\n'.join([ 'X-UID: <4>', 'Message-Id: <with-encoding@test>', 'Subject: Re: не пора ли подкрепиться?', 'Date: Wed, 07 Jan 2015 13:23:22 +0000', 'From: "Катя К." <*****@*****.**>', 'To: Grisha <*****@*****.**>', ]) assert msg.startswith(expect) gm_client.add_emails([{ 'raw': load_file('msg-header-with-no-encoding.txt') }], parse=False) local.parse(batch=1) msg = latest(raw=True)['body'].decode() expect = '\r\n'.join([ 'X-UID: <5>', 'Message-Id: <with-no-encoding@test>', 'Subject: Re: не пора ли подкрепиться?', 'Date: Wed, 07 Jan 2015 13:23:22 +0000', 'From: "Катя К." <*****@*****.**>', 'To: Гриша <*****@*****.**>', ]) assert msg.startswith(expect)
def test_batched_uids(gm_client): con = local.client() bsize = 25000 assert [] == con.fetch([str(i) for i in range(1, 100, 2)], 'FLAGS') assert [] == con.fetch([str(i) for i in range(1, bsize, 2)], 'FLAGS') con.select(local.ALL, readonly=False) assert [] == con.store([str(i) for i in range(1, 100, 2)], '+FLAGS', '#') assert [] == con.store([str(i) for i in range(1, bsize, 2)], '+FLAGS', '#') # with one message msg = message.binary('42') msg.add_header('Message-Id', message.gen_msgid()) con.append(local.SRC, None, None, msg.as_bytes()) con.select(local.SRC, readonly=True) assert [b'1 (UID 1 FLAGS (\\Recent))' ] == (con.fetch([str(i) for i in range(1, 100, 2)], 'FLAGS')) assert [b'1 (UID 1 FLAGS (\\Recent))' ] == (con.fetch([str(i) for i in range(1, bsize, 2)], 'FLAGS')) con.select(local.SRC, readonly=False) assert [b'1 (UID 1 FLAGS (\\Recent #1))' ] == (con.store([str(i) for i in range(1, 100, 2)], '+FLAGS', '#1')) assert [b'1 (UID 1 FLAGS (\\Recent #1 #2))' ] == (con.store([str(i) for i in range(1, bsize, 2)], '+FLAGS', '#2')) gm_client.add_emails([{} for i in range(1, 22)], parse=False) assert local.parse(batch=10) is None
def test_update_metadata(gm_client, msgs, patch, call): gm_client.add_emails([{}, {}]) assert ['1', '2'] == [i['uid'] for i in msgs(local.SRC)] local.link_threads(['1', '2']) thrids, thrs = local.data_threads.get() assert {'1': '2', '2': '2'} == thrids # emulate that metadata wasn't updated when messages were re-parsed with patch.object(local, 'update_metadata') as m: local.parse('1:*') m.assert_called_with('3:*') # refresh metadata local.update_metadata('1:*') thrids, thrs = local.data_threads.get() assert {'3': '4', '4': '4'} == thrids
def test_batched_uids(gm_client): con = local.client() bsize = 25000 assert [] == con.fetch([str(i) for i in range(1, 100, 2)], 'FLAGS') assert [] == con.fetch([str(i) for i in range(1, bsize, 2)], 'FLAGS') con.select(local.ALL, readonly=False) assert [] == con.store([str(i) for i in range(1, 100, 2)], '+FLAGS', '#') assert [] == con.store([str(i) for i in range(1, bsize, 2)], '+FLAGS', '#') # with one message msg = message.binary('42') msg.add_header('Message-Id', message.gen_msgid()) con.append(local.SRC, None, None, msg.as_bytes()) con.select(local.SRC, readonly=True) assert [b'1 (UID 1 FLAGS (\\Recent))'] == ( con.fetch([str(i) for i in range(1, 100, 2)], 'FLAGS') ) assert [b'1 (UID 1 FLAGS (\\Recent))'] == ( con.fetch([str(i) for i in range(1, bsize, 2)], 'FLAGS') ) con.select(local.SRC, readonly=False) assert [b'1 (UID 1 FLAGS (\\Recent #1))'] == ( con.store([str(i) for i in range(1, 100, 2)], '+FLAGS', '#1') ) assert [b'1 (UID 1 FLAGS (\\Recent #1 #2))'] == ( con.store([str(i) for i in range(1, bsize, 2)], '+FLAGS', '#2') ) gm_client.add_emails([{} for i in range(1, 22)], parse=False) assert local.parse(batch=10) is None
def test_link_threads_part1(gm_client, msgs): gm_client.add_emails([{}, {}]) refs = [ '<*****@*****.**>', '<*****@*****.**>' ] assert local.data_threads.get()[1] == {'2': ['2'], '1': ['1']} res = msgs() assert [i['body']['references'] for i in res] == refs local.link_threads(['1', '2']) assert local.data_threads.get()[1] == {'2': ['1', '2']} res = msgs(local.SRC) assert [i['body']['references'] for i in res] == [None, None] res = msgs() assert [i['body']['references'] for i in res] == refs local.parse('all') assert local.data_threads.get()[1] == {'4': ['3', '4']} res = msgs() assert [i['body']['references'] for i in res] == refs gm_client.add_emails([{}]) refs += ['<*****@*****.**>'] assert local.search_thrs('all') == ['5', '4'] assert local.data_threads.get()[1] == {'4': ['3', '4'], '5': ['5']} local.link_threads(['4', '5']) assert local.search_thrs('all') == ['5'] assert local.data_threads.get()[1] == {'5': ['3', '4', '5']} res = msgs(local.SRC) assert [i['body']['references'] for i in res] == [None, None, None] res = msgs() assert [i['body']['references'] for i in res] == refs gm_client.add_emails([{'refs': '<101@mlr>'}]) refs += ['<*****@*****.**> <101@mlr>'] assert local.search_thrs('all') == ['6'] assert local.data_threads.get()[1] == {'6': ['3', '4', '5', '6']} res = msgs(local.SRC) assert [i['body']['references'] for i in res] == [ None, None, None, '<101@mlr>' ] res = msgs() assert [i['body']['references'] for i in res] == refs assert local.search_thrs('uid 4') == [i[0] for i in local.thrs_info(['4'])]
def test_link_threads_part1(gm_client, msgs): gm_client.add_emails([{}, {}]) refs = [ '<*****@*****.**>', '<*****@*****.**>' ] assert local.data_threads.get()[1] == {'2': ['2'], '1': ['1']} res = msgs() assert [i['body']['references'] for i in res] == refs local.link_threads(['1', '2']) assert local.data_threads.get()[1] == {'2': ['1', '2']} res = msgs(local.SRC) assert [i['body']['references'] for i in res] == [None, None] res = msgs() assert [i['body']['references'] for i in res] == refs local.parse('all') assert local.data_threads.get()[1] == {'4': ['3', '4']} res = msgs() assert [i['body']['references'] for i in res] == refs gm_client.add_emails([{}]) refs += ['<*****@*****.**>'] assert local.search_thrs('all') == ['5', '4'] assert local.data_threads.get()[1] == {'4': ['3', '4'], '5': ['5']} local.link_threads(['4', '5']) assert local.search_thrs('all') == ['5'] assert local.data_threads.get()[1] == {'5': ['3', '4', '5']} res = msgs(local.SRC) assert [i['body']['references'] for i in res] == [None, None, None] res = msgs() assert [i['body']['references'] for i in res] == refs gm_client.add_emails([{'refs': '<101@mlr>'}]) refs += ['<*****@*****.**> <101@mlr>'] assert local.search_thrs('all') == ['6'] assert local.data_threads.get()[1] == {'6': ['3', '4', '5', '6']} res = msgs(local.SRC) assert [i['body']['references'] for i in res] == [ None, None, None, '<101@mlr>' ] res = msgs() assert [i['body']['references'] for i in res] == refs assert local.search_thrs('uid 4') == [i[0] for i in local.thrs_info(['4'])]
def test_cli_idle(gm_client, msgs, login, patch): spawn(lambda: cli.main('sync %s --timeout=300' % login.user1)) sleep(2) gm_client.add_emails([{}] * 4, fetch=False, parse=False) sleep(2) assert len(msgs(local.SRC)) == 4 assert len(msgs()) == 4 local.parse('all') gm_client.add_emails([{}], fetch=False, parse=False) sleep(2) assert len(msgs(local.SRC)) == 5 assert len(msgs()) == 5 con_src = local.client(local.SRC, readonly=False) con_src.store('1:*', '+FLAGS', '#1') sleep(2) assert [i['flags'] for i in msgs(local.SRC)] == ['#1'] * 5 assert [i['flags'] for i in msgs()] == ['#1'] * 5
def test_cli_idle(gm_client, msgs, login, patch): spawn(lambda: cli.main('sync %s --timeout=300' % login.user1)) sleep(2) gm_client.add_emails([{}] * 4, fetch=False, parse=False) sleep(2) assert len(msgs(local.SRC)) == 4 assert len(msgs()) == 4 local.parse('all') gm_client.add_emails([{}], fetch=False, parse=False) sleep(2) assert len(msgs(local.SRC)) == 5 assert len(msgs()) == 5 con_src = local.client(local.SRC, readonly=False) con_src.store('1:*', '+FLAGS', '#1') sleep(2) assert [i['flags'] for i in msgs(local.SRC)] == ['#1'] * 5 assert [i['flags'] for i in msgs()] == ['#1'] * 5
def test_fetch_and_parse(clean_users, gm_client, some): lm = local.client() gmail.fetch_folder() local.parse() def gm_uidnext(): res = lm.getmetadata(local.SRC, 'gmail/uidnext/all') assert res == [(b'Src (/private/gmail/uidnext/all {12}', some), b')'] return some.value def mlr_uidnext(): res = lm.getmetadata(local.ALL, 'uidnext') assert res == [(b'All (/private/uidnext {1}', some), b')'] return some.value assert gm_uidnext().endswith(b',1') assert lm.getmetadata(local.ALL, 'uidnext') == [b'All (/private/uidnext NIL)'] gm_client.add_emails() assert gm_uidnext().endswith(b',2') assert mlr_uidnext() == b'2' assert lm.select(local.SRC) == [b'1'] assert lm.select(local.ALL) == [b'1'] gm_client.add_emails([{'txt': '1'}, {'txt': '2'}]) assert gm_uidnext().endswith(b',4') assert mlr_uidnext() == b'4' assert lm.select(local.SRC) == [b'3'] assert lm.select(local.ALL) == [b'3'] gmail.fetch_folder() local.parse('all') assert gm_uidnext().endswith(b',4') assert mlr_uidnext() == b'4' assert lm.select(local.SRC) == [b'3'] assert lm.select(local.ALL) == [b'3'] assert lm.status(local.ALL, '(UIDNEXT)') == [b'All (UIDNEXT 7)']
def test_cli_idle(gm_client, msgs, login, patch): with patch('mailur.gmail.get_credentials') as m: m.return_value = login.user2, 'user' spawn(lambda: cli.main('sync %s --timeout=300' % login.user1)) sleep(3) gm_client.add_emails([{}] * 4, fetch=False, parse=False) sleep(2) assert len(msgs(local.SRC)) == 4 assert len(msgs()) == 4 local.parse('all') gm_client.add_emails([{}], fetch=False, parse=False) sleep(1) assert len(msgs(local.SRC)) == 5 assert len(msgs()) == 5 con_src = local.client(local.SRC, readonly=False) con_src.store('1:*', '+FLAGS', '#1') sleep(1) assert [i['flags'] for i in msgs(local.SRC)] == ['#1'] * 5 assert [i['flags'] for i in msgs()] == ['#latest #1'] * 5
def test_fetch_and_parse(gm_client, some, raises): lm = local.client() remote.fetch() local.parse() def gm_uidnext(): account = remote.data_account.get() key = ':'.join((account['imap_host'], account['username'], '\\All')) res = remote.data_uidnext.key(key) assert res return res[1] def mlr_uidnext(): return local.data_uidnext.get() assert gm_uidnext() == 1 assert mlr_uidnext() is None gm_client.add_emails() assert gm_uidnext() == 2 assert mlr_uidnext() == 2 assert lm.select(local.SRC) == [b'1'] assert lm.select(local.ALL) == [b'1'] gm_client.add_emails([{'txt': '1'}, {'txt': '2'}]) assert gm_uidnext() == 4 assert mlr_uidnext() == 4 assert lm.select(local.SRC) == [b'3'] assert lm.select(local.ALL) == [b'3'] remote.fetch() local.parse('all') assert gm_uidnext() == 4 assert mlr_uidnext() == 4 assert lm.select(local.SRC) == [b'3'] assert lm.select(local.ALL) == [b'3'] assert lm.status(local.ALL, '(UIDNEXT)') == [b'mlr/All (UIDNEXT 7)']
def test_fetch_and_parse(gm_client, some, raises): lm = local.client() remote.fetch() local.parse() def gm_uidnext(): account = remote.data_account.get() key = ':'.join((account['imap_host'], account['username'], '\\All')) res = remote.data_uidnext.key(key) assert res return res[1] def mlr_uidnext(): return local.data_uidnext.get() assert gm_uidnext() == 1 assert mlr_uidnext() is None gm_client.add_emails() assert gm_uidnext() == 2 assert mlr_uidnext() == 2 assert lm.select(local.SRC) == [b'1'] assert lm.select(local.ALL) == [b'1'] gm_client.add_emails([{'txt': '1'}, {'txt': '2'}]) assert gm_uidnext() == 4 assert mlr_uidnext() == 4 assert lm.select(local.SRC) == [b'3'] assert lm.select(local.ALL) == [b'3'] remote.fetch() local.parse('all') assert gm_uidnext() == 4 assert mlr_uidnext() == 4 assert lm.select(local.SRC) == [b'3'] assert lm.select(local.ALL) == [b'3'] assert lm.status(local.ALL, '(UIDNEXT)') == [b'mlr/All (UIDNEXT 7)']
def test_uidpairs(gm_client, msgs, patch, call): gm_client.add_emails([{}, {}], parse=False) assert ['1', '2'] == [i['uid'] for i in msgs(local.SRC)] assert local.pair_origin_uids(['1', '2']) == () assert local.pair_parsed_uids(['1', '2']) == () local.parse() assert local.data_uidpairs.get() == {'1': '1', '2': '2'} assert local.pair_origin_uids(['1', '2']) == ('1', '2') assert local.pair_parsed_uids(['1', '2']) == ('1', '2') local.parse('uid 1') assert ['2', '3'] == [i['uid'] for i in msgs()] assert local.data_uidpairs.get() == {'1': '3', '2': '2'} assert local.pair_origin_uids(['1', '2']) == ('3', '2') assert local.pair_parsed_uids(['2', '3']) == ('2', '1') local.parse('all') assert ['4', '5'] == [i['uid'] for i in msgs()] assert local.data_uidpairs.get() == {'1': '4', '2': '5'} assert local.pair_origin_uids(['1', '2']) == ('4', '5') assert local.pair_parsed_uids(['4', '5']) == ('1', '2') assert local.pair_origin_uids(['2']) == ('5',) assert local.pair_parsed_uids(['5']) == ('2',) # warm cache up local.data_addresses.get() local.data_uidpairs.get() local.data_threads.get() local.data_msgids.get() with patch('imaplib.IMAP4.uid') as m: m.return_value = 'OK', [] local.update_metadata('4') assert m.called assert m.call_args_list == [ call('FETCH', '4', '(FLAGS BINARY.PEEK[1])'), call('FETCH', '1:*', '(UID BODY[HEADER.FIELDS (Subject)])'), call('THREAD', 'REFS UTF-8 INTHREAD REFS UID 4'), ] patched = {'wraps': local.update_metadata} with patch('mailur.local.update_metadata', **patched) as m: local.parse('uid 1') assert m.called assert m.call_args == call('6') m.reset_mock() local.parse('all') assert m.called assert m.call_args == call('1:*') m.reset_mock() local.parse() assert not m.called m.reset_mock() gm_client.add_emails([{}]) assert m.called assert m.call_args == call('9')
def test_general(gm_client, load_file, latest, load_email): gm_client.add_emails([{'flags': '\\Flagged'}]) msg = latest() assert 'X-UID' in msg['body'] assert re.match(r'<\d+>', msg['body']['X-UID']) assert '\\Flagged' in msg['flags'] # `email.policy.default` is not working with long addresses. # Exits with: "segmentation fault (core dumped)" # when running in threads. gm_client.add_emails([ {'txt': 'some text'}, {'raw': load_file('msg-header-with-long-addresses.txt')} ]) msg = latest()['body'] assert msg['to'].startswith('"primary discussion list') # should be decoding of headers during parsing gm_client.add_emails([ {'raw': load_file('msg-header-with-encoding.txt')} ], parse=False) local.parse(batch=1) msg = latest(raw=True)['body'].decode() expect = '\r\n'.join([ 'X-UID: <4>', 'Message-ID: <with-encoding@test>', 'Subject: Re: не пора ли подкрепиться?', 'Date: Wed, 07 Jan 2015 13:23:22 +0000', 'From: "Катя К." <*****@*****.**>', 'To: "Grisha" <*****@*****.**>', ]) assert msg.startswith(expect), '%s\n\n%s' % (expect, msg) gm_client.add_emails([ {'raw': load_file('msg-header-with-no-encoding.txt')} ], parse=False) local.parse(batch=1) msg = latest(raw=True)['body'].decode() expect = '\r\n'.join([ 'X-UID: <5>', 'Message-ID: <with-no-encoding@test>', 'Subject: Re: не пора ли подкрепиться?', 'Date: Wed, 07 Jan 2015 13:23:22 +0000', 'From: "Катя К." <*****@*****.**>', 'To: "Гриша" <*****@*****.**>', ]) assert msg.startswith(expect), '%s\n\n%s' % (expect, msg) # msg with UnicodeDecodeError raw = b'\r\n'.join([ b'Message-ID: <with-bad-symbol@test>', b'Subject: bad symbol?', b'Date: Wed, 07 Jan 2015 13:23:22 +0000', b'From: [email protected]', b'To: [email protected]', b'Content-type: text/html; charset=utf-8', b'Content-Transfer-Encoding: 8bit', b'MIME-Version: 1.0', b'', b'', b'\xd0\xb2\xd0\xbe\xd0\xb7\xd0\xbc\xd0\xbe\xd0\xb6\xd0\xbd\xd0\r\n ' b'\xbe\xd1\x81\xd1\x82\xd0\xb8,' ]) gm_client.add_emails([{'raw': raw}]) msg = latest(parsed=True) assert msg['meta']['preview'] == 'возможн� �сти,' assert msg['body'] == '<p>возможн�\r\n �сти,</p>' assert msg['meta']['errors'] assert ( "error on 'text/html()': [UnicodeDecodeError] 'utf-8' codec can't " 'decode byte 0xd0 in position 16: invalid continuation byte' in msg['meta']['errors'][0] ) m = load_email('msg-lookup-error.txt', parsed=True) assert m['meta']['preview'] == 'test' assert m['body'] == '<p>test</p>' assert m['meta']['errors'] assert ( '[LookupError] unknown encoding: iso-2022-int-1' in m['meta']['errors'][0] ) # date can't be parsed raw = b'\r\n'.join([ b'Message-ID: <with-bad-symbol@test>', b'Subject: bad symbol?', b'Date: 15.06.2018 09:24:13 +0800', b'From: [email protected]', b'To: [email protected]', b'Content-type: text/html; charset=utf-8', b'Content-Transfer-Encoding: 8bit', b'MIME-Version: 1.0', b'', b'', b'Hello!' ]) gm_client.add_emails([{'raw': raw}]) msg = latest(parsed=True) assert msg['meta']['date'] == msg['meta']['arrived'] assert msg['meta']['errors'] == [ 'error on date: val=\'15.06.2018 09:24:13 +0800\' ' 'err=TypeError("\'NoneType\' object is not iterable",)' ] # ending @ symbol and address without @ symbol at all m = load_email('msg-from-ending-snail.txt', parsed=True) assert m['meta']['from'] == { 'addr': 'grrr@', 'name': 'grrr', 'title': 'grrr@', 'hash': '8ea2bc312c94c9596ad95772d6cd579c', } assert m['meta']['reply-to'] == [{ 'addr': 'grrr', 'name': 'grrr', 'title': 'grrr', 'hash': 'd4468c0c805f9a0e200c0e916824547a', }] assert m['meta']['to'] assert m['meta']['reply-to'] assert m['body_full']['to'] == 'katya@' assert m['body_full']['from'] == 'grrr@' assert m['body_full']['reply-to'] == 'grrr' raw = load_email('msg-from-rss2email.txt', parsed=True)['raw'].decode() assert 'From: "БлоGнот: Gray" <*****@*****.**>' in raw, raw # test links m = load_email('msg-links.txt', parsed=True) link = '<a href="{0}" target="_blank">' l1 = link.format('https://github.com/naspeh/mailur') # l2 = link.format('http://bottlepy.org/docs/dev/routing.html#rule-syntax') l2 = link.format('http://bottlepy.org/docs/dev/routing.html') assert m['body'].count(l1) == 3, '%s\n%s' % (l1, m['body']) assert m['body'].count(l2) == 2, '%s\n%s' % (l2, m['body'])
def test_general(gm_client, load_email, latest, login, some): web = login() res = web.search({'q': '', 'preload': 10}) assert res == { 'uids': [], 'msgs': {}, 'msgs_info': '/msgs/info', } msg = {'labels': '\\Inbox'} gm_client.add_emails([msg, dict(msg, refs='<101@mlr>')]) res = web.search({'q': '', 'preload': 10}) assert res == { 'uids': ['2', '1'], 'msgs': { '1': { 'arrived': 1499504910, 'count': 0, 'date': some, 'errors': [], 'files': [], 'from_list': [], 'is_draft': False, 'is_link': False, 'is_pinned': False, 'is_unread': True, 'msgid': '<101@mlr>', 'origin_uid': '1', 'parent': None, 'preview': '42', 'query_msgid': 'ref:<101@mlr>', 'query_subject': ':threads subj:"Subj 101"', 'query_thread': 'thread:1', 'subject': 'Subj 101', 'tags': ['#inbox'], 'time_human': some, 'time_title': some, 'thrid': '<*****@*****.**>', 'uid': '1', 'url_raw': '/raw/1/original-msg.eml', 'url_reply': '/reply/1', }, '2': { 'arrived': 1499504910, 'count': 0, 'date': some, 'errors': [], 'files': [], 'from_list': [], 'is_draft': False, 'is_link': False, 'is_pinned': False, 'is_unread': True, 'msgid': '<102@mlr>', 'origin_uid': '2', 'parent': '<101@mlr>', 'preview': '42', 'query_msgid': 'ref:<102@mlr>', 'query_subject': ':threads subj:"Subj 102"', 'query_thread': 'thread:2', 'subject': 'Subj 102', 'tags': ['#inbox'], 'time_human': some, 'time_title': some, 'uid': '2', 'url_raw': '/raw/2/original-msg.eml', 'url_reply': '/reply/2', } }, 'msgs_info': '/msgs/info', } web.post_json('/msgs/body', {'uids': ['1']}, status=200) res = web.search({'q': 'in:#inbox'}) assert [i['is_unread'] for i in res['msgs'].values()] == [False, True] web.post_json('/msgs/body', {'uids': ['1']}, status=200) res = web.search({'q': ':threads'}) assert res == { 'uids': ['2'], 'msgs': { '2': { 'arrived': 1499504910, 'count': 2, 'date': some, 'errors': [], 'files': [], 'from_list': [], 'is_draft': False, 'is_link': False, 'is_pinned': False, 'is_unread': True, 'msgid': '<102@mlr>', 'origin_uid': '2', 'parent': '<101@mlr>', 'preview': '42', 'query_msgid': 'ref:<102@mlr>', 'query_subject': ':threads subj:"Subj 102"', 'query_thread': 'thread:2', 'subject': 'Subj 102', 'tags': ['#inbox'], 'time_human': some, 'time_title': some, 'uid': '2', 'uids': ['1', '2'], 'url_raw': '/raw/2/original-msg.eml', 'url_reply': '/reply/2', } }, 'msgs_info': '/thrs/info', 'threads': True } web.flag({'uids': ['2'], 'new': ['\\Seen']}) res = web.search({'q': ':threads in:#inbox'}) assert not res['msgs']['2']['is_unread'] res = web.get('/raw/2') assert res.content_type == 'text/plain' assert 'Message-ID: <102@mlr>' in res.text res = web.get('/raw/2/1') assert res.content_type == 'text/plain' assert '42' in res.text m = load_email('msg-attachments-two-gmail.txt') q = 'thread:%s' % m['uid'] res = web.search({'q': q}) assert res == { 'uids': ['3'], 'edit': None, 'has_link': False, 'msgs_info': '/msgs/info', 'msgs': { '3': some }, 'same_subject': [], 'tags': [], 'thread': True, } assert some['files'] == [{ 'filename': '08.png', 'image': True, 'path': '2', 'size': 553, 'url': '/raw/3/2/08.png', }, { 'filename': '09.png', 'image': True, 'path': '3', 'size': 520, 'url': '/raw/3/3/09.png', }] assert some['preview'] == ( 'ответ на тело 2014-03-03 18:09 GMT+02:00 Ne Greh ' '< [email protected] > : тело [08.png, 09.png]') res = web.get(some['files'][0]['url'], status=200) assert res.content_type == 'image/png' res = web.get(some['files'][1]['url'], status=200) assert res.content_type == 'image/png' res = web.get('/raw/3') assert res.content_type == 'text/plain' res = web.search({'q': 'tag:#inbox'}) assert res['tags'] == ['#inbox'] assert [i['tags'] for i in res['msgs'].values()] == [[], []] # one message from thread is going to #trash gm_client.add_emails([{'labels': '\\Inbox', 'from': '*****@*****.**'}]) res = web.search({'q': ':threads tag:#inbox'}) assert res['uids'] == ['4', '2'] res = web.post_json('/thrs/link', {'uids': res['uids']}).json assert res == {'uid': '5'} res = web.search({'q': ':threads tag:#inbox'}) assert res['uids'] == ['4'] web.flag({'uids': ['4'], 'new': ['#trash']}) res = web.search({'q': ':threads tag:#inbox'}) assert res['uids'] == ['4'] assert res['msgs']['4']['subject'] == 'Subj 102' assert 'from' not in res['msgs']['4'] res = web.search({'q': 'thread:4'}) assert res['uids'] == ['1', '2'] res = web.search({'q': ':threads tag:#trash'}) assert res['uids'] == ['4'] assert res['msgs']['4']['subject'] == 'Subj 104' assert res['msgs']['4']['from'] == { 'addr': '*****@*****.**', 'hash': 'bc11cf997156ef71c34c23457e67fd65', 'name': 'one', 'query': 'tag:#trash :threads from:[email protected]', 'title': '*****@*****.**' } res = web.search({'q': 'tag:#trash thread:4'}) assert res['uids'] == ['4'] # thread in #trash gm_client.add_emails([{'labels': '\\Trash'}]) m = latest(parsed=True) res = web.post_json('/thrs/info', {'uids': [m['uid']]}, status=200).json assert res == {} # unlink thread gm_client.add_emails([{'labels': 'test'}] * 2) local.parse('all') res = web.search({'q': ':threads tag:test'}) assert res['uids'] == ['16', '15'] res = web.post_json('/thrs/link', {'uids': res['uids']}).json res = web.search({'q': ':threads tag:test'}) assert res['uids'] == ['16'] res = web.post_json('/thrs/unlink', {'uids': res['uids']}).json assert res == {'query': ':threads uid:16,15'} res = web.search({'q': res['query']}) assert res['uids'] == ['16', '15']
def test_data_threads(gm_client): gm_client.add_emails([{'subj': 'new subj'}]) assert local.data_threads.get()[1] == {'1': ['1']} assert local.search_thrs('all') == ['1'] local.parse('all') assert local.data_threads.get()[1] == {'2': ['2']} assert local.search_thrs('all') == ['2'] gm_client.add_emails([{'subj': 'new subj'}]) assert local.data_threads.get()[1] == {'3': ['2', '3']} assert local.search_thrs('all') == ['3'] gm_client.add_emails([{'in_reply_to': '<101@mlr>'}]) assert local.data_threads.get()[1] == {'4': ['2', '3', '4']} assert local.search_thrs('all') == ['4'] gm_client.add_emails([{'refs': '<101@mlr> <102@mlr>'}]) assert local.data_threads.get()[1] == {'5': ['2', '3', '4', '5']} assert local.search_thrs('all') == ['5'] local.parse('all') assert local.data_threads.get()[1] == {'9': ['6', '7', '8', '9']} assert local.search_thrs('all') == ['9'] local.parse('uid *') assert local.data_threads.get()[1] == {'10': ['6', '7', '8', '10']} assert local.search_thrs('all') == ['10'] local.update_threads('1:*') assert local.data_threads.get()[1] == {'10': ['6', '7', '8', '10']} assert local.search_thrs('all') == ['10'] local.update_threads('1') assert local.data_threads.get()[1] == {'10': ['6', '7', '8', '10']} assert local.search_thrs('all') == ['10'] local.update_threads('1:*') assert local.data_threads.get()[1] == {'10': ['6', '7', '8', '10']} assert local.search_thrs('all') == ['10'] gm_client.add_emails([ {'refs': '<non-exist@mlr>'}, {'refs': '<non-exist@mlr> <101@mlr>'} ]) assert local.data_threads.get()[1] == { '11': ['11'], '12': ['6', '7', '8', '10', '12'] } assert local.search_thrs('all') == ['12', '11'] gm_client.add_emails([{'labels': 't1'}, {'labels': 't2'}], parse=False) local.parse('UID 6:*') assert local.data_threads.get()[1] == { '11': ['11'], '13': ['6', '7', '8', '10', '13'], '14': ['14'], '15': ['15'] } assert local.search_thrs('all') == ['15', '14', '13', '11'] local.update_threads('*') assert local.data_threads.get()[1] == { '11': ['11'], '13': ['6', '7', '8', '10', '13'], '14': ['14'], '15': ['15'] } assert local.search_thrs('all') == ['15', '14', '13', '11']
def test_update_threads(clean_users, gm_client, msgs): gm_client.add_emails([{}]) res = msgs() assert ['#latest'] == [i['flags'] for i in res] local.parse('all') res = msgs() assert ['#latest'] == [i['flags'] for i in res] gm_client.add_emails([{}]) res = msgs() assert ['#latest', '#latest'] == [i['flags'] for i in res] gm_client.add_emails([{'in_reply_to': '<101@mlr>'}]) res = msgs() assert ['', '#latest', '#latest'] == [i['flags'] for i in res] gm_client.add_emails([{'refs': '<101@mlr> <102@mlr>'}]) res = msgs() assert ['', '', '', '#latest'] == [i['flags'] for i in res] local.parse('all') res = msgs() assert ['', '', '', '#latest'] == [i['flags'] for i in res] local.parse('uid *') res = msgs() assert ['', '', '', '#latest'] == [i['flags'] for i in res] con = local.client() local.update_threads(con, 'all') res = msgs() assert ['', '', '', '#latest'] == [i['flags'] for i in res] local.update_threads(con, 'UID 1') res = msgs() assert ['', '', '', '#latest'] == [i['flags'] for i in res] local.update_threads(con) res = msgs() assert ['', '', '', '#latest'] == [i['flags'] for i in res] gm_client.add_emails([{ 'refs': '<non-exist@mlr>' }, { 'refs': '<non-exist@mlr> <101@mlr>' }]) res = msgs() assert [i['flags'] for i in res] == ['', '', '', '', '#latest', '#latest'] gm_client.add_emails([{'labels': 't1'}, {'labels': 't2'}], parse=False) local.parse('UID 6:*') res = msgs() assert [i['flags'] for i in res] == [ '', '', '', '', '#latest', '#latest', '#latest t1', '#latest t2' ] local.update_threads(con, 'UID *') res = msgs() assert [i['flags'] for i in res] == [ '', '', '', '', '#latest', '#latest', '#latest t1', '#latest t2' ]
def test_link_threads_part1(clean_users, gm_client, msgs): gm_client.add_emails([{}, {}]) res = msgs() assert ['1', '2'] == [i['uid'] for i in res] assert ['#latest', '#latest'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [None, None] local.link_threads(['1', '2']) res = msgs(local.SRC) assert (('1', '2', '3'), ) == thread() assert ['', '', '#link'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [None, None, '<101@mlr> <102@mlr>'] res = msgs() assert ['1', '2', '3'] == [i['uid'] for i in res] assert ['', '#latest', '#link'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [None, None, '<101@mlr> <102@mlr>'] local.parse('all') res = msgs(local.SRC) assert (('1', '2', '3'), ) == thread() assert ['', '', '#link'] == [i['flags'] for i in res] res = msgs() assert ['4', '5', '6'] == [i['uid'] for i in res] assert ['', '#latest', '#link'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [None, None, '<101@mlr> <102@mlr>'] gm_client.add_emails([{}]) local.link_threads(['4', '7']) res = msgs(local.SRC) assert (('1', '2', '4', '5', '3'), ) == thread() assert ['1', '2', '3', '4', '5'] == [i['uid'] for i in res] assert [i['flags'] for i in res] == ['', '', '\\Deleted #link', '', '#link'] assert [i['body']['references'] for i in res] == [ None, None, '<101@mlr> <102@mlr>', None, '<101@mlr> <102@mlr> <103@mlr>' ] res = msgs() assert ['4', '5', '7', '8'] == [i['uid'] for i in res] assert ['', '', '#latest', '#link'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res ] == [None, None, None, '<101@mlr> <102@mlr> <103@mlr>'] gm_client.add_emails([{'refs': '<101@mlr>'}]) res = msgs(local.SRC) assert (('1', '2', '4', '5', '3', '6'), ) == thread() assert ['1', '2', '3', '4', '5', '6'] == [i['uid'] for i in res] assert [i['flags'] for i in res] == ['', '', '\\Deleted #link', '', '#link', ''] assert [i['body']['references'] for i in res] == [ None, None, '<101@mlr> <102@mlr>', None, '<101@mlr> <102@mlr> <103@mlr>', '<101@mlr>' ] res = msgs() assert ['4', '5', '7', '8', '9'] == [i['uid'] for i in res] assert ['', '', '', '#link', '#latest'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [ None, None, None, '<101@mlr> <102@mlr> <103@mlr>', '<101@mlr>' ]
def test_uid_pairs(clean_users, gm_client, msgs, patch): gm_client.add_emails([{}, {}], parse=False) assert ['1', '2'] == [i['uid'] for i in msgs(local.SRC)] assert local.pair_origin_uids(['1', '2']) == () assert local.pair_parsed_uids(['1', '2']) == () local.parse() assert local.uid_pairs() == {'1': '1', '2': '2'} assert local.pair_origin_uids(['1', '2']) == ('1', '2') assert local.pair_parsed_uids(['1', '2']) == ('1', '2') local.parse('uid 1') assert ['2', '3'] == [i['uid'] for i in msgs()] assert local.uid_pairs() == {'1': '3', '2': '2'} assert local.pair_origin_uids(['1', '2']) == ('3', '2') assert local.pair_parsed_uids(['2', '3']) == ('2', '1') local.parse('all') assert ['4', '5'] == [i['uid'] for i in msgs()] assert local.uid_pairs() == {'1': '4', '2': '5'} assert local.pair_origin_uids(['1', '2']) == ('4', '5') assert local.pair_parsed_uids(['4', '5']) == ('1', '2') assert local.pair_origin_uids(['2']) == ('5', ) assert local.pair_parsed_uids(['5']) == ('2', ) with patch('imaplib.IMAP4.uid') as m: m.return_value = 'OK', [] local.save_uid_pairs('4') assert m.called assert m.call_args[0][1] == '4' with patch('mailur.local.save_uid_pairs', wraps=local.save_uid_pairs) as m: local.parse('uid 1') assert m.called assert m.call_args[0][0] == '6' m.reset_mock() local.parse('all') assert m.called assert m.call_args[0][0] == '1:*' m.reset_mock() local.parse() assert not m.called m.reset_mock() gm_client.add_emails([{}]) assert m.called assert m.call_args[0][0] == '9'
def test_link_threads_part1(gm_client, msgs): gm_client.add_emails([{}, {}]) res = msgs() assert ['1', '2'] == [i['uid'] for i in res] assert ['#latest', '#latest'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [ '<*****@*****.**>', '<*****@*****.**>' ] local.link_threads(['1', '2']) res = msgs(local.SRC) assert (('1', '2', '3'), ) == thread() assert ['', '', '\\Seen #link'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [ None, None, ('<*****@*****.**> <101@mlr> ' '<*****@*****.**> <102@mlr>') ] res = msgs() assert ['1', '2', '3'] == [i['uid'] for i in res] assert ['', '#latest', '\\Seen #link'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [ '<*****@*****.**>', '<*****@*****.**>', ('<*****@*****.**> <101@mlr> ' '<*****@*****.**> <102@mlr>') ] local.parse('all') res = msgs(local.SRC) assert (('1', '2', '3'), ) == thread() assert ['', '', '\\Seen #link'] == [i['flags'] for i in res] res = msgs() assert ['4', '5', '6'] == [i['uid'] for i in res] assert ['', '#latest', '\\Seen #link'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [ '<*****@*****.**>', '<*****@*****.**>', ('<*****@*****.**> <101@mlr> ' '<*****@*****.**> <102@mlr>') ] gm_client.add_emails([{}]) local.link_threads(['4', '7']) res = msgs(local.SRC) assert (('1', '2', '4', '5'), ) == thread() assert ['1', '2', '4', '5'] == [i['uid'] for i in res] assert [i['flags'] for i in res] == ['', '', '', '\\Seen #link'] assert [i['body']['references'] for i in res] == [ None, None, None, ('<*****@*****.**> <101@mlr> ' '<*****@*****.**> <102@mlr> ' '<*****@*****.**> <103@mlr>') ] res = msgs() assert ['4', '5', '7', '8'] == [i['uid'] for i in res] assert ['', '', '#latest', '\\Seen #link'] == [i['flags'] for i in res] assert [i['body']['references'] for i in res] == [ '<*****@*****.**>', '<*****@*****.**>', '<*****@*****.**>', ('<*****@*****.**> <101@mlr> ' '<*****@*****.**> <102@mlr> ' '<*****@*****.**> <103@mlr>') ] gm_client.add_emails([{'refs': '<101@mlr>'}]) res = msgs(local.SRC) assert (('1', '2', '4', '5', '6'), ) == thread() assert ['1', '2', '4', '5', '6'] == [i['uid'] for i in res] assert [i['flags'] for i in res] == ['', '', '', '\\Seen #link', ''] assert [i['body']['references'] for i in res] == [ None, None, None, ('<*****@*****.**> <101@mlr> ' '<*****@*****.**> <102@mlr> ' '<*****@*****.**> <103@mlr>'), '<101@mlr>' ] res = msgs() assert ['4', '5', '7', '8', '9'] == [i['uid'] for i in res] assert [i['flags'] for i in res] == ['', '', '', '\\Seen #link', '#latest'] assert [i['body']['references'] for i in res] == [ '<*****@*****.**>', '<*****@*****.**>', '<*****@*****.**>', ('<*****@*****.**> <101@mlr> ' '<*****@*****.**> <102@mlr> ' '<*****@*****.**> <103@mlr>'), '<101@mlr>' ] assert local.search_thrs('uid 4') == [i[0] for i in local.thrs_info(['4'])] local.update_links() res = msgs(local.SRC) assert (('1', '2', '4', '6', '7'), ) == thread() assert ['1', '2', '4', '6', '7'] == [i['uid'] for i in res] assert [i['flags'] for i in res] == ['', '', '', '', '\\Seen #link'] res = msgs() assert ['4', '5', '7', '9', '10'] == [i['uid'] for i in res] assert [i['flags'] for i in res] == ['', '', '', '#latest', '\\Seen #link']
def test_general(gm_client, load_file, latest, load_email): gm_client.add_emails([{'flags': '\\Flagged'}]) msg = latest() assert 'X-UID' in msg['body'] assert re.match(r'<\d+>', msg['body']['X-UID']) assert '\\Flagged' in msg['flags'] # `email.policy.default` is not working with long addresses. # Exits with: "segmentation fault (core dumped)" # when running in threads. gm_client.add_emails([{ 'txt': 'some text' }, { 'raw': load_file('msg-header-with-long-addresses.txt') }]) msg = latest()['body'] assert msg['to'].startswith('"primary discussion list') # should be decoding of headers during parsing gm_client.add_emails([{ 'raw': load_file('msg-header-with-encoding.txt') }], parse=False) local.parse(batch=1) msg = latest(raw=True)['body'].decode() expect = '\r\n'.join([ 'X-UID: <4>', 'Message-ID: <with-encoding@test>', 'Subject: Re: не пора ли подкрепиться?', 'Date: Wed, 07 Jan 2015 13:23:22 +0000', 'From: "Катя К." <*****@*****.**>', 'To: "Grisha" <*****@*****.**>', ]) assert msg.startswith(expect), '%s\n\n%s' % (expect, msg) gm_client.add_emails([{ 'raw': load_file('msg-header-with-no-encoding.txt') }], parse=False) local.parse(batch=1) msg = latest(raw=True)['body'].decode() expect = '\r\n'.join([ 'X-UID: <5>', 'Message-ID: <with-no-encoding@test>', 'Subject: Re: не пора ли подкрепиться?', 'Date: Wed, 07 Jan 2015 13:23:22 +0000', 'From: "Катя К." <*****@*****.**>', 'To: "Гриша" <*****@*****.**>', ]) assert msg.startswith(expect), '%s\n\n%s' % (expect, msg) # msg with UnicodeDecodeError raw = b'\r\n'.join([ b'Message-ID: <with-bad-symbol@test>', b'Subject: bad symbol?', b'Date: Wed, 07 Jan 2015 13:23:22 +0000', b'From: [email protected]', b'To: [email protected]', b'Content-type: text/html; charset=utf-8', b'Content-Transfer-Encoding: 8bit', b'MIME-Version: 1.0', b'', b'', b'\xd0\xb2\xd0\xbe\xd0\xb7\xd0\xbc\xd0\xbe\xd0\xb6\xd0\xbd\xd0\r\n ' b'\xbe\xd1\x81\xd1\x82\xd0\xb8,' ]) gm_client.add_emails([{'raw': raw}]) msg = latest(parsed=True) assert msg['meta']['preview'] == 'возможн� �сти,' assert msg['body'] == '<p>возможн�\r\n �сти,</p>' assert msg['meta']['errors'] assert ("error on 'text/html()': [UnicodeDecodeError] 'utf-8' codec can't " 'decode byte 0xd0 in position 16: invalid continuation byte' in msg['meta']['errors'][0]) m = load_email('msg-lookup-error.txt', parsed=True) assert m['meta']['preview'] == 'test' assert m['body'] == '<p>test</p>' assert m['meta']['errors'] assert ('[LookupError] unknown encoding: iso-2022-int-1' in m['meta']['errors'][0]) # date can't be parsed raw = b'\r\n'.join([ b'Message-ID: <with-bad-symbol@test>', b'Subject: bad symbol?', b'Date: 15.06.2018 09:24:13 +0800', b'From: [email protected]', b'To: [email protected]', b'Content-type: text/html; charset=utf-8', b'Content-Transfer-Encoding: 8bit', b'MIME-Version: 1.0', b'', b'', b'Hello!' ]) gm_client.add_emails([{'raw': raw}]) msg = latest(parsed=True) assert msg['meta']['date'] == msg['meta']['arrived'] assert msg['meta']['errors'] == [ 'error on date: val=\'15.06.2018 09:24:13 +0800\' ' 'err=TypeError("\'NoneType\' object is not iterable",)' ] # ending @ symbol and address without @ symbol at all m = load_email('msg-from-ending-snail.txt', parsed=True) assert m['meta']['from'] == { 'addr': 'grrr@', 'name': 'grrr', 'title': 'grrr@', 'hash': '8ea2bc312c94c9596ad95772d6cd579c', } assert m['meta']['reply-to'] == [{ 'addr': 'grrr', 'name': 'grrr', 'title': 'grrr', 'hash': 'd4468c0c805f9a0e200c0e916824547a', }] assert m['meta']['to'] assert m['meta']['reply-to'] assert m['body_full']['to'] == 'katya@' assert m['body_full']['from'] == 'grrr@' assert m['body_full']['reply-to'] == 'grrr' raw = load_email('msg-from-rss2email.txt', parsed=True)['raw'].decode() assert 'From: "БлоGнот: Gray" <*****@*****.**>' in raw, raw # test links m = load_email('msg-links.txt', parsed=True) link = '<a href="{0}" target="_blank">' l1 = link.format('https://github.com/naspeh/mailur') # l2 = link.format('http://bottlepy.org/docs/dev/routing.html#rule-syntax') l2 = link.format('http://bottlepy.org/docs/dev/routing.html') assert m['body'].count(l1) == 3, '%s\n%s' % (l1, m['body']) assert m['body'].count(l2) == 2, '%s\n%s' % (l2, m['body'])
def test_cli_idle_gmail(gm_client, msgs, login, patch): actions = [] def fetch(con, uids, fields): responces = getattr(gm_client, 'fetch', None) if 'CHANGEDSINCE' in fields: index = fields.split()[-1] if index == '5)': return ('OK', [ b'4 (X-GM-MSGID 10400 ' b'X-GM-LABELS ("\\Inbox" "\\Starred" "mlr/thrid/777") ' b'FLAGS (\\Seen) UID 104 MODSEQ (427368))' ]) return ('OK', []) elif responces: return responces.pop() elif 'X-GM-LABELS' in fields: if con.current_box != local.SRC: return ('OK', [ b'1 (X-GM-MSGID 10100 X-GM-LABELS () ' b'FLAGS (\\Seen) UID 101 MODSEQ (427368))' ]) return ('OK', [ (b'2 (X-GM-MSGID 10200 X-GM-LABELS () ' b'FLAGS (\\Seen) UID 102 MODSEQ (427368))'), (b'3 (X-GM-MSGID 10300 X-GM-LABELS () ' b'FLAGS (\\Seen) UID 103 MODSEQ (427368))'), (b'4 (X-GM-MSGID 10400 X-GM-LABELS () ' b'FLAGS (\\Seen) UID 104 MODSEQ (427368))'), (b'6 (X-GM-MSGID 10600 X-GM-LABELS (\\Draft) ' b'FLAGS (\\Seen) UID 106 MODSEQ (427368))'), ]) return con._uid('FETCH', uids, fields) def search(con, charset, *criteria): if 'X-GM-MSGID' in criteria[0]: uid = int(criteria[0].split()[-1]) // 100 return ('OK', [(b'%d' % uid)]) return con._uid('SEARCH', charset, *criteria) def store(con, uids, cmd, flags): if 'X-GM-LABELS' in cmd: actions.append((uids, cmd, sorted(flags.split()))) return ('OK', []) return con._uid('STORE', uids, cmd, flags) gm_client.fake_fetch = fetch gm_client.fake_search = search gm_client.fake_store = store spawn(lambda: cli.main('%s sync --timeout=300' % login.user1)) sleep(5) gm_client.add_emails([{}] * 4, fetch=False, parse=False) sleep(3) assert len(msgs(local.SRC)) == 4 assert len(msgs()) == 4 local.parse('all') gm_client.add_emails([{}], fetch=False, parse=False) sleep(3) assert len(msgs(local.SRC)) == 5 assert len(msgs()) == 5 expected_flags = ['', '', '', '\\Flagged \\Seen #inbox', ''] assert [i['flags'] for i in msgs(local.SRC)] == expected_flags assert [i['flags'] for i in msgs()] == expected_flags con_src = local.client(local.SRC, readonly=False) con_src.store('1:*', '+FLAGS', '#1') sleep(3) expected_flags = [(f + ' #1').strip() for f in expected_flags] assert [i['flags'] for i in msgs(local.SRC)] == expected_flags assert [i['flags'] for i in msgs()] == expected_flags assert actions == [ ('101', '-X-GM-LABELS', ['\\Spam']), ('101', '+X-GM-LABELS', ['\\Inbox']), # move to \\All ('101', '-X-GM-LABELS', ['\\Trash']), ('101', '+X-GM-LABELS', ['\\Inbox']), # move to \\All ('104', '+X-GM-LABELS', ['\\Inbox', '\\Starred']), ('104', '+X-GM-LABELS', ['\\Inbox', '\\Starred']), ('101', '-X-GM-LABELS', ['\\Spam']), ('101', '+X-GM-LABELS', ['\\Inbox']), # move to \\All ('101', '-X-GM-LABELS', ['\\Trash']), ('101', '+X-GM-LABELS', ['\\Inbox']), # move to \\All ] actions.clear() con_src = local.client(local.SRC, readonly=False) con_src.store('2', '+FLAGS', '#inbox') sleep(3) con_src.store('2', '+FLAGS', '#trash') sleep(3) expected_flags[1] = '#inbox #1 #trash' assert [i['flags'] for i in msgs(local.SRC)] == expected_flags assert [i['flags'] for i in msgs()] == expected_flags assert actions == [ ('102', '+X-GM-LABELS', ['\\Inbox']), ('102', '-X-GM-LABELS', ['\\Inbox']), ('102', '+X-GM-LABELS', ['\\Trash']), ]
def test_uidpairs(gm_client, msgs, patch, call): gm_client.add_emails([{}, {}], parse=False) assert ['1', '2'] == [i['uid'] for i in msgs(local.SRC)] assert local.pair_origin_uids(['1', '2']) == () assert local.pair_parsed_uids(['1', '2']) == () local.parse() assert local.data_uidpairs.get() == {'1': '1', '2': '2'} assert local.pair_origin_uids(['1', '2']) == ('1', '2') assert local.pair_parsed_uids(['1', '2']) == ('1', '2') local.parse('uid 1') assert ['2', '3'] == [i['uid'] for i in msgs()] assert local.data_uidpairs.get() == {'1': '3', '2': '2'} assert local.pair_origin_uids(['1', '2']) == ('3', '2') assert local.pair_parsed_uids(['2', '3']) == ('2', '1') local.parse('all') assert ['4', '5'] == [i['uid'] for i in msgs()] assert local.data_uidpairs.get() == {'1': '4', '2': '5'} assert local.pair_origin_uids(['1', '2']) == ('4', '5') assert local.pair_parsed_uids(['4', '5']) == ('1', '2') assert local.pair_origin_uids(['2']) == ('5',) assert local.pair_parsed_uids(['5']) == ('2',) # warm cache up local.data_addresses.get() local.data_uidpairs.get() local.data_threads.get() local.data_msgids.get() with patch('imaplib.IMAP4.uid') as m: m.return_value = 'OK', [] local.update_metadata('4') assert m.called assert m.call_args_list == [ call('FETCH', '4', '(FLAGS BINARY.PEEK[1])'), call('FETCH', '1:*', '(UID BODY[HEADER.FIELDS (Subject)])'), call('THREAD', 'REFS UTF-8 INTHREAD REFS UID 4'), ] patched = {'wraps': local.update_metadata} with patch('mailur.local.update_metadata', **patched) as m: local.parse('uid 1') assert m.called assert m.call_args == call('6') m.reset_mock() local.parse('all') assert m.called assert m.call_args == call('1:*') m.reset_mock() local.parse() assert not m.called m.reset_mock() gm_client.add_emails([{}]) assert m.called assert m.call_args == call('9')
def test_data_threads(gm_client): gm_client.add_emails([{'subj': 'new subj'}]) assert local.data_threads.get()[1] == {'1': ['1']} assert local.search_thrs('all') == ['1'] local.parse('all') assert local.data_threads.get()[1] == {'2': ['2']} assert local.search_thrs('all') == ['2'] gm_client.add_emails([{'subj': 'new subj'}]) assert local.data_threads.get()[1] == {'3': ['2', '3']} assert local.search_thrs('all') == ['3'] gm_client.add_emails([{'in_reply_to': '<101@mlr>'}]) assert local.data_threads.get()[1] == {'4': ['2', '3', '4']} assert local.search_thrs('all') == ['4'] gm_client.add_emails([{'refs': '<101@mlr> <102@mlr>'}]) assert local.data_threads.get()[1] == {'5': ['2', '3', '4', '5']} assert local.search_thrs('all') == ['5'] local.parse('all') assert local.data_threads.get()[1] == {'9': ['6', '7', '8', '9']} assert local.search_thrs('all') == ['9'] local.parse('uid *') assert local.data_threads.get()[1] == {'10': ['6', '7', '8', '10']} assert local.search_thrs('all') == ['10'] local.update_threads('1:*') assert local.data_threads.get()[1] == {'10': ['6', '7', '8', '10']} assert local.search_thrs('all') == ['10'] local.update_threads('1') assert local.data_threads.get()[1] == {'10': ['6', '7', '8', '10']} assert local.search_thrs('all') == ['10'] local.update_threads('1:*') assert local.data_threads.get()[1] == {'10': ['6', '7', '8', '10']} assert local.search_thrs('all') == ['10'] gm_client.add_emails([ {'refs': '<non-exist@mlr>'}, {'refs': '<non-exist@mlr> <101@mlr>'} ]) assert local.data_threads.get()[1] == { '11': ['11'], '12': ['6', '7', '8', '10', '12'] } assert local.search_thrs('all') == ['12', '11'] gm_client.add_emails([{'labels': 't1'}, {'labels': 't2'}], parse=False) local.parse('UID 6:*') assert local.data_threads.get()[1] == { '11': ['11'], '13': ['6', '7', '8', '10', '13'], '14': ['14'], '15': ['15'] } assert local.search_thrs('all') == ['15', '14', '13', '11'] local.update_threads('*') assert local.data_threads.get()[1] == { '11': ['11'], '13': ['6', '7', '8', '10', '13'], '14': ['14'], '15': ['15'] } assert local.search_thrs('all') == ['15', '14', '13', '11']