diff --git a/tests/Dockerfile b/tests/Dockerfile index aa27576..af2e1bd 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -12,6 +12,6 @@ ADD tests/homeserver.yaml . RUN python -m synapse.app.homeserver --config-path homeserver.yaml --generate-keys RUN chown -R 991:991 . -RUN pip install --no-cache-dir aiohttp matrix-nio markdown coverage +RUN pip install --no-cache-dir markdown matrix-nio httpx coverage WORKDIR /app diff --git a/tests/start.py b/tests/start.py index b27512b..8eef22e 100755 --- a/tests/start.py +++ b/tests/start.py @@ -1,13 +1,53 @@ #!/usr/bin/env python """Entry point to start an instrumentalized bot for coverage and run tests.""" +from os import environ from subprocess import Popen, run +from time import time from unittest import main +import httpx +import yaml +from synapse._scripts.register_new_matrix_user import request_registration + +BOT_URL = 'http://localhost:4785' +MATRIX_URL, MATRIX_ID, MATRIX_PW = (environ[v] for v in ['MATRIX_URL', 'MATRIX_ID', 'MATRIX_PW']) + + +def check_json(url: str, key: str) -> bool: + """Ensure a service at a given url answers with valid json containing a certain key.""" + try: + data = httpx.get(url).json() + return key in data + except httpx.ConnectError: + return False + + +def wait_available(url: str, key: str, timeout: int = 10) -> bool: + """Wait until a service answer correctly or timeout.""" + start = time() + while True: + if check_json(url, key): + return True + if time() > start + timeout: + return False + def run_and_test(): """Launch the bot and its tests.""" + if not wait_available(f'{MATRIX_URL}/_matrix/client/r0/login', 'flows'): + return False + + # Try to register an user for the bot. + with open('/srv/homeserver.yaml') as f: + secret = yaml.safe_load(f.read()).get("registration_shared_secret", None) + request_registration(MATRIX_ID, MATRIX_PW, MATRIX_URL, secret, admin=True) + bot = Popen(['coverage', 'run', 'matrix_webhook.py']) + + if not wait_available(BOT_URL, 'status'): + return False + ret = main(module=None, exit=False).result.wasSuccessful() bot.terminate() for cmd in ['report', 'html', 'xml']: diff --git a/tests/tests.py b/tests/tests.py index cdb32a8..9a0e807 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,30 +1,35 @@ """Main test module.""" -import json import os +import unittest -import aiohttp +import httpx import nio -from .utils import BOT_URL, MATRIX_ID, MATRIX_PW, MATRIX_URL, AbstractBotTest +from .start import BOT_URL, MATRIX_ID, MATRIX_PW, MATRIX_URL KEY = os.environ['API_KEY'] +FULL_ID = f'@{MATRIX_ID}:{MATRIX_URL.split("/")[2]}' -class BotTest(AbstractBotTest): +def bot_req(req=None, key=None, room_id=None): + """Bot requests boilerplate.""" + if key is not None: + req['key'] = key + url = BOT_URL if room_id is None else f'{BOT_URL}/{room_id}' + return httpx.post(url, json=req).json() + + +class BotTest(unittest.IsolatedAsyncioTestCase): """Main test class.""" - async def test_errors(self): + def test_errors(self): """Check the bot's error paths.""" - async with aiohttp.ClientSession() as session: - async with session.get(BOT_URL) as response: - self.assertEqual(await response.json(), {'status': 400, 'ret': 'Invalid JSON'}) - async with session.post(BOT_URL, data=json.dumps({'toto': 3})) as response: - self.assertEqual(await response.json(), {'status': 400, 'ret': 'Missing text and/or API key property'}) - async with session.post(BOT_URL, data=json.dumps({'text': 3, 'key': None})) as response: - self.assertEqual(await response.json(), {'status': 401, 'ret': 'Invalid API key'}) - async with session.post(BOT_URL, data=json.dumps({'text': 3, 'key': KEY})) as response: - # TODO: we are not sending to a real room, so this should not be "OK" - self.assertEqual(await response.json(), {'status': 200, 'ret': 'OK'}) + self.assertEqual(bot_req(), {'status': 400, 'ret': 'Invalid JSON'}) + self.assertEqual(bot_req({'toto': 3}), {'status': 400, 'ret': 'Missing text and/or API key property'}) + self.assertEqual(bot_req({'text': 3, 'key': None}), {'status': 401, 'ret': 'Invalid API key'}) + + # TODO: we are not sending to a real room, so this should not be "OK" + self.assertEqual(bot_req({'text': 3}, KEY), {'status': 200, 'ret': 'OK'}) async def test_message(self): """Send a markdown message, and check the result.""" @@ -33,19 +38,32 @@ class BotTest(AbstractBotTest): client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) await client.login(MATRIX_PW) - room = await client.room_create() - url = f'{BOT_URL}/{room.room_id}' - async with aiohttp.ClientSession() as session: - async with session.post(url, data=json.dumps({'text': text, 'key': KEY})) as response: - self.assertEqual(await response.json(), {'status': 200, 'ret': 'OK'}) + self.assertEqual(bot_req({'text': text}, KEY, room.room_id), {'status': 200, 'ret': 'OK'}) sync = await client.sync() messages = await client.room_messages(room.room_id, sync.next_batch) + await client.close() message = messages.chunk[0] - self.assertEqual(message.sender, '@bot:tests') + self.assertEqual(message.sender, FULL_ID) self.assertEqual(message.body, text) self.assertEqual(message.formatted_body, '

Hello

') + + async def test_z_disconnected(self): + """Send a message after disconnection, and check the error.""" + client = nio.AsyncClient(MATRIX_URL, MATRIX_ID) + await client.login(MATRIX_PW) + token = client.access_token + + resp = httpx.post(f'{MATRIX_URL}/_synapse/admin/v1/deactivate/{FULL_ID}', + json={'erase': True}, + params={'access_token': token}) + self.assertEqual(resp.json(), {'id_server_unbind_result': 'success'}) + + await client.logout(all_devices=True) await client.close() + + # TODO: I was hopping that one wouldn't be happy + self.assertEqual(bot_req({'text': 'bye'}, KEY), {'status': 200, 'ret': 'OK'}) diff --git a/tests/utils.py b/tests/utils.py deleted file mode 100644 index 3b4bd09..0000000 --- a/tests/utils.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Utility tools to run tests.""" -import asyncio -import os -import time -import unittest - -import aiohttp -import yaml -from synapse._scripts.register_new_matrix_user import request_registration - -BOT_URL = 'http://localhost:4785' -MATRIX_URL, MATRIX_ID, MATRIX_PW = (os.environ[v] for v in ['MATRIX_URL', 'MATRIX_ID', 'MATRIX_PW']) - - -class AbstractBotTest(unittest.IsolatedAsyncioTestCase): - """Abstract test class.""" - async def asyncSetUp(self): - """Set up the test environment.""" - # Wait for synapse and the bot to answer - self.assertTrue( - all(await asyncio.gather( - wait_available(f'{MATRIX_URL}/_matrix/client/r0/login', 'flows'), - wait_available(BOT_URL, 'status'), - ))) - - # Try to register an user for the bot. Don't worry if it already exists. - with open('/srv/homeserver.yaml') as f: - secret = yaml.safe_load(f.read()).get("registration_shared_secret", None) - request_registration(MATRIX_ID, MATRIX_PW, MATRIX_URL, secret, admin=False, user_type=None, exit=lambda x: x) - - -async def check_json(session: aiohttp.ClientSession, url: str, key: str) -> bool: - """Ensure a service at a given url answers with valid json containing a certain key.""" - try: - async with session.get(url) as response: - data = await response.json() - return key in data - except aiohttp.client_exceptions.ClientConnectorError: - return False - - -async def wait_available(url: str, key: str, timeout: int = 60) -> bool: - """Wait until a service answer correctly or timeout.""" - start = time.time() - async with aiohttp.ClientSession() as session: - while True: - if await check_json(session, url, key): - return True - if time.time() > start + timeout: - return False