summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml15
-rw-r--r--itests/__init__.py0
-rw-r--r--itests/test_basic_connect_and_message.py28
-rw-r--r--itests/test_muc.py78
-rwxr-xr-xrun_integration_tests.py71
-rw-r--r--slixmpp/test/integration.py61
-rw-r--r--slixmpp/xmlstream/xmlstream.py17
7 files changed, 269 insertions, 1 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e3daa539..3aa76989 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,6 +13,21 @@ test:
- pip3 install emoji aiohttp
- ./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:
stage: trigger
tags:
diff --git a/itests/__init__.py b/itests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/itests/__init__.py
diff --git a/itests/test_basic_connect_and_message.py b/itests/test_basic_connect_and_message.py
new file mode 100644
index 00000000..79ad37be
--- /dev/null
+++ b/itests/test_basic_connect_and_message.py
@@ -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)
diff --git a/itests/test_muc.py b/itests/test_muc.py
new file mode 100644
index 00000000..3dc91955
--- /dev/null
+++ b/itests/test_muc.py
@@ -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)
diff --git a/run_integration_tests.py b/run_integration_tests.py
new file mode 100755
index 00000000..9f670b5c
--- /dev/null
+++ b/run_integration_tests.py
@@ -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())
diff --git a/slixmpp/test/integration.py b/slixmpp/test/integration.py
new file mode 100644
index 00000000..d15019cc
--- /dev/null
+++ b/slixmpp/test/integration.py
@@ -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()
diff --git a/slixmpp/xmlstream/xmlstream.py b/slixmpp/xmlstream/xmlstream.py
index af494903..066d84df 100644
--- a/slixmpp/xmlstream/xmlstream.py
+++ b/slixmpp/xmlstream/xmlstream.py
@@ -12,7 +12,7 @@
:license: MIT, see LICENSE for more details
"""
-from typing import Optional, Set, Callable
+from typing import Optional, Set, Callable, Any
import functools
import logging
@@ -1130,3 +1130,18 @@ class XMLStream(asyncio.BaseProtocol):
:param exception: An unhandled exception object.
"""
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)