MongoDB chat storage backend example

Example Service

import argparse
import uuid

from aiohttp import web
from motor import motor_asyncio
import pymongo

HTTP_TIMEOUT = 10


def main():
    parser = argparse.ArgumentParser(
        description='MongoDB based storage service.',
    )
    parser.add_argument('--port', type=int, default=8080)
    parser.add_argument(
        '--mongo-uri',
        default='mongodb://localhost:27017/',
        help='mongodb connection uri',
    )

    args = parser.parse_args()
    web.run_app(create_app(args), port=args.port)


async def create_app(args):
    connection = motor_asyncio.AsyncIOMotorClient(args.mongo_uri)
    collection = connection['chat_db']['messages']
    routes = web.RouteTableDef()

    @routes.get('/ping')
    async def handle_ping(request):
        return web.Response(text='OK.')

    @routes.post('/messages/send')
    async def post(request):
        doc = await request.json()
        doc_id = uuid.uuid4().hex
        await collection.update_one(
            {'_id': doc_id},
            {
                '$set': {'username': doc['username'], 'text': doc['text']},
                '$currentDate': {'created': True},
            },
            upsert=True,
        )
        return web.json_response({'id': doc_id})

    @routes.post('/messages/retrieve')
    async def get(request):
        docs = await (
            collection.find({}).sort('created', pymongo.DESCENDING).limit(20)
        ).to_list(20)
        messages = [
            {
                'username': doc['username'],
                'text': doc['text'],
                'created': doc['created'].isoformat(),
            }
            for doc in docs
        ]
        return web.json_response({'messages': messages})

    app = web.Application()
    app.add_routes(routes)
    return app


if __name__ == '__main__':
    main()

Conftest

import pathlib
import sys

import pytest

pytest_plugins = [
    'testsuite.pytest_plugin',
    'testsuite.databases.mongo.pytest_plugin',
]

MONGO_COLLECTIONS = {
    'messages': {
        'settings': {
            'collection': 'messages',
            'connection': 'example',
            'database': 'chat_db',
        },
        'indexes': [{'key': 'created'}],
    },
}


def pytest_addoption(parser):
    group = parser.getgroup('Example service')
    group.addoption(
        '--example-service-port',
        help='Bind example services to this port (default is %(default)s)',
        default=8080,
        type=int,
    )


@pytest.fixture
async def example_service(
    ensure_daemon_started,
    # Service process holder
    example_service_scope,
    # Service dependencies
    mongodb,
    mockserver,
):
    # Start service if not started yet
    await ensure_daemon_started(example_service_scope)


@pytest.fixture
async def example_client(
    create_service_client,
    example_service_baseurl,
    example_service,
):
    # Create service client instance
    return create_service_client(example_service_baseurl)


@pytest.fixture(scope='session')
def example_service_baseurl(pytestconfig):
    return f'http://localhost:{pytestconfig.option.example_service_port}/'


@pytest.fixture(scope='session')
def example_root():
    """Path to example service root."""
    return pathlib.Path(__file__).parent.parent


@pytest.fixture(scope='session')
async def example_service_scope(
    pytestconfig,
    create_daemon_scope,
    example_service_baseurl,
    example_root,
    mongo_connection_info,
):
    async with create_daemon_scope(
        args=[
            sys.executable,
            str(example_root.joinpath('server.py')),
            '--port',
            str(pytestconfig.option.example_service_port),
            '--mongo-uri',
            mongo_connection_info.get_uri(),
        ],
        ping_url=example_service_baseurl + 'ping',
    ) as scope:
        yield scope


@pytest.fixture(scope='session')
def mongodb_settings():
    return MONGO_COLLECTIONS

Test

async def test_messages_send(example_client, mongodb):
    message = {
        'username': 'Alice',
        'text': 'Rabbits are so cute, love them ^_^',
    }
    response = await example_client.post('messages/send', json=message)
    assert response.status_code == 200
    body = response.json()
    msg_id = body['id']

    # read directly from mongodb
    stored_message = mongodb['messages'].find_one({'_id': msg_id})
    stored_message.pop('_id')
    stored_message.pop('created')
    assert stored_message == message


async def test_messages_retrieve(example_client):
    # db_messages.json
    read_response = await example_client.post('messages/retrieve')
    assert read_response.status_code == 200
    doc = read_response.json()
    assert doc == {
        'messages': [
            {
                'username': 'Alice',
                'created': '2020-01-01T12:02:00',
                'text': 'Hi Bob!',
            },
            {
                'username': 'Bob',
                'created': '2020-01-01T12:01:00',
                'text': 'Hi, my name is Bob!',
            },
        ],
    }