Как мне высмеять IMAP-сервер в Python, несмотря на крайнюю лень?

Мне интересно узнать, есть ли простой способ издеваться над IMAP-сервером (a la the imaplib module) в Python, не выполняя много работы.

Есть ли уже существующее решение? В идеале я мог бы подключиться к существующему IMAP-серверу, сделать дамп и отключить макет-сервер с реальной структурой почтового ящика/электронной почты.

Некоторый фон в лень: у меня неприятное ощущение, что этот маленький script, который я пишу, со временем будет расти и хотел бы создать надлежащую тестовую среду, но, учитывая, что со временем она не будет расти, я Не хочу делать много работы, чтобы запустить макет сервера.

Ответ 1

Мне было довольно легко написать IMAP-сервер в скрученном прошлом, когда я пытался. Он поставляется с поддержкой для написания серверов IMAP, и вы обладаете огромной гибкостью.

Ответ 2

Сколько вам нужно для любого теста? Если вы начнете что-то строить по порядку сложности реального сервера, чтобы вы могли использовать его во всех своих тестах, вы уже поступили не так. Просто издевайтесь над битами, которые нужны каждому из них.

Не утруждайте себя попыткой поделиться макетной реализацией. Они не должны быть активами, но отбрасываемые биты-n-части.

Ответ 3

Поскольку я не нашел что-то удобное в python 3 для моих нужд (почтовая часть twisted не работает в python 3), я сделал небольшой макет с асинчио, который вы можете улучшить, если хотите:

Я определил ImapProtocol, который расширяет asyncio.Protocol. Затем запустите сервер следующим образом:

factory = loop.create_server(lambda: ImapProtocol(mailbox_map), 'localhost', 1143)
server = loop.run_until_complete(factory)

Mailbox_map - это карта карты: электронная почта → карта почтовых ящиков → набор сообщений. Таким образом, все сообщения/почтовые ящики находятся в памяти.

Каждый раз, когда клиент подключается, создается новый экземпляр ImapProtocol. Затем ImapProtocol выполняет и отвечает за каждого клиента, реализуя возможности /login/fetch/select/search/store:

class ImapHandler(object):
    def __init__(self, mailbox_map):
        self.mailbox_map = mailbox_map
        self.user_login = None
        # ...

    def connection_made(self, transport):
        self.transport = transport
        transport.write('* OK IMAP4rev1 MockIMAP Server ready\r\n'.encode())

    def data_received(self, data):
        command_array = data.decode().rstrip().split()
        tag = command_array[0]
        self.by_uid = False
        self.exec_command(tag, command_array[1:])

    def connection_lost(self, error):
        if error:
            log.error(error)
        else:
            log.debug('closing')
            self.transport.close()
        super().connection_lost(error)

    def exec_command(self, tag, command_array):
        command = command_array[0].lower()
        if not hasattr(self, command):
            return self.error(tag, 'Command "%s" not implemented' % command)
        getattr(self, command)(tag, *command_array[1:])

    def capability(self, tag, *args):
        # code for it...
    def login(self, tag, *args):
        # code for it...

Затем в моих тестах я запускаю сервер во время установки с помощью:

self.loop = asyncio.get_event_loop()
self.server = self.loop.run_until_complete(self.loop.create_server(create_imap_protocol, 'localhost', 12345))

Когда я хочу симулировать новое сообщение:

imap_receive(Mail(to='[email protected]', mail_from='[email protected]', subject='hello'))

И остановите его при разрыве:

self.server.close()
asyncio.wait_for(self.server.wait_closed(), 1)

cf https://github.com/bamthomas/aioimaplib/blob/master/aioimaplib/tests/imapserver.py


РЕДАКТИРОВАТЬ: у меня была багги-остановка сервера, я переписал ее с помощью asyncio.Protocol и изменил ответ, чтобы отразить изменения

Ответ 4

Я никогда не пробовал, но если бы мне пришлось, я бы начал с существующего SMTP-сервера.