changeset 557:c729f06c6f27

Tests: convert cmd-rule-*, #593
author David Demelier <markand@malikania.fr>
date Sat, 25 Nov 2017 14:34:20 +0100
parents c9b703f923d0
children f1ad428208d3
files libcommon/irccd/json_util.hpp libirccd-test/irccd/command_test.hpp tests/CMakeLists.txt tests/cmd-plugin-list/main.cpp tests/cmd-rule-add/main.cpp tests/cmd-rule-edit/main.cpp tests/cmd-rule-info/main.cpp tests/cmd-rule-list/main.cpp tests/cmd-rule-move/main.cpp tests/cmd-rule-remove/main.cpp
diffstat 10 files changed, 1104 insertions(+), 1103 deletions(-) [+]
line wrap: on
line diff
--- a/libcommon/irccd/json_util.hpp	Fri Nov 24 21:20:17 2017 +0100
+++ b/libcommon/irccd/json_util.hpp	Sat Nov 25 14:34:20 2017 +0100
@@ -262,6 +262,22 @@
     return pretty(*it);
 }
 
+/**
+ * Check if the array contains the given value.
+ *
+ * \param array the array
+ * \param value the JSON value to check
+ * \return true if present
+ */
+inline bool contains(const nlohmann::json& array, const nlohmann::json& value)
+{
+    for (const auto &v : array)
+        if (v == value)
+            return true;
+
+    return false;
+}
+
 } // !json_util
 
 } // !irccd
--- a/libirccd-test/irccd/command_test.hpp	Fri Nov 24 21:20:17 2017 +0100
+++ b/libirccd-test/irccd/command_test.hpp	Sat Nov 25 14:34:20 2017 +0100
@@ -48,6 +48,8 @@
     template <typename Condition>
     void wait_for(Condition&& cond)
     {
+        service_.reset();
+
         while (!cond())
             service_.poll();
     }
--- a/tests/CMakeLists.txt	Fri Nov 24 21:20:17 2017 +0100
+++ b/tests/CMakeLists.txt	Sat Nov 25 14:34:20 2017 +0100
@@ -26,12 +26,12 @@
     add_subdirectory(cmd-plugin-load)
     add_subdirectory(cmd-plugin-reload)
     add_subdirectory(cmd-plugin-unload)
-#    add_subdirectory(cmd-rule-add)
-#    add_subdirectory(cmd-rule-edit)
-#    add_subdirectory(cmd-rule-info)
-#    add_subdirectory(cmd-rule-list)
-#    add_subdirectory(cmd-rule-move)
-#    add_subdirectory(cmd-rule-remove)
+    add_subdirectory(cmd-rule-add)
+    add_subdirectory(cmd-rule-edit)
+    add_subdirectory(cmd-rule-info)
+    add_subdirectory(cmd-rule-list)
+    add_subdirectory(cmd-rule-move)
+    add_subdirectory(cmd-rule-remove)
 #    add_subdirectory(cmd-server-cmode)
 #    add_subdirectory(cmd-server-cnotice)
 #    add_subdirectory(cmd-server-connect)
--- a/tests/cmd-plugin-list/main.cpp	Fri Nov 24 21:20:17 2017 +0100
+++ b/tests/cmd-plugin-list/main.cpp	Sat Nov 25 14:34:20 2017 +0100
@@ -42,7 +42,7 @@
     auto response = nlohmann::json();
 
     ctl_->send({{"command", "plugin-list"}});
-    ctl_->recv([&] (auto code, auto message) {
+    ctl_->recv([&] (auto, auto message) {
         response = message;
     });
 
--- a/tests/cmd-rule-add/main.cpp	Fri Nov 24 21:20:17 2017 +0100
+++ b/tests/cmd-rule-add/main.cpp	Sat Nov 25 14:34:20 2017 +0100
@@ -16,43 +16,37 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <command.hpp>
-#include <command-tester.hpp>
-#include <service.hpp>
+#define BOOST_TEST_MODULE "rule-add"
+#include <boost/test/unit_test.hpp>
 
-using namespace irccd;
+#include <irccd/json_util.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
 
-class RuleAddCommandTest : public CommandTester {
-protected:
-    nlohmann::json m_result;
+#include <command_test.hpp>
+
+namespace irccd {
 
-    /*
-     * Rule sets are unordered so use this function to search a string in
-     * the JSON array.
-     */
-    inline bool contains(const nlohmann::json &array, const std::string &str)
+namespace {
+
+class rule_add_test : public command_test<rule_add_command> {
+public:
+    rule_add_test()
     {
-        for (const auto &v : array)
-            if (v.is_string() && v == str)
-                return true;
-
-        return false;
-    }
-
-public:
-    RuleAddCommandTest()
-        : CommandTester(std::make_unique<rule_add_command>())
-    {
-        m_irccd.commands().add(std::make_unique<rule_list_command>());
-        m_irccdctl.client().onMessage.connect([&] (auto result) {
-            m_result = result;
-        });
+        daemon_->commands().add(std::make_unique<rule_list_command>());
     }
 };
 
-TEST_F(RuleAddCommandTest, basic)
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_add_test_suite, rule_add_test)
+
+BOOST_AUTO_TEST_CASE(basic)
 {
-    m_irccdctl.client().request({
+    nlohmann::json result;
+
+    ctl_->send({
         { "command",    "rule-add"          },
         { "servers",    { "s1", "s2" }      },
         { "channels",   { "c1", "c2" }      },
@@ -61,127 +55,128 @@
         { "action",     "accept"            },
         { "index",      0                   }
     });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-    try {
-        poll([&] () {
-            return m_result.is_object();
-        });
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    result = nullptr;
+    ctl_->send({{"command", "rule-list"}});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    auto servers = result["list"][0]["servers"];
+    auto channels = result["list"][0]["channels"];
+    auto plugins = result["list"][0]["plugins"];
+    auto events = result["list"][0]["events"];
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    BOOST_TEST(json_util::contains(servers, "s1"));
+    BOOST_TEST(json_util::contains(servers, "s2"));
+    BOOST_TEST(json_util::contains(channels, "c1"));
+    BOOST_TEST(json_util::contains(channels, "c2"));
+    BOOST_TEST(json_util::contains(plugins, "p1"));
+    BOOST_TEST(json_util::contains(plugins, "p2"));
+    BOOST_TEST(json_util::contains(events, "onMessage"));
+    BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+}
+
+BOOST_AUTO_TEST_CASE(append)
+{
+    nlohmann::json result;
 
-        m_result = nullptr;
-        m_irccdctl.client().request({{ "command", "rule-list" }});
+    ctl_->send({
+        { "command",    "rule-add"          },
+        { "servers",    { "s1" }            },
+        { "channels",   { "c1" }            },
+        { "plugins",    { "p1" }            },
+        { "events",     { "onMessage" }     },
+        { "action",     "accept"            },
+        { "index",      0                   }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    result = nullptr;
+    ctl_->send({
+        { "command",    "rule-add"          },
+        { "servers",    { "s2" }            },
+        { "channels",   { "c2" }            },
+        { "plugins",    { "p2" }            },
+        { "events",     { "onMessage" }     },
+        { "action",     "drop"              },
+        { "index",      1                   }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        auto servers = m_result["list"][0]["servers"];
-        auto channels = m_result["list"][0]["channels"];
-        auto plugins = m_result["list"][0]["plugins"];
-        auto events = m_result["list"][0]["events"];
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    result = nullptr;
+    ctl_->send({{"command", "rule-list"}});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        ASSERT_TRUE(contains(servers, "s1"));
-        ASSERT_TRUE(contains(servers, "s2"));
-        ASSERT_TRUE(contains(channels, "c1"));
-        ASSERT_TRUE(contains(channels, "c2"));
-        ASSERT_TRUE(contains(plugins, "p1"));
-        ASSERT_TRUE(contains(plugins, "p2"));
-        ASSERT_TRUE(contains(events, "onMessage"));
-        ASSERT_EQ("accept", m_result["list"][0]["action"]);
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(result["list"].size() == 2U);
+
+    // Rule 0.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+    }
+
+    // Rule 1.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "drop");
     }
 }
 
-TEST_F(RuleAddCommandTest, append)
-{
-    try {
-        m_irccdctl.client().request({
-            { "command",    "rule-add"          },
-            { "servers",    { "s1" }            },
-            { "channels",   { "c1" }            },
-            { "plugins",    { "p1" }            },
-            { "events",     { "onMessage" }     },
-            { "action",     "accept"            },
-            { "index",      0                   }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
-
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command",    "rule-add"          },
-            { "servers",    { "s2" }            },
-            { "channels",   { "c2" }            },
-            { "plugins",    { "p2" }            },
-            { "events",     { "onMessage" }     },
-            { "action",     "drop"              },
-            { "index",      1                   }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
-
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-
-        m_result = nullptr;
-        m_irccdctl.client().request({{ "command", "rule-list" }});
+BOOST_AUTO_TEST_SUITE_END()
 
-        poll([&] () {
-            return m_result.is_object();
-        });
-
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_EQ(2U, m_result["list"].size());
-
-        // Rule 0.
-        {
-            auto servers = m_result["list"][0]["servers"];
-            auto channels = m_result["list"][0]["channels"];
-            auto plugins = m_result["list"][0]["plugins"];
-            auto events = m_result["list"][0]["events"];
-
-            ASSERT_TRUE(contains(servers, "s1"));
-            ASSERT_TRUE(contains(channels, "c1"));
-            ASSERT_TRUE(contains(plugins, "p1"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][0]["action"]);
-        }
-
-        // Rule 1.
-        {
-            auto servers = m_result["list"][1]["servers"];
-            auto channels = m_result["list"][1]["channels"];
-            auto plugins = m_result["list"][1]["plugins"];
-            auto events = m_result["list"][1]["events"];
-
-            ASSERT_TRUE(contains(servers, "s2"));
-            ASSERT_TRUE(contains(channels, "c2"));
-            ASSERT_TRUE(contains(plugins, "p2"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("drop", m_result["list"][1]["action"]);
-        }
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
-}
-
-int main(int argc, char **argv)
-{
-    testing::InitGoogleTest(&argc, argv);
-
-    return RUN_ALL_TESTS();
-}
+} // !irccd
--- a/tests/cmd-rule-edit/main.cpp	Fri Nov 24 21:20:17 2017 +0100
+++ b/tests/cmd-rule-edit/main.cpp	Sat Nov 25 14:34:20 2017 +0100
@@ -1,5 +1,5 @@
 /*
- * main.cpp -- test rule-info remote command
+ * main.cpp -- test rule-edit remote command
  *
  * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
  *
@@ -16,35 +16,26 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <command.hpp>
-#include <command-tester.hpp>
-#include <service.hpp>
+#define BOOST_TEST_MODULE "rule-edit"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
 
-using namespace irccd;
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
 
-class RuleEditCommandTest : public CommandTester {
-protected:
-    nlohmann::json m_result;
+#include <command_test.hpp>
 
-    /*
-     * Rule sets are unordered so use this function to search a string in
-     * the JSON array.
-     */
-    inline bool contains(const nlohmann::json &array, const std::string &str)
-    {
-        for (const auto &v : array)
-            if (v.is_string() && v == str)
-                return true;
+namespace irccd {
+
+namespace {
 
-        return false;
-    }
-
+class rule_edit_test : public command_test<rule_edit_command> {
 public:
-    RuleEditCommandTest()
-        : CommandTester(std::make_unique<rule_edit_command>())
+    rule_edit_test()
     {
-        m_irccd.commands().add(std::make_unique<rule_info_command>());
-        m_irccd.rules().add(rule(
+        daemon_->commands().add(std::make_unique<rule_info_command>());
+        daemon_->rules().add(rule(
             { "s1", "s2" },
             { "c1", "c2" },
             { "o1", "o2" },
@@ -52,485 +43,505 @@
             { "onMessage", "onCommand" },
             rule::action_type::drop
         ));
-        m_irccdctl.client().onMessage.connect([&] (auto result) {
-            m_result = result;
-        });
     }
 };
 
-TEST_F(RuleEditCommandTest, addServer)
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_edit_test_suite, rule_edit_test)
+
+BOOST_AUTO_TEST_CASE(add_server)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "add-servers",    { "new-s3" }    },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "add-servers",    { "new-s3" }    },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_TRUE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["servers"], "new-s3"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["servers"], "new-s3"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleEditCommandTest, addChannel)
+BOOST_AUTO_TEST_CASE(add_channel)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "add-channels",   { "new-c3" }    },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "add-channels",   { "new-c3" }    },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_TRUE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["channels"], "new-c3"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["channels"], "new-c3"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleEditCommandTest, addPlugin)
+BOOST_AUTO_TEST_CASE(add_plugin)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "add-plugins",    { "new-p3" }    },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "add-plugins",    { "new-p3" }    },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_TRUE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "new-p3"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "new-p3"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleEditCommandTest, addEvent)
+BOOST_AUTO_TEST_CASE(add_event)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "add-events",     { "onQuery" }   },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "add-events",     { "onQuery" }   },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_TRUE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
-        ASSERT_TRUE(contains(m_result["events"], "onQuery"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(json_util::contains(result["events"], "onQuery"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleEditCommandTest, addEventAndServer)
+BOOST_AUTO_TEST_CASE(add_event_and_server)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "add-servers",    { "new-s3" }    },
-            { "add-events",     { "onQuery" }   },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "add-servers",    { "new-s3" }    },
+        { "add-events",     { "onQuery" }   },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_TRUE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["servers"], "new-s3"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
-        ASSERT_TRUE(contains(m_result["events"], "onQuery"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["servers"], "new-s3"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(json_util::contains(result["events"], "onQuery"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleEditCommandTest, changeAction)
+BOOST_AUTO_TEST_CASE(change_action)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "action",         "accept"        },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "action",         "accept"        },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_TRUE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "accept");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "accept");
 }
 
-TEST_F(RuleEditCommandTest, removeServer)
+BOOST_AUTO_TEST_CASE(remove_server)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "remove-servers", { "s2" }        },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "remove-servers", { "s2" }        },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_FALSE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(!json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleEditCommandTest, removeChannel)
+BOOST_AUTO_TEST_CASE(remove_channel)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "remove-channels", { "c2" }       },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "remove-channels", { "c2" }       },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_TRUE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_FALSE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(!json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleEditCommandTest, removePlugin)
+BOOST_AUTO_TEST_CASE(remove_plugin)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "remove-plugins", { "p2" }        },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "remove-plugins", { "p2" }        },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_TRUE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_FALSE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_TRUE(contains(m_result["events"], "onCommand"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(!json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleEditCommandTest, removeEvent)
+BOOST_AUTO_TEST_CASE(remove_event)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "remove-events",  { "onCommand" } },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "remove-events",  { "onCommand" } },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_TRUE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_FALSE(contains(m_result["events"], "onCommand"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(!json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleEditCommandTest, removeEventAndServer)
+BOOST_AUTO_TEST_CASE(remove_event_and_server)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",        "rule-edit"     },
-            { "remove-servers", { "s2" }        },
-            { "remove-events",  { "onCommand" } },
-            { "index",          0               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",        "rule-edit"     },
+        { "remove-servers", { "s2" }        },
+        { "remove-events",  { "onCommand" } },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-        m_result = nullptr;
-        m_irccdctl.client().request({
-            { "command", "rule-info" },
-            { "index", 0 }
-        });
-
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({
+        { "command",        "rule-info"     },
+        { "index",          0               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
-        ASSERT_TRUE(contains(m_result["servers"], "s1"));
-        ASSERT_FALSE(contains(m_result["servers"], "s2"));
-        ASSERT_TRUE(contains(m_result["channels"], "c1"));
-        ASSERT_TRUE(contains(m_result["channels"], "c2"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p1"));
-        ASSERT_TRUE(contains(m_result["plugins"], "p2"));
-        ASSERT_TRUE(contains(m_result["events"], "onMessage"));
-        ASSERT_FALSE(contains(m_result["events"], "onCommand"));
-        ASSERT_EQ(m_result["action"].get<std::string>(), "drop");
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(json_util::contains(result["servers"], "s1"));
+    BOOST_TEST(!json_util::contains(result["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result["events"], "onMessage"));
+    BOOST_TEST(!json_util::contains(result["events"], "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-int main(int argc, char **argv)
-{
-    testing::InitGoogleTest(&argc, argv);
+BOOST_AUTO_TEST_SUITE_END()
 
-    return RUN_ALL_TESTS();
-}
+} // !irccd
--- a/tests/cmd-rule-info/main.cpp	Fri Nov 24 21:20:17 2017 +0100
+++ b/tests/cmd-rule-info/main.cpp	Sat Nov 25 14:34:20 2017 +0100
@@ -16,34 +16,25 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <command.hpp>
-#include <command-tester.hpp>
-#include <service.hpp>
+#define BOOST_TEST_MODULE "rule-info"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
 
-using namespace irccd;
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
 
-class RuleInfoCommandTest : public CommandTester {
-protected:
-    nlohmann::json m_result;
+#include <command_test.hpp>
 
-    /*
-     * Rule sets are unordered so use this function to search a string in
-     * the JSON array.
-     */
-    inline bool contains(const nlohmann::json &array, const std::string &str)
+namespace irccd {
+
+namespace {
+
+class rule_info_test : public command_test<rule_info_command> {
+public:
+    rule_info_test()
     {
-        for (const auto &v : array)
-            if (v.is_string() && v == str)
-                return true;
-
-        return false;
-    }
-
-public:
-    RuleInfoCommandTest()
-        : CommandTester(std::make_unique<rule_info_command>())
-    {
-        m_irccd.rules().add(rule(
+        daemon_->rules().add(rule(
             { "s1", "s2" },
             { "c1", "c2" },
             { "o1", "o2" },
@@ -51,7 +42,7 @@
             { "onMessage", "onCommand" },
             rule::action_type::drop
         ));
-        m_irccd.rules().add(rule(
+        daemon_->rules().add(rule(
             { "s1", },
             { "c1", },
             { "o1", },
@@ -59,67 +50,68 @@
             { "onMessage", },
             rule::action_type::accept
         ));
-        m_irccdctl.client().onMessage.connect([&] (auto result) {
-            m_result = result;
-        });
     }
 };
 
-TEST_F(RuleInfoCommandTest, basic)
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_info_test_suite, rule_info_test)
+
+BOOST_AUTO_TEST_CASE(basic)
 {
-    m_irccdctl.client().request({
+    nlohmann::json result;
+
+    ctl_->send({
         { "command",    "rule-info" },
         { "index",      0           }
     });
-
-    try {
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        auto servers = m_result["servers"];
-        auto channels = m_result["channels"];
-        auto plugins = m_result["plugins"];
-        auto events = m_result["events"];
+    BOOST_TEST(result.is_object());
 
-        ASSERT_TRUE(contains(servers, "s1"));
-        ASSERT_TRUE(contains(servers, "s2"));
-        ASSERT_TRUE(contains(channels, "c1"));
-        ASSERT_TRUE(contains(channels, "c2"));
-        ASSERT_TRUE(contains(plugins, "p1"));
-        ASSERT_TRUE(contains(plugins, "p2"));
-        ASSERT_TRUE(contains(events, "onMessage"));
-        ASSERT_TRUE(contains(events, "onCommand"));
-        ASSERT_EQ("drop", m_result["action"]);
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    auto servers = result["servers"];
+    auto channels = result["channels"];
+    auto plugins = result["plugins"];
+    auto events = result["events"];
+
+    BOOST_TEST(json_util::contains(servers, "s1"));
+    BOOST_TEST(json_util::contains(servers, "s2"));
+    BOOST_TEST(json_util::contains(channels, "c1"));
+    BOOST_TEST(json_util::contains(channels, "c2"));
+    BOOST_TEST(json_util::contains(plugins, "p1"));
+    BOOST_TEST(json_util::contains(plugins, "p2"));
+    BOOST_TEST(json_util::contains(events, "onMessage"));
+    BOOST_TEST(json_util::contains(events, "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleInfoCommandTest, outOfBounds)
+BOOST_AUTO_TEST_CASE(out_of_bounds)
 {
-    m_irccdctl.client().request({
+    nlohmann::json result;
+
+    ctl_->send({
         { "command",    "rule-info" },
         { "index",      123         }
     });
-
-    try {
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_FALSE(m_result["status"]);
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    // TODO: error code
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(result.count("error"));
 }
 
-int main(int argc, char **argv)
-{
-    testing::InitGoogleTest(&argc, argv);
+BOOST_AUTO_TEST_SUITE_END()
 
-    return RUN_ALL_TESTS();
-}
+} // !irccd
--- a/tests/cmd-rule-list/main.cpp	Fri Nov 24 21:20:17 2017 +0100
+++ b/tests/cmd-rule-list/main.cpp	Sat Nov 25 14:34:20 2017 +0100
@@ -16,34 +16,25 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <command.hpp>
-#include <command-tester.hpp>
-#include <service.hpp>
+#define BOOST_TEST_MODULE "rule-list"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
 
-using namespace irccd;
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
 
-class RuleListCommandTest : public CommandTester {
-protected:
-    nlohmann::json m_result;
+#include <command_test.hpp>
 
-    /*
-     * Rule sets are unordered so use this function to search a string in
-     * the JSON array.
-     */
-    inline bool contains(const nlohmann::json &array, const std::string &str)
+namespace irccd {
+
+namespace {
+
+class rule_list_test : public command_test<rule_list_command> {
+public:
+    rule_list_test()
     {
-        for (const auto &v : array)
-            if (v.is_string() && v == str)
-                return true;
-
-        return false;
-    }
-
-public:
-    RuleListCommandTest()
-        : CommandTester(std::make_unique<rule_list_command>())
-    {
-        m_irccd.rules().add(rule(
+        daemon_->rules().add(rule(
             { "s1", "s2" },
             { "c1", "c2" },
             { "o1", "o2" },
@@ -51,7 +42,7 @@
             { "onMessage", "onCommand" },
             rule::action_type::drop
         ));
-        m_irccd.rules().add(rule(
+        daemon_->rules().add(rule(
             { "s1", },
             { "c1", },
             { "o1", },
@@ -59,81 +50,82 @@
             { "onMessage", },
             rule::action_type::accept
         ));
-        m_irccdctl.client().request({{ "command", "rule-list" }});
-        m_irccdctl.client().onMessage.connect([&] (auto result) {
-            m_result = result;
-        });
     }
 };
 
-TEST_F(RuleListCommandTest, basic)
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_list_test_suite, rule_list_test)
+
+BOOST_AUTO_TEST_CASE(basic)
 {
-    try {
-        poll([&] () {
-            return m_result.is_object();
-        });
+    nlohmann::json result;
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["list"].is_array());
-        ASSERT_EQ(2U, m_result["list"].size());
+    ctl_->send({{"command", "rule-list"}});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        // Rule 0.
-        {
-            auto servers = m_result["list"][0]["servers"];
-            auto channels = m_result["list"][0]["channels"];
-            auto plugins = m_result["list"][0]["plugins"];
-            auto events = m_result["list"][0]["events"];
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(result["list"].is_array());
+    BOOST_TEST(result["list"].size() == 2U);
 
-            ASSERT_TRUE(contains(servers, "s1"));
-            ASSERT_TRUE(contains(servers, "s2"));
-            ASSERT_TRUE(contains(channels, "c1"));
-            ASSERT_TRUE(contains(channels, "c2"));
-            ASSERT_TRUE(contains(plugins, "p1"));
-            ASSERT_TRUE(contains(plugins, "p2"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_TRUE(contains(events, "onCommand"));
-            ASSERT_EQ("drop", m_result["list"][0]["action"]);
-        }
+    // Rule 0.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
 
-        // Rule 1.
-        {
-            auto servers = m_result["list"][1]["servers"];
-            auto channels = m_result["list"][1]["channels"];
-            auto plugins = m_result["list"][1]["plugins"];
-            auto events = m_result["list"][1]["events"];
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(json_util::contains(events, "onCommand"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "drop");
+    }
 
-            ASSERT_TRUE(contains(servers, "s1"));
-            ASSERT_TRUE(contains(channels, "c1"));
-            ASSERT_TRUE(contains(plugins, "p1"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][1]["action"]);
-        }
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
+    // Rule 1.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
     }
 }
 
-TEST_F(RuleListCommandTest, empty)
+BOOST_AUTO_TEST_CASE(empty)
 {
-    m_irccd.rules().remove(0);
-    m_irccd.rules().remove(0);
+    nlohmann::json result;
+
+    daemon_->rules().remove(0);
+    daemon_->rules().remove(0);
 
-    try {
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({{"command", "rule-list"}});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["list"].is_array());
-        ASSERT_EQ(0U, m_result["list"].size());
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(result["list"].is_array());
+    BOOST_TEST(result["list"].empty());
 }
 
-int main(int argc, char **argv)
-{
-    testing::InitGoogleTest(&argc, argv);
+BOOST_AUTO_TEST_SUITE_END()
 
-    return RUN_ALL_TESTS();
-}
+} // !irccd
--- a/tests/cmd-rule-move/main.cpp	Fri Nov 24 21:20:17 2017 +0100
+++ b/tests/cmd-rule-move/main.cpp	Sat Nov 25 14:34:20 2017 +0100
@@ -16,35 +16,26 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <command.hpp>
-#include <command-tester.hpp>
-#include <service.hpp>
+#define BOOST_TEST_MODULE "rule-move"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
 
-using namespace irccd;
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
 
-class RuleMoveCommandTest : public CommandTester {
-protected:
-    nlohmann::json m_result;
+#include <command_test.hpp>
 
-    /*
-     * Rule sets are unordered so use this function to search a string in
-     * the JSON array.
-     */
-    inline bool contains(const nlohmann::json &array, const std::string &str)
-    {
-        for (const auto &v : array)
-            if (v.is_string() && v == str)
-                return true;
+namespace irccd {
+
+namespace {
 
-        return false;
-    }
-
+class rule_move_test : public command_test<rule_move_command> {
 public:
-    RuleMoveCommandTest()
-        : CommandTester(std::make_unique<rule_move_command>())
+    rule_move_test()
     {
-        m_irccd.commands().add(std::make_unique<rule_list_command>());
-        m_irccd.rules().add(rule(
+        daemon_->commands().add(std::make_unique<rule_list_command>());
+        daemon_->rules().add(rule(
             { "s0" },
             { "c0" },
             { "o0" },
@@ -52,7 +43,7 @@
             { "onMessage" },
             rule::action_type::drop
         ));
-        m_irccd.rules().add(rule(
+        daemon_->rules().add(rule(
             { "s1", },
             { "c1", },
             { "o1", },
@@ -60,7 +51,7 @@
             { "onMessage", },
             rule::action_type::accept
         ));
-        m_irccd.rules().add(rule(
+        daemon_->rules().add(rule(
             { "s2", },
             { "c2", },
             { "o2", },
@@ -68,323 +59,331 @@
             { "onMessage", },
             rule::action_type::accept
         ));
-        m_irccdctl.client().onMessage.connect([&] (auto result) {
-            m_result = result;
-        });
     }
 };
 
-TEST_F(RuleMoveCommandTest, backward)
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_move_test_suite, rule_move_test)
+
+BOOST_AUTO_TEST_CASE(backward)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",    "rule-move" },
-            { "from",       2           },
-            { "to",         0           }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
-
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       2           },
+        { "to",         0           }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        m_result = nullptr;
-        m_irccdctl.client().request({{ "command", "rule-list" }});
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    BOOST_TEST(result.is_object());
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        // Rule 2.
-        {
-            auto servers = m_result["list"][0]["servers"];
-            auto channels = m_result["list"][0]["channels"];
-            auto plugins = m_result["list"][0]["plugins"];
-            auto events = m_result["list"][0]["events"];
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-            ASSERT_TRUE(contains(servers, "s2"));
-            ASSERT_TRUE(contains(channels, "c2"));
-            ASSERT_TRUE(contains(plugins, "p2"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][0]["action"].get<std::string>());
-        }
+    // Rule 2.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
 
-        // Rule 0.
-        {
-            auto servers = m_result["list"][1]["servers"];
-            auto channels = m_result["list"][1]["channels"];
-            auto plugins = m_result["list"][1]["plugins"];
-            auto events = m_result["list"][1]["events"];
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+    }
 
-            ASSERT_TRUE(contains(servers, "s0"));
-            ASSERT_TRUE(contains(channels, "c0"));
-            ASSERT_TRUE(contains(plugins, "p0"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("drop", m_result["list"][1]["action"].get<std::string>());
-        }
+    // Rule 0.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
 
-        // Rule 1.
-        {
-            auto servers = m_result["list"][2]["servers"];
-            auto channels = m_result["list"][2]["channels"];
-            auto plugins = m_result["list"][2]["plugins"];
-            auto events = m_result["list"][2]["events"];
+        BOOST_TEST(json_util::contains(servers, "s0"));
+        BOOST_TEST(json_util::contains(channels, "c0"));
+        BOOST_TEST(json_util::contains(plugins, "p0"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "drop");
+    }
 
-            ASSERT_TRUE(contains(servers, "s1"));
-            ASSERT_TRUE(contains(channels, "c1"));
-            ASSERT_TRUE(contains(plugins, "p1"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][2]["action"].get<std::string>());
-        }
-    } catch (const std::exception& ex) {
-        FAIL() << ex.what();
+    // Rule 1.
+    {
+        auto servers = result["list"][2]["servers"];
+        auto channels = result["list"][2]["channels"];
+        auto plugins = result["list"][2]["plugins"];
+        auto events = result["list"][2]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "accept");
     }
 }
 
-TEST_F(RuleMoveCommandTest, upward)
+BOOST_AUTO_TEST_CASE(upward)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",    "rule-move" },
-            { "from",       0           },
-            { "to",         2           }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
-
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       0           },
+        { "to",         2           }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        m_result = nullptr;
-        m_irccdctl.client().request({{ "command", "rule-list" }});
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    BOOST_TEST(result.is_object());
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        // Rule 1.
-        {
-            auto servers = m_result["list"][0]["servers"];
-            auto channels = m_result["list"][0]["channels"];
-            auto plugins = m_result["list"][0]["plugins"];
-            auto events = m_result["list"][0]["events"];
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-            ASSERT_TRUE(contains(servers, "s1"));
-            ASSERT_TRUE(contains(channels, "c1"));
-            ASSERT_TRUE(contains(plugins, "p1"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][0]["action"].get<std::string>());
-        }
+    // Rule 1.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
 
-        // Rule 2.
-        {
-            auto servers = m_result["list"][1]["servers"];
-            auto channels = m_result["list"][1]["channels"];
-            auto plugins = m_result["list"][1]["plugins"];
-            auto events = m_result["list"][1]["events"];
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+    }
 
-            ASSERT_TRUE(contains(servers, "s2"));
-            ASSERT_TRUE(contains(channels, "c2"));
-            ASSERT_TRUE(contains(plugins, "p2"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][1]["action"].get<std::string>());
-        }
+    // Rule 2.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
 
-        // Rule 0.
-        {
-            auto servers = m_result["list"][2]["servers"];
-            auto channels = m_result["list"][2]["channels"];
-            auto plugins = m_result["list"][2]["plugins"];
-            auto events = m_result["list"][2]["events"];
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
+    }
 
-            ASSERT_TRUE(contains(servers, "s0"));
-            ASSERT_TRUE(contains(channels, "c0"));
-            ASSERT_TRUE(contains(plugins, "p0"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("drop", m_result["list"][2]["action"].get<std::string>());
-        }
-    } catch (const std::exception& ex) {
-        FAIL() << ex.what();
+    // Rule 0.
+    {
+        auto servers = result["list"][2]["servers"];
+        auto channels = result["list"][2]["channels"];
+        auto plugins = result["list"][2]["plugins"];
+        auto events = result["list"][2]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s0"));
+        BOOST_TEST(json_util::contains(channels, "c0"));
+        BOOST_TEST(json_util::contains(plugins, "p0"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "drop");
     }
 }
 
-TEST_F(RuleMoveCommandTest, same)
+BOOST_AUTO_TEST_CASE(same)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",    "rule-move" },
-            { "from",       1           },
-            { "to",         1           }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
-
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       1           },
+        { "to",         1           }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        m_result = nullptr;
-        m_irccdctl.client().request({{ "command", "rule-list" }});
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    BOOST_TEST(result.is_object());
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        // Rule 0.
-        {
-            auto servers = m_result["list"][0]["servers"];
-            auto channels = m_result["list"][0]["channels"];
-            auto plugins = m_result["list"][0]["plugins"];
-            auto events = m_result["list"][0]["events"];
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-            ASSERT_TRUE(contains(servers, "s0"));
-            ASSERT_TRUE(contains(channels, "c0"));
-            ASSERT_TRUE(contains(plugins, "p0"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("drop", m_result["list"][0]["action"].get<std::string>());
-        }
+    // Rule 0.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
 
-        // Rule 1.
-        {
-            auto servers = m_result["list"][1]["servers"];
-            auto channels = m_result["list"][1]["channels"];
-            auto plugins = m_result["list"][1]["plugins"];
-            auto events = m_result["list"][1]["events"];
+        BOOST_TEST(json_util::contains(servers, "s0"));
+        BOOST_TEST(json_util::contains(channels, "c0"));
+        BOOST_TEST(json_util::contains(plugins, "p0"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "drop");
+    }
 
-            ASSERT_TRUE(contains(servers, "s1"));
-            ASSERT_TRUE(contains(channels, "c1"));
-            ASSERT_TRUE(contains(plugins, "p1"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][1]["action"].get<std::string>());
-        }
+    // Rule 1.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
 
-        // Rule 2.
-        {
-            auto servers = m_result["list"][2]["servers"];
-            auto channels = m_result["list"][2]["channels"];
-            auto plugins = m_result["list"][2]["plugins"];
-            auto events = m_result["list"][2]["events"];
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
+    }
 
-            ASSERT_TRUE(contains(servers, "s2"));
-            ASSERT_TRUE(contains(channels, "c2"));
-            ASSERT_TRUE(contains(plugins, "p2"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][2]["action"].get<std::string>());
-        }
-    } catch (const std::exception& ex) {
-        FAIL() << ex.what();
+    // Rule 2.
+    {
+        auto servers = result["list"][2]["servers"];
+        auto channels = result["list"][2]["channels"];
+        auto plugins = result["list"][2]["plugins"];
+        auto events = result["list"][2]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "accept");
     }
 }
 
-TEST_F(RuleMoveCommandTest, beyond)
+BOOST_AUTO_TEST_CASE(beyond)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",    "rule-move" },
-            { "from",       0           },
-            { "to",         123         }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
-
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       0           },
+        { "to",         123         }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        m_result = nullptr;
-        m_irccdctl.client().request({{ "command", "rule-list" }});
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    BOOST_TEST(result.is_object());
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        // Rule 1.
-        {
-            auto servers = m_result["list"][0]["servers"];
-            auto channels = m_result["list"][0]["channels"];
-            auto plugins = m_result["list"][0]["plugins"];
-            auto events = m_result["list"][0]["events"];
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
 
-            ASSERT_TRUE(contains(servers, "s1"));
-            ASSERT_TRUE(contains(channels, "c1"));
-            ASSERT_TRUE(contains(plugins, "p1"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][0]["action"].get<std::string>());
-        }
+    // Rule 1.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
 
-        // Rule 2.
-        {
-            auto servers = m_result["list"][1]["servers"];
-            auto channels = m_result["list"][1]["channels"];
-            auto plugins = m_result["list"][1]["plugins"];
-            auto events = m_result["list"][1]["events"];
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+    }
 
-            ASSERT_TRUE(contains(servers, "s2"));
-            ASSERT_TRUE(contains(channels, "c2"));
-            ASSERT_TRUE(contains(plugins, "p2"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("accept", m_result["list"][1]["action"].get<std::string>());
-        }
+    // Rule 2.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
 
-        // Rule 0.
-        {
-            auto servers = m_result["list"][2]["servers"];
-            auto channels = m_result["list"][2]["channels"];
-            auto plugins = m_result["list"][2]["plugins"];
-            auto events = m_result["list"][2]["events"];
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
+    }
 
-            ASSERT_TRUE(contains(servers, "s0"));
-            ASSERT_TRUE(contains(channels, "c0"));
-            ASSERT_TRUE(contains(plugins, "p0"));
-            ASSERT_TRUE(contains(events, "onMessage"));
-            ASSERT_EQ("drop", m_result["list"][2]["action"].get<std::string>());
-        }
-    } catch (const std::exception& ex) {
-        FAIL() << ex.what();
+    // Rule 0.
+    {
+        auto servers = result["list"][2]["servers"];
+        auto channels = result["list"][2]["channels"];
+        auto plugins = result["list"][2]["plugins"];
+        auto events = result["list"][2]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s0"));
+        BOOST_TEST(json_util::contains(channels, "c0"));
+        BOOST_TEST(json_util::contains(plugins, "p0"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "drop");
     }
 }
 
-TEST_F(RuleMoveCommandTest, outOfBounds)
+BOOST_AUTO_TEST_CASE(out_of_bounds)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",    "rule-move" },
-            { "from",       1024        },
-            { "to",         0           }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       1024        },
+        { "to",         0           }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_FALSE(m_result["status"].get<bool>());
-    } catch (const std::exception& ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    // TODO: error code
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(result.count("error"));
 }
 
-int main(int argc, char **argv)
-{
-    testing::InitGoogleTest(&argc, argv);
+BOOST_AUTO_TEST_SUITE_END()
 
-    return RUN_ALL_TESTS();
-}
+} // !irccd
--- a/tests/cmd-rule-remove/main.cpp	Fri Nov 24 21:20:17 2017 +0100
+++ b/tests/cmd-rule-remove/main.cpp	Sat Nov 25 14:34:20 2017 +0100
@@ -16,35 +16,26 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <command.hpp>
-#include <command-tester.hpp>
-#include <service.hpp>
+#define BOOST_TEST_MODULE "rule-remove"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
 
-using namespace irccd;
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
 
-class RuleRemoveCommandTest : public CommandTester {
-protected:
-    nlohmann::json m_result;
+#include <command_test.hpp>
 
-    /*
-     * Rule sets are unordered so use this function to search a string in
-     * the JSON array.
-     */
-    inline bool contains(const nlohmann::json &array, const std::string &str)
-    {
-        for (const auto &v : array)
-            if (v.is_string() && v == str)
-                return true;
+namespace irccd {
+
+namespace {
 
-        return false;
-    }
-
+class rule_remove_test : public command_test<rule_remove_command> {
 public:
-    RuleRemoveCommandTest()
-        : CommandTester(std::make_unique<rule_remove_command>())
+    rule_remove_test()
     {
-        m_irccd.commands().add(std::make_unique<rule_list_command>());
-        m_irccd.rules().add(rule(
+        daemon_->commands().add(std::make_unique<rule_list_command>());
+        daemon_->rules().add(rule(
             { "s1", "s2" },
             { "c1", "c2" },
             { "o1", "o2" },
@@ -52,7 +43,7 @@
             { "onMessage", "onCommand" },
             rule::action_type::drop
         ));
-        m_irccd.rules().add(rule(
+        daemon_->rules().add(rule(
             { "s1", },
             { "c1", },
             { "o1", },
@@ -60,100 +51,103 @@
             { "onMessage", },
             rule::action_type::accept
         ));
-        m_irccdctl.client().onMessage.connect([&] (auto result) {
-            m_result = result;
-        });
     }
 };
 
-TEST_F(RuleRemoveCommandTest, basic)
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_remove_test_suite, rule_remove_test)
+
+BOOST_AUTO_TEST_CASE(basic)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",    "rule-remove"   },
-            { "index",      1               }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",    "rule-remove"   },
+        { "index",      1               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_TRUE(m_result["status"].get<bool>());
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        m_result = nullptr;
-        m_irccdctl.client().request({{ "command", "rule-list" }});
+    BOOST_TEST(result.is_object());
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result["list"].is_array());
-        ASSERT_EQ(1U, m_result["list"].size());
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        auto servers = m_result["list"][0]["servers"];
-        auto channels = m_result["list"][0]["channels"];
-        auto plugins = m_result["list"][0]["plugins"];
-        auto events = m_result["list"][0]["events"];
+    BOOST_TEST(result["list"].is_array());
+    BOOST_TEST(result["list"].size() == 1U);
 
-        ASSERT_TRUE(contains(servers, "s1"));
-        ASSERT_TRUE(contains(servers, "s2"));
-        ASSERT_TRUE(contains(channels, "c1"));
-        ASSERT_TRUE(contains(channels, "c2"));
-        ASSERT_TRUE(contains(plugins, "p1"));
-        ASSERT_TRUE(contains(plugins, "p2"));
-        ASSERT_TRUE(contains(events, "onMessage"));
-        ASSERT_TRUE(contains(events, "onCommand"));
-        ASSERT_EQ("drop", m_result["list"][0]["action"]);
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    auto servers = result["list"][0]["servers"];
+    auto channels = result["list"][0]["channels"];
+    auto plugins = result["list"][0]["plugins"];
+    auto events = result["list"][0]["events"];
+
+    BOOST_TEST(json_util::contains(servers, "s1"));
+    BOOST_TEST(json_util::contains(servers, "s2"));
+    BOOST_TEST(json_util::contains(channels, "c1"));
+    BOOST_TEST(json_util::contains(channels, "c2"));
+    BOOST_TEST(json_util::contains(plugins, "p1"));
+    BOOST_TEST(json_util::contains(plugins, "p2"));
+    BOOST_TEST(json_util::contains(events, "onMessage"));
+    BOOST_TEST(json_util::contains(events, "onCommand"));
+    BOOST_TEST(result["list"][0]["action"].get<std::string>() == "drop");
 }
 
-TEST_F(RuleRemoveCommandTest, empty)
+BOOST_AUTO_TEST_CASE(empty)
 {
-    m_irccd.rules().remove(0);
-    m_irccd.rules().remove(0);
+    nlohmann::json result;
+
+    daemon_->rules().remove(0);
+    daemon_->rules().remove(0);
 
-    try {
-        m_irccdctl.client().request({
-            { "command",    "rule-remove"   },
-            { "index",      1               }
-        });
+    ctl_->send({
+        { "command",    "rule-remove"   },
+        { "index",      1               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    wait_for([&] () {
+        return result.is_object();
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_FALSE(m_result["status"].get<bool>());
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    BOOST_TEST(result.is_object());
 }
 
-TEST_F(RuleRemoveCommandTest, outOfBounds)
+BOOST_AUTO_TEST_CASE(out_of_bounds)
 {
-    try {
-        m_irccdctl.client().request({
-            { "command",    "rule-remove"   },
-            { "index",      123             }
-        });
+    nlohmann::json result;
 
-        poll([&] () {
-            return m_result.is_object();
-        });
+    ctl_->send({
+        { "command",    "rule-remove"   },
+        { "index",      123             }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
 
-        ASSERT_TRUE(m_result.is_object());
-        ASSERT_FALSE(m_result["status"].get<bool>());
-    } catch (const std::exception &ex) {
-        FAIL() << ex.what();
-    }
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    // TODO: error code
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(result.count("error"));
 }
 
-int main(int argc, char **argv)
-{
-    testing::InitGoogleTest(&argc, argv);
+BOOST_AUTO_TEST_SUITE_END()
 
-    return RUN_ALL_TESTS();
-}
+} // !irccd