diff --git a/Makefile.am b/Makefile.am index f21b6eea..a7648fcb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -187,6 +187,7 @@ functionaltest_sources = \ tests/functionaltests/test_disconnect.c tests/functionaltests/test_disconnect.h \ tests/functionaltests/test_lastactivity.c tests/functionaltests/test_lastactivity.h \ tests/functionaltests/test_autoping.c tests/functionaltests/test_autoping.h \ + tests/functionaltests/test_disco.c tests/functionaltests/test_disco.h \ tests/functionaltests/functionaltests.c main_source = src/main.c diff --git a/tests/functionaltests/functionaltests.c b/tests/functionaltests/functionaltests.c index 331543ad..ad2f25eb 100644 --- a/tests/functionaltests/functionaltests.c +++ b/tests/functionaltests/functionaltests.c @@ -54,6 +54,7 @@ #include "test_disconnect.h" #include "test_lastactivity.h" #include "test_autoping.h" +#include "test_disco.h" /* Macro to wrap each test with setup/teardown functions */ #define PROF_FUNC_TEST(test) cmocka_unit_test_setup_teardown(test, init_prof_test, close_prof_test) @@ -96,12 +97,6 @@ main(int argc, char* argv[]) PROF_FUNC_TEST(ping_jid), PROF_FUNC_TEST(ping_jid_not_supported), - /* Autoping command tests - fast, no waiting */ - PROF_FUNC_TEST(autoping_set_interval), - PROF_FUNC_TEST(autoping_set_zero_disables), - PROF_FUNC_TEST(autoping_timeout_set), - PROF_FUNC_TEST(autoping_timeout_zero_disables), - /* Room discovery - XEP-0045 */ PROF_FUNC_TEST(rooms_query), @@ -116,6 +111,16 @@ main(int argc, char* argv[]) /* Last Activity - XEP-0012 */ PROF_FUNC_TEST(responds_to_last_activity_request), PROF_FUNC_TEST(last_activity_request_to_contact), + + /* Autoping command tests - fast, no waiting */ + PROF_FUNC_TEST(autoping_set_interval), + PROF_FUNC_TEST(autoping_set_zero_disables), + PROF_FUNC_TEST(autoping_timeout_set), + PROF_FUNC_TEST(autoping_timeout_zero_disables), + + /* Autoping slow tests - require sleep for timer triggers (~2s each) */ + PROF_FUNC_TEST(autoping_sends_ping_after_interval), + PROF_FUNC_TEST(autoping_server_not_supporting_ping), }; /* ============================================================ @@ -150,7 +155,7 @@ main(int argc, char* argv[]) }; /* ============================================================ - * GROUP 3: Presence, Disconnect + * GROUP 3: Presence, Disconnect, Disco * Online/away/xa/dnd/chat status management * ============================================================ */ const struct CMUnitTest group3_tests[] = { @@ -173,9 +178,15 @@ main(int argc, char* argv[]) /* Disconnect - clean session termination */ PROF_FUNC_TEST(disconnect_ends_session), - /* Autoping slow tests - require sleep for timer triggers */ - PROF_FUNC_TEST(autoping_sends_ping_after_interval), - PROF_FUNC_TEST(autoping_server_not_supporting_ping), + /* Service Discovery - XEP-0030 */ + PROF_FUNC_TEST(disco_info_shows_identity), + PROF_FUNC_TEST(disco_info_shows_features), + PROF_FUNC_TEST(disco_info_to_server), + PROF_FUNC_TEST(disco_info_to_jid), + PROF_FUNC_TEST(disco_info_not_found), + PROF_FUNC_TEST(disco_items_shows_items), + PROF_FUNC_TEST(disco_items_empty_result), + PROF_FUNC_TEST(disco_requires_connection), }; /* ============================================================ @@ -223,9 +234,9 @@ main(int argc, char* argv[]) const struct CMUnitTest* tests; size_t count; } groups[] = { - { "Group 1: Connect/Ping/Rooms/Software", group1_tests, ARRAY_SIZE(group1_tests) }, + { "Group 1: Connect/Ping/Rooms/Software/Autoping", group1_tests, ARRAY_SIZE(group1_tests) }, { "Group 2: Message/Receipts/Roster/Session", group2_tests, ARRAY_SIZE(group2_tests) }, - { "Group 3: Presence/Disconnect", group3_tests, ARRAY_SIZE(group3_tests) }, + { "Group 3: Presence/Disconnect/Disco", group3_tests, ARRAY_SIZE(group3_tests) }, { "Group 4: MUC/Carbons", group4_tests, ARRAY_SIZE(group4_tests) }, }; const int num_groups = ARRAY_SIZE(groups); diff --git a/tests/functionaltests/proftest.c b/tests/functionaltests/proftest.c index b67954f7..37813327 100644 --- a/tests/functionaltests/proftest.c +++ b/tests/functionaltests/proftest.c @@ -362,7 +362,6 @@ close_prof_test(void **state) fd = 0; child_pid = 0; } - _cleanup_dirs(); if (config_orig) { setenv("XDG_CONFIG_HOME", config_orig, 1); diff --git a/tests/functionaltests/test_autoping.c b/tests/functionaltests/test_autoping.c index 55c99df3..1e1a1332 100644 --- a/tests/functionaltests/test_autoping.c +++ b/tests/functionaltests/test_autoping.c @@ -49,7 +49,7 @@ autoping_sends_ping_after_interval(void** state) { /* * This test verifies that autoping sends a ping IQ after the configured - * interval. We set a short interval (2 seconds) and verify the ping is sent. + * interval. We set a short interval (1 second) and verify the ping is sent. */ // Register disco#info response with ping support @@ -69,15 +69,12 @@ autoping_sends_ping_after_interval(void** state) prof_connect(); - // Wait for disco exchange - sleep(2); - // Set short autoping interval - prof_input("/autoping set 2"); - assert_true(prof_output_exact("Autoping interval set to 2 seconds.")); + prof_input("/autoping set 1"); + assert_true(prof_output_exact("Autoping interval set to 1 seconds.")); // Wait for autoping to trigger (interval + some buffer) - sleep(4); + sleep(2); // Verify ping was sent (no 'to' attribute means server ping) assert_true(stbbr_received( @@ -105,15 +102,12 @@ autoping_server_not_supporting_ping(void** state) prof_connect(); - // Wait for disco exchange - sleep(2); - // Set short autoping interval - prof_input("/autoping set 2"); - assert_true(prof_output_exact("Autoping interval set to 2 seconds.")); + prof_input("/autoping set 1"); + assert_true(prof_output_exact("Autoping interval set to 1 seconds.")); // Wait for autoping to trigger - sleep(4); + sleep(2); // Should show error about ping not being supported assert_true(prof_output_regex("Server ping not supported")); diff --git a/tests/functionaltests/test_disco.c b/tests/functionaltests/test_disco.c new file mode 100644 index 00000000..dfafdc09 --- /dev/null +++ b/tests/functionaltests/test_disco.c @@ -0,0 +1,250 @@ +/* + * 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.")); +} diff --git a/tests/functionaltests/test_disco.h b/tests/functionaltests/test_disco.h new file mode 100644 index 00000000..4b98ed55 --- /dev/null +++ b/tests/functionaltests/test_disco.h @@ -0,0 +1,13 @@ +/* test_disco.h + * + * Functional tests for /disco command (XEP-0030 Service Discovery) + */ + +void disco_info_shows_identity(void **state); +void disco_info_shows_features(void **state); +void disco_info_to_server(void **state); +void disco_info_to_jid(void **state); +void disco_info_not_found(void **state); +void disco_items_shows_items(void **state); +void disco_items_empty_result(void **state); +void disco_requires_connection(void **state);