Files
vnctalk-prosody/tests/test_07_module_load.py
Stefan-SangerandClaude Opus 4.8 54ca4e1877 fix(tests): correct module-load detection tests (test_07)
- mod_vnc_lastactivity: the jabber:iq:last handler returns <forbidden> when
  the query has no 'to'; send it to another existing user's bare JID and
  assert the result carries a 'seconds' attribute
- set body via msg["body"] and do not await message send() (returns None)
- use unique uuid room names instead of a fixed duplicated localpart
- unlock the freshly-created (locked) room with configure_muc before a
  second user joins / is affiliated
- the unregister IQ is fire-and-forget (no reply); tolerate IqTimeout rather
  than failing the kick-tracking smoke test

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
2026-07-15 17:58:02 +02:00

142 lines
6.7 KiB
Python

"""Zero-coverage module load detection.
These tests send minimal IQ/presence probes to verify that modules
which have no dedicated test coverage are at least loaded and not
crashing on entry. A module that fails to load after a Prosody upgrade
will typically return service-unavailable or item-not-found.
For modules without any IQ/presence hook, see MANUAL_TESTS.md §4.
"""
import uuid
import pytest
import slixmpp
import xml.etree.ElementTree as ET
@pytest.mark.asyncio
class TestModuleLoadDetection:
"""Smoke tests for modules that would otherwise have zero coverage."""
async def test_mod_vnc_lastactivity_loaded(self, xmpp_client, second_client):
"""mod_vnc_lastactivity hooks on jabber:iq:last and jabber:iq:batch.
A get request should return a result (not service-unavailable).
The handler requires a 'to' on the query: a jabber:iq:last get with no
target returns <forbidden>, so we query another existing user's bare JID.
"""
target = second_client.boundjid.bare
iq = xmpp_client.make_iq_get(ito=target)
ET.SubElement(iq.xml, "{jabber:iq:last}query")
try:
res = await iq.send()
assert res["type"] == "result", (
f"mod_vnc_lastactivity returned {res['type']} instead of result — module may not be loaded"
)
q = res.xml.find("{jabber:iq:last}query")
assert q is not None and q.get("seconds") is not None, (
"jabber:iq:last result missing the 'seconds' attribute"
)
except slixmpp.exceptions.IqError as e:
pytest.fail(f"mod_vnc_lastactivity returned an error ({e.condition}) — module may not be loaded")
async def test_mod_vnc_delfile_message_hook_present(self, xmpp_client, xmpp_config, second_client):
"""mod_vnc_delfile hooks on pre-message with a message-correction trigger.
We verify it doesn't crash by sending a message with <replace>.
The module has no IQ handler; we just verify no error bounces back.
"""
if xmpp_client.boundjid.bare == second_client.boundjid.bare:
pytest.skip("Need two clients for message roundtrip")
msg = xmpp_client.make_message(mto=second_client.boundjid.bare, mtype="chat")
msg["body"] = "delfile test" # slixmpp: dict-style, not msg.body
msg["id"] = "delfile-test-1"
replace = ET.SubElement(msg.xml, "{urn:xmpp:message-correct:0}replace")
replace.set("id", "original-msg-id")
try:
msg.send() # message.send() returns None, not awaitable
# If no exception and no error bounce, the module at least didn't crash.
# The actual HTTP POST to del_api_url is an async side-effect we can't observe.
except Exception as e:
pytest.fail(f"mod_vnc_delfile message hook crashed: {e}")
async def test_mod_vnc_remotemucstore_message_hook_present(self, xmpp_client, xmpp_config, second_client):
"""mod_vnc_remotemucstore hooks on message/bare and message/full.
We send a normal chat message and verify no crash.
"""
if xmpp_client.boundjid.bare == second_client.boundjid.bare:
pytest.skip("Need two clients for message roundtrip")
msg = xmpp_client.make_message(mto=second_client.boundjid.bare, mtype="chat")
msg["body"] = "remotemucstore test" # slixmpp: dict-style, not msg.body
try:
msg.send() # message.send() returns None, not awaitable
except Exception as e:
pytest.fail(f"mod_vnc_remotemucstore message hook crashed: {e}")
async def test_mod_vnc_remotemucinvite_event_handler_present(self, xmpp_client, xmpp_config, second_client):
"""mod_vnc_remotemucinvite hooks on vnc-muc-invitation event.
We trigger a mediated invite and verify no crash.
"""
muc_domain = xmpp_config.get("muc_domain")
if not muc_domain:
pytest.skip("No MUC domain configured")
if xmpp_client.boundjid.bare == second_client.boundjid.bare:
pytest.skip("Need two distinct accounts")
room_jid = f"reminv_{uuid.uuid4().hex[:8]}@{muc_domain}"
nick = "owner"
await xmpp_client.join_muc(room_jid, nick)
# unlock the freshly-created (locked) room so the invitee can join
await xmpp_client.configure_muc(room_jid)
await second_client.join_muc(room_jid, "invited")
try:
# Mediated invite
invite_msg = xmpp_client.make_message(mto=room_jid, mtype="normal")
x = ET.SubElement(invite_msg.xml, "{http://jabber.org/protocol/muc#user}x")
invite = ET.SubElement(x, "{http://jabber.org/protocol/muc#user}invite")
invite.set("to", second_client.boundjid.bare)
invite_msg.send() # message.send() returns None, not awaitable
# If no crash, the event handler is at least present.
except Exception as e:
pytest.fail(f"mod_vnc_remotemucinvite event hook crashed: {e}")
finally:
await xmpp_client.leave_muc(room_jid, nick)
await second_client.leave_muc(room_jid, "invited")
await xmpp_client.destroy_muc(room_jid)
async def test_mod_vnc_track_kicks_event_handler_present(self, xmpp_client, xmpp_config, second_client):
"""mod_vnc_track_kicks hooks on vnc-muc-kick event.
We trigger it via unregister IQ (which fires vnc-muc-kick) and verify no crash.
"""
muc_domain = xmpp_config.get("muc_domain")
if not muc_domain:
pytest.skip("No MUC domain configured")
if xmpp_client.boundjid.bare == second_client.boundjid.bare:
pytest.skip("Need two distinct accounts")
room_jid = f"trackk_{uuid.uuid4().hex[:8]}@{muc_domain}"
nick1 = "owner"
nick2 = "member"
await xmpp_client.join_muc(room_jid, nick1)
# unlock the freshly-created (locked) room before affiliating others
await xmpp_client.configure_muc(room_jid)
await xmpp_client.set_muc_affiliation(room_jid, second_client.boundjid.bare, "member")
try:
iq = second_client.make_iq_set(ito=room_jid)
ET.SubElement(iq.xml, "{xmpp:vnctalk:unregister}query")
# handle_unregister_iq fires vnc-muc-kick but sends no IQ reply, so the
# send times out by design; that still proves the handler ran without crashing.
try:
await iq.send(timeout=5)
except slixmpp.exceptions.IqTimeout:
pass
except Exception as e:
pytest.fail(f"mod_vnc_track_kicks event hook crashed: {e}")
finally:
await xmpp_client.leave_muc(room_jid, nick1)
await xmpp_client.destroy_muc(room_jid)