- Add AGENTS.md with repo-specific conventions, build steps, and quirks - Add PATCHES_AND_MODULES.md documenting every upstream deviation - Add tests/ with pytest/slixmpp integration suite for core, MUC, vnctalk extensions, and infrastructure verification - Include pytest.ini and .gitignore Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
214 lines
7.4 KiB
Python
214 lines
7.4 KiB
Python
"""Test configuration and shared fixtures for vnctalk-prosody verification."""
|
|
import asyncio
|
|
import os
|
|
import ssl
|
|
import pytest
|
|
import slixmpp
|
|
from slixmpp.exceptions import IqError, IqTimeout
|
|
|
|
|
|
class VNCXmppClient(slixmpp.ClientXMPP):
|
|
"""Async-friendly XMPP client for vnctalk tests."""
|
|
|
|
def __init__(self, jid, password, host=None, port=5222, use_ssl=True):
|
|
super().__init__(jid, password)
|
|
self.connected_event = asyncio.Event()
|
|
self.disconnected_event = asyncio.Event()
|
|
self.session_started_event = asyncio.Event()
|
|
self.add_event_handler("session_start", self._on_session_start)
|
|
self.add_event_handler("disconnected", self._on_disconnected)
|
|
|
|
# Force connection parameters if provided
|
|
if host:
|
|
self.connect_address = (host, port)
|
|
else:
|
|
self.connect_address = None
|
|
|
|
self.use_ssl = use_ssl
|
|
|
|
def _on_session_start(self, event):
|
|
self.session_started_event.set()
|
|
|
|
def _on_disconnected(self, event):
|
|
self.disconnected_event.set()
|
|
|
|
async def async_connect(self, timeout=30):
|
|
if self.connect_address:
|
|
self.use_ssl = self.use_ssl
|
|
# slixmpp register_plugins is called automatically
|
|
if self.use_ssl:
|
|
await self.connect(self.connect_address, use_ssl=True)
|
|
else:
|
|
await self.connect(self.connect_address, use_ssl=False)
|
|
else:
|
|
await self.connect()
|
|
self.process(timeout=1)
|
|
await asyncio.wait_for(self.session_started_event.wait(), timeout=timeout)
|
|
|
|
async def async_disconnect(self):
|
|
self.disconnect()
|
|
await asyncio.wait_for(self.disconnected_event.wait(), timeout=10)
|
|
|
|
async def enable_carbons(self):
|
|
iq = self.make_iq_set()
|
|
iq.append(slixmpp.etree.Element("{urn:xmpp:carbons:2}enable"))
|
|
await iq.send()
|
|
|
|
async def get_mam_prefs(self):
|
|
iq = self.make_iq_get()
|
|
iq.append(slixmpp.etree.Element("{urn:xmpp:mam:2}prefs"))
|
|
res = await iq.send()
|
|
return res
|
|
|
|
async def query_mam(self, with_jid=None, start=None, end=None):
|
|
iq = self.make_iq_set()
|
|
query = slixmpp.etree.SubElement(iq.xml, "{urn:xmpp:mam:2}query")
|
|
if with_jid:
|
|
x = slixmpp.etree.SubElement(query, "{jabber:x:data}x")
|
|
x.set("type", "submit")
|
|
field = slixmpp.etree.SubElement(x, "{jabber:x:data}field")
|
|
field.set("var", "with")
|
|
value = slixmpp.etree.SubElement(field, "{jabber:x:data}value")
|
|
value.text = with_jid
|
|
res = await iq.send()
|
|
return res
|
|
|
|
async def get_vcard(self, to=None):
|
|
iq = self.make_iq_get(to=to)
|
|
iq.append(slixmpp.etree.Element("{vcard-temp}vCard"))
|
|
res = await iq.send()
|
|
return res
|
|
|
|
async def set_vcard(self, vcard_xml):
|
|
iq = self.make_iq_set()
|
|
iq.append(vcard_xml)
|
|
res = await iq.send()
|
|
return res
|
|
|
|
async def disco_info(self, to=None):
|
|
iq = self.make_iq_get(to=to)
|
|
iq.append(slixmpp.etree.Element("{http://jabber.org/protocol/disco#info}query"))
|
|
res = await iq.send()
|
|
return res
|
|
|
|
async def disco_items(self, to=None):
|
|
iq = self.make_iq_get(to=to)
|
|
iq.append(slixmpp.etree.Element("{http://jabber.org/protocol/disco#items}query"))
|
|
res = await iq.send()
|
|
return res
|
|
|
|
|
|
def pytest_addoption(parser):
|
|
parser.addoption(
|
|
"--xmpp-host", action="store", default=os.getenv("XMPP_HOST", "localhost"),
|
|
help="XMPP server hostname/IP"
|
|
)
|
|
parser.addoption(
|
|
"--xmpp-port", action="store", type=int, default=int(os.getenv("XMPP_PORT", "5222")),
|
|
help="XMPP C2S port"
|
|
)
|
|
parser.addoption(
|
|
"--xmpp-jid", action="store", default=os.getenv("XMPP_JID", ""),
|
|
help="Test account JID"
|
|
)
|
|
parser.addoption(
|
|
"--xmpp-password", action="store", default=os.getenv("XMPP_PASSWORD", ""),
|
|
help="Test account password"
|
|
)
|
|
parser.addoption(
|
|
"--xmpp-domain", action="store", default=os.getenv("XMPP_DOMAIN", "example.com"),
|
|
help="XMPP domain"
|
|
)
|
|
parser.addoption(
|
|
"--bosh-url", action="store", default=os.getenv("BOSH_URL", ""),
|
|
help="BOSH URL to test (e.g. http://localhost:5280/http-bind)"
|
|
)
|
|
parser.addoption(
|
|
"--ws-url", action="store", default=os.getenv("WS_URL", ""),
|
|
help="WebSocket URL to test (e.g. ws://localhost:5280/xmpp-websocket)"
|
|
)
|
|
parser.addoption(
|
|
"--rest-url", action="store", default=os.getenv("REST_URL", ""),
|
|
help="mod_http_rest URL (e.g. http://localhost:5280/rest)"
|
|
)
|
|
parser.addoption(
|
|
"--muc-domain", action="store", default=os.getenv("MUC_DOMAIN", ""),
|
|
help="MUC component domain (e.g. conference.example.com)"
|
|
)
|
|
parser.addoption(
|
|
"--admin-telnet-host", action="store", default=os.getenv("ADMIN_TELNET_HOST", "127.0.0.1"),
|
|
help="Admin telnet host"
|
|
)
|
|
parser.addoption(
|
|
"--admin-telnet-port", action="store", type=int, default=int(os.getenv("ADMIN_TELNET_PORT", "5582")),
|
|
help="Admin telnet port"
|
|
)
|
|
parser.addoption(
|
|
"--skip-live", action="store_true", default=False,
|
|
help="Skip tests that require a live XMPP connection"
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def xmpp_config(request):
|
|
return {
|
|
"host": request.config.getoption("--xmpp-host"),
|
|
"port": request.config.getoption("--xmpp-port"),
|
|
"jid": request.config.getoption("--xmpp-jid"),
|
|
"password": request.config.getoption("--xmpp-password"),
|
|
"domain": request.config.getoption("--xmpp-domain"),
|
|
"bosh_url": request.config.getoption("--bosh-url"),
|
|
"ws_url": request.config.getoption("--ws-url"),
|
|
"rest_url": request.config.getoption("--rest-url"),
|
|
"muc_domain": request.config.getoption("--muc-domain"),
|
|
"admin_telnet_host": request.config.getoption("--admin-telnet-host"),
|
|
"admin_telnet_port": request.config.getoption("--admin-telnet-port"),
|
|
"skip_live": request.config.getoption("--skip-live"),
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
async def xmpp_client(xmpp_config):
|
|
"""Yield a connected XMPP client."""
|
|
cfg = xmpp_config
|
|
if cfg["skip_live"] or not cfg["jid"] or not cfg["password"]:
|
|
pytest.skip("Live XMPP tests disabled or credentials missing")
|
|
|
|
client = VNCXmppClient(
|
|
cfg["jid"], cfg["password"],
|
|
host=cfg["host"], port=cfg["port"], use_ssl=True
|
|
)
|
|
try:
|
|
await client.async_connect(timeout=30)
|
|
yield client
|
|
finally:
|
|
if client.session_started_event.is_set():
|
|
await client.async_disconnect()
|
|
|
|
|
|
@pytest.fixture
|
|
async def second_client(xmpp_config):
|
|
"""Yield a second connected client (useful for MUC/carbons tests)."""
|
|
cfg = xmpp_config
|
|
if cfg["skip_live"] or not cfg["jid"] or not cfg["password"]:
|
|
pytest.skip("Live XMPP tests disabled or credentials missing")
|
|
|
|
# Derive a second resource by appending _test2
|
|
jid = cfg["jid"]
|
|
if "/" in jid:
|
|
bare, _ = jid.split("/", 1)
|
|
else:
|
|
bare = jid
|
|
second_jid = f"{bare}/test2"
|
|
|
|
client = VNCXmppClient(
|
|
second_jid, cfg["password"],
|
|
host=cfg["host"], port=cfg["port"], use_ssl=True
|
|
)
|
|
try:
|
|
await client.async_connect(timeout=30)
|
|
yield client
|
|
finally:
|
|
if client.session_started_event.is_set():
|
|
await client.async_disconnect()
|