changeset 651:1081e45b8628

Tests: use command_test::request helper, closes #784 @1h
author David Demelier <markand@malikania.fr>
date Tue, 27 Mar 2018 20:12:02 +0200
parents 27896e9bcd9e
children e37ac2269ba6
files libirccd-test/irccd/test/command_test.hpp libirccd/irccd/daemon/service/rule_service.cpp tests/src/libirccd/command-plugin-config/main.cpp tests/src/libirccd/command-plugin-info/main.cpp tests/src/libirccd/command-plugin-list/main.cpp tests/src/libirccd/command-plugin-load/main.cpp tests/src/libirccd/command-plugin-reload/main.cpp tests/src/libirccd/command-plugin-unload/main.cpp tests/src/libirccd/command-rule-add/main.cpp tests/src/libirccd/command-rule-edit/main.cpp tests/src/libirccd/command-rule-info/main.cpp tests/src/libirccd/command-rule-list/main.cpp tests/src/libirccd/command-rule-remove/main.cpp tests/src/libirccd/command-server-connect/main.cpp tests/src/libirccd/command-server-disconnect/main.cpp tests/src/libirccd/command-server-info/main.cpp tests/src/libirccd/command-server-invite/main.cpp tests/src/libirccd/command-server-join/main.cpp tests/src/libirccd/command-server-kick/main.cpp tests/src/libirccd/command-server-list/main.cpp tests/src/libirccd/command-server-me/main.cpp tests/src/libirccd/command-server-message/main.cpp tests/src/libirccd/command-server-mode/main.cpp tests/src/libirccd/command-server-nick/main.cpp tests/src/libirccd/command-server-notice/main.cpp tests/src/libirccd/command-server-part/main.cpp tests/src/libirccd/command-server-reconnect/main.cpp tests/src/libirccd/command-server-topic/main.cpp
diffstat 28 files changed, 675 insertions(+), 2153 deletions(-) [+]
line wrap: on
line diff
--- a/libirccd-test/irccd/test/command_test.hpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/libirccd-test/irccd/test/command_test.hpp	Tue Mar 27 20:12:02 2018 +0200
@@ -48,6 +48,11 @@
     }
 
 protected:
+    /**
+     * Result for request function.
+     */
+    using result = std::pair<nlohmann::json, boost::system::error_code>;
+
     boost::asio::io_service service_;
     boost::asio::deadline_timer timer_;
 
@@ -68,6 +73,22 @@
         while (!cond())
             service_.poll();
     }
+
+    result request(nlohmann::json json)
+    {
+        result r;
+
+        ctl_->send(std::move(json));
+        ctl_->recv([&] (auto result, auto message) {
+            r.first = message;
+            r.second = result;
+        });
+        wait_for([&] {
+            return r.second || r.first.is_object();
+        });
+
+        return r;
+    }
 };
 
 template <typename... Commands>
--- a/libirccd/irccd/daemon/service/rule_service.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/libirccd/irccd/daemon/service/rule_service.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -29,11 +29,6 @@
 
 namespace irccd {
 
-namespace {
-
-
-} // !namespace
-
 rule_service::rule_service(irccd &irccd)
     : irccd_(irccd)
 {
--- a/tests/src/libirccd/command-plugin-config/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-plugin-config/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -81,24 +81,16 @@
         { "x1", "10" },
         { "x2", "20" }
     });
+    daemon_->plugins().add(std::move(plugin));
 
-    daemon_->plugins().add(std::move(plugin));
-    ctl_->send({
-        { "command", "plugin-config" },
-        { "plugin", "test" },
-        { "variable", "x1" }
-    });
-    ctl_->recv([&] (auto, auto message) {
-        json = std::move(message);
+    auto result = request({
+        { "command",    "plugin-config" },
+        { "plugin",     "test"          },
+        { "variable",   "x1"            }
     });
 
-    wait_for([&] {
-        return json.is_object();
-    });
-
-    BOOST_TEST(json.is_object());
-    BOOST_TEST(json["variables"]["x1"].get<std::string>() == "10");
-    BOOST_TEST(json["variables"]["x2"].is_null());
+    BOOST_TEST(result.first["variables"]["x1"].get<std::string>() == "10");
+    BOOST_TEST(result.first["variables"]["x2"].is_null());
 }
 
 BOOST_AUTO_TEST_CASE(getall)
@@ -110,70 +102,40 @@
         { "x1", "10" },
         { "x2", "20" }
     });
+    daemon_->plugins().add(std::move(plugin));
 
-    daemon_->plugins().add(std::move(plugin));
-    ctl_->send({
+    auto result = request({
         { "command", "plugin-config" },
         { "plugin", "test" }
     });
-    ctl_->recv([&] (auto, auto message) {
-        json = std::move(message);
-    });
 
-    wait_for([&] {
-        return json.is_object();
-    });
-
-    BOOST_TEST(json.is_object());
-    BOOST_TEST(json["variables"]["x1"].get<std::string>() == "10");
-    BOOST_TEST(json["variables"]["x2"].get<std::string>() == "20");
+    BOOST_TEST(result.first["variables"]["x1"].get<std::string>() == "10");
+    BOOST_TEST(result.first["variables"]["x2"].get<std::string>() == "20");
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
 
 BOOST_AUTO_TEST_CASE(invalid_identifier)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-config" }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-config" },
         { "plugin",     "unknown"       }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-plugin-info/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-plugin-info/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -37,72 +37,42 @@
     plg->set_license("GPL");
     plg->set_summary("Completely useless plugin");
     plg->set_version("0.0.0.0.0.0.0.0.1-beta5");
+    daemon_->plugins().add(std::move(plg));
 
-    daemon_->plugins().add(std::move(plg));
-    ctl_->recv([&] (auto, auto msg) {
-        response = std::move(msg);
-    });
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-info"       },
         { "plugin",     "test"              },
     });
 
-    wait_for([&] () {
-        return response.is_object();
-    });
-
-    BOOST_TEST(response.is_object());
-    BOOST_TEST(response["author"].get<std::string>() == "Francis Beaugrand");
-    BOOST_TEST(response["license"].get<std::string>() == "GPL");
-    BOOST_TEST(response["summary"].get<std::string>() == "Completely useless plugin");
-    BOOST_TEST(response["version"].get<std::string>() == "0.0.0.0.0.0.0.0.1-beta5");
+    BOOST_TEST(result.first["author"].get<std::string>() == "Francis Beaugrand");
+    BOOST_TEST(result.first["license"].get<std::string>() == "GPL");
+    BOOST_TEST(result.first["summary"].get<std::string>() == "Completely useless plugin");
+    BOOST_TEST(result.first["version"].get<std::string>() == "0.0.0.0.0.0.0.0.1-beta5");
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
 
 BOOST_AUTO_TEST_CASE(invalid_identifier)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-info"   }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-info"   },
         { "plugin",     "unknown"       }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-plugin-list/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-plugin-list/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -39,20 +39,13 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    auto response = nlohmann::json();
-
-    ctl_->send({{"command", "plugin-list"}});
-    ctl_->recv([&] (auto, auto message) {
-        response = message;
+    const auto result = request({
+        { "command", "plugin-list" }
     });
 
-    wait_for([&] () {
-        return response.is_object();
-    });
-
-    BOOST_TEST(response.is_object());
-    BOOST_TEST("t1", response["list"][0]);
-    BOOST_TEST("t2", response["list"][1]);
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(result.first["list"][0].template get<std::string>() == "t1");
+    BOOST_TEST(result.first["list"][1].template get<std::string>() == "t2");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-plugin-load/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-plugin-load/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -98,85 +98,49 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier)
 {
-    boost::system::error_code result;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-load"   }
     });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::invalid_identifier);
+    BOOST_TEST(result.second == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-load"   },
         { "plugin",     "unknown"       }
     });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::not_found);
+    BOOST_TEST(result.second == plugin_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_CASE(already_exists)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-load"   },
         { "plugin",     "already"       }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::already_exists);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::already_exists);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::already_exists);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::already_exists);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_CASE(exec_error)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-load"   },
         { "plugin",     "broken"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::exec_error);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::exec_error);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::exec_error);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::exec_error);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-plugin-reload/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-plugin-reload/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -87,70 +87,37 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-reload" }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-reload" },
         { "plugin",     "unknown"       }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_CASE(exec_error)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-reload" },
         { "plugin",     "broken"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::exec_error);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::exec_error);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::exec_error);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::exec_error);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-plugin-unload/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-plugin-unload/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -87,70 +87,37 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-unload" }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-unload" },
         { "plugin",     "unknown"       }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
 }
 
 BOOST_AUTO_TEST_CASE(exec_error)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "plugin-unload" },
         { "plugin",     "broken"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == plugin_error::exec_error);
-    BOOST_TEST(message["error"].template get<int>() == plugin_error::exec_error);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "plugin");
+    BOOST_TEST(result.second == plugin_error::exec_error);
+    BOOST_TEST(result.first["error"].template get<int>() == plugin_error::exec_error);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "plugin");
     BOOST_TEST(!daemon_->plugins().has("broken"));
 }
 
--- a/tests/src/libirccd/command-rule-add/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-rule-add/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -39,9 +39,7 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",    "rule-add"          },
         { "servers",    { "s1", "s2" }      },
         { "channels",   { "c1", "c2" }      },
@@ -50,32 +48,17 @@
         { "action",     "accept"            },
         { "index",      0                   }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
+    const auto result = request({
+        { "command", "rule-list" }
     });
 
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({{"command", "rule-list"}});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
+    BOOST_TEST(result.first.is_object());
 
-    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"];
+    auto servers = result.first["list"][0]["servers"];
+    auto channels = result.first["list"][0]["channels"];
+    auto plugins = result.first["list"][0]["plugins"];
+    auto events = result.first["list"][0]["events"];
 
     BOOST_TEST(json_util::contains(servers, "s1"));
     BOOST_TEST(json_util::contains(servers, "s2"));
@@ -84,14 +67,12 @@
     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_TEST(result.first["list"][0]["action"].get<std::string>() == "accept");
 }
 
 BOOST_AUTO_TEST_CASE(append)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",    "rule-add"          },
         { "servers",    { "s1" }            },
         { "channels",   { "c1" }            },
@@ -100,18 +81,8 @@
         { "action",     "accept"            },
         { "index",      0                   }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    request({
         { "command",    "rule-add"          },
         { "servers",    { "s2" }            },
         { "channels",   { "c2" }            },
@@ -120,55 +91,40 @@
         { "action",     "drop"              },
         { "index",      1                   }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
+    const auto result = request({
+        { "command", "rule-list" }
     });
 
-    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());
-    BOOST_TEST(result["list"].size() == 2U);
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(result.first["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"];
+        auto servers = result.first["list"][0]["servers"];
+        auto channels = result.first["list"][0]["channels"];
+        auto plugins = result.first["list"][0]["plugins"];
+        auto events = result.first["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");
+        BOOST_TEST(result.first["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"];
+        auto servers = result.first["list"][1]["servers"];
+        auto channels = result.first["list"][1]["channels"];
+        auto plugins = result.first["list"][1]["plugins"];
+        auto events = result.first["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");
+        BOOST_TEST(result.first["list"][1]["action"].get<std::string>() == "drop");
     }
 }
 
@@ -176,25 +132,14 @@
 
 BOOST_AUTO_TEST_CASE(invalid_action)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-add"  },
         { "action",     "unknown"   }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_action);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_action);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_action);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_action);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-rule-edit/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-rule-edit/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -52,592 +52,339 @@
 
 BOOST_AUTO_TEST_CASE(add_server)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "add-servers",    { "new-s3" }    },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "new-s3"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(add_channel)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "add-channels",   { "new-c3" }    },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "new-c3"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(add_plugin)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "add-plugins",    { "new-p3" }    },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "new-p3"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(add_event)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "add-events",     { "onQuery" }   },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onQuery"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(add_event_and_server)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "add-servers",    { "new-s3" }    },
         { "add-events",     { "onQuery" }   },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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(json_util::contains(result["events"], "onQuery"));
-    BOOST_TEST(result["action"].get<std::string>() == "drop");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "new-s3"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onQuery"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(change_action)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "action",         "accept"        },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "accept");
 }
 
 BOOST_AUTO_TEST_CASE(remove_server)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "remove-servers", { "s2" }        },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(!json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(remove_channel)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "remove-channels", { "c2" }       },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(!json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(remove_plugin)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "remove-plugins", { "p2" }        },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(!json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(remove_event)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "remove-events",  { "onCommand" } },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(!json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(remove_event_and_server)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",        "rule-edit"     },
         { "remove-servers", { "s2" }        },
         { "remove-events",  { "onCommand" } },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({
+    const auto result = request({
         { "command",        "rule-info"     },
         { "index",          0               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    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");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(json_util::contains(result.first["servers"], "s1"));
+    BOOST_TEST(!json_util::contains(result.first["servers"], "s2"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c1"));
+    BOOST_TEST(json_util::contains(result.first["channels"], "c2"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p1"));
+    BOOST_TEST(json_util::contains(result.first["plugins"], "p2"));
+    BOOST_TEST(json_util::contains(result.first["events"], "onMessage"));
+    BOOST_TEST(!json_util::contains(result.first["events"], "onCommand"));
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
 
 BOOST_AUTO_TEST_CASE(invalid_index_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-edit" },
         { "index",      -100        },
         { "action",     "drop"      }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-edit" },
         { "index",      100         },
         { "action",     "drop"      }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_3)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-edit" },
         { "index",      "notaint"   },
         { "action",     "drop"      }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_action)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-edit" },
         { "index",      0           },
         { "action",     "unknown"   }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_action);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_action);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_action);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_action);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-rule-info/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-rule-info/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -59,26 +59,15 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-info" },
         { "index",      0           }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    auto servers = result["servers"];
-    auto channels = result["channels"];
-    auto plugins = result["plugins"];
-    auto events = result["events"];
+    auto servers = result.first["servers"];
+    auto channels = result.first["channels"];
+    auto plugins = result.first["plugins"];
+    auto events = result.first["events"];
 
     BOOST_TEST(json_util::contains(servers, "s1"));
     BOOST_TEST(json_util::contains(servers, "s2"));
@@ -88,78 +77,45 @@
     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");
+    BOOST_TEST(result.first["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
 
 BOOST_AUTO_TEST_CASE(invalid_index_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-info" },
         { "index",      -100        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-info" },
         { "index",      100         }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_3)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-info" },
         { "index",      "notaint"   }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-rule-list/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-rule-list/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -59,26 +59,20 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    nlohmann::json result;
-
-    ctl_->send({{"command", "rule-list"}});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-    wait_for([&] () {
-        return result.is_object();
+    const auto result = request({
+        { "command", "rule-list" }
     });
 
-    BOOST_TEST(result.is_object());
-    BOOST_TEST(result["list"].is_array());
-    BOOST_TEST(result["list"].size() == 2U);
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(result.first["list"].is_array());
+    BOOST_TEST(result.first["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"];
+        auto servers = result.first["list"][0]["servers"];
+        auto channels = result.first["list"][0]["channels"];
+        auto plugins = result.first["list"][0]["plugins"];
+        auto events = result.first["list"][0]["events"];
 
         BOOST_TEST(json_util::contains(servers, "s1"));
         BOOST_TEST(json_util::contains(servers, "s2"));
@@ -88,42 +82,36 @@
         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");
+        BOOST_TEST(result.first["list"][0]["action"].get<std::string>() == "drop");
     }
 
     // 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"];
+        auto servers = result.first["list"][1]["servers"];
+        auto channels = result.first["list"][1]["channels"];
+        auto plugins = result.first["list"][1]["plugins"];
+        auto events = result.first["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");
+        BOOST_TEST(result.first["list"][1]["action"].get<std::string>() == "accept");
     }
 }
 
 BOOST_AUTO_TEST_CASE(empty)
 {
-    nlohmann::json result;
-
     daemon_->rules().remove(0);
     daemon_->rules().remove(0);
 
-    ctl_->send({{"command", "rule-list"}});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-    wait_for([&] () {
-        return result.is_object();
+    const auto result = request({
+        { "command", "rule-list" }
     });
 
-    BOOST_TEST(result.is_object());
-    BOOST_TEST(result["list"].is_array());
-    BOOST_TEST(result["list"].empty());
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(result.first["list"].is_array());
+    BOOST_TEST(result.first["list"].empty());
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-rule-remove/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-rule-remove/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -60,39 +60,22 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    request({
         { "command",    "rule-remove"   },
         { "index",      1               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
+    const auto result = request({
+        { "command", "rule-list" }
     });
 
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
+    BOOST_TEST(result.first["list"].is_array());
+    BOOST_TEST(result.first["list"].size() == 1U);
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result["list"].is_array());
-    BOOST_TEST(result["list"].size() == 1U);
-
-    auto servers = result["list"][0]["servers"];
-    auto channels = result["list"][0]["channels"];
-    auto plugins = result["list"][0]["plugins"];
-    auto events = result["list"][0]["events"];
+    auto servers = result.first["list"][0]["servers"];
+    auto channels = result.first["list"][0]["channels"];
+    auto plugins = result.first["list"][0]["plugins"];
+    auto events = result.first["list"][0]["events"];
 
     BOOST_TEST(json_util::contains(servers, "s1"));
     BOOST_TEST(json_util::contains(servers, "s2"));
@@ -102,100 +85,58 @@
     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");
+    BOOST_TEST(result.first["list"][0]["action"].get<std::string>() == "drop");
 }
 
 BOOST_AUTO_TEST_CASE(empty)
 {
-    nlohmann::json result;
-
     daemon_->rules().remove(0);
     daemon_->rules().remove(0);
 
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-remove"   },
         { "index",      1               }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
+    BOOST_TEST(result.first.is_object());
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
 
 BOOST_AUTO_TEST_CASE(invalid_index_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-remove"   },
         { "index",      -100            }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-remove"   },
         { "index",      100             }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_index_3)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "rule-remove"   },
         { "index",      "notaint"       }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == rule_error::invalid_index);
-    BOOST_TEST(message["error"].template get<int>() == rule_error::invalid_index);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "rule");
+    BOOST_TEST(result.second == rule_error::invalid_index);
+    BOOST_TEST(result.first["error"].template get<int>() == rule_error::invalid_index);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "rule");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-connect/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-connect/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -31,22 +31,13 @@
 
 BOOST_AUTO_TEST_CASE(minimal)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       "local"             },
         { "host",       "irc.example.org"   }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    auto s = daemon_->servers().get("local");
+    const auto s = daemon_->servers().get("local");
 
     BOOST_TEST(s);
     BOOST_TEST(s->get_name() == "local");
@@ -58,9 +49,7 @@
 
 BOOST_AUTO_TEST_CASE(full)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       "local2"            },
         { "host",       "irc.example2.org"  },
@@ -76,15 +65,8 @@
         { "autoRejoin", true                },
         { "joinInvite", true                }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    auto s = daemon_->servers().get("local2");
+    const auto s = daemon_->servers().get("local2");
 
     BOOST_TEST(s);
     BOOST_TEST(s->get_name() == "local2");
@@ -108,224 +90,126 @@
 
 BOOST_AUTO_TEST_CASE(already_exists)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
+    daemon_->servers().add(std::make_unique<journal_server>(service_, "local"));
 
-    daemon_->servers().add(std::make_unique<journal_server>(service_, "local"));
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       "local"             },
         { "host",       "127.0.0.1"         }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::already_exists);
-    BOOST_TEST(message["error"].template get<int>() == server_error::already_exists);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::already_exists);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::already_exists);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_hostname_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       "new"               },
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_hostname);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_hostname);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_hostname);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_hostname);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_hostname_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       "new"               },
         { "host",       123456              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_hostname);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_hostname);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_hostname);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_hostname);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       ""                  },
         { "host",       "127.0.0.1"         }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       123456              },
         { "host",       "127.0.0.1"         }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_port_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       "new"               },
         { "host",       "127.0.0.1"         },
         { "port",       "notaint"           }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_port);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_port);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_port);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_port);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_port_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       "new"               },
         { "host",       "127.0.0.1"         },
         { "port",       -123                }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_port);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_port);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_port);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_port);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_port_3)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       "new"               },
         { "host",       "127.0.0.1"         },
         { "port",       1000000             }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_port);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_port);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_port);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_port);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 #if !defined(HAVE_SSL)
 
 BOOST_AUTO_TEST_CASE(ssl_disabled)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-connect"    },
         { "name",       "new"               },
         { "host",       "127.0.0.1"         },
         { "ssl",        true                }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::ssl_disabled);
-    BOOST_TEST(message["error"].template get<int>() == server_error::ssl_disabled);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::ssl_disabled);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::ssl_disabled);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 #endif
--- a/tests/src/libirccd/command-server-disconnect/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-disconnect/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -54,39 +54,23 @@
 
 BOOST_AUTO_TEST_CASE(one)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-disconnect" },
         { "server",     "s1"                }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result["command"].get<std::string>() == "server-disconnect");
+    BOOST_TEST(result.first["command"].get<std::string>() == "server-disconnect");
     BOOST_TEST(!daemon_->servers().has("s1"));
     BOOST_TEST(daemon_->servers().has("s2"));
 }
 
 BOOST_AUTO_TEST_CASE(all)
 {
-    nlohmann::json result;
-
-    ctl_->send({{"command", "server-disconnect"}});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
+    const auto result = request({
+        { "command", "server-disconnect" }
     });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result["command"].get<std::string>() == "server-disconnect");
+    BOOST_TEST(result.first["command"].get<std::string>() == "server-disconnect");
     BOOST_TEST(!daemon_->servers().has("s1"));
     BOOST_TEST(!daemon_->servers().has("s2"));
 }
@@ -95,48 +79,26 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-disconnect" },
         { "server",     123456              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-disconnect" },
         { "server",     "unknown"           }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-info/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-info/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -44,99 +44,58 @@
     server->set_reconnect_tries(80);
     server->set_ping_timeout(20000);
 
-    nlohmann::json result;
+    daemon_->servers().add(std::move(server));
 
-    daemon_->servers().add(std::move(server));
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-info"       },
         { "server",     "test"              },
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-    BOOST_TEST(result["host"].get<std::string>() == "example.org");
-    BOOST_TEST(result["name"].get<std::string>() == "test");
-    BOOST_TEST(result["nickname"].get<std::string>() == "pascal");
-    BOOST_TEST(result["port"].get<int>() == 8765);
-    BOOST_TEST(result["realname"].get<std::string>() == "Pascal le grand frere");
-    BOOST_TEST(result["username"].get<std::string>() == "psc");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(result.first["host"].get<std::string>() == "example.org");
+    BOOST_TEST(result.first["name"].get<std::string>() == "test");
+    BOOST_TEST(result.first["nickname"].get<std::string>() == "pascal");
+    BOOST_TEST(result.first["port"].get<int>() == 8765);
+    BOOST_TEST(result.first["realname"].get<std::string>() == "Pascal le grand frere");
+    BOOST_TEST(result.first["username"].get<std::string>() == "psc");
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-info"   },
         { "server",     123456          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-info"   },
         { "server",     ""              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-info"   },
         { "server",     "unknown"       }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-invite/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-invite/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -67,177 +67,100 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-invite" },
         { "server",     123456          },
         { "target",     "francis"       },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-invite" },
         { "server",     ""              },
         { "target",     "francis"       },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_nickname_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-invite" },
         { "server",     "test"          },
         { "target",     ""              },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_nickname);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_nickname);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_nickname);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_nickname);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_nickname_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-invite" },
         { "server",     "test"          },
         { "target",     123456          },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_nickname);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_nickname);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_nickname);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_nickname);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-invite" },
         { "server",     "test"          },
         { "target",     "jean"          },
         { "channel",    ""              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-invite" },
         { "server",     "test"          },
         { "target",     "jean"          },
         { "channel",    123456          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-invite" },
         { "server",     "unknown"       },
         { "target",     "francis"       },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-join/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-join/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -65,16 +65,12 @@
 
 BOOST_AUTO_TEST_CASE(nopassword)
 {
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-join"       },
         { "server",     "test"              },
         { "channel",    "#music"            }
     });
 
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
     auto cmd = server_->cqueue().back();
 
     BOOST_TEST(cmd["command"].get<std::string>() == "join");
@@ -86,147 +82,81 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-join"   },
         { "server",     123456          },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-join"   },
         { "server",     ""              },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-join"   },
         { "server",     "test"          },
         { "channel",    ""              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-join"   },
         { "server",     "test"          },
         { "channel",    123456          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_password)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-join"   },
         { "server",     "test"          },
         { "channel",    "#staff"        },
         { "password",   123456          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_password);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_password);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_password);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_password);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-join"   },
         { "server",     "unknown"       },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-kick/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-kick/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -90,203 +90,115 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-kick"   },
         { "server",     123456          },
         { "target",     "francis"       },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-kick"   },
         { "server",     ""              },
         { "target",     "francis"       },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_nickname_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-kick"   },
         { "server",     "test"          },
         { "target",     ""              },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_nickname);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_nickname);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_nickname);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_nickname);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_nickname_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-kick"   },
         { "server",     "test"          },
         { "target",     123456          },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_nickname);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_nickname);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_nickname);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_nickname);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-kick"   },
         { "server",     "test"          },
         { "target",     "jean"          },
         { "channel",    ""              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-kick"   },
         { "server",     "test"          },
         { "target",     "jean"          },
         { "channel",    123456          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_message)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-kick"   },
         { "server",     "test"          },
         { "target",     "jean"          },
         { "channel",    "#staff"        },
         { "reason",     123456          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_message);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_message);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_message);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_message);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-kick"   },
         { "server",     "unknown"       },
         { "target",     "francis"       },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-list/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-list/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -44,22 +44,15 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    nlohmann::json result;
-
-    ctl_->send({{"command", "server-list"}});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
+    const auto result = request({
+        { "command", "server-list" }
     });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-    BOOST_TEST(result["list"].is_array());
-    BOOST_TEST(result["list"].size() == 2U);
-    BOOST_TEST(result["list"][0].get<std::string>() == "s1");
-    BOOST_TEST(result["list"][1].get<std::string>() == "s2");
+    BOOST_TEST(result.first.is_object());
+    BOOST_TEST(result.first["list"].is_array());
+    BOOST_TEST(result.first["list"].size() == 2U);
+    BOOST_TEST(result.first["list"][0].get<std::string>() == "s1");
+    BOOST_TEST(result.first["list"][1].get<std::string>() == "s2");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-me/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-me/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -45,17 +45,13 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-me"         },
         { "server",     "test"              },
         { "target",     "jean"              },
         { "message",    "hello!"            }
     });
 
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
     auto cmd = server_->cqueue().back();
 
     BOOST_TEST(cmd["command"].get<std::string>() == "me");
@@ -67,127 +63,72 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-me" },
         { "server",     123456      },
         { "target",     "#music"    },
         { "message",    "hello!"    }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-me" },
         { "server",     ""          },
         { "target",     "#music"    },
         { "message",    "hello!"    }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-me" },
         { "server",     "test"      },
         { "target",     ""          },
         { "message",    "hello!"    }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-me" },
         { "server",     "test"      },
         { "target",     123456      },
         { "message",    "hello!"    }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-me" },
         { "server",     "unknown"   },
         { "target",     "#music"    },
         { "message",    "hello!"    }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-message/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-message/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -45,17 +45,13 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-message"    },
         { "server",     "test"              },
         { "target",     "#staff"            },
         { "message",    "plop!"             }
     });
 
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
     auto cmd = server_->cqueue().back();
 
     BOOST_TEST(cmd["command"].get<std::string>() == "message");
@@ -67,127 +63,72 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-message"    },
         { "server",     123456              },
         { "target",     "#music"            },
         { "message",    "plop!"             }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-message"    },
         { "server",     ""                  },
         { "target",     "#music"            },
         { "message",    "plop!"             }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-message"    },
         { "server",     "test"              },
         { "target",     ""                  },
         { "message",    "plop!"             }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-message"    },
         { "server",     "test"              },
         { "target",     123456              },
         { "message",    "plop!"             }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-message"    },
         { "server",     "unknown"           },
         { "target",     "#music"            },
         { "message",    "plop!"             }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-mode/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-mode/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -67,176 +67,99 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-mode"   },
         { "server",     123456          },
         { "channel",    "#music"        },
         { "mode",       "+i"            }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-mode"   },
         { "server",     ""              },
         { "channel",    "#music"        },
         { "mode",       "+i"            }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-mode"   },
         { "server",     "test"          },
         { "channel",    ""              },
         { "mode",       "+i"            }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-mode"   },
         { "server",     "test"          },
         { "channel",    123456          },
         { "mode",       "+i"            }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_mode_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-mode"   },
         { "server",     "test"          },
         { "channel",    "#music"        },
         { "mode",       ""              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_mode);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_mode);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_mode);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_mode);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_mode_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-mode"   },
         { "server",     "test"          },
         { "channel",    "#music"        },
         { "mode",       123456          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_mode);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_mode);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_mode);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_mode);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-mode"   },
         { "server",     "unknown"       },
         { "channel",    "#music"        },
         { "mode",       "+i"            }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-nick/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-nick/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -45,22 +45,13 @@
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    nlohmann::json result;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-nick"   },
         { "server",     "test"          },
         { "nickname",   "chris"         }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
 
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
+    BOOST_TEST(result.first.is_object());
     BOOST_TEST(server_->get_nickname() == "chris");
 }
 
@@ -68,121 +59,66 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-nick"   },
         { "server",     123456          },
         { "nickname",   "chris"         }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-nick"   },
         { "server",     ""              },
         { "nickname",   "chris"         }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_nickname_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-nick"   },
         { "server",     "test"          },
         { "nickname",   ""              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_nickname);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_nickname);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_nickname);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_nickname);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_nickname_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-nick"   },
         { "server",     "test"          },
         { "nickname",   123456          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_nickname);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_nickname);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_nickname);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_nickname);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-nick"   },
         { "server",     "unknown"       },
         { "nickname",   "chris"         }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-notice/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-notice/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -67,127 +67,72 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-notice" },
         { "server",     123456          },
         { "target",     "#music"        },
         { "message",    "quiet!"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-notice" },
         { "server",     ""              },
         { "target",     "#music"        },
         { "message",    "quiet!"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-notice" },
         { "server",     "test"          },
         { "target",     ""              },
         { "message",    "quiet!"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-notice" },
         { "server",     "test"          },
         { "target",     123456          },
         { "message",    "quiet!"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-notice" },
         { "server",     "unknown"       },
         { "target",     "#music"        },
         { "message",    "quiet!"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-part/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-part/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -86,122 +86,67 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-part"   },
         { "server",     123456          },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-part"   },
         { "server",     ""              },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-part"   },
         { "server",     "test"          },
         { "channel",    ""              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-part"   },
         { "server",     "test"          },
         { "channel",    123456          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-part"   },
         { "server",     "unknown"       },
         { "channel",    "#music"        }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-reconnect/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-reconnect/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -81,71 +81,38 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-reconnect"  },
         { "server",     123456              }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-reconnect"  },
         { "server",     ""                  }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-reconnect"  },
         { "server",     "unknown"           }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()
--- a/tests/src/libirccd/command-server-topic/main.cpp	Mon Mar 26 20:46:02 2018 +0200
+++ b/tests/src/libirccd/command-server-topic/main.cpp	Tue Mar 27 20:12:02 2018 +0200
@@ -67,127 +67,72 @@
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-topic"  },
         { "server",     123456          },
         { "channel",    "#music"        },
         { "topic",      "plop"          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_identifier_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-topic"  },
         { "server",     ""              },
         { "channel",    "#music"        },
         { "topic",      "plop"          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_identifier);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_identifier);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_identifier);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_identifier);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_1)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-topic"  },
         { "server",     "test"          },
         { "channel",    ""              },
         { "topic",      "plop"          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(invalid_channel_2)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-topic"  },
         { "server",     "test"          },
         { "channel",    123456          },
         { "topic",      "plop"          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::invalid_channel);
-    BOOST_TEST(message["error"].template get<int>() == server_error::invalid_channel);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::invalid_channel);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::invalid_channel);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_CASE(not_found)
 {
-    boost::system::error_code result;
-    nlohmann::json message;
-
-    ctl_->send({
+    const auto result = request({
         { "command",    "server-topic"  },
         { "server",     "unknown"       },
         { "channel",    "#music"        },
         { "topic",      "plop"          }
     });
-    ctl_->recv([&] (auto rresult, auto rmessage) {
-        result = rresult;
-        message = rmessage;
-    });
 
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_TEST(result == server_error::not_found);
-    BOOST_TEST(message["error"].template get<int>() == server_error::not_found);
-    BOOST_TEST(message["errorCategory"].template get<std::string>() == "server");
+    BOOST_TEST(result.second == server_error::not_found);
+    BOOST_TEST(result.first["error"].template get<int>() == server_error::not_found);
+    BOOST_TEST(result.first["errorCategory"].template get<std::string>() == "server");
 }
 
 BOOST_AUTO_TEST_SUITE_END()