Merge branch 'first-integration-tests' into 'master'
First integration tests See merge request poezio/slixmpp!75
This commit is contained in:
commit
73cc2a4008
@ -13,6 +13,21 @@ test:
|
|||||||
- pip3 install emoji aiohttp
|
- pip3 install emoji aiohttp
|
||||||
- ./run_tests.py
|
- ./run_tests.py
|
||||||
|
|
||||||
|
test_integration:
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- docker
|
||||||
|
image: ubuntu:latest
|
||||||
|
only:
|
||||||
|
variables:
|
||||||
|
- $CI_ACCOUNT1
|
||||||
|
- $CI_ACCOUNT2
|
||||||
|
script:
|
||||||
|
- apt update
|
||||||
|
- apt install -y python3 python3-pip cython3 gpg
|
||||||
|
- pip3 install emoji aiohttp aiodns
|
||||||
|
- ./run_integration_tests.py
|
||||||
|
|
||||||
trigger_poezio:
|
trigger_poezio:
|
||||||
stage: trigger
|
stage: trigger
|
||||||
tags:
|
tags:
|
||||||
|
0
itests/__init__.py
Normal file
0
itests/__init__.py
Normal file
28
itests/test_basic_connect_and_message.py
Normal file
28
itests/test_basic_connect_and_message.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import unittest
|
||||||
|
from slixmpp.test.integration import SlixIntegration
|
||||||
|
|
||||||
|
|
||||||
|
class TestConnect(SlixIntegration):
|
||||||
|
async def asyncSetUp(self):
|
||||||
|
await super().asyncSetUp()
|
||||||
|
self.add_client(
|
||||||
|
self.envjid('CI_ACCOUNT1'),
|
||||||
|
self.envstr('CI_ACCOUNT1_PASSWORD'),
|
||||||
|
)
|
||||||
|
self.add_client(
|
||||||
|
self.envjid('CI_ACCOUNT2'),
|
||||||
|
self.envstr('CI_ACCOUNT2_PASSWORD'),
|
||||||
|
)
|
||||||
|
await self.connect_clients()
|
||||||
|
|
||||||
|
async def test_send_message(self):
|
||||||
|
"""Make sure we can send and receive messages"""
|
||||||
|
msg = self.clients[0].make_message(
|
||||||
|
mto=self.clients[1].boundjid, mbody='Msg body',
|
||||||
|
)
|
||||||
|
msg.send()
|
||||||
|
message = await self.clients[1].wait_until('message')
|
||||||
|
self.assertEqual(message['body'], msg['body'])
|
||||||
|
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestConnect)
|
78
itests/test_muc.py
Normal file
78
itests/test_muc.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import asyncio
|
||||||
|
import unittest
|
||||||
|
from uuid import uuid4
|
||||||
|
from slixmpp import JID
|
||||||
|
from slixmpp.test.integration import SlixIntegration
|
||||||
|
|
||||||
|
UNIQUE = uuid4().hex
|
||||||
|
|
||||||
|
|
||||||
|
class TestConnect(SlixIntegration):
|
||||||
|
|
||||||
|
async def asyncSetUp(self):
|
||||||
|
self.mucserver = self.envjid('CI_MUC_SERVER')
|
||||||
|
self.muc = JID('%s@%s' % (UNIQUE, self.mucserver))
|
||||||
|
self.add_client(
|
||||||
|
self.envjid('CI_ACCOUNT1'),
|
||||||
|
self.envstr('CI_ACCOUNT1_PASSWORD'),
|
||||||
|
)
|
||||||
|
self.add_client(
|
||||||
|
self.envjid('CI_ACCOUNT2'),
|
||||||
|
self.envstr('CI_ACCOUNT2_PASSWORD'),
|
||||||
|
)
|
||||||
|
self.register_plugins(['xep_0045'])
|
||||||
|
await self.connect_clients()
|
||||||
|
|
||||||
|
async def test_initial_join(self):
|
||||||
|
"""Check that we can connect to a new muc"""
|
||||||
|
self.clients[0]['xep_0045'].join_muc(self.muc, 'client1')
|
||||||
|
presence = await self.clients[0].wait_until('muc::%s::got_online' % self.muc)
|
||||||
|
self.assertEqual(presence['muc']['affiliation'], 'owner')
|
||||||
|
|
||||||
|
async def test_setup_muc(self):
|
||||||
|
"""Check that sending the initial room config and affiliation list works"""
|
||||||
|
self.clients[0]['xep_0045'].join_muc(self.muc, 'client1')
|
||||||
|
presence = await self.clients[0].wait_until('muc::%s::got_online' % self.muc)
|
||||||
|
self.assertEqual(presence['muc']['affiliation'], 'owner')
|
||||||
|
# Send initial configuration
|
||||||
|
config = await self.clients[0]['xep_0045'].get_room_config(self.muc)
|
||||||
|
values = config.get_values()
|
||||||
|
values['muc#roomconfig_persistentroom'] = False
|
||||||
|
values['muc#roomconfig_membersonly'] = True
|
||||||
|
config['values'] = values
|
||||||
|
config.reply()
|
||||||
|
config = await self.clients[0]['xep_0045'].set_room_config(self.muc, config)
|
||||||
|
|
||||||
|
# Send affiliation list including client 2
|
||||||
|
await self.clients[0]['xep_0045'].send_affiliation_list(
|
||||||
|
self.muc,
|
||||||
|
[
|
||||||
|
(self.clients[1].boundjid.bare, 'member'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
async def test_join_after_config(self):
|
||||||
|
"""Join a room after being added to the affiliation list"""
|
||||||
|
await self.test_setup_muc()
|
||||||
|
self.clients[1]['xep_0045'].join_muc(self.muc, 'client2')
|
||||||
|
await self.clients[1].wait_until('muc::%s::got_online' % self.muc)
|
||||||
|
|
||||||
|
async def test_leave(self):
|
||||||
|
"""Check that we leave properly"""
|
||||||
|
await self.test_join_after_config()
|
||||||
|
self.clients[0]['xep_0045'].leave_muc(self.muc, 'client1', 'boooring')
|
||||||
|
pres = await self.clients[1].wait_until('muc::%s::got_offline' % self.muc)
|
||||||
|
self.assertEqual(pres['status'], 'boooring')
|
||||||
|
self.assertEqual(pres['type'], 'unavailable')
|
||||||
|
|
||||||
|
|
||||||
|
async def test_kick(self):
|
||||||
|
"""Test kicking a user"""
|
||||||
|
await self.test_join_after_config()
|
||||||
|
await asyncio.gather(
|
||||||
|
self.clients[0].wait_until('muc::%s::got_offline' % self.muc),
|
||||||
|
self.clients[0]['xep_0045'].set_role(self.muc, 'client2', 'none')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestConnect)
|
71
run_integration_tests.py
Executable file
71
run_integration_tests.py
Executable file
@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from distutils.core import Command
|
||||||
|
from importlib import import_module
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def run_tests(filenames=None):
|
||||||
|
"""
|
||||||
|
Find and run all tests in the tests/ directory.
|
||||||
|
|
||||||
|
Excludes live tests (tests/live_*).
|
||||||
|
"""
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
raise ValueError('Your python version is too old to run these tests')
|
||||||
|
if not filenames:
|
||||||
|
filenames = [i for i in Path('itests').glob('test_*')]
|
||||||
|
else:
|
||||||
|
filenames = [Path(i) for i in filenames]
|
||||||
|
|
||||||
|
modules = ['.'.join(test.parts[:-1] + (test.stem,)) for test in filenames]
|
||||||
|
|
||||||
|
suites = []
|
||||||
|
for filename in modules:
|
||||||
|
module = import_module(filename)
|
||||||
|
suites.append(module.suite)
|
||||||
|
|
||||||
|
tests = unittest.TestSuite(suites)
|
||||||
|
runner = unittest.TextTestRunner(verbosity=2)
|
||||||
|
|
||||||
|
# Disable logging output
|
||||||
|
logging.basicConfig(level=100)
|
||||||
|
logging.disable(100)
|
||||||
|
|
||||||
|
result = runner.run(tests)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Add a 'test' command for setup.py
|
||||||
|
|
||||||
|
class TestCommand(Command):
|
||||||
|
|
||||||
|
user_options = []
|
||||||
|
|
||||||
|
def initialize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def finalize_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
run_tests()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = ArgumentParser(description='Run unit tests.')
|
||||||
|
parser.add_argument('tests', metavar='TEST', nargs='*', help='list of tests to run, or nothing to run them all')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
result = run_tests(args.tests)
|
||||||
|
print("<tests %s ran='%s' errors='%s' fails='%s' success='%s'/>" % (
|
||||||
|
"xmlns='http//andyet.net/protocol/tests'",
|
||||||
|
result.testsRun, len(result.errors),
|
||||||
|
len(result.failures), result.wasSuccessful()))
|
||||||
|
|
||||||
|
sys.exit(not result.wasSuccessful())
|
61
slixmpp/test/integration.py
Normal file
61
slixmpp/test/integration.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2020 Mathieu Pasquet
|
||||||
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
try:
|
||||||
|
from unittest import IsolatedAsyncioTestCase
|
||||||
|
except ImportError:
|
||||||
|
# Python < 3.8
|
||||||
|
# just to make sure the imports do not break, but
|
||||||
|
# not usable.
|
||||||
|
from unittest import TestCase as IsolatedAsyncioTestCase
|
||||||
|
from typing import (
|
||||||
|
List,
|
||||||
|
)
|
||||||
|
|
||||||
|
from slixmpp import JID
|
||||||
|
from slixmpp.clientxmpp import ClientXMPP
|
||||||
|
|
||||||
|
|
||||||
|
class SlixIntegration(IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.clients = []
|
||||||
|
self.addAsyncCleanup(self._destroy)
|
||||||
|
|
||||||
|
def envjid(self, name):
|
||||||
|
"""Get a JID from an env var"""
|
||||||
|
value = os.getenv(name)
|
||||||
|
return JID(value)
|
||||||
|
|
||||||
|
def envstr(self, name):
|
||||||
|
"""get a str from an env var"""
|
||||||
|
return os.getenv(name)
|
||||||
|
|
||||||
|
def register_plugins(self, plugins: List[str]):
|
||||||
|
"""Register plugins on all known clients"""
|
||||||
|
for plugin in plugins:
|
||||||
|
for client in self.clients:
|
||||||
|
client.register_plugin(plugin)
|
||||||
|
|
||||||
|
def add_client(self, jid: JID, password: str):
|
||||||
|
"""Register a new client"""
|
||||||
|
self.clients.append(ClientXMPP(jid, password))
|
||||||
|
|
||||||
|
async def connect_clients(self):
|
||||||
|
"""Connect all clients"""
|
||||||
|
for client in self.clients:
|
||||||
|
client.connect()
|
||||||
|
await client.wait_until('session_start')
|
||||||
|
|
||||||
|
async def _destroy(self):
|
||||||
|
"""Kill all clients"""
|
||||||
|
for client in self.clients:
|
||||||
|
client.abort()
|
@ -12,7 +12,7 @@
|
|||||||
:license: MIT, see LICENSE for more details
|
:license: MIT, see LICENSE for more details
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional, Set, Callable
|
from typing import Optional, Set, Callable, Any
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
@ -1130,3 +1130,18 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
:param exception: An unhandled exception object.
|
:param exception: An unhandled exception object.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
async def wait_until(self, event: str, timeout=30) -> Any:
|
||||||
|
"""Utility method to wake on the next firing of an event.
|
||||||
|
(Registers a disposable handler on it)
|
||||||
|
|
||||||
|
:param str event: Event to wait on.
|
||||||
|
:param int timeout: Timeout
|
||||||
|
"""
|
||||||
|
fut = asyncio.Future()
|
||||||
|
self.add_event_handler(
|
||||||
|
event,
|
||||||
|
fut.set_result,
|
||||||
|
disposable=True,
|
||||||
|
)
|
||||||
|
return await asyncio.wait_for(fut, timeout)
|
||||||
|
Loading…
Reference in New Issue
Block a user