/* * test_disco.c * * Functional tests for /disco command (XEP-0030 Service Discovery). * Tests cover: * - /disco info [jid] - query entity capabilities and features * - /disco items [jid] - query entity items/services * * XEP-0030: https://xmpp.org/extensions/xep-0030.html */ #include #include "prof_cmocka.h" #include #include #include #include #include "proftest.h" void disco_info_shows_identity(void **state) { /* * Test that /disco info displays identity information correctly. * Identity includes: name, type, category */ stbbr_for_query("http://jabber.org/protocol/disco#info", "" "" "" "" "" ); prof_connect(); prof_input("/disco info"); prof_timeout(10); assert_true(prof_output_exact("Service discovery info for localhost")); assert_true(prof_output_exact("Identities")); assert_true(prof_output_regex("Prosody.*im.*server")); prof_timeout_reset(); } void disco_info_shows_features(void **state) { /* * Test that /disco info displays feature list. */ stbbr_for_query("http://jabber.org/protocol/disco#info", "" "" "" "" "" "" "" "" ); prof_connect(); prof_input("/disco info"); prof_timeout(10); assert_true(prof_output_exact("Features:")); assert_true(prof_output_exact("urn:xmpp:ping")); assert_true(prof_output_exact("http://jabber.org/protocol/disco#info")); prof_timeout_reset(); } void disco_info_to_server(void **state) { /* * Test that /disco info without arguments queries the server (domainpart). */ stbbr_for_query("http://jabber.org/protocol/disco#info", "" "" "" "" "" ); prof_connect(); prof_input("/disco info"); /* Verify request was sent to server (localhost) */ prof_timeout(10); assert_true(stbbr_received( "" "" "" )); prof_timeout_reset(); } void disco_info_to_jid(void **state) { /* * Test that /disco info queries the specified JID. */ stbbr_for_query("http://jabber.org/protocol/disco#info", "" "" "" "" "" "" ); prof_connect(); prof_input("/disco info conference.localhost"); prof_timeout(10); /* Verify request was sent to specified JID */ assert_true(stbbr_received( "" "" "" )); assert_true(prof_output_exact("Service discovery info for conference.localhost")); prof_timeout_reset(); } void disco_info_not_found(void **state) { /* * Test error handling when disco info returns item-not-found. */ stbbr_for_query("http://jabber.org/protocol/disco#info", "" "" "" "" "" ); prof_connect(); prof_input("/disco info unknown.localhost"); prof_timeout(10); assert_true(prof_output_regex("Service discovery failed.*item-not-found")); prof_timeout_reset(); } void disco_items_shows_items(void **state) { /* * Test that /disco items displays items list with JID and name. */ stbbr_for_query("http://jabber.org/protocol/disco#items", "" "" "" "" "" "" "" ); prof_connect(); prof_input("/disco items"); prof_timeout(10); assert_true(prof_output_exact("Service discovery items for localhost:")); assert_true(prof_output_regex("conference.localhost.*Chat Rooms")); assert_true(prof_output_regex("pubsub.localhost.*Publish-Subscribe")); assert_true(prof_output_regex("proxy.localhost.*SOCKS5 Bytestreams")); /* Verify IQ was sent with correct id */ assert_true(stbbr_received( "" "" "" )); prof_timeout_reset(); } void disco_items_empty_result(void **state) { /* * Test that /disco items handles empty result gracefully. * Per XEP-0030: "if an entity has no associated items, it MUST return * an empty element (rather than an error)" */ stbbr_for_query("http://jabber.org/protocol/disco#items", "" "" "" ); prof_connect(); prof_input("/disco items"); prof_timeout(10); assert_true(prof_output_exact("No service discovery items for localhost")); prof_timeout_reset(); } void disco_requires_connection(void **state) { /* * Test that /disco info and /disco items require an active connection. * First test without any connection, then after connect/disconnect. */ /* Without connection */ prof_input("/disconnect"); assert_true(prof_output_exact("You are not currently connected.")); prof_input("/disco info"); assert_true(prof_output_exact("You are not currently connected.")); prof_input("/disco items"); assert_true(prof_output_exact("You are not currently connected.")); /* After connect and disconnect */ prof_connect(); prof_input("/disconnect"); assert_true(prof_output_exact("stabber@localhost logged out successfully.")); prof_input("/disco info"); assert_true(prof_output_exact("You are not currently connected.")); prof_input("/disco items"); assert_true(prof_output_exact("You are not currently connected.")); } void disco_items_to_jid(void **state) { /* * Test that /disco items queries the specified JID. */ stbbr_for_query("http://jabber.org/protocol/disco#items", "" "" "" "" "" "" ); prof_connect(); prof_input("/disco items conference.localhost"); prof_timeout(10); /* Verify request was sent to specified JID */ assert_true(stbbr_received( "" "" "" )); assert_true(prof_output_exact("Service discovery items for conference.localhost:")); assert_true(prof_output_regex("room1@conference.localhost.*General Chat")); prof_timeout_reset(); } void disco_info_empty_result(void **state) { /* * Test that /disco info handles empty result (no identities/features). * This can happen with minimal server configurations. */ stbbr_for_query("http://jabber.org/protocol/disco#info", "" "" "" ); prof_connect(); prof_input("/disco info minimal.localhost"); prof_timeout(10); /* Verify request was sent */ assert_true(stbbr_received( "" "" "" )); /* Empty result should not crash and should not show "Service discovery info" */ prof_timeout_reset(); } void disco_info_multiple_identities(void **state) { /* * Test that /disco info displays multiple identities correctly. * Entities can have multiple identities (e.g., server + gateway). */ stbbr_for_query("http://jabber.org/protocol/disco#info", "" "" "" "" "" "" "" ); prof_connect(); prof_input("/disco info gateway.localhost"); prof_timeout(10); assert_true(prof_output_exact("Service discovery info for gateway.localhost")); assert_true(prof_output_exact("Identities")); assert_true(prof_output_regex("IRC Gateway.*irc.*gateway")); assert_true(prof_output_regex("Room Directory.*chatroom.*directory")); assert_true(prof_output_regex("Ad-Hoc Commands.*command-node.*automation")); prof_timeout_reset(); } void disco_info_without_name(void **state) { /* * Test that /disco info handles identity without name attribute. * Per XEP-0030: name is OPTIONAL, only category and type are REQUIRED. */ stbbr_for_query("http://jabber.org/protocol/disco#info", "" "" "" "" "" ); prof_connect(); prof_input("/disco info"); prof_timeout(10); assert_true(prof_output_exact("Service discovery info for localhost")); assert_true(prof_output_exact("Identities")); /* Should show type and category even without name */ assert_true(prof_output_regex("im.*server")); prof_timeout_reset(); } void disco_items_without_name(void **state) { /* * Test that /disco items handles items without name attribute. * Per XEP-0030: name is OPTIONAL for items, only jid is REQUIRED. */ stbbr_for_query("http://jabber.org/protocol/disco#items", "" "" "" "" "" "" "" ); prof_connect(); prof_input("/disco items"); prof_timeout(10); assert_true(prof_output_exact("Service discovery items for localhost:")); /* Items without name should still display their JID */ assert_true(prof_output_exact("conference.localhost")); assert_true(prof_output_regex("pubsub.localhost.*PubSub Service")); assert_true(prof_output_exact("upload.localhost")); prof_timeout_reset(); } void disco_info_service_unavailable(void **state) { /* * Test error handling when disco info returns service-unavailable. */ stbbr_for_query("http://jabber.org/protocol/disco#info", "" "" "" "" "" ); prof_connect(); prof_input("/disco info offline.localhost"); prof_timeout(10); assert_true(prof_output_regex("Service discovery failed.*service-unavailable")); prof_timeout_reset(); }