about summary refs log tree commit diff stats
path: root/unittests
diff options
context:
space:
mode:
Diffstat (limited to 'unittests')
-rw-r--r--unittests/config/stub_accounts.c185
-rw-r--r--unittests/helpers.c148
-rw-r--r--unittests/helpers.h12
-rw-r--r--unittests/log/stub_log.c70
-rw-r--r--unittests/otr/stub_otr.c108
-rw-r--r--unittests/test_autocomplete.c119
-rw-r--r--unittests/test_autocomplete.h10
-rw-r--r--unittests/test_chat_session.c51
-rw-r--r--unittests/test_chat_session.h4
-rw-r--r--unittests/test_cmd_account.c1017
-rw-r--r--unittests/test_cmd_account.h55
-rw-r--r--unittests/test_cmd_alias.c159
-rw-r--r--unittests/test_cmd_alias.h9
-rw-r--r--unittests/test_cmd_bookmark.c297
-rw-r--r--unittests/test_cmd_bookmark.h14
-rw-r--r--unittests/test_cmd_connect.c478
-rw-r--r--unittests/test_cmd_connect.h26
-rw-r--r--unittests/test_cmd_disconnect.c37
-rw-r--r--unittests/test_cmd_disconnect.h1
-rw-r--r--unittests/test_cmd_join.c184
-rw-r--r--unittests/test_cmd_join.h9
-rw-r--r--unittests/test_cmd_otr.c578
-rw-r--r--unittests/test_cmd_otr.h45
-rw-r--r--unittests/test_cmd_rooms.c104
-rw-r--r--unittests/test_cmd_rooms.h7
-rw-r--r--unittests/test_cmd_roster.c279
-rw-r--r--unittests/test_cmd_roster.h16
-rw-r--r--unittests/test_cmd_statuses.c223
-rw-r--r--unittests/test_cmd_statuses.h13
-rw-r--r--unittests/test_cmd_sub.c45
-rw-r--r--unittests/test_cmd_sub.h2
-rw-r--r--unittests/test_common.c633
-rw-r--r--unittests/test_common.h58
-rw-r--r--unittests/test_contact.c407
-rw-r--r--unittests/test_contact.h24
-rw-r--r--unittests/test_form.c727
-rw-r--r--unittests/test_form.h21
-rw-r--r--unittests/test_jid.c185
-rw-r--r--unittests/test_jid.h25
-rw-r--r--unittests/test_keyhandlers.c734
-rw-r--r--unittests/test_keyhandlers.h47
-rw-r--r--unittests/test_muc.c78
-rw-r--r--unittests/test_muc.h9
-rw-r--r--unittests/test_parser.c662
-rw-r--r--unittests/test_parser.h50
-rw-r--r--unittests/test_preferences.c33
-rw-r--r--unittests/test_preferences.h3
-rw-r--r--unittests/test_roster_list.c271
-rw-r--r--unittests/test_roster_list.h18
-rw-r--r--unittests/test_server_events.c100
-rw-r--r--unittests/test_server_events.h14
-rw-r--r--unittests/ui/stub_ui.c512
-rw-r--r--unittests/ui/stub_ui.h6
-rw-r--r--unittests/unittests.c595
-rw-r--r--unittests/xmpp/stub_xmpp.c228
55 files changed, 9745 insertions, 0 deletions
diff --git a/unittests/config/stub_accounts.c b/unittests/config/stub_accounts.c
new file mode 100644
index 00000000..32a80fda
--- /dev/null
+++ b/unittests/config/stub_accounts.c
@@ -0,0 +1,185 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "common.h"
+#include "config/account.h"
+
+void accounts_load(void) {}
+void accounts_close(void) {}
+
+char * accounts_find_all(char *prefix)
+{
+    return NULL;
+}
+
+char * accounts_find_enabled(char *prefix)
+{
+    return NULL;
+}
+
+void accounts_reset_all_search(void) {}
+void accounts_reset_enabled_search(void) {}
+
+void accounts_add(const char *jid, const char *altdomain, const int port)
+{
+    check_expected(jid);
+    check_expected(altdomain);
+    check_expected(port);
+}
+
+int  accounts_remove(const char *jid)
+{
+    return 0;
+}
+
+gchar** accounts_get_list(void)
+{
+    return (gchar **)mock();
+}
+
+ProfAccount* accounts_get_account(const char * const name)
+{
+    check_expected(name);
+    return (ProfAccount*)mock();
+}
+
+gboolean accounts_enable(const char * const name)
+{
+    check_expected(name);
+    return (gboolean)mock();
+}
+
+gboolean accounts_disable(const char * const name)
+{
+    check_expected(name);
+    return (gboolean)mock();
+}
+
+gboolean accounts_rename(const char * const account_name,
+    const char * const new_name)
+{
+    check_expected(account_name);
+    check_expected(new_name);
+    return (gboolean)mock();
+}
+
+gboolean accounts_account_exists(const char * const account_name)
+{
+    check_expected(account_name);
+    return (gboolean)mock();
+}
+
+void accounts_set_jid(const char * const account_name, const char * const value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_server(const char * const account_name, const char * const value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_port(const char * const account_name, const int value) {}
+
+void accounts_set_resource(const char * const account_name, const char * const value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_password(const char * const account_name, const char * const value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_eval_password(const char * const account_name, const char * const value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_muc_service(const char * const account_name, const char * const value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_muc_nick(const char * const account_name, const char * const value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_otr_policy(const char * const account_name, const char * const value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_last_presence(const char * const account_name, const char * const value) {}
+
+void accounts_set_login_presence(const char * const account_name, const char * const value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+resource_presence_t accounts_get_login_presence(const char * const account_name)
+{
+    return RESOURCE_ONLINE;
+}
+
+resource_presence_t accounts_get_last_presence(const char * const account_name)
+{
+    check_expected(account_name);
+    return (resource_presence_t)mock();
+}
+
+void accounts_set_priority_online(const char * const account_name, const gint value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_priority_chat(const char * const account_name, const gint value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_priority_away(const char * const account_name, const gint value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_priority_xa(const char * const account_name, const gint value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_priority_dnd(const char * const account_name, const gint value)
+{
+    check_expected(account_name);
+    check_expected(value);
+}
+
+void accounts_set_priority_all(const char * const account_name, const gint value) {}
+gint accounts_get_priority_for_presence_type(const char * const account_name,
+    resource_presence_t presence_type)
+{
+    return 0;
+}
+
+void accounts_clear_password(const char * const account_name) {}
+void accounts_clear_eval_password(const char * const account_name) {}
+void accounts_clear_server(const char * const account_name) {}
+void accounts_clear_port(const char * const account_name) {}
+void accounts_clear_otr(const char * const account_name) {}
+void accounts_add_otr_policy(const char * const account_name, const char * const contact_jid, const char * const policy) {}
diff --git a/unittests/helpers.c b/unittests/helpers.c
new file mode 100644
index 00000000..564b2716
--- /dev/null
+++ b/unittests/helpers.c
@@ -0,0 +1,148 @@
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <cmocka.h>
+#include <glib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "helpers.h"
+#include "config/preferences.h"
+#include "chat_session.h"
+
+void create_config_dir(void **state)
+{
+    setenv("XDG_CONFIG_HOME", "./tests/files/xdg_config_home", 1);
+    gchar *xdg_config = xdg_get_config_home();
+
+    GString *profanity_dir = g_string_new(xdg_config);
+    g_string_append(profanity_dir, "/profanity");
+
+    if (!mkdir_recursive(profanity_dir->str)) {
+        assert_true(FALSE);
+    }
+
+    g_free(xdg_config);
+    g_string_free(profanity_dir, TRUE);
+}
+
+void remove_config_dir(void **state)
+{
+    rmdir("./tests/files/xdg_config_home/profanity");
+    rmdir("./tests/files/xdg_config_home");
+}
+
+void create_data_dir(void **state)
+{
+    setenv("XDG_DATA_HOME", "./tests/files/xdg_data_home", 1);
+    gchar *xdg_data = xdg_get_data_home();
+
+    GString *profanity_dir = g_string_new(xdg_data);
+    g_string_append(profanity_dir, "/profanity");
+
+    if (!mkdir_recursive(profanity_dir->str)) {
+        assert_true(FALSE);
+    }
+
+    g_free(xdg_data);
+    g_string_free(profanity_dir, TRUE);
+}
+
+void remove_data_dir(void **state)
+{
+    rmdir("./tests/files/xdg_data_home/profanity");
+    rmdir("./tests/files/xdg_data_home");
+}
+
+void load_preferences(void **state)
+{
+    create_config_dir(state);
+    FILE *f = fopen("./tests/files/xdg_config_home/profanity/profrc", "ab+");
+    if (f) {
+        prefs_load();
+    }
+}
+
+void close_preferences(void **state)
+{
+    prefs_close();
+    remove("./tests/files/xdg_config_home/profanity/profrc");
+    remove_config_dir(state);
+    rmdir("./tests/files");
+}
+
+void init_chat_sessions(void **state)
+{
+    load_preferences(NULL);
+    chat_sessions_init();
+}
+
+void close_chat_sessions(void **state)
+{
+    chat_sessions_clear();
+    close_preferences(NULL);
+}
+
+int
+utf8_pos_to_col(char *str, int utf8_pos)
+{
+    int col = 0;
+
+    int i = 0;
+    for (i = 0; i<utf8_pos; i++) {
+        col++;
+        gchar *ch = g_utf8_offset_to_pointer(str, i);
+        gunichar uni = g_utf8_get_char(ch);
+        if (g_unichar_iswide(uni)) {
+            col++;
+        }
+    }
+
+    return col;
+}
+
+static GCompareFunc cmp_func;
+
+void
+glist_set_cmp(GCompareFunc func)
+{
+    cmp_func = func;
+}
+
+int
+glist_contents_equal(const void *actual, const void *expected)
+{
+    GList *ac = (GList *)actual;
+    GList *ex = (GList *)expected;
+
+    GList *p = ex;
+    printf("\nExpected\n");
+    while(ex) {
+        printf("\n\n%s\n", (char*)p->data);
+        ex = g_list_next(ex);
+    }
+    printf("\n\n");
+    p = ac;
+    printf("\nActual\n");
+    while(ac) {
+        printf("\n\n%s\n", (char *)p->data);
+        ac = g_list_next(ac);
+    }
+    printf("\n\n");
+
+    if (g_list_length(ex) != g_list_length(ac)) {
+        return 0;
+    }
+
+    GList *ex_curr = ex;
+    while (ex_curr != NULL) {
+        if (g_list_find_custom(ac, ex_curr->data, cmp_func) == NULL) {
+            return 0;
+        }
+        ex_curr = g_list_next(ex_curr);
+    }
+
+    return 1;
+}
diff --git a/unittests/helpers.h b/unittests/helpers.h
new file mode 100644
index 00000000..75d446d0
--- /dev/null
+++ b/unittests/helpers.h
@@ -0,0 +1,12 @@
+#include "glib.h"
+
+void load_preferences(void **state);
+void close_preferences(void **state);
+
+void init_chat_sessions(void **state);
+void close_chat_sessions(void **state);
+
+int utf8_pos_to_col(char *str, int utf8_pos);
+
+void glist_set_cmp(GCompareFunc func);
+int glist_contents_equal(const void *actual, const void *expected);
\ No newline at end of file
diff --git a/unittests/log/stub_log.c b/unittests/log/stub_log.c
new file mode 100644
index 00000000..f88871a7
--- /dev/null
+++ b/unittests/log/stub_log.c
@@ -0,0 +1,70 @@
+/*
+ * mock_log.c
+ *
+ * Copyright (C) 2012, 2013 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Profanity is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Profanity.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <glib.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "log.h"
+
+void log_init(log_level_t filter) {}
+log_level_t log_get_filter(void)
+{
+    return (log_level_t)mock();
+}
+void log_reinit(void) {}
+void log_close(void) {}
+void log_debug(const char * const msg, ...) {}
+void log_info(const char * const msg, ...) {}
+void log_warning(const char * const msg, ...) {}
+void log_error(const char * const msg, ...) {}
+void log_msg(log_level_t level, const char * const area,
+    const char * const msg) {}
+char * get_log_file_location(void)
+{
+    return mock_ptr_type(char *);
+}
+
+log_level_t log_level_from_string(char *log_level)
+{
+    return (log_level_t)mock();
+}
+
+void chat_log_init(void) {}
+
+void chat_log_msg_out(const char * const barejid, const char * const msg) {}
+void chat_log_otr_msg_out(const char * const barejid, const char * const msg) {}
+
+void chat_log_msg_in(const char * const barejid, const char * const msg) {}
+void chat_log_msg_in_delayed(const char * const barejid, const char * msg, GTimeVal *tv_stamp) {}
+void chat_log_otr_msg_in(const char * const barejid, const char * const msg, gboolean was_decrypted) {}
+
+void chat_log_close(void) {}
+GSList * chat_log_get_previous(const gchar * const login,
+    const gchar * const recipient)
+{
+    return mock_ptr_type(GSList *);
+}
+
+void groupchat_log_init(void) {}
+void groupchat_log_chat(const gchar * const login, const gchar * const room,
+    const gchar * const nick, const gchar * const msg) {}
diff --git a/unittests/otr/stub_otr.c b/unittests/otr/stub_otr.c
new file mode 100644
index 00000000..482f0a7f
--- /dev/null
+++ b/unittests/otr/stub_otr.c
@@ -0,0 +1,108 @@
+#include <libotr/proto.h>
+#include <libotr/message.h>
+#include <glib.h>
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "config/account.h"
+
+#include "otr/otr.h"
+
+OtrlUserState otr_userstate(void)
+{
+    return NULL;
+}
+
+OtrlMessageAppOps* otr_messageops(void)
+{
+    return NULL;
+}
+
+GHashTable* otr_smpinitators(void)
+{
+    return NULL;
+}
+
+void otr_init(void) {}
+void otr_shutdown(void) {}
+
+char* otr_libotr_version(void)
+{
+    return (char*)mock();
+}
+
+char* otr_start_query(void)
+{
+    return (char*)mock();
+}
+
+void otr_poll(void) {}
+void otr_on_connect(ProfAccount *account) {}
+void otr_on_message_recv(const char * const barejid, const char * const resource, const char * const message) {}
+void otr_on_message_send(ProfChatWin *chatwin, const char * const message) {}
+
+void otr_keygen(ProfAccount *account)
+{
+    check_expected(account);
+}
+
+gboolean otr_key_loaded(void)
+{
+    return (gboolean)mock();
+}
+
+char* otr_tag_message(const char * const msg)
+{
+    return NULL;
+}
+
+gboolean otr_is_secure(const char * const recipient)
+{
+    return FALSE;
+}
+
+gboolean otr_is_trusted(const char * const recipient)
+{
+    return FALSE;
+}
+
+void otr_trust(const char * const recipient) {}
+void otr_untrust(const char * const recipient) {}
+
+void otr_smp_secret(const char * const recipient, const char *secret) {}
+void otr_smp_question(const char * const recipient, const char *question, const char *answer) {}
+void otr_smp_answer(const char * const recipient, const char *answer) {}
+
+void otr_end_session(const char * const recipient) {}
+
+char * otr_get_my_fingerprint(void)
+{
+    return (char *)mock();
+}
+
+char * otr_get_their_fingerprint(const char * const recipient)
+{
+    check_expected(recipient);
+    return (char *)mock();
+}
+
+char * otr_encrypt_message(const char * const to, const char * const message)
+{
+    return NULL;
+}
+
+char * otr_decrypt_message(const char * const from, const char * const message,
+    gboolean *was_decrypted)
+{
+    return NULL;
+}
+
+void otr_free_message(char *message) {}
+
+prof_otrpolicy_t otr_get_policy(const char * const recipient)
+{
+    return PROF_OTRPOLICY_MANUAL;
+}
diff --git a/unittests/test_autocomplete.c b/unittests/test_autocomplete.c
new file mode 100644
index 00000000..f6ef8653
--- /dev/null
+++ b/unittests/test_autocomplete.c
@@ -0,0 +1,119 @@
+#include <glib.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+#include "contact.h"
+#include "tools/autocomplete.h"
+
+void clear_empty(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    autocomplete_clear(ac);
+}
+
+void reset_after_create(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    autocomplete_reset(ac);
+    autocomplete_clear(ac);
+}
+
+void find_after_create(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    autocomplete_complete(ac, "hello", TRUE);
+    autocomplete_clear(ac);
+}
+
+void get_after_create_returns_null(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    GSList *result = autocomplete_create_list(ac);
+
+    assert_null(result);
+
+    autocomplete_clear(ac);
+    g_slist_free_full(result, g_free);
+}
+
+void add_one_and_complete(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    autocomplete_add(ac, "Hello");
+    char *result = autocomplete_complete(ac, "Hel", TRUE);
+
+    assert_string_equal("Hello", result);
+
+    autocomplete_clear(ac);
+}
+
+void add_two_and_complete_returns_first(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    autocomplete_add(ac, "Hello");
+    autocomplete_add(ac, "Help");
+    char *result = autocomplete_complete(ac, "Hel", TRUE);
+
+    assert_string_equal("Hello", result);
+
+    autocomplete_clear(ac);
+}
+
+void add_two_and_complete_returns_second(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    autocomplete_add(ac, "Hello");
+    autocomplete_add(ac, "Help");
+    char *result1 = autocomplete_complete(ac, "Hel", TRUE);
+    char *result2 = autocomplete_complete(ac, result1, TRUE);
+
+    assert_string_equal("Help", result2);
+
+    autocomplete_clear(ac);
+}
+
+void add_two_adds_two(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    autocomplete_add(ac, "Hello");
+    autocomplete_add(ac, "Help");
+    GSList *result = autocomplete_create_list(ac);
+
+    assert_int_equal(2, g_slist_length(result));
+
+    autocomplete_clear(ac);
+    g_slist_free_full(result, g_free);
+}
+
+void add_two_same_adds_one(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    autocomplete_add(ac, "Hello");
+    autocomplete_add(ac, "Hello");
+    GSList *result = autocomplete_create_list(ac);
+
+    assert_int_equal(1, g_slist_length(result));
+
+    autocomplete_clear(ac);
+    g_slist_free_full(result, g_free);
+}
+
+void add_two_same_updates(void **state)
+{
+    Autocomplete ac = autocomplete_new();
+    autocomplete_add(ac, "Hello");
+    autocomplete_add(ac, "Hello");
+    GSList *result = autocomplete_create_list(ac);
+
+    GSList *first = g_slist_nth(result, 0);
+
+    char *str = first->data;
+
+    assert_string_equal("Hello", str);
+
+    autocomplete_clear(ac);
+    g_slist_free_full(result, g_free);
+}
diff --git a/unittests/test_autocomplete.h b/unittests/test_autocomplete.h
new file mode 100644
index 00000000..4ad327c0
--- /dev/null
+++ b/unittests/test_autocomplete.h
@@ -0,0 +1,10 @@
+void clear_empty(void **state);
+void reset_after_create(void **state);
+void find_after_create(void **state);
+void get_after_create_returns_null(void **state);
+void add_one_and_complete(void **state);
+void add_two_and_complete_returns_first(void **state);
+void add_two_and_complete_returns_second(void **state);
+void add_two_adds_two(void **state);
+void add_two_same_adds_one(void **state);
+void add_two_same_updates(void **state);
diff --git a/unittests/test_chat_session.c b/unittests/test_chat_session.c
new file mode 100644
index 00000000..b5e1f7b6
--- /dev/null
+++ b/unittests/test_chat_session.c
@@ -0,0 +1,51 @@
+#include <stdarg.h>
+#include <string.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+#include "chat_session.h"
+
+void returns_false_when_chat_session_does_not_exist(void **state)
+{
+    ChatSession *session = chat_session_get("somejid@server.org");
+    assert_null(session);
+}
+
+void creates_chat_session_on_recipient_activity(void **state)
+{
+    char *barejid = "myjid@server.org";
+    char *resource = "tablet";
+
+    chat_session_recipient_active(barejid, resource, FALSE);
+    ChatSession *session = chat_session_get(barejid);
+
+    assert_non_null(session);
+    assert_string_equal(session->resource, resource);
+}
+
+void replaces_chat_session_on_recipient_activity_with_different_resource(void **state)
+{
+    char *barejid = "myjid@server.org";
+    char *resource1 = "tablet";
+    char *resource2 = "mobile";
+
+    chat_session_recipient_active(barejid, resource1, FALSE);
+    chat_session_recipient_active(barejid, resource2, FALSE);
+    ChatSession *session = chat_session_get(barejid);
+
+    assert_string_equal(session->resource, resource2);
+}
+
+void removes_chat_session(void **state)
+{
+    char *barejid = "myjid@server.org";
+    char *resource1 = "laptop";
+
+    chat_session_recipient_active(barejid, resource1, FALSE);
+    chat_session_remove(barejid);
+    ChatSession *session = chat_session_get(barejid);
+
+    assert_null(session);
+}
\ No newline at end of file
diff --git a/unittests/test_chat_session.h b/unittests/test_chat_session.h
new file mode 100644
index 00000000..4ce03fd5
--- /dev/null
+++ b/unittests/test_chat_session.h
@@ -0,0 +1,4 @@
+void returns_false_when_chat_session_does_not_exist(void **state);
+void creates_chat_session_on_recipient_activity(void **state);
+void replaces_chat_session_on_recipient_activity_with_different_resource(void **state);
+void removes_chat_session(void **state);
\ No newline at end of file
diff --git a/unittests/test_cmd_account.c b/unittests/test_cmd_account.c
new file mode 100644
index 00000000..bddc4c6d
--- /dev/null
+++ b/unittests/test_cmd_account.c
@@ -0,0 +1,1017 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "xmpp/xmpp.h"
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#include "config/accounts.h"
+
+#include "command/commands.h"
+
+void cmd_account_shows_usage_when_not_connected_and_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+
+void cmd_account_shows_account_when_connected_and_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    ProfAccount *account = account_new("jabber_org", "me@jabber.org", NULL, NULL,
+        TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+    gchar *args[] = { NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_account_name, "account_name");
+    expect_any(accounts_get_account, name);
+    will_return(accounts_get_account, account);
+
+    expect_memory(cons_show_account, account, account, sizeof(ProfAccount));
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_list_shows_accounts(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "list", NULL };
+
+    gchar **accounts = malloc(sizeof(gchar *) * 4);
+    accounts[0] = strdup("account1");
+    accounts[1] = strdup("account2");
+    accounts[2] = strdup("account3");
+    accounts[3] = NULL;
+
+    will_return(accounts_get_list, accounts);
+
+    expect_memory(cons_show_account_list, accounts, accounts, sizeof(accounts));
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_show_shows_usage_when_no_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "show", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_show_shows_message_when_account_does_not_exist(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "show", "account_name", NULL };
+
+    expect_any(accounts_get_account, name);
+    will_return(accounts_get_account, NULL);
+
+    expect_cons_show("No such account.");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_show_shows_account_when_exists(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "show", "account_name", NULL };
+    ProfAccount *account = account_new("jabber_org", "me@jabber.org", NULL, NULL,
+        TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+    expect_any(accounts_get_account, name);
+    will_return(accounts_get_account, account);
+
+    expect_memory(cons_show_account, account, account, sizeof(account));
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_add_shows_usage_when_no_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "add", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_add_adds_account(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "add", "new_account", NULL };
+
+    expect_string(accounts_add, jid, "new_account");
+    expect_value(accounts_add, altdomain, NULL);
+    expect_value(accounts_add, port, 0);
+    expect_cons_show("Account created.");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_enable_shows_usage_when_no_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "enable", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_enable_enables_account(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "enable", "account_name", NULL };
+
+    expect_string(accounts_enable, name, "account_name");
+    will_return(accounts_enable, TRUE);
+
+    expect_cons_show("Account enabled.");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_enable_shows_message_when_account_doesnt_exist(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "enable", "account_name", NULL };
+
+    expect_any(accounts_enable, name);
+    will_return(accounts_enable, FALSE);
+
+    expect_cons_show("No such account: account_name");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_disable_shows_usage_when_no_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "disable", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_disable_disables_account(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "disable", "account_name", NULL };
+
+    expect_string(accounts_disable, name, "account_name");
+    will_return(accounts_disable, TRUE);
+
+    expect_cons_show("Account disabled.");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_disable_shows_message_when_account_doesnt_exist(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "disable", "account_name", NULL };
+
+    expect_any(accounts_disable, name);
+    will_return(accounts_disable, FALSE);
+
+    expect_cons_show("No such account: account_name");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_rename_shows_usage_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "rename", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_rename_shows_usage_when_one_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "rename", "original_name", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_rename_renames_account(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "rename", "original_name", "new_name", NULL };
+
+    expect_string(accounts_rename, account_name, "original_name");
+    expect_string(accounts_rename, new_name, "new_name");
+    will_return(accounts_rename, TRUE);
+
+    expect_cons_show("Account renamed.");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_rename_shows_message_when_not_renamed(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "rename", "original_name", "new_name", NULL };
+
+    expect_any(accounts_rename, account_name);
+    expect_any(accounts_rename, new_name);
+    will_return(accounts_rename, FALSE);
+
+    expect_cons_show("Either account original_name doesn't exist, or account new_name already exists.");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_shows_usage_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "set", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_shows_usage_when_one_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "set", "a_account", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_shows_usage_when_two_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "set", "a_account", "a_property", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_shows_message_when_account_doesnt_exist(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "a_property", "a_value", NULL };
+
+    expect_string(accounts_account_exists, account_name, "a_account");
+    will_return(accounts_account_exists, FALSE);
+
+    expect_cons_show("Account a_account doesn't exist");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_jid_shows_message_for_malformed_jid(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "jid", "@malformed", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("Malformed jid: @malformed");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_jid_sets_barejid(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "jid", "a_local@a_domain", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_jid, account_name, "a_account");
+    expect_string(accounts_set_jid, value, "a_local@a_domain");
+
+    expect_cons_show("Updated jid for account a_account: a_local@a_domain");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_jid_sets_resource(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "jid", "a_local@a_domain/a_resource", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_jid, account_name, "a_account");
+    expect_string(accounts_set_jid, value, "a_local@a_domain");
+
+    expect_cons_show("Updated jid for account a_account: a_local@a_domain");
+
+    expect_string(accounts_set_resource, account_name, "a_account");
+    expect_string(accounts_set_resource, value, "a_resource");
+
+    expect_cons_show("Updated resource for account a_account: a_resource");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_server_sets_server(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "server", "a_server", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_server, account_name, "a_account");
+    expect_string(accounts_set_server, value, "a_server");
+
+    expect_cons_show("Updated server for account a_account: a_server");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_resource_sets_resource(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "resource", "a_resource", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_resource, account_name, "a_account");
+    expect_string(accounts_set_resource, value, "a_resource");
+
+    expect_cons_show("Updated resource for account a_account: a_resource");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_password_sets_password(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "password", "a_password", NULL };
+    ProfAccount *account = account_new("a_account", NULL, NULL, NULL,
+    TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_get_account, name, "a_account");
+    will_return(accounts_get_account, account);
+
+    expect_string(accounts_set_password, account_name, "a_account");
+    expect_string(accounts_set_password, value, "a_password");
+
+    expect_cons_show("Updated password for account a_account");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_eval_password_sets_eval_password(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "eval_password", "a_password", NULL };
+    ProfAccount *account = account_new("a_account", NULL, NULL, NULL,
+    TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_get_account, name, "a_account");
+    will_return(accounts_get_account, account);
+
+    expect_string(accounts_set_eval_password, account_name, "a_account");
+    expect_string(accounts_set_eval_password, value, "a_password");
+
+    expect_cons_show("Updated eval_password for account a_account");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_password_when_eval_password_set(void **state) {
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "password", "a_password", NULL };
+    ProfAccount *account = account_new("a_account", NULL, NULL, "a_password",
+    TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_get_account, name, "a_account");
+    will_return(accounts_get_account, account);
+
+    expect_cons_show("Cannot set password when eval_password is set.");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_eval_password_when_password_set(void **state) {
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "eval_password", "a_password", NULL };
+    ProfAccount *account = account_new("a_account", NULL, "a_password", NULL,
+    TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_get_account, name, "a_account");
+    will_return(accounts_get_account, account);
+
+    expect_cons_show("Cannot set eval_password when password is set.");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_muc_sets_muc(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "muc", "a_muc", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_muc_service, account_name, "a_account");
+    expect_string(accounts_set_muc_service, value, "a_muc");
+
+    expect_cons_show("Updated muc service for account a_account: a_muc");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_nick_sets_nick(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "nick", "a_nick", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_muc_nick, account_name, "a_account");
+    expect_string(accounts_set_muc_nick, value, "a_nick");
+
+    expect_cons_show("Updated muc nick for account a_account: a_nick");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_show_message_for_missing_otr_policy(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "Some usage";
+    gchar *args[] = { "set", "a_account", "otr", NULL };
+
+    expect_cons_show("Usage: Some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_show_message_for_invalid_otr_policy(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "otr", "bad_otr_policy", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("OTR policy must be one of: manual, opportunistic or always.");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_otr_sets_otr(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "otr", "opportunistic", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_otr_policy, account_name, "a_account");
+    expect_string(accounts_set_otr_policy, value, "opportunistic");
+
+    expect_cons_show("Updated OTR policy for account a_account: opportunistic");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_status_shows_message_when_invalid_status(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "status", "bad_status", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("Invalid status: bad_status");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_status_sets_status_when_valid(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "status", "away", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_login_presence, account_name, "a_account");
+    expect_string(accounts_set_login_presence, value, "away");
+
+    expect_cons_show("Updated login status for account a_account: away");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_status_sets_status_when_last(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "status", "last", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_login_presence, account_name, "a_account");
+    expect_string(accounts_set_login_presence, value, "last");
+
+    expect_cons_show("Updated login status for account a_account: last");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_invalid_presence_string_priority_shows_message(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "blah", "10", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("Invalid property: blah");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_last_priority_shows_message(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "last", "10", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("Invalid property: last");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_online_priority_sets_preference(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "online", "10", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_priority_online, account_name, "a_account");
+    expect_value(accounts_set_priority_online, value, 10);
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Updated online priority for account a_account: 10");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_chat_priority_sets_preference(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "chat", "10", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_priority_chat, account_name, "a_account");
+    expect_value(accounts_set_priority_chat, value, 10);
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Updated chat priority for account a_account: 10");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_away_priority_sets_preference(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "away", "10", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_priority_away, account_name, "a_account");
+    expect_value(accounts_set_priority_away, value, 10);
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Updated away priority for account a_account: 10");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_xa_priority_sets_preference(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "xa", "10", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_priority_xa, account_name, "a_account");
+    expect_value(accounts_set_priority_xa, value, 10);
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Updated xa priority for account a_account: 10");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_dnd_priority_sets_preference(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "dnd", "10", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_string(accounts_set_priority_dnd, account_name, "a_account");
+    expect_value(accounts_set_priority_dnd, value, 10);
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Updated dnd priority for account a_account: 10");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_priority_too_low_shows_message(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "online", "-150", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("Value -150 out of range. Must be in -128..127.");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_priority_too_high_shows_message(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "online", "150", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("Value 150 out of range. Must be in -128..127.");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_priority_when_not_number_shows_message(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "online", "abc", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("Could not convert \"abc\" to a number.");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_priority_when_empty_shows_message(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "online", "", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("Could not convert \"\" to a number.");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_set_priority_updates_presence_when_account_connected_with_presence(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "set", "a_account", "online", "10", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_any(accounts_set_priority_online, account_name);
+    expect_any(accounts_set_priority_online, value);
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_account_name, "a_account");
+
+    expect_any(accounts_get_last_presence, account_name);
+    will_return(accounts_get_last_presence, RESOURCE_ONLINE);
+
+    will_return(jabber_get_presence_message, "Free to chat");
+
+    expect_value(presence_send, status, RESOURCE_ONLINE);
+    expect_string(presence_send, msg, "Free to chat");
+    expect_value(presence_send, idle, 0);
+
+    expect_cons_show("Updated online priority for account a_account: 10");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_clear_shows_usage_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "clear", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_clear_shows_usage_when_one_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "clear", "a_account", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_clear_shows_message_when_account_doesnt_exist(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "clear", "a_account", "a_property", NULL };
+
+    expect_string(accounts_account_exists, account_name, "a_account");
+    will_return(accounts_account_exists, FALSE);
+
+    expect_cons_show("Account a_account doesn't exist");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_account_clear_shows_message_when_invalid_property(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "clear", "a_account", "badproperty", NULL };
+
+    expect_any(accounts_account_exists, account_name);
+    will_return(accounts_account_exists, TRUE);
+
+    expect_cons_show("Invalid property: badproperty");
+    expect_cons_show("");
+
+    gboolean result = cmd_account(args, *help);
+    assert_true(result);
+
+    free(help);
+}
diff --git a/unittests/test_cmd_account.h b/unittests/test_cmd_account.h
new file mode 100644
index 00000000..91bd1e70
--- /dev/null
+++ b/unittests/test_cmd_account.h
@@ -0,0 +1,55 @@
+void cmd_account_shows_usage_when_not_connected_and_no_args(void **state);
+void cmd_account_shows_account_when_connected_and_no_args(void **state);
+void cmd_account_list_shows_accounts(void **state);
+void cmd_account_show_shows_usage_when_no_arg(void **state);
+void cmd_account_show_shows_message_when_account_does_not_exist(void **state);
+void cmd_account_show_shows_account_when_exists(void **state);
+void cmd_account_add_shows_usage_when_no_arg(void **state);
+void cmd_account_add_adds_account(void **state);
+void cmd_account_enable_shows_usage_when_no_arg(void **state);
+void cmd_account_enable_enables_account(void **state);
+void cmd_account_enable_shows_message_when_account_doesnt_exist(void **state);
+void cmd_account_disable_shows_usage_when_no_arg(void **state);
+void cmd_account_disable_disables_account(void **state);
+void cmd_account_disable_shows_message_when_account_doesnt_exist(void **state);
+void cmd_account_rename_shows_usage_when_no_args(void **state);
+void cmd_account_rename_shows_usage_when_one_arg(void **state);
+void cmd_account_rename_renames_account(void **state);
+void cmd_account_rename_shows_message_when_not_renamed(void **state);
+void cmd_account_set_shows_usage_when_no_args(void **state);
+void cmd_account_set_shows_usage_when_one_arg(void **state);
+void cmd_account_set_shows_usage_when_two_args(void **state);
+void cmd_account_set_shows_message_when_account_doesnt_exist(void **state);
+void cmd_account_set_jid_shows_message_for_malformed_jid(void **state);
+void cmd_account_set_jid_sets_barejid(void **state);
+void cmd_account_set_jid_sets_resource(void **state);
+void cmd_account_set_server_sets_server(void **state);
+void cmd_account_set_resource_sets_resource(void **state);
+void cmd_account_set_password_sets_password(void **state);
+void cmd_account_set_eval_password_sets_eval_password(void **state);
+void cmd_account_set_password_when_eval_password_set(void **state);
+void cmd_account_set_eval_password_when_password_set(void **state);
+void cmd_account_set_muc_sets_muc(void **state);
+void cmd_account_set_nick_sets_nick(void **state);
+void cmd_account_show_message_for_missing_otr_policy(void **state);
+void cmd_account_show_message_for_invalid_otr_policy(void **state);
+void cmd_account_set_otr_sets_otr(void **state);
+void cmd_account_set_status_shows_message_when_invalid_status(void **state);
+void cmd_account_set_status_sets_status_when_valid(void **state);
+void cmd_account_set_status_sets_status_when_last(void **state);
+void cmd_account_set_invalid_presence_string_priority_shows_message(void **state);
+void cmd_account_set_last_priority_shows_message(void **state);
+void cmd_account_set_online_priority_sets_preference(void **state);
+void cmd_account_set_chat_priority_sets_preference(void **state);
+void cmd_account_set_away_priority_sets_preference(void **state);
+void cmd_account_set_xa_priority_sets_preference(void **state);
+void cmd_account_set_dnd_priority_sets_preference(void **state);
+void cmd_account_set_priority_too_low_shows_message(void **state);
+void cmd_account_set_priority_too_high_shows_message(void **state);
+void cmd_account_set_priority_when_not_number_shows_message(void **state);
+void cmd_account_set_priority_when_empty_shows_message(void **state);
+void cmd_account_set_priority_updates_presence_when_account_connected_with_presence(void **state);
+void cmd_account_clear_shows_usage_when_no_args(void **state);
+void cmd_account_clear_shows_usage_when_one_arg(void **state);
+void cmd_account_clear_shows_message_when_account_doesnt_exist(void **state);
+void cmd_account_clear_shows_message_when_invalid_property(void **state);
diff --git a/unittests/test_cmd_alias.c b/unittests/test_cmd_alias.c
new file mode 100644
index 00000000..10ab7f53
--- /dev/null
+++ b/unittests/test_cmd_alias.c
@@ -0,0 +1,159 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "xmpp/xmpp.h"
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#include "config/preferences.h"
+
+#include "command/command.h"
+#include "command/commands.h"
+
+void cmd_alias_add_shows_usage_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "add", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_alias(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_alias_add_shows_usage_when_no_value(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "add", "alias", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_alias(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_alias_remove_shows_usage_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "remove", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_alias(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_alias_show_usage_when_invalid_subcmd(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "blah", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_alias(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_alias_add_adds_alias(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "add", "hc", "/help commands", NULL };
+
+    expect_cons_show("Command alias added /hc -> /help commands");
+
+    gboolean result = cmd_alias(args, *help);
+
+    char *returned_val = prefs_get_alias("hc");
+
+    assert_true(result);
+    assert_string_equal("/help commands", returned_val);
+
+    free(help);
+}
+
+void cmd_alias_add_shows_message_when_exists(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "add", "hc", "/help commands", NULL };
+
+    cmd_init();
+    prefs_add_alias("hc", "/help commands");
+    cmd_autocomplete_add("/hc");
+
+    expect_cons_show("Command or alias '/hc' already exists.");
+
+    gboolean result = cmd_alias(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_alias_remove_removes_alias(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "remove", "hn", NULL };
+
+    prefs_add_alias("hn", "/help navigation");
+
+    expect_cons_show("Command alias removed -> /hn");
+
+    gboolean result = cmd_alias(args, *help);
+
+    char *returned_val = prefs_get_alias("hn");
+
+    assert_true(result);
+    assert_null(returned_val);
+
+    free(help);
+}
+
+void cmd_alias_remove_shows_message_when_no_alias(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "remove", "hn", NULL };
+
+    expect_cons_show("No such command alias /hn");
+
+    gboolean result = cmd_alias(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_alias_list_shows_all_aliases(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "list", NULL };
+
+    prefs_add_alias("vy", "/vercheck on");
+    prefs_add_alias("q", "/quit");
+    prefs_add_alias("hn", "/help navigation");
+    prefs_add_alias("hc", "/help commands");
+    prefs_add_alias("vn", "/vercheck off");
+
+    // write a custom checker to check the correct list is passed
+    expect_any(cons_show_aliases, aliases);
+
+    gboolean result = cmd_alias(args, *help);
+    assert_true(result);
+
+    free(help);
+}
diff --git a/unittests/test_cmd_alias.h b/unittests/test_cmd_alias.h
new file mode 100644
index 00000000..bd93ef1a
--- /dev/null
+++ b/unittests/test_cmd_alias.h
@@ -0,0 +1,9 @@
+void cmd_alias_add_shows_usage_when_no_args(void **state);
+void cmd_alias_add_shows_usage_when_no_value(void **state);
+void cmd_alias_remove_shows_usage_when_no_args(void **state);
+void cmd_alias_show_usage_when_invalid_subcmd(void **state);
+void cmd_alias_add_adds_alias(void **state);
+void cmd_alias_add_shows_message_when_exists(void **state);
+void cmd_alias_remove_removes_alias(void **state);
+void cmd_alias_remove_shows_message_when_no_alias(void **state);
+void cmd_alias_list_shows_all_aliases(void **state);
diff --git a/unittests/test_cmd_bookmark.c b/unittests/test_cmd_bookmark.c
new file mode 100644
index 00000000..695e7362
--- /dev/null
+++ b/unittests/test_cmd_bookmark.c
@@ -0,0 +1,297 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "xmpp/xmpp.h"
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#include "muc.h"
+#include "common.h"
+
+#include "command/commands.h"
+
+#include "xmpp/bookmark.h"
+
+#include "helpers.h"
+
+static void test_with_connection_status(jabber_conn_status_t status)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+
+    will_return(jabber_get_connection_status, status);
+    expect_cons_show("You are not currently connected.");
+
+    gboolean result = cmd_bookmark(NULL, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_bookmark_shows_message_when_disconnected(void **state)
+{
+    test_with_connection_status(JABBER_DISCONNECTED);
+}
+
+void cmd_bookmark_shows_message_when_disconnecting(void **state)
+{
+    test_with_connection_status(JABBER_DISCONNECTING);
+}
+
+void cmd_bookmark_shows_message_when_connecting(void **state)
+{
+    test_with_connection_status(JABBER_CONNECTING);
+}
+
+void cmd_bookmark_shows_message_when_started(void **state)
+{
+    test_with_connection_status(JABBER_STARTED);
+}
+
+void cmd_bookmark_shows_message_when_undefined(void **state)
+{
+    test_with_connection_status(JABBER_UNDEFINED);
+}
+
+void cmd_bookmark_shows_usage_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CONSOLE);
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_bookmark(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+static void _free_bookmark(Bookmark *bookmark)
+{
+    free(bookmark->jid);
+    free(bookmark->nick);
+    free(bookmark);
+}
+
+static gboolean
+_cmp_bookmark(Bookmark *bm1, Bookmark *bm2)
+{
+    if (strcmp(bm1->jid, bm2->jid) != 0) {
+        return FALSE;
+    }
+    if (strcmp(bm1->nick, bm2->nick) != 0) {
+        return FALSE;
+    }
+    if (bm1->autojoin != bm2->autojoin) {
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+void cmd_bookmark_list_shows_bookmarks(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "list", NULL };
+    GList *bookmarks = NULL;
+
+    Bookmark *bm1 = malloc(sizeof(Bookmark));
+    bm1->jid = strdup("room1@conf.org");
+    bm1->nick = strdup("bob");
+    bm1->autojoin = FALSE;
+    Bookmark *bm2 = malloc(sizeof(Bookmark));
+    bm2->jid = strdup("room2@conf.org");
+    bm2->nick = strdup("steve");
+    bm2->autojoin = TRUE;
+    Bookmark *bm3 = malloc(sizeof(Bookmark));
+    bm3->jid = strdup("room3@conf.org");
+    bm3->nick = strdup("dave");
+    bm3->autojoin = TRUE;
+    Bookmark *bm4 = malloc(sizeof(Bookmark));
+    bm4->jid = strdup("room4@conf.org");
+    bm4->nick = strdup("james");
+    bm4->autojoin = FALSE;
+    Bookmark *bm5 = malloc(sizeof(Bookmark));
+    bm5->jid = strdup("room5@conf.org");
+    bm5->nick = strdup("mike");
+    bm5->autojoin = FALSE;
+
+    bookmarks = g_list_append(bookmarks, bm1);
+    bookmarks = g_list_append(bookmarks, bm2);
+    bookmarks = g_list_append(bookmarks, bm3);
+    bookmarks = g_list_append(bookmarks, bm4);
+    bookmarks = g_list_append(bookmarks, bm5);
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CONSOLE);
+    will_return(bookmark_get_list, bookmarks);
+
+    // TODO - Custom list compare
+    glist_set_cmp((GCompareFunc)_cmp_bookmark);
+    expect_any(cons_show_bookmarks, list);
+
+    gboolean result = cmd_bookmark(args, *help);
+    assert_true(result);
+
+    free(help);
+    g_list_free_full(bookmarks, (GDestroyNotify)_free_bookmark);
+}
+
+void cmd_bookmark_add_shows_message_when_invalid_jid(void **state)
+{
+    char *jid = "room";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "add", jid, NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CONSOLE);
+
+    expect_cons_show("Can't add bookmark with JID 'room'; should be 'room@domain.tld'");
+
+    gboolean result = cmd_bookmark(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_bookmark_add_adds_bookmark_with_jid(void **state)
+{
+    char *jid = "room@conf.server";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "add", jid, NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CONSOLE);
+
+    expect_string(bookmark_add, jid, jid);
+    expect_any(bookmark_add, nick);
+    expect_any(bookmark_add, password);
+    expect_any(bookmark_add, autojoin_str);
+    will_return(bookmark_add, TRUE);
+
+    expect_cons_show("Bookmark added for room@conf.server.");
+
+    gboolean result = cmd_bookmark(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_bookmark_add_adds_bookmark_with_jid_nick(void **state)
+{    char *jid = "room@conf.server";
+    char *nick = "bob";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "add", jid, "nick", nick, NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CONSOLE);
+
+    expect_string(bookmark_add, jid, jid);
+    expect_string(bookmark_add, nick, nick);
+    expect_any(bookmark_add, password);
+    expect_any(bookmark_add, autojoin_str);
+    will_return(bookmark_add, TRUE);
+
+    expect_cons_show("Bookmark added for room@conf.server.");
+
+    gboolean result = cmd_bookmark(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_bookmark_add_adds_bookmark_with_jid_autojoin(void **state)
+{
+    char *jid = "room@conf.server";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "add", jid, "autojoin", "on", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CONSOLE);
+
+    expect_string(bookmark_add, jid, jid);
+    expect_any(bookmark_add, nick);
+    expect_any(bookmark_add, password);
+    expect_string(bookmark_add, autojoin_str, "on");
+    will_return(bookmark_add, TRUE);
+
+    expect_cons_show("Bookmark added for room@conf.server.");
+
+    gboolean result = cmd_bookmark(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_bookmark_add_adds_bookmark_with_jid_nick_autojoin(void **state)
+{
+    char *jid = "room@conf.server";
+    char *nick = "bob";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "add", jid, "nick", nick, "autojoin", "on", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CONSOLE);
+
+    expect_string(bookmark_add, jid, jid);
+    expect_string(bookmark_add, nick, nick);
+    expect_any(bookmark_add, password);
+    expect_string(bookmark_add, autojoin_str, "on");
+    will_return(bookmark_add, TRUE);
+
+    expect_cons_show("Bookmark added for room@conf.server.");
+
+    gboolean result = cmd_bookmark(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_bookmark_remove_removes_bookmark(void **state)
+{
+    char *jid = "room@conf.server";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "remove", jid, NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CONSOLE);
+
+    expect_string(bookmark_remove, jid, jid);
+    will_return(bookmark_remove, TRUE);
+
+    expect_cons_show("Bookmark removed for room@conf.server.");
+
+    gboolean result = cmd_bookmark(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_bookmark_remove_shows_message_when_no_bookmark(void **state)
+{
+    char *jid = "room@conf.server";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "remove", jid, NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CONSOLE);
+
+    expect_any(bookmark_remove, jid);
+    will_return(bookmark_remove, FALSE);
+
+    expect_cons_show("No bookmark exists for room@conf.server.");
+
+    gboolean result = cmd_bookmark(args, *help);
+    assert_true(result);
+
+    free(help);
+}
diff --git a/unittests/test_cmd_bookmark.h b/unittests/test_cmd_bookmark.h
new file mode 100644
index 00000000..9505e105
--- /dev/null
+++ b/unittests/test_cmd_bookmark.h
@@ -0,0 +1,14 @@
+void cmd_bookmark_shows_message_when_disconnected(void **state);
+void cmd_bookmark_shows_message_when_disconnecting(void **state);
+void cmd_bookmark_shows_message_when_connecting(void **state);
+void cmd_bookmark_shows_message_when_started(void **state);
+void cmd_bookmark_shows_message_when_undefined(void **state);
+void cmd_bookmark_shows_usage_when_no_args(void **state);
+void cmd_bookmark_list_shows_bookmarks(void **state);
+void cmd_bookmark_add_shows_message_when_invalid_jid(void **state);
+void cmd_bookmark_add_adds_bookmark_with_jid(void **state);
+void cmd_bookmark_add_adds_bookmark_with_jid_nick(void **state);
+void cmd_bookmark_add_adds_bookmark_with_jid_autojoin(void **state);
+void cmd_bookmark_add_adds_bookmark_with_jid_nick_autojoin(void **state);
+void cmd_bookmark_remove_removes_bookmark(void **state);
+void cmd_bookmark_remove_shows_message_when_no_bookmark(void **state);
diff --git a/unittests/test_cmd_connect.c b/unittests/test_cmd_connect.c
new file mode 100644
index 00000000..e2089a09
--- /dev/null
+++ b/unittests/test_cmd_connect.c
@@ -0,0 +1,478 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "xmpp/xmpp.h"
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#include "command/commands.h"
+#include "config/accounts.h"
+
+static void test_with_connection_status(jabber_conn_status_t status)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+
+    will_return(jabber_get_connection_status, status);
+
+    expect_cons_show("You are either connected already, or a login is in process.");
+
+    gboolean result = cmd_connect(NULL, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_message_when_disconnecting(void **state)
+{
+    test_with_connection_status(JABBER_DISCONNECTING);
+}
+
+void cmd_connect_shows_message_when_connecting(void **state)
+{
+    test_with_connection_status(JABBER_CONNECTING);
+}
+
+void cmd_connect_shows_message_when_connected(void **state)
+{
+    test_with_connection_status(JABBER_CONNECTED);
+}
+
+void cmd_connect_shows_message_when_undefined(void **state)
+{
+    test_with_connection_status(JABBER_UNDEFINED);
+}
+
+void cmd_connect_shows_usage_when_no_server_value(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "user@server.org", "server", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Usage: some usage");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_usage_when_server_no_port_value(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "user@server.org", "server", "aserver", "port", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Usage: some usage");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_usage_when_no_port_value(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "user@server.org", "port", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Usage: some usage");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_usage_when_port_no_server_value(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "user@server.org", "port", "5678", "server", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Usage: some usage");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_message_when_port_0(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "user@server.org", "port", "0", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Value 0 out of range. Must be in 1..65535.");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_message_when_port_minus1(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "user@server.org", "port", "-1", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Value -1 out of range. Must be in 1..65535.");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_message_when_port_65536(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "user@server.org", "port", "65536", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Value 65536 out of range. Must be in 1..65535.");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_message_when_port_contains_chars(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "user@server.org", "port", "52f66", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Could not convert \"52f66\" to a number.");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_usage_when_server_provided_twice(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "user@server.org", "server", "server1", "server", "server2", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Usage: some usage");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_usage_when_port_provided_twice(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "user@server.org", "port", "1111", "port", "1111", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Usage: some usage");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_usage_when_invalid_first_property(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "user@server.org", "wrong", "server", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Usage: some usage");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_usage_when_invalid_second_property(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "user@server.org", "server", "aserver", "wrong", "1234", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("Usage: some usage");
+    expect_cons_show("");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_when_no_account(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "user@server.org", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_string(accounts_get_account, name, "user@server.org");
+    will_return(accounts_get_account, NULL);
+
+    will_return(ui_ask_password, strdup("password"));
+
+    expect_cons_show("Connecting as user@server.org");
+
+    expect_string(jabber_connect_with_details, jid, "user@server.org");
+    expect_string(jabber_connect_with_details, passwd, "password");
+    expect_value(jabber_connect_with_details, altdomain, NULL);
+    expect_value(jabber_connect_with_details, port, 0);
+    will_return(jabber_connect_with_details, JABBER_CONNECTING);
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_with_server_when_provided(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "user@server.org", "server", "aserver", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_string(accounts_get_account, name, "user@server.org");
+    will_return(accounts_get_account, NULL);
+
+    will_return(ui_ask_password, strdup("password"));
+
+    expect_cons_show("Connecting as user@server.org");
+
+    expect_string(jabber_connect_with_details, jid, "user@server.org");
+    expect_string(jabber_connect_with_details, passwd, "password");
+    expect_string(jabber_connect_with_details, altdomain, "aserver");
+    expect_value(jabber_connect_with_details, port, 0);
+    will_return(jabber_connect_with_details, JABBER_CONNECTING);
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_with_port_when_provided(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "user@server.org", "port", "5432", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_string(accounts_get_account, name, "user@server.org");
+    will_return(accounts_get_account, NULL);
+
+    will_return(ui_ask_password, strdup("password"));
+
+    expect_cons_show("Connecting as user@server.org");
+
+    expect_string(jabber_connect_with_details, jid, "user@server.org");
+    expect_string(jabber_connect_with_details, passwd, "password");
+    expect_value(jabber_connect_with_details, altdomain, NULL);
+    expect_value(jabber_connect_with_details, port, 5432);
+    will_return(jabber_connect_with_details, JABBER_CONNECTING);
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_with_server_and_port_when_provided(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "user@server.org", "port", "5432", "server", "aserver", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_string(accounts_get_account, name, "user@server.org");
+    will_return(accounts_get_account, NULL);
+
+    will_return(ui_ask_password, strdup("password"));
+
+    expect_cons_show("Connecting as user@server.org");
+
+    expect_string(jabber_connect_with_details, jid, "user@server.org");
+    expect_string(jabber_connect_with_details, passwd, "password");
+    expect_string(jabber_connect_with_details, altdomain, "aserver");
+    expect_value(jabber_connect_with_details, port, 5432);
+    will_return(jabber_connect_with_details, JABBER_CONNECTING);
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_fail_message(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "user@server.org", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_any(accounts_get_account, name);
+    will_return(accounts_get_account, NULL);
+
+    will_return(ui_ask_password, strdup("password"));
+
+    expect_cons_show("Connecting as user@server.org");
+
+    expect_any(jabber_connect_with_details, jid);
+    expect_any(jabber_connect_with_details, passwd);
+    expect_any(jabber_connect_with_details, altdomain);
+    expect_any(jabber_connect_with_details, port);
+    will_return(jabber_connect_with_details, JABBER_DISCONNECTED);
+
+    expect_cons_show_error("Connection attempt for user@server.org failed.");
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_lowercases_argument(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "USER@server.ORG", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_string(accounts_get_account, name, "user@server.org");
+    will_return(accounts_get_account, NULL);
+
+    will_return(ui_ask_password, strdup("password"));
+
+    expect_cons_show("Connecting as user@server.org");
+
+    expect_any(jabber_connect_with_details, jid);
+    expect_any(jabber_connect_with_details, passwd);
+    expect_any(jabber_connect_with_details, altdomain);
+    expect_any(jabber_connect_with_details, port);
+    will_return(jabber_connect_with_details, JABBER_CONNECTING);
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_asks_password_when_not_in_account(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "jabber_org", NULL };
+    ProfAccount *account = account_new("jabber_org", "me@jabber.org", NULL, NULL,
+        TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_any(accounts_get_account, name);
+    will_return(accounts_get_account, account);
+
+    will_return(ui_ask_password, strdup("password"));
+
+    expect_cons_show("Connecting with account jabber_org as me@jabber.org");
+
+    expect_any(jabber_connect_with_account, account);
+    will_return(jabber_connect_with_account, JABBER_CONNECTING);
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_shows_message_when_connecting_with_account(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "jabber_org", NULL };
+    ProfAccount *account = account_new("jabber_org", "user@jabber.org", "password", NULL,
+        TRUE, NULL, 0, "laptop", NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_any(accounts_get_account, name);
+    will_return(accounts_get_account, account);
+
+    expect_cons_show("Connecting with account jabber_org as user@jabber.org/laptop");
+
+    expect_any(jabber_connect_with_account, account);
+    will_return(jabber_connect_with_account, JABBER_CONNECTING);
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_connect_connects_with_account(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "jabber_org", NULL };
+    ProfAccount *account = account_new("jabber_org", "me@jabber.org", "password", NULL,
+        TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_any(accounts_get_account, name);
+    will_return(accounts_get_account, account);
+
+    expect_cons_show("Connecting with account jabber_org as me@jabber.org");
+
+    expect_memory(jabber_connect_with_account, account, account, sizeof(account));
+    will_return(jabber_connect_with_account, JABBER_CONNECTING);
+
+    gboolean result = cmd_connect(args, *help);
+    assert_true(result);
+
+    free(help);
+}
diff --git a/unittests/test_cmd_connect.h b/unittests/test_cmd_connect.h
new file mode 100644
index 00000000..ad27a0a5
--- /dev/null
+++ b/unittests/test_cmd_connect.h
@@ -0,0 +1,26 @@
+void cmd_connect_shows_message_when_disconnecting(void **state);
+void cmd_connect_shows_message_when_connecting(void **state);
+void cmd_connect_shows_message_when_connected(void **state);
+void cmd_connect_shows_message_when_undefined(void **state);
+void cmd_connect_when_no_account(void **state);
+void cmd_connect_with_altdomain_when_provided(void **state);
+void cmd_connect_fail_message(void **state);
+void cmd_connect_lowercases_argument(void **state);
+void cmd_connect_asks_password_when_not_in_account(void **state);
+void cmd_connect_shows_message_when_connecting_with_account(void **state);
+void cmd_connect_connects_with_account(void **state);
+void cmd_connect_shows_usage_when_no_server_value(void **state);
+void cmd_connect_shows_usage_when_server_no_port_value(void **state);
+void cmd_connect_shows_usage_when_no_port_value(void **state);
+void cmd_connect_shows_usage_when_port_no_server_value(void **state);
+void cmd_connect_shows_message_when_port_0(void **state);
+void cmd_connect_shows_message_when_port_minus1(void **state);
+void cmd_connect_shows_message_when_port_65536(void **state);
+void cmd_connect_shows_message_when_port_contains_chars(void **state);
+void cmd_connect_with_server_when_provided(void **state);
+void cmd_connect_with_port_when_provided(void **state);
+void cmd_connect_with_server_and_port_when_provided(void **state);
+void cmd_connect_shows_usage_when_server_provided_twice(void **state);
+void cmd_connect_shows_usage_when_port_provided_twice(void **state);
+void cmd_connect_shows_usage_when_invalid_first_property(void **state);
+void cmd_connect_shows_usage_when_invalid_second_property(void **state);
diff --git a/unittests/test_cmd_disconnect.c b/unittests/test_cmd_disconnect.c
new file mode 100644
index 00000000..68253820
--- /dev/null
+++ b/unittests/test_cmd_disconnect.c
@@ -0,0 +1,37 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "chat_session.h"
+#include "command/commands.h"
+#include "xmpp/xmpp.h"
+#include "roster_list.h"
+
+#include "ui/stub_ui.h"
+
+void clears_chat_sessions(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+
+    chat_sessions_init();
+    roster_init();
+    chat_session_recipient_active("bob@server.org", "laptop", FALSE);
+    chat_session_recipient_active("mike@server.org", "work", FALSE);
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_fulljid, "myjid@myserver.com");
+    expect_any_cons_show();
+
+    gboolean result = cmd_disconnect(NULL, *help);
+
+    assert_true(result);
+
+    ChatSession *session1 = chat_session_get("bob@server.org");
+    ChatSession *session2 = chat_session_get("mike@server.org");
+    assert_null(session1);
+    assert_null(session2);
+    free(help);
+}
\ No newline at end of file
diff --git a/unittests/test_cmd_disconnect.h b/unittests/test_cmd_disconnect.h
new file mode 100644
index 00000000..856b501e
--- /dev/null
+++ b/unittests/test_cmd_disconnect.h
@@ -0,0 +1 @@
+void clears_chat_sessions(void **state);
diff --git a/unittests/test_cmd_join.c b/unittests/test_cmd_join.c
new file mode 100644
index 00000000..19824b3a
--- /dev/null
+++ b/unittests/test_cmd_join.c
@@ -0,0 +1,184 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "xmpp/xmpp.h"
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#include "config/accounts.h"
+
+#include "command/commands.h"
+#include "muc.h"
+
+static void test_with_connection_status(jabber_conn_status_t status)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+
+    will_return(jabber_get_connection_status, status);
+
+    expect_cons_show("You are not currently connected.");
+
+    gboolean result = cmd_join(NULL, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_join_shows_message_when_disconnecting(void **state)
+{
+    test_with_connection_status(JABBER_DISCONNECTING);
+}
+
+void cmd_join_shows_message_when_connecting(void **state)
+{
+    test_with_connection_status(JABBER_CONNECTING);
+}
+
+void cmd_join_shows_message_when_disconnected(void **state)
+{
+    test_with_connection_status(JABBER_DISCONNECTED);
+}
+
+void cmd_join_shows_message_when_undefined(void **state)
+{
+    test_with_connection_status(JABBER_UNDEFINED);
+}
+
+void cmd_join_shows_error_message_when_invalid_room_jid(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "//@@/", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show_error("Specified room has incorrect format.");
+    expect_cons_show("");
+
+    gboolean result = cmd_join(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_join_uses_account_mucservice_when_no_service_specified(void **state)
+{
+    char *account_name = "an_account";
+    char *room = "room";
+    char *nick = "bob";
+    char *account_service = "conference.server.org";
+    char *expected_room = "room@conference.server.org";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { room, "nick", nick, NULL };
+    ProfAccount *account = account_new(account_name, "user@server.org", NULL, NULL,
+        TRUE, NULL, 0, "laptop", NULL, NULL, 0, 0, 0, 0, 0, account_service, NULL, NULL, NULL, NULL, NULL);
+
+    muc_init();
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_account_name, account_name);
+
+    expect_string(accounts_get_account, name, account_name);
+    will_return(accounts_get_account, account);
+
+    expect_string(presence_join_room, room, expected_room);
+    expect_string(presence_join_room, nick, nick);
+    expect_value(presence_join_room, passwd, NULL);
+
+    gboolean result = cmd_join(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_join_uses_supplied_nick(void **state)
+{
+    char *account_name = "an_account";
+    char *room = "room@conf.server.org";
+    char *nick = "bob";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { room, "nick", nick, NULL };
+    ProfAccount *account = account_new(account_name, "user@server.org", NULL, NULL,
+        TRUE, NULL, 0, "laptop", NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+    muc_init();
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_account_name, account_name);
+
+    expect_string(accounts_get_account, name, account_name);
+    will_return(accounts_get_account, account);
+
+    expect_string(presence_join_room, room, room);
+    expect_string(presence_join_room, nick, nick);
+    expect_value(presence_join_room, passwd, NULL);
+
+    gboolean result = cmd_join(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_join_uses_account_nick_when_not_supplied(void **state)
+{
+    char *account_name = "an_account";
+    char *room = "room2@conf.server.org";
+    char *account_nick = "a_nick";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { room, NULL };
+    ProfAccount *account = account_new(account_name, "user@server.org", NULL, NULL,
+        TRUE, NULL, 0, "laptop", NULL, NULL, 0, 0, 0, 0, 0, NULL, account_nick, NULL, NULL, NULL, NULL);
+
+    muc_init();
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_account_name, account_name);
+
+    expect_string(accounts_get_account, name, account_name);
+    will_return(accounts_get_account, account);
+
+    expect_string(presence_join_room, room, room);
+    expect_string(presence_join_room, nick, account_nick);
+    expect_value(presence_join_room, passwd, NULL);
+
+    gboolean result = cmd_join(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_join_uses_password_when_supplied(void **state)
+{
+    char *account_name = "an_account";
+    char *room = "room";
+    char *password = "a_password";
+    char *account_nick = "a_nick";
+    char *account_service = "a_service";
+    char *expected_room = "room@a_service";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { room, "password", password, NULL };
+    ProfAccount *account = account_new(account_name, "user@server.org", NULL, NULL,
+        TRUE, NULL, 0, "laptop", NULL, NULL, 0, 0, 0, 0, 0, account_service, account_nick, NULL, NULL, NULL, NULL);
+
+    muc_init();
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_account_name, account_name);
+
+    expect_string(accounts_get_account, name, account_name);
+    will_return(accounts_get_account, account);
+
+    expect_string(presence_join_room, room, expected_room);
+    expect_string(presence_join_room, nick, account_nick);
+    expect_value(presence_join_room, passwd, password);
+
+    gboolean result = cmd_join(args, *help);
+    assert_true(result);
+
+    free(help);
+}
diff --git a/unittests/test_cmd_join.h b/unittests/test_cmd_join.h
new file mode 100644
index 00000000..a96fa435
--- /dev/null
+++ b/unittests/test_cmd_join.h
@@ -0,0 +1,9 @@
+void cmd_join_shows_message_when_disconnecting(void **state);
+void cmd_join_shows_message_when_connecting(void **state);
+void cmd_join_shows_message_when_disconnected(void **state);
+void cmd_join_shows_message_when_undefined(void **state);
+void cmd_join_shows_error_message_when_invalid_room_jid(void **state);
+void cmd_join_uses_account_mucservice_when_no_service_specified(void **state);
+void cmd_join_uses_supplied_nick(void **state);
+void cmd_join_uses_account_nick_when_not_supplied(void **state);
+void cmd_join_uses_password_when_supplied(void **state);
diff --git a/unittests/test_cmd_otr.c b/unittests/test_cmd_otr.c
new file mode 100644
index 00000000..dae17947
--- /dev/null
+++ b/unittests/test_cmd_otr.c
@@ -0,0 +1,578 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "config.h"
+
+#ifdef HAVE_LIBOTR
+#include <libotr/proto.h>
+#include "otr/otr.h"
+#endif
+
+#include "config/preferences.h"
+
+#include "command/command.h"
+#include "command/commands.h"
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#ifdef HAVE_LIBOTR
+void cmd_otr_shows_usage_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "Some usage";
+    gchar *args[] = { NULL };
+
+    expect_cons_show("Usage: Some usage");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_shows_usage_when_invalid_subcommand(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "Some usage";
+    gchar *args[] = { "unknown", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show("Usage: Some usage");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_log_shows_usage_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "Some usage";
+    gchar *args[] = { "log", NULL };
+
+    expect_cons_show("Usage: Some usage");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_log_shows_usage_when_invalid_subcommand(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "Some usage";
+    gchar *args[] = { "log", "wrong", NULL };
+
+    expect_cons_show("Usage: Some usage");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_log_on_enables_logging(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "log", "on", NULL };
+    prefs_set_string(PREF_OTR_LOG, "off");
+    prefs_set_boolean(PREF_CHLOG, TRUE);
+
+    expect_cons_show("OTR messages will be logged as plaintext.");
+
+    gboolean result = cmd_otr(args, *help);
+    char *pref_otr_log = prefs_get_string(PREF_OTR_LOG);
+
+    assert_true(result);
+    assert_string_equal("on", pref_otr_log);
+
+    free(help);
+}
+
+void cmd_otr_log_on_shows_warning_when_chlog_disabled(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "log", "on", NULL };
+    prefs_set_string(PREF_OTR_LOG, "off");
+    prefs_set_boolean(PREF_CHLOG, FALSE);
+
+    expect_cons_show("OTR messages will be logged as plaintext.");
+    expect_cons_show("Chat logging is currently disabled, use '/chlog on' to enable.");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_log_off_disables_logging(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "log", "off", NULL };
+    prefs_set_string(PREF_OTR_LOG, "on");
+    prefs_set_boolean(PREF_CHLOG, TRUE);
+
+    expect_cons_show("OTR message logging disabled.");
+
+    gboolean result = cmd_otr(args, *help);
+    char *pref_otr_log = prefs_get_string(PREF_OTR_LOG);
+
+    assert_true(result);
+    assert_string_equal("off", pref_otr_log);
+
+    free(help);
+}
+
+void cmd_otr_redact_redacts_logging(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "log", "redact", NULL };
+    prefs_set_string(PREF_OTR_LOG, "on");
+    prefs_set_boolean(PREF_CHLOG, TRUE);
+
+    expect_cons_show("OTR messages will be logged as '[redacted]'.");
+
+    gboolean result = cmd_otr(args, *help);
+    char *pref_otr_log = prefs_get_string(PREF_OTR_LOG);
+
+    assert_true(result);
+    assert_string_equal("redact", pref_otr_log);
+
+    free(help);
+}
+
+void cmd_otr_log_redact_shows_warning_when_chlog_disabled(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "log", "redact", NULL };
+    prefs_set_string(PREF_OTR_LOG, "off");
+    prefs_set_boolean(PREF_CHLOG, FALSE);
+
+    expect_cons_show("OTR messages will be logged as '[redacted]'.");
+    expect_cons_show("Chat logging is currently disabled, use '/chlog on' to enable.");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_warn_shows_usage_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "Some usage";
+    gchar *args[] = { "warn", NULL };
+
+    expect_cons_show("Usage: Some usage");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_warn_shows_usage_when_invalid_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "Some usage";
+    gchar *args[] = { "warn", "badarg", NULL };
+
+    expect_cons_show("Usage: Some usage");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_warn_on_enables_unencrypted_warning(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "warn", "on", NULL };
+    prefs_set_boolean(PREF_OTR_WARN, FALSE);
+
+    expect_cons_show("OTR warning message enabled.");
+
+    gboolean result = cmd_otr(args, *help);
+    gboolean otr_warn_enabled = prefs_get_boolean(PREF_OTR_WARN);
+
+    assert_true(result);
+    assert_true(otr_warn_enabled);
+
+    free(help);
+}
+
+void cmd_otr_warn_off_disables_unencrypted_warning(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "warn", "off", NULL };
+    prefs_set_boolean(PREF_OTR_WARN, TRUE);
+
+    expect_cons_show("OTR warning message disabled.");
+
+    gboolean result = cmd_otr(args, *help);
+    gboolean otr_warn_enabled = prefs_get_boolean(PREF_OTR_WARN);
+
+    assert_true(result);
+    assert_false(otr_warn_enabled);
+
+    free(help);
+}
+
+void cmd_otr_libver_shows_libotr_version(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "libver", NULL };
+    char *version = "9.9.9";
+    GString *message = g_string_new("Using libotr version ");
+    g_string_append(message, version);
+
+    will_return(otr_libotr_version, version);
+
+    expect_cons_show(message->str);
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    g_string_free(message, TRUE);
+    free(help);
+}
+
+void cmd_otr_gen_shows_message_when_not_connected(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "gen", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("You must be connected with an account to load OTR information.");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+static void test_with_command_and_connection_status(char *command, jabber_conn_status_t status)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { command, NULL };
+
+    will_return(jabber_get_connection_status, status);
+
+    expect_cons_show("You must be connected with an account to load OTR information.");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_gen_shows_message_when_disconnected(void **state)
+{
+    test_with_command_and_connection_status("gen", JABBER_DISCONNECTED);
+}
+
+void cmd_otr_gen_shows_message_when_undefined(void **state)
+{
+    test_with_command_and_connection_status("gen", JABBER_UNDEFINED);
+}
+
+void cmd_otr_gen_shows_message_when_started(void **state)
+{
+    test_with_command_and_connection_status("gen", JABBER_STARTED);
+}
+
+void cmd_otr_gen_shows_message_when_connecting(void **state)
+{
+    test_with_command_and_connection_status("gen", JABBER_CONNECTING);
+}
+
+void cmd_otr_gen_shows_message_when_disconnecting(void **state)
+{
+    test_with_command_and_connection_status("gen", JABBER_DISCONNECTING);
+}
+
+void cmd_otr_gen_generates_key_for_connected_account(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "gen", NULL };
+    char *account_name = "myaccount";
+    ProfAccount *account = account_new(account_name, "me@jabber.org", NULL, NULL,
+        TRUE, NULL, 0, NULL, NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_account_name, account_name);
+
+    expect_string(accounts_get_account, name, account_name);
+
+    will_return(accounts_get_account, account);
+
+    expect_memory(otr_keygen, account, account, sizeof(ProfAccount));
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_myfp_shows_message_when_disconnected(void **state)
+{
+    test_with_command_and_connection_status("myfp", JABBER_DISCONNECTED);
+}
+
+void cmd_otr_myfp_shows_message_when_undefined(void **state)
+{
+    test_with_command_and_connection_status("myfp", JABBER_UNDEFINED);
+}
+
+void cmd_otr_myfp_shows_message_when_started(void **state)
+{
+    test_with_command_and_connection_status("myfp", JABBER_STARTED);
+}
+
+void cmd_otr_myfp_shows_message_when_connecting(void **state)
+{
+    test_with_command_and_connection_status("myfp", JABBER_CONNECTING);
+}
+
+void cmd_otr_myfp_shows_message_when_disconnecting(void **state)
+{
+    test_with_command_and_connection_status("myfp", JABBER_DISCONNECTING);
+}
+
+void cmd_otr_myfp_shows_message_when_no_key(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "myfp", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(otr_key_loaded, FALSE);
+
+    expect_ui_current_print_formatted_line('!', 0, "You have not generated or loaded a private key, use '/otr gen'");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_myfp_shows_my_fingerprint(void **state)
+{
+    char *fingerprint = "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD EEEEEEEE";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "myfp", NULL };
+    GString *message = g_string_new("Your OTR fingerprint: ");
+    g_string_append(message, fingerprint);
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(otr_key_loaded, TRUE);
+    will_return(otr_get_my_fingerprint, strdup(fingerprint));
+
+    expect_ui_current_print_formatted_line('!', 0, message->str);
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    g_string_free(message, TRUE);
+    free(help);
+}
+
+static void
+test_cmd_otr_theirfp_from_wintype(win_type_t wintype)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "theirfp", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, wintype);
+
+    expect_ui_current_print_line("You must be in a regular chat window to view a recipient's fingerprint.");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_theirfp_shows_message_when_in_console(void **state)
+{
+    test_cmd_otr_theirfp_from_wintype(WIN_CONSOLE);
+}
+
+void cmd_otr_theirfp_shows_message_when_in_muc(void **state)
+{
+    test_cmd_otr_theirfp_from_wintype(WIN_MUC);
+}
+
+void cmd_otr_theirfp_shows_message_when_in_private(void **state)
+{
+    test_cmd_otr_theirfp_from_wintype(WIN_PRIVATE);
+}
+
+void cmd_otr_theirfp_shows_message_when_non_otr_chat_window(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "theirfp", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CHAT);
+    will_return(ui_current_win_is_otr, FALSE);
+
+    expect_ui_current_print_formatted_line('!', 0, "You are not currently in an OTR session.");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_theirfp_shows_fingerprint(void **state)
+{
+    char *recipient = "someone@chat.com";
+    char *fingerprint = "AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD EEEEEEEE";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "theirfp", NULL };
+    ProfChatWin *chatwin = malloc(sizeof(ProfChatWin));
+    chatwin->barejid = strdup(recipient);
+    GString *message = g_string_new(chatwin->barejid);
+    g_string_append(message, "'s OTR fingerprint: ");
+    g_string_append(message, fingerprint);
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CHAT);
+    will_return(ui_get_current_chat, chatwin);
+    will_return(ui_current_win_is_otr, TRUE);
+
+    expect_string(otr_get_their_fingerprint, recipient, chatwin->barejid);
+    will_return(otr_get_their_fingerprint, strdup(fingerprint));
+
+    expect_ui_current_print_formatted_line('!', 0, message->str);
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    g_string_free(message, TRUE);
+    free(help);
+    free(chatwin->barejid);
+    free(chatwin);
+}
+
+static void
+test_cmd_otr_start_from_wintype(win_type_t wintype)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "start", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, wintype);
+
+    expect_ui_current_print_line("You must be in a regular chat window to start an OTR session.");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_start_shows_message_when_in_console(void **state)
+{
+    test_cmd_otr_start_from_wintype(WIN_CONSOLE);
+}
+
+void cmd_otr_start_shows_message_when_in_muc(void **state)
+{
+    test_cmd_otr_start_from_wintype(WIN_MUC);
+}
+
+void cmd_otr_start_shows_message_when_in_private(void **state)
+{
+    test_cmd_otr_start_from_wintype(WIN_PRIVATE);
+}
+
+void cmd_otr_start_shows_message_when_already_started(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "start", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CHAT);
+    will_return(ui_current_win_is_otr, TRUE);
+
+    expect_ui_current_print_formatted_line('!', 0, "You are already in an OTR session.");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_otr_start_shows_message_when_no_key(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "start", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CHAT);
+    will_return(ui_current_win_is_otr, FALSE);
+    will_return(otr_key_loaded, FALSE);
+
+    expect_ui_current_print_formatted_line('!', 0, "You have not generated or loaded a private key, use '/otr gen'");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void
+cmd_otr_start_sends_otr_query_message_to_current_recipeint(void **state)
+{
+    char *recipient = "buddy@chat.com";
+    ProfChatWin *chatwin = malloc(sizeof(ProfChatWin));
+    chatwin->barejid = strdup(recipient);
+    char *query_message = "?OTR?";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "start", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(ui_current_win_type, WIN_CHAT);
+    will_return(ui_get_current_chat, chatwin);
+    will_return(ui_current_win_is_otr, FALSE);
+    will_return(otr_key_loaded, TRUE);
+    will_return(otr_start_query, query_message);
+
+    expect_string(message_send_chat_encrypted, barejid, chatwin->barejid);
+    expect_string(message_send_chat_encrypted, msg, query_message);
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+    free(chatwin->barejid);
+    free(chatwin);
+}
+
+#else
+void cmd_otr_shows_message_when_otr_unsupported(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "gen", NULL };
+
+    expect_cons_show("This version of Profanity has not been built with OTR support enabled");
+
+    gboolean result = cmd_otr(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+#endif
diff --git a/unittests/test_cmd_otr.h b/unittests/test_cmd_otr.h
new file mode 100644
index 00000000..8ef182e9
--- /dev/null
+++ b/unittests/test_cmd_otr.h
@@ -0,0 +1,45 @@
+#include "config.h"
+
+#ifdef HAVE_LIBOTR
+void cmd_otr_shows_usage_when_no_args(void **state);
+void cmd_otr_shows_usage_when_invalid_subcommand(void **state);
+void cmd_otr_log_shows_usage_when_no_args(void **state);
+void cmd_otr_log_shows_usage_when_invalid_subcommand(void **state);
+void cmd_otr_log_on_enables_logging(void **state);
+void cmd_otr_log_off_disables_logging(void **state);
+void cmd_otr_redact_redacts_logging(void **state);
+void cmd_otr_log_on_shows_warning_when_chlog_disabled(void **state);
+void cmd_otr_log_redact_shows_warning_when_chlog_disabled(void **state);
+void cmd_otr_warn_shows_usage_when_no_args(void **state);
+void cmd_otr_warn_shows_usage_when_invalid_arg(void **state);
+void cmd_otr_warn_on_enables_unencrypted_warning(void **state);
+void cmd_otr_warn_off_disables_unencrypted_warning(void **state);
+void cmd_otr_libver_shows_libotr_version(void **state);
+void cmd_otr_gen_shows_message_when_not_connected(void **state);
+void cmd_otr_gen_generates_key_for_connected_account(void **state);
+void cmd_otr_gen_shows_message_when_disconnected(void **state);
+void cmd_otr_gen_shows_message_when_undefined(void **state);
+void cmd_otr_gen_shows_message_when_started(void **state);
+void cmd_otr_gen_shows_message_when_connecting(void **state);
+void cmd_otr_gen_shows_message_when_disconnecting(void **state);
+void cmd_otr_myfp_shows_message_when_disconnected(void **state);
+void cmd_otr_myfp_shows_message_when_undefined(void **state);
+void cmd_otr_myfp_shows_message_when_started(void **state);
+void cmd_otr_myfp_shows_message_when_connecting(void **state);
+void cmd_otr_myfp_shows_message_when_disconnecting(void **state);
+void cmd_otr_myfp_shows_message_when_no_key(void **state);
+void cmd_otr_myfp_shows_my_fingerprint(void **state);
+void cmd_otr_theirfp_shows_message_when_in_console(void **state);
+void cmd_otr_theirfp_shows_message_when_in_muc(void **state);
+void cmd_otr_theirfp_shows_message_when_in_private(void **state);
+void cmd_otr_theirfp_shows_message_when_non_otr_chat_window(void **state);
+void cmd_otr_theirfp_shows_fingerprint(void **state);
+void cmd_otr_start_shows_message_when_in_console(void **state);
+void cmd_otr_start_shows_message_when_in_muc(void **state);
+void cmd_otr_start_shows_message_when_in_private(void **state);
+void cmd_otr_start_shows_message_when_already_started(void **state);
+void cmd_otr_start_shows_message_when_no_key(void **state);
+void cmd_otr_start_sends_otr_query_message_to_current_recipeint(void **state);
+#else
+void cmd_otr_shows_message_when_otr_unsupported(void **state);
+#endif
diff --git a/unittests/test_cmd_rooms.c b/unittests/test_cmd_rooms.c
new file mode 100644
index 00000000..5114bfbf
--- /dev/null
+++ b/unittests/test_cmd_rooms.c
@@ -0,0 +1,104 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "xmpp/xmpp.h"
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#include "config/accounts.h"
+#include "command/commands.h"
+
+static void test_with_connection_status(jabber_conn_status_t status)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+
+    will_return(jabber_get_connection_status, status);
+
+    expect_cons_show("You are not currently connected.");
+
+    gboolean result = cmd_rooms(NULL, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_rooms_shows_message_when_disconnected(void **state)
+{
+    test_with_connection_status(JABBER_DISCONNECTED);
+}
+
+void cmd_rooms_shows_message_when_disconnecting(void **state)
+{
+    test_with_connection_status(JABBER_DISCONNECTING);
+}
+
+void cmd_rooms_shows_message_when_connecting(void **state)
+{
+    test_with_connection_status(JABBER_CONNECTING);
+}
+
+void cmd_rooms_shows_message_when_started(void **state)
+{
+    test_with_connection_status(JABBER_STARTED);
+}
+
+void cmd_rooms_shows_message_when_undefined(void **state)
+{
+    test_with_connection_status(JABBER_UNDEFINED);
+}
+
+void cmd_rooms_uses_account_default_when_no_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { NULL };
+    ProfAccount *account = malloc(sizeof(ProfAccount));
+    account->name = NULL;
+    account->jid = NULL;
+    account->password = NULL;
+    account->eval_password = NULL;
+    account->resource = NULL;
+    account->server = NULL;
+    account->last_presence = NULL;
+    account->login_presence = NULL;
+    account->muc_nick = NULL;
+    account->otr_policy = NULL;
+    account->otr_manual = NULL;
+    account->otr_opportunistic = NULL;
+    account->otr_always = NULL;
+    account->muc_service = strdup("default_conf_server");
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+    will_return(jabber_get_account_name, "account_name");
+    expect_any(accounts_get_account, name);
+    will_return(accounts_get_account, account);
+
+    expect_string(iq_room_list_request, conferencejid, "default_conf_server");
+
+    gboolean result = cmd_rooms(args, *help);
+
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_rooms_arg_used_when_passed(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "conf_server_arg" };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_string(iq_room_list_request, conferencejid, "conf_server_arg");
+
+    gboolean result = cmd_rooms(args, *help);
+
+    assert_true(result);
+
+    free(help);
+}
diff --git a/unittests/test_cmd_rooms.h b/unittests/test_cmd_rooms.h
new file mode 100644
index 00000000..a9a7af83
--- /dev/null
+++ b/unittests/test_cmd_rooms.h
@@ -0,0 +1,7 @@
+void cmd_rooms_shows_message_when_disconnected(void **state);
+void cmd_rooms_shows_message_when_disconnecting(void **state);
+void cmd_rooms_shows_message_when_connecting(void **state);
+void cmd_rooms_shows_message_when_started(void **state);
+void cmd_rooms_shows_message_when_undefined(void **state);
+void cmd_rooms_uses_account_default_when_no_arg(void **state);
+void cmd_rooms_arg_used_when_passed(void **state);
diff --git a/unittests/test_cmd_roster.c b/unittests/test_cmd_roster.c
new file mode 100644
index 00000000..a7160cf5
--- /dev/null
+++ b/unittests/test_cmd_roster.c
@@ -0,0 +1,279 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#include "xmpp/xmpp.h"
+#include "roster_list.h"
+#include "command/commands.h"
+
+static void test_with_connection_status(jabber_conn_status_t status)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+
+    will_return(jabber_get_connection_status, status);
+
+    expect_cons_show("You are not currently connected.");
+
+    gboolean result = cmd_roster(NULL, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_roster_shows_message_when_disconnecting(void **state)
+{
+    test_with_connection_status(JABBER_DISCONNECTING);
+}
+
+void cmd_roster_shows_message_when_connecting(void **state)
+{
+    test_with_connection_status(JABBER_CONNECTING);
+}
+
+void cmd_roster_shows_message_when_disconnected(void **state)
+{
+    test_with_connection_status(JABBER_DISCONNECTED);
+}
+
+void cmd_roster_shows_message_when_undefined(void **state)
+{
+    test_with_connection_status(JABBER_UNDEFINED);
+}
+
+void cmd_roster_shows_roster_when_no_args(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    roster_init();
+    roster_add("bob@server.org", "bob", NULL, "both", FALSE);
+    GSList *roster = roster_get_contacts();
+
+    expect_memory(cons_show_roster, list, roster, sizeof(roster));
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+    roster_free();
+}
+
+void cmd_roster_add_shows_message_when_no_jid(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "add", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_roster_add_sends_roster_add_request(void **state)
+{
+    char *jid = "bob@server.org";
+    char *nick = "bob";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "add", jid, nick, NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_string(roster_send_add_new, barejid, jid);
+    expect_string(roster_send_add_new, name, nick);
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_roster_remove_shows_message_when_no_jid(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "remove", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_roster_remove_sends_roster_remove_request(void **state)
+{
+    char *jid = "bob@server.org";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "remove", jid, NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_string(roster_send_remove, barejid, jid);
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_roster_nick_shows_message_when_no_jid(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "nick", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_roster_nick_shows_message_when_no_nick(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "nick", "bob@server.org", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_roster_nick_shows_message_when_no_contact_exists(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "nick", "bob@server.org", "bobster", NULL };
+
+    roster_init();
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show("Contact not found in roster: bob@server.org");
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+    roster_free();
+}
+
+void cmd_roster_nick_sends_name_change_request(void **state)
+{
+    char *jid = "bob@server.org";
+    char *nick = "bobster";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "nick", jid, nick, NULL };
+
+    roster_init();
+    GSList *groups = NULL;
+    groups = g_slist_append(groups, "group1");
+    roster_add(jid, "bob", groups, "both", FALSE);
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_string(roster_send_name_change, barejid, jid);
+    expect_string(roster_send_name_change, new_name, nick);
+    expect_memory(roster_send_name_change, groups, groups, sizeof(groups));
+
+    expect_cons_show("Nickname for bob@server.org set to: bobster.");
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    PContact contact = roster_get_contact(jid);
+    assert_string_equal(p_contact_name(contact), nick);
+
+    free(help);
+    roster_free();
+}
+
+void cmd_roster_clearnick_shows_message_when_no_jid(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "clearnick", NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_roster_clearnick_shows_message_when_no_contact_exists(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "clearnick", "bob@server.org", NULL };
+
+    roster_init();
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show("Contact not found in roster: bob@server.org");
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    free(help);
+    roster_free();
+}
+
+void cmd_roster_clearnick_sends_name_change_request_with_empty_nick(void **state)
+{
+    char *jid = "bob@server.org";
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "clearnick", jid, NULL };
+
+    roster_init();
+    GSList *groups = NULL;
+    groups = g_slist_append(groups, "group1");
+    roster_add(jid, "bob", groups, "both", FALSE);
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_string(roster_send_name_change, barejid, jid);
+    expect_value(roster_send_name_change, new_name, NULL);
+    expect_memory(roster_send_name_change, groups, groups, sizeof(groups));
+
+    expect_cons_show("Nickname for bob@server.org removed.");
+
+    gboolean result = cmd_roster(args, *help);
+    assert_true(result);
+
+    PContact contact = roster_get_contact(jid);
+    assert_null(p_contact_name(contact));
+
+    free(help);
+    roster_free();
+}
diff --git a/unittests/test_cmd_roster.h b/unittests/test_cmd_roster.h
new file mode 100644
index 00000000..79f69ec8
--- /dev/null
+++ b/unittests/test_cmd_roster.h
@@ -0,0 +1,16 @@
+void cmd_roster_shows_message_when_disconnecting(void **state);
+void cmd_roster_shows_message_when_connecting(void **state);
+void cmd_roster_shows_message_when_disconnected(void **state);
+void cmd_roster_shows_message_when_undefined(void **state);
+void cmd_roster_shows_roster_when_no_args(void **state);
+void cmd_roster_add_shows_message_when_no_jid(void **state);
+void cmd_roster_add_sends_roster_add_request(void **state);
+void cmd_roster_remove_shows_message_when_no_jid(void **state);
+void cmd_roster_remove_sends_roster_remove_request(void **state);
+void cmd_roster_nick_shows_message_when_no_jid(void **state);
+void cmd_roster_nick_shows_message_when_no_nick(void **state);
+void cmd_roster_nick_shows_message_when_no_contact_exists(void **state);
+void cmd_roster_nick_sends_name_change_request(void **state);
+void cmd_roster_clearnick_shows_message_when_no_jid(void **state);
+void cmd_roster_clearnick_shows_message_when_no_contact_exists(void **state);
+void cmd_roster_clearnick_sends_name_change_request_with_empty_nick(void **state);
diff --git a/unittests/test_cmd_statuses.c b/unittests/test_cmd_statuses.c
new file mode 100644
index 00000000..ed37021a
--- /dev/null
+++ b/unittests/test_cmd_statuses.c
@@ -0,0 +1,223 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "config/preferences.h"
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#include "command/commands.h"
+
+void cmd_statuses_shows_usage_when_bad_subcmd(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "badcmd", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_statuses(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_shows_usage_when_bad_console_setting(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "console", "badsetting", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_statuses(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_shows_usage_when_bad_chat_setting(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "chat", "badsetting", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_statuses(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_shows_usage_when_bad_muc_setting(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "some usage";
+    gchar *args[] = { "muc", "badsetting", NULL };
+
+    expect_cons_show("Usage: some usage");
+
+    gboolean result = cmd_statuses(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_console_sets_all(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "console", "all", NULL };
+
+    expect_cons_show("All presence updates will appear in the console.");
+
+    gboolean result = cmd_statuses(args, *help);
+
+    char *setting = prefs_get_string(PREF_STATUSES_CONSOLE);
+    assert_non_null(setting);
+    assert_string_equal("all", setting);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_console_sets_online(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "console", "online", NULL };
+
+    expect_cons_show("Only online/offline presence updates will appear in the console.");
+
+    gboolean result = cmd_statuses(args, *help);
+
+    char *setting = prefs_get_string(PREF_STATUSES_CONSOLE);
+    assert_non_null(setting);
+    assert_string_equal("online", setting);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_console_sets_none(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "console", "none", NULL };
+
+    expect_cons_show("Presence updates will not appear in the console.");
+
+    gboolean result = cmd_statuses(args, *help);
+
+    char *setting = prefs_get_string(PREF_STATUSES_CONSOLE);
+    assert_non_null(setting);
+    assert_string_equal("none", setting);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_chat_sets_all(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "chat", "all", NULL };
+
+    expect_cons_show("All presence updates will appear in chat windows.");
+
+    gboolean result = cmd_statuses(args, *help);
+
+    char *setting = prefs_get_string(PREF_STATUSES_CHAT);
+    assert_non_null(setting);
+    assert_string_equal("all", setting);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_chat_sets_online(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "chat", "online", NULL };
+
+    expect_cons_show("Only online/offline presence updates will appear in chat windows.");
+
+    gboolean result = cmd_statuses(args, *help);
+
+    char *setting = prefs_get_string(PREF_STATUSES_CHAT);
+    assert_non_null(setting);
+    assert_string_equal("online", setting);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_chat_sets_none(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "chat", "none", NULL };
+
+    expect_cons_show("Presence updates will not appear in chat windows.");
+
+    gboolean result = cmd_statuses(args, *help);
+
+    char *setting = prefs_get_string(PREF_STATUSES_CHAT);
+    assert_non_null(setting);
+    assert_string_equal("none", setting);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_muc_sets_all(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "muc", "all", NULL };
+
+    expect_cons_show("All presence updates will appear in chat room windows.");
+
+    gboolean result = cmd_statuses(args, *help);
+
+    char *setting = prefs_get_string(PREF_STATUSES_MUC);
+    assert_non_null(setting);
+    assert_string_equal("all", setting);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_muc_sets_online(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "muc", "online", NULL };
+
+    expect_cons_show("Only join/leave presence updates will appear in chat room windows.");
+
+    gboolean result = cmd_statuses(args, *help);
+
+    char *setting = prefs_get_string(PREF_STATUSES_MUC);
+    assert_non_null(setting);
+    assert_string_equal("online", setting);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_statuses_muc_sets_none(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { "muc", "none", NULL };
+
+    expect_cons_show("Presence updates will not appear in chat room windows.");
+
+    gboolean result = cmd_statuses(args, *help);
+
+    char *setting = prefs_get_string(PREF_STATUSES_MUC);
+    assert_non_null(setting);
+    assert_string_equal("none", setting);
+    assert_true(result);
+
+    free(help);
+}
diff --git a/unittests/test_cmd_statuses.h b/unittests/test_cmd_statuses.h
new file mode 100644
index 00000000..306a6fc7
--- /dev/null
+++ b/unittests/test_cmd_statuses.h
@@ -0,0 +1,13 @@
+void cmd_statuses_shows_usage_when_bad_subcmd(void **state);
+void cmd_statuses_shows_usage_when_bad_console_setting(void **state);
+void cmd_statuses_shows_usage_when_bad_chat_setting(void **state);
+void cmd_statuses_shows_usage_when_bad_muc_setting(void **state);
+void cmd_statuses_console_sets_all(void **state);
+void cmd_statuses_console_sets_online(void **state);
+void cmd_statuses_console_sets_none(void **state);
+void cmd_statuses_chat_sets_all(void **state);
+void cmd_statuses_chat_sets_online(void **state);
+void cmd_statuses_chat_sets_none(void **state);
+void cmd_statuses_muc_sets_all(void **state);
+void cmd_statuses_muc_sets_online(void **state);
+void cmd_statuses_muc_sets_none(void **state);
diff --git a/unittests/test_cmd_sub.c b/unittests/test_cmd_sub.c
new file mode 100644
index 00000000..80b85f15
--- /dev/null
+++ b/unittests/test_cmd_sub.c
@@ -0,0 +1,45 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "xmpp/xmpp.h"
+
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+
+#include "command/commands.h"
+
+void cmd_sub_shows_message_when_not_connected(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    gchar *args[] = { NULL };
+
+    will_return(jabber_get_connection_status, JABBER_DISCONNECTED);
+
+    expect_cons_show("You are currently not connected.");
+
+    gboolean result = cmd_sub(args, *help);
+    assert_true(result);
+
+    free(help);
+}
+
+void cmd_sub_shows_usage_when_no_arg(void **state)
+{
+    CommandHelp *help = malloc(sizeof(CommandHelp));
+    help->usage = "Some usage";
+    gchar *args[] = { NULL };
+
+    will_return(jabber_get_connection_status, JABBER_CONNECTED);
+
+    expect_cons_show("Usage: Some usage");
+
+    gboolean result = cmd_sub(args, *help);
+    assert_true(result);
+
+    free(help);
+}
diff --git a/unittests/test_cmd_sub.h b/unittests/test_cmd_sub.h
new file mode 100644
index 00000000..6e8addd3
--- /dev/null
+++ b/unittests/test_cmd_sub.h
@@ -0,0 +1,2 @@
+void cmd_sub_shows_message_when_not_connected(void **state);
+void cmd_sub_shows_usage_when_no_arg(void **state);
diff --git a/unittests/test_common.c b/unittests/test_common.c
new file mode 100644
index 00000000..980f2198
--- /dev/null
+++ b/unittests/test_common.c
@@ -0,0 +1,633 @@
+#include "common.h"
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+void replace_one_substr(void **state)
+{
+    char *string = "it is a string";
+    char *sub = "is";
+    char *new = "was";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("it was a string", result);
+
+    free(result);
+}
+
+void replace_one_substr_beginning(void **state)
+{
+    char *string = "it is a string";
+    char *sub = "it";
+    char *new = "that";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("that is a string", result);
+
+    free(result);
+}
+
+void replace_one_substr_end(void **state)
+{
+    char *string = "it is a string";
+    char *sub = "string";
+    char *new = "thing";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("it is a thing", result);
+
+    free(result);
+}
+
+void replace_two_substr(void **state)
+{
+    char *string = "it is a is string";
+    char *sub = "is";
+    char *new = "was";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("it was a was string", result);
+
+    free(result);
+}
+
+void replace_char(void **state)
+{
+    char *string = "some & a thing & something else";
+    char *sub = "&";
+    char *new = "&amp;";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("some &amp; a thing &amp; something else", result);
+
+    free(result);
+}
+
+void replace_when_none(void **state)
+{
+    char *string = "its another string";
+    char *sub = "haha";
+    char *new = "replaced";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("its another string", result);
+
+    free(result);
+}
+
+void replace_when_match(void **state)
+{
+    char *string = "hello";
+    char *sub = "hello";
+    char *new = "goodbye";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("goodbye", result);
+
+    free(result);
+}
+
+void replace_when_string_empty(void **state)
+{
+    char *string = "";
+    char *sub = "hello";
+    char *new = "goodbye";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("", result);
+
+    free(result);
+}
+
+void replace_when_string_null(void **state)
+{
+    char *string = NULL;
+    char *sub = "hello";
+    char *new = "goodbye";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_null(result);
+}
+
+void replace_when_sub_empty(void **state)
+{
+    char *string = "hello";
+    char *sub = "";
+    char *new = "goodbye";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("hello", result);
+
+    free(result);
+}
+
+void replace_when_sub_null(void **state)
+{
+    char *string = "hello";
+    char *sub = NULL;
+    char *new = "goodbye";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("hello", result);
+
+    free(result);
+}
+
+void replace_when_new_empty(void **state)
+{
+    char *string = "hello";
+    char *sub = "hello";
+    char *new = "";
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("", result);
+
+    free(result);
+}
+
+void replace_when_new_null(void **state)
+{
+    char *string = "hello";
+    char *sub = "hello";
+    char *new = NULL;
+
+    char *result = str_replace(string, sub, new);
+
+    assert_string_equal("hello", result);
+
+    free(result);
+}
+
+void compare_win_nums_less(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(2);
+    gconstpointer b = GINT_TO_POINTER(3);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result < 0);
+}
+
+void compare_win_nums_equal(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(5);
+    gconstpointer b = GINT_TO_POINTER(5);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result == 0);
+}
+
+void compare_win_nums_greater(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(7);
+    gconstpointer b = GINT_TO_POINTER(6);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result > 0);
+}
+
+void compare_0s_equal(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(0);
+    gconstpointer b = GINT_TO_POINTER(0);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result == 0);
+}
+
+void compare_0_greater_than_1(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(0);
+    gconstpointer b = GINT_TO_POINTER(1);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result > 0);
+}
+
+void compare_1_less_than_0(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(1);
+    gconstpointer b = GINT_TO_POINTER(0);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result < 0);
+}
+
+void compare_0_less_than_11(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(0);
+    gconstpointer b = GINT_TO_POINTER(11);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result < 0);
+}
+
+void compare_11_greater_than_0(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(11);
+    gconstpointer b = GINT_TO_POINTER(0);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result > 0);
+}
+
+void compare_0_greater_than_9(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(0);
+    gconstpointer b = GINT_TO_POINTER(9);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result > 0);
+}
+
+void compare_9_less_than_0(void **state)
+{
+    gconstpointer a = GINT_TO_POINTER(9);
+    gconstpointer b = GINT_TO_POINTER(0);
+
+    int result = cmp_win_num(a, b);
+
+    assert_true(result < 0);
+}
+
+void next_available_when_only_console(void **state)
+{
+    GList *used = NULL;
+    used = g_list_append(used, GINT_TO_POINTER(1));
+
+    int result = get_next_available_win_num(used);
+
+    assert_int_equal(2, result);
+}
+
+void next_available_3_at_end(void **state)
+{
+    GList *used = NULL;
+    used = g_list_append(used, GINT_TO_POINTER(1));
+    used = g_list_append(used, GINT_TO_POINTER(2));
+
+    int result = get_next_available_win_num(used);
+
+    assert_int_equal(3, result);
+}
+
+void next_available_9_at_end(void **state)
+{
+    GList *used = NULL;
+    used = g_list_append(used, GINT_TO_POINTER(1));
+    used = g_list_append(used, GINT_TO_POINTER(2));
+    used = g_list_append(used, GINT_TO_POINTER(3));
+    used = g_list_append(used, GINT_TO_POINTER(4));
+    used = g_list_append(used, GINT_TO_POINTER(5));
+    used = g_list_append(used, GINT_TO_POINTER(6));
+    used = g_list_append(used, GINT_TO_POINTER(7));
+    used = g_list_append(used, GINT_TO_POINTER(8));
+
+    int result = get_next_available_win_num(used);
+
+    assert_int_equal(9, result);
+}
+
+void next_available_0_at_end(void **state)
+{
+    GList *used = NULL;
+    used = g_list_append(used, GINT_TO_POINTER(1));
+    used = g_list_append(used, GINT_TO_POINTER(2));
+    used = g_list_append(used, GINT_TO_POINTER(3));
+    used = g_list_append(used, GINT_TO_POINTER(4));
+    used = g_list_append(used, GINT_TO_POINTER(5));
+    used = g_list_append(used, GINT_TO_POINTER(6));
+    used = g_list_append(used, GINT_TO_POINTER(7));
+    used = g_list_append(used, GINT_TO_POINTER(8));
+    used = g_list_append(used, GINT_TO_POINTER(9));
+
+    int result = get_next_available_win_num(used);
+
+    assert_int_equal(0, result);
+}
+
+void next_available_2_in_first_gap(void **state)
+{
+    GList *used = NULL;
+    used = g_list_append(used, GINT_TO_POINTER(1));
+    used = g_list_append(used, GINT_TO_POINTER(3));
+    used = g_list_append(used, GINT_TO_POINTER(4));
+    used = g_list_append(used, GINT_TO_POINTER(5));
+    used = g_list_append(used, GINT_TO_POINTER(9));
+    used = g_list_append(used, GINT_TO_POINTER(0));
+
+    int result = get_next_available_win_num(used);
+
+    assert_int_equal(2, result);
+}
+
+void next_available_9_in_first_gap(void **state)
+{
+    GList *used = NULL;
+    used = g_list_append(used, GINT_TO_POINTER(1));
+    used = g_list_append(used, GINT_TO_POINTER(2));
+    used = g_list_append(used, GINT_TO_POINTER(3));
+    used = g_list_append(used, GINT_TO_POINTER(4));
+    used = g_list_append(used, GINT_TO_POINTER(5));
+    used = g_list_append(used, GINT_TO_POINTER(6));
+    used = g_list_append(used, GINT_TO_POINTER(7));
+    used = g_list_append(used, GINT_TO_POINTER(8));
+    used = g_list_append(used, GINT_TO_POINTER(0));
+    used = g_list_append(used, GINT_TO_POINTER(11));
+    used = g_list_append(used, GINT_TO_POINTER(12));
+    used = g_list_append(used, GINT_TO_POINTER(13));
+    used = g_list_append(used, GINT_TO_POINTER(20));
+
+    int result = get_next_available_win_num(used);
+
+    assert_int_equal(9, result);
+}
+
+void next_available_0_in_first_gap(void **state)
+{
+    GList *used = NULL;
+    used = g_list_append(used, GINT_TO_POINTER(1));
+    used = g_list_append(used, GINT_TO_POINTER(2));
+    used = g_list_append(used, GINT_TO_POINTER(3));
+    used = g_list_append(used, GINT_TO_POINTER(4));
+    used = g_list_append(used, GINT_TO_POINTER(5));
+    used = g_list_append(used, GINT_TO_POINTER(6));
+    used = g_list_append(used, GINT_TO_POINTER(7));
+    used = g_list_append(used, GINT_TO_POINTER(8));
+    used = g_list_append(used, GINT_TO_POINTER(9));
+    used = g_list_append(used, GINT_TO_POINTER(11));
+    used = g_list_append(used, GINT_TO_POINTER(12));
+    used = g_list_append(used, GINT_TO_POINTER(13));
+    used = g_list_append(used, GINT_TO_POINTER(20));
+
+    int result = get_next_available_win_num(used);
+
+    assert_int_equal(0, result);
+}
+
+void next_available_11_in_first_gap(void **state)
+{
+    GList *used = NULL;
+    used = g_list_append(used, GINT_TO_POINTER(1));
+    used = g_list_append(used, GINT_TO_POINTER(2));
+    used = g_list_append(used, GINT_TO_POINTER(3));
+    used = g_list_append(used, GINT_TO_POINTER(4));
+    used = g_list_append(used, GINT_TO_POINTER(5));
+    used = g_list_append(used, GINT_TO_POINTER(6));
+    used = g_list_append(used, GINT_TO_POINTER(7));
+    used = g_list_append(used, GINT_TO_POINTER(8));
+    used = g_list_append(used, GINT_TO_POINTER(9));
+    used = g_list_append(used, GINT_TO_POINTER(0));
+    used = g_list_append(used, GINT_TO_POINTER(12));
+    used = g_list_append(used, GINT_TO_POINTER(13));
+    used = g_list_append(used, GINT_TO_POINTER(20));
+
+    int result = get_next_available_win_num(used);
+
+    assert_int_equal(11, result);
+}
+
+void next_available_24_first_big_gap(void **state)
+{
+    GList *used = NULL;
+    used = g_list_append(used, GINT_TO_POINTER(1));
+    used = g_list_append(used, GINT_TO_POINTER(2));
+    used = g_list_append(used, GINT_TO_POINTER(3));
+    used = g_list_append(used, GINT_TO_POINTER(4));
+    used = g_list_append(used, GINT_TO_POINTER(5));
+    used = g_list_append(used, GINT_TO_POINTER(6));
+    used = g_list_append(used, GINT_TO_POINTER(7));
+    used = g_list_append(used, GINT_TO_POINTER(8));
+    used = g_list_append(used, GINT_TO_POINTER(9));
+    used = g_list_append(used, GINT_TO_POINTER(0));
+    used = g_list_append(used, GINT_TO_POINTER(11));
+    used = g_list_append(used, GINT_TO_POINTER(12));
+    used = g_list_append(used, GINT_TO_POINTER(13));
+    used = g_list_append(used, GINT_TO_POINTER(14));
+    used = g_list_append(used, GINT_TO_POINTER(15));
+    used = g_list_append(used, GINT_TO_POINTER(16));
+    used = g_list_append(used, GINT_TO_POINTER(17));
+    used = g_list_append(used, GINT_TO_POINTER(18));
+    used = g_list_append(used, GINT_TO_POINTER(19));
+    used = g_list_append(used, GINT_TO_POINTER(20));
+    used = g_list_append(used, GINT_TO_POINTER(21));
+    used = g_list_append(used, GINT_TO_POINTER(22));
+    used = g_list_append(used, GINT_TO_POINTER(23));
+    used = g_list_append(used, GINT_TO_POINTER(51));
+    used = g_list_append(used, GINT_TO_POINTER(52));
+    used = g_list_append(used, GINT_TO_POINTER(53));
+    used = g_list_append(used, GINT_TO_POINTER(89));
+    used = g_list_append(used, GINT_TO_POINTER(90));
+    used = g_list_append(used, GINT_TO_POINTER(100));
+    used = g_list_append(used, GINT_TO_POINTER(101));
+    used = g_list_append(used, GINT_TO_POINTER(102));
+
+    int result = get_next_available_win_num(used);
+
+    assert_int_equal(24, result);
+}
+
+void test_online_is_valid_resource_presence_string(void **state)
+{
+    assert_true(valid_resource_presence_string("online"));
+}
+
+void test_chat_is_valid_resource_presence_string(void **state)
+{
+    assert_true(valid_resource_presence_string("chat"));
+}
+
+void test_away_is_valid_resource_presence_string(void **state)
+{
+    assert_true(valid_resource_presence_string("away"));
+}
+
+void test_xa_is_valid_resource_presence_string(void **state)
+{
+    assert_true(valid_resource_presence_string("xa"));
+}
+
+void test_dnd_is_valid_resource_presence_string(void **state)
+{
+    assert_true(valid_resource_presence_string("dnd"));
+}
+
+void test_available_is_not_valid_resource_presence_string(void **state)
+{
+    assert_false(valid_resource_presence_string("available"));
+}
+
+void test_unavailable_is_not_valid_resource_presence_string(void **state)
+{
+    assert_false(valid_resource_presence_string("unavailable"));
+}
+
+void test_blah_is_not_valid_resource_presence_string(void **state)
+{
+    assert_false(valid_resource_presence_string("blah"));
+}
+
+void test_p_sha1_hash1(void **state)
+{
+    char *inp = "<message>some message</message>\n<element>another element</element>\n";
+    char *result = p_sha1_hash(inp);
+
+    assert_string_equal(result, "ZJLLzkYc51Lug3fZ7MJJzK95Ikg=");
+}
+
+void test_p_sha1_hash2(void **state)
+{
+    char *inp = "";
+    char *result = p_sha1_hash(inp);
+
+    assert_string_equal(result, "2jmj7l5rSw0yVb/vlWAYkK/YBwk=");
+}
+
+void test_p_sha1_hash3(void **state)
+{
+    char *inp = "m";
+    char *result = p_sha1_hash(inp);
+
+    assert_string_equal(result, "aw0xwNVjIjAk2kVpFYRkOseMlug=");
+}
+
+void test_p_sha1_hash4(void **state)
+{
+    char *inp = "<element/>\n";
+    char *result = p_sha1_hash(inp);
+
+    assert_string_equal(result, "xcgld4ZfXvU0P7+cW3WFLUuE3C8=");
+}
+
+void test_p_sha1_hash5(void **state)
+{
+    char *inp = "  ";
+    char *result = p_sha1_hash(inp);
+
+    assert_string_equal(result, "CZYAoQqUQRSqxAbRNrYl+0Ft13k=");
+}
+
+void test_p_sha1_hash6(void **state)
+{
+    char *inp = " sdf  \n ";
+    char *result = p_sha1_hash(inp);
+
+    assert_string_equal(result, "zjtm8dKlTj1KhYDlM2z8FsmAhSQ=");
+}
+
+void test_p_sha1_hash7(void **state)
+{
+    char *inp = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum sit amet pede facilisis laoreet. Donec lacus nunc, viverra nec, blandit vel, egestas et, augue. Vestibulum tincidunt malesuada tellus. Ut ultrices ultrices enim. Curabitur sit amet mauris. Morbi in dui quis est pulvinar ullamcorper. Nulla facilisi. Integer lacinia sollicitudin massa. Cras metus. Sed aliquet risus a tortor. Integer id quam. Morbi mi. Quisque nisl felis, venenatis tristique, dignissim in, ultrices sit amet, augue. Proin sodales libero eget ante. Nulla quam. Aenean laoreet. Vestibulum nisi lectus, commodo ac, facilisis ac, ultricies eu, pede. Ut orci risus, accumsan porttitor, cursus quis, aliquet eget, justo. Sed pretium blandit orci. Ut eu diam at pede suscipit sodales. Aenean lectus elit, fermentum non, convallis id, sagittis at, neque. Nullam mauris orci, aliquet et, iaculis et, viverra vitae, ligula. Nulla ut felis in purus aliquam imperdiet. Maecenas aliquet mollis lectus. Vivamus consectetuer risus et tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh. Quisque volutpat condimentum velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam nec ante. Sed lacinia, urna non tincidunt mattis, tortor neque adipiscing diam, a cursus ipsum ante quis turpis. Nulla facilisi. Ut fringilla. Suspendisse potenti. Nunc feugiat mi a tellus consequat imperdiet. Vestibulum sapien. Proin quam. Etiam ultrices. Suspendisse in justo eu magna luctus suscipit. Sed lectus. Integer euismod lacus luctus magna. Quisque cursus, metus vitae pharetra auctor, sem massa mattis sem, at interdum magna augue eget diam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi lacinia molestie dui. Praesent blandit dolor. Sed non quam. In vel mi sit amet augue congue elementum. Morbi in ipsum si.";
+    char *result = p_sha1_hash(inp);
+
+    assert_string_equal(result, "bNfKVfqEOGmzlH8M+e8FYTB46SU=");
+}
+
+void utf8_display_len_null_str(void **state)
+{
+    int result = utf8_display_len(NULL);
+
+    assert_int_equal(0, result);
+}
+
+void utf8_display_len_1_non_wide(void **state)
+{
+    int result = utf8_display_len("1");
+
+    assert_int_equal(1, result);
+}
+
+void utf8_display_len_1_wide(void **state)
+{
+    int result = utf8_display_len("四");
+
+    assert_int_equal(2, result);
+}
+
+void utf8_display_len_non_wide(void **state)
+{
+    int result = utf8_display_len("123456789abcdef");
+
+    assert_int_equal(15, result);
+}
+
+void utf8_display_len_wide(void **state)
+{
+    int result = utf8_display_len("12三四56");
+
+    assert_int_equal(8, result);
+}
+
+void utf8_display_len_all_wide(void **state)
+{
+    int result = utf8_display_len("ひらがな");
+
+    assert_int_equal(8, result);
+}
+
+void strip_quotes_does_nothing_when_no_quoted(void **state)
+{
+    char *input = "/cmd test string";
+
+    char *result = strip_arg_quotes(input);
+
+    assert_string_equal("/cmd test string", result);
+
+    free(result);
+}
+
+void strip_quotes_strips_first(void **state)
+{
+    char *input = "/cmd \"test string";
+
+    char *result = strip_arg_quotes(input);
+
+    assert_string_equal("/cmd test string", result);
+    
+    free(result);
+}
+
+void strip_quotes_strips_last(void **state)
+{
+    char *input = "/cmd test string\"";
+
+    char *result = strip_arg_quotes(input);
+
+    assert_string_equal("/cmd test string", result);
+
+    free(result);
+}
+
+void strip_quotes_strips_both(void **state)
+{
+    char *input = "/cmd \"test string\"";
+
+    char *result = strip_arg_quotes(input);
+
+    assert_string_equal("/cmd test string", result);
+
+    free(result);
+}
+
diff --git a/unittests/test_common.h b/unittests/test_common.h
new file mode 100644
index 00000000..b4b98e5a
--- /dev/null
+++ b/unittests/test_common.h
@@ -0,0 +1,58 @@
+void replace_one_substr(void **state);
+void replace_one_substr_beginning(void **state);
+void replace_one_substr_end(void **state);
+void replace_two_substr(void **state);
+void replace_char(void **state);
+void replace_when_none(void **state);
+void replace_when_match(void **state);
+void replace_when_string_empty(void **state);
+void replace_when_string_null(void **state);
+void replace_when_sub_empty(void **state);
+void replace_when_sub_null(void **state);
+void replace_when_new_empty(void **state);
+void replace_when_new_null(void **state);
+void compare_win_nums_less(void **state);
+void compare_win_nums_equal(void **state);
+void compare_win_nums_greater(void **state);
+void compare_0s_equal(void **state);
+void compare_0_greater_than_1(void **state);
+void compare_1_less_than_0(void **state);
+void compare_0_less_than_11(void **state);
+void compare_11_greater_than_0(void **state);
+void compare_0_greater_than_9(void **state);
+void compare_9_less_than_0(void **state);
+void next_available_when_only_console(void **state);
+void next_available_3_at_end(void **state);
+void next_available_9_at_end(void **state);
+void next_available_0_at_end(void **state);
+void next_available_2_in_first_gap(void **state);
+void next_available_9_in_first_gap(void **state);
+void next_available_0_in_first_gap(void **state);
+void next_available_11_in_first_gap(void **state);
+void next_available_24_first_big_gap(void **state);
+void test_online_is_valid_resource_presence_string(void **state);
+void test_chat_is_valid_resource_presence_string(void **state);
+void test_away_is_valid_resource_presence_string(void **state);
+void test_xa_is_valid_resource_presence_string(void **state);
+void test_dnd_is_valid_resource_presence_string(void **state);
+void test_available_is_not_valid_resource_presence_string(void **state);
+void test_unavailable_is_not_valid_resource_presence_string(void **state);
+void test_blah_is_not_valid_resource_presence_string(void **state);
+void test_p_sha1_hash1(void **state);
+void test_p_sha1_hash2(void **state);
+void test_p_sha1_hash3(void **state);
+void test_p_sha1_hash4(void **state);
+void test_p_sha1_hash5(void **state);
+void test_p_sha1_hash6(void **state);
+void test_p_sha1_hash6(void **state);
+void test_p_sha1_hash7(void **state);
+void utf8_display_len_null_str(void **state);
+void utf8_display_len_1_non_wide(void **state);
+void utf8_display_len_1_wide(void **state);
+void utf8_display_len_non_wide(void **state);
+void utf8_display_len_wide(void **state);
+void utf8_display_len_all_wide(void **state);
+void strip_quotes_does_nothing_when_no_quoted(void **state);
+void strip_quotes_strips_first(void **state);
+void strip_quotes_strips_last(void **state);
+void strip_quotes_strips_both(void **state);
diff --git a/unittests/test_contact.c b/unittests/test_contact.c
new file mode 100644
index 00000000..cad88907
--- /dev/null
+++ b/unittests/test_contact.c
@@ -0,0 +1,407 @@
+#include <glib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+#include "contact.h"
+
+void contact_in_group(void **state)
+{
+    GSList *groups = NULL;
+    groups = g_slist_append(groups, strdup("somegroup"));
+    PContact contact = p_contact_new("bob@server.com", "bob", groups, "both",
+        "is offline", FALSE);
+
+    gboolean result = p_contact_in_group(contact, "somegroup");
+
+    assert_true(result);
+
+    p_contact_free(contact);
+//    g_slist_free(groups);
+}
+
+void contact_not_in_group(void **state)
+{
+    GSList *groups = NULL;
+    groups = g_slist_append(groups, strdup("somegroup"));
+    PContact contact = p_contact_new("bob@server.com", "bob", groups, "both",
+        "is offline", FALSE);
+
+    gboolean result = p_contact_in_group(contact, "othergroup");
+
+    assert_false(result);
+
+    p_contact_free(contact);
+//    g_slist_free(groups);
+}
+
+void contact_name_when_name_exists(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    const char *name = p_contact_name_or_jid(contact);
+
+    assert_string_equal("bob", name);
+
+    p_contact_free(contact);
+}
+
+void contact_jid_when_name_not_exists(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", NULL, NULL, "both",
+        "is offline", FALSE);
+
+    const char *jid = p_contact_name_or_jid(contact);
+
+    assert_string_equal("bob@server.com", jid);
+
+    p_contact_free(contact);
+}
+
+void contact_string_when_name_exists(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    char *str = p_contact_create_display_string(contact, "laptop");
+
+    assert_string_equal("bob (laptop)", str);
+
+    p_contact_free(contact);
+    free(str);
+}
+
+void contact_string_when_name_not_exists(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", NULL, NULL, "both",
+        "is offline", FALSE);
+
+    char *str = p_contact_create_display_string(contact, "laptop");
+
+    assert_string_equal("bob@server.com (laptop)", str);
+
+    p_contact_free(contact);
+    free(str);
+}
+
+void contact_string_when_default_resource(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    char *str = p_contact_create_display_string(contact, "__prof_default");
+
+    assert_string_equal("bob", str);
+
+    p_contact_free(contact);
+    free(str);
+}
+
+void contact_presence_offline(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    const char *presence = p_contact_presence(contact);
+
+    assert_string_equal("offline", presence);
+
+    p_contact_free(contact);
+}
+
+void contact_presence_uses_highest_priority(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    Resource *resource10 = resource_new("resource10", RESOURCE_ONLINE, NULL, 10);
+    Resource *resource20 = resource_new("resource20", RESOURCE_CHAT, NULL, 20);
+    Resource *resource30 = resource_new("resource30", RESOURCE_AWAY, NULL, 30);
+    Resource *resource1 = resource_new("resource1", RESOURCE_XA, NULL, 1);
+    Resource *resource2 = resource_new("resource2", RESOURCE_DND, NULL, 2);
+    p_contact_set_presence(contact, resource10);
+    p_contact_set_presence(contact, resource20);
+    p_contact_set_presence(contact, resource30);
+    p_contact_set_presence(contact, resource1);
+    p_contact_set_presence(contact, resource2);
+
+    const char *presence = p_contact_presence(contact);
+
+    assert_string_equal("away", presence);
+
+    p_contact_free(contact);
+}
+
+void contact_presence_chat_when_same_prioroty(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    Resource *resource_online = resource_new("resource_online", RESOURCE_ONLINE, NULL, 10);
+    Resource *resource_chat = resource_new("resource_chat", RESOURCE_CHAT, NULL, 10);
+    Resource *resource_away = resource_new("resource_away", RESOURCE_AWAY, NULL, 10);
+    Resource *resource_xa = resource_new("resource_xa", RESOURCE_XA, NULL, 10);
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 10);
+    p_contact_set_presence(contact, resource_online);
+    p_contact_set_presence(contact, resource_chat);
+    p_contact_set_presence(contact, resource_away);
+    p_contact_set_presence(contact, resource_xa);
+    p_contact_set_presence(contact, resource_dnd);
+
+    const char *presence = p_contact_presence(contact);
+
+    assert_string_equal("chat", presence);
+
+    p_contact_free(contact);
+}
+
+void contact_presence_online_when_same_prioroty(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    Resource *resource_online = resource_new("resource_online", RESOURCE_ONLINE, NULL, 10);
+    Resource *resource_away = resource_new("resource_away", RESOURCE_AWAY, NULL, 10);
+    Resource *resource_xa = resource_new("resource_xa", RESOURCE_XA, NULL, 10);
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 10);
+    p_contact_set_presence(contact, resource_online);
+    p_contact_set_presence(contact, resource_away);
+    p_contact_set_presence(contact, resource_xa);
+    p_contact_set_presence(contact, resource_dnd);
+
+    const char *presence = p_contact_presence(contact);
+
+    assert_string_equal("online", presence);
+
+    p_contact_free(contact);
+}
+
+void contact_presence_away_when_same_prioroty(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    Resource *resource_away = resource_new("resource_away", RESOURCE_AWAY, NULL, 10);
+    Resource *resource_xa = resource_new("resource_xa", RESOURCE_XA, NULL, 10);
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 10);
+    p_contact_set_presence(contact, resource_away);
+    p_contact_set_presence(contact, resource_xa);
+    p_contact_set_presence(contact, resource_dnd);
+
+    const char *presence = p_contact_presence(contact);
+
+    assert_string_equal("away", presence);
+
+    p_contact_free(contact);
+}
+
+void contact_presence_xa_when_same_prioroty(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    Resource *resource_xa = resource_new("resource_xa", RESOURCE_XA, NULL, 10);
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 10);
+    p_contact_set_presence(contact, resource_xa);
+    p_contact_set_presence(contact, resource_dnd);
+
+    const char *presence = p_contact_presence(contact);
+
+    assert_string_equal("xa", presence);
+
+    p_contact_free(contact);
+}
+
+void contact_presence_dnd(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 10);
+    p_contact_set_presence(contact, resource_dnd);
+
+    const char *presence = p_contact_presence(contact);
+
+    assert_string_equal("dnd", presence);
+
+    p_contact_free(contact);
+}
+
+void contact_subscribed_when_to(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "to",
+        "is offline", FALSE);
+
+    gboolean result = p_contact_subscribed(contact);
+
+    assert_true(result);
+
+    p_contact_free(contact);
+}
+
+void contact_subscribed_when_both(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "both",
+        "is offline", FALSE);
+
+    gboolean result = p_contact_subscribed(contact);
+
+    assert_true(result);
+
+    p_contact_free(contact);
+}
+
+void contact_not_subscribed_when_from(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, "from",
+        "is offline", FALSE);
+
+    gboolean result = p_contact_subscribed(contact);
+
+    assert_false(result);
+
+    p_contact_free(contact);
+}
+
+void contact_not_subscribed_when_no_subscription_value(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, NULL,
+        "is offline", FALSE);
+
+    gboolean result = p_contact_subscribed(contact);
+
+    assert_false(result);
+
+    p_contact_free(contact);
+}
+
+void contact_not_available(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, NULL,
+        "is offline", FALSE);
+
+    gboolean result = p_contact_is_available(contact);
+
+    assert_false(result);
+
+    p_contact_free(contact);
+}
+
+void contact_not_available_when_highest_priority_away(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, NULL,
+        "is offline", FALSE);
+
+    Resource *resource_online = resource_new("resource_online", RESOURCE_ONLINE, NULL, 10);
+    Resource *resource_chat = resource_new("resource_chat", RESOURCE_CHAT, NULL, 10);
+    Resource *resource_away = resource_new("resource_away", RESOURCE_AWAY, NULL, 20);
+    Resource *resource_xa = resource_new("resource_xa", RESOURCE_XA, NULL, 10);
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 10);
+    p_contact_set_presence(contact, resource_online);
+    p_contact_set_presence(contact, resource_chat);
+    p_contact_set_presence(contact, resource_away);
+    p_contact_set_presence(contact, resource_xa);
+    p_contact_set_presence(contact, resource_dnd);
+
+    gboolean result = p_contact_is_available(contact);
+
+    assert_false(result);
+
+    p_contact_free(contact);
+}
+
+void contact_not_available_when_highest_priority_xa(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, NULL,
+        "is offline", FALSE);
+
+    Resource *resource_online = resource_new("resource_online", RESOURCE_ONLINE, NULL, 10);
+    Resource *resource_chat = resource_new("resource_chat", RESOURCE_CHAT, NULL, 10);
+    Resource *resource_away = resource_new("resource_away", RESOURCE_AWAY, NULL, 10);
+    Resource *resource_xa = resource_new("resource_xa", RESOURCE_XA, NULL, 20);
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 10);
+    p_contact_set_presence(contact, resource_online);
+    p_contact_set_presence(contact, resource_chat);
+    p_contact_set_presence(contact, resource_away);
+    p_contact_set_presence(contact, resource_xa);
+    p_contact_set_presence(contact, resource_dnd);
+
+    gboolean result = p_contact_is_available(contact);
+
+    assert_false(result);
+
+    p_contact_free(contact);
+}
+
+void contact_not_available_when_highest_priority_dnd(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, NULL,
+        "is offline", FALSE);
+
+    Resource *resource_online = resource_new("resource_online", RESOURCE_ONLINE, NULL, 10);
+    Resource *resource_chat = resource_new("resource_chat", RESOURCE_CHAT, NULL, 10);
+    Resource *resource_away = resource_new("resource_away", RESOURCE_AWAY, NULL, 10);
+    Resource *resource_xa = resource_new("resource_xa", RESOURCE_XA, NULL, 10);
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 20);
+    p_contact_set_presence(contact, resource_online);
+    p_contact_set_presence(contact, resource_chat);
+    p_contact_set_presence(contact, resource_away);
+    p_contact_set_presence(contact, resource_xa);
+    p_contact_set_presence(contact, resource_dnd);
+
+    gboolean result = p_contact_is_available(contact);
+
+    assert_false(result);
+
+    p_contact_free(contact);
+}
+
+void contact_available_when_highest_priority_online(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, NULL,
+        "is offline", FALSE);
+
+    Resource *resource_online = resource_new("resource_online", RESOURCE_ONLINE, NULL, 20);
+    Resource *resource_chat = resource_new("resource_chat", RESOURCE_CHAT, NULL, 10);
+    Resource *resource_away = resource_new("resource_away", RESOURCE_AWAY, NULL, 10);
+    Resource *resource_xa = resource_new("resource_xa", RESOURCE_XA, NULL, 10);
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 10);
+    p_contact_set_presence(contact, resource_online);
+    p_contact_set_presence(contact, resource_chat);
+    p_contact_set_presence(contact, resource_away);
+    p_contact_set_presence(contact, resource_xa);
+    p_contact_set_presence(contact, resource_dnd);
+
+    gboolean result = p_contact_is_available(contact);
+
+    assert_true(result);
+
+    p_contact_free(contact);
+}
+
+void contact_available_when_highest_priority_chat(void **state)
+{
+    PContact contact = p_contact_new("bob@server.com", "bob", NULL, NULL,
+        "is offline", FALSE);
+
+    Resource *resource_online = resource_new("resource_online", RESOURCE_ONLINE, NULL, 10);
+    Resource *resource_chat = resource_new("resource_chat", RESOURCE_CHAT, NULL, 20);
+    Resource *resource_away = resource_new("resource_away", RESOURCE_AWAY, NULL, 10);
+    Resource *resource_xa = resource_new("resource_xa", RESOURCE_XA, NULL, 10);
+    Resource *resource_dnd = resource_new("resource_dnd", RESOURCE_DND, NULL, 10);
+    p_contact_set_presence(contact, resource_online);
+    p_contact_set_presence(contact, resource_chat);
+    p_contact_set_presence(contact, resource_away);
+    p_contact_set_presence(contact, resource_xa);
+    p_contact_set_presence(contact, resource_dnd);
+
+    gboolean result = p_contact_is_available(contact);
+
+    assert_true(result);
+
+    p_contact_free(contact);
+}
diff --git a/unittests/test_contact.h b/unittests/test_contact.h
new file mode 100644
index 00000000..c9d8c1fd
--- /dev/null
+++ b/unittests/test_contact.h
@@ -0,0 +1,24 @@
+void contact_in_group(void **state);
+void contact_not_in_group(void **state);
+void contact_name_when_name_exists(void **state);
+void contact_jid_when_name_not_exists(void **state);
+void contact_string_when_name_exists(void **state);
+void contact_string_when_name_not_exists(void **state);
+void contact_string_when_default_resource(void **state);
+void contact_presence_offline(void **state);
+void contact_presence_uses_highest_priority(void **state);
+void contact_presence_chat_when_same_prioroty(void **state);
+void contact_presence_online_when_same_prioroty(void **state);
+void contact_presence_away_when_same_prioroty(void **state);
+void contact_presence_xa_when_same_prioroty(void **state);
+void contact_presence_dnd(void **state);
+void contact_subscribed_when_to(void **state);
+void contact_subscribed_when_both(void **state);
+void contact_not_subscribed_when_from(void **state);
+void contact_not_subscribed_when_no_subscription_value(void **state);
+void contact_not_available(void **state);
+void contact_not_available_when_highest_priority_away(void **state);
+void contact_not_available_when_highest_priority_xa(void **state);
+void contact_not_available_when_highest_priority_dnd(void **state);
+void contact_available_when_highest_priority_online(void **state);
+void contact_available_when_highest_priority_chat(void **state);
diff --git a/unittests/test_form.c b/unittests/test_form.c
new file mode 100644
index 00000000..b3158a83
--- /dev/null
+++ b/unittests/test_form.c
@@ -0,0 +1,727 @@
+#include <stdarg.h>
+#include <string.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+#include "xmpp/form.h"
+
+xmpp_ctx_t* connection_get_ctx(void)
+{
+    return NULL;
+}
+
+static DataForm*
+_new_form(void)
+{
+    DataForm *form = malloc(sizeof(DataForm));
+    form->type = NULL;
+    form->title = NULL;
+    form->instructions = NULL;
+    form->fields = NULL;
+    form->var_to_tag = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
+    form->tag_to_var = g_hash_table_new_full(g_str_hash, g_str_equal, free, free);
+    form->tag_ac = NULL;
+
+    return form;
+}
+
+static FormField*
+_new_field(void)
+{
+    FormField *field = malloc(sizeof(FormField));
+    field->label = NULL;
+    field->type = NULL;
+    field->description = NULL;
+    field->required = FALSE;
+    field->options = NULL;
+    field->var = NULL;
+    field->values = NULL;
+    field->value_ac = NULL;
+
+    return field;
+
+}
+
+void get_form_type_field_returns_null_no_fields(void **state)
+{
+    DataForm *form = _new_form();
+
+    char *result = form_get_form_type_field(form);
+
+    assert_null(result);
+
+    form_destroy(form);
+}
+
+void get_form_type_field_returns_null_when_not_present(void **state)
+{
+    DataForm *form = _new_form();
+    FormField *field = _new_field();
+    field->var = strdup("var1");
+    field->values = g_slist_append(field->values, strdup("value1"));
+    form->fields = g_slist_append(form->fields, field);
+
+    char *result = form_get_form_type_field(form);
+
+    assert_null(result);
+
+    form_destroy(form);
+}
+
+void get_form_type_field_returns_value_when_present(void **state)
+{
+    DataForm *form = _new_form();
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->values = g_slist_append(field1->values, strdup("value1"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    FormField *field2 = _new_field();
+    field2->var = strdup("FORM_TYPE");
+    field2->values = g_slist_append(field2->values, strdup("value2"));
+    form->fields = g_slist_append(form->fields, field2);
+
+    FormField *field3 = _new_field();
+    field3->var = strdup("var3");
+    field3->values = g_slist_append(field3->values, strdup("value3"));
+    form->fields = g_slist_append(form->fields, field3);
+
+    char *result = form_get_form_type_field(form);
+
+    assert_string_equal(result, "value2");
+
+    form_destroy(form);
+}
+
+void get_field_type_returns_unknown_when_no_fields(void **state)
+{
+    DataForm *form = _new_form();
+
+    form_field_type_t result = form_get_field_type(form, "tag");
+
+    assert_int_equal(result, FIELD_UNKNOWN);
+
+    form_destroy(form);
+}
+
+void get_field_type_returns_correct_type(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+    g_hash_table_insert(form->tag_to_var, strdup("tag2"), strdup("var2"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_TEXT_SINGLE;
+    field1->values = g_slist_append(field1->values, strdup("value1"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    FormField *field2 = _new_field();
+    field2->var = strdup("var2");
+    field2->type_t = FIELD_TEXT_MULTI;
+    field2->values = g_slist_append(field2->values, strdup("value2"));
+    form->fields = g_slist_append(form->fields, field2);
+
+    form_field_type_t result = form_get_field_type(form, "tag2");
+
+    assert_int_equal(result, FIELD_TEXT_MULTI);
+
+    form_destroy(form);
+}
+
+void set_value_adds_when_none(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+    g_hash_table_insert(form->tag_to_var, strdup("tag2"), strdup("var2"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_TEXT_SINGLE;
+    field1->values = g_slist_append(field1->values, strdup("value1"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    FormField *field2 = _new_field();
+    field2->var = strdup("var2");
+    field2->type_t = FIELD_LIST_SINGLE;
+    form->fields = g_slist_append(form->fields, field2);
+
+    form_set_value(form, "tag2", "a new value");
+
+    int length = 0;
+    char *value = NULL;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var2") == 0) {
+            length = g_slist_length(field->values);
+            value = field->values->data;
+            break;
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_int_equal(length, 1);
+    assert_string_equal(value, "a new value");
+
+    form_destroy(form);
+}
+
+void set_value_updates_when_one(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+    g_hash_table_insert(form->tag_to_var, strdup("tag2"), strdup("var2"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_TEXT_SINGLE;
+    form->fields = g_slist_append(form->fields, field1);
+
+    FormField *field2 = _new_field();
+    field2->var = strdup("var2");
+    field2->type_t = FIELD_LIST_SINGLE;
+    field2->values = g_slist_append(field2->values, strdup("value2"));
+    form->fields = g_slist_append(form->fields, field2);
+
+    form_set_value(form, "tag2", "a new value");
+
+    int length = 0;
+    char *value = NULL;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var2") == 0) {
+            length = g_slist_length(field->values);
+            value = field->values->data;
+            break;
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_int_equal(length, 1);
+    assert_string_equal(value, "a new value");
+
+    form_destroy(form);
+}
+
+void add_unique_value_adds_when_none(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+    g_hash_table_insert(form->tag_to_var, strdup("tag2"), strdup("var2"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_JID_MULTI;
+    form->fields = g_slist_append(form->fields, field1);
+
+    FormField *field2 = _new_field();
+    field2->var = strdup("var2");
+    field2->type_t = FIELD_LIST_SINGLE;
+    field2->values = g_slist_append(field2->values, strdup("value2"));
+    form->fields = g_slist_append(form->fields, field2);
+
+    gboolean ret = form_add_unique_value(form, "tag1", "me@server.com");
+
+    int length = 0;
+    char *value = NULL;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+            value = field->values->data;
+            break;
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_true(ret);
+    assert_int_equal(length, 1);
+    assert_string_equal(value, "me@server.com");
+
+    form_destroy(form);
+}
+
+void add_unique_value_does_nothing_when_exists(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+    g_hash_table_insert(form->tag_to_var, strdup("tag2"), strdup("var2"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_JID_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("me@server.com"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    FormField *field2 = _new_field();
+    field2->var = strdup("var2");
+    field2->type_t = FIELD_LIST_SINGLE;
+    field2->values = g_slist_append(field2->values, strdup("value2"));
+    form->fields = g_slist_append(form->fields, field2);
+
+    gboolean ret = form_add_unique_value(form, "tag1", "me@server.com");
+
+    int length = 0;
+    char *value = NULL;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+            value = field->values->data;
+            break;
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_false(ret);
+    assert_int_equal(length, 1);
+    assert_string_equal(value, "me@server.com");
+
+    form_destroy(form);
+}
+
+void add_unique_value_adds_when_doesnt_exist(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+    g_hash_table_insert(form->tag_to_var, strdup("tag2"), strdup("var2"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_JID_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("dolan@server.com"));
+    field1->values = g_slist_append(field1->values, strdup("kieran@server.com"));
+    field1->values = g_slist_append(field1->values, strdup("chi@server.com"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    FormField *field2 = _new_field();
+    field2->var = strdup("var2");
+    field2->type_t = FIELD_LIST_SINGLE;
+    field2->values = g_slist_append(field2->values, strdup("value2"));
+    form->fields = g_slist_append(form->fields, field2);
+
+    gboolean ret = form_add_unique_value(form, "tag1", "me@server.com");
+
+    int length = 0;
+    int count = 0;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+            GSList *curr_value = field->values;
+            while (curr_value != NULL) {
+                if (g_strcmp0(curr_value->data, "me@server.com") == 0) {
+                    count++;
+                }
+                curr_value = g_slist_next(curr_value);
+            }
+            break;
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_true(ret);
+    assert_int_equal(length, 4);
+    assert_int_equal(count, 1);
+
+    form_destroy(form);
+}
+
+void add_value_adds_when_none(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    form->fields = g_slist_append(form->fields, field1);
+
+    form_add_value(form, "tag1", "somevalue");
+
+    int length = 0;
+    char *value = NULL;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+            value = field->values->data;
+            break;
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_int_equal(length, 1);
+    assert_string_equal(value, "somevalue");
+
+    form_destroy(form);
+}
+
+void add_value_adds_when_some(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("some text"));
+    field1->values = g_slist_append(field1->values, strdup("some more text"));
+    field1->values = g_slist_append(field1->values, strdup("yet some more text"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    form_add_value(form, "tag1", "new value");
+
+    int num_values = 0;
+    int new_value_count = 0;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            GSList *curr_value = field->values;
+            while (curr_value != NULL) {
+                num_values++;
+                if (g_strcmp0(curr_value->data, "new value") == 0) {
+                    new_value_count++;
+                }
+                curr_value = g_slist_next(curr_value);
+            }
+            break;
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_int_equal(num_values, 4);
+    assert_int_equal(new_value_count, 1);
+
+    form_destroy(form);
+}
+
+void add_value_adds_when_exists(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("some text"));
+    field1->values = g_slist_append(field1->values, strdup("some more text"));
+    field1->values = g_slist_append(field1->values, strdup("yet some more text"));
+    field1->values = g_slist_append(field1->values, strdup("new value"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    form_add_value(form, "tag1", "new value");
+
+    int num_values = 0;
+    int new_value_count = 0;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            GSList *curr_value = field->values;
+            while (curr_value != NULL) {
+                num_values++;
+                if (g_strcmp0(curr_value->data, "new value") == 0) {
+                    new_value_count++;
+                }
+                curr_value = g_slist_next(curr_value);
+            }
+            break;
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_int_equal(num_values, 5);
+    assert_int_equal(new_value_count, 2);
+
+    form_destroy(form);
+}
+
+void remove_value_does_nothing_when_none(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    form->fields = g_slist_append(form->fields, field1);
+
+    gboolean res = form_remove_value(form, "tag1", "some value");
+
+    int length = -1;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_false(res);
+    assert_int_equal(length, 0);
+
+    form_destroy(form);
+}
+
+void remove_value_does_nothing_when_doesnt_exist(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("value1"));
+    field1->values = g_slist_append(field1->values, strdup("value2"));
+    field1->values = g_slist_append(field1->values, strdup("value3"));
+    field1->values = g_slist_append(field1->values, strdup("value4"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    gboolean res = form_remove_value(form, "tag1", "value5");
+
+    int length = -1;
+    int value_count = 0;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+            GSList *curr_value = field->values;
+            while (curr_value != NULL) {
+                if (g_strcmp0(curr_value->data, "value5") == 0) {
+                    value_count++;
+                }
+                curr_value = g_slist_next(curr_value);
+            }
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_false(res);
+    assert_int_equal(length, 4);
+    assert_int_equal(value_count, 0);
+
+    form_destroy(form);
+}
+
+void remove_value_removes_when_one(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("value4"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    gboolean res = form_remove_value(form, "tag1", "value4");
+
+    int length = -1;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_true(res);
+    assert_int_equal(length, 0);
+
+    form_destroy(form);
+}
+
+void remove_value_removes_when_many(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("value1"));
+    field1->values = g_slist_append(field1->values, strdup("value2"));
+    field1->values = g_slist_append(field1->values, strdup("value3"));
+    field1->values = g_slist_append(field1->values, strdup("value4"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    gboolean res = form_remove_value(form, "tag1", "value2");
+
+    int length = -1;
+    int value_count = 0;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+            GSList *curr_value = field->values;
+            while (curr_value != NULL) {
+                if (g_strcmp0(curr_value->data, "value2") == 0) {
+                    value_count++;
+                }
+                curr_value = g_slist_next(curr_value);
+            }
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_true(res);
+    assert_int_equal(length, 3);
+    assert_int_equal(value_count, 0);
+
+    form_destroy(form);
+}
+
+void remove_text_multi_value_does_nothing_when_none(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    form->fields = g_slist_append(form->fields, field1);
+
+    gboolean res = form_remove_text_multi_value(form, "tag1", 3);
+
+    int length = -1;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_false(res);
+    assert_int_equal(length, 0);
+
+    form_destroy(form);
+}
+
+void remove_text_multi_value_does_nothing_when_doesnt_exist(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("value1"));
+    field1->values = g_slist_append(field1->values, strdup("value2"));
+    field1->values = g_slist_append(field1->values, strdup("value3"));
+    field1->values = g_slist_append(field1->values, strdup("value4"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    gboolean res = form_remove_text_multi_value(form, "tag1", 5);
+
+    int length = -1;
+    int value_count = 0;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+            GSList *curr_value = field->values;
+            while (curr_value != NULL) {
+                if (g_strcmp0(curr_value->data, "value5") == 0) {
+                    value_count++;
+                }
+                curr_value = g_slist_next(curr_value);
+            }
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_false(res);
+    assert_int_equal(length, 4);
+    assert_int_equal(value_count, 0);
+
+    form_destroy(form);
+}
+
+void remove_text_multi_value_removes_when_one(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("value4"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    gboolean res = form_remove_text_multi_value(form, "tag1", 1);
+
+    int length = -1;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_true(res);
+    assert_int_equal(length, 0);
+
+    form_destroy(form);
+}
+
+void remove_text_multi_value_removes_when_many(void **state)
+{
+    DataForm *form = _new_form();
+    g_hash_table_insert(form->tag_to_var, strdup("tag1"), strdup("var1"));
+
+    FormField *field1 = _new_field();
+    field1->var = strdup("var1");
+    field1->type_t = FIELD_LIST_MULTI;
+    field1->values = g_slist_append(field1->values, strdup("value1"));
+    field1->values = g_slist_append(field1->values, strdup("value2"));
+    field1->values = g_slist_append(field1->values, strdup("value3"));
+    field1->values = g_slist_append(field1->values, strdup("value4"));
+    form->fields = g_slist_append(form->fields, field1);
+
+    gboolean res = form_remove_text_multi_value(form, "tag1", 2);
+
+    int length = -1;
+    int value_count = 0;
+    GSList *curr_field = form->fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+        if (g_strcmp0(field->var, "var1") == 0) {
+            length = g_slist_length(field->values);
+            GSList *curr_value = field->values;
+            while (curr_value != NULL) {
+                if (g_strcmp0(curr_value->data, "value2") == 0) {
+                    value_count++;
+                }
+                curr_value = g_slist_next(curr_value);
+            }
+        }
+        curr_field = g_slist_next(curr_field);
+    }
+
+    assert_true(res);
+    assert_int_equal(length, 3);
+    assert_int_equal(value_count, 0);
+
+    form_destroy(form);
+}
+
diff --git a/unittests/test_form.h b/unittests/test_form.h
new file mode 100644
index 00000000..65911d0a
--- /dev/null
+++ b/unittests/test_form.h
@@ -0,0 +1,21 @@
+void get_form_type_field_returns_null_no_fields(void **state);
+void get_form_type_field_returns_null_when_not_present(void **state);
+void get_form_type_field_returns_value_when_present(void **state);
+void get_field_type_returns_unknown_when_no_fields(void **state);
+void get_field_type_returns_correct_type(void **state);
+void set_value_adds_when_none(void **state);
+void set_value_updates_when_one(void **state);
+void add_unique_value_adds_when_none(void **state);
+void add_unique_value_does_nothing_when_exists(void **state);
+void add_unique_value_adds_when_doesnt_exist(void **state);
+void add_value_adds_when_none(void **state);
+void add_value_adds_when_some(void **state);
+void add_value_adds_when_exists(void **state);
+void remove_value_does_nothing_when_none(void **state);
+void remove_value_does_nothing_when_doesnt_exist(void **state);
+void remove_value_removes_when_one(void **state);
+void remove_value_removes_when_many(void **state);
+void remove_text_multi_value_does_nothing_when_none(void **state);
+void remove_text_multi_value_does_nothing_when_doesnt_exist(void **state);
+void remove_text_multi_value_removes_when_one(void **state);
+void remove_text_multi_value_removes_when_many(void **state);
diff --git a/unittests/test_jid.c b/unittests/test_jid.c
new file mode 100644
index 00000000..ff5f4c9a
--- /dev/null
+++ b/unittests/test_jid.c
@@ -0,0 +1,185 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+#include "jid.h"
+
+void create_jid_from_null_returns_null(void **state)
+{
+    Jid *result = jid_create(NULL);
+    assert_null(result);
+}
+
+void create_jid_from_empty_string_returns_null(void **state)
+{
+    Jid *result = jid_create("");
+    assert_null(result);
+}
+
+void create_jid_from_full_returns_full(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain/laptop");
+    assert_string_equal("myuser@mydomain/laptop", result->fulljid);
+}
+
+void create_jid_from_full_returns_bare(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain/laptop");
+    assert_string_equal("myuser@mydomain", result->barejid);
+}
+
+void create_jid_from_full_returns_resourcepart(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain/laptop");
+    assert_string_equal("laptop", result->resourcepart);
+}
+
+void create_jid_from_full_returns_localpart(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain/laptop");
+    assert_string_equal("myuser", result->localpart);
+}
+
+void create_jid_from_full_returns_domainpart(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain/laptop");
+    assert_string_equal("mydomain", result->domainpart);
+}
+
+void create_jid_from_full_nolocal_returns_full(void **state)
+{
+    Jid *result = jid_create("mydomain/laptop");
+    assert_string_equal("mydomain/laptop", result->fulljid);
+}
+
+void create_jid_from_full_nolocal_returns_bare(void **state)
+{
+    Jid *result = jid_create("mydomain/laptop");
+    assert_string_equal("mydomain", result->barejid);
+}
+
+void create_jid_from_full_nolocal_returns_resourcepart(void **state)
+{
+    Jid *result = jid_create("mydomain/laptop");
+    assert_string_equal("laptop", result->resourcepart);
+}
+
+void create_jid_from_full_nolocal_returns_domainpart(void **state)
+{
+    Jid *result = jid_create("mydomain/laptop");
+    assert_string_equal("mydomain", result->domainpart);
+}
+
+void create_jid_from_full_nolocal_returns_null_localpart(void **state)
+{
+    Jid *result = jid_create("mydomain/laptop");
+    assert_null(result->localpart);
+}
+
+void create_jid_from_bare_returns_null_full(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain");
+    assert_null(result->fulljid);
+}
+
+void create_jid_from_bare_returns_null_resource(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain");
+    assert_null(result->resourcepart);
+}
+
+void create_jid_from_bare_returns_bare(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain");
+    assert_string_equal("myuser@mydomain", result->barejid);
+}
+
+void create_jid_from_bare_returns_localpart(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain");
+    assert_string_equal("myuser", result->localpart);
+}
+
+void create_jid_from_bare_returns_domainpart(void **state)
+{
+    Jid *result = jid_create("myuser@mydomain");
+    assert_string_equal("mydomain", result->domainpart);
+}
+
+void create_room_jid_returns_room(void **state)
+{
+    Jid *result = jid_create_from_bare_and_resource("room@conference.domain.org", "myname");
+
+    assert_string_equal("room@conference.domain.org", result->barejid);
+}
+
+void create_room_jid_returns_nick(void **state)
+{
+    Jid *result = jid_create_from_bare_and_resource("room@conference.domain.org", "myname");
+
+    assert_string_equal("myname", result->resourcepart);
+}
+
+void create_with_slash_in_resource(void **state)
+{
+    Jid *result = jid_create("room@conference.domain.org/my/nick");
+
+    assert_string_equal("room", result->localpart);
+    assert_string_equal("conference.domain.org", result->domainpart);
+    assert_string_equal("my/nick", result->resourcepart);
+    assert_string_equal("room@conference.domain.org", result->barejid);
+    assert_string_equal("room@conference.domain.org/my/nick", result->fulljid);
+}
+
+void create_with_at_in_resource(void **state)
+{
+    Jid *result = jid_create("room@conference.domain.org/my@nick");
+
+    assert_string_equal("room", result->localpart);
+    assert_string_equal("conference.domain.org", result->domainpart);
+    assert_string_equal("my@nick", result->resourcepart);
+    assert_string_equal("room@conference.domain.org", result->barejid);
+    assert_string_equal("room@conference.domain.org/my@nick", result->fulljid);
+}
+
+void create_with_at_and_slash_in_resource(void **state)
+{
+    Jid *result = jid_create("room@conference.domain.org/my@nick/something");
+
+    assert_string_equal("room", result->localpart);
+    assert_string_equal("conference.domain.org", result->domainpart);
+    assert_string_equal("my@nick/something", result->resourcepart);
+    assert_string_equal("room@conference.domain.org", result->barejid);
+    assert_string_equal("room@conference.domain.org/my@nick/something", result->fulljid);
+}
+
+void create_full_with_trailing_slash(void **state)
+{
+    Jid *result = jid_create("room@conference.domain.org/nick/");
+
+    assert_string_equal("room", result->localpart);
+    assert_string_equal("conference.domain.org", result->domainpart);
+    assert_string_equal("nick/", result->resourcepart);
+    assert_string_equal("room@conference.domain.org", result->barejid);
+    assert_string_equal("room@conference.domain.org/nick/", result->fulljid);
+}
+
+void returns_fulljid_when_exists(void **state)
+{
+    Jid *jid = jid_create("localpart@domainpart/resourcepart");
+
+    char *result = jid_fulljid_or_barejid(jid);
+
+    assert_string_equal("localpart@domainpart/resourcepart", result);
+}
+
+void returns_barejid_when_fulljid_not_exists(void **state)
+{
+    Jid *jid = jid_create("localpart@domainpart");
+
+    char *result = jid_fulljid_or_barejid(jid);
+
+    assert_string_equal("localpart@domainpart", result);
+}
\ No newline at end of file
diff --git a/unittests/test_jid.h b/unittests/test_jid.h
new file mode 100644
index 00000000..9b96d0b8
--- /dev/null
+++ b/unittests/test_jid.h
@@ -0,0 +1,25 @@
+void create_jid_from_null_returns_null(void **state);
+void create_jid_from_empty_string_returns_null(void **state);
+void create_jid_from_full_returns_full(void **state);
+void create_jid_from_full_returns_bare(void **state);
+void create_jid_from_full_returns_resourcepart(void **state);
+void create_jid_from_full_returns_localpart(void **state);
+void create_jid_from_full_returns_domainpart(void **state);
+void create_jid_from_full_nolocal_returns_full(void **state);
+void create_jid_from_full_nolocal_returns_bare(void **state);
+void create_jid_from_full_nolocal_returns_resourcepart(void **state);
+void create_jid_from_full_nolocal_returns_domainpart(void **state);
+void create_jid_from_full_nolocal_returns_null_localpart(void **state);
+void create_jid_from_bare_returns_null_full(void **state);
+void create_jid_from_bare_returns_null_resource(void **state);
+void create_jid_from_bare_returns_bare(void **state);
+void create_jid_from_bare_returns_localpart(void **state);
+void create_jid_from_bare_returns_domainpart(void **state);
+void create_room_jid_returns_room(void **state);
+void create_room_jid_returns_nick(void **state);
+void create_with_slash_in_resource(void **state);
+void create_with_at_in_resource(void **state);
+void create_with_at_and_slash_in_resource(void **state);
+void create_full_with_trailing_slash(void **state);
+void returns_fulljid_when_exists(void **state);
+void returns_barejid_when_fulljid_not_exists(void **state);
diff --git a/unittests/test_keyhandlers.c b/unittests/test_keyhandlers.c
new file mode 100644
index 00000000..a6d39143
--- /dev/null
+++ b/unittests/test_keyhandlers.c
@@ -0,0 +1,734 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <locale.h>
+
+#include "ui/keyhandlers.h"
+#include "ui/inputwin.h"
+#include "tests/helpers.h"
+
+static char line[INP_WIN_MAX];
+
+// append
+
+void append_to_empty(void **state)
+{
+    setlocale(LC_ALL, "");
+    line[0] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'a', 80);
+
+    assert_string_equal("a", line);
+    assert_int_equal(line_utf8_pos, 1);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_wide_to_empty(void **state)
+{
+    setlocale(LC_ALL, "");
+    line[0] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 0x56DB, 80);
+
+    assert_string_equal("四", line);
+    assert_int_equal(line_utf8_pos, 1);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_to_single(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "a", 1);
+    line[1] = '\0';
+    int line_utf8_pos = 1;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'b', 80);
+
+    assert_string_equal("ab", line);
+    assert_int_equal(line_utf8_pos, 2);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+
+void append_wide_to_single_non_wide(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "a", 1);
+    line[1] = '\0';
+    int line_utf8_pos = 1;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 0x56DB, 80);
+
+    assert_string_equal("a四", line);
+    assert_int_equal(line_utf8_pos, 2);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_non_wide_to_single_wide(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "四", 1);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 1;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'b', 80);
+
+    assert_string_equal("四b", line);
+    assert_int_equal(line_utf8_pos, 2);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_wide_to_single_wide(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "四", 1);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 1;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 0x4E09, 80);
+
+    assert_string_equal("四三", line);
+    assert_int_equal(line_utf8_pos, 2);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void append_non_wide_when_overrun(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "0123456789四1234567", 18);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 18;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'z', 20);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'z', 20);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'z', 20);
+
+    assert_string_equal("0123456789四1234567zzz", line);
+    assert_int_equal(line_utf8_pos, 21);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 3);
+}
+
+void insert_non_wide_to_non_wide(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd", 4);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 2;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, '0', 80);
+
+    assert_string_equal("ab0cd", line);
+    assert_int_equal(line_utf8_pos, 3);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void insert_single_non_wide_when_pad_scrolled(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "AAAAAAAAAAAAAAA", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 2;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 2;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'B', 12);
+
+    assert_string_equal("AABAAAAAAAAAAAAA", line);
+    assert_int_equal(line_utf8_pos, 3);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 2);
+}
+
+void insert_many_non_wide_when_pad_scrolled(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "AAAAAAAAAAAAAAA", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 2;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 2;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'B', 12);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'C', 12);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, 'D', 12);
+
+    assert_string_equal("AABCDAAAAAAAAAAAAA", line);
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 2);
+}
+
+void insert_single_non_wide_last_column(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcdefghijklmno", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 2;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, '1', 5);
+
+    assert_string_equal("abcdefg1hijklmno", line);
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 3);
+}
+
+void insert_many_non_wide_last_column(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcdefghijklmno", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 2;
+
+    key_printable(line, &line_utf8_pos, &col, &pad_start, '1', 5);
+    key_printable(line, &line_utf8_pos, &col, &pad_start, '2', 5);
+
+    assert_string_equal("abcdefg12hijklmno", line);
+    assert_int_equal(line_utf8_pos, 9);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 4);
+}
+
+void ctrl_left_when_no_input(void **state)
+{
+    setlocale(LC_ALL, "");
+    line[0] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_start(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_first_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 2;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_first_space(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 4;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_start_of_second_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 5;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_second_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 8;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_end_of_second_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 10;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_second_space(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 11;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_start_of_third_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 12;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 5);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_third_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 14;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 12);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_end_of_third_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 15;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 12);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_third_space(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 16;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 12);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_at_end(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "abcd efghij klmn opqr", 21);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 20;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 17);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_in_only_whitespace(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "       ", 7);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 5;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_start_whitespace_start_of_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "    hello", 9);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 4;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_start_whitespace_middle_of_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "    hello", 9);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 4);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_in_whitespace_between_words(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "hey    hello", 12);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 5;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_in_whitespace_between_words_start_of_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "hey    hello", 12);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_in_whitespace_between_words_middle_of_word(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "hey    hello", 12);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 9;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 7);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_left_when_word_overrun_to_left(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 18;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 14;
+
+    key_ctrl_left(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 9);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 9);
+}
+
+void ctrl_right_when_no_input(void **state)
+{
+    setlocale(LC_ALL, "");
+    line[0] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 0);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_when_at_end(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 20;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 20);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_one_word_at_start(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword", 8);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 0;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_one_word_in_middle(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword", 8);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 3;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_one_word_at_end(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword", 8);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_from_middle_first(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 4;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_from_end_first(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 7;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_from_space(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 8;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 20);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_from_start_second(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword anotherword", 20);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 9;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 20);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_one_word_leading_whitespace(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "       someword", 15);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 3;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 15);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_two_words_in_whitespace(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "       someword        adfasdf", 30);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 19;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 30);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
+
+void ctrl_right_trailing_whitespace_from_middle(void **state)
+{
+    setlocale(LC_ALL, "");
+    g_utf8_strncpy(line, "someword        ", 16);
+    line[strlen(line)] = '\0';
+    int line_utf8_pos = 3;
+    int col = utf8_pos_to_col(line, line_utf8_pos);
+    int pad_start = 0;
+
+    key_ctrl_right(line, &line_utf8_pos, &col, &pad_start, 80);
+
+    assert_int_equal(line_utf8_pos, 8);
+    assert_int_equal(col, utf8_pos_to_col(line, line_utf8_pos));
+    assert_int_equal(pad_start, 0);
+}
\ No newline at end of file
diff --git a/unittests/test_keyhandlers.h b/unittests/test_keyhandlers.h
new file mode 100644
index 00000000..4be429a9
--- /dev/null
+++ b/unittests/test_keyhandlers.h
@@ -0,0 +1,47 @@
+void append_to_empty(void **state);
+void append_wide_to_empty(void **state);
+void append_to_single(void **state);
+void append_wide_to_single_non_wide(void **state);
+void append_non_wide_to_single_wide(void **state);
+void append_wide_to_single_wide(void **state);
+void append_non_wide_when_overrun(void **state);
+
+void insert_non_wide_to_non_wide(void **state);
+void insert_single_non_wide_when_pad_scrolled(void **state);
+void insert_many_non_wide_when_pad_scrolled(void **state);
+void insert_single_non_wide_last_column(void **state);
+void insert_many_non_wide_last_column(void **state);
+
+void ctrl_left_when_no_input(void **state);
+void ctrl_left_when_at_start(void **state);
+void ctrl_left_when_in_first_word(void **state);
+void ctrl_left_when_in_first_space(void **state);
+void ctrl_left_when_at_start_of_second_word(void **state);
+void ctrl_left_when_in_second_word(void **state);
+void ctrl_left_when_at_end_of_second_word(void **state);
+void ctrl_left_when_in_second_space(void **state);
+void ctrl_left_when_at_start_of_third_word(void **state);
+void ctrl_left_when_in_third_word(void **state);
+void ctrl_left_when_at_end_of_third_word(void **state);
+void ctrl_left_when_in_third_space(void **state);
+void ctrl_left_when_at_end(void **state);
+void ctrl_left_when_in_only_whitespace(void **state);
+void ctrl_left_when_start_whitespace_start_of_word(void **state);
+void ctrl_left_when_start_whitespace_middle_of_word(void **state);
+void ctrl_left_in_whitespace_between_words(void **state);
+void ctrl_left_in_whitespace_between_words_start_of_word(void **state);
+void ctrl_left_in_whitespace_between_words_middle_of_word(void **state);
+void ctrl_left_when_word_overrun_to_left(void **state);
+
+void ctrl_right_when_no_input(void **state);
+void ctrl_right_when_at_end(void **state);
+void ctrl_right_one_word_at_start(void **state);
+void ctrl_right_one_word_in_middle(void **state);
+void ctrl_right_one_word_at_end(void **state);
+void ctrl_right_two_words_from_middle_first(void **state);
+void ctrl_right_two_words_from_end_first(void **state);
+void ctrl_right_two_words_from_space(void **state);
+void ctrl_right_two_words_from_start_second(void **state);
+void ctrl_right_one_word_leading_whitespace(void **state);
+void ctrl_right_two_words_in_whitespace(void **state);
+void ctrl_right_trailing_whitespace_from_middle(void **state);
\ No newline at end of file
diff --git a/unittests/test_muc.c b/unittests/test_muc.c
new file mode 100644
index 00000000..e3b7f9b0
--- /dev/null
+++ b/unittests/test_muc.c
@@ -0,0 +1,78 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+#include "muc.h"
+
+void muc_before_test(void **state)
+{
+    muc_init();
+}
+
+void muc_after_test(void **state)
+{
+    muc_close();
+}
+
+void test_muc_invites_add(void **state)
+{
+    char *room = "room@conf.server";
+    muc_invites_add(room, NULL);
+
+    gboolean invite_exists = muc_invites_contain(room);
+
+    assert_true(invite_exists);
+}
+
+void test_muc_remove_invite(void **state)
+{
+    char *room = "room@conf.server";
+    muc_invites_add(room, NULL);
+    muc_invites_remove(room);
+
+    gboolean invite_exists = muc_invites_contain(room);
+
+    assert_false(invite_exists);
+}
+
+void test_muc_invites_count_0(void **state)
+{
+    int invite_count = muc_invites_count();
+
+    assert_true(invite_count == 0);
+}
+
+void test_muc_invites_count_5(void **state)
+{
+    muc_invites_add("room1@conf.server", NULL);
+    muc_invites_add("room2@conf.server", NULL);
+    muc_invites_add("room3@conf.server", NULL);
+    muc_invites_add("room4@conf.server", NULL);
+    muc_invites_add("room5@conf.server", NULL);
+
+    int invite_count = muc_invites_count();
+
+    assert_true(invite_count == 5);
+}
+
+void test_muc_room_is_not_active(void **state)
+{
+    char *room = "room@server.org";
+
+    gboolean room_is_active = muc_active(room);
+
+    assert_false(room_is_active);
+}
+
+void test_muc_active(void **state)
+{
+    char *room = "room@server.org";
+    char *nick = "bob";
+    muc_join(room, nick, NULL, FALSE);
+
+    gboolean room_is_active = muc_active(room);
+
+    assert_true(room_is_active);
+}
diff --git a/unittests/test_muc.h b/unittests/test_muc.h
new file mode 100644
index 00000000..8df54a5d
--- /dev/null
+++ b/unittests/test_muc.h
@@ -0,0 +1,9 @@
+void muc_before_test(void **state);
+void muc_after_test(void **state);
+
+void test_muc_invites_add(void **state);
+void test_muc_remove_invite(void **state);
+void test_muc_invites_count_0(void **state);
+void test_muc_invites_count_5(void **state);
+void test_muc_room_is_not_active(void **state);
+void test_muc_active(void **state);
diff --git a/unittests/test_parser.c b/unittests/test_parser.c
new file mode 100644
index 00000000..faefc9c7
--- /dev/null
+++ b/unittests/test_parser.c
@@ -0,0 +1,662 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+#include "tools/parser.h"
+
+void
+parse_null_returns_null(void **state)
+{
+    char *inp = NULL;
+    gboolean result = TRUE;
+    gchar **args = parse_args(inp, 1, 2, &result);
+
+    assert_false(result);
+    assert_null(args);
+    g_strfreev(args);
+}
+
+void
+parse_empty_returns_null(void **state)
+{
+    char *inp = "";
+    gboolean result = TRUE;
+    gchar **args = parse_args(inp, 1, 2, &result);
+
+    assert_false(result);
+    assert_null(args);
+    g_strfreev(args);
+}
+
+void
+parse_space_returns_null(void **state)
+{
+    char *inp = "   ";
+    gboolean result = TRUE;
+    gchar **args = parse_args(inp, 1, 2, &result);
+
+    assert_false(result);
+    assert_null(args);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_no_args_returns_null(void **state)
+{
+    char *inp = "/cmd";
+    gboolean result = TRUE;
+    gchar **args = parse_args(inp, 1, 2, &result);
+
+    assert_false(result);
+    assert_null(args);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_space_returns_null(void **state)
+{
+    char *inp = "/cmd   ";
+    gboolean result = TRUE;
+    gchar **args = parse_args(inp, 1, 2, &result);
+
+    assert_false(result);
+    assert_null(args);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_too_few_returns_null(void **state)
+{
+    char *inp = "/cmd arg1";
+    gboolean result = TRUE;
+    gchar **args = parse_args(inp, 2, 3, &result);
+
+    assert_false(result);
+    assert_null(args);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_too_many_returns_null(void **state)
+{
+    char *inp = "/cmd arg1 arg2 arg3 arg4";
+    gboolean result = TRUE;
+    gchar **args = parse_args(inp, 1, 3, &result);
+
+    assert_false(result);
+    assert_null(args);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_one_arg(void **state)
+{
+    char *inp = "/cmd arg1";
+    gboolean result = FALSE;
+    gchar **args = parse_args(inp, 1, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(1, g_strv_length(args));
+    assert_string_equal("arg1", args[0]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_two_args(void **state)
+{
+    char *inp = "/cmd arg1 arg2";
+    gboolean result = FALSE;
+    gchar **args = parse_args(inp, 1, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(2, g_strv_length(args));
+    assert_string_equal("arg1", args[0]);
+    assert_string_equal("arg2", args[1]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_three_args(void **state)
+{
+    char *inp = "/cmd arg1 arg2 arg3";
+    gboolean result = FALSE;
+    gchar **args = parse_args(inp, 3, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("arg1", args[0]);
+    assert_string_equal("arg2", args[1]);
+    assert_string_equal("arg3", args[2]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_three_args_with_spaces(void **state)
+{
+    char *inp = "  /cmd    arg1  arg2     arg3 ";
+    gboolean result = FALSE;
+    gchar **args = parse_args(inp, 3, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("arg1", args[0]);
+    assert_string_equal("arg2", args[1]);
+    assert_string_equal("arg3", args[2]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_freetext(void **state)
+{
+    char *inp = "/cmd this is some free text";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 1, 1, &result);
+
+    assert_true(result);
+    assert_int_equal(1, g_strv_length(args));
+    assert_string_equal("this is some free text", args[0]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_one_arg_with_freetext(void **state)
+{
+    char *inp = "/cmd arg1 this is some free text";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 1, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(2, g_strv_length(args));
+    assert_string_equal("arg1", args[0]);
+    assert_string_equal("this is some free text", args[1]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_two_args_with_freetext(void **state)
+{
+    char *inp = "/cmd arg1 arg2 this is some free text";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 1, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("arg1", args[0]);
+    assert_string_equal("arg2", args[1]);
+    assert_string_equal("this is some free text", args[2]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_min_zero(void **state)
+{
+    char *inp = "/cmd";
+    gboolean result = FALSE;
+    gchar **args = parse_args(inp, 0, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(0, g_strv_length(args));
+    assert_null(args[0]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_min_zero_with_freetext(void **state)
+{
+    char *inp = "/cmd";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 0, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(0, g_strv_length(args));
+    assert_null(args[0]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_quoted(void **state)
+{
+    char *inp = "/cmd \"arg1\" arg2";
+    gboolean result = FALSE;
+    gchar **args = parse_args(inp, 2, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(2, g_strv_length(args));
+    assert_string_equal("arg1", args[0]);
+    assert_string_equal("arg2", args[1]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_quoted_and_space(void **state)
+{
+    char *inp = "/cmd \"the arg1\" arg2";
+    gboolean result = FALSE;
+    gchar **args = parse_args(inp, 2, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(2, g_strv_length(args));
+    assert_string_equal("the arg1", args[0]);
+    assert_string_equal("arg2", args[1]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_quoted_and_many_spaces(void **state)
+{
+    char *inp = "/cmd \"the arg1 is here\" arg2";
+    gboolean result = FALSE;
+    gchar **args = parse_args(inp, 2, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(2, g_strv_length(args));
+    assert_string_equal("the arg1 is here", args[0]);
+    assert_string_equal("arg2", args[1]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_many_quoted_and_many_spaces(void **state)
+{
+    char *inp = "/cmd \"the arg1 is here\" \"and arg2 is right here\"";
+    gboolean result = FALSE;
+    gchar **args = parse_args(inp, 2, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(2, g_strv_length(args));
+    assert_string_equal("the arg1 is here", args[0]);
+    assert_string_equal("and arg2 is right here", args[1]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_freetext_with_quoted(void **state)
+{
+    char *inp = "/cmd \"arg1\" arg2 hello there whats up";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 3, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("arg1", args[0]);
+    assert_string_equal("arg2", args[1]);
+    assert_string_equal("hello there whats up", args[2]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_freetext_with_quoted_and_space(void **state)
+{
+    char *inp = "/cmd \"the arg1\" arg2 another bit of freetext";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 3, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("the arg1", args[0]);
+    assert_string_equal("arg2", args[1]);
+    assert_string_equal("another bit of freetext", args[2]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_freetext_with_quoted_and_many_spaces(void **state)
+{
+    char *inp = "/cmd \"the arg1 is here\" arg2 some more freetext";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 3, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("the arg1 is here", args[0]);
+    assert_string_equal("arg2", args[1]);
+    assert_string_equal("some more freetext", args[2]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_freetext_with_many_quoted_and_many_spaces(void **state)
+{
+    char *inp = "/cmd \"the arg1 is here\" \"and arg2 is right here\" and heres the free text";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 3, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("the arg1 is here", args[0]);
+    assert_string_equal("and arg2 is right here", args[1]);
+    assert_string_equal("and heres the free text", args[2]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_quoted_freetext(void **state)
+{
+    char *inp = "/cmd arg1 here is \"some\" quoted freetext";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 1, 2, &result);
+
+    assert_true(result);
+    assert_int_equal(2, g_strv_length(args));
+    assert_string_equal("arg1", args[0]);
+    assert_string_equal("here is \"some\" quoted freetext", args[1]);
+    g_strfreev(args);
+}
+
+void
+parse_cmd_with_third_arg_quoted_0_min_3_max(void **state)
+{
+    char *inp = "/group add friends \"The User\"";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 0, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("add", args[0]);
+    assert_string_equal("friends", args[1]);
+    assert_string_equal("The User", args[2]);
+}
+
+void
+parse_cmd_with_second_arg_quoted_0_min_3_max(void **state)
+{
+    char *inp = "/group add \"The Group\" friend";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 0, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("add", args[0]);
+    assert_string_equal("The Group", args[1]);
+    assert_string_equal("friend", args[2]);
+}
+
+void
+parse_cmd_with_second_and_third_arg_quoted_0_min_3_max(void **state)
+{
+    char *inp = "/group add \"The Group\" \"The User\"";
+    gboolean result = FALSE;
+    gchar **args = parse_args_with_freetext(inp, 0, 3, &result);
+
+    assert_true(result);
+    assert_int_equal(3, g_strv_length(args));
+    assert_string_equal("add", args[0]);
+    assert_string_equal("The Group", args[1]);
+    assert_string_equal("The User", args[2]);
+}
+
+void
+count_one_token(void **state)
+{
+    char *inp = "one";
+    int result = count_tokens(inp);
+
+    assert_int_equal(1, result);
+}
+
+void
+count_one_token_quoted_no_whitespace(void **state)
+{
+    char *inp = "\"one\"";
+    int result = count_tokens(inp);
+
+    assert_int_equal(1, result);
+}
+
+void
+count_one_token_quoted_with_whitespace(void **state)
+{
+    char *inp = "\"one two\"";
+    int result = count_tokens(inp);
+
+    assert_int_equal(1, result);
+}
+
+void
+count_two_tokens(void **state)
+{
+    char *inp = "one two";
+    int result = count_tokens(inp);
+
+    assert_int_equal(2, result);
+}
+
+void
+count_two_tokens_first_quoted(void **state)
+{
+    char *inp = "\"one and\" two";
+    int result = count_tokens(inp);
+
+    assert_int_equal(2, result);
+}
+
+void
+count_two_tokens_second_quoted(void **state)
+{
+    char *inp = "one \"two and\"";
+    int result = count_tokens(inp);
+
+    assert_int_equal(2, result);
+}
+
+void
+count_two_tokens_both_quoted(void **state)
+{
+    char *inp = "\"one and then\" \"two and\"";
+    int result = count_tokens(inp);
+
+    assert_int_equal(2, result);
+}
+
+void
+get_first_of_one(void **state)
+{
+    char *inp = "one";
+    char *result = get_start(inp, 2);
+
+    assert_string_equal("one", result);
+}
+
+void
+get_first_of_two(void **state)
+{
+    char *inp = "one two";
+    char *result = get_start(inp, 2);
+
+    assert_string_equal("one ", result);
+}
+
+void
+get_first_two_of_three(void **state)
+{
+    char *inp = "one two three";
+    char *result = get_start(inp, 3);
+
+    assert_string_equal("one two ", result);
+}
+
+void
+get_first_two_of_three_first_quoted(void **state)
+{
+    char *inp = "\"one\" two three";
+    char *result = get_start(inp, 3);
+
+    assert_string_equal("\"one\" two ", result);
+}
+
+void
+get_first_two_of_three_second_quoted(void **state)
+{
+    char *inp = "one \"two\" three";
+    char *result = get_start(inp, 3);
+
+    assert_string_equal("one \"two\" ", result);
+}
+
+void
+get_first_two_of_three_first_and_second_quoted(void **state)
+{
+    char *inp = "\"one\" \"two\" three";
+    char *result = get_start(inp, 3);
+
+    assert_string_equal("\"one\" \"two\" ", result);
+}
+
+void
+parse_options_when_none_returns_empty_hasmap(void **state)
+{
+    gchar *args[] = { "cmd1", "cmd2", NULL };
+    gchar *keys[] = { "opt1", NULL };
+
+    gboolean res = FALSE;
+
+    GHashTable *options = parse_options(&args[2], keys, &res);
+
+    assert_true(options != NULL);
+    assert_int_equal(0, g_hash_table_size(options));
+    assert_true(res);
+
+    options_destroy(options);
+}
+
+void
+parse_options_when_opt1_no_val_sets_error(void **state)
+{
+    gchar *args[] = { "cmd1", "cmd2", "opt1", NULL };
+    gchar *keys[] = { "opt1", NULL };
+
+    gboolean res = TRUE;
+
+    GHashTable *options = parse_options(&args[2], keys, &res);
+
+    assert_null(options);
+    assert_false(res);
+
+    options_destroy(options);
+}
+
+void
+parse_options_when_one_returns_map(void **state)
+{
+    gchar *args[] = { "cmd1", "cmd2", "opt1", "val1", NULL };
+    gchar *keys[] = { "opt1", NULL };
+
+    gboolean res = FALSE;
+
+    GHashTable *options = parse_options(&args[2], keys, &res);
+
+    assert_int_equal(1, g_hash_table_size(options));
+    assert_true(g_hash_table_contains(options, "opt1"));
+    assert_string_equal("val1", g_hash_table_lookup(options, "opt1"));
+    assert_true(res);
+
+    options_destroy(options);
+}
+
+void
+parse_options_when_opt2_no_val_sets_error(void **state)
+{
+    gchar *args[] = { "cmd1", "cmd2", "opt1", "val1", "opt2", NULL };
+    gchar *keys[] = { "opt1", "opt2", NULL };
+
+    gboolean res = TRUE;
+
+    GHashTable *options = parse_options(&args[2], keys, &res);
+
+    assert_null(options);
+    assert_false(res);
+
+    options_destroy(options);
+}
+
+void
+parse_options_when_two_returns_map(void **state)
+{
+    gchar *args[] = { "cmd1", "cmd2", "opt1", "val1", "opt2", "val2", NULL };
+    gchar *keys[] = { "opt1", "opt2", NULL };
+
+    gboolean res = FALSE;
+
+    GHashTable *options = parse_options(&args[2], keys, &res);
+
+    assert_int_equal(2, g_hash_table_size(options));
+    assert_true(g_hash_table_contains(options, "opt1"));
+    assert_true(g_hash_table_contains(options, "opt2"));
+    assert_string_equal("val1", g_hash_table_lookup(options, "opt1"));
+    assert_string_equal("val2", g_hash_table_lookup(options, "opt2"));
+    assert_true(res);
+
+    options_destroy(options);
+}
+
+void
+parse_options_when_opt3_no_val_sets_error(void **state)
+{
+    gchar *args[] = { "cmd1", "cmd2", "opt1", "val1", "opt2", "val2", "opt3", NULL };
+    gchar *keys[] = { "opt1", "opt2", "opt3", NULL };
+
+    gboolean res = TRUE;
+
+    GHashTable *options = parse_options(&args[2], keys, &res);
+
+    assert_null(options);
+    assert_false(res);
+
+    options_destroy(options);
+}
+
+void
+parse_options_when_three_returns_map(void **state)
+{
+    gchar *args[] = { "cmd1", "cmd2", "opt1", "val1", "opt2", "val2", "opt3", "val3", NULL };
+    gchar *keys[] = { "opt1", "opt2", "opt3", NULL };
+
+    gboolean res = FALSE;
+
+    GHashTable *options = parse_options(&args[2], keys, &res);
+
+    assert_int_equal(3, g_hash_table_size(options));
+    assert_true(g_hash_table_contains(options, "opt1"));
+    assert_true(g_hash_table_contains(options, "opt2"));
+    assert_true(g_hash_table_contains(options, "opt3"));
+    assert_string_equal("val1", g_hash_table_lookup(options, "opt1"));
+    assert_string_equal("val2", g_hash_table_lookup(options, "opt2"));
+    assert_string_equal("val3", g_hash_table_lookup(options, "opt3"));
+    assert_true(res);
+
+    options_destroy(options);
+}
+
+void
+parse_options_when_unknown_opt_sets_error(void **state)
+{
+    gchar *args[] = { "cmd1", "cmd2", "opt1", "val1", "oops", "val2", "opt3", "val3", NULL };
+    gchar *keys[] = { "opt1", "opt2", "opt3", NULL };
+
+    gboolean res = TRUE;
+
+    GHashTable *options = parse_options(&args[2], keys, &res);
+
+    assert_null(options);
+    assert_false(res);
+
+    options_destroy(options);
+}
+
+void
+parse_options_with_duplicated_option_sets_error(void **state)
+{
+    gchar *args[] = { "cmd1", "cmd2", "opt1", "val1", "opt2", "val2", "opt1", "val3", NULL };
+    gchar *keys[] = { "opt1", "opt2", "opt3", NULL };
+
+    gboolean res = TRUE;
+
+    GHashTable *options = parse_options(&args[2], keys, &res);
+
+    assert_null(options);
+    assert_false(res);
+
+    options_destroy(options);
+}
\ No newline at end of file
diff --git a/unittests/test_parser.h b/unittests/test_parser.h
new file mode 100644
index 00000000..51d768fe
--- /dev/null
+++ b/unittests/test_parser.h
@@ -0,0 +1,50 @@
+void parse_null_returns_null(void **state);
+void parse_empty_returns_null(void **state);
+void parse_space_returns_null(void **state);
+void parse_cmd_no_args_returns_null(void **state);
+void parse_cmd_with_space_returns_null(void **state);
+void parse_cmd_with_too_few_returns_null(void **state);
+void parse_cmd_with_too_many_returns_null(void **state);
+void parse_cmd_one_arg(void **state);
+void parse_cmd_two_args(void **state);
+void parse_cmd_three_args(void **state);
+void parse_cmd_three_args_with_spaces(void **state);
+void parse_cmd_with_freetext(void **state);
+void parse_cmd_one_arg_with_freetext(void **state);
+void parse_cmd_two_args_with_freetext(void **state);
+void parse_cmd_min_zero(void **state);
+void parse_cmd_min_zero_with_freetext(void **state);
+void parse_cmd_with_quoted(void **state);
+void parse_cmd_with_quoted_and_space(void **state);
+void parse_cmd_with_quoted_and_many_spaces(void **state);
+void parse_cmd_with_many_quoted_and_many_spaces(void **state);
+void parse_cmd_freetext_with_quoted(void **state);
+void parse_cmd_freetext_with_quoted_and_space(void **state);
+void parse_cmd_freetext_with_quoted_and_many_spaces(void **state);
+void parse_cmd_freetext_with_many_quoted_and_many_spaces(void **state);
+void parse_cmd_with_quoted_freetext(void **state);
+void parse_cmd_with_third_arg_quoted_0_min_3_max(void **state);
+void parse_cmd_with_second_arg_quoted_0_min_3_max(void **state);
+void parse_cmd_with_second_and_third_arg_quoted_0_min_3_max(void **state);
+void count_one_token(void **state);
+void count_one_token_quoted_no_whitespace(void **state);
+void count_one_token_quoted_with_whitespace(void **state);
+void count_two_tokens(void **state);
+void count_two_tokens_first_quoted(void **state);
+void count_two_tokens_second_quoted(void **state);
+void count_two_tokens_both_quoted(void **state);
+void get_first_of_one(void **state);
+void get_first_of_two(void **state);
+void get_first_two_of_three(void **state);
+void get_first_two_of_three_first_quoted(void **state);
+void get_first_two_of_three_second_quoted(void **state);
+void get_first_two_of_three_first_and_second_quoted(void **state);
+void parse_options_when_none_returns_empty_hasmap(void **state);
+void parse_options_when_opt1_no_val_sets_error(void **state);
+void parse_options_when_one_returns_map(void **state);
+void parse_options_when_opt2_no_val_sets_error(void **state);
+void parse_options_when_two_returns_map(void **state);
+void parse_options_when_opt3_no_val_sets_error(void **state);
+void parse_options_when_three_returns_map(void **state);
+void parse_options_when_unknown_opt_sets_error(void **state);
+void parse_options_with_duplicated_option_sets_error(void **state);
diff --git a/unittests/test_preferences.c b/unittests/test_preferences.c
new file mode 100644
index 00000000..c4bcbf77
--- /dev/null
+++ b/unittests/test_preferences.c
@@ -0,0 +1,33 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "config/preferences.h"
+
+void statuses_console_defaults_to_all(void **state)
+{
+    char *setting = prefs_get_string(PREF_STATUSES_CONSOLE);
+
+    assert_non_null(setting);
+    assert_string_equal("all", setting);
+}
+
+void statuses_chat_defaults_to_all(void **state)
+{
+    char *setting = prefs_get_string(PREF_STATUSES_CHAT);
+
+    assert_non_null(setting);
+    assert_string_equal("all", setting);
+}
+
+void statuses_muc_defaults_to_all(void **state)
+{
+    char *setting = prefs_get_string(PREF_STATUSES_MUC);
+
+    assert_non_null(setting);
+    assert_string_equal("all", setting);
+}
diff --git a/unittests/test_preferences.h b/unittests/test_preferences.h
new file mode 100644
index 00000000..5bf79a6a
--- /dev/null
+++ b/unittests/test_preferences.h
@@ -0,0 +1,3 @@
+void statuses_console_defaults_to_all(void **state);
+void statuses_chat_defaults_to_all(void **state);
+void statuses_muc_defaults_to_all(void **state);
diff --git a/unittests/test_roster_list.c b/unittests/test_roster_list.c
new file mode 100644
index 00000000..41ccb8cf
--- /dev/null
+++ b/unittests/test_roster_list.c
@@ -0,0 +1,271 @@
+#include <glib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+
+#include "contact.h"
+#include "roster_list.h"
+
+void empty_list_when_none_added(void **state)
+{
+    roster_init();
+    GSList *list = roster_get_contacts();
+    assert_null(list);
+    roster_free();
+}
+
+void contains_one_element(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    GSList *list = roster_get_contacts();
+    assert_int_equal(1, g_slist_length(list));
+    roster_free();
+}
+
+void first_element_correct(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    GSList *list = roster_get_contacts();
+    PContact james = list->data;
+
+    assert_string_equal("James", p_contact_barejid(james));
+    roster_free();
+}
+
+void contains_two_elements(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    GSList *list = roster_get_contacts();
+
+    assert_int_equal(2, g_slist_length(list));
+    roster_free();
+}
+
+void first_and_second_elements_correct(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    GSList *list = roster_get_contacts();
+
+    PContact first = list->data;
+    PContact second = (g_slist_next(list))->data;
+
+    assert_string_equal("Dave", p_contact_barejid(first));
+    assert_string_equal("James", p_contact_barejid(second));
+    roster_free();
+}
+
+void contains_three_elements(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    GSList *list = roster_get_contacts();
+
+    assert_int_equal(3, g_slist_length(list));
+    roster_free();
+}
+
+void first_three_elements_correct(void **state)
+{
+    roster_init();
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    GSList *list = roster_get_contacts();
+    PContact bob = list->data;
+    PContact dave = (g_slist_next(list))->data;
+    PContact james = (g_slist_next(g_slist_next(list)))->data;
+
+    assert_string_equal("James", p_contact_barejid(james));
+    assert_string_equal("Dave", p_contact_barejid(dave));
+    assert_string_equal("Bob", p_contact_barejid(bob));
+    roster_free();
+}
+
+void add_twice_at_beginning_adds_once(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+    GSList *list = roster_get_contacts();
+    PContact first = list->data;
+    PContact second = (g_slist_next(list))->data;
+    PContact third = (g_slist_next(g_slist_next(list)))->data;
+
+    assert_int_equal(3, g_slist_length(list));
+    assert_string_equal("Bob", p_contact_barejid(first));
+    assert_string_equal("Dave", p_contact_barejid(second));
+    assert_string_equal("James", p_contact_barejid(third));
+    roster_free();
+}
+
+void add_twice_in_middle_adds_once(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+    GSList *list = roster_get_contacts();
+    PContact first = list->data;
+    PContact second = (g_slist_next(list))->data;
+    PContact third = (g_slist_next(g_slist_next(list)))->data;
+
+    assert_int_equal(3, g_slist_length(list));
+    assert_string_equal("Bob", p_contact_barejid(first));
+    assert_string_equal("Dave", p_contact_barejid(second));
+    assert_string_equal("James", p_contact_barejid(third));
+    roster_free();
+}
+
+void add_twice_at_end_adds_once(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    GSList *list = roster_get_contacts();
+    PContact first = list->data;
+    PContact second = (g_slist_next(list))->data;
+    PContact third = (g_slist_next(g_slist_next(list)))->data;
+
+    assert_int_equal(3, g_slist_length(list));
+    assert_string_equal("Bob", p_contact_barejid(first));
+    assert_string_equal("Dave", p_contact_barejid(second));
+    assert_string_equal("James", p_contact_barejid(third));
+    roster_free();
+}
+
+void find_first_exists(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+
+    char *search = strdup("B");
+
+    char *result = roster_contact_autocomplete(search);
+    assert_string_equal("Bob", result);
+    free(result);
+    free(search);
+    roster_free();
+}
+
+void find_second_exists(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+
+    char *result = roster_contact_autocomplete("Dav");
+    assert_string_equal("Dave", result);
+    free(result);
+    roster_free();
+}
+
+void find_third_exists(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+
+    char *result = roster_contact_autocomplete("Ja");
+    assert_string_equal("James", result);
+    free(result);
+    roster_free();
+}
+
+void find_returns_null(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+
+    char *result = roster_contact_autocomplete("Mike");
+    assert_null(result);
+    roster_free();
+}
+
+void find_on_empty_returns_null(void **state)
+{
+    roster_init();
+    char *result = roster_contact_autocomplete("James");
+    assert_null(result);
+    roster_free();
+}
+
+void find_twice_returns_second_when_two_match(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Jamie", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+
+    char *result1 = roster_contact_autocomplete("Jam");
+    char *result2 = roster_contact_autocomplete(result1);
+    assert_string_equal("Jamie", result2);
+    free(result1);
+    free(result2);
+    roster_free();
+}
+
+void find_five_times_finds_fifth(void **state)
+{
+    roster_init();
+    roster_add("Jama", NULL, NULL, NULL, FALSE);
+    roster_add("Jamb", NULL, NULL, NULL, FALSE);
+    roster_add("Mike", NULL, NULL, NULL, FALSE);
+    roster_add("Dave", NULL, NULL, NULL, FALSE);
+    roster_add("Jamm", NULL, NULL, NULL, FALSE);
+    roster_add("Jamn", NULL, NULL, NULL, FALSE);
+    roster_add("Matt", NULL, NULL, NULL, FALSE);
+    roster_add("Jamo", NULL, NULL, NULL, FALSE);
+    roster_add("Jamy", NULL, NULL, NULL, FALSE);
+    roster_add("Jamz", NULL, NULL, NULL, FALSE);
+
+    char *result1 = roster_contact_autocomplete("Jam");
+    char *result2 = roster_contact_autocomplete(result1);
+    char *result3 = roster_contact_autocomplete(result2);
+    char *result4 = roster_contact_autocomplete(result3);
+    char *result5 = roster_contact_autocomplete(result4);
+    assert_string_equal("Jamo", result5);
+    free(result1);
+    free(result2);
+    free(result3);
+    free(result4);
+    free(result5);
+    roster_free();
+}
+
+void find_twice_returns_first_when_two_match_and_reset(void **state)
+{
+    roster_init();
+    roster_add("James", NULL, NULL, NULL, FALSE);
+    roster_add("Jamie", NULL, NULL, NULL, FALSE);
+    roster_add("Bob", NULL, NULL, NULL, FALSE);
+
+    char *result1 = roster_contact_autocomplete("Jam");
+    roster_reset_search_attempts();
+    char *result2 = roster_contact_autocomplete(result1);
+    assert_string_equal("James", result2);
+    free(result1);
+    free(result2);
+    roster_free();
+}
diff --git a/unittests/test_roster_list.h b/unittests/test_roster_list.h
new file mode 100644
index 00000000..080bca9f
--- /dev/null
+++ b/unittests/test_roster_list.h
@@ -0,0 +1,18 @@
+void empty_list_when_none_added(void **state);
+void contains_one_element(void **state);
+void first_element_correct(void **state);
+void contains_two_elements(void **state);
+void first_and_second_elements_correct(void **state);
+void contains_three_elements(void **state);
+void first_three_elements_correct(void **state);
+void add_twice_at_beginning_adds_once(void **state);
+void add_twice_in_middle_adds_once(void **state);
+void add_twice_at_end_adds_once(void **state);
+void find_first_exists(void **state);
+void find_second_exists(void **state);
+void find_third_exists(void **state);
+void find_returns_null(void **state);
+void find_on_empty_returns_null(void **state);
+void find_twice_returns_second_when_two_match(void **state);
+void find_five_times_finds_fifth(void **state);
+void find_twice_returns_first_when_two_match_and_reset(void **state);
diff --git a/unittests/test_server_events.c b/unittests/test_server_events.c
new file mode 100644
index 00000000..58489807
--- /dev/null
+++ b/unittests/test_server_events.c
@@ -0,0 +1,100 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+
+#include "event/server_events.h"
+#include "roster_list.h"
+#include "chat_session.h"
+#include "config/preferences.h"
+#include "ui/ui.h"
+#include "ui/stub_ui.h"
+#include "muc.h"
+
+void console_shows_online_presence_when_set_online(void **state)
+{
+    prefs_set_string(PREF_STATUSES_CONSOLE, "online");
+    roster_init();
+    char *barejid = "test1@server";
+    roster_add(barejid, "bob", NULL, "both", FALSE);
+    Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);
+
+    expect_memory(ui_contact_online, barejid, barejid, sizeof(barejid));
+    expect_memory(ui_contact_online, resource, resource, sizeof(resource));
+    expect_value(ui_contact_online, last_activity, NULL);
+
+    sv_ev_contact_online(barejid, resource, NULL);
+
+    roster_clear();
+}
+
+void console_shows_online_presence_when_set_all(void **state)
+{
+    prefs_set_string(PREF_STATUSES_CONSOLE, "all");
+    roster_init();
+    char *barejid = "test1@server";
+    roster_add(barejid, "bob", NULL, "both", FALSE);
+    Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);
+
+    expect_memory(ui_contact_online, barejid, barejid, sizeof(barejid));
+    expect_memory(ui_contact_online, resource, resource, sizeof(resource));
+    expect_value(ui_contact_online, last_activity, NULL);
+
+    sv_ev_contact_online(barejid, resource, NULL);
+
+    roster_clear();
+}
+
+void console_shows_dnd_presence_when_set_all(void **state)
+{
+    prefs_set_string(PREF_STATUSES_CONSOLE, "all");
+    roster_init();
+    char *barejid = "test1@server";
+    roster_add(barejid, "bob", NULL, "both", FALSE);
+    Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);
+
+    expect_memory(ui_contact_online, barejid, barejid, sizeof(barejid));
+    expect_memory(ui_contact_online, resource, resource, sizeof(resource));
+    expect_value(ui_contact_online, last_activity, NULL);
+
+    sv_ev_contact_online(barejid, resource, NULL);
+
+    roster_clear();
+}
+
+void handle_offline_removes_chat_session(void **state)
+{
+    chat_sessions_init();
+    char *barejid = "friend@server.chat.com";
+    char *resource = "home";
+    roster_init();
+    roster_add(barejid, "bob", NULL, "both", FALSE);
+    Resource *resourcep = resource_new(resource, RESOURCE_ONLINE, NULL, 10);
+    roster_update_presence(barejid, resourcep, NULL);
+    chat_session_recipient_active(barejid, resource, FALSE);
+    sv_ev_contact_offline(barejid, resource, NULL);
+    ChatSession *session = chat_session_get(barejid);
+
+    assert_null(session);
+
+    roster_clear();
+    chat_sessions_clear();
+}
+
+void lost_connection_clears_chat_sessions(void **state)
+{
+    chat_sessions_init();
+    chat_session_recipient_active("bob@server.org", "laptop", FALSE);
+    chat_session_recipient_active("steve@server.org", "mobile", FALSE);
+    expect_any_cons_show_error();
+
+    sv_ev_lost_connection();
+
+    ChatSession *session1 = chat_session_get("bob@server.org");
+    ChatSession *session2 = chat_session_get("steve@server.org");
+    assert_null(session1);
+    assert_null(session2);
+}
diff --git a/unittests/test_server_events.h b/unittests/test_server_events.h
new file mode 100644
index 00000000..81a436f4
--- /dev/null
+++ b/unittests/test_server_events.h
@@ -0,0 +1,14 @@
+void console_doesnt_show_online_presence_when_set_none(void **state);
+void console_shows_online_presence_when_set_online(void **state);
+void console_shows_online_presence_when_set_all(void **state);
+void console_doesnt_show_dnd_presence_when_set_none(void **state);
+void console_doesnt_show_dnd_presence_when_set_online(void **state);
+void console_shows_dnd_presence_when_set_all(void **state);
+void handle_message_error_when_no_recipient(void **state);
+void handle_message_error_when_recipient_cancel(void **state);
+void handle_message_error_when_recipient_cancel_disables_chat_session(void **state);
+void handle_message_error_when_recipient_and_no_type(void **state);
+void handle_presence_error_when_no_recipient(void **state);
+void handle_presence_error_when_from_recipient(void **state);
+void handle_offline_removes_chat_session(void **state);
+void lost_connection_clears_chat_sessions(void **state);
diff --git a/unittests/ui/stub_ui.c b/unittests/ui/stub_ui.c
new file mode 100644
index 00000000..0052bfc8
--- /dev/null
+++ b/unittests/ui/stub_ui.c
@@ -0,0 +1,512 @@
+#include <glib.h>
+#include <wchar.h>
+
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "ui/window.h"
+#include "ui/ui.h"
+
+#include "unittests/ui/stub_ui.h"
+
+// mock state
+
+static char output[256];
+
+void
+expect_cons_show(char *expected)
+{
+    expect_string(cons_show, output, expected);
+}
+
+void
+expect_any_cons_show(void)
+{
+    expect_any(cons_show, output);
+}
+
+void
+expect_cons_show_error(char *expected)
+{
+    expect_string(cons_show_error, output, expected);
+}
+
+void
+expect_any_cons_show_error(void)
+{
+    expect_any(cons_show_error, output);
+}
+
+void
+expect_ui_current_print_line(char *message)
+{
+    expect_string(ui_current_print_line, output, message);
+}
+
+void
+expect_ui_current_print_formatted_line(char show_char, int attrs, char *message)
+{
+    expect_value(ui_current_print_formatted_line, show_char, show_char);
+    expect_value(ui_current_print_formatted_line, attrs, attrs);
+    expect_string(ui_current_print_formatted_line, output, message);
+}
+
+// stubs
+
+void ui_init(void) {}
+void ui_load_colours(void) {}
+void ui_update(void) {}
+void ui_close(void) {}
+void ui_redraw(void) {}
+void ui_resize(void) {}
+GSList* ui_get_chat_recipients(void)
+{
+    return NULL;
+}
+
+void ui_switch_win(ProfWin *win) {}
+
+void ui_gone_secure(const char * const barejid, gboolean trusted) {}
+void ui_gone_insecure(const char * const barejid) {}
+void ui_trust(const char * const barejid) {}
+void ui_untrust(const char * const barejid) {}
+void ui_smp_recipient_initiated(const char * const barejid) {}
+void ui_smp_recipient_initiated_q(const char * const barejid, const char *question) {}
+
+void ui_smp_successful(const char * const barejid) {}
+void ui_smp_unsuccessful_sender(const char * const barejid) {}
+void ui_smp_unsuccessful_receiver(const char * const barejid) {}
+void ui_smp_aborted(const char * const barejid) {}
+
+void ui_smp_answer_success(const char * const barejid) {}
+void ui_smp_answer_failure(const char * const barejid) {}
+
+void ui_otr_authenticating(const char * const barejid) {}
+void ui_otr_authetication_waiting(const char * const recipient) {}
+void ui_sigwinch_handler(int sig) {}
+
+unsigned long ui_get_idle_time(void)
+{
+    return 0;
+}
+
+void ui_reset_idle_time(void) {}
+ProfPrivateWin* ui_new_private_win(const char * const fulljid)
+{
+    return NULL;
+}
+
+ProfChatWin* ui_new_chat_win(const char * const barejid)
+{
+    return NULL;
+}
+
+void ui_print_system_msg_from_recipient(const char * const barejid, const char *message) {}
+gint ui_unread(void)
+{
+    return 0;
+}
+
+void ui_close_connected_win(int index) {}
+int ui_close_all_wins(void)
+{
+    return 0;
+}
+
+int ui_close_read_wins(void)
+{
+    return 0;
+}
+
+// current window actions
+void ui_clear_current(void) {}
+
+win_type_t ui_current_win_type(void)
+{
+    return (win_type_t)mock();
+}
+
+gboolean ui_current_win_is_otr(void)
+{
+    return (gboolean)mock();
+}
+
+ProfChatWin *ui_get_current_chat(void)
+{
+    return (ProfChatWin*)mock();
+}
+
+void ui_current_print_line(const char * const msg, ...)
+{
+    va_list args;
+    va_start(args, msg);
+    vsnprintf(output, sizeof(output), msg, args);
+    check_expected(output);
+    va_end(args);
+}
+
+void ui_current_print_formatted_line(const char show_char, int attrs, const char * const msg, ...)
+{
+    check_expected(show_char);
+    check_expected(attrs);
+    va_list args;
+    va_start(args, msg);
+    vsnprintf(output, sizeof(output), msg, args);
+    check_expected(output);
+    va_end(args);
+}
+
+void ui_current_error_line(const char * const msg) {}
+void ui_win_error_line(ProfWin *window, const char * const msg) {}
+
+
+win_type_t ui_win_type(int index)
+{
+    return WIN_CONSOLE;
+}
+
+void ui_close_win(int index) {}
+
+int ui_win_unread(int index)
+{
+    return 0;
+}
+
+void ui_page_up(void) {}
+void ui_page_down(void) {}
+void ui_subwin_page_up(void) {}
+void ui_subwin_page_down(void) {}
+
+char * ui_ask_password(void)
+{
+    return mock_ptr_type(char *);
+}
+
+void ui_handle_stanza(const char * const msg) {}
+
+// ui events
+void ui_contact_online(char *barejid, Resource *resource, GDateTime *last_activity)
+{
+    check_expected(barejid);
+    check_expected(resource);
+    check_expected(last_activity);
+}
+
+void ui_contact_typing(const char * const barejid, const char * const resource) {}
+void ui_incoming_msg(const char * const from, const char * const resource, const char * const message, GTimeVal *tv_stamp) {}
+void ui_message_receipt(const char * const barejid, const char * const id) {}
+
+void ui_incoming_private_msg(const char * const fulljid, const char * const message, GTimeVal *tv_stamp) {}
+
+void ui_disconnected(void) {}
+void ui_recipient_gone(const char * const barejid, const char * const resource) {}
+
+void ui_outgoing_chat_msg(ProfChatWin *chatwin, const char * const message, char *id) {}
+void ui_outgoing_chat_msg_carbon(const char * const barejid, const char * const message) {}
+void ui_outgoing_private_msg(ProfPrivateWin *privwin, const char * const message) {}
+
+void ui_room_join(const char * const roomjid, gboolean focus) {}
+void ui_switch_to_room(const char * const roomjid) {}
+
+void ui_room_role_change(const char * const roomjid, const char * const role, const char * const actor,
+    const char * const reason) {}
+void ui_room_affiliation_change(const char * const roomjid, const char * const affiliation, const char * const actor,
+    const char * const reason) {}
+void ui_room_role_and_affiliation_change(const char * const roomjid, const char * const role,
+    const char * const affiliation, const char * const actor, const char * const reason) {}
+void ui_room_occupant_role_change(const char * const roomjid, const char * const nick, const char * const role,
+    const char * const actor, const char * const reason) {}
+void ui_room_occupant_affiliation_change(const char * const roomjid, const char * const nick, const char * const affiliation,
+    const char * const actor, const char * const reason) {}
+void ui_room_occupant_role_and_affiliation_change(const char * const roomjid, const char * const nick, const char * const role,
+    const char * const affiliation, const char * const actor, const char * const reason) {}
+void ui_room_roster(const char * const roomjid, GList *occupants, const char * const presence) {}
+void ui_room_history(const char * const roomjid, const char * const nick,
+    GTimeVal tv_stamp, const char * const message) {}
+void ui_room_message(const char * const roomjid, const char * const nick,
+    const char * const message) {}
+void ui_room_subject(const char * const roomjid, const char * const nick, const char * const subject) {}
+void ui_room_requires_config(const char * const roomjid) {}
+void ui_room_destroy(const char * const roomjid) {}
+void ui_show_room_info(ProfMucWin *mucwin) {}
+void ui_show_room_role_list(ProfMucWin *mucwin, muc_role_t role) {}
+void ui_show_room_affiliation_list(ProfMucWin *mucwin, muc_affiliation_t affiliation) {}
+void ui_handle_room_info_error(const char * const roomjid, const char * const error) {}
+void ui_show_room_disco_info(const char * const roomjid, GSList *identities, GSList *features) {}
+void ui_room_destroyed(const char * const roomjid, const char * const reason, const char * const new_jid,
+    const char * const password) {}
+void ui_room_kicked(const char * const roomjid, const char * const actor, const char * const reason) {}
+void ui_room_member_kicked(const char * const roomjid, const char * const nick, const char * const actor,
+    const char * const reason) {}
+void ui_room_banned(const char * const roomjid, const char * const actor, const char * const reason) {}
+void ui_room_member_banned(const char * const roomjid, const char * const nick, const char * const actor,
+    const char * const reason) {}
+void ui_leave_room(const char * const roomjid) {}
+void ui_room_broadcast(const char * const roomjid,
+    const char * const message) {}
+void ui_room_member_offline(const char * const roomjid, const char * const nick) {}
+void ui_room_member_online(const char * const roomjid, const char * const nick, const char * const roles,
+    const char * const affiliation, const char * const show, const char * const status) {}
+void ui_room_member_nick_change(const char * const roomjid,
+    const char * const old_nick, const char * const nick) {}
+void ui_room_nick_change(const char * const roomjid, const char * const nick) {}
+void ui_room_member_presence(const char * const roomjid,
+    const char * const nick, const char * const show, const char * const status) {}
+void ui_room_update_occupants(const char * const roomjid) {}
+void ui_room_show_occupants(const char * const roomjid) {}
+void ui_room_hide_occupants(const char * const roomjid) {}
+void ui_show_roster(void) {}
+void ui_hide_roster(void) {}
+void ui_roster_add(const char * const barejid, const char * const name) {}
+void ui_roster_remove(const char * const barejid) {}
+void ui_contact_already_in_group(const char * const contact, const char * const group) {}
+void ui_contact_not_in_group(const char * const contact, const char * const group) {}
+void ui_group_added(const char * const contact, const char * const group) {}
+void ui_group_removed(const char * const contact, const char * const group) {}
+void ui_chat_win_contact_online(PContact contact, Resource *resource, GDateTime *last_activity) {}
+void ui_chat_win_contact_offline(PContact contact, char *resource, char *status) {}
+gboolean ui_chat_win_exists(const char * const barejid)
+{
+    return TRUE;
+}
+
+void ui_contact_offline(char *barejid, char *resource, char *status) {}
+
+void ui_handle_recipient_not_found(const char * const recipient, const char * const err_msg)
+{
+    check_expected(recipient);
+    check_expected(err_msg);
+}
+
+void ui_handle_recipient_error(const char * const recipient, const char * const err_msg)
+{
+    check_expected(recipient);
+    check_expected(err_msg);
+}
+
+void ui_handle_error(const char * const err_msg)
+{
+    check_expected(err_msg);
+}
+
+void ui_clear_win_title(void) {}
+void ui_goodbye_title(void) {}
+void ui_handle_room_join_error(const char * const roomjid, const char * const err) {}
+void ui_handle_room_configuration(const char * const roomjid, DataForm *form) {}
+void ui_handle_room_configuration_form_error(const char * const roomjid, const char * const message) {}
+void ui_handle_room_config_submit_result(const char * const roomjid) {}
+void ui_handle_room_config_submit_result_error(const char * const roomjid, const char * const message) {}
+void ui_handle_room_affiliation_list_error(const char * const roomjid, const char * const affiliation,
+    const char * const error) {}
+void ui_handle_room_affiliation_list(const char * const roomjid, const char * const affiliation, GSList *jids) {}
+void ui_handle_room_affiliation_set_error(const char * const roomjid, const char * const jid,
+    const char * const affiliation, const char * const error) {}
+void ui_handle_room_role_set_error(const char * const roomjid, const char * const nick, const char * const role,
+    const char * const error) {}
+void ui_handle_room_role_list_error(const char * const roomjid, const char * const role, const char * const error) {}
+void ui_handle_room_role_list(const char * const roomjid, const char * const role, GSList *nicks) {}
+void ui_handle_room_kick_error(const char * const roomjid, const char * const nick, const char * const error) {}
+void ui_show_form(ProfMucConfWin *confwin) {}
+void ui_show_form_field(ProfWin *window, DataForm *form, char *tag) {}
+void ui_show_form_help(ProfMucConfWin *confwin) {}
+void ui_show_form_field_help(ProfMucConfWin *confwin, char *tag) {}
+void ui_show_lines(ProfWin *window, const gchar** lines) {}
+void ui_redraw_all_room_rosters(void) {}
+void ui_show_all_room_rosters(void) {}
+void ui_hide_all_room_rosters(void) {}
+
+gboolean ui_tidy_wins(void) {
+    return TRUE;
+}
+void ui_prune_wins(void) {}
+gboolean ui_swap_wins(int source_win, int target_win)
+{
+    return FALSE;
+}
+
+void ui_auto_away(void) {}
+void ui_end_auto_away(void) {}
+void ui_titlebar_presence(contact_presence_t presence) {}
+void ui_handle_login_account_success(ProfAccount *account) {}
+void ui_update_presence(const resource_presence_t resource_presence,
+    const char * const message, const char * const show) {}
+void ui_about(void) {}
+void ui_statusbar_new(const int win) {}
+
+char*  ui_readline(void)
+{
+    return NULL;
+}
+
+void ui_inp_history_append(char *inp) {}
+
+void ui_input_clear(void) {}
+void ui_input_nonblocking(gboolean reset) {}
+
+void ui_invalid_command_usage(const char * const usage, void (*setting_func)(void)) {}
+
+void ui_create_xmlconsole_win(void) {}
+gboolean ui_xmlconsole_exists(void)
+{
+    return FALSE;
+}
+
+void ui_open_xmlconsole_win(void) {}
+
+gboolean ui_win_has_unsaved_form(int num)
+{
+    return FALSE;
+}
+
+void
+ui_write(char *line, int offset) {}
+
+// console window actions
+
+void cons_show(const char * const msg, ...)
+{
+    va_list args;
+    va_start(args, msg);
+    vsnprintf(output, sizeof(output), msg, args);
+    check_expected(output);
+    va_end(args);
+}
+
+void cons_about(void) {}
+void cons_help(void) {}
+void cons_navigation_help(void) {}
+void cons_prefs(void) {}
+void cons_show_ui_prefs(void) {}
+void cons_show_desktop_prefs(void) {}
+void cons_show_chat_prefs(void) {}
+void cons_show_log_prefs(void) {}
+void cons_show_presence_prefs(void) {}
+void cons_show_connection_prefs(void) {}
+void cons_show_otr_prefs(void) {}
+
+void cons_show_account(ProfAccount *account)
+{
+    check_expected(account);
+}
+
+void cons_debug(const char * const msg, ...) {}
+void cons_show_time(void) {}
+void cons_show_word(const char * const word) {}
+
+void cons_show_error(const char * const cmd, ...)
+{
+    va_list args;
+    va_start(args, cmd);
+    vsnprintf(output, sizeof(output), cmd, args);
+    check_expected(output);
+    va_end(args);
+}
+
+void cons_show_contacts(GSList * list) {}
+
+void cons_show_roster(GSList * list)
+{
+    check_expected(list);
+}
+
+void cons_show_roster_group(const char * const group, GSList * list) {}
+void cons_show_wins(void) {}
+void cons_show_status(const char * const barejid) {}
+void cons_show_info(PContact pcontact) {}
+void cons_show_caps(const char * const fulljid, resource_presence_t presence) {}
+void cons_show_themes(GSList *themes) {}
+
+void cons_show_aliases(GList *aliases)
+{
+    check_expected(aliases);
+}
+
+void cons_show_login_success(ProfAccount *account) {}
+void cons_show_software_version(const char * const jid,
+    const char * const presence, const char * const name,
+    const char * const version, const char * const os) {}
+
+void cons_show_account_list(gchar **accounts)
+{
+    check_expected(accounts);
+}
+
+void cons_show_room_list(GSList *room, const char * const conference_node) {}
+
+void cons_show_bookmarks(const GList *list)
+{
+    check_expected(list);
+}
+
+void cons_show_disco_items(GSList *items, const char * const jid) {}
+void cons_show_disco_info(const char *from, GSList *identities, GSList *features) {}
+void cons_show_room_invite(const char * const invitor, const char * const room,
+    const char * const reason) {}
+void cons_check_version(gboolean not_available_msg) {}
+void cons_show_typing(const char * const barejid) {}
+void cons_show_incoming_message(const char * const short_from, const int win_index) {}
+void cons_show_room_invites(GSList *invites) {}
+void cons_show_received_subs(void) {}
+void cons_show_sent_subs(void) {}
+void cons_alert(void) {}
+void cons_theme_setting(void) {}
+void cons_privileges_setting(void) {}
+void cons_beep_setting(void) {}
+void cons_flash_setting(void) {}
+void cons_splash_setting(void) {}
+void cons_vercheck_setting(void) {}
+void cons_resource_setting(void) {}
+void cons_occupants_setting(void) {}
+void cons_roster_setting(void) {}
+void cons_presence_setting(void) {}
+void cons_wrap_setting(void) {}
+void cons_winstidy_setting(void) {}
+void cons_time_setting(void) {}
+void cons_mouse_setting(void) {}
+void cons_statuses_setting(void) {}
+void cons_titlebar_setting(void) {}
+void cons_notify_setting(void) {}
+void cons_states_setting(void) {}
+void cons_outtype_setting(void) {}
+void cons_intype_setting(void) {}
+void cons_gone_setting(void) {}
+void cons_history_setting(void) {}
+void cons_carbons_setting(void) {}
+void cons_receipts_setting(void) {}
+void cons_log_setting(void) {}
+void cons_chlog_setting(void) {}
+void cons_grlog_setting(void) {}
+void cons_autoaway_setting(void) {}
+void cons_reconnect_setting(void) {}
+void cons_autoping_setting(void) {}
+void cons_priority_setting(void) {}
+void cons_autoconnect_setting(void) {}
+void cons_inpblock_setting(void) {}
+
+void cons_show_contact_online(PContact contact, Resource *resource, GDateTime *last_activity)
+{
+    check_expected(contact);
+    check_expected(resource);
+    check_expected(last_activity);
+}
+
+void cons_show_contact_offline(PContact contact, char *resource, char *status) {}
+void cons_theme_colours(void) {}
+
+// roster window
+void rosterwin_roster(void) {}
+
+// occupants window
+void occupantswin_occupants(const char * const room) {}
+
+// desktop notifier actions
+void notifier_uninit(void) {}
+
+void notify_typing(const char * const handle) {}
+void notify_message(ProfWin *window, const char * const name, const char * const text) {}
+void notify_room_message(const char * const handle, const char * const room,
+    int win, const char * const text) {}
+void notify_remind(void) {}
+void notify_invite(const char * const from, const char * const room,
+    const char * const reason) {}
+void notify_subscription(const char * const from) {}
diff --git a/unittests/ui/stub_ui.h b/unittests/ui/stub_ui.h
new file mode 100644
index 00000000..81357a86
--- /dev/null
+++ b/unittests/ui/stub_ui.h
@@ -0,0 +1,6 @@
+void expect_cons_show(char *expected);
+void expect_any_cons_show(void);
+void expect_cons_show_error(char *expected);
+void expect_any_cons_show_error(void);
+void expect_ui_current_print_line(char *message);
+void expect_ui_current_print_formatted_line(char show_char, int attrs, char *message);
\ No newline at end of file
diff --git a/unittests/unittests.c b/unittests/unittests.c
new file mode 100644
index 00000000..3f860178
--- /dev/null
+++ b/unittests/unittests.c
@@ -0,0 +1,595 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <sys/stat.h>
+
+#include "config.h"
+#include "chat_session.h"
+
+#include "helpers.h"
+#include "test_autocomplete.h"
+#include "test_chat_session.h"
+#include "test_common.h"
+#include "test_contact.h"
+#include "test_cmd_connect.h"
+#include "test_cmd_account.h"
+#include "test_cmd_rooms.h"
+#include "test_cmd_sub.h"
+#include "test_cmd_statuses.h"
+#include "test_cmd_otr.h"
+#include "test_jid.h"
+#include "test_parser.h"
+#include "test_roster_list.h"
+#include "test_preferences.h"
+#include "test_server_events.h"
+#include "test_cmd_alias.h"
+#include "test_cmd_bookmark.h"
+#include "test_cmd_join.h"
+#include "test_muc.h"
+#include "test_cmd_roster.h"
+#include "test_cmd_disconnect.h"
+#include "test_form.h"
+
+int main(int argc, char* argv[]) {
+    const UnitTest all_tests[] = {
+        unit_test(replace_one_substr),
+        unit_test(replace_one_substr_beginning),
+        unit_test(replace_one_substr_end),
+        unit_test(replace_two_substr),
+        unit_test(replace_char),
+        unit_test(replace_when_none),
+        unit_test(replace_when_match),
+        unit_test(replace_when_string_empty),
+        unit_test(replace_when_string_null),
+        unit_test(replace_when_sub_empty),
+        unit_test(replace_when_sub_null),
+        unit_test(replace_when_new_empty),
+        unit_test(replace_when_new_null),
+        unit_test(compare_win_nums_less),
+        unit_test(compare_win_nums_equal),
+        unit_test(compare_win_nums_greater),
+        unit_test(compare_0s_equal),
+        unit_test(compare_0_greater_than_1),
+        unit_test(compare_1_less_than_0),
+        unit_test(compare_0_less_than_11),
+        unit_test(compare_11_greater_than_0),
+        unit_test(compare_0_greater_than_9),
+        unit_test(compare_9_less_than_0),
+        unit_test(next_available_when_only_console),
+        unit_test(next_available_3_at_end),
+        unit_test(next_available_9_at_end),
+        unit_test(next_available_0_at_end),
+        unit_test(next_available_2_in_first_gap),
+        unit_test(next_available_9_in_first_gap),
+        unit_test(next_available_0_in_first_gap),
+        unit_test(next_available_11_in_first_gap),
+        unit_test(next_available_24_first_big_gap),
+        unit_test(test_online_is_valid_resource_presence_string),
+        unit_test(test_chat_is_valid_resource_presence_string),
+        unit_test(test_away_is_valid_resource_presence_string),
+        unit_test(test_xa_is_valid_resource_presence_string),
+        unit_test(test_dnd_is_valid_resource_presence_string),
+        unit_test(test_available_is_not_valid_resource_presence_string),
+        unit_test(test_unavailable_is_not_valid_resource_presence_string),
+        unit_test(test_blah_is_not_valid_resource_presence_string),
+        unit_test(test_p_sha1_hash1),
+        unit_test(test_p_sha1_hash2),
+        unit_test(test_p_sha1_hash3),
+        unit_test(test_p_sha1_hash4),
+        unit_test(test_p_sha1_hash5),
+        unit_test(test_p_sha1_hash6),
+        unit_test(test_p_sha1_hash7),
+        unit_test(utf8_display_len_null_str),
+        unit_test(utf8_display_len_1_non_wide),
+        unit_test(utf8_display_len_1_wide),
+        unit_test(utf8_display_len_non_wide),
+        unit_test(utf8_display_len_wide),
+        unit_test(utf8_display_len_all_wide),
+        unit_test(strip_quotes_does_nothing_when_no_quoted),
+        unit_test(strip_quotes_strips_first),
+        unit_test(strip_quotes_strips_last),
+        unit_test(strip_quotes_strips_both),
+
+        unit_test(clear_empty),
+        unit_test(reset_after_create),
+        unit_test(find_after_create),
+        unit_test(get_after_create_returns_null),
+        unit_test(add_one_and_complete),
+        unit_test(add_two_and_complete_returns_first),
+        unit_test(add_two_and_complete_returns_second),
+        unit_test(add_two_adds_two),
+        unit_test(add_two_same_adds_one),
+        unit_test(add_two_same_updates),
+
+        unit_test(create_jid_from_null_returns_null),
+        unit_test(create_jid_from_empty_string_returns_null),
+        unit_test(create_jid_from_full_returns_full),
+        unit_test(create_jid_from_full_returns_bare),
+        unit_test(create_jid_from_full_returns_resourcepart),
+        unit_test(create_jid_from_full_returns_localpart),
+        unit_test(create_jid_from_full_returns_domainpart),
+        unit_test(create_jid_from_full_nolocal_returns_full),
+        unit_test(create_jid_from_full_nolocal_returns_bare),
+        unit_test(create_jid_from_full_nolocal_returns_resourcepart),
+        unit_test(create_jid_from_full_nolocal_returns_domainpart),
+        unit_test(create_jid_from_full_nolocal_returns_null_localpart),
+        unit_test(create_jid_from_bare_returns_null_full),
+        unit_test(create_jid_from_bare_returns_null_resource),
+        unit_test(create_jid_from_bare_returns_bare),
+        unit_test(create_jid_from_bare_returns_localpart),
+        unit_test(create_jid_from_bare_returns_domainpart),
+        unit_test(create_room_jid_returns_room),
+        unit_test(create_room_jid_returns_nick),
+        unit_test(create_with_slash_in_resource),
+        unit_test(create_with_at_in_resource),
+        unit_test(create_with_at_and_slash_in_resource),
+        unit_test(create_full_with_trailing_slash),
+        unit_test(returns_fulljid_when_exists),
+        unit_test(returns_barejid_when_fulljid_not_exists),
+
+        unit_test(parse_null_returns_null),
+        unit_test(parse_empty_returns_null),
+        unit_test(parse_space_returns_null),
+        unit_test(parse_cmd_no_args_returns_null),
+        unit_test(parse_cmd_with_space_returns_null),
+        unit_test(parse_cmd_with_too_few_returns_null),
+        unit_test(parse_cmd_with_too_many_returns_null),
+        unit_test(parse_cmd_one_arg),
+        unit_test(parse_cmd_two_args),
+        unit_test(parse_cmd_three_args),
+        unit_test(parse_cmd_three_args_with_spaces),
+        unit_test(parse_cmd_with_freetext),
+        unit_test(parse_cmd_one_arg_with_freetext),
+        unit_test(parse_cmd_two_args_with_freetext),
+        unit_test(parse_cmd_min_zero),
+        unit_test(parse_cmd_min_zero_with_freetext),
+        unit_test(parse_cmd_with_quoted),
+        unit_test(parse_cmd_with_quoted_and_space),
+        unit_test(parse_cmd_with_quoted_and_many_spaces),
+        unit_test(parse_cmd_with_many_quoted_and_many_spaces),
+        unit_test(parse_cmd_freetext_with_quoted),
+        unit_test(parse_cmd_freetext_with_quoted_and_space),
+        unit_test(parse_cmd_freetext_with_quoted_and_many_spaces),
+        unit_test(parse_cmd_freetext_with_many_quoted_and_many_spaces),
+        unit_test(parse_cmd_with_quoted_freetext),
+        unit_test(parse_cmd_with_third_arg_quoted_0_min_3_max),
+        unit_test(parse_cmd_with_second_arg_quoted_0_min_3_max),
+        unit_test(parse_cmd_with_second_and_third_arg_quoted_0_min_3_max),
+        unit_test(count_one_token),
+        unit_test(count_one_token_quoted_no_whitespace),
+        unit_test(count_one_token_quoted_with_whitespace),
+        unit_test(count_two_tokens),
+        unit_test(count_two_tokens_first_quoted),
+        unit_test(count_two_tokens_second_quoted),
+        unit_test(count_two_tokens_both_quoted),
+        unit_test(get_first_of_one),
+        unit_test(get_first_of_two),
+        unit_test(get_first_two_of_three),
+        unit_test(get_first_two_of_three_first_quoted),
+        unit_test(get_first_two_of_three_second_quoted),
+        unit_test(get_first_two_of_three_first_and_second_quoted),
+        unit_test(parse_options_when_none_returns_empty_hasmap),
+        unit_test(parse_options_when_opt1_no_val_sets_error),
+        unit_test(parse_options_when_one_returns_map),
+        unit_test(parse_options_when_opt2_no_val_sets_error),
+        unit_test(parse_options_when_two_returns_map),
+        unit_test(parse_options_when_opt3_no_val_sets_error),
+        unit_test(parse_options_when_three_returns_map),
+        unit_test(parse_options_when_unknown_opt_sets_error),
+        unit_test(parse_options_with_duplicated_option_sets_error),
+
+        unit_test(empty_list_when_none_added),
+        unit_test(contains_one_element),
+        unit_test(first_element_correct),
+        unit_test(contains_two_elements),
+        unit_test(first_and_second_elements_correct),
+        unit_test(contains_three_elements),
+        unit_test(first_three_elements_correct),
+        unit_test(add_twice_at_beginning_adds_once),
+        unit_test(add_twice_in_middle_adds_once),
+        unit_test(add_twice_at_end_adds_once),
+        unit_test(find_first_exists),
+        unit_test(find_second_exists),
+        unit_test(find_third_exists),
+        unit_test(find_returns_null),
+        unit_test(find_on_empty_returns_null),
+        unit_test(find_twice_returns_second_when_two_match),
+        unit_test(find_five_times_finds_fifth),
+        unit_test(find_twice_returns_first_when_two_match_and_reset),
+
+        unit_test_setup_teardown(returns_false_when_chat_session_does_not_exist,
+            init_chat_sessions,
+            close_chat_sessions),
+        unit_test_setup_teardown(creates_chat_session_on_recipient_activity,
+            init_chat_sessions,
+            close_chat_sessions),
+        unit_test_setup_teardown(replaces_chat_session_on_recipient_activity_with_different_resource,
+            init_chat_sessions,
+            close_chat_sessions),
+        unit_test_setup_teardown(removes_chat_session,
+            init_chat_sessions,
+            close_chat_sessions),
+        unit_test_setup_teardown(cmd_connect_shows_message_when_disconnecting,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_message_when_connecting,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_message_when_connected,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_message_when_undefined,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_when_no_account,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_fail_message,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_lowercases_argument,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_asks_password_when_not_in_account,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_message_when_connecting_with_account,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_connects_with_account,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_usage_when_no_server_value,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_usage_when_server_no_port_value,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_usage_when_no_port_value,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_usage_when_port_no_server_value,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_message_when_port_0,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_message_when_port_minus1,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_message_when_port_65536,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_message_when_port_contains_chars,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_with_server_when_provided,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_with_port_when_provided,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_with_server_and_port_when_provided,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_usage_when_server_provided_twice,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_usage_when_port_provided_twice,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_usage_when_invalid_first_property,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_connect_shows_usage_when_invalid_second_property,
+            load_preferences,
+            close_preferences),
+
+        unit_test(cmd_rooms_shows_message_when_disconnected),
+        unit_test(cmd_rooms_shows_message_when_disconnecting),
+        unit_test(cmd_rooms_shows_message_when_connecting),
+        unit_test(cmd_rooms_shows_message_when_started),
+        unit_test(cmd_rooms_shows_message_when_undefined),
+        unit_test(cmd_rooms_uses_account_default_when_no_arg),
+        unit_test(cmd_rooms_arg_used_when_passed),
+
+        unit_test(cmd_account_shows_usage_when_not_connected_and_no_args),
+        unit_test(cmd_account_shows_account_when_connected_and_no_args),
+        unit_test(cmd_account_list_shows_accounts),
+        unit_test(cmd_account_show_shows_usage_when_no_arg),
+        unit_test(cmd_account_show_shows_message_when_account_does_not_exist),
+        unit_test(cmd_account_show_shows_account_when_exists),
+        unit_test(cmd_account_add_shows_usage_when_no_arg),
+        unit_test(cmd_account_add_adds_account),
+        unit_test(cmd_account_enable_shows_usage_when_no_arg),
+        unit_test(cmd_account_enable_enables_account),
+        unit_test(cmd_account_enable_shows_message_when_account_doesnt_exist),
+        unit_test(cmd_account_disable_shows_usage_when_no_arg),
+        unit_test(cmd_account_disable_disables_account),
+        unit_test(cmd_account_disable_shows_message_when_account_doesnt_exist),
+        unit_test(cmd_account_rename_shows_usage_when_no_args),
+        unit_test(cmd_account_rename_shows_usage_when_one_arg),
+        unit_test(cmd_account_rename_renames_account),
+        unit_test(cmd_account_rename_shows_message_when_not_renamed),
+        unit_test(cmd_account_set_shows_usage_when_no_args),
+        unit_test(cmd_account_set_shows_usage_when_one_arg),
+        unit_test(cmd_account_set_shows_usage_when_two_args),
+        unit_test(cmd_account_set_shows_message_when_account_doesnt_exist),
+        unit_test(cmd_account_set_jid_shows_message_for_malformed_jid),
+        unit_test(cmd_account_set_jid_sets_barejid),
+        unit_test(cmd_account_set_jid_sets_resource),
+        unit_test(cmd_account_set_server_sets_server),
+        unit_test(cmd_account_set_resource_sets_resource),
+        unit_test(cmd_account_set_password_sets_password),
+        unit_test(cmd_account_set_eval_password_sets_eval_password),
+        unit_test(cmd_account_set_password_when_eval_password_set),
+        unit_test(cmd_account_set_eval_password_when_password_set),
+        unit_test(cmd_account_set_muc_sets_muc),
+        unit_test(cmd_account_set_nick_sets_nick),
+#ifdef HAVE_LIBOTR
+        unit_test(cmd_account_show_message_for_missing_otr_policy),
+        unit_test(cmd_account_show_message_for_invalid_otr_policy),
+        unit_test(cmd_account_set_otr_sets_otr),
+#endif
+        unit_test(cmd_account_set_status_shows_message_when_invalid_status),
+        unit_test(cmd_account_set_status_sets_status_when_valid),
+        unit_test(cmd_account_set_status_sets_status_when_last),
+        unit_test(cmd_account_set_invalid_presence_string_priority_shows_message),
+        unit_test(cmd_account_set_last_priority_shows_message),
+        unit_test(cmd_account_set_online_priority_sets_preference),
+        unit_test(cmd_account_set_chat_priority_sets_preference),
+        unit_test(cmd_account_set_away_priority_sets_preference),
+        unit_test(cmd_account_set_xa_priority_sets_preference),
+        unit_test(cmd_account_set_dnd_priority_sets_preference),
+        unit_test(cmd_account_set_priority_too_low_shows_message),
+        unit_test(cmd_account_set_priority_too_high_shows_message),
+        unit_test(cmd_account_set_priority_when_not_number_shows_message),
+        unit_test(cmd_account_set_priority_when_empty_shows_message),
+        unit_test(cmd_account_set_priority_updates_presence_when_account_connected_with_presence),
+        unit_test(cmd_account_clear_shows_usage_when_no_args),
+        unit_test(cmd_account_clear_shows_usage_when_one_arg),
+        unit_test(cmd_account_clear_shows_message_when_account_doesnt_exist),
+        unit_test(cmd_account_clear_shows_message_when_invalid_property),
+
+        unit_test(cmd_sub_shows_message_when_not_connected),
+        unit_test(cmd_sub_shows_usage_when_no_arg),
+
+        unit_test(contact_in_group),
+        unit_test(contact_not_in_group),
+        unit_test(contact_name_when_name_exists),
+        unit_test(contact_jid_when_name_not_exists),
+        unit_test(contact_string_when_name_exists),
+        unit_test(contact_string_when_name_not_exists),
+        unit_test(contact_string_when_default_resource),
+        unit_test(contact_presence_offline),
+        unit_test(contact_presence_uses_highest_priority),
+        unit_test(contact_presence_chat_when_same_prioroty),
+        unit_test(contact_presence_online_when_same_prioroty),
+        unit_test(contact_presence_away_when_same_prioroty),
+        unit_test(contact_presence_xa_when_same_prioroty),
+        unit_test(contact_presence_dnd),
+        unit_test(contact_subscribed_when_to),
+        unit_test(contact_subscribed_when_both),
+        unit_test(contact_not_subscribed_when_from),
+        unit_test(contact_not_subscribed_when_no_subscription_value),
+        unit_test(contact_not_available),
+        unit_test(contact_not_available_when_highest_priority_away),
+        unit_test(contact_not_available_when_highest_priority_xa),
+        unit_test(contact_not_available_when_highest_priority_dnd),
+        unit_test(contact_available_when_highest_priority_online),
+        unit_test(contact_available_when_highest_priority_chat),
+
+        unit_test(cmd_statuses_shows_usage_when_bad_subcmd),
+        unit_test(cmd_statuses_shows_usage_when_bad_console_setting),
+        unit_test(cmd_statuses_shows_usage_when_bad_chat_setting),
+        unit_test(cmd_statuses_shows_usage_when_bad_muc_setting),
+        unit_test_setup_teardown(cmd_statuses_console_sets_all,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_statuses_console_sets_online,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_statuses_console_sets_none,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_statuses_chat_sets_all,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_statuses_chat_sets_online,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_statuses_chat_sets_none,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_statuses_muc_sets_all,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_statuses_muc_sets_online,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_statuses_muc_sets_none,
+            load_preferences,
+            close_preferences),
+
+        unit_test_setup_teardown(statuses_console_defaults_to_all,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(statuses_chat_defaults_to_all,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(statuses_muc_defaults_to_all,
+            load_preferences,
+            close_preferences),
+
+        unit_test_setup_teardown(console_shows_online_presence_when_set_online,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(console_shows_online_presence_when_set_all,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(console_shows_dnd_presence_when_set_all,
+            load_preferences,
+            close_preferences),
+        unit_test(handle_offline_removes_chat_session),
+        unit_test(lost_connection_clears_chat_sessions),
+
+        unit_test(cmd_alias_add_shows_usage_when_no_args),
+        unit_test(cmd_alias_add_shows_usage_when_no_value),
+        unit_test(cmd_alias_remove_shows_usage_when_no_args),
+        unit_test(cmd_alias_show_usage_when_invalid_subcmd),
+        unit_test_setup_teardown(cmd_alias_add_adds_alias,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_alias_add_shows_message_when_exists,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_alias_remove_removes_alias,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_alias_remove_shows_message_when_no_alias,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_alias_list_shows_all_aliases,
+            load_preferences,
+            close_preferences),
+
+        unit_test_setup_teardown(test_muc_invites_add, muc_before_test, muc_after_test),
+        unit_test_setup_teardown(test_muc_remove_invite, muc_before_test, muc_after_test),
+        unit_test_setup_teardown(test_muc_invites_count_0, muc_before_test, muc_after_test),
+        unit_test_setup_teardown(test_muc_invites_count_5, muc_before_test, muc_after_test),
+        unit_test_setup_teardown(test_muc_room_is_not_active, muc_before_test, muc_after_test),
+        unit_test_setup_teardown(test_muc_active, muc_before_test, muc_after_test),
+
+        unit_test(cmd_bookmark_shows_message_when_disconnected),
+        unit_test(cmd_bookmark_shows_message_when_disconnecting),
+        unit_test(cmd_bookmark_shows_message_when_connecting),
+        unit_test(cmd_bookmark_shows_message_when_started),
+        unit_test(cmd_bookmark_shows_message_when_undefined),
+        unit_test(cmd_bookmark_shows_usage_when_no_args),
+        unit_test(cmd_bookmark_list_shows_bookmarks),
+        unit_test(cmd_bookmark_add_shows_message_when_invalid_jid),
+        unit_test(cmd_bookmark_add_adds_bookmark_with_jid),
+        unit_test(cmd_bookmark_add_adds_bookmark_with_jid_nick),
+        unit_test(cmd_bookmark_add_adds_bookmark_with_jid_autojoin),
+        unit_test(cmd_bookmark_add_adds_bookmark_with_jid_nick_autojoin),
+        unit_test(cmd_bookmark_remove_removes_bookmark),
+        unit_test(cmd_bookmark_remove_shows_message_when_no_bookmark),
+
+#ifdef HAVE_LIBOTR
+        unit_test(cmd_otr_shows_usage_when_no_args),
+        unit_test(cmd_otr_shows_usage_when_invalid_subcommand),
+        unit_test(cmd_otr_log_shows_usage_when_no_args),
+        unit_test(cmd_otr_log_shows_usage_when_invalid_subcommand),
+        unit_test_setup_teardown(cmd_otr_log_on_enables_logging,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_otr_log_off_disables_logging,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_otr_redact_redacts_logging,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_otr_log_on_shows_warning_when_chlog_disabled,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_otr_log_redact_shows_warning_when_chlog_disabled,
+            load_preferences,
+            close_preferences),
+        unit_test(cmd_otr_warn_shows_usage_when_no_args),
+        unit_test(cmd_otr_warn_shows_usage_when_invalid_arg),
+        unit_test_setup_teardown(cmd_otr_warn_on_enables_unencrypted_warning,
+            load_preferences,
+            close_preferences),
+        unit_test_setup_teardown(cmd_otr_warn_off_disables_unencrypted_warning,
+            load_preferences,
+            close_preferences),
+        unit_test(cmd_otr_libver_shows_libotr_version),
+        unit_test(cmd_otr_gen_shows_message_when_not_connected),
+        unit_test(cmd_otr_gen_generates_key_for_connected_account),
+        unit_test(cmd_otr_gen_shows_message_when_disconnected),
+        unit_test(cmd_otr_gen_shows_message_when_undefined),
+        unit_test(cmd_otr_gen_shows_message_when_started),
+        unit_test(cmd_otr_gen_shows_message_when_connecting),
+        unit_test(cmd_otr_gen_shows_message_when_disconnecting),
+        unit_test(cmd_otr_myfp_shows_message_when_disconnected),
+        unit_test(cmd_otr_myfp_shows_message_when_undefined),
+        unit_test(cmd_otr_myfp_shows_message_when_started),
+        unit_test(cmd_otr_myfp_shows_message_when_connecting),
+        unit_test(cmd_otr_myfp_shows_message_when_disconnecting),
+        unit_test(cmd_otr_myfp_shows_message_when_no_key),
+        unit_test(cmd_otr_myfp_shows_my_fingerprint),
+        unit_test(cmd_otr_theirfp_shows_message_when_in_console),
+        unit_test(cmd_otr_theirfp_shows_message_when_in_muc),
+        unit_test(cmd_otr_theirfp_shows_message_when_in_private),
+        unit_test(cmd_otr_theirfp_shows_message_when_non_otr_chat_window),
+        unit_test(cmd_otr_theirfp_shows_fingerprint),
+        unit_test(cmd_otr_start_shows_message_when_in_console),
+        unit_test(cmd_otr_start_shows_message_when_in_muc),
+        unit_test(cmd_otr_start_shows_message_when_in_private),
+        unit_test(cmd_otr_start_shows_message_when_already_started),
+        unit_test(cmd_otr_start_shows_message_when_no_key),
+        unit_test_setup_teardown(cmd_otr_start_sends_otr_query_message_to_current_recipeint,
+            load_preferences,
+            close_preferences),
+#else
+        unit_test(cmd_otr_shows_message_when_otr_unsupported),
+#endif
+
+        unit_test(cmd_join_shows_message_when_disconnecting),
+        unit_test(cmd_join_shows_message_when_connecting),
+        unit_test(cmd_join_shows_message_when_disconnected),
+        unit_test(cmd_join_shows_message_when_undefined),
+        unit_test(cmd_join_shows_error_message_when_invalid_room_jid),
+        unit_test(cmd_join_uses_account_mucservice_when_no_service_specified),
+        unit_test(cmd_join_uses_supplied_nick),
+        unit_test(cmd_join_uses_account_nick_when_not_supplied),
+        unit_test(cmd_join_uses_password_when_supplied),
+
+        unit_test(cmd_roster_shows_message_when_disconnecting),
+        unit_test(cmd_roster_shows_message_when_connecting),
+        unit_test(cmd_roster_shows_message_when_disconnected),
+        unit_test(cmd_roster_shows_message_when_undefined),
+        unit_test(cmd_roster_shows_roster_when_no_args),
+        unit_test(cmd_roster_add_shows_message_when_no_jid),
+        unit_test(cmd_roster_add_sends_roster_add_request),
+        unit_test(cmd_roster_remove_shows_message_when_no_jid),
+        unit_test(cmd_roster_remove_sends_roster_remove_request),
+        unit_test(cmd_roster_nick_shows_message_when_no_jid),
+        unit_test(cmd_roster_nick_shows_message_when_no_nick),
+        unit_test(cmd_roster_nick_shows_message_when_no_contact_exists),
+        unit_test(cmd_roster_nick_sends_name_change_request),
+        unit_test(cmd_roster_clearnick_shows_message_when_no_jid),
+        unit_test(cmd_roster_clearnick_shows_message_when_no_contact_exists),
+        unit_test(cmd_roster_clearnick_sends_name_change_request_with_empty_nick),
+
+        unit_test(get_form_type_field_returns_null_no_fields),
+        unit_test(get_form_type_field_returns_null_when_not_present),
+        unit_test(get_form_type_field_returns_value_when_present),
+        unit_test(get_field_type_returns_unknown_when_no_fields),
+        unit_test(get_field_type_returns_correct_type),
+        unit_test(set_value_adds_when_none),
+        unit_test(set_value_updates_when_one),
+        unit_test(add_unique_value_adds_when_none),
+        unit_test(add_unique_value_does_nothing_when_exists),
+        unit_test(add_unique_value_adds_when_doesnt_exist),
+        unit_test(add_value_adds_when_none),
+        unit_test(add_value_adds_when_some),
+        unit_test(add_value_adds_when_exists),
+        unit_test(remove_value_does_nothing_when_none),
+        unit_test(remove_value_does_nothing_when_doesnt_exist),
+        unit_test(remove_value_removes_when_one),
+        unit_test(remove_value_removes_when_many),
+        unit_test(remove_text_multi_value_does_nothing_when_none),
+        unit_test(remove_text_multi_value_does_nothing_when_doesnt_exist),
+        unit_test(remove_text_multi_value_removes_when_one),
+        unit_test(remove_text_multi_value_removes_when_many),
+
+        unit_test(clears_chat_sessions),
+    };
+
+    return run_tests(all_tests);
+}
diff --git a/unittests/xmpp/stub_xmpp.c b/unittests/xmpp/stub_xmpp.c
new file mode 100644
index 00000000..d3be1af6
--- /dev/null
+++ b/unittests/xmpp/stub_xmpp.c
@@ -0,0 +1,228 @@
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "xmpp/xmpp.h"
+
+// connection functions
+void jabber_init(const int disable_tls) {}
+
+jabber_conn_status_t jabber_connect_with_details(const char * const jid,
+    const char * const passwd, const char * const altdomain, const int port)
+{
+    check_expected(jid);
+    check_expected(passwd);
+    check_expected(altdomain);
+    check_expected(port);
+    return (jabber_conn_status_t)mock();
+}
+
+jabber_conn_status_t jabber_connect_with_account(const ProfAccount * const account)
+{
+    check_expected(account);
+    return (jabber_conn_status_t)mock();
+}
+
+void jabber_disconnect(void) {}
+void jabber_shutdown(void) {}
+void jabber_process_events(int millis) {}
+const char * jabber_get_fulljid(void)
+{
+    return (char *)mock();
+}
+
+const char * jabber_get_domain(void)
+{
+    return NULL;
+}
+
+jabber_conn_status_t jabber_get_connection_status(void)
+{
+    return (jabber_conn_status_t)mock();
+}
+
+char* jabber_get_presence_message(void)
+{
+    return (char*)mock();
+}
+
+char* jabber_get_account_name(void)
+{
+    return (char*)mock();
+}
+
+GList * jabber_get_available_resources(void)
+{
+    return NULL;
+}
+
+// message functions
+char* message_send_chat(const char * const barejid, const char * const msg)
+{
+    check_expected(barejid);
+    check_expected(msg);
+    return NULL;
+}
+
+char* message_send_chat_encrypted(const char * const barejid, const char * const msg)
+{
+    check_expected(barejid);
+    check_expected(msg);
+    return NULL;
+}
+
+void message_send_private(const char * const fulljid, const char * const msg) {}
+void message_send_groupchat(const char * const roomjid, const char * const msg) {}
+void message_send_groupchat_subject(const char * const roomjid, const char * const subject) {}
+
+void message_send_inactive(const char * const barejid) {}
+void message_send_composing(const char * const barejid) {}
+void message_send_paused(const char * const barejid) {}
+void message_send_gone(const char * const barejid) {}
+
+void message_send_invite(const char * const room, const char * const contact,
+    const char * const reason) {}
+
+// presence functions
+void presence_subscription(const char * const jid, const jabber_subscr_t action) {}
+
+GSList* presence_get_subscription_requests(void)
+{
+    return NULL;
+}
+
+gint presence_sub_request_count(void)
+{
+    return 0;
+}
+
+void presence_reset_sub_request_search(void) {}
+
+char * presence_sub_request_find(const char * const search_str)
+{
+    return  NULL;
+}
+
+void presence_join_room(char *room, char *nick, char * passwd)
+{
+    check_expected(room);
+    check_expected(nick);
+    check_expected(passwd);
+}
+
+void presence_change_room_nick(const char * const room, const char * const nick) {}
+void presence_leave_chat_room(const char * const room_jid) {}
+
+void presence_send(resource_presence_t status, const char * const msg, int idle)
+{
+    check_expected(status);
+    check_expected(msg);
+    check_expected(idle);
+}
+
+gboolean presence_sub_request_exists(const char * const bare_jid)
+{
+    return FALSE;
+}
+
+// iq functions
+void iq_disable_carbons() {};
+void iq_enable_carbons() {};
+void iq_send_software_version(const char * const fulljid) {}
+
+void iq_room_list_request(gchar *conferencejid)
+{
+    check_expected(conferencejid);
+}
+
+void iq_disco_info_request(gchar *jid) {}
+void iq_disco_items_request(gchar *jid) {}
+void iq_set_autoping(int seconds) {}
+void iq_confirm_instant_room(const char * const room_jid) {}
+void iq_destroy_room(const char * const room_jid) {}
+void iq_request_room_config_form(const char * const room_jid) {}
+void iq_submit_room_config(const char * const room, DataForm *form) {}
+void iq_room_config_cancel(const char * const room_jid) {}
+void iq_send_ping(const char * const target) {}
+void iq_send_caps_request(const char * const to, const char * const id,
+    const char * const node, const char * const ver) {}
+void iq_send_caps_request_for_jid(const char * const to, const char * const id,
+    const char * const node, const char * const ver) {}
+void iq_send_caps_request_legacy(const char * const to, const char * const id,
+    const char * const node, const char * const ver) {}
+void iq_room_info_request(const char * const room, gboolean display) {}
+void iq_room_affiliation_list(const char * const room, char *affiliation) {}
+void iq_room_affiliation_set(const char * const room, const char * const jid, char *affiliation,
+    const char * const reason) {}
+void iq_room_kick_occupant(const char * const room, const char * const nick, const char * const reason) {}
+void iq_room_role_set(const char * const room, const char * const nick, char *role,
+    const char * const reason) {}
+void iq_room_role_list(const char * const room, char *role) {}
+
+// caps functions
+Capabilities* caps_lookup(const char * const jid)
+{
+    return NULL;
+}
+
+void caps_close(void) {}
+void caps_destroy(Capabilities *caps) {}
+
+gboolean bookmark_add(const char *jid, const char *nick, const char *password, const char *autojoin_str)
+{
+    check_expected(jid);
+    check_expected(nick);
+    check_expected(password);
+    check_expected(autojoin_str);
+    return (gboolean)mock();
+}
+
+gboolean bookmark_update(const char *jid, const char *nick, const char *password, const char *autojoin_str)
+{
+    return FALSE;
+}
+
+gboolean bookmark_remove(const char *jid)
+{
+    check_expected(jid);
+    return (gboolean)mock();
+}
+
+gboolean bookmark_join(const char *jid)
+{
+    return FALSE;
+}
+
+const GList * bookmark_get_list(void)
+{
+    return (GList *)mock();
+}
+
+char * bookmark_find(const char * const search_str)
+{
+    return NULL;
+}
+
+void bookmark_autocomplete_reset(void) {}
+
+void roster_send_name_change(const char * const barejid, const char * const new_name, GSList *groups)
+{
+    check_expected(barejid);
+    check_expected(new_name);
+    check_expected(groups);
+}
+
+void roster_send_add_to_group(const char * const group, PContact contact) {}
+void roster_send_remove_from_group(const char * const group, PContact contact) {}
+
+void roster_send_add_new(const char * const barejid, const char * const name)
+{
+    check_expected(barejid);
+    check_expected(name);
+}
+
+void roster_send_remove(const char * const barejid)
+{
+    check_expected(barejid);
+}