diff --git a/meson.build b/meson.build
index e10168b5d..ee10f68d0 100644
--- a/meson.build
+++ b/meson.build
@@ -682,6 +682,7 @@ if get_option('tests')
'tests/unittests/command/test_cmd_roster.c',
'tests/unittests/command/test_cmd_ac.c',
'tests/unittests/command/test_cmd_disconnect.c',
+ 'tests/unittests/command/test_cmd_mucping.c',
'tests/unittests/plugins/test_callbacks.c',
'tests/unittests/plugins/test_plugins_disco.c',
'tests/unittests/unittests.c',
diff --git a/profanity.doap b/profanity.doap
index fa93ec17d..bb188d403 100644
--- a/profanity.doap
+++ b/profanity.doap
@@ -463,7 +463,18 @@
-
+
+
+
+
+ complete
+ 1.1.0
+ DEV
+
+
+
+
+
diff --git a/src/command/cmd_ac.c b/src/command/cmd_ac.c
index 44798f95b..f6ad8caa1 100644
--- a/src/command/cmd_ac.c
+++ b/src/command/cmd_ac.c
@@ -226,6 +226,7 @@ static Autocomplete script_show_ac;
static Autocomplete console_ac;
static Autocomplete console_msg_ac;
static Autocomplete autoping_ac;
+static Autocomplete mucping_ac;
static Autocomplete plugins_ac;
static Autocomplete plugins_load_ac;
static Autocomplete plugins_unload_ac;
@@ -386,6 +387,7 @@ static Autocomplete* all_acs[] = {
&console_ac,
&console_msg_ac,
&autoping_ac,
+ &mucping_ac,
&plugins_ac,
&filepath_ac,
&blocked_ac,
@@ -1035,6 +1037,9 @@ cmd_ac_init(void)
autocomplete_add(autoping_ac, "set");
autocomplete_add(autoping_ac, "timeout");
+ autocomplete_add(mucping_ac, "set");
+ autocomplete_add(mucping_ac, "timeout");
+
autocomplete_add(plugins_ac, "install");
autocomplete_add(plugins_ac, "update");
autocomplete_add(plugins_ac, "uninstall");
@@ -1855,6 +1860,7 @@ _cmd_ac_complete_params(ProfWin* window, const char* const input, gboolean previ
{ "/disco", disco_ac },
{ "/room", room_ac },
{ "/autoping", autoping_ac },
+ { "/mucping", mucping_ac },
{ "/mainwin", winpos_ac },
{ "/inputwin", winpos_ac },
};
diff --git a/src/command/cmd_defs.c b/src/command/cmd_defs.c
index 9e13450d8..4fe028fdd 100644
--- a/src/command/cmd_defs.c
+++ b/src/command/cmd_defs.c
@@ -1963,6 +1963,22 @@ static const struct cmd_t command_defs[] = {
{ "timeout ", "Seconds to wait for autoping responses, after which the connection is considered broken." })
},
+ { CMD_PREAMBLE("/mucping",
+ parse_args, 2, 2, &cons_mucping_setting)
+ CMD_MAINFUNC(cmd_mucping)
+ CMD_TAGS(
+ CMD_TAG_CONNECTION)
+ CMD_SYN(
+ "/mucping set ",
+ "/mucping timeout ")
+ CMD_DESC(
+ "Set the interval and timeout for MUC self-pings (XEP-0410). Recommended interval is between 300 and 900 seconds, with a timeout of 10 to 60 seconds. "
+ "Note that the interval must be greater than the timeout.")
+ CMD_ARGS(
+ { "set ", "Number of seconds of idle room activity before sending a ping, a value of 0 disables MUC self-pings." },
+ { "timeout ", "Seconds to wait for MUC self-ping responses, after which the room connection is considered disconnected. A value of 0 disables MUC self-pings entirely." })
+ },
+
{ CMD_PREAMBLE("/ping",
parse_args, 0, 1, NULL)
CMD_MAINFUNC(cmd_ping)
diff --git a/src/command/cmd_funcs.c b/src/command/cmd_funcs.c
index 2a9b47e2b..d7a31167a 100644
--- a/src/command/cmd_funcs.c
+++ b/src/command/cmd_funcs.c
@@ -6297,7 +6297,7 @@ cmd_reconnect(ProfWin* window, const char* const command, gchar** args)
} else if (strtoi_range(value, &intval, 0, INT_MAX, &err_msg)) {
prefs_set_reconnect(intval);
if (intval == 0) {
- cons_show("Reconnect disabled.", intval);
+ cons_show("Reconnect disabled.");
} else {
cons_show("Reconnect interval set to %d seconds.", intval);
}
@@ -6355,6 +6355,70 @@ cmd_autoping(ProfWin* window, const char* const command, gchar** args)
return TRUE;
}
+gboolean
+cmd_mucping(ProfWin* window, const char* const command, gchar** args)
+{
+ char* cmd = args[0];
+ char* value = args[1];
+
+ if (g_strcmp0(cmd, "set") == 0) {
+ int intval = 0;
+ auto_char char* err_msg = NULL;
+ gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
+ if (res) {
+ gint timeout = prefs_get_muc_ping_timeout();
+ if (intval != 0 && timeout == 0) {
+ timeout = (intval > 60) ? 60 : (intval / 2);
+ if (timeout == 0) {
+ timeout = 1;
+ }
+ prefs_set_muc_ping_timeout(timeout);
+ }
+ if (intval != 0 && intval <= timeout) {
+ cons_show_error("MUC self-ping interval must be greater than timeout (%d seconds).", timeout);
+ } else {
+ prefs_set_muc_ping_interval(intval);
+ if (intval == 0) {
+ cons_show("MUC self-ping disabled.");
+ } else {
+ cons_show("MUC self-ping interval set to %d seconds.", intval);
+ }
+ }
+ } else {
+ cons_show(err_msg);
+ cons_bad_cmd_usage(command);
+ }
+
+ } else if (g_strcmp0(cmd, "timeout") == 0) {
+ int intval = 0;
+ auto_char char* err_msg = NULL;
+ gboolean res = strtoi_range(value, &intval, 0, INT_MAX, &err_msg);
+ if (res) {
+ if (intval == 0) {
+ prefs_set_muc_ping_interval(0);
+ prefs_set_muc_ping_timeout(0);
+ cons_show("MUC self-ping disabled.");
+ } else {
+ gint interval = prefs_get_muc_ping_interval();
+ if (interval != 0 && intval >= interval) {
+ cons_show_error("MUC self-ping timeout must be less than interval (%d seconds).", interval);
+ } else {
+ prefs_set_muc_ping_timeout(intval);
+ cons_show("MUC self-ping timeout set to %d seconds.", intval);
+ }
+ }
+ } else {
+ cons_show(err_msg);
+ cons_bad_cmd_usage(command);
+ }
+
+ } else {
+ cons_bad_cmd_usage(command);
+ }
+
+ return TRUE;
+}
+
gboolean
cmd_ping(ProfWin* window, const char* const command, gchar** args)
{
diff --git a/src/command/cmd_funcs.h b/src/command/cmd_funcs.h
index 10455dc07..882387d6b 100644
--- a/src/command/cmd_funcs.h
+++ b/src/command/cmd_funcs.h
@@ -58,6 +58,7 @@ gboolean cmd_about(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_autoaway(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_autoconnect(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_autoping(ProfWin* window, const char* const command, gchar** args);
+gboolean cmd_mucping(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_beep(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_caps(ProfWin* window, const char* const command, gchar** args);
gboolean cmd_logging(ProfWin* window, const char* const command, gchar** args);
diff --git a/src/config/preferences.c b/src/config/preferences.c
index aa59fa592..f92d49524 100644
--- a/src/config/preferences.c
+++ b/src/config/preferences.c
@@ -836,6 +836,38 @@ prefs_set_autoping_timeout(gint value)
g_key_file_set_integer(prefs, PREF_GROUP_CONNECTION, "autoping.timeout", value);
}
+gint
+prefs_get_muc_ping_interval(void)
+{
+ if (!g_key_file_has_key(prefs, PREF_GROUP_CONNECTION, "muc.ping.interval", NULL)) {
+ return 600;
+ } else {
+ return g_key_file_get_integer(prefs, PREF_GROUP_CONNECTION, "muc.ping.interval", NULL);
+ }
+}
+
+void
+prefs_set_muc_ping_interval(gint value)
+{
+ g_key_file_set_integer(prefs, PREF_GROUP_CONNECTION, "muc.ping.interval", value);
+}
+
+gint
+prefs_get_muc_ping_timeout(void)
+{
+ if (!g_key_file_has_key(prefs, PREF_GROUP_CONNECTION, "muc.ping.timeout", NULL)) {
+ return 60;
+ } else {
+ return g_key_file_get_integer(prefs, PREF_GROUP_CONNECTION, "muc.ping.timeout", NULL);
+ }
+}
+
+void
+prefs_set_muc_ping_timeout(gint value)
+{
+ g_key_file_set_integer(prefs, PREF_GROUP_CONNECTION, "muc.ping.timeout", value);
+}
+
gint
prefs_get_autoaway_time(void)
{
diff --git a/src/config/preferences.h b/src/config/preferences.h
index 1708e4428..76934bbf3 100644
--- a/src/config/preferences.h
+++ b/src/config/preferences.h
@@ -206,6 +206,10 @@ void prefs_set_autoping(gint value);
gint prefs_get_autoping(void);
void prefs_set_autoping_timeout(gint value);
gint prefs_get_autoping_timeout(void);
+void prefs_set_muc_ping_interval(gint value);
+gint prefs_get_muc_ping_interval(void);
+void prefs_set_muc_ping_timeout(gint value);
+gint prefs_get_muc_ping_timeout(void);
gint prefs_get_inpblock(void);
void prefs_set_inpblock(gint value);
diff --git a/src/ui/console.c b/src/ui/console.c
index 57ca3fdad..5ad9019dc 100644
--- a/src/ui/console.c
+++ b/src/ui/console.c
@@ -1817,6 +1817,8 @@ cons_show_chat_prefs(void)
cons_history_setting();
cons_carbons_setting();
cons_receipts_setting();
+ cons_mam_setting();
+ cons_silence_setting();
cons_alert(NULL);
}
@@ -2033,6 +2035,28 @@ cons_autoping_setting(void)
}
}
+void
+cons_mucping_setting(void)
+{
+ gint muc_ping_interval = prefs_get_muc_ping_interval();
+ if (muc_ping_interval == 0) {
+ cons_show("MUC self-ping interval (/mucping) : OFF");
+ } else if (muc_ping_interval == 1) {
+ cons_show("MUC self-ping interval (/mucping) : 1 second");
+ } else {
+ cons_show("MUC self-ping interval (/mucping) : %d seconds", muc_ping_interval);
+ }
+
+ gint muc_ping_timeout = prefs_get_muc_ping_timeout();
+ if (muc_ping_timeout == 0) {
+ cons_show("MUC self-ping timeout (/mucping) : OFF");
+ } else if (muc_ping_timeout == 1) {
+ cons_show("MUC self-ping timeout (/mucping) : 1 second");
+ } else {
+ cons_show("MUC self-ping timeout (/mucping) : %d seconds", muc_ping_timeout);
+ }
+}
+
void
cons_color_setting(void)
{
@@ -2146,6 +2170,7 @@ cons_show_connection_prefs(void)
cons_show("");
cons_reconnect_setting();
cons_autoping_setting();
+ cons_mucping_setting();
cons_autoconnect_setting();
cons_rooms_cache_setting();
cons_strophe_setting();
diff --git a/src/ui/ui.h b/src/ui/ui.h
index 63d8ab0b7..bcd2c428c 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -311,6 +311,7 @@ void cons_logging_setting(void);
void cons_autoaway_setting(void);
void cons_reconnect_setting(void);
void cons_autoping_setting(void);
+void cons_mucping_setting(void);
void cons_autoconnect_setting(void);
void cons_room_cache_setting(void);
void cons_inpblock_setting(void);
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index 90107eac6..dc13af3e7 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -135,6 +135,8 @@ static void _iq_id_handler_free(ProfIqHandler* handler);
// scheduled
static int _autoping_timed_send(xmpp_conn_t* const conn, void* const userdata);
+static int _muc_self_ping_timer_check(xmpp_conn_t* const conn, void* const userdata);
+static int _muc_self_pong_handler(xmpp_stanza_t* const stanza, void* const userdata);
static void _identity_destroy(DiscoIdentity* identity);
static void _item_destroy(DiscoItem* item);
@@ -244,6 +246,9 @@ iq_handlers_init(void)
int millis = prefs_get_autoping() * 1000;
xmpp_timed_handler_add(conn, _autoping_timed_send, millis, ctx);
}
+ if (prefs_get_muc_ping_interval() != 0) {
+ xmpp_timed_handler_add(conn, _muc_self_ping_timer_check, 10000, ctx);
+ }
received_disco_items = FALSE;
iq_handlers_clear();
@@ -1452,6 +1457,109 @@ autoping_timer_extend(void)
g_timer_start(autoping_time);
}
+static int
+_muc_self_pong_handler(xmpp_stanza_t* const stanza, void* const userdata)
+{
+ char* room = (char*)userdata;
+ log_debug("MUC self-ping: Pong received for room %s", room);
+
+ // Reset ping sent time
+ muc_set_ping_sent_time(room, 0);
+
+ const char* type = xmpp_stanza_get_type(stanza);
+ if (g_strcmp0(type, STANZA_TYPE_ERROR) == 0) {
+ xmpp_stanza_t* error_stanza = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_ERROR);
+ if (error_stanza) {
+ if (xmpp_stanza_get_child_by_name(error_stanza, "not-acceptable") || xmpp_stanza_get_child_by_name(error_stanza, "not-allowed") || xmpp_stanza_get_child_by_name(error_stanza, "item-not-found")) {
+
+ log_warning("MUC selfping: Disconnected from room %s (stale connection detected)", room);
+
+ ProfMucWin* mucwin = wins_get_muc(room);
+ if (mucwin) {
+ win_println((ProfWin*)mucwin, THEME_DEFAULT, "!", "Connection to room lost. Attempting to rejoin...");
+ }
+
+ char* password = muc_password(room);
+ const char* nick = muc_nick(room);
+ if (nick) {
+ presence_join_room(room, nick, password);
+ }
+ return 0;
+ }
+ }
+ }
+
+ // Still connected. Reset idle timer
+ muc_update_activity(room);
+
+ return 0;
+}
+
+static int
+_muc_self_ping_timer_check(xmpp_conn_t* const conn, void* const userdata)
+{
+ if (connection_get_status() != JABBER_CONNECTED) {
+ return 1;
+ }
+
+ gint64 current_time = g_get_monotonic_time() / G_TIME_SPAN_SECOND;
+ gint interval = prefs_get_muc_ping_interval();
+ gint timeout = prefs_get_muc_ping_timeout();
+
+ if (interval <= 0) {
+ return 1;
+ }
+
+ GList* rooms_list = muc_rooms();
+ GList* curr = rooms_list;
+
+ while (curr) {
+ char* room = curr->data;
+ gint64 last_act = muc_last_activity(room);
+ gint64 sent_time = muc_ping_sent_time(room);
+
+ if (sent_time > 0) {
+ // A ping is currently in flight for this room. Check for timeout.
+ if (current_time - sent_time > timeout) {
+ log_debug("MUC self-ping: Timeout for room %s", room);
+ muc_set_ping_sent_time(room, 0);
+
+ ProfMucWin* mucwin = wins_get_muc(room);
+ if (mucwin) {
+ win_println((ProfWin*)mucwin, THEME_DEFAULT, "!", "MUC service not responding.");
+ }
+ }
+ } else {
+ // No ping is in flight. Check if room is idle.
+ if (current_time - last_act > interval) {
+ const char* nick = muc_nick(room);
+ if (nick) {
+ auto_gchar gchar* occupant_jid = create_fulljid(room, nick);
+ log_debug("MUC self-ping: Sending ping to %s", occupant_jid);
+
+ xmpp_ctx_t* ctx = (xmpp_ctx_t*)userdata;
+ xmpp_stanza_t* iq = stanza_create_ping_iq(ctx, occupant_jid);
+ const char* id = xmpp_stanza_get_id(iq);
+
+ // Add response handler
+ iq_id_handler_add(id, _muc_self_pong_handler, free, strdup(room));
+
+ iq_send_stanza(iq);
+ xmpp_stanza_release(iq);
+
+ muc_set_ping_sent_time(room, current_time);
+ }
+ }
+ }
+
+ curr = g_list_next(curr);
+ }
+
+ g_list_free(rooms_list);
+
+ return 1;
+}
+
static int
_auto_pong_id_handler(xmpp_stanza_t* const stanza, void* const userdata)
{
diff --git a/src/xmpp/message.c b/src/xmpp/message.c
index 130cace03..232457905 100644
--- a/src/xmpp/message.c
+++ b/src/xmpp/message.c
@@ -997,6 +997,8 @@ _handle_groupchat(xmpp_stanza_t* const stanza)
return;
}
+ muc_update_activity(from_jid->barejid);
+
// handle room subject
xmpp_stanza_t* subject = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_SUBJECT);
if (subject) {
@@ -1245,6 +1247,8 @@ _handle_muc_private_message(xmpp_stanza_t* const stanza)
goto out;
}
+ muc_update_activity(message->from_jid->barejid);
+
// message stanza id
const char* id = xmpp_stanza_get_id(stanza);
if (id) {
diff --git a/src/xmpp/muc.c b/src/xmpp/muc.c
index 472c7ee31..c5fedf696 100644
--- a/src/xmpp/muc.c
+++ b/src/xmpp/muc.c
@@ -48,6 +48,8 @@ typedef struct _muc_room_t
gboolean roster_received;
muc_member_type_t member_type;
muc_anonymity_type_t anonymity_type;
+ gint64 last_activity;
+ gint64 ping_sent_time;
} ChatRoom;
GHashTable* rooms = NULL;
@@ -212,6 +214,8 @@ muc_join(const char* const room, const char* const nick, const char* const passw
new_room->autojoin = autojoin;
new_room->member_type = MUC_MEMBER_TYPE_UNKNOWN;
new_room->anonymity_type = MUC_ANONYMITY_TYPE_UNKNOWN;
+ new_room->last_activity = g_get_monotonic_time() / G_TIME_SPAN_SECOND;
+ new_room->ping_sent_time = 0;
g_hash_table_insert(rooms, strdup(room), new_room);
}
@@ -222,6 +226,58 @@ muc_leave(const char* const room)
g_hash_table_remove(rooms, room);
}
+void
+muc_update_activity(const char* const room)
+{
+ if (room == NULL) {
+ return;
+ }
+ ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
+ if (chat_room) {
+ chat_room->last_activity = g_get_monotonic_time() / G_TIME_SPAN_SECOND;
+ }
+}
+
+gint64
+muc_last_activity(const char* const room)
+{
+ if (room == NULL) {
+ return 0;
+ }
+ ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
+ if (chat_room) {
+ return chat_room->last_activity;
+ } else {
+ return 0;
+ }
+}
+
+void
+muc_set_ping_sent_time(const char* const room, gint64 time)
+{
+ if (room == NULL) {
+ return;
+ }
+ ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
+ if (chat_room) {
+ chat_room->ping_sent_time = time;
+ }
+}
+
+gint64
+muc_ping_sent_time(const char* const room)
+{
+ if (room == NULL) {
+ return 0;
+ }
+ ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
+ if (chat_room) {
+ return chat_room->ping_sent_time;
+ } else {
+ return 0;
+ }
+}
+
gboolean
muc_requires_config(const char* const room)
{
diff --git a/src/xmpp/muc.h b/src/xmpp/muc.h
index 715fdd886..8885d06f6 100644
--- a/src/xmpp/muc.h
+++ b/src/xmpp/muc.h
@@ -60,6 +60,11 @@ void muc_init(void);
void muc_join(const char* const room, const char* const nick, const char* const password, gboolean autojoin);
void muc_leave(const char* const room);
+void muc_update_activity(const char* const room);
+gint64 muc_last_activity(const char* const room);
+void muc_set_ping_sent_time(const char* const room, gint64 time);
+gint64 muc_ping_sent_time(const char* const room);
+
gboolean muc_active(const char* const room);
gboolean muc_autojoin(const char* const room);
diff --git a/src/xmpp/presence.c b/src/xmpp/presence.c
index f1aed9acb..c62d862ac 100644
--- a/src/xmpp/presence.c
+++ b/src/xmpp/presence.c
@@ -793,6 +793,7 @@ _muc_user_self_handler(xmpp_stanza_t* stanza)
}
if (muc_active(room)) {
+ muc_update_activity(room);
sv_ev_muc_self_online(room, nick, config_required, role, affiliation, actor, reason, jid, show_str, status_str);
} else {
log_debug("presence: self-presence received for untracked room: %s", room);
@@ -817,6 +818,8 @@ _muc_user_occupant_handler(xmpp_stanza_t* stanza)
return;
}
+ muc_update_activity(room);
+
const char* type = xmpp_stanza_get_type(stanza);
if (g_strcmp0(type, STANZA_TYPE_UNAVAILABLE) == 0) {
diff --git a/tests/unittests/command/test_cmd_mucping.c b/tests/unittests/command/test_cmd_mucping.c
new file mode 100644
index 000000000..cae9a203d
--- /dev/null
+++ b/tests/unittests/command/test_cmd_mucping.c
@@ -0,0 +1,98 @@
+#include "prof_cmocka.h"
+#include
+#include
+#include
+
+#include "config/preferences.h"
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+#include "command/cmd_funcs.h"
+
+#define CMD_MUCPING "/mucping"
+
+void
+cmd_mucping__shows__usage_when_invalid_interval_chars(void** state)
+{
+ gchar* args[] = { "set", "abc", NULL };
+
+ expect_any_cons_show();
+ expect_string(cons_bad_cmd_usage, cmd, CMD_MUCPING);
+
+ gboolean result = cmd_mucping(NULL, CMD_MUCPING, args);
+ assert_true(result);
+}
+
+void
+cmd_mucping__shows__usage_when_invalid_interval_range(void** state)
+{
+ gchar* args[] = { "set", "-5", NULL };
+
+ expect_any_cons_show();
+ expect_string(cons_bad_cmd_usage, cmd, CMD_MUCPING);
+
+ gboolean result = cmd_mucping(NULL, CMD_MUCPING, args);
+ assert_true(result);
+}
+
+void
+cmd_mucping__shows__usage_when_invalid_timeout_chars(void** state)
+{
+ gchar* args[] = { "timeout", "xyz", NULL };
+
+ expect_any_cons_show();
+ expect_string(cons_bad_cmd_usage, cmd, CMD_MUCPING);
+
+ gboolean result = cmd_mucping(NULL, CMD_MUCPING, args);
+ assert_true(result);
+}
+
+void
+cmd_mucping__shows__usage_when_invalid_timeout_range(void** state)
+{
+ gchar* args[] = { "timeout", "-1", NULL };
+
+ expect_any_cons_show();
+ expect_string(cons_bad_cmd_usage, cmd, CMD_MUCPING);
+
+ gboolean result = cmd_mucping(NULL, CMD_MUCPING, args);
+ assert_true(result);
+}
+
+void
+cmd_mucping__shows__error_when_interval_smaller_than_timeout(void** state)
+{
+ // Default interval = 600, timeout = 60
+ assert_int_equal(600, prefs_get_muc_ping_interval());
+ assert_int_equal(60, prefs_get_muc_ping_timeout());
+
+ // Set timeout to 80 (valid)
+ gchar* args_timeout[] = { "timeout", "80", NULL };
+ expect_cons_show("MUC self-ping timeout set to 80 seconds.");
+ gboolean result1 = cmd_mucping(NULL, CMD_MUCPING, args_timeout);
+ assert_true(result1);
+ assert_int_equal(80, prefs_get_muc_ping_timeout());
+
+ // Try setting interval to 50 (invalid: <= timeout of 80)
+ gchar* args_interval[] = { "set", "50", NULL };
+ expect_cons_show_error("MUC self-ping interval must be greater than timeout (80 seconds).");
+ gboolean result2 = cmd_mucping(NULL, CMD_MUCPING, args_interval);
+ assert_true(result2);
+ // Interval remains 600
+ assert_int_equal(600, prefs_get_muc_ping_interval());
+}
+
+void
+cmd_mucping__shows__error_when_timeout_greater_than_interval(void** state)
+{
+ // Reset interval and timeout
+ prefs_set_muc_ping_interval(100);
+ prefs_set_muc_ping_timeout(60);
+
+ // Try setting timeout to 120 (invalid: >= interval of 100)
+ gchar* args_timeout[] = { "timeout", "120", NULL };
+ expect_cons_show_error("MUC self-ping timeout must be less than interval (100 seconds).");
+ gboolean result1 = cmd_mucping(NULL, CMD_MUCPING, args_timeout);
+ assert_true(result1);
+ // Timeout remains 60
+ assert_int_equal(60, prefs_get_muc_ping_timeout());
+}
diff --git a/tests/unittests/command/test_cmd_mucping.h b/tests/unittests/command/test_cmd_mucping.h
new file mode 100644
index 000000000..be6f29bb6
--- /dev/null
+++ b/tests/unittests/command/test_cmd_mucping.h
@@ -0,0 +1,11 @@
+#ifndef TESTS_TEST_CMD_MUCPING_H
+#define TESTS_TEST_CMD_MUCPING_H
+
+void cmd_mucping__shows__usage_when_invalid_interval_chars(void** state);
+void cmd_mucping__shows__usage_when_invalid_interval_range(void** state);
+void cmd_mucping__shows__usage_when_invalid_timeout_chars(void** state);
+void cmd_mucping__shows__usage_when_invalid_timeout_range(void** state);
+void cmd_mucping__shows__error_when_interval_smaller_than_timeout(void** state);
+void cmd_mucping__shows__error_when_timeout_greater_than_interval(void** state);
+
+#endif
diff --git a/tests/unittests/ui/stub_ui.c b/tests/unittests/ui/stub_ui.c
index 7c90c480f..84de8aa72 100644
--- a/tests/unittests/ui/stub_ui.c
+++ b/tests/unittests/ui/stub_ui.c
@@ -1116,6 +1116,10 @@ cons_autoping_setting(void)
{
}
void
+cons_mucping_setting(void)
+{
+}
+void
cons_autoconnect_setting(void)
{
}
diff --git a/tests/unittests/unittests.c b/tests/unittests/unittests.c
index 17e3774e3..31a03cb95 100644
--- a/tests/unittests/unittests.c
+++ b/tests/unittests/unittests.c
@@ -27,6 +27,7 @@
#include "command/test_cmd_rooms.h"
#include "command/test_cmd_sub.h"
#include "command/test_cmd_presence.h"
+#include "command/test_cmd_mucping.h"
#include "command/test_cmd_otr.h"
#include "command/test_cmd_pgp.h"
#include "xmpp/test_jid.h"
@@ -459,6 +460,17 @@ main(int argc, char* argv[])
cmocka_unit_test(p_contact_is_available__is__true_when_highest_priority_online),
cmocka_unit_test(p_contact_is_available__is__true_when_highest_priority_chat),
+ cmocka_unit_test(cmd_mucping__shows__usage_when_invalid_interval_chars),
+ cmocka_unit_test(cmd_mucping__shows__usage_when_invalid_interval_range),
+ cmocka_unit_test(cmd_mucping__shows__usage_when_invalid_timeout_chars),
+ cmocka_unit_test(cmd_mucping__shows__usage_when_invalid_timeout_range),
+ cmocka_unit_test_setup_teardown(cmd_mucping__shows__error_when_interval_smaller_than_timeout,
+ load_preferences,
+ close_preferences),
+ cmocka_unit_test_setup_teardown(cmd_mucping__shows__error_when_timeout_greater_than_interval,
+ load_preferences,
+ close_preferences),
+
cmocka_unit_test(cmd_presence__shows__usage_when_bad_subcmd),
cmocka_unit_test(cmd_presence__shows__usage_when_bad_console_setting),
cmocka_unit_test(cmd_presence__shows__usage_when_bad_chat_setting),