setup tests
This commit is contained in:
parent
fb17a87016
commit
abe6497421
10 changed files with 3060 additions and 6 deletions
12
.github/workflows/test.yml
vendored
Normal file
12
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name: build docker image and run tests inside
|
||||||
|
on: push
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Start
|
||||||
|
run: docker-compose -f test.yml up -d
|
||||||
|
- name: Tests
|
||||||
|
run: docker-compose -f test.yml run --entrypoint "" tests python -m unittest
|
|
@ -40,8 +40,7 @@ async def handler(request):
|
||||||
return create_json_response(HTTPStatus.BAD_REQUEST, 'Invalid JSON')
|
return create_json_response(HTTPStatus.BAD_REQUEST, 'Invalid JSON')
|
||||||
|
|
||||||
if not all(key in data for key in ['text', 'key']):
|
if not all(key in data for key in ['text', 'key']):
|
||||||
return create_json_response(HTTPStatus.BAD_REQUEST,
|
return create_json_response(HTTPStatus.BAD_REQUEST, 'Missing text and/or API key property')
|
||||||
'Missing text and/or API key property')
|
|
||||||
|
|
||||||
if data['key'] != API_KEY:
|
if data['key'] != API_KEY:
|
||||||
return create_json_response(HTTPStatus.UNAUTHORIZED, 'Invalid API key')
|
return create_json_response(HTTPStatus.UNAUTHORIZED, 'Invalid API key')
|
||||||
|
@ -51,7 +50,7 @@ async def handler(request):
|
||||||
'msgtype': 'm.text',
|
'msgtype': 'm.text',
|
||||||
'body': data['text'],
|
'body': data['text'],
|
||||||
'format': 'org.matrix.custom.html',
|
'format': 'org.matrix.custom.html',
|
||||||
'formatted_body': markdown(data['text'], extensions=['extra']),
|
'formatted_body': markdown(str(data['text']), extensions=['extra']),
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
await send_room_message(room_id, content)
|
await send_room_message(room_id, content)
|
||||||
|
@ -70,9 +69,7 @@ def create_json_response(status, ret):
|
||||||
|
|
||||||
async def send_room_message(room_id, content):
|
async def send_room_message(room_id, content):
|
||||||
"""Send a message to a room."""
|
"""Send a message to a room."""
|
||||||
return await CLIENT.room_send(room_id=room_id,
|
return await CLIENT.room_send(room_id=room_id, message_type='m.room.message', content=content)
|
||||||
message_type='m.room.message',
|
|
||||||
content=content)
|
|
||||||
|
|
||||||
|
|
||||||
async def main(event):
|
async def main(event):
|
||||||
|
|
2
setup.cfg
Normal file
2
setup.cfg
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[pydocstyle]
|
||||||
|
ignore = D203,D204,D212
|
11
test.yml
Normal file
11
test.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
bot:
|
||||||
|
build: .
|
||||||
|
env_file:
|
||||||
|
- tests/.env
|
||||||
|
tests:
|
||||||
|
build: tests
|
||||||
|
env_file:
|
||||||
|
- tests/.env
|
4
tests/.env
Normal file
4
tests/.env
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
MATRIX_URL=http://tests
|
||||||
|
MATRIX_ID=bot
|
||||||
|
MATRIX_PW=pw
|
||||||
|
API_KEY=ak
|
17
tests/Dockerfile
Normal file
17
tests/Dockerfile
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
FROM matrixdotorg/synapse
|
||||||
|
|
||||||
|
# This defaults to /data which is a volume aiming at keeping data.
|
||||||
|
# Here, we want to trash those, and avoid the permission issues, so let's use something else
|
||||||
|
ENV SYNAPSE_CONFIG_DIR=/srv
|
||||||
|
|
||||||
|
WORKDIR $SYNAPSE_CONFIG_DIR
|
||||||
|
|
||||||
|
ADD homeserver.yaml .
|
||||||
|
|
||||||
|
RUN python -m synapse.app.homeserver --config-path homeserver.yaml --generate-keys
|
||||||
|
|
||||||
|
RUN chown -R 991:991 .
|
||||||
|
|
||||||
|
RUN python -m pip install aiohttp matrix-nio
|
||||||
|
|
||||||
|
ADD . .
|
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Make this directory a valid module for unittests autodiscover to work."""
|
2910
tests/homeserver.yaml
Normal file
2910
tests/homeserver.yaml
Normal file
File diff suppressed because it is too large
Load diff
51
tests/tests.py
Normal file
51
tests/tests.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
"""Main test module."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import nio
|
||||||
|
|
||||||
|
from utils import MATRIX_ID, MATRIX_PW, MATRIX_URL, AbstractBotTest
|
||||||
|
|
||||||
|
KEY = os.environ['API_KEY']
|
||||||
|
|
||||||
|
|
||||||
|
class BotTest(AbstractBotTest):
|
||||||
|
"""Main test class."""
|
||||||
|
async def test_errors(self):
|
||||||
|
"""Check the bot's error paths."""
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get('http://bot:4785') as response:
|
||||||
|
self.assertEqual(await response.json(), {'status': 400, 'ret': 'Invalid JSON'})
|
||||||
|
async with session.post('http://bot:4785', 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('http://bot:4785', data=json.dumps({'text': 3, 'key': None})) as response:
|
||||||
|
self.assertEqual(await response.json(), {'status': 401, 'ret': 'Invalid API key'})
|
||||||
|
async with session.post('http://bot:4785', 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'})
|
||||||
|
|
||||||
|
async def test_message(self):
|
||||||
|
"""Send a markdown message, and check the result."""
|
||||||
|
text = '# Hello'
|
||||||
|
messages = []
|
||||||
|
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
|
||||||
|
|
||||||
|
await client.login(MATRIX_PW)
|
||||||
|
|
||||||
|
room = await client.room_create()
|
||||||
|
|
||||||
|
url = f'http://bot:4785/{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'})
|
||||||
|
|
||||||
|
sync = await client.sync()
|
||||||
|
messages = await client.room_messages(room.room_id, sync.next_batch)
|
||||||
|
|
||||||
|
message = messages.chunk[0]
|
||||||
|
self.assertEqual(message.sender, '@bot:tests')
|
||||||
|
self.assertEqual(message.body, text)
|
||||||
|
self.assertEqual(message.formatted_body, '<h1>Hello</h1>')
|
||||||
|
await client.close()
|
49
tests/utils.py
Normal file
49
tests/utils.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
"""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
|
||||||
|
|
||||||
|
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('http://bot:4785/', 'status'),
|
||||||
|
)))
|
||||||
|
|
||||||
|
# Try to register an user for the bot. Don't worry if it already exists.
|
||||||
|
with open('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
|
Loading…
Reference in a new issue