changeset 579:84ea13c850f4

Tests: rename close to target names
author David Demelier <markand@malikania.fr>
date Mon, 04 Dec 2017 13:49:51 +0100
parents a8b892177909
children 2e16c3623531
files tests/CMakeLists.txt tests/cmd-plugin-config/CMakeLists.txt tests/cmd-plugin-config/main.cpp tests/cmd-plugin-info/CMakeLists.txt tests/cmd-plugin-info/main.cpp tests/cmd-plugin-list/CMakeLists.txt tests/cmd-plugin-list/main.cpp tests/cmd-plugin-load/CMakeLists.txt tests/cmd-plugin-load/main.cpp tests/cmd-plugin-reload/CMakeLists.txt tests/cmd-plugin-reload/main.cpp tests/cmd-plugin-unload/CMakeLists.txt tests/cmd-plugin-unload/main.cpp tests/cmd-rule-add/CMakeLists.txt tests/cmd-rule-add/main.cpp tests/cmd-rule-edit/CMakeLists.txt tests/cmd-rule-edit/main.cpp tests/cmd-rule-info/CMakeLists.txt tests/cmd-rule-info/main.cpp tests/cmd-rule-list/CMakeLists.txt tests/cmd-rule-list/main.cpp tests/cmd-rule-move/CMakeLists.txt tests/cmd-rule-move/main.cpp tests/cmd-rule-remove/CMakeLists.txt tests/cmd-rule-remove/main.cpp tests/cmd-server-connect/CMakeLists.txt tests/cmd-server-connect/main.cpp tests/cmd-server-disconnect/CMakeLists.txt tests/cmd-server-disconnect/main.cpp tests/cmd-server-info/CMakeLists.txt tests/cmd-server-info/main.cpp tests/cmd-server-invite/CMakeLists.txt tests/cmd-server-invite/main.cpp tests/cmd-server-join/CMakeLists.txt tests/cmd-server-join/main.cpp tests/cmd-server-kick/CMakeLists.txt tests/cmd-server-kick/main.cpp tests/cmd-server-list/CMakeLists.txt tests/cmd-server-list/main.cpp tests/cmd-server-me/CMakeLists.txt tests/cmd-server-me/main.cpp tests/cmd-server-message/CMakeLists.txt tests/cmd-server-message/main.cpp tests/cmd-server-mode/CMakeLists.txt tests/cmd-server-mode/main.cpp tests/cmd-server-nick/CMakeLists.txt tests/cmd-server-nick/main.cpp tests/cmd-server-notice/CMakeLists.txt tests/cmd-server-notice/main.cpp tests/cmd-server-part/CMakeLists.txt tests/cmd-server-part/main.cpp tests/cmd-server-reconnect/CMakeLists.txt tests/cmd-server-reconnect/main.cpp tests/cmd-server-topic/CMakeLists.txt tests/cmd-server-topic/main.cpp tests/directory-jsapi/CMakeLists.txt tests/directory-jsapi/main.cpp tests/elapsedtimer-jsapi/CMakeLists.txt tests/elapsedtimer-jsapi/empty.js tests/elapsedtimer-jsapi/main.cpp tests/file-jsapi/CMakeLists.txt tests/file-jsapi/empty.js tests/file-jsapi/main.cpp tests/irccd-jsapi/CMakeLists.txt tests/irccd-jsapi/empty.js tests/irccd-jsapi/main.cpp tests/js-directory/CMakeLists.txt tests/js-directory/main.cpp tests/js-elapsedtimer/CMakeLists.txt tests/js-elapsedtimer/empty.js tests/js-elapsedtimer/main.cpp tests/js-file/CMakeLists.txt tests/js-file/empty.js tests/js-file/main.cpp tests/js-irccd/CMakeLists.txt tests/js-irccd/empty.js tests/js-irccd/main.cpp tests/js-logger/CMakeLists.txt tests/js-logger/empty.js tests/js-logger/main.cpp tests/js-system/CMakeLists.txt tests/js-system/empty.js tests/js-system/main.cpp tests/js-timer/CMakeLists.txt tests/js-timer/main.cpp tests/js-timer/timer.js tests/js-unicode/CMakeLists.txt tests/js-unicode/empty.js tests/js-unicode/main.cpp tests/js-util/CMakeLists.txt tests/js-util/empty.js tests/js-util/main.cpp tests/logger-jsapi/CMakeLists.txt tests/logger-jsapi/empty.js tests/logger-jsapi/main.cpp tests/plugin-config-command/CMakeLists.txt tests/plugin-config-command/main.cpp tests/plugin-info-command/CMakeLists.txt tests/plugin-info-command/main.cpp tests/plugin-list-command/CMakeLists.txt tests/plugin-list-command/main.cpp tests/plugin-load-command/CMakeLists.txt tests/plugin-load-command/main.cpp tests/plugin-reload-command/CMakeLists.txt tests/plugin-reload-command/main.cpp tests/plugin-unload-command/CMakeLists.txt tests/plugin-unload-command/main.cpp tests/rule-add-command/CMakeLists.txt tests/rule-add-command/main.cpp tests/rule-edit-command/CMakeLists.txt tests/rule-edit-command/main.cpp tests/rule-info-command/CMakeLists.txt tests/rule-info-command/main.cpp tests/rule-list-command/CMakeLists.txt tests/rule-list-command/main.cpp tests/rule-move-command/CMakeLists.txt tests/rule-move-command/main.cpp tests/rule-remove-command/CMakeLists.txt tests/rule-remove-command/main.cpp tests/server-connect-command/CMakeLists.txt tests/server-connect-command/main.cpp tests/server-disconnect-command/CMakeLists.txt tests/server-disconnect-command/main.cpp tests/server-info-command/CMakeLists.txt tests/server-info-command/main.cpp tests/server-invite-command/CMakeLists.txt tests/server-invite-command/main.cpp tests/server-join-command/CMakeLists.txt tests/server-join-command/main.cpp tests/server-kick-command/CMakeLists.txt tests/server-kick-command/main.cpp tests/server-list-command/CMakeLists.txt tests/server-list-command/main.cpp tests/server-me-command/CMakeLists.txt tests/server-me-command/main.cpp tests/server-message-command/CMakeLists.txt tests/server-message-command/main.cpp tests/server-mode-command/CMakeLists.txt tests/server-mode-command/main.cpp tests/server-nick-command/CMakeLists.txt tests/server-nick-command/main.cpp tests/server-notice-command/CMakeLists.txt tests/server-notice-command/main.cpp tests/server-part-command/CMakeLists.txt tests/server-part-command/main.cpp tests/server-reconnect-command/CMakeLists.txt tests/server-reconnect-command/main.cpp tests/server-topic-command/CMakeLists.txt tests/server-topic-command/main.cpp tests/system-jsapi/CMakeLists.txt tests/system-jsapi/empty.js tests/system-jsapi/main.cpp tests/timer-jsapi/CMakeLists.txt tests/timer-jsapi/main.cpp tests/timer-jsapi/timer.js tests/unicode-jsapi/CMakeLists.txt tests/unicode-jsapi/empty.js tests/unicode-jsapi/main.cpp tests/util-jsapi/CMakeLists.txt tests/util-jsapi/empty.js tests/util-jsapi/main.cpp
diffstat 147 files changed, 7192 insertions(+), 7210 deletions(-) [+]
line wrap: on
line diff
--- a/tests/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ b/tests/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -20,54 +20,52 @@
 project(tests)
 
 if (WITH_TESTS)
-    add_subdirectory(cmd-plugin-config)
-    add_subdirectory(cmd-plugin-info)
-    add_subdirectory(cmd-plugin-list)
-    add_subdirectory(cmd-plugin-load)
-    add_subdirectory(cmd-plugin-reload)
-    add_subdirectory(cmd-plugin-unload)
-    add_subdirectory(cmd-rule-add)
-    add_subdirectory(cmd-rule-edit)
-    add_subdirectory(cmd-rule-info)
-    add_subdirectory(cmd-rule-list)
-    add_subdirectory(cmd-rule-move)
-    add_subdirectory(cmd-rule-remove)
-    add_subdirectory(cmd-server-connect)
-    add_subdirectory(cmd-server-disconnect)
-    add_subdirectory(cmd-server-info)
-    add_subdirectory(cmd-server-invite)
-    add_subdirectory(cmd-server-join)
-    add_subdirectory(cmd-server-kick)
-    add_subdirectory(cmd-server-list)
-    add_subdirectory(cmd-server-me)
-    add_subdirectory(cmd-server-message)
-    add_subdirectory(cmd-server-mode)
-    add_subdirectory(cmd-server-nick)
-    add_subdirectory(cmd-server-notice)
-    add_subdirectory(cmd-server-part)
-    add_subdirectory(cmd-server-reconnect)
-    add_subdirectory(cmd-server-topic)
-#    add_subdirectory(dynlib_plugin)
+    add_subdirectory(plugin-config-command)
+    add_subdirectory(plugin-info-command)
+    add_subdirectory(plugin-list-command)
+    add_subdirectory(plugin-load-command)
+    add_subdirectory(plugin-reload-command)
+    add_subdirectory(plugin-unload-command)
+    add_subdirectory(rule-add-command)
+    add_subdirectory(rule-edit-command)
+    add_subdirectory(rule-info-command)
+    add_subdirectory(rule-list-command)
+    add_subdirectory(rule-move-command)
+    add_subdirectory(rule-remove-command)
+    add_subdirectory(server-connect-command)
+    add_subdirectory(server-disconnect-command)
+    add_subdirectory(server-info-command)
+    add_subdirectory(server-invite-command)
+    add_subdirectory(server-join-command)
+    add_subdirectory(server-kick-command)
+    add_subdirectory(server-list-command)
+    add_subdirectory(server-me-command)
+    add_subdirectory(server-message-command)
+    add_subdirectory(server-mode-command)
+    add_subdirectory(server-nick-command)
+    add_subdirectory(server-notice-command)
+    add_subdirectory(server-part-command)
+    add_subdirectory(server-reconnect-command)
+    add_subdirectory(server-topic-command)
 
     add_subdirectory(irc)
     add_subdirectory(logger)
     add_subdirectory(rules)
     add_subdirectory(util)
 
-    # JS API
     if (HAVE_JS)
-        # Javascript plugin object.
         add_subdirectory(js-plugin)
 
-        add_subdirectory(js-elapsedtimer)
-        add_subdirectory(js-directory)
-        add_subdirectory(js-file)
-        add_subdirectory(js-irccd)
-        add_subdirectory(js-logger)
-        add_subdirectory(js-system)
-        add_subdirectory(js-timer)
-        add_subdirectory(js-unicode)
-        add_subdirectory(js-util)
+        add_subdirectory(elapsedtimer-jsapi)
+        add_subdirectory(directory-jsapi)
+        add_subdirectory(file-jsapi)
+        add_subdirectory(irccd-jsapi)
+        add_subdirectory(logger-jsapi)
+        add_subdirectory(system-jsapi)
+        add_subdirectory(timer-jsapi)
+        add_subdirectory(unicode-jsapi)
+        add_subdirectory(util-jsapi)
+
         add_subdirectory(plugin-ask)
         add_subdirectory(plugin-auth)
         add_subdirectory(plugin-hangman)
--- a/tests/cmd-plugin-config/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-plugin-config
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-plugin-config/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,157 +0,0 @@
-/*
- * main.cpp -- test plugin-config remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "plugin-config"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class custom_plugin : public plugin {
-public:
-    plugin_config config_;
-
-    custom_plugin(std::string name = "test")
-        : plugin(std::move(name), "")
-    {
-    }
-
-    plugin_config config() override
-    {
-        return config_;
-    }
-
-    void set_config(plugin_config config) override
-    {
-        config_ = std::move(config);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(plugin_config_test_suite, command_test<plugin_config_command>)
-
-BOOST_AUTO_TEST_CASE(set)
-{
-    daemon_->plugins().add(std::make_unique<custom_plugin>("test"));
-    ctl_->send({
-        { "command",    "plugin-config" },
-        { "plugin",     "test"          },
-        { "variable",   "verbosy"       },
-        { "value",      "falsy"         }
-    });
-
-    wait_for([&] {
-        return !daemon_->plugins().require("test")->config().empty();
-    });
-
-    auto config = daemon_->plugins().require("test")->config();
-
-    BOOST_TEST(!config.empty());
-    BOOST_TEST(config["verbosy"] == "falsy");
-}
-
-BOOST_AUTO_TEST_CASE(get)
-{
-    auto plugin = std::make_unique<custom_plugin>("test");
-    auto json = nlohmann::json();
-
-    plugin->set_config({
-        { "x1", "10" },
-        { "x2", "20" }
-    });
-
-    daemon_->plugins().add(std::move(plugin));
-    ctl_->send({
-        { "command", "plugin-config" },
-        { "plugin", "test" },
-        { "variable", "x1" }
-    });
-    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"].is_null());
-}
-
-BOOST_AUTO_TEST_CASE(getall)
-{
-    auto plugin = std::make_unique<custom_plugin>("test");
-    auto json = nlohmann::json();
-
-    plugin->set_config({
-        { "x1", "10" },
-        { "x2", "20" }
-    });
-
-    daemon_->plugins().add(std::move(plugin));
-    ctl_->send({
-        { "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_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "plugin-config" },
-        { "plugin",     "unknown"       }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == plugin_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-plugin-info/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-plugin-info
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-plugin-info/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,86 +0,0 @@
-/*
- * main.cpp -- test plugin-info remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "plugin-info"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-BOOST_FIXTURE_TEST_SUITE(plugin_info_test_suite, command_test<plugin_info_command>)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    auto plg = std::make_unique<plugin>("test", "");
-    auto response = nlohmann::json();
-
-    plg->set_author("Francis Beaugrand");
-    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));
-    ctl_->recv([&] (auto, auto msg) {
-        response = std::move(msg);
-    });
-    ctl_->send({
-        { "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_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "plugin-info"   },
-        { "plugin",     "unknown"       }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == plugin_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-plugin-list/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-plugin-list
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-plugin-list/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * main.cpp -- test plugin-list remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "plugin-list"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-class plugin_list_test : public command_test<plugin_list_command> {
-public:
-    plugin_list_test()
-    {
-        daemon_->plugins().add(std::make_unique<plugin>("t1", ""));
-        daemon_->plugins().add(std::make_unique<plugin>("t2", ""));
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(plugin_list_test_suite, plugin_list_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    auto response = nlohmann::json();
-
-    ctl_->send({{"command", "plugin-list"}});
-    ctl_->recv([&] (auto, auto message) {
-        response = message;
-    });
-
-    wait_for([&] () {
-        return response.is_object();
-    });
-
-    BOOST_TEST(response.is_object());
-    BOOST_TEST("t1", response["list"][0]);
-    BOOST_TEST("t2", response["list"][1]);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-plugin-load/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-plugin-load
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-plugin-load/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/*
- * main.cpp -- test plugin-load remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "plugin-load"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class custom_loader : public plugin_loader {
-public:
-    std::shared_ptr<plugin> open(const std::string&,
-                                 const std::string&) noexcept override
-    {
-        return nullptr;
-    }
-
-    std::shared_ptr<plugin> find(const std::string& id) noexcept override
-    {
-        class broken : public plugin {
-        public:
-            using plugin::plugin;
-
-            void on_load(irccd&) override
-            {
-                throw std::runtime_error("broken");
-            }
-        };
-
-        /*
-         * The 'magic' plugin will be created for the unit tests, all other
-         * plugins will return null.
-         */
-        if (id == "magic")
-            return std::make_unique<plugin>(id, "");
-        if (id == "broken")
-            return std::make_unique<broken>(id, "");
-
-        return nullptr;
-    }
-};
-
-class plugin_load_test : public command_test<plugin_load_command> {
-public:
-    plugin_load_test()
-    {
-        daemon_->plugins().add_loader(std::make_unique<custom_loader>());
-        daemon_->plugins().add(std::make_unique<plugin>("already", ""));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(plugin_load_test_suite, plugin_load_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "plugin-load"   },
-        { "plugin",     "magic"         }
-    });
-
-    wait_for([&] () {
-        return daemon_->plugins().has("magic");
-    });
-
-    BOOST_TEST(!daemon_->plugins().list().empty());
-    BOOST_TEST(daemon_->plugins().has("magic"));
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "plugin-load"   },
-        { "plugin",     "unknown"       }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == plugin_error::not_found);
-}
-
-BOOST_AUTO_TEST_CASE(already_exists)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "plugin-load"   },
-        { "plugin",     "already"       }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == plugin_error::already_exists);
-}
-
-BOOST_AUTO_TEST_CASE(exec_error)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "plugin-load"   },
-        { "plugin",     "broken"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == plugin_error::exec_error);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-plugin-reload/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-plugin-reload
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-plugin-reload/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-/*
- * main.cpp -- test plugin-reload remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "plugin-reload"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class custom_plugin : public plugin {
-public:
-    bool reloaded{false};
-
-    custom_plugin()
-        : plugin("test", "")
-    {
-    }
-
-    void on_reload(irccd&) override
-    {
-        reloaded = true;
-    }
-};
-
-class broken_plugin : public plugin {
-public:
-    using plugin::plugin;
-
-    void on_reload(irccd&) override
-    {
-        throw std::runtime_error("broken");
-    }
-};
-
-class plugin_reload_test : public command_test<plugin_reload_command> {
-protected:
-    std::shared_ptr<custom_plugin> plugin_;
-
-    plugin_reload_test()
-        : plugin_(std::make_shared<custom_plugin>())
-    {
-        daemon_->plugins().add(plugin_);
-        daemon_->plugins().add(std::make_unique<broken_plugin>("broken", ""));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(plugin_reload_test_suite, plugin_reload_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "plugin-reload" },
-        { "plugin",     "test"          }
-    });
-
-    wait_for([&] () {
-        return plugin_->reloaded;
-    });
-
-    BOOST_TEST(plugin_->reloaded);
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "plugin-reload" },
-        { "plugin",     "unknown"       }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == plugin_error::not_found);
-}
-
-BOOST_AUTO_TEST_CASE(exec_error)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "plugin-reload" },
-        { "plugin",     "broken"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == plugin_error::exec_error);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-plugin-unload/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-plugin-unload
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-plugin-unload/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-/*
- * main.cpp -- test plugin-unload remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "plugin-unload"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class custom_plugin : public plugin {
-public:
-    bool unloaded{false};
-
-    custom_plugin()
-        : plugin("test", "")
-    {
-    }
-
-    void on_unload(irccd &) override
-    {
-        unloaded = true;
-    }
-};
-
-class broken_plugin : public plugin {
-public:
-    using plugin::plugin;
-
-    void on_unload(irccd&) override
-    {
-        throw std::runtime_error("broken");
-    }
-};
-
-class plugin_unload_test : public command_test<plugin_unload_command> {
-protected:
-    std::shared_ptr<custom_plugin> plugin_;
-
-    plugin_unload_test()
-        : plugin_(std::make_shared<custom_plugin>())
-    {
-        daemon_->plugins().add(plugin_);
-        daemon_->plugins().add(std::make_unique<broken_plugin>("broken", ""));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(plugin_unload_test_suite, plugin_unload_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "plugin-unload" },
-        { "plugin",     "test"          }
-    });
-
-    wait_for([&] () {
-        return plugin_->unloaded;
-    });
-
-    BOOST_TEST(plugin_->unloaded);
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "plugin-unload" },
-        { "plugin",     "unknown"       }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == plugin_error::not_found);
-}
-
-BOOST_AUTO_TEST_CASE(exec_error)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "plugin-unload" },
-        { "plugin",     "broken"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == plugin_error::exec_error);
-    BOOST_ASSERT(!daemon_->plugins().has("broken"));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-rule-add/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-rule-add
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-rule-add/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,205 +0,0 @@
-/*
- * main.cpp -- test rule-add remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "rule-add"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/json_util.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class rule_add_test : public command_test<rule_add_command> {
-public:
-    rule_add_test()
-    {
-        daemon_->commands().add(std::make_unique<rule_list_command>());
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(rule_add_test_suite, rule_add_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "command",    "rule-add"          },
-        { "servers",    { "s1", "s2" }      },
-        { "channels",   { "c1", "c2" }      },
-        { "plugins",    { "p1", "p2" }      },
-        { "events",     { "onMessage" }     },
-        { "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({{"command", "rule-list"}});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    auto servers = result["list"][0]["servers"];
-    auto channels = result["list"][0]["channels"];
-    auto plugins = result["list"][0]["plugins"];
-    auto events = result["list"][0]["events"];
-
-    BOOST_TEST(json_util::contains(servers, "s1"));
-    BOOST_TEST(json_util::contains(servers, "s2"));
-    BOOST_TEST(json_util::contains(channels, "c1"));
-    BOOST_TEST(json_util::contains(channels, "c2"));
-    BOOST_TEST(json_util::contains(plugins, "p1"));
-    BOOST_TEST(json_util::contains(plugins, "p2"));
-    BOOST_TEST(json_util::contains(events, "onMessage"));
-    BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
-}
-
-BOOST_AUTO_TEST_CASE(append)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "command",    "rule-add"          },
-        { "servers",    { "s1" }            },
-        { "channels",   { "c1" }            },
-        { "plugins",    { "p1" }            },
-        { "events",     { "onMessage" }     },
-        { "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({
-        { "command",    "rule-add"          },
-        { "servers",    { "s2" }            },
-        { "channels",   { "c2" }            },
-        { "plugins",    { "p2" }            },
-        { "events",     { "onMessage" }     },
-        { "action",     "drop"              },
-        { "index",      1                   }
-    });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({{"command", "rule-list"}});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-    BOOST_TEST(result["list"].size() == 2U);
-
-    // Rule 0.
-    {
-        auto servers = result["list"][0]["servers"];
-        auto channels = result["list"][0]["channels"];
-        auto plugins = result["list"][0]["plugins"];
-        auto events = result["list"][0]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s1"));
-        BOOST_TEST(json_util::contains(channels, "c1"));
-        BOOST_TEST(json_util::contains(plugins, "p1"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
-    }
-
-    // Rule 1.
-    {
-        auto servers = result["list"][1]["servers"];
-        auto channels = result["list"][1]["channels"];
-        auto plugins = result["list"][1]["plugins"];
-        auto events = result["list"][1]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s2"));
-        BOOST_TEST(json_util::contains(channels, "c2"));
-        BOOST_TEST(json_util::contains(plugins, "p2"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "drop");
-    }
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_action)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-add"  },
-        { "action",     "unknown"   }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_action);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-rule-edit/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-rule-edit
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-rule-edit/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,631 +0,0 @@
-/*
- * main.cpp -- test rule-edit remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "rule-edit"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/json_util.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class rule_edit_test : public command_test<rule_edit_command> {
-public:
-    rule_edit_test()
-    {
-        daemon_->commands().add(std::make_unique<rule_info_command>());
-        daemon_->rules().add(rule(
-            { "s1", "s2" },
-            { "c1", "c2" },
-            { "o1", "o2" },
-            { "p1", "p2" },
-            { "onMessage", "onCommand" },
-            rule::action_type::drop
-        ));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(rule_edit_test_suite, rule_edit_test)
-
-BOOST_AUTO_TEST_CASE(add_server)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(add_channel)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(add_plugin)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(add_event)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(add_event_and_server)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(change_action)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(remove_server)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(remove_channel)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(remove_plugin)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(remove_event)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_CASE(remove_event_and_server)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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({
-        { "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_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_index_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-edit" },
-        { "index",      -100        },
-        { "action",     "drop"      }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-edit" },
-        { "index",      100         },
-        { "action",     "drop"      }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_3)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-edit" },
-        { "index",      "notaint"   },
-        { "action",     "drop"      }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_action)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-edit" },
-        { "index",      0           },
-        { "action",     "unknown"   }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_action);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-rule-info/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-rule-info
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-rule-info/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,157 +0,0 @@
-/*
- * main.cpp -- test rule-info remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "rule-info"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/json_util.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class rule_info_test : public command_test<rule_info_command> {
-public:
-    rule_info_test()
-    {
-        daemon_->rules().add(rule(
-            { "s1", "s2" },
-            { "c1", "c2" },
-            { "o1", "o2" },
-            { "p1", "p2" },
-            { "onMessage", "onCommand" },
-            rule::action_type::drop
-        ));
-        daemon_->rules().add(rule(
-            { "s1", },
-            { "c1", },
-            { "o1", },
-            { "p1", },
-            { "onMessage", },
-            rule::action_type::accept
-        ));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(rule_info_test_suite, rule_info_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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"];
-
-    BOOST_TEST(json_util::contains(servers, "s1"));
-    BOOST_TEST(json_util::contains(servers, "s2"));
-    BOOST_TEST(json_util::contains(channels, "c1"));
-    BOOST_TEST(json_util::contains(channels, "c2"));
-    BOOST_TEST(json_util::contains(plugins, "p1"));
-    BOOST_TEST(json_util::contains(plugins, "p2"));
-    BOOST_TEST(json_util::contains(events, "onMessage"));
-    BOOST_TEST(json_util::contains(events, "onCommand"));
-    BOOST_TEST(result["action"].get<std::string>() == "drop");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_index_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-info" },
-        { "index",      -100        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-info" },
-        { "index",      100         }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_3)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-info" },
-        { "index",      "notaint"   }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-rule-list/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-rule-list
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-rule-list/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-/*
- * main.cpp -- test rule-list remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "rule-list"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/json_util.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class rule_list_test : public command_test<rule_list_command> {
-public:
-    rule_list_test()
-    {
-        daemon_->rules().add(rule(
-            { "s1", "s2" },
-            { "c1", "c2" },
-            { "o1", "o2" },
-            { "p1", "p2" },
-            { "onMessage", "onCommand" },
-            rule::action_type::drop
-        ));
-        daemon_->rules().add(rule(
-            { "s1", },
-            { "c1", },
-            { "o1", },
-            { "p1", },
-            { "onMessage", },
-            rule::action_type::accept
-        ));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(rule_list_test_suite, rule_list_test)
-
-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();
-    });
-
-    BOOST_TEST(result.is_object());
-    BOOST_TEST(result["list"].is_array());
-    BOOST_TEST(result["list"].size() == 2U);
-
-    // Rule 0.
-    {
-        auto servers = result["list"][0]["servers"];
-        auto channels = result["list"][0]["channels"];
-        auto plugins = result["list"][0]["plugins"];
-        auto events = result["list"][0]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s1"));
-        BOOST_TEST(json_util::contains(servers, "s2"));
-        BOOST_TEST(json_util::contains(channels, "c1"));
-        BOOST_TEST(json_util::contains(channels, "c2"));
-        BOOST_TEST(json_util::contains(plugins, "p1"));
-        BOOST_TEST(json_util::contains(plugins, "p2"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(json_util::contains(events, "onCommand"));
-        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "drop");
-    }
-
-    // Rule 1.
-    {
-        auto servers = result["list"][1]["servers"];
-        auto channels = result["list"][1]["channels"];
-        auto plugins = result["list"][1]["plugins"];
-        auto events = result["list"][1]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s1"));
-        BOOST_TEST(json_util::contains(channels, "c1"));
-        BOOST_TEST(json_util::contains(plugins, "p1"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
-    }
-}
-
-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();
-    });
-
-    BOOST_TEST(result.is_object());
-    BOOST_TEST(result["list"].is_array());
-    BOOST_TEST(result["list"].empty());
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-rule-move/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-rule-move
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-rule-move/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,471 +0,0 @@
-/*
- * main.cpp -- test rule-move remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "rule-move"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/json_util.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class rule_move_test : public command_test<rule_move_command> {
-public:
-    rule_move_test()
-    {
-        daemon_->commands().add(std::make_unique<rule_list_command>());
-        daemon_->rules().add(rule(
-            { "s0" },
-            { "c0" },
-            { "o0" },
-            { "p0" },
-            { "onMessage" },
-            rule::action_type::drop
-        ));
-        daemon_->rules().add(rule(
-            { "s1", },
-            { "c1", },
-            { "o1", },
-            { "p1", },
-            { "onMessage", },
-            rule::action_type::accept
-        ));
-        daemon_->rules().add(rule(
-            { "s2", },
-            { "c2", },
-            { "o2", },
-            { "p2", },
-            { "onMessage", },
-            rule::action_type::accept
-        ));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(rule_move_test_suite, rule_move_test)
-
-BOOST_AUTO_TEST_CASE(backward)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "command",    "rule-move" },
-        { "from",       2           },
-        { "to",         0           }
-    });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    // Rule 2.
-    {
-        auto servers = result["list"][0]["servers"];
-        auto channels = result["list"][0]["channels"];
-        auto plugins = result["list"][0]["plugins"];
-        auto events = result["list"][0]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s2"));
-        BOOST_TEST(json_util::contains(channels, "c2"));
-        BOOST_TEST(json_util::contains(plugins, "p2"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
-    }
-
-    // Rule 0.
-    {
-        auto servers = result["list"][1]["servers"];
-        auto channels = result["list"][1]["channels"];
-        auto plugins = result["list"][1]["plugins"];
-        auto events = result["list"][1]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s0"));
-        BOOST_TEST(json_util::contains(channels, "c0"));
-        BOOST_TEST(json_util::contains(plugins, "p0"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "drop");
-    }
-
-    // Rule 1.
-    {
-        auto servers = result["list"][2]["servers"];
-        auto channels = result["list"][2]["channels"];
-        auto plugins = result["list"][2]["plugins"];
-        auto events = result["list"][2]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s1"));
-        BOOST_TEST(json_util::contains(channels, "c1"));
-        BOOST_TEST(json_util::contains(plugins, "p1"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "accept");
-    }
-}
-
-BOOST_AUTO_TEST_CASE(upward)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "command",    "rule-move" },
-        { "from",       0           },
-        { "to",         2           }
-    });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    // Rule 1.
-    {
-        auto servers = result["list"][0]["servers"];
-        auto channels = result["list"][0]["channels"];
-        auto plugins = result["list"][0]["plugins"];
-        auto events = result["list"][0]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s1"));
-        BOOST_TEST(json_util::contains(channels, "c1"));
-        BOOST_TEST(json_util::contains(plugins, "p1"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
-    }
-
-    // Rule 2.
-    {
-        auto servers = result["list"][1]["servers"];
-        auto channels = result["list"][1]["channels"];
-        auto plugins = result["list"][1]["plugins"];
-        auto events = result["list"][1]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s2"));
-        BOOST_TEST(json_util::contains(channels, "c2"));
-        BOOST_TEST(json_util::contains(plugins, "p2"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
-    }
-
-    // Rule 0.
-    {
-        auto servers = result["list"][2]["servers"];
-        auto channels = result["list"][2]["channels"];
-        auto plugins = result["list"][2]["plugins"];
-        auto events = result["list"][2]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s0"));
-        BOOST_TEST(json_util::contains(channels, "c0"));
-        BOOST_TEST(json_util::contains(plugins, "p0"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "drop");
-    }
-}
-
-BOOST_AUTO_TEST_CASE(same)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "command",    "rule-move" },
-        { "from",       1           },
-        { "to",         1           }
-    });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    // Rule 0.
-    {
-        auto servers = result["list"][0]["servers"];
-        auto channels = result["list"][0]["channels"];
-        auto plugins = result["list"][0]["plugins"];
-        auto events = result["list"][0]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s0"));
-        BOOST_TEST(json_util::contains(channels, "c0"));
-        BOOST_TEST(json_util::contains(plugins, "p0"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "drop");
-    }
-
-    // Rule 1.
-    {
-        auto servers = result["list"][1]["servers"];
-        auto channels = result["list"][1]["channels"];
-        auto plugins = result["list"][1]["plugins"];
-        auto events = result["list"][1]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s1"));
-        BOOST_TEST(json_util::contains(channels, "c1"));
-        BOOST_TEST(json_util::contains(plugins, "p1"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
-    }
-
-    // Rule 2.
-    {
-        auto servers = result["list"][2]["servers"];
-        auto channels = result["list"][2]["channels"];
-        auto plugins = result["list"][2]["plugins"];
-        auto events = result["list"][2]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s2"));
-        BOOST_TEST(json_util::contains(channels, "c2"));
-        BOOST_TEST(json_util::contains(plugins, "p2"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "accept");
-    }
-}
-
-BOOST_AUTO_TEST_CASE(beyond)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "command",    "rule-move" },
-        { "from",       0           },
-        { "to",         123         }
-    });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    // Rule 1.
-    {
-        auto servers = result["list"][0]["servers"];
-        auto channels = result["list"][0]["channels"];
-        auto plugins = result["list"][0]["plugins"];
-        auto events = result["list"][0]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s1"));
-        BOOST_TEST(json_util::contains(channels, "c1"));
-        BOOST_TEST(json_util::contains(plugins, "p1"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
-    }
-
-    // Rule 2.
-    {
-        auto servers = result["list"][1]["servers"];
-        auto channels = result["list"][1]["channels"];
-        auto plugins = result["list"][1]["plugins"];
-        auto events = result["list"][1]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s2"));
-        BOOST_TEST(json_util::contains(channels, "c2"));
-        BOOST_TEST(json_util::contains(plugins, "p2"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
-    }
-
-    // Rule 0.
-    {
-        auto servers = result["list"][2]["servers"];
-        auto channels = result["list"][2]["channels"];
-        auto plugins = result["list"][2]["plugins"];
-        auto events = result["list"][2]["events"];
-
-        BOOST_TEST(json_util::contains(servers, "s0"));
-        BOOST_TEST(json_util::contains(channels, "c0"));
-        BOOST_TEST(json_util::contains(plugins, "p0"));
-        BOOST_TEST(json_util::contains(events, "onMessage"));
-        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "drop");
-    }
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_index_1_from)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-move" },
-        { "from",       -100        },
-        { "to",         0           }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_1_to)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-move" },
-        { "from",       0           },
-        { "to",         -100        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_2_from)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-move" },
-        { "from",       100         },
-        { "to",         0           }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_3_from)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-move" },
-        { "from",       "notaint"   },
-        { "to",         0           }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_3_to)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-move" },
-        { "from",       0           },
-        { "to",         "notaint"   }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-rule-remove/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-rule-remove
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-rule-remove/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,193 +0,0 @@
-/*
- * main.cpp -- test rule-remove remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "rule-remove"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/json_util.hpp>
-
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
-
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class rule_remove_test : public command_test<rule_remove_command> {
-public:
-    rule_remove_test()
-    {
-        daemon_->commands().add(std::make_unique<rule_list_command>());
-        daemon_->rules().add(rule(
-            { "s1", "s2" },
-            { "c1", "c2" },
-            { "o1", "o2" },
-            { "p1", "p2" },
-            { "onMessage", "onCommand" },
-            rule::action_type::drop
-        ));
-        daemon_->rules().add(rule(
-            { "s1", },
-            { "c1", },
-            { "o1", },
-            { "p1", },
-            { "onMessage", },
-            rule::action_type::accept
-        ));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(rule_remove_test_suite, rule_remove_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "command",    "rule-remove"   },
-        { "index",      1               }
-    });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-
-    result = nullptr;
-    ctl_->send({{ "command", "rule-list" }});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result["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"];
-
-    BOOST_TEST(json_util::contains(servers, "s1"));
-    BOOST_TEST(json_util::contains(servers, "s2"));
-    BOOST_TEST(json_util::contains(channels, "c1"));
-    BOOST_TEST(json_util::contains(channels, "c2"));
-    BOOST_TEST(json_util::contains(plugins, "p1"));
-    BOOST_TEST(json_util::contains(plugins, "p2"));
-    BOOST_TEST(json_util::contains(events, "onMessage"));
-    BOOST_TEST(json_util::contains(events, "onCommand"));
-    BOOST_TEST(result["list"][0]["action"].get<std::string>() == "drop");
-}
-
-BOOST_AUTO_TEST_CASE(empty)
-{
-    nlohmann::json result;
-
-    daemon_->rules().remove(0);
-    daemon_->rules().remove(0);
-
-    ctl_->send({
-        { "command",    "rule-remove"   },
-        { "index",      1               }
-    });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result.is_object());
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_index_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-remove"   },
-        { "index",      -100            }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-remove"   },
-        { "index",      100             }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_index_3)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "rule-remove"   },
-        { "index",      "notaint"       }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == rule_error::invalid_index);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-connect/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-connect
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-connect/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,300 +0,0 @@
-/*
- * main.cpp -- test server-connect remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-connect"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-BOOST_FIXTURE_TEST_SUITE(server_connect_test_suite, command_test<server_connect_command>)
-
-BOOST_AUTO_TEST_CASE(minimal)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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");
-
-    BOOST_TEST(s);
-    BOOST_TEST(s->name() == "local");
-    BOOST_TEST(s->host() == "irc.example.org");
-    BOOST_TEST(s->port() == 6667U);
-}
-
-#if defined(HAVE_SSL)
-
-BOOST_AUTO_TEST_CASE(full)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       "local2"            },
-        { "host",       "irc.example2.org"  },
-        { "password",   "nonono"            },
-        { "nickname",   "francis"           },
-        { "realname",   "the_francis"       },
-        { "username",   "frc"               },
-        { "ctcpVersion", "ultra bot"        },
-        { "commandChar", "::"               },
-        { "port",       18000               },
-        { "ssl",        true                },
-        { "sslVerify",  true                },
-        { "autoRejoin", true                },
-        { "joinInvite", true                }
-    });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    auto s = daemon_->servers().get("local2");
-
-    BOOST_TEST(s);
-    BOOST_TEST(s->name() == "local2");
-    BOOST_TEST(s->host() == "irc.example2.org");
-    BOOST_TEST(s->port() == 18000U);
-    BOOST_TEST(s->password() == "nonono");
-    BOOST_TEST(s->nickname() == "francis");
-    BOOST_TEST(s->realname() == "the_francis");
-    BOOST_TEST(s->username() == "frc");
-    BOOST_TEST(s->command_char() == "::");
-    BOOST_TEST(s->ctcp_version() == "ultra bot");
-    BOOST_TEST(s->flags() & server::ssl);
-    BOOST_TEST(s->flags() & server::ssl_verify);
-    BOOST_TEST(s->flags() & server::auto_rejoin);
-    BOOST_TEST(s->flags() & server::join_invite);
-}
-
-#endif // !HAVE_SSL
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(already_exists)
-{
-    boost::system::error_code result;
-
-    daemon_->servers().add(std::make_unique<journal_server>(service_, "local"));
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       "local"             },
-        { "host",       "127.0.0.1"         }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::already_exists);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_hostname_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       "new"               },
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_hostname);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_hostname_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       "new"               },
-        { "host",       123456              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_hostname);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       ""                  },
-        { "host",       "127.0.0.1"         }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       123456              },
-        { "host",       "127.0.0.1"         }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_port_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       "new"               },
-        { "host",       "127.0.0.1"         },
-        { "port",       "notaint"           }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_port_number);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_port_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       "new"               },
-        { "host",       "127.0.0.1"         },
-        { "port",       -123                }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_port_number);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_port_3)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       "new"               },
-        { "host",       "127.0.0.1"         },
-        { "port",       1000000             }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_port_number);
-}
-
-#if !defined(HAVE_SSL)
-
-BOOST_AUTO_TEST_CASE(ssl_disabled)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-connect"    },
-        { "name",       "new"               },
-        { "host",       "127.0.0.1"         },
-        { "ssl",        true                }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::ssl_disabled);
-}
-
-#endif
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-disconnect/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-disconnect
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-disconnect/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-/*
- * main.cpp -- test server-disconnect remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-disconnect"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_disconnect_test : public command_test<server_disconnect_command> {
-protected:
-    server_disconnect_test()
-    {
-        daemon_->servers().add(std::make_unique<journal_server>(service_, "s1"));
-        daemon_->servers().add(std::make_unique<journal_server>(service_, "s2"));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_disconnect_test_suite, server_disconnect_test)
-
-BOOST_AUTO_TEST_CASE(one)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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(!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;
-    });
-
-    wait_for([&] () {
-        return result.is_object();
-    });
-
-    BOOST_TEST(result["command"].get<std::string>() == "server-disconnect");
-    BOOST_TEST(!daemon_->servers().has("s1"));
-    BOOST_TEST(!daemon_->servers().has("s2"));
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-disconnect" },
-        { "server",     123456              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-disconnect" },
-        { "server",     "unknown"           }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-info/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-info
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-server-info/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-/*
- * main.cpp -- test server-info remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-info"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-BOOST_FIXTURE_TEST_SUITE(server_info_test_suite, command_test<server_info_command>)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    auto server = std::make_unique<journal_server>(service_, "test");
-
-    server->set_host("example.org");
-    server->set_port(8765);
-    server->set_password("none");
-    server->set_nickname("pascal");
-    server->set_username("psc");
-    server->set_realname("Pascal le grand frere");
-    server->set_ctcp_version("yeah");
-    server->set_command_char("@");
-    server->set_reconnect_tries(80);
-    server->set_ping_timeout(20000);
-
-    nlohmann::json result;
-
-    daemon_->servers().add(std::move(server));
-    ctl_->send({
-        { "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_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-info"   },
-        { "server",     123456          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-info"   },
-        { "server",     ""              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-info"   },
-        { "server",     "unknown"       }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-invite/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-invite
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-invite/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,218 +0,0 @@
-/*
- * main.cpp -- test server-invite remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-invite"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_invite_test : public command_test<server_invite_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_invite_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_invite_test_suite, server_invite_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "server-invite"     },
-        { "server",     "test"              },
-        { "target",     "francis"           },
-        { "channel",    "#music"            }
-    });
-
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
-    auto cmd = server_->cqueue().back();
-
-    BOOST_TEST(cmd["command"].get<std::string>() == "invite");
-    BOOST_TEST(cmd["channel"].get<std::string>() == "#music");
-    BOOST_TEST(cmd["target"].get<std::string>() == "francis");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-invite" },
-        { "server",     123456          },
-        { "target",     "francis"       },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-invite" },
-        { "server",     ""              },
-        { "target",     "francis"       },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_nickname_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-invite" },
-        { "server",     "test"          },
-        { "target",     ""              },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_nickname);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_nickname_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-invite" },
-        { "server",     "test"          },
-        { "target",     123456          },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_nickname);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-invite" },
-        { "server",     "test"          },
-        { "target",     "jean"          },
-        { "channel",    ""              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-invite" },
-        { "server",     "test"          },
-        { "target",     "jean"          },
-        { "channel",    123456          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-invite" },
-        { "server",     "unknown"       },
-        { "target",     "francis"       },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-join/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-join
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-join/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,190 +0,0 @@
-/*
- * main.cpp -- test server-join remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-join"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_join_test : public command_test<server_join_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_join_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_join_test_suite, server_join_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "server-join"       },
-        { "server",     "test"              },
-        { "channel",    "#music"            },
-        { "password",   "plop"              }
-    });
-
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
-    auto cmd = server_->cqueue().back();
-
-    BOOST_TEST(cmd["command"].get<std::string>() == "join");
-    BOOST_TEST(cmd["channel"].get<std::string>() == "#music");
-    BOOST_TEST(cmd["password"].get<std::string>() == "plop");
-}
-
-BOOST_AUTO_TEST_CASE(nopassword)
-{
-    ctl_->send({
-        { "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");
-    BOOST_TEST(cmd["channel"].get<std::string>() == "#music");
-    BOOST_TEST(cmd["password"].get<std::string>() == "");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-join"   },
-        { "server",     123456          },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-join"   },
-        { "server",     ""              },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-join"   },
-        { "server",     "test"          },
-        { "channel",    ""              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-join"   },
-        { "server",     "test"          },
-        { "channel",    123456          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-join"   },
-        { "server",     "unknown"       },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-kick/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-kick
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-kick/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,241 +0,0 @@
-/*
- * main.cpp -- test server-kick remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-kick"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_kick_test : public command_test<server_kick_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_kick_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_kick_test_suite, server_kick_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "server-kick"       },
-        { "server",     "test"              },
-        { "target",     "francis"           },
-        { "channel",    "#staff"            },
-        { "reason",     "too noisy"         }
-    });
-
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
-    auto cmd = server_->cqueue().back();
-
-    BOOST_TEST(cmd["command"].get<std::string>() == "kick");
-    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
-    BOOST_TEST(cmd["target"].get<std::string>() == "francis");
-    BOOST_TEST(cmd["reason"].get<std::string>() == "too noisy");
-}
-
-BOOST_AUTO_TEST_CASE(noreason)
-{
-    ctl_->send({
-        { "command",    "server-kick"       },
-        { "server",     "test"              },
-        { "target",     "francis"           },
-        { "channel",    "#staff"            }
-    });
-
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
-    auto cmd = server_->cqueue().back();
-
-    BOOST_TEST(cmd["command"].get<std::string>() == "kick");
-    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
-    BOOST_TEST(cmd["target"].get<std::string>() == "francis");
-    BOOST_TEST(cmd["reason"].get<std::string>() == "");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-kick"   },
-        { "server",     123456          },
-        { "target",     "francis"       },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-kick"   },
-        { "server",     ""              },
-        { "target",     "francis"       },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_nickname_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-kick"   },
-        { "server",     "test"          },
-        { "target",     ""              },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_nickname);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_nickname_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-kick"   },
-        { "server",     "test"          },
-        { "target",     123456          },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_nickname);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-kick"   },
-        { "server",     "test"          },
-        { "target",     "jean"          },
-        { "channel",    ""              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-kick"   },
-        { "server",     "test"          },
-        { "target",     "jean"          },
-        { "channel",    123456          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-kick"   },
-        { "server",     "unknown"       },
-        { "target",     "francis"       },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-list/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-list
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-list/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-/*
- * main.cpp -- test server-list remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-list"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_list_test : public command_test<server_list_command> {
-protected:
-    server_list_test()
-    {
-        daemon_->servers().add(std::make_unique<journal_server>(service_, "s1"));
-        daemon_->servers().add(std::make_unique<journal_server>(service_, "s2"));
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_list_test_suite, server_list_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    nlohmann::json result;
-
-    ctl_->send({{"command", "server-list"}});
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
-    });
-
-    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_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-me/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-me
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-me/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +0,0 @@
-/*
- * main.cpp -- test server-me remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-me"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_me_test : public command_test<server_me_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_me_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_me_test_suite, server_me_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "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");
-    BOOST_TEST(cmd["message"].get<std::string>() == "hello!");
-    BOOST_TEST(cmd["target"].get<std::string>() == "jean");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-me" },
-        { "server",     123456      },
-        { "target",     "#music"    },
-        { "message",    "hello!"    }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-me" },
-        { "server",     ""          },
-        { "target",     "#music"    },
-        { "message",    "hello!"    }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-me" },
-        { "server",     "test"      },
-        { "target",     ""          },
-        { "message",    "hello!"    }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-me" },
-        { "server",     "test"      },
-        { "target",     123456      },
-        { "message",    "hello!"    }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-me" },
-        { "server",     "unknown"   },
-        { "target",     "#music"    },
-        { "message",    "hello!"    }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-message/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-message
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-message/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +0,0 @@
-/*
- * main.cpp -- test server-message remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-message"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_message_test : public command_test<server_message_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_message_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_message_test_suite, server_message_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "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");
-    BOOST_TEST(cmd["message"].get<std::string>() == "plop!");
-    BOOST_TEST(cmd["target"].get<std::string>() == "#staff");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-message"    },
-        { "server",     123456              },
-        { "target",     "#music"            },
-        { "message",    "plop!"             }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-message"    },
-        { "server",     ""                  },
-        { "target",     "#music"            },
-        { "message",    "plop!"             }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-message"    },
-        { "server",     "test"              },
-        { "target",     ""                  },
-        { "message",    "plop!"             }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-message"    },
-        { "server",     "test"              },
-        { "target",     123456              },
-        { "message",    "plop!"             }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-message"    },
-        { "server",     "unknown"           },
-        { "target",     "#music"            },
-        { "message",    "plop!"             }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-mode/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-mode
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-mode/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,217 +0,0 @@
-/*
- * main.cpp -- test server-mode remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-mode"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_mode_test : public command_test<server_mode_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_mode_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_mode_test_suite, server_mode_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "server-mode"   },
-        { "server",     "test"          },
-        { "channel",    "#irccd"        },
-        { "mode",       "+t"            }
-    });
-
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
-    auto cmd = server_->cqueue().back();
-
-    BOOST_TEST(cmd["command"].get<std::string>() == "mode");
-    BOOST_TEST(cmd["channel"].get<std::string>() == "#irccd");
-    BOOST_TEST(cmd["mode"].get<std::string>() == "+t");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-mode"   },
-        { "server",     123456          },
-        { "channel",    "#music"        },
-        { "mode",       "+i"            }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-mode"   },
-        { "server",     ""              },
-        { "channel",    "#music"        },
-        { "mode",       "+i"            }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-mode"   },
-        { "server",     "test"          },
-        { "channel",    ""              },
-        { "mode",       "+i"            }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-mode"   },
-        { "server",     "test"          },
-        { "channel",    123456          },
-        { "mode",       "+i"            }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_mode_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-mode"   },
-        { "server",     "test"          },
-        { "channel",    "#music"        },
-        { "mode",       ""              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_mode);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_mode_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-mode"   },
-        { "server",     "test"          },
-        { "channel",    "#music"        },
-        { "mode",       123456          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_mode);
-}
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-mode"   },
-        { "server",     "unknown"       },
-        { "channel",    "#music"        },
-        { "mode",       "+i"            }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-nick/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-nick
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-nick/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,171 +0,0 @@
-/*
- * main.cpp -- test server-nick remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-nick"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_nick_test : public command_test<server_nick_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_nick_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_nick_test_suite, server_nick_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    nlohmann::json result;
-
-    ctl_->send({
-        { "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(server_->nickname() == "chris");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-nick"   },
-        { "server",     123456          },
-        { "nickname",   "chris"         }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-nick"   },
-        { "server",     ""              },
-        { "nickname",   "chris"         }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_nickname_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-nick"   },
-        { "server",     "test"          },
-        { "nickname",   ""              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_nickname);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_nickname_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-nick"   },
-        { "server",     "test"          },
-        { "nickname",   123456          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_nickname);
-}
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-nick"   },
-        { "server",     "unknown"       },
-        { "nickname",   "chris"         }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-notice/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-notice
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-notice/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +0,0 @@
-/*
- * main.cpp -- test server-notice remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-notice"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_notice_test : public command_test<server_notice_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_notice_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_notice_test_suite, server_notice_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "server-notice" },
-        { "server",     "test"          },
-        { "target",     "#staff"        },
-        { "message",    "quiet!"        }
-    });
-
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
-    auto cmd = server_->cqueue().back();
-
-    BOOST_TEST(cmd["command"].get<std::string>() == "notice");
-    BOOST_TEST(cmd["message"].get<std::string>() == "quiet!");
-    BOOST_TEST(cmd["target"].get<std::string>() == "#staff");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-notice" },
-        { "server",     123456          },
-        { "target",     "#music"        },
-        { "message",    "quiet!"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-notice" },
-        { "server",     ""              },
-        { "target",     "#music"        },
-        { "message",    "quiet!"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-notice" },
-        { "server",     "test"          },
-        { "target",     ""              },
-        { "message",    "quiet!"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-notice" },
-        { "server",     "test"          },
-        { "target",     123456          },
-        { "message",    "quiet!"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-notice" },
-        { "server",     "unknown"       },
-        { "target",     "#music"        },
-        { "message",    "quiet!"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-part/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-part
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-part/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,190 +0,0 @@
-/*
- * main.cpp -- test server-part remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright part and this permission part appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-part"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_part_test : public command_test<server_part_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_part_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_part_test_suite, server_part_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "server-part"   },
-        { "server",     "test"          },
-        { "channel",    "#staff"        },
-        { "reason",     "too noisy"     }
-    });
-
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
-    auto cmd = server_->cqueue().back();
-
-    BOOST_TEST(cmd["command"].get<std::string>() == "part");
-    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
-    BOOST_TEST(cmd["reason"].get<std::string>() == "too noisy");
-}
-
-BOOST_AUTO_TEST_CASE(noreason)
-{
-    ctl_->send({
-        { "command",    "server-part"   },
-        { "server",     "test"          },
-        { "channel",    "#staff"        }
-    });
-
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
-    auto cmd = server_->cqueue().back();
-
-    BOOST_TEST(cmd["command"].get<std::string>() == "part");
-    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
-    BOOST_TEST(cmd["reason"].get<std::string>() == "");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-part"   },
-        { "server",     123456          },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-part"   },
-        { "server",     ""              },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-part"   },
-        { "server",     "test"          },
-        { "channel",    ""              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-part"   },
-        { "server",     "test"          },
-        { "channel",    123456          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-part"   },
-        { "server",     "unknown"       },
-        { "channel",    "#music"        }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-reconnect/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-reconnect
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
--- a/tests/cmd-server-reconnect/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-/*
- * main.cpp -- test server-reconnect remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-reconnect"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_reconnect_test : public command_test<server_reconnect_command> {
-protected:
-    std::shared_ptr<journal_server> server1_{new journal_server(service_, "s1")};
-    std::shared_ptr<journal_server> server2_{new journal_server(service_, "s2")};
-
-    server_reconnect_test()
-    {
-        daemon_->servers().add(server1_);
-        daemon_->servers().add(server2_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_reconnect_test_suite, server_reconnect_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "server-reconnect"  },
-        { "server",     "s1"                }
-    });
-
-    wait_for([this] () {
-        return !server1_->cqueue().empty();
-    });
-
-    auto cmd1 = server1_->cqueue().back();
-
-    BOOST_TEST(cmd1["command"].get<std::string>() == "reconnect");
-    BOOST_TEST(server2_->cqueue().empty());
-}
-
-BOOST_AUTO_TEST_CASE(all)
-{
-    ctl_->send({{"command", "server-reconnect"}});
-
-    wait_for([this] () {
-        return !server1_->cqueue().empty() && !server2_->cqueue().empty();
-    });
-
-    auto cmd1 = server1_->cqueue().back();
-    auto cmd2 = server2_->cqueue().back();
-
-    BOOST_TEST(cmd1["command"].get<std::string>() == "reconnect");
-    BOOST_TEST(cmd2["command"].get<std::string>() == "reconnect");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-reconnect"  },
-        { "server",     123456              }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-reconnect"  },
-        { "server",     ""                  }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-reconnect"  },
-        { "server",     "unknown"           }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/cmd-server-topic/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME cmd-server-topic
-    SOURCES main.cpp
-    LIBRARIES libirccd libirccdctl
-)
-
--- a/tests/cmd-server-topic/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,176 +0,0 @@
-/*
- * main.cpp -- test server-topic remote command
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "server-topic"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/server_service.hpp>
-
-#include <journal_server.hpp>
-#include <command_test.hpp>
-
-namespace irccd {
-
-namespace {
-
-class server_topic_test : public command_test<server_topic_command> {
-protected:
-    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
-
-    server_topic_test()
-    {
-        daemon_->servers().add(server_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(server_topic_test_suite, server_topic_test)
-
-BOOST_AUTO_TEST_CASE(basic)
-{
-    ctl_->send({
-        { "command",    "server-topic"  },
-        { "server",     "test"          },
-        { "channel",    "#staff"        },
-        { "topic",      "new version"   }
-    });
-
-    wait_for([this] () {
-        return !server_->cqueue().empty();
-    });
-
-    auto cmd = server_->cqueue().back();
-
-    BOOST_TEST(cmd["command"].get<std::string>() == "topic");
-    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
-    BOOST_TEST(cmd["topic"].get<std::string>() == "new version");
-}
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-topic"  },
-        { "server",     123456          },
-        { "channel",    "#music"        },
-        { "topic",      "plop"          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_identifier_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-topic"  },
-        { "server",     ""              },
-        { "channel",    "#music"        },
-        { "topic",      "plop"          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_identifier);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_1)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-topic"  },
-        { "server",     "test"          },
-        { "channel",    ""              },
-        { "topic",      "plop"          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(invalid_channel_2)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-topic"  },
-        { "server",     "test"          },
-        { "channel",    123456          },
-        { "topic",      "plop"          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::invalid_channel);
-}
-
-BOOST_AUTO_TEST_CASE(not_found)
-{
-    boost::system::error_code result;
-
-    ctl_->send({
-        { "command",    "server-topic"  },
-        { "server",     "unknown"       },
-        { "channel",    "#music"        },
-        { "topic",      "plop"          }
-    });
-    ctl_->recv([&] (auto code, auto) {
-        result = code;
-    });
-
-    wait_for([&] {
-        return result;
-    });
-
-    BOOST_ASSERT(result == server_error::not_found);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/directory-jsapi/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME directory-jsapi
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/directory-jsapi/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,47 @@
+/*
+ * main.cpp -- test Irccd.Directory API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "Directory Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/js/directory_jsapi.hpp>
+
+#include <js_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(directory_jsapi_suite, js_test<directory_jsapi>)
+
+BOOST_AUTO_TEST_CASE(constructor)
+{
+    const std::string script(
+        "d = new Irccd.Directory(CMAKE_SOURCE_DIR + \"/tests/root\");"
+        "p = d.path;"
+        "l = d.entries.length;"
+    );
+
+    if (duk_peval_string(plugin_->context(), script.c_str()) != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    duk_get_global_string(plugin_->context(), "l");
+    BOOST_TEST(duk_get_int(plugin_->context(), -1) == 3);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/elapsedtimer-jsapi/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME elapsedtimer-jsapi
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/elapsedtimer-jsapi/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,51 @@
+/*
+ * main.cpp -- test Irccd.ElapsedTimer API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "ElapsedTimer Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <thread>
+
+#include <irccd/js/elapsed_timer_jsapi.hpp>
+
+#include <js_test.hpp>
+
+using namespace std::chrono_literals;
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(elapsed_timer_jsapi_suite, js_test<elapsed_timer_jsapi>)
+
+BOOST_AUTO_TEST_CASE(standard)
+{
+    if (duk_peval_string(plugin_->context(), "timer = new Irccd.ElapsedTimer();") != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    std::this_thread::sleep_for(300ms);
+
+    if (duk_peval_string(plugin_->context(), "result = timer.elapsed();") != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_REQUIRE(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_REQUIRE_GE(duk_get_int(plugin_->context(), -1), 250);
+    BOOST_REQUIRE_LE(duk_get_int(plugin_->context(), -1), 350);
+}
+
+} // !irccd
+
+BOOST_AUTO_TEST_SUITE_END()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/file-jsapi/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME file-jsapi
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/file-jsapi/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,308 @@
+/*
+ * main.cpp -- test Irccd.File API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "File Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <fstream>
+
+#include <irccd/js/file_jsapi.hpp>
+
+#include <js_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(file_jsapi_suite, js_test<file_jsapi>)
+
+BOOST_AUTO_TEST_CASE(function_basename)
+{
+    if (duk_peval_string(plugin_->context(), "result = Irccd.File.basename('/usr/local/etc/irccd.conf');"))
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST("irccd.conf" == duk_get_string(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(function_dirname)
+{
+    if (duk_peval_string(plugin_->context(), "result = Irccd.File.dirname('/usr/local/etc/irccd.conf');"))
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST("/usr/local/etc" == duk_get_string(plugin_->context(), -1));
+}
+
+
+BOOST_AUTO_TEST_CASE(function_exists)
+{
+    if (duk_peval_string(plugin_->context(), "result = Irccd.File.exists(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt')"))
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(function_exists2)
+{
+    if (duk_peval_string(plugin_->context(), "result = Irccd.File.exists('file_which_does_not_exist.txt')"))
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(!duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(function_remove)
+{
+    // First create a dummy file
+    std::ofstream("test-js-fs.remove");
+
+    if (duk_peval_string(plugin_->context(), "Irccd.File.remove('test-js-fs.remove');") != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    std::ifstream in("test-js-fs.remove");
+
+    BOOST_TEST(!in.is_open());
+}
+
+BOOST_AUTO_TEST_CASE(method_basename)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "result = f.basename();"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST("file-1.txt" == duk_get_string(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_basename_closed)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "result = f.basename();"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST("file-1.txt" == duk_get_string(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_dirname)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "result = f.dirname();"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(CMAKE_SOURCE_DIR "/tests/root" == duk_get_string(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_dirname_closed)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "result = f.dirname();"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(CMAKE_SOURCE_DIR "/tests/root" == duk_get_string(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_lines)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "result = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r').lines();"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    std::vector<std::string> expected{"a", "b", "c"};
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek1)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.seek(Irccd.File.SeekSet, 6);"
+        "result = f.read(1);"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(".", dukx_get<std::string>(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek1_closed)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "f.seek(Irccd.File.SeekSet, 4);"
+        "result = f.read(1);"
+        "result = typeof (result) === \"undefined\";"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek2)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.seek(Irccd.File.SeekSet, 2);"
+        "f.seek(Irccd.File.SeekCur, 4);"
+        "result = f.read(1);"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST("." == dukx_get<std::string>(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek2c_losed)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "f.seek(Irccd.File.SeekSet, 2);"
+        "f.seek(Irccd.File.SeekCur, 2);"
+        "result = f.read(1);"
+        "result = typeof (result) === \"undefined\";"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek3)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.seek(Irccd.File.SeekEnd, -2);"
+        "result = f.read(1);"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST("t" == duk_get_string(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_seek3_closed)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "f.close();"
+        "f.seek(Irccd.File.SeekEnd, -2);"
+        "result = f.read(1);"
+        "result = typeof (result) === \"undefined\";"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_read1)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
+        "result = f.read();"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST("file-1.txt\n" == duk_get_string(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_readline)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "result = [];"
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
+        "for (var s; s = f.readline(); ) {"
+        "  result.push(s);"
+        "}"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    std::vector<std::string> expected{"a", "b", "c"};
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(method_readline_closed)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "result = [];"
+        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
+        "f.close();"
+        "for (var s; s = f.readline(); ) {"
+        "  result.push(s);"
+        "}"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    std::vector<std::string> expected;
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/irccd-jsapi/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME irccd-jsapi
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/irccd-jsapi/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,115 @@
+/*
+ * main.cpp -- test Irccd API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "Irccd Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <js_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(irccd_jsapi_suite, js_test<irccd_jsapi>)
+
+BOOST_AUTO_TEST_CASE(version)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "major = Irccd.version.major;"
+        "minor = Irccd.version.minor;"
+        "patch = Irccd.version.patch;"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "major"));
+    BOOST_TEST(IRCCD_VERSION_MAJOR == duk_get_int(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "minor"));
+    BOOST_TEST(IRCCD_VERSION_MINOR == duk_get_int(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "patch"));
+    BOOST_TEST(IRCCD_VERSION_PATCH == duk_get_int(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(from_javascript)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "try {"
+        "  throw new Irccd.SystemError(1, 'test');"
+        "} catch (e) {"
+        "  errno = e.errno;"
+        "  name = e.name;"
+        "  message = e.message;"
+        "  v1 = (e instanceof Error);"
+        "  v2 = (e instanceof Irccd.SystemError);"
+        "}"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "errno"));
+    BOOST_TEST(1 == duk_get_int(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
+    BOOST_TEST("SystemError" == duk_get_string(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
+    BOOST_TEST("test" == duk_get_string(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "v1"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "v2"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(from_native)
+{
+    duk_push_c_function(plugin_->context(), [] (duk_context *ctx) -> duk_ret_t {
+        dukx_throw(ctx, system_error(EINVAL, "hey"));
+
+        return 0;
+    }, 0);
+
+    duk_put_global_string(plugin_->context(), "f");
+
+    auto ret = duk_peval_string(plugin_->context(),
+        "try {"
+        "  f();"
+        "} catch (e) {"
+        "  errno = e.errno;"
+        "  name = e.name;"
+        "  message = e.message;"
+        "  v1 = (e instanceof Error);"
+        "  v2 = (e instanceof Irccd.SystemError);"
+        "}"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "errno"));
+    BOOST_TEST(EINVAL == duk_get_int(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
+    BOOST_TEST("SystemError" == duk_get_string(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
+    BOOST_TEST("hey" == duk_get_string(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "v1"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "v2"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- a/tests/js-directory/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME js-directory
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/js-directory/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- * main.cpp -- test Irccd.Directory API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "Directory Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/js/directory_jsapi.hpp>
-
-#include <js_test.hpp>
-
-namespace irccd {
-
-BOOST_FIXTURE_TEST_SUITE(directory_jsapi_suite, js_test<directory_jsapi>)
-
-BOOST_AUTO_TEST_CASE(constructor)
-{
-    const std::string script(
-        "d = new Irccd.Directory(CMAKE_SOURCE_DIR + \"/tests/root\");"
-        "p = d.path;"
-        "l = d.entries.length;"
-    );
-
-    if (duk_peval_string(plugin_->context(), script.c_str()) != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    duk_get_global_string(plugin_->context(), "l");
-    BOOST_TEST(duk_get_int(plugin_->context(), -1) == 3);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/js-elapsedtimer/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME js-elapsedtimer
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
-
--- a/tests/js-elapsedtimer/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/*
- * main.cpp -- test Irccd.ElapsedTimer API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "ElapsedTimer Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <thread>
-
-#include <irccd/js/elapsed_timer_jsapi.hpp>
-
-#include <js_test.hpp>
-
-using namespace std::chrono_literals;
-
-namespace irccd {
-
-BOOST_FIXTURE_TEST_SUITE(elapsed_timer_jsapi_suite, js_test<elapsed_timer_jsapi>)
-
-BOOST_AUTO_TEST_CASE(standard)
-{
-    if (duk_peval_string(plugin_->context(), "timer = new Irccd.ElapsedTimer();") != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    std::this_thread::sleep_for(300ms);
-
-    if (duk_peval_string(plugin_->context(), "result = timer.elapsed();") != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_REQUIRE(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_REQUIRE_GE(duk_get_int(plugin_->context(), -1), 250);
-    BOOST_REQUIRE_LE(duk_get_int(plugin_->context(), -1), 350);
-}
-
-} // !irccd
-
-BOOST_AUTO_TEST_SUITE_END()
--- a/tests/js-file/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME js-file
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/js-file/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,308 +0,0 @@
-/*
- * main.cpp -- test Irccd.File API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "File Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <fstream>
-
-#include <irccd/js/file_jsapi.hpp>
-
-#include <js_test.hpp>
-
-namespace irccd {
-
-BOOST_FIXTURE_TEST_SUITE(file_jsapi_suite, js_test<file_jsapi>)
-
-BOOST_AUTO_TEST_CASE(function_basename)
-{
-    if (duk_peval_string(plugin_->context(), "result = Irccd.File.basename('/usr/local/etc/irccd.conf');"))
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST("irccd.conf" == duk_get_string(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(function_dirname)
-{
-    if (duk_peval_string(plugin_->context(), "result = Irccd.File.dirname('/usr/local/etc/irccd.conf');"))
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST("/usr/local/etc" == duk_get_string(plugin_->context(), -1));
-}
-
-
-BOOST_AUTO_TEST_CASE(function_exists)
-{
-    if (duk_peval_string(plugin_->context(), "result = Irccd.File.exists(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt')"))
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(function_exists2)
-{
-    if (duk_peval_string(plugin_->context(), "result = Irccd.File.exists('file_which_does_not_exist.txt')"))
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(!duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(function_remove)
-{
-    // First create a dummy file
-    std::ofstream("test-js-fs.remove");
-
-    if (duk_peval_string(plugin_->context(), "Irccd.File.remove('test-js-fs.remove');") != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    std::ifstream in("test-js-fs.remove");
-
-    BOOST_TEST(!in.is_open());
-}
-
-BOOST_AUTO_TEST_CASE(method_basename)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "result = f.basename();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST("file-1.txt" == duk_get_string(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_basename_closed)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "result = f.basename();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST("file-1.txt" == duk_get_string(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_dirname)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "result = f.dirname();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(CMAKE_SOURCE_DIR "/tests/root" == duk_get_string(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_dirname_closed)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "result = f.dirname();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(CMAKE_SOURCE_DIR "/tests/root" == duk_get_string(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_lines)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "result = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r').lines();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    std::vector<std::string> expected{"a", "b", "c"};
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek1)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.seek(Irccd.File.SeekSet, 6);"
-        "result = f.read(1);"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(".", dukx_get<std::string>(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek1_closed)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "f.seek(Irccd.File.SeekSet, 4);"
-        "result = f.read(1);"
-        "result = typeof (result) === \"undefined\";"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek2)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.seek(Irccd.File.SeekSet, 2);"
-        "f.seek(Irccd.File.SeekCur, 4);"
-        "result = f.read(1);"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST("." == dukx_get<std::string>(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek2c_losed)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "f.seek(Irccd.File.SeekSet, 2);"
-        "f.seek(Irccd.File.SeekCur, 2);"
-        "result = f.read(1);"
-        "result = typeof (result) === \"undefined\";"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek3)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.seek(Irccd.File.SeekEnd, -2);"
-        "result = f.read(1);"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST("t" == duk_get_string(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_seek3_closed)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "f.close();"
-        "f.seek(Irccd.File.SeekEnd, -2);"
-        "result = f.read(1);"
-        "result = typeof (result) === \"undefined\";"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_read1)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
-        "result = f.read();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST("file-1.txt\n" == duk_get_string(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_readline)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "result = [];"
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
-        "for (var s; s = f.readline(); ) {"
-        "  result.push(s);"
-        "}"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    std::vector<std::string> expected{"a", "b", "c"};
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(method_readline_closed)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "result = [];"
-        "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
-        "f.close();"
-        "for (var s; s = f.readline(); ) {"
-        "  result.push(s);"
-        "}"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    std::vector<std::string> expected;
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(expected == dukx_get_array<std::vector<std::string>>(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/js-irccd/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME js-irccd
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/js-irccd/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +0,0 @@
-/*
- * main.cpp -- test Irccd API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "Irccd Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <js_test.hpp>
-
-namespace irccd {
-
-BOOST_FIXTURE_TEST_SUITE(irccd_jsapi_suite, js_test<irccd_jsapi>)
-
-BOOST_AUTO_TEST_CASE(version)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "major = Irccd.version.major;"
-        "minor = Irccd.version.minor;"
-        "patch = Irccd.version.patch;"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "major"));
-    BOOST_TEST(IRCCD_VERSION_MAJOR == duk_get_int(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "minor"));
-    BOOST_TEST(IRCCD_VERSION_MINOR == duk_get_int(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "patch"));
-    BOOST_TEST(IRCCD_VERSION_PATCH == duk_get_int(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(from_javascript)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "try {"
-        "  throw new Irccd.SystemError(1, 'test');"
-        "} catch (e) {"
-        "  errno = e.errno;"
-        "  name = e.name;"
-        "  message = e.message;"
-        "  v1 = (e instanceof Error);"
-        "  v2 = (e instanceof Irccd.SystemError);"
-        "}"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "errno"));
-    BOOST_TEST(1 == duk_get_int(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
-    BOOST_TEST("SystemError" == duk_get_string(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
-    BOOST_TEST("test" == duk_get_string(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "v1"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "v2"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(from_native)
-{
-    duk_push_c_function(plugin_->context(), [] (duk_context *ctx) -> duk_ret_t {
-        dukx_throw(ctx, system_error(EINVAL, "hey"));
-
-        return 0;
-    }, 0);
-
-    duk_put_global_string(plugin_->context(), "f");
-
-    auto ret = duk_peval_string(plugin_->context(),
-        "try {"
-        "  f();"
-        "} catch (e) {"
-        "  errno = e.errno;"
-        "  name = e.name;"
-        "  message = e.message;"
-        "  v1 = (e instanceof Error);"
-        "  v2 = (e instanceof Irccd.SystemError);"
-        "}"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "errno"));
-    BOOST_TEST(EINVAL == duk_get_int(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
-    BOOST_TEST("SystemError" == duk_get_string(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
-    BOOST_TEST("hey" == duk_get_string(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "v1"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "v2"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/js-logger/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME js-logger
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/js-logger/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-/*
- * main.cpp -- test Irccd.Logger API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "Logger Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/logger.hpp>
-
-#include <irccd/js/logger_jsapi.hpp>
-#include <irccd/js/plugin_jsapi.hpp>
-
-#include <js_test.hpp>
-
-namespace irccd {
-
-class logger_test : public js_test<logger_jsapi, plugin_jsapi> {
-protected:
-    std::string line_info;
-    std::string line_warning;
-    std::string line_debug;
-
-    class my_logger : public log::logger {
-    private:
-        logger_test& test_;
-
-    public:
-        inline my_logger(logger_test& test) noexcept
-            : test_(test)
-        {
-        }
-
-        void info(const std::string& line) override
-        {
-            test_.line_info = line;
-        }
-
-        void warning(const std::string& line) override
-        {
-            test_.line_warning = line;
-        }
-
-        void debug(const std::string& line) override
-        {
-            test_.line_debug = line;
-        }
-    };
-
-    logger_test()
-    {
-        log::set_verbose(true);
-        log::set_logger(std::make_unique<my_logger>(*this));
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(logger_jsapi_suite, logger_test)
-
-BOOST_AUTO_TEST_CASE(info)
-{
-    if (duk_peval_string(plugin_->context(), "Irccd.Logger.info(\"hello!\");") != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST("plugin test: hello!" == line_info);
-}
-
-BOOST_AUTO_TEST_CASE(warning)
-{
-    if (duk_peval_string(plugin_->context(), "Irccd.Logger.warning(\"FAIL!\");") != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST("plugin test: FAIL!" == line_warning);
-}
-
-#if !defined(NDEBUG)
-
-BOOST_AUTO_TEST_CASE(debug)
-{
-    if (duk_peval_string(plugin_->context(), "Irccd.Logger.debug(\"starting\");") != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST("plugin test: starting" == line_debug);
-}
-
-#endif
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/js-system/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME js-system
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-    FLAGS IRCCD_EXECUTABLE=\"$<TARGET_FILE:irccd>\"
-)
-
--- a/tests/js-system/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-/*
- * main.cpp -- test Irccd.System API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "System Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/irccd.hpp>
-#include <irccd/system.hpp>
-
-#include <irccd/js/file_jsapi.hpp>
-#include <irccd/js/system_jsapi.hpp>
-
-#include <js_test.hpp>
-
-namespace irccd {
-
-using fixture = js_test<file_jsapi, system_jsapi>;
-
-BOOST_FIXTURE_TEST_SUITE(system_jsapi_suite, fixture)
-
-BOOST_AUTO_TEST_CASE(home)
-{
-    duk_peval_string_noresult(plugin_->context(), "result = Irccd.System.home();");
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(),"result"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == sys::home());
-}
-
-#if defined(HAVE_POPEN)
-
-BOOST_AUTO_TEST_CASE(popen)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "f = Irccd.System.popen(\"" IRCCD_EXECUTABLE " --version\", \"r\");"
-        "r = f.readline();"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "r"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == IRCCD_VERSION);
-}
-
-#endif
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/js-timer/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME js-timer
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-    FLAGS DIRECTORY=\"${CMAKE_CURRENT_SOURCE_DIR}\"
-)
--- a/tests/js-timer/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/*
- * main.cpp -- test Irccd.Timer API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "Timer Javascript API"
-#include <boost/test/unit_test.hpp>
-#include <boost/timer/timer.hpp>
-
-#include <irccd/js/plugin_jsapi.hpp>
-#include <irccd/js/timer_jsapi.hpp>
-
-#include <js_test.hpp>
-
-#include <irccd/js/logger_jsapi.hpp>
-
-namespace irccd {
-
-namespace {
-
-class js_timer_test : public js_test<plugin_jsapi, timer_jsapi> {
-public:
-    js_timer_test()
-        : js_test(CMAKE_CURRENT_SOURCE_DIR "/timer.js")
-    {
-    }
-
-    void set_type(const std::string& name)
-    {
-        duk_get_global_string(plugin_->context(), "Irccd");
-        duk_get_prop_string(plugin_->context(), -1, "Timer");
-        duk_get_prop_string(plugin_->context(), -1, name.c_str());
-        duk_put_global_string(plugin_->context(), "type");
-        duk_pop_n(plugin_->context(), 2);
-
-        plugin_->open();
-        plugin_->on_load(irccd_);
-    }
-};
-
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(js_timer_test_suite, js_timer_test)
-
-BOOST_AUTO_TEST_CASE(single)
-{
-    boost::timer::cpu_timer timer;
-
-    set_type("Single");
-
-    while (timer.elapsed().wall / 1000000LL < 3000) {
-        service_.reset();
-        service_.poll();
-    }
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "count"));
-    BOOST_TEST(duk_get_int(plugin_->context(), -1) == 1);
-}
-
-BOOST_AUTO_TEST_CASE(repeat)
-{
-    boost::timer::cpu_timer timer;
-
-    set_type("Repeat");
-
-    while (timer.elapsed().wall / 1000000LL < 3000) {
-        service_.reset();
-        service_.poll();
-    }
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "count"));
-    BOOST_TEST(duk_get_int(plugin_->context(), -1) >= 5);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/js-timer/timer.js	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-count = 0;
-
-function onLoad()
-{
-    if (typeof (type) === "undefined")
-        throw Error("global timer type not defined");
-
-    t = new Irccd.Timer(type, 50, function () {
-        count += 1;
-    });
-
-    t.start();
-}
--- a/tests/js-unicode/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME js-unicode
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/js-unicode/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +0,0 @@
-/*
- * main.cpp -- test Irccd.Unicode API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-/*
- * /!\ Be sure that this file is kept saved in UTF-8 /!\
- */
-
-#define BOOST_TEST_MODULE "Unicode Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/js/unicode_jsapi.hpp>
-
-#include <js_test.hpp>
-
-namespace irccd {
-
-BOOST_FIXTURE_TEST_SUITE(unicode_jsapi_suite, js_test<unicode_jsapi>)
-
-BOOST_AUTO_TEST_CASE(is_letter)
-{
-    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isLetter(String('é').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-
-    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isLetter(String('€').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(!duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(is_lower)
-{
-    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isLower(String('é').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-
-    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isLower(String('É').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(!duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(is_upper)
-{
-    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isUpper(String('É').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
-
-    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isUpper(String('é').charCodeAt(0));");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(!duk_get_boolean(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- a/tests/js-util/CMakeLists.txt	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#
-
-irccd_define_test(
-    NAME js-util
-    SOURCES main.cpp
-    LIBRARIES libirccd-js
-)
--- a/tests/js-util/main.cpp	Fri Dec 01 21:34:21 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,260 +0,0 @@
-/*
- * main.cpp -- test Irccd.Util API
- *
- * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#define BOOST_TEST_MODULE "Unicode Javascript API"
-#include <boost/test/unit_test.hpp>
-
-#include <irccd/js/util_jsapi.hpp>
-
-#include <js_test.hpp>
-
-namespace irccd {
-
-BOOST_FIXTURE_TEST_SUITE(util_jsapi_suite, js_test<util_jsapi>)
-
-/*
- * Irccd.Util misc.
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_CASE(format_simple)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "result = Irccd.Util.format(\"#{target}\", { target: \"markand\" })"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "markand");
-}
-
-BOOST_AUTO_TEST_CASE(splituser)
-{
-    if (duk_peval_string(plugin_->context(), "result = Irccd.Util.splituser(\"user!~user@hyper/super/host\");") != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "user");
-}
-
-BOOST_AUTO_TEST_CASE(splithost)
-{
-    if (duk_peval_string(plugin_->context(), "result = Irccd.Util.splithost(\"user!~user@hyper/super/host\");") != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "~user@hyper/super/host");
-}
-
-/*
- * Irccd.Util.cut.
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_CASE(cut_string_simple)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "lines = Irccd.Util.cut('hello world');\n"
-        "line0 = lines[0];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_double)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "lines = Irccd.Util.cut('hello world', 5);\n"
-        "line0 = lines[0];\n"
-        "line1 = lines[1];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line1"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_dirty)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "lines = Irccd.Util.cut('     hello    world     ', 5);\n"
-        "line0 = lines[0];\n"
-        "line1 = lines[1];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line1"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_too_much_lines)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "lines = Irccd.Util.cut('abc def ghi jkl', 3, 3);\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "lines"));
-    BOOST_TEST(duk_is_undefined(plugin_->context(), -1));
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_token_too_big)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "try {\n"
-        "  lines = Irccd.Util.cut('hello world', 3);\n"
-        "} catch (e) {\n"
-        "  name = e.name;\n"
-        "  message = e.message;\n"
-        "}\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "RangeError");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "word 'hello' could not fit in maxc limit (3)");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_negative_maxc)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "try {\n"
-        "  lines = Irccd.Util.cut('hello world', -3);\n"
-        "} catch (e) {\n"
-        "  name = e.name;\n"
-        "  message = e.message;\n"
-        "}\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "RangeError");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "argument 1 (maxc) must be positive");
-}
-
-BOOST_AUTO_TEST_CASE(cut_string_negative_maxl)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "try {\n"
-        "  lines = Irccd.Util.cut('hello world', undefined, -1);\n"
-        "} catch (e) {\n"
-        "  name = e.name;\n"
-        "  message = e.message;\n"
-        "}\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "RangeError");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "argument 2 (maxl) must be positive");
-}
-
-BOOST_AUTO_TEST_CASE(cut_array_simple)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "lines = Irccd.Util.cut([ 'hello', 'world' ]);\n"
-        "line0 = lines[0];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_array_double)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "lines = Irccd.Util.cut([ 'hello', 'world' ], 5);\n"
-        "line0 = lines[0];\n"
-        "line1 = lines[1];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line1"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_array_dirty)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "lines = Irccd.Util.cut([ '   ', ' hello  ', '  world ', '    '], 5);\n"
-        "line0 = lines[0];\n"
-        "line1 = lines[1];\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello");
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "line1"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "world");
-}
-
-BOOST_AUTO_TEST_CASE(cut_invalid_data)
-{
-    auto ret = duk_peval_string(plugin_->context(),
-        "try {\n"
-        "  lines = Irccd.Util.cut(123);\n"
-        "} catch (e) {\n"
-        "  name = e.name;\n"
-        "  message = e.message;\n"
-        "}\n"
-    );
-
-    if (ret != 0)
-        throw dukx_stack(plugin_->context(), -1);
-
-    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
-    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "TypeError");
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/logger-jsapi/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME logger-jsapi
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/logger-jsapi/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,102 @@
+/*
+ * main.cpp -- test Irccd.Logger API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "Logger Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/logger.hpp>
+
+#include <irccd/js/logger_jsapi.hpp>
+#include <irccd/js/plugin_jsapi.hpp>
+
+#include <js_test.hpp>
+
+namespace irccd {
+
+class logger_test : public js_test<logger_jsapi, plugin_jsapi> {
+protected:
+    std::string line_info;
+    std::string line_warning;
+    std::string line_debug;
+
+    class my_logger : public log::logger {
+    private:
+        logger_test& test_;
+
+    public:
+        inline my_logger(logger_test& test) noexcept
+            : test_(test)
+        {
+        }
+
+        void info(const std::string& line) override
+        {
+            test_.line_info = line;
+        }
+
+        void warning(const std::string& line) override
+        {
+            test_.line_warning = line;
+        }
+
+        void debug(const std::string& line) override
+        {
+            test_.line_debug = line;
+        }
+    };
+
+    logger_test()
+    {
+        log::set_verbose(true);
+        log::set_logger(std::make_unique<my_logger>(*this));
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(logger_jsapi_suite, logger_test)
+
+BOOST_AUTO_TEST_CASE(info)
+{
+    if (duk_peval_string(plugin_->context(), "Irccd.Logger.info(\"hello!\");") != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST("plugin test: hello!" == line_info);
+}
+
+BOOST_AUTO_TEST_CASE(warning)
+{
+    if (duk_peval_string(plugin_->context(), "Irccd.Logger.warning(\"FAIL!\");") != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST("plugin test: FAIL!" == line_warning);
+}
+
+#if !defined(NDEBUG)
+
+BOOST_AUTO_TEST_CASE(debug)
+{
+    if (duk_peval_string(plugin_->context(), "Irccd.Logger.debug(\"starting\");") != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST("plugin test: starting" == line_debug);
+}
+
+#endif
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-config-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME plugin-config-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-config-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,157 @@
+/*
+ * main.cpp -- test plugin-config remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "plugin-config"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/plugin_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class custom_plugin : public plugin {
+public:
+    plugin_config config_;
+
+    custom_plugin(std::string name = "test")
+        : plugin(std::move(name), "")
+    {
+    }
+
+    plugin_config config() override
+    {
+        return config_;
+    }
+
+    void set_config(plugin_config config) override
+    {
+        config_ = std::move(config);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(plugin_config_test_suite, command_test<plugin_config_command>)
+
+BOOST_AUTO_TEST_CASE(set)
+{
+    daemon_->plugins().add(std::make_unique<custom_plugin>("test"));
+    ctl_->send({
+        { "command",    "plugin-config" },
+        { "plugin",     "test"          },
+        { "variable",   "verbosy"       },
+        { "value",      "falsy"         }
+    });
+
+    wait_for([&] {
+        return !daemon_->plugins().require("test")->config().empty();
+    });
+
+    auto config = daemon_->plugins().require("test")->config();
+
+    BOOST_TEST(!config.empty());
+    BOOST_TEST(config["verbosy"] == "falsy");
+}
+
+BOOST_AUTO_TEST_CASE(get)
+{
+    auto plugin = std::make_unique<custom_plugin>("test");
+    auto json = nlohmann::json();
+
+    plugin->set_config({
+        { "x1", "10" },
+        { "x2", "20" }
+    });
+
+    daemon_->plugins().add(std::move(plugin));
+    ctl_->send({
+        { "command", "plugin-config" },
+        { "plugin", "test" },
+        { "variable", "x1" }
+    });
+    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"].is_null());
+}
+
+BOOST_AUTO_TEST_CASE(getall)
+{
+    auto plugin = std::make_unique<custom_plugin>("test");
+    auto json = nlohmann::json();
+
+    plugin->set_config({
+        { "x1", "10" },
+        { "x2", "20" }
+    });
+
+    daemon_->plugins().add(std::move(plugin));
+    ctl_->send({
+        { "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_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "plugin-config" },
+        { "plugin",     "unknown"       }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == plugin_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-info-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME plugin-info-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-info-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,86 @@
+/*
+ * main.cpp -- test plugin-info remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "plugin-info"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/plugin_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(plugin_info_test_suite, command_test<plugin_info_command>)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    auto plg = std::make_unique<plugin>("test", "");
+    auto response = nlohmann::json();
+
+    plg->set_author("Francis Beaugrand");
+    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));
+    ctl_->recv([&] (auto, auto msg) {
+        response = std::move(msg);
+    });
+    ctl_->send({
+        { "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_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "plugin-info"   },
+        { "plugin",     "unknown"       }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == plugin_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-list-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME plugin-list-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-list-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,60 @@
+/*
+ * main.cpp -- test plugin-list remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "plugin-list"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/plugin_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+class plugin_list_test : public command_test<plugin_list_command> {
+public:
+    plugin_list_test()
+    {
+        daemon_->plugins().add(std::make_unique<plugin>("t1", ""));
+        daemon_->plugins().add(std::make_unique<plugin>("t2", ""));
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(plugin_list_test_suite, plugin_list_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    auto response = nlohmann::json();
+
+    ctl_->send({{"command", "plugin-list"}});
+    ctl_->recv([&] (auto, auto message) {
+        response = message;
+    });
+
+    wait_for([&] () {
+        return response.is_object();
+    });
+
+    BOOST_TEST(response.is_object());
+    BOOST_TEST("t1", response["list"][0]);
+    BOOST_TEST("t2", response["list"][1]);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-load-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME plugin-load-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-load-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,155 @@
+/*
+ * main.cpp -- test plugin-load remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "plugin-load"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/plugin_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class custom_loader : public plugin_loader {
+public:
+    std::shared_ptr<plugin> open(const std::string&,
+                                 const std::string&) noexcept override
+    {
+        return nullptr;
+    }
+
+    std::shared_ptr<plugin> find(const std::string& id) noexcept override
+    {
+        class broken : public plugin {
+        public:
+            using plugin::plugin;
+
+            void on_load(irccd&) override
+            {
+                throw std::runtime_error("broken");
+            }
+        };
+
+        /*
+         * The 'magic' plugin will be created for the unit tests, all other
+         * plugins will return null.
+         */
+        if (id == "magic")
+            return std::make_unique<plugin>(id, "");
+        if (id == "broken")
+            return std::make_unique<broken>(id, "");
+
+        return nullptr;
+    }
+};
+
+class plugin_load_test : public command_test<plugin_load_command> {
+public:
+    plugin_load_test()
+    {
+        daemon_->plugins().add_loader(std::make_unique<custom_loader>());
+        daemon_->plugins().add(std::make_unique<plugin>("already", ""));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(plugin_load_test_suite, plugin_load_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "plugin-load"   },
+        { "plugin",     "magic"         }
+    });
+
+    wait_for([&] () {
+        return daemon_->plugins().has("magic");
+    });
+
+    BOOST_TEST(!daemon_->plugins().list().empty());
+    BOOST_TEST(daemon_->plugins().has("magic"));
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "plugin-load"   },
+        { "plugin",     "unknown"       }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == plugin_error::not_found);
+}
+
+BOOST_AUTO_TEST_CASE(already_exists)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "plugin-load"   },
+        { "plugin",     "already"       }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == plugin_error::already_exists);
+}
+
+BOOST_AUTO_TEST_CASE(exec_error)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "plugin-load"   },
+        { "plugin",     "broken"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == plugin_error::exec_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-reload-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME plugin-reload-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-reload-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,130 @@
+/*
+ * main.cpp -- test plugin-reload remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "plugin-reload"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/plugin_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class custom_plugin : public plugin {
+public:
+    bool reloaded{false};
+
+    custom_plugin()
+        : plugin("test", "")
+    {
+    }
+
+    void on_reload(irccd&) override
+    {
+        reloaded = true;
+    }
+};
+
+class broken_plugin : public plugin {
+public:
+    using plugin::plugin;
+
+    void on_reload(irccd&) override
+    {
+        throw std::runtime_error("broken");
+    }
+};
+
+class plugin_reload_test : public command_test<plugin_reload_command> {
+protected:
+    std::shared_ptr<custom_plugin> plugin_;
+
+    plugin_reload_test()
+        : plugin_(std::make_shared<custom_plugin>())
+    {
+        daemon_->plugins().add(plugin_);
+        daemon_->plugins().add(std::make_unique<broken_plugin>("broken", ""));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(plugin_reload_test_suite, plugin_reload_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "plugin-reload" },
+        { "plugin",     "test"          }
+    });
+
+    wait_for([&] () {
+        return plugin_->reloaded;
+    });
+
+    BOOST_TEST(plugin_->reloaded);
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "plugin-reload" },
+        { "plugin",     "unknown"       }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == plugin_error::not_found);
+}
+
+BOOST_AUTO_TEST_CASE(exec_error)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "plugin-reload" },
+        { "plugin",     "broken"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == plugin_error::exec_error);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-unload-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME plugin-unload-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/plugin-unload-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,131 @@
+/*
+ * main.cpp -- test plugin-unload remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "plugin-unload"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/plugin_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class custom_plugin : public plugin {
+public:
+    bool unloaded{false};
+
+    custom_plugin()
+        : plugin("test", "")
+    {
+    }
+
+    void on_unload(irccd &) override
+    {
+        unloaded = true;
+    }
+};
+
+class broken_plugin : public plugin {
+public:
+    using plugin::plugin;
+
+    void on_unload(irccd&) override
+    {
+        throw std::runtime_error("broken");
+    }
+};
+
+class plugin_unload_test : public command_test<plugin_unload_command> {
+protected:
+    std::shared_ptr<custom_plugin> plugin_;
+
+    plugin_unload_test()
+        : plugin_(std::make_shared<custom_plugin>())
+    {
+        daemon_->plugins().add(plugin_);
+        daemon_->plugins().add(std::make_unique<broken_plugin>("broken", ""));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(plugin_unload_test_suite, plugin_unload_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "plugin-unload" },
+        { "plugin",     "test"          }
+    });
+
+    wait_for([&] () {
+        return plugin_->unloaded;
+    });
+
+    BOOST_TEST(plugin_->unloaded);
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "plugin-unload" },
+        { "plugin",     "unknown"       }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == plugin_error::not_found);
+}
+
+BOOST_AUTO_TEST_CASE(exec_error)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "plugin-unload" },
+        { "plugin",     "broken"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == plugin_error::exec_error);
+    BOOST_ASSERT(!daemon_->plugins().has("broken"));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-add-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME rule_add_command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-add-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,205 @@
+/*
+ * main.cpp -- test rule-add remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "rule-add"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class rule_add_test : public command_test<rule_add_command> {
+public:
+    rule_add_test()
+    {
+        daemon_->commands().add(std::make_unique<rule_list_command>());
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_add_test_suite, rule_add_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "command",    "rule-add"          },
+        { "servers",    { "s1", "s2" }      },
+        { "channels",   { "c1", "c2" }      },
+        { "plugins",    { "p1", "p2" }      },
+        { "events",     { "onMessage" }     },
+        { "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({{"command", "rule-list"}});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    auto servers = result["list"][0]["servers"];
+    auto channels = result["list"][0]["channels"];
+    auto plugins = result["list"][0]["plugins"];
+    auto events = result["list"][0]["events"];
+
+    BOOST_TEST(json_util::contains(servers, "s1"));
+    BOOST_TEST(json_util::contains(servers, "s2"));
+    BOOST_TEST(json_util::contains(channels, "c1"));
+    BOOST_TEST(json_util::contains(channels, "c2"));
+    BOOST_TEST(json_util::contains(plugins, "p1"));
+    BOOST_TEST(json_util::contains(plugins, "p2"));
+    BOOST_TEST(json_util::contains(events, "onMessage"));
+    BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+}
+
+BOOST_AUTO_TEST_CASE(append)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "command",    "rule-add"          },
+        { "servers",    { "s1" }            },
+        { "channels",   { "c1" }            },
+        { "plugins",    { "p1" }            },
+        { "events",     { "onMessage" }     },
+        { "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({
+        { "command",    "rule-add"          },
+        { "servers",    { "s2" }            },
+        { "channels",   { "c2" }            },
+        { "plugins",    { "p2" }            },
+        { "events",     { "onMessage" }     },
+        { "action",     "drop"              },
+        { "index",      1                   }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    result = nullptr;
+    ctl_->send({{"command", "rule-list"}});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(result["list"].size() == 2U);
+
+    // Rule 0.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+    }
+
+    // Rule 1.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "drop");
+    }
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_action)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-add"  },
+        { "action",     "unknown"   }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_action);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-edit-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME rule-edit-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-edit-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,631 @@
+/*
+ * main.cpp -- test rule-edit remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "rule-edit"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class rule_edit_test : public command_test<rule_edit_command> {
+public:
+    rule_edit_test()
+    {
+        daemon_->commands().add(std::make_unique<rule_info_command>());
+        daemon_->rules().add(rule(
+            { "s1", "s2" },
+            { "c1", "c2" },
+            { "o1", "o2" },
+            { "p1", "p2" },
+            { "onMessage", "onCommand" },
+            rule::action_type::drop
+        ));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_edit_test_suite, rule_edit_test)
+
+BOOST_AUTO_TEST_CASE(add_server)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(add_channel)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(add_plugin)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(add_event)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(add_event_and_server)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(change_action)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(remove_server)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(remove_channel)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(remove_plugin)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(remove_event)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_CASE(remove_event_and_server)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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({
+        { "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_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_index_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-edit" },
+        { "index",      -100        },
+        { "action",     "drop"      }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-edit" },
+        { "index",      100         },
+        { "action",     "drop"      }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_3)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-edit" },
+        { "index",      "notaint"   },
+        { "action",     "drop"      }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_action)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-edit" },
+        { "index",      0           },
+        { "action",     "unknown"   }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_action);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-info-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME rule-info-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-info-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,157 @@
+/*
+ * main.cpp -- test rule-info remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "rule-info"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class rule_info_test : public command_test<rule_info_command> {
+public:
+    rule_info_test()
+    {
+        daemon_->rules().add(rule(
+            { "s1", "s2" },
+            { "c1", "c2" },
+            { "o1", "o2" },
+            { "p1", "p2" },
+            { "onMessage", "onCommand" },
+            rule::action_type::drop
+        ));
+        daemon_->rules().add(rule(
+            { "s1", },
+            { "c1", },
+            { "o1", },
+            { "p1", },
+            { "onMessage", },
+            rule::action_type::accept
+        ));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_info_test_suite, rule_info_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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"];
+
+    BOOST_TEST(json_util::contains(servers, "s1"));
+    BOOST_TEST(json_util::contains(servers, "s2"));
+    BOOST_TEST(json_util::contains(channels, "c1"));
+    BOOST_TEST(json_util::contains(channels, "c2"));
+    BOOST_TEST(json_util::contains(plugins, "p1"));
+    BOOST_TEST(json_util::contains(plugins, "p2"));
+    BOOST_TEST(json_util::contains(events, "onMessage"));
+    BOOST_TEST(json_util::contains(events, "onCommand"));
+    BOOST_TEST(result["action"].get<std::string>() == "drop");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_index_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-info" },
+        { "index",      -100        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-info" },
+        { "index",      100         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_3)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-info" },
+        { "index",      "notaint"   }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-list-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME rule-list-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-list-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,131 @@
+/*
+ * main.cpp -- test rule-list remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "rule-list"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class rule_list_test : public command_test<rule_list_command> {
+public:
+    rule_list_test()
+    {
+        daemon_->rules().add(rule(
+            { "s1", "s2" },
+            { "c1", "c2" },
+            { "o1", "o2" },
+            { "p1", "p2" },
+            { "onMessage", "onCommand" },
+            rule::action_type::drop
+        ));
+        daemon_->rules().add(rule(
+            { "s1", },
+            { "c1", },
+            { "o1", },
+            { "p1", },
+            { "onMessage", },
+            rule::action_type::accept
+        ));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_list_test_suite, rule_list_test)
+
+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();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(result["list"].is_array());
+    BOOST_TEST(result["list"].size() == 2U);
+
+    // Rule 0.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(json_util::contains(events, "onCommand"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "drop");
+    }
+
+    // Rule 1.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
+    }
+}
+
+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();
+    });
+
+    BOOST_TEST(result.is_object());
+    BOOST_TEST(result["list"].is_array());
+    BOOST_TEST(result["list"].empty());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-move-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME rule-move-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-move-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,471 @@
+/*
+ * main.cpp -- test rule-move remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "rule-move"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class rule_move_test : public command_test<rule_move_command> {
+public:
+    rule_move_test()
+    {
+        daemon_->commands().add(std::make_unique<rule_list_command>());
+        daemon_->rules().add(rule(
+            { "s0" },
+            { "c0" },
+            { "o0" },
+            { "p0" },
+            { "onMessage" },
+            rule::action_type::drop
+        ));
+        daemon_->rules().add(rule(
+            { "s1", },
+            { "c1", },
+            { "o1", },
+            { "p1", },
+            { "onMessage", },
+            rule::action_type::accept
+        ));
+        daemon_->rules().add(rule(
+            { "s2", },
+            { "c2", },
+            { "o2", },
+            { "p2", },
+            { "onMessage", },
+            rule::action_type::accept
+        ));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_move_test_suite, rule_move_test)
+
+BOOST_AUTO_TEST_CASE(backward)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       2           },
+        { "to",         0           }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    // Rule 2.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+    }
+
+    // Rule 0.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s0"));
+        BOOST_TEST(json_util::contains(channels, "c0"));
+        BOOST_TEST(json_util::contains(plugins, "p0"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "drop");
+    }
+
+    // Rule 1.
+    {
+        auto servers = result["list"][2]["servers"];
+        auto channels = result["list"][2]["channels"];
+        auto plugins = result["list"][2]["plugins"];
+        auto events = result["list"][2]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "accept");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(upward)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       0           },
+        { "to",         2           }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    // Rule 1.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+    }
+
+    // Rule 2.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
+    }
+
+    // Rule 0.
+    {
+        auto servers = result["list"][2]["servers"];
+        auto channels = result["list"][2]["channels"];
+        auto plugins = result["list"][2]["plugins"];
+        auto events = result["list"][2]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s0"));
+        BOOST_TEST(json_util::contains(channels, "c0"));
+        BOOST_TEST(json_util::contains(plugins, "p0"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "drop");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(same)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       1           },
+        { "to",         1           }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    // Rule 0.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s0"));
+        BOOST_TEST(json_util::contains(channels, "c0"));
+        BOOST_TEST(json_util::contains(plugins, "p0"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "drop");
+    }
+
+    // Rule 1.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
+    }
+
+    // Rule 2.
+    {
+        auto servers = result["list"][2]["servers"];
+        auto channels = result["list"][2]["channels"];
+        auto plugins = result["list"][2]["plugins"];
+        auto events = result["list"][2]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "accept");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(beyond)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       0           },
+        { "to",         123         }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    // Rule 1.
+    {
+        auto servers = result["list"][0]["servers"];
+        auto channels = result["list"][0]["channels"];
+        auto plugins = result["list"][0]["plugins"];
+        auto events = result["list"][0]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s1"));
+        BOOST_TEST(json_util::contains(channels, "c1"));
+        BOOST_TEST(json_util::contains(plugins, "p1"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][0]["action"].get<std::string>() == "accept");
+    }
+
+    // Rule 2.
+    {
+        auto servers = result["list"][1]["servers"];
+        auto channels = result["list"][1]["channels"];
+        auto plugins = result["list"][1]["plugins"];
+        auto events = result["list"][1]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s2"));
+        BOOST_TEST(json_util::contains(channels, "c2"));
+        BOOST_TEST(json_util::contains(plugins, "p2"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][1]["action"].get<std::string>() == "accept");
+    }
+
+    // Rule 0.
+    {
+        auto servers = result["list"][2]["servers"];
+        auto channels = result["list"][2]["channels"];
+        auto plugins = result["list"][2]["plugins"];
+        auto events = result["list"][2]["events"];
+
+        BOOST_TEST(json_util::contains(servers, "s0"));
+        BOOST_TEST(json_util::contains(channels, "c0"));
+        BOOST_TEST(json_util::contains(plugins, "p0"));
+        BOOST_TEST(json_util::contains(events, "onMessage"));
+        BOOST_TEST(result["list"][2]["action"].get<std::string>() == "drop");
+    }
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_index_1_from)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       -100        },
+        { "to",         0           }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_1_to)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       0           },
+        { "to",         -100        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_2_from)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       100         },
+        { "to",         0           }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_3_from)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       "notaint"   },
+        { "to",         0           }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_3_to)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-move" },
+        { "from",       0           },
+        { "to",         "notaint"   }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-remove-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME rule-remove-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/rule-remove-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,193 @@
+/*
+ * main.cpp -- test rule-remove remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "rule-remove"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/json_util.hpp>
+
+#include <irccd/command.hpp>
+#include <irccd/rule_service.hpp>
+
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class rule_remove_test : public command_test<rule_remove_command> {
+public:
+    rule_remove_test()
+    {
+        daemon_->commands().add(std::make_unique<rule_list_command>());
+        daemon_->rules().add(rule(
+            { "s1", "s2" },
+            { "c1", "c2" },
+            { "o1", "o2" },
+            { "p1", "p2" },
+            { "onMessage", "onCommand" },
+            rule::action_type::drop
+        ));
+        daemon_->rules().add(rule(
+            { "s1", },
+            { "c1", },
+            { "o1", },
+            { "p1", },
+            { "onMessage", },
+            rule::action_type::accept
+        ));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(rule_remove_test_suite, rule_remove_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "command",    "rule-remove"   },
+        { "index",      1               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+
+    result = nullptr;
+    ctl_->send({{ "command", "rule-list" }});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result["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"];
+
+    BOOST_TEST(json_util::contains(servers, "s1"));
+    BOOST_TEST(json_util::contains(servers, "s2"));
+    BOOST_TEST(json_util::contains(channels, "c1"));
+    BOOST_TEST(json_util::contains(channels, "c2"));
+    BOOST_TEST(json_util::contains(plugins, "p1"));
+    BOOST_TEST(json_util::contains(plugins, "p2"));
+    BOOST_TEST(json_util::contains(events, "onMessage"));
+    BOOST_TEST(json_util::contains(events, "onCommand"));
+    BOOST_TEST(result["list"][0]["action"].get<std::string>() == "drop");
+}
+
+BOOST_AUTO_TEST_CASE(empty)
+{
+    nlohmann::json result;
+
+    daemon_->rules().remove(0);
+    daemon_->rules().remove(0);
+
+    ctl_->send({
+        { "command",    "rule-remove"   },
+        { "index",      1               }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result.is_object());
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_index_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-remove"   },
+        { "index",      -100            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-remove"   },
+        { "index",      100             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_index_3)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "rule-remove"   },
+        { "index",      "notaint"       }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == rule_error::invalid_index);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-connect-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-connect-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-connect-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,300 @@
+/*
+ * main.cpp -- test server-connect remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-connect"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(server_connect_test_suite, command_test<server_connect_command>)
+
+BOOST_AUTO_TEST_CASE(minimal)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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");
+
+    BOOST_TEST(s);
+    BOOST_TEST(s->name() == "local");
+    BOOST_TEST(s->host() == "irc.example.org");
+    BOOST_TEST(s->port() == 6667U);
+}
+
+#if defined(HAVE_SSL)
+
+BOOST_AUTO_TEST_CASE(full)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "local2"            },
+        { "host",       "irc.example2.org"  },
+        { "password",   "nonono"            },
+        { "nickname",   "francis"           },
+        { "realname",   "the_francis"       },
+        { "username",   "frc"               },
+        { "ctcpVersion", "ultra bot"        },
+        { "commandChar", "::"               },
+        { "port",       18000               },
+        { "ssl",        true                },
+        { "sslVerify",  true                },
+        { "autoRejoin", true                },
+        { "joinInvite", true                }
+    });
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    auto s = daemon_->servers().get("local2");
+
+    BOOST_TEST(s);
+    BOOST_TEST(s->name() == "local2");
+    BOOST_TEST(s->host() == "irc.example2.org");
+    BOOST_TEST(s->port() == 18000U);
+    BOOST_TEST(s->password() == "nonono");
+    BOOST_TEST(s->nickname() == "francis");
+    BOOST_TEST(s->realname() == "the_francis");
+    BOOST_TEST(s->username() == "frc");
+    BOOST_TEST(s->command_char() == "::");
+    BOOST_TEST(s->ctcp_version() == "ultra bot");
+    BOOST_TEST(s->flags() & server::ssl);
+    BOOST_TEST(s->flags() & server::ssl_verify);
+    BOOST_TEST(s->flags() & server::auto_rejoin);
+    BOOST_TEST(s->flags() & server::join_invite);
+}
+
+#endif // !HAVE_SSL
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(already_exists)
+{
+    boost::system::error_code result;
+
+    daemon_->servers().add(std::make_unique<journal_server>(service_, "local"));
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "local"             },
+        { "host",       "127.0.0.1"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::already_exists);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_hostname_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_hostname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_hostname_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       123456              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_hostname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       ""                  },
+        { "host",       "127.0.0.1"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       123456              },
+        { "host",       "127.0.0.1"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_port_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       "127.0.0.1"         },
+        { "port",       "notaint"           }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_port_number);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_port_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       "127.0.0.1"         },
+        { "port",       -123                }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_port_number);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_port_3)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       "127.0.0.1"         },
+        { "port",       1000000             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_port_number);
+}
+
+#if !defined(HAVE_SSL)
+
+BOOST_AUTO_TEST_CASE(ssl_disabled)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       "127.0.0.1"         },
+        { "ssl",        true                }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::ssl_disabled);
+}
+
+#endif
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-disconnect-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-disconnect-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-disconnect-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,127 @@
+/*
+ * main.cpp -- test server-disconnect remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-disconnect"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_disconnect_test : public command_test<server_disconnect_command> {
+protected:
+    server_disconnect_test()
+    {
+        daemon_->servers().add(std::make_unique<journal_server>(service_, "s1"));
+        daemon_->servers().add(std::make_unique<journal_server>(service_, "s2"));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_disconnect_test_suite, server_disconnect_test)
+
+BOOST_AUTO_TEST_CASE(one)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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(!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;
+    });
+
+    wait_for([&] () {
+        return result.is_object();
+    });
+
+    BOOST_TEST(result["command"].get<std::string>() == "server-disconnect");
+    BOOST_TEST(!daemon_->servers().has("s1"));
+    BOOST_TEST(!daemon_->servers().has("s2"));
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-disconnect" },
+        { "server",     123456              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-disconnect" },
+        { "server",     "unknown"           }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-info-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-info-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-info-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,133 @@
+/*
+ * main.cpp -- test server-info remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-info"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(server_info_test_suite, command_test<server_info_command>)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    auto server = std::make_unique<journal_server>(service_, "test");
+
+    server->set_host("example.org");
+    server->set_port(8765);
+    server->set_password("none");
+    server->set_nickname("pascal");
+    server->set_username("psc");
+    server->set_realname("Pascal le grand frere");
+    server->set_ctcp_version("yeah");
+    server->set_command_char("@");
+    server->set_reconnect_tries(80);
+    server->set_ping_timeout(20000);
+
+    nlohmann::json result;
+
+    daemon_->servers().add(std::move(server));
+    ctl_->send({
+        { "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_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-info"   },
+        { "server",     123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-info"   },
+        { "server",     ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-info"   },
+        { "server",     "unknown"       }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-invite-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-invite-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-invite-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,218 @@
+/*
+ * main.cpp -- test server-invite remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-invite"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_invite_test : public command_test<server_invite_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_invite_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_invite_test_suite, server_invite_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "server-invite"     },
+        { "server",     "test"              },
+        { "target",     "francis"           },
+        { "channel",    "#music"            }
+    });
+
+    wait_for([this] () {
+        return !server_->cqueue().empty();
+    });
+
+    auto cmd = server_->cqueue().back();
+
+    BOOST_TEST(cmd["command"].get<std::string>() == "invite");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#music");
+    BOOST_TEST(cmd["target"].get<std::string>() == "francis");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     123456          },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     ""              },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "test"          },
+        { "target",     ""              },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "test"          },
+        { "target",     123456          },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "test"          },
+        { "target",     "jean"          },
+        { "channel",    ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "test"          },
+        { "target",     "jean"          },
+        { "channel",    123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "unknown"       },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-join-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-join-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-join-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,190 @@
+/*
+ * main.cpp -- test server-join remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-join"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_join_test : public command_test<server_join_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_join_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_join_test_suite, server_join_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "server-join"       },
+        { "server",     "test"              },
+        { "channel",    "#music"            },
+        { "password",   "plop"              }
+    });
+
+    wait_for([this] () {
+        return !server_->cqueue().empty();
+    });
+
+    auto cmd = server_->cqueue().back();
+
+    BOOST_TEST(cmd["command"].get<std::string>() == "join");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#music");
+    BOOST_TEST(cmd["password"].get<std::string>() == "plop");
+}
+
+BOOST_AUTO_TEST_CASE(nopassword)
+{
+    ctl_->send({
+        { "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");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#music");
+    BOOST_TEST(cmd["password"].get<std::string>() == "");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     123456          },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     ""              },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     "test"          },
+        { "channel",    ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     "test"          },
+        { "channel",    123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     "unknown"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-kick-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-kick-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-kick-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,241 @@
+/*
+ * main.cpp -- test server-kick remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-kick"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_kick_test : public command_test<server_kick_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_kick_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_kick_test_suite, server_kick_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "server-kick"       },
+        { "server",     "test"              },
+        { "target",     "francis"           },
+        { "channel",    "#staff"            },
+        { "reason",     "too noisy"         }
+    });
+
+    wait_for([this] () {
+        return !server_->cqueue().empty();
+    });
+
+    auto cmd = server_->cqueue().back();
+
+    BOOST_TEST(cmd["command"].get<std::string>() == "kick");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
+    BOOST_TEST(cmd["target"].get<std::string>() == "francis");
+    BOOST_TEST(cmd["reason"].get<std::string>() == "too noisy");
+}
+
+BOOST_AUTO_TEST_CASE(noreason)
+{
+    ctl_->send({
+        { "command",    "server-kick"       },
+        { "server",     "test"              },
+        { "target",     "francis"           },
+        { "channel",    "#staff"            }
+    });
+
+    wait_for([this] () {
+        return !server_->cqueue().empty();
+    });
+
+    auto cmd = server_->cqueue().back();
+
+    BOOST_TEST(cmd["command"].get<std::string>() == "kick");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
+    BOOST_TEST(cmd["target"].get<std::string>() == "francis");
+    BOOST_TEST(cmd["reason"].get<std::string>() == "");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     123456          },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     ""              },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "test"          },
+        { "target",     ""              },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "test"          },
+        { "target",     123456          },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "test"          },
+        { "target",     "jean"          },
+        { "channel",    ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "test"          },
+        { "target",     "jean"          },
+        { "channel",    123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "unknown"       },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-list-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-list-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-list-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,66 @@
+/*
+ * main.cpp -- test server-list remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-list"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_list_test : public command_test<server_list_command> {
+protected:
+    server_list_test()
+    {
+        daemon_->servers().add(std::make_unique<journal_server>(service_, "s1"));
+        daemon_->servers().add(std::make_unique<journal_server>(service_, "s2"));
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_list_test_suite, server_list_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    nlohmann::json result;
+
+    ctl_->send({{"command", "server-list"}});
+    ctl_->recv([&] (auto, auto msg) {
+        result = msg;
+    });
+
+    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_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-me-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-me-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-me-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,176 @@
+/*
+ * main.cpp -- test server-me remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-me"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_me_test : public command_test<server_me_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_me_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_me_test_suite, server_me_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "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");
+    BOOST_TEST(cmd["message"].get<std::string>() == "hello!");
+    BOOST_TEST(cmd["target"].get<std::string>() == "jean");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     123456      },
+        { "target",     "#music"    },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     ""          },
+        { "target",     "#music"    },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     "test"      },
+        { "target",     ""          },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     "test"      },
+        { "target",     123456      },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     "unknown"   },
+        { "target",     "#music"    },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-message-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-message-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-message-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,176 @@
+/*
+ * main.cpp -- test server-message remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-message"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_message_test : public command_test<server_message_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_message_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_message_test_suite, server_message_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "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");
+    BOOST_TEST(cmd["message"].get<std::string>() == "plop!");
+    BOOST_TEST(cmd["target"].get<std::string>() == "#staff");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     123456              },
+        { "target",     "#music"            },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     ""                  },
+        { "target",     "#music"            },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     "test"              },
+        { "target",     ""                  },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     "test"              },
+        { "target",     123456              },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     "unknown"           },
+        { "target",     "#music"            },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-mode-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-mode-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-mode-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,217 @@
+/*
+ * main.cpp -- test server-mode remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-mode"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_mode_test : public command_test<server_mode_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_mode_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_mode_test_suite, server_mode_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "test"          },
+        { "channel",    "#irccd"        },
+        { "mode",       "+t"            }
+    });
+
+    wait_for([this] () {
+        return !server_->cqueue().empty();
+    });
+
+    auto cmd = server_->cqueue().back();
+
+    BOOST_TEST(cmd["command"].get<std::string>() == "mode");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#irccd");
+    BOOST_TEST(cmd["mode"].get<std::string>() == "+t");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     123456          },
+        { "channel",    "#music"        },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     ""              },
+        { "channel",    "#music"        },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "test"          },
+        { "channel",    ""              },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "test"          },
+        { "channel",    123456          },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_mode_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "test"          },
+        { "channel",    "#music"        },
+        { "mode",       ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_mode);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_mode_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "test"          },
+        { "channel",    "#music"        },
+        { "mode",       123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_mode);
+}
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "unknown"       },
+        { "channel",    "#music"        },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-nick-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-nick-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-nick-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,171 @@
+/*
+ * main.cpp -- test server-nick remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-nick"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_nick_test : public command_test<server_nick_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_nick_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_nick_test_suite, server_nick_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    nlohmann::json result;
+
+    ctl_->send({
+        { "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(server_->nickname() == "chris");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     123456          },
+        { "nickname",   "chris"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     ""              },
+        { "nickname",   "chris"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     "test"          },
+        { "nickname",   ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     "test"          },
+        { "nickname",   123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     "unknown"       },
+        { "nickname",   "chris"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-notice-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-notice-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-notice-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,176 @@
+/*
+ * main.cpp -- test server-notice remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-notice"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_notice_test : public command_test<server_notice_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_notice_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_notice_test_suite, server_notice_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     "test"          },
+        { "target",     "#staff"        },
+        { "message",    "quiet!"        }
+    });
+
+    wait_for([this] () {
+        return !server_->cqueue().empty();
+    });
+
+    auto cmd = server_->cqueue().back();
+
+    BOOST_TEST(cmd["command"].get<std::string>() == "notice");
+    BOOST_TEST(cmd["message"].get<std::string>() == "quiet!");
+    BOOST_TEST(cmd["target"].get<std::string>() == "#staff");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     123456          },
+        { "target",     "#music"        },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     ""              },
+        { "target",     "#music"        },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     "test"          },
+        { "target",     ""              },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     "test"          },
+        { "target",     123456          },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     "unknown"       },
+        { "target",     "#music"        },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-part-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-part-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-part-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,190 @@
+/*
+ * main.cpp -- test server-part remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright part and this permission part appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-part"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_part_test : public command_test<server_part_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_part_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_part_test_suite, server_part_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     "test"          },
+        { "channel",    "#staff"        },
+        { "reason",     "too noisy"     }
+    });
+
+    wait_for([this] () {
+        return !server_->cqueue().empty();
+    });
+
+    auto cmd = server_->cqueue().back();
+
+    BOOST_TEST(cmd["command"].get<std::string>() == "part");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
+    BOOST_TEST(cmd["reason"].get<std::string>() == "too noisy");
+}
+
+BOOST_AUTO_TEST_CASE(noreason)
+{
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     "test"          },
+        { "channel",    "#staff"        }
+    });
+
+    wait_for([this] () {
+        return !server_->cqueue().empty();
+    });
+
+    auto cmd = server_->cqueue().back();
+
+    BOOST_TEST(cmd["command"].get<std::string>() == "part");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
+    BOOST_TEST(cmd["reason"].get<std::string>() == "");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     123456          },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     ""              },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     "test"          },
+        { "channel",    ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     "test"          },
+        { "channel",    123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     "unknown"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-reconnect-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-reconnect-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-reconnect-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,142 @@
+/*
+ * main.cpp -- test server-reconnect remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-reconnect"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_reconnect_test : public command_test<server_reconnect_command> {
+protected:
+    std::shared_ptr<journal_server> server1_{new journal_server(service_, "s1")};
+    std::shared_ptr<journal_server> server2_{new journal_server(service_, "s2")};
+
+    server_reconnect_test()
+    {
+        daemon_->servers().add(server1_);
+        daemon_->servers().add(server2_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_reconnect_test_suite, server_reconnect_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "server-reconnect"  },
+        { "server",     "s1"                }
+    });
+
+    wait_for([this] () {
+        return !server1_->cqueue().empty();
+    });
+
+    auto cmd1 = server1_->cqueue().back();
+
+    BOOST_TEST(cmd1["command"].get<std::string>() == "reconnect");
+    BOOST_TEST(server2_->cqueue().empty());
+}
+
+BOOST_AUTO_TEST_CASE(all)
+{
+    ctl_->send({{"command", "server-reconnect"}});
+
+    wait_for([this] () {
+        return !server1_->cqueue().empty() && !server2_->cqueue().empty();
+    });
+
+    auto cmd1 = server1_->cqueue().back();
+    auto cmd2 = server2_->cqueue().back();
+
+    BOOST_TEST(cmd1["command"].get<std::string>() == "reconnect");
+    BOOST_TEST(cmd2["command"].get<std::string>() == "reconnect");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-reconnect"  },
+        { "server",     123456              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-reconnect"  },
+        { "server",     ""                  }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-reconnect"  },
+        { "server",     "unknown"           }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-topic-command/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME server-topic-command
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/server-topic-command/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,176 @@
+/*
+ * main.cpp -- test server-topic remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "server-topic"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/server_service.hpp>
+
+#include <journal_server.hpp>
+#include <command_test.hpp>
+
+namespace irccd {
+
+namespace {
+
+class server_topic_test : public command_test<server_topic_command> {
+protected:
+    std::shared_ptr<journal_server> server_{new journal_server(service_, "test")};
+
+    server_topic_test()
+    {
+        daemon_->servers().add(server_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(server_topic_test_suite, server_topic_test)
+
+BOOST_AUTO_TEST_CASE(basic)
+{
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     "test"          },
+        { "channel",    "#staff"        },
+        { "topic",      "new version"   }
+    });
+
+    wait_for([this] () {
+        return !server_->cqueue().empty();
+    });
+
+    auto cmd = server_->cqueue().back();
+
+    BOOST_TEST(cmd["command"].get<std::string>() == "topic");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#staff");
+    BOOST_TEST(cmd["topic"].get<std::string>() == "new version");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     123456          },
+        { "channel",    "#music"        },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     ""              },
+        { "channel",    "#music"        },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     "test"          },
+        { "channel",    ""              },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     "test"          },
+        { "channel",    123456          },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     "unknown"       },
+        { "channel",    "#music"        },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/system-jsapi/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,24 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME system-jsapi
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+    FLAGS IRCCD_EXECUTABLE=\"$<TARGET_FILE:irccd>\"
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/system-jsapi/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,64 @@
+/*
+ * main.cpp -- test Irccd.System API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "System Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/irccd.hpp>
+#include <irccd/system.hpp>
+
+#include <irccd/js/file_jsapi.hpp>
+#include <irccd/js/system_jsapi.hpp>
+
+#include <js_test.hpp>
+
+namespace irccd {
+
+using fixture = js_test<file_jsapi, system_jsapi>;
+
+BOOST_FIXTURE_TEST_SUITE(system_jsapi_suite, fixture)
+
+BOOST_AUTO_TEST_CASE(home)
+{
+    duk_peval_string_noresult(plugin_->context(), "result = Irccd.System.home();");
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(),"result"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == sys::home());
+}
+
+#if defined(HAVE_POPEN)
+
+BOOST_AUTO_TEST_CASE(popen)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "f = Irccd.System.popen(\"" IRCCD_EXECUTABLE " --version\", \"r\");"
+        "r = f.readline();"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "r"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == IRCCD_VERSION);
+}
+
+#endif
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/timer-jsapi/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,24 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME timer-jsapi
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+    FLAGS DIRECTORY=\"${CMAKE_CURRENT_SOURCE_DIR}\"
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/timer-jsapi/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,90 @@
+/*
+ * main.cpp -- test Irccd.Timer API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "Timer Javascript API"
+#include <boost/test/unit_test.hpp>
+#include <boost/timer/timer.hpp>
+
+#include <irccd/js/plugin_jsapi.hpp>
+#include <irccd/js/timer_jsapi.hpp>
+
+#include <js_test.hpp>
+
+#include <irccd/js/logger_jsapi.hpp>
+
+namespace irccd {
+
+namespace {
+
+class js_timer_test : public js_test<plugin_jsapi, timer_jsapi> {
+public:
+    js_timer_test()
+        : js_test(CMAKE_CURRENT_SOURCE_DIR "/timer.js")
+    {
+    }
+
+    void set_type(const std::string& name)
+    {
+        duk_get_global_string(plugin_->context(), "Irccd");
+        duk_get_prop_string(plugin_->context(), -1, "Timer");
+        duk_get_prop_string(plugin_->context(), -1, name.c_str());
+        duk_put_global_string(plugin_->context(), "type");
+        duk_pop_n(plugin_->context(), 2);
+
+        plugin_->open();
+        plugin_->on_load(irccd_);
+    }
+};
+
+} // !namespace
+
+BOOST_FIXTURE_TEST_SUITE(js_timer_test_suite, js_timer_test)
+
+BOOST_AUTO_TEST_CASE(single)
+{
+    boost::timer::cpu_timer timer;
+
+    set_type("Single");
+
+    while (timer.elapsed().wall / 1000000LL < 3000) {
+        service_.reset();
+        service_.poll();
+    }
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "count"));
+    BOOST_TEST(duk_get_int(plugin_->context(), -1) == 1);
+}
+
+BOOST_AUTO_TEST_CASE(repeat)
+{
+    boost::timer::cpu_timer timer;
+
+    set_type("Repeat");
+
+    while (timer.elapsed().wall / 1000000LL < 3000) {
+        service_.reset();
+        service_.poll();
+    }
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "count"));
+    BOOST_TEST(duk_get_int(plugin_->context(), -1) >= 5);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/timer-jsapi/timer.js	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,13 @@
+count = 0;
+
+function onLoad()
+{
+    if (typeof (type) === "undefined")
+        throw Error("global timer type not defined");
+
+    t = new Irccd.Timer(type, 50, function () {
+        count += 1;
+    });
+
+    t.start();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/unicode-jsapi/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME unicode-jsapi
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/unicode-jsapi/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,69 @@
+/*
+ * main.cpp -- test Irccd.Unicode API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * /!\ Be sure that this file is kept saved in UTF-8 /!\
+ */
+
+#define BOOST_TEST_MODULE "Unicode Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/js/unicode_jsapi.hpp>
+
+#include <js_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(unicode_jsapi_suite, js_test<unicode_jsapi>)
+
+BOOST_AUTO_TEST_CASE(is_letter)
+{
+    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isLetter(String('é').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+
+    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isLetter(String('€').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(!duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(is_lower)
+{
+    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isLower(String('é').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+
+    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isLower(String('É').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(!duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(is_upper)
+{
+    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isUpper(String('É').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_boolean(plugin_->context(), -1));
+
+    duk_peval_string_noresult(plugin_->context(), "result = Irccd.Unicode.isUpper(String('é').charCodeAt(0));");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(!duk_get_boolean(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/util-jsapi/CMakeLists.txt	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME util-jsapi
+    SOURCES main.cpp
+    LIBRARIES libirccd-js
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/util-jsapi/main.cpp	Mon Dec 04 13:49:51 2017 +0100
@@ -0,0 +1,260 @@
+/*
+ * main.cpp -- test Irccd.Util API
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define BOOST_TEST_MODULE "Unicode Javascript API"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/js/util_jsapi.hpp>
+
+#include <js_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(util_jsapi_suite, js_test<util_jsapi>)
+
+/*
+ * Irccd.Util misc.
+ * ------------------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_CASE(format_simple)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "result = Irccd.Util.format(\"#{target}\", { target: \"markand\" })"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "markand");
+}
+
+BOOST_AUTO_TEST_CASE(splituser)
+{
+    if (duk_peval_string(plugin_->context(), "result = Irccd.Util.splituser(\"user!~user@hyper/super/host\");") != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "user");
+}
+
+BOOST_AUTO_TEST_CASE(splithost)
+{
+    if (duk_peval_string(plugin_->context(), "result = Irccd.Util.splithost(\"user!~user@hyper/super/host\");") != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "result"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "~user@hyper/super/host");
+}
+
+/*
+ * Irccd.Util.cut.
+ * ------------------------------------------------------------------
+ */
+
+BOOST_AUTO_TEST_CASE(cut_string_simple)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "lines = Irccd.Util.cut('hello world');\n"
+        "line0 = lines[0];\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_double)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "lines = Irccd.Util.cut('hello world', 5);\n"
+        "line0 = lines[0];\n"
+        "line1 = lines[1];\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line1"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_dirty)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "lines = Irccd.Util.cut('     hello    world     ', 5);\n"
+        "line0 = lines[0];\n"
+        "line1 = lines[1];\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line1"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_too_much_lines)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "lines = Irccd.Util.cut('abc def ghi jkl', 3, 3);\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "lines"));
+    BOOST_TEST(duk_is_undefined(plugin_->context(), -1));
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_token_too_big)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "try {\n"
+        "  lines = Irccd.Util.cut('hello world', 3);\n"
+        "} catch (e) {\n"
+        "  name = e.name;\n"
+        "  message = e.message;\n"
+        "}\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "RangeError");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "word 'hello' could not fit in maxc limit (3)");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_negative_maxc)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "try {\n"
+        "  lines = Irccd.Util.cut('hello world', -3);\n"
+        "} catch (e) {\n"
+        "  name = e.name;\n"
+        "  message = e.message;\n"
+        "}\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "RangeError");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "argument 1 (maxc) must be positive");
+}
+
+BOOST_AUTO_TEST_CASE(cut_string_negative_maxl)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "try {\n"
+        "  lines = Irccd.Util.cut('hello world', undefined, -1);\n"
+        "} catch (e) {\n"
+        "  name = e.name;\n"
+        "  message = e.message;\n"
+        "}\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "RangeError");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "message"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "argument 2 (maxl) must be positive");
+}
+
+BOOST_AUTO_TEST_CASE(cut_array_simple)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "lines = Irccd.Util.cut([ 'hello', 'world' ]);\n"
+        "line0 = lines[0];\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_array_double)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "lines = Irccd.Util.cut([ 'hello', 'world' ], 5);\n"
+        "line0 = lines[0];\n"
+        "line1 = lines[1];\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line1"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_array_dirty)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "lines = Irccd.Util.cut([ '   ', ' hello  ', '  world ', '    '], 5);\n"
+        "line0 = lines[0];\n"
+        "line1 = lines[1];\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line0"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "hello");
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "line1"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "world");
+}
+
+BOOST_AUTO_TEST_CASE(cut_invalid_data)
+{
+    auto ret = duk_peval_string(plugin_->context(),
+        "try {\n"
+        "  lines = Irccd.Util.cut(123);\n"
+        "} catch (e) {\n"
+        "  name = e.name;\n"
+        "  message = e.message;\n"
+        "}\n"
+    );
+
+    if (ret != 0)
+        throw dukx_stack(plugin_->context(), -1);
+
+    BOOST_TEST(duk_get_global_string(plugin_->context(), "name"));
+    BOOST_TEST(duk_get_string(plugin_->context(), -1) == "TypeError");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd