- test_02_muc.py: add test_muc_mam_presence_not_archived (negative assertion that presence stanzas are excluded from MUC MAM) and test_hidden_lib_rejects_public_override (config form absence + raw override submission with xfail for admin accounts) - test_06_postgres.py: add TestPostgresStoreUserInversion which injects via REST and queries the archive table directly to assert the 'user' column is the sender; add TestPostgresKickStore and TestPostgresActivityStore for mod_vnc_track_kicks and mod_vnc_lastactivity store tables - test_07_module_load.py: new file with smoke tests for the five previously zero-coverage modules (lastactivity IQ, delfile message hook, remotemucstore message hook, remotemucinvite event, track_kicks event) - MANUAL_TESTS.md: expand mod_auth_http_async section with mock-HTTP-server procedure; add container build verification steps - AUDIT.md: revise verdict to 'adequate for upgrade safety'; update all coverage tables to reflect 55 tests across 7 files Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
11 KiB
Manual Tests — VNCtalk Prosody
Some behavioral contracts cannot be verified through the XMPP client API or HTTP endpoints. They require server-side inspection, federation, or direct file/database access inside the running container.
This document lists those contracts and provides step-by-step manual verification procedures.
§ 1 — Patch contracts not testable via XMPP
1.1 mod_mam.lua — store_user inversion for REST messages
Contract: When a message is injected via POST /rest, the store_user variable is set to the sender (orig_from), not the recipient. This inverts normal C2S message archiving where store_user is the recipient.
Why automated test is hard: The test can verify the message appears in MAM, but it cannot distinguish whether it was archived under the sender's user row or the recipient's user row without direct database inspection.
Manual verification:
- Ensure Prosody is configured with
default_storage = "sql"and PostgreSQL. - Connect as
user1@domainanduser2@domain. POST /resta message:<message id="rest-test-1" from="user1@domain" to="user2@domain" type="chat"> <body>manual test</body> </message>- Query the
prosodyarchive(or host-prefixed archive) table:SELECT "user", "with", value FROM prosodyarchive WHERE key = 'rest-test-1'; - Expected: The
"user"column isuser1(the sender), notuser2(the recipient). The"with"column isuser2@domain. - If
"user"isuser2, thevnc_reststore-user inversion patch is missing.
1.2 moduleapi.lua — open_host_store cross-component store opening
Contract: A module running inside a MUC component can call module:open_host_store(mainHost, "private") and receive a valid store handle. The entire FCM push pipeline (mod_vnc_muc_fcm) depends on this.
Why automated test is hard: slixmpp cannot observe whether a Lua module:open_host_store() call throws or succeeds. The only observable effect is downstream FCM push delivery, which requires an actual FCM proxy and device tokens.
Manual verification:
- Enable debug logging in Prosody:
log = { debug = "/var/log/prosody/prosody.log" } - Restart the server with
mod_vnc_muc_fcmloaded on the MUC component. - Look for these log lines:
mod_vnc_muc_fcm: info founs storage_host: <mainHost> mod_vnc_muc_fcm: info private/vcard_store are now <table>/... - Expected: The module logs success when opening stores on the main virtual host.
- If the log shows
private/vcard_store nil/nilor a Lua error traceback, theopen_host_storepatch is missing.
1.3 muc.lib.lua — Broadcast to offline remote affiliates
Contract: After broadcasting to all online occupants, the broadcast method iterates over _affiliations and sends the stanza to any affiliated JIDs whose domain is not hosted locally.
Why automated test is hard: Requires a federated deployment with at least two Prosody instances and S2S connectivity. The test environment is typically a single server.
Manual verification:
- Set up two Prosody servers (local:
domainA, remote:domainB) with S2S enabled. - On
domainA, create a MUC room and grantuser@domainBmember affiliation. - Ensure
user@domainBis not joined to the room. - From an occupant on
domainA, send a groupchat message. - On
domainB's server, inspect the logs or usemod_debugto verify the message stanza was received over S2S. - Expected: The remote user receives the MUC message despite not being in the room.
- If the message is not received, the offline-affiliate broadcast patch is missing.
1.4 mod_admin_telnet.lua — Bound to all interfaces
Contract: The admin console binds to "*" (all interfaces), not "127.0.0.1".
Why automated test is weak: The test only connects to 127.0.0.1:5582. It cannot verify binding on other interfaces without a second network namespace or host.
Manual verification:
- From a host/container with network access to the Prosody container's non-loopback IP:
telnet <prosody-container-ip> 5582 - Expected: Connection succeeds and Prosody banner is displayed.
- If connection refused, the patch binding to
"*"is missing.
1.5 portmanager.lua — network_default_read_size > 4096
Contract: The socket read buffer size is taken from network_default_read_size config (8192 in VNCtalk), not hardcoded 4096.
Why automated test is indirect: The large-stanza test in test_05_patches.py can fail for other reasons (MTU, BOSH chunking, TLS record limits). A definitive check requires observing the actual buffer size in the running process.
Manual verification:
- Inside the Prosody container, run:
or inspect
netstat -tunapl | grep prosody/proc/<pid>/fdinfo/<fd>for the C2S socket. - Alternatively, verify the config is rendered correctly:
grep network_default_read_size /etc/prosody/prosody.cfg.lua - Expected: Output shows
network_default_read_size = 8192. - Send a stanza > 8192 bytes and capture with tcpdump:
tcpdump -i any -s 0 -w /tmp/large.pcap port 5222 - Open in Wireshark and verify the full stanza appears in a single TCP segment (or at least is not truncated at 4096 bytes).
§ 2 — Custom module contracts requiring external infrastructure
2.1 mod_auth_http_async — HTTP auth delegation (Priority 4 from AUDIT)
Contract: All client authentication requests are forwarded to http_auth_url with a Basic header containing base64(username@host:password). user_exists always returns true. There is no local password database fallback.
Why automated test is hard: Changing Prosody's http_auth_url at runtime requires a config reload or restart, which the test suite cannot do. Without controlling the auth backend, we cannot verify delegation vs. fallback.
Manual verification:
- Start a mock HTTP auth server (e.g. Python
http.serverornc) on a free port:python3 -c " import http.server, base64 class H(http.server.BaseHTTPRequestHandler): def do_GET(self): auth = self.headers.get('Authorization', '') if auth.startswith('Basic '): creds = base64.b64decode(auth[6:]).decode() print(f'AUTH: {creds}') if creds == 'user@domain.com:correctpass': self.send_response(200) else: self.send_response(401) else: self.send_response(401) self.end_headers() http.server.HTTPServer(('', 9999), H).serve_forever() " - Point Prosody at the mock server by editing
config/prosody.cfg.lua.template:http_auth_url = "http://localhost:9999/auth" - Rebuild the image and start the container.
- Attempt XMPP login with wrong credentials (
wrongpass). - Expected: The mock server logs the request with
Authorization: Basic ...and returns 401. Prosody rejects the XMPP auth. - Attempt XMPP login with correct credentials (
correctpass). - Expected: Backend returns 200. Prosody accepts the XMPP auth.
- Stop the mock server (so the auth URL is unreachable).
- Attempt XMPP login again.
- Expected: Prosody rejects auth (not accept with a local fallback). If Prosody accepts auth when the backend is down, the module has a dangerous fallback.
- Try to authenticate with a user that does not exist in the backend.
- Expected: Prosody still forwards the request (because
user_existsreturnstrue). The backend decides whether to accept or reject. If Prosody rejects before contacting the backend,user_existsis not returningtrue.
2.2 mod_vnc_fcm / mod_vnc_muc_fcm — FCM push delivery
Contract: When a message is sent and the recipient has no active non-hibernated session, an HTTP POST is made to fcm_api_url with the FCM token.
Manual verification:
- Register an FCM token via the vnctalk IQ endpoint:
<iq type="set" id="reg1"> <add xmlns="xmpp:vnctalk:fcm"> <fcm device="test-device" token="test-token" os="android"/> </add> </iq> - Ensure the recipient client is disconnected or hibernated.
- Send a message to the recipient.
- Inspect Prosody logs or run an HTTP mock server at
fcm_api_url. - Expected: Prosody POSTs to the FCM URL with a JSON body containing the token and message payload.
- If no HTTP request is made, the FCM module or
open_host_storepatch is broken.
2.3 mod_vnc_vcard_avatar — Avatar HTTP upload side-effect
Contract: When a vCard with PHOTO is set, the module HTTP-PUTs the decoded image to avatar_upload_url.
Manual verification:
- Configure
avatar_upload_urlto a local HTTP server or usetcpdump. - Send a vCard IQ-set with a PHOTO binval.
- Expected: An HTTP PUT request is observed at the upload URL with
Content-Type: image/png(or the photo type). - If no request is observed, the avatar upload trigger is broken.
2.4 mod_vnc_remotemucstore — Remote MUC archiving
Contract: Messages from a federated (remote) MUC are archived in the local muc_remote store.
Manual verification:
- Join a remote MUC room from a local account.
- Ensure messages are exchanged.
- Query PostgreSQL:
SELECT * FROM prosodymuc_remote WHERE host = '<local_domain>'; - Expected: Rows exist with the remote MUC messages.
- If empty, the remote MUC archiving is not working.
§ 3 — Healthcheck script
3.1 healthcheck.sh — Exit code 2 on cert mismatch
Contract: When /etc/tls-update/tls.crt differs from /etc/prosody/certs/prosody-ssl.pem, the script exits 2 (not 1).
Manual verification:
- Inside the running container:
cp /etc/prosody/certs/prosody-ssl.pem /tmp/original.pem echo "different-cert-data" > /etc/tls-update/tls.crt /vnc/config/healthcheck.sh; echo "Exit code: $?" - Expected: Exit code is
2. - Restore the original cert:
cp /tmp/original.pem /etc/tls-update/tls.crt - Run healthcheck again:
/vnc/config/healthcheck.sh; echo "Exit code: $?" - Expected: Exit code is
0(or 1 if prosody admin telnet is down).
§ 4 — Container build verification
4.1 Patches are actually applied in the image
Contract: The Dockerfile RUN cp block copies each patch over the upstream source. If a patch file is renamed or a new patch is added without updating the Dockerfile, the upstream file remains unpatched.
Manual verification:
- Build the image:
docker build -t vnctalk-prosody:test . - Run a shell in the image:
docker run --rm -it vnctalk-prosody:test sh - Verify each patched file contains the VNCtalk-specific code:
grep "vnc-rest-message" /usr/local/lib/prosody/modules/mod_mam/mod_mam.lua grep "open_host_store" /usr/local/lib/prosody/core/moduleapi.lua grep "vnc-muc-kick" /usr/local/lib/prosody/modules/muc/register.lib.lua grep "dumpTable" /usr/local/lib/prosody/modules/muc/muc.lib.lua - Expected: All greps return matches.
- If any grep is empty, that patch was not copied during the build.