changeset 757:97b356010785

Irccd: create (command|jsapi)::registry Make two list of constructor functions to initialize all command and all Javascript modules to avoid duplicating efforts in case of change. While here, update test fixtures to load all of them for simplicity.
author David Demelier <markand@malikania.fr>
date Mon, 06 Aug 2018 21:27:00 +0200
parents 1b4f82c952d6
children 445c071e8efb
files irccd-test/main.cpp irccd/main.cpp libirccd-js/CMakeLists.txt libirccd-js/irccd/js/js_plugin.cpp libirccd-js/irccd/js/js_plugin.hpp libirccd-js/irccd/js/jsapi.cpp libirccd-js/irccd/js/jsapi.hpp libirccd-test/CMakeLists.txt libirccd-test/irccd/test/command_fixture.cpp libirccd-test/irccd/test/command_fixture.hpp libirccd-test/irccd/test/command_test.hpp libirccd-test/irccd/test/irccd_fixture.cpp libirccd-test/irccd/test/irccd_fixture.hpp libirccd-test/irccd/test/javascript_fixture.cpp libirccd-test/irccd/test/javascript_fixture.hpp libirccd-test/irccd/test/js_test.hpp libirccd/irccd/daemon/command.cpp libirccd/irccd/daemon/command.hpp libirccd/irccd/daemon/irccd.hpp libirccd/irccd/daemon/plugin_service.cpp libirccd/irccd/daemon/plugin_service.hpp libirccd/irccd/daemon/transport_service.cpp tests/src/libirccd-js/jsapi-directory/main.cpp tests/src/libirccd-js/jsapi-elapsedtimer/empty.js tests/src/libirccd-js/jsapi-elapsedtimer/main.cpp tests/src/libirccd-js/jsapi-file/empty.js tests/src/libirccd-js/jsapi-file/main.cpp tests/src/libirccd-js/jsapi-irccd/empty.js tests/src/libirccd-js/jsapi-irccd/main.cpp tests/src/libirccd-js/jsapi-logger/main.cpp tests/src/libirccd-js/jsapi-system/empty.js tests/src/libirccd-js/jsapi-system/main.cpp tests/src/libirccd-js/jsapi-timer/main.cpp tests/src/libirccd-js/jsapi-unicode/empty.js tests/src/libirccd-js/jsapi-unicode/main.cpp tests/src/libirccd-js/jsapi-util/empty.js tests/src/libirccd-js/jsapi-util/main.cpp tests/src/libirccd/command-plugin-config/main.cpp tests/src/libirccd/command-plugin-info/main.cpp tests/src/libirccd/command-plugin-list/main.cpp tests/src/libirccd/command-plugin-load/main.cpp tests/src/libirccd/command-plugin-reload/main.cpp tests/src/libirccd/command-plugin-unload/main.cpp tests/src/libirccd/command-rule-add/main.cpp tests/src/libirccd/command-rule-edit/main.cpp tests/src/libirccd/command-rule-info/main.cpp tests/src/libirccd/command-rule-list/main.cpp tests/src/libirccd/command-rule-move/main.cpp tests/src/libirccd/command-rule-remove/main.cpp tests/src/libirccd/command-server-connect/main.cpp tests/src/libirccd/command-server-disconnect/main.cpp tests/src/libirccd/command-server-info/main.cpp tests/src/libirccd/command-server-invite/main.cpp tests/src/libirccd/command-server-join/main.cpp tests/src/libirccd/command-server-kick/main.cpp tests/src/libirccd/command-server-list/main.cpp tests/src/libirccd/command-server-me/main.cpp tests/src/libirccd/command-server-message/main.cpp tests/src/libirccd/command-server-mode/main.cpp tests/src/libirccd/command-server-nick/main.cpp tests/src/libirccd/command-server-notice/main.cpp tests/src/libirccd/command-server-part/main.cpp tests/src/libirccd/command-server-reconnect/main.cpp tests/src/libirccd/command-server-topic/main.cpp
diffstat 58 files changed, 863 insertions(+), 820 deletions(-) [+]
line wrap: on
line diff
--- a/irccd-test/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/irccd-test/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -631,9 +631,15 @@
 void load(int argc, char** argv)
 {
     daemon = std::make_unique<irccd>(io);
+    daemon->plugins().add_loader(std::make_unique<dynlib_plugin_loader>());
 
-#if defined(IRCCD_HAVE_JS)
-    daemon->plugins().add_loader(js_plugin_loader::defaults(*daemon));
+#if defined(HAVE_JS)
+    auto loader = std::make_unique<js_plugin_loader>();
+
+    for (const auto& f : jsapi::registry)
+        daemon->get_modules().push_back(f());
+
+    daemon->plugins().add_loader(std::move(loader));
 #endif
 
     load_options(argc, argv);
--- a/irccd/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/irccd/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,9 +21,10 @@
 #include <csignal>
 #include <iostream>
 
+#include <boost/asio.hpp>
+
 #include <irccd/config.hpp>
 #include <irccd/options.hpp>
-#include <irccd/string_util.hpp>
 #include <irccd/system.hpp>
 
 #include <irccd/daemon/command.hpp>
@@ -31,12 +32,11 @@
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/logger.hpp>
 #include <irccd/daemon/plugin_service.hpp>
-#include <irccd/daemon/rule_service.hpp>
-#include <irccd/daemon/server_service.hpp>
 #include <irccd/daemon/transport_service.hpp>
 
-#if defined(IRCCD_HAVE_JS)
+#if defined(HAVE_JS)
 #   include <irccd/js/js_plugin.hpp>
+#   include <irccd/js/jsapi.hpp>
 #endif
 
 namespace irccd {
@@ -176,41 +176,23 @@
 
     init(argc, argv);
 
-    const auto options = parse(argc, argv);
+    // 1. Load commands.
+    for (const auto& f : command::registry)
+        instance->transports().get_commands().push_back(f());
+
+    // 2. Load plugin loaders.
+    instance->plugins().add_loader(std::make_unique<dynlib_plugin_loader>());
 
-    instance->transports().get_commands().push_back(std::make_unique<plugin_config_command>());
-    instance->transports().get_commands().push_back(std::make_unique<plugin_info_command>());
-    instance->transports().get_commands().push_back(std::make_unique<plugin_list_command>());
-    instance->transports().get_commands().push_back(std::make_unique<plugin_load_command>());
-    instance->transports().get_commands().push_back(std::make_unique<plugin_reload_command>());
-    instance->transports().get_commands().push_back(std::make_unique<plugin_unload_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_connect_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_disconnect_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_info_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_invite_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_join_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_kick_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_list_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_me_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_message_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_mode_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_nick_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_notice_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_part_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_reconnect_command>());
-    instance->transports().get_commands().push_back(std::make_unique<server_topic_command>());
-    instance->transports().get_commands().push_back(std::make_unique<rule_add_command>());
-    instance->transports().get_commands().push_back(std::make_unique<rule_edit_command>());
-    instance->transports().get_commands().push_back(std::make_unique<rule_info_command>());
-    instance->transports().get_commands().push_back(std::make_unique<rule_list_command>());
-    instance->transports().get_commands().push_back(std::make_unique<rule_move_command>());
-    instance->transports().get_commands().push_back(std::make_unique<rule_remove_command>());
+#if defined(HAVE_JS)
+    auto loader = std::make_unique<js_plugin_loader>();
 
-#if defined(IRCCD_HAVE_JS)
-    instance->plugins().add_loader(js_plugin_loader::defaults(*instance));
+    for (const auto& f : jsapi::registry)
+        loader->get_modules().push_back(f());
+
+    instance->plugins().add_loader(std::move(loader));
 #endif
 
-    instance->plugins().add_loader(std::make_unique<dynlib_plugin_loader>());
+    const auto options = parse(argc, argv);
 
     try {
         instance->set_config(open(options));
--- a/libirccd-js/CMakeLists.txt	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd-js/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
@@ -22,22 +22,22 @@
 
 set(
     HEADERS
+    ${libirccd-js_SOURCE_DIR}/irccd/js/directory_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/duktape.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/duktape_vector.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/directory_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/file_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/irccd_jsapi.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/logger_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/plugin_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/server_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/system_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/timer_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/jsapi.hpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/unicode_jsapi.hpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/util_jsapi.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.hpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.hpp
 )
 
 set(
@@ -46,15 +46,16 @@
     ${libirccd-js_SOURCE_DIR}/irccd/js/elapsed_timer_jsapi.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/file_jsapi.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/irccd_jsapi.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/jsapi.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/logger_jsapi.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/plugin_jsapi.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/server_jsapi.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/system_jsapi.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/timer_jsapi.cpp
+    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/unicode_jsapi.cpp
     ${libirccd-js_SOURCE_DIR}/irccd/js/util_jsapi.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/js_plugin.cpp
-    ${libirccd-js_SOURCE_DIR}/irccd/js/unicode.cpp
 )
 
 irccd_define_library(
--- a/libirccd-js/irccd/js/js_plugin.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd-js/irccd/js/js_plugin.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -324,25 +324,6 @@
     call("onWhois", event.server, event.whois);
 }
 
-auto js_plugin_loader::defaults(irccd& irccd) -> std::unique_ptr<js_plugin_loader>
-{
-    auto self = std::make_unique<js_plugin_loader>(irccd);
-
-    self->get_modules().push_back(std::make_unique<irccd_jsapi>());
-    self->get_modules().push_back(std::make_unique<directory_jsapi>());
-    self->get_modules().push_back(std::make_unique<elapsed_timer_jsapi>());
-    self->get_modules().push_back(std::make_unique<file_jsapi>());
-    self->get_modules().push_back(std::make_unique<logger_jsapi>());
-    self->get_modules().push_back(std::make_unique<plugin_jsapi>());
-    self->get_modules().push_back(std::make_unique<server_jsapi>());
-    self->get_modules().push_back(std::make_unique<system_jsapi>());
-    self->get_modules().push_back(std::make_unique<timer_jsapi>());
-    self->get_modules().push_back(std::make_unique<unicode_jsapi>());
-    self->get_modules().push_back(std::make_unique<util_jsapi>());
-
-    return self;
-}
-
 js_plugin_loader::js_plugin_loader(irccd& irccd) noexcept
     : plugin_loader({}, { ".js" })
     , irccd_(irccd)
@@ -351,12 +332,12 @@
 
 js_plugin_loader::~js_plugin_loader() noexcept = default;
 
-auto js_plugin_loader::get_modules() const noexcept -> const modules_t&
+auto js_plugin_loader::get_modules() const noexcept -> const modules&
 {
     return modules_;
 }
 
-auto js_plugin_loader::get_modules() noexcept -> modules_t&
+auto js_plugin_loader::get_modules() noexcept -> modules&
 {
     return modules_;
 }
--- a/libirccd-js/irccd/js/js_plugin.hpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd-js/irccd/js/js_plugin.hpp	Mon Aug 06 21:27:00 2018 +0200
@@ -244,22 +244,14 @@
  */
 class js_plugin_loader : public plugin_loader {
 public:
-    using modules_t = std::vector<std::unique_ptr<jsapi>>;
+    using modules = std::vector<std::unique_ptr<jsapi>>;
 
 private:
     irccd& irccd_;
-    modules_t modules_;
+    modules modules_;
 
 public:
     /**
-     * Create a plugin_loader with all irccd Javascript modules.
-     *
-     * \param irccd the irccd instance
-     * \return a ready to use plugin_loader
-     */
-    static auto defaults(irccd& irccd) -> std::unique_ptr<js_plugin_loader>;
-
-    /**
      * Constructor.
      *
      * \param irccd the irccd instance
@@ -276,14 +268,14 @@
      *
      * \return the modules
      */
-    auto get_modules() const noexcept -> const modules_t&;
+    auto get_modules() const noexcept -> const modules&;
 
     /**
      * Overloaded function.
      *
      * \return the modules
      */
-    auto get_modules() noexcept -> modules_t&;
+    auto get_modules() noexcept -> modules&;
 
     /**
      * \copydoc plugin_loader::open
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-js/irccd/js/jsapi.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -0,0 +1,60 @@
+/*
+ * jsapi.cpp -- Javascript API module
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "directory_jsapi.hpp"
+#include "elapsed_timer_jsapi.hpp"
+#include "file_jsapi.hpp"
+#include "irccd_jsapi.hpp"
+#include "logger_jsapi.hpp"
+#include "plugin_jsapi.hpp"
+#include "server_jsapi.hpp"
+#include "system_jsapi.hpp"
+#include "timer_jsapi.hpp"
+#include "unicode_jsapi.hpp"
+#include "util_jsapi.hpp"
+
+namespace irccd {
+
+namespace {
+
+template <typename T>
+auto bind() noexcept -> jsapi::factory
+{
+    return [] () noexcept {
+        return std::make_unique<T>();
+    };
+}
+
+} // !namespace
+
+const std::vector<jsapi::factory> jsapi::registry{
+    // Irccd API must be loaded first.
+    bind<irccd_jsapi>(),
+    bind<directory_jsapi>(),
+    bind<elapsed_timer_jsapi>(),
+    bind<file_jsapi>(),
+    bind<logger_jsapi>(),
+    bind<plugin_jsapi>(),
+    bind<server_jsapi>(),
+    bind<system_jsapi>(),
+    bind<timer_jsapi>(),
+    bind<unicode_jsapi>(),
+    bind<util_jsapi>()
+};
+
+} // !irccd
--- a/libirccd-js/irccd/js/jsapi.hpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd-js/irccd/js/jsapi.hpp	Mon Aug 06 21:27:00 2018 +0200
@@ -29,8 +29,10 @@
  * \brief Modules for the Javascript API.
  */
 
+#include <functional>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "duktape.hpp"
 
@@ -45,6 +47,16 @@
 class jsapi {
 public:
     /**
+     * \brief Command constructor factory.
+     */
+    using factory = std::function<auto () -> std::unique_ptr<jsapi>>;
+
+    /**
+     * \brief Registry of all commands.
+     */
+    static const std::vector<factory> registry;
+
+    /**
      * Default constructor.
      */
     jsapi() noexcept = default;
--- a/libirccd-test/CMakeLists.txt	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd-test/CMakeLists.txt	Mon Aug 06 21:27:00 2018 +0200
@@ -21,7 +21,8 @@
 set(
     HEADERS
     ${libirccd-test_SOURCE_DIR}/irccd/test/cli_test.hpp
-    ${libirccd-test_SOURCE_DIR}/irccd/test/command_test.hpp
+    ${libirccd-test_SOURCE_DIR}/irccd/test/command_fixture.hpp
+    ${libirccd-test_SOURCE_DIR}/irccd/test/irccd_fixture.hpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/debug_server.hpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/mock.hpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/mock_plugin.hpp
@@ -30,11 +31,13 @@
     ${libirccd-test_SOURCE_DIR}/irccd/test/rule_cli_test.hpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/server_cli_test.hpp
     $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/plugin_test.hpp>
-    $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/js_test.hpp>
+    $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/javascript_fixture.hpp>
 )
 
 set(
     SOURCES
+    ${libirccd-test_SOURCE_DIR}/irccd/test/command_fixture.cpp
+    ${libirccd-test_SOURCE_DIR}/irccd/test/irccd_fixture.cpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/cli_test.cpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/debug_server.cpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/mock.cpp
@@ -44,6 +47,7 @@
     ${libirccd-test_SOURCE_DIR}/irccd/test/rule_cli_test.cpp
     ${libirccd-test_SOURCE_DIR}/irccd/test/server_cli_test.cpp
     $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/plugin_test.cpp>
+    $<$<BOOL:${IRCCD_HAVE_JS}>:${libirccd-test_SOURCE_DIR}/irccd/test/javascript_fixture.cpp>
 )
 
 if (${IRCCD_HAVE_JS})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/command_fixture.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -0,0 +1,108 @@
+/*
+ * command_fixture.cpp -- test fixture helper for transport commands
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include <irccd/socket_acceptor.hpp>
+#include <irccd/socket_connector.hpp>
+
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/transport_server.hpp>
+#include <irccd/daemon/transport_service.hpp>
+
+#include "command_fixture.hpp"
+
+namespace irccd::test {
+
+template <typename Condition>
+void command_fixture::wait_for(Condition&& cond)
+{
+    ctx_.reset();
+
+    while (!cond())
+        ctx_.poll();
+}
+
+auto command_fixture::request(nlohmann::json json) -> result
+{
+    result r;
+
+    ctl_->write(std::move(json));
+    ctl_->read([&] (auto result, auto message) {
+        r.first = message;
+        r.second = result;
+    });
+    wait_for([&] {
+        return r.second || r.first.is_object();
+    });
+
+    return r;
+}
+
+command_fixture::command_fixture()
+    : server_(new mock_server(ctx_, "test", "localhost"))
+    , plugin_(new mock_plugin("test"))
+{
+    using boost::asio::ip::tcp;
+
+    // 1. Add all commands.
+    for (const auto& f : command::registry)
+        irccd_.transports().get_commands().push_back(f());
+
+    // 2. Bind to a random port.
+    tcp::endpoint ep(tcp::v4(), 0);
+    tcp::acceptor acc(ctx_, ep);
+
+    // 3. Create controller and transport server.
+    ctl_ = std::make_unique<ctl::controller>(
+        std::make_unique<io::ip_connector>(ctx_, acc.local_endpoint()));
+    irccd_.transports().add(std::make_unique<transport_server>(
+        std::make_unique<io::ip_acceptor>(std::move(acc))));
+
+    // Wait for controller to connect.
+    boost::asio::deadline_timer timer(ctx_);
+
+    timer.expires_from_now(boost::posix_time::seconds(10));
+    timer.async_wait([] (auto code) {
+        if (code && code != boost::asio::error::operation_aborted)
+            throw std::system_error(make_error_code(std::errc::timed_out));
+    });
+
+    bool connected = false;
+
+    ctl_->connect([&] (auto code, auto) {
+        timer.cancel();
+
+        if (code)
+            throw std::system_error(code);
+
+        connected = true;
+    });
+
+    /**
+     * Irccd will block indefinitely since transport_service will wait for any
+     * new client again, so we need to check with a boolean.
+     */
+    while (!connected)
+        ctx_.poll();
+
+    irccd_.servers().add(server_);
+    irccd_.plugins().add(plugin_);
+    server_->clear();
+    plugin_->clear();
+}
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/command_fixture.hpp	Mon Aug 06 21:27:00 2018 +0200
@@ -0,0 +1,87 @@
+/*
+ * command_fixture.hpp -- test fixture helper for transport commands
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_TEST_COMMAND_FIXTURE_HPP
+#define IRCCD_TEST_COMMAND_FIXTURE_HPP
+
+/**
+ * \file command_fixture.hpp
+ * \brief Test fixture helper for transport commands.
+ */
+
+#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/server_service.hpp>
+
+#include <irccd/ctl/controller.hpp>
+
+#include "irccd_fixture.hpp"
+#include "mock_server.hpp"
+#include "mock_plugin.hpp"
+
+namespace irccd::test {
+
+/**
+ * \brief Test fixture helper for transport commands.
+ *
+ * This fixture automatically adds a mock_server and mock_plugin named "test"
+ * and added to the respective services.
+ */
+class command_fixture : public irccd_fixture {
+private:
+    template <typename Condition>
+    void wait_for(Condition&&);
+
+protected:
+    /**
+     * \brief Result for request function.
+     */
+    using result = std::pair<nlohmann::json, std::error_code>;
+
+    /**
+     * \brief Irccd controller
+     */
+    std::unique_ptr<ctl::controller> ctl_;
+
+    /**
+     * \brief Mock server object.
+     */
+    std::shared_ptr<mock_server> server_;
+
+    /**
+     * \brief Mock plugin object.
+     */
+    std::shared_ptr<mock_plugin> plugin_;
+
+    /**
+     * Constructor.
+     */
+    command_fixture();
+
+    /**
+     * Get result from irccd.
+     *
+     * \param json the request
+     * \return the result/error pair
+     */
+    auto request(nlohmann::json json) -> result;
+};
+
+} // !irccd::test
+
+#endif // !IRCCD_TEST_COMMAND_FIXTURE_HPP
--- a/libirccd-test/irccd/test/command_test.hpp	Mon Aug 06 12:40:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,145 +0,0 @@
-/*
- * command_test.hpp -- test fixture helper for transport commands
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_TEST_COMMAND_TEST_HPP
-#define IRCCD_TEST_COMMAND_TEST_HPP
-
-#include <memory>
-
-#include <irccd/socket_acceptor.hpp>
-#include <irccd/socket_connector.hpp>
-
-#include <irccd/daemon/command.hpp>
-#include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/transport_server.hpp>
-#include <irccd/daemon/transport_service.hpp>
-
-#include <irccd/ctl/controller.hpp>
-
-namespace irccd {
-
-template <typename... Commands>
-class command_test {
-private:
-    template <typename Command>
-    inline void add()
-    {
-        daemon_->transports().get_commands().push_back(std::make_unique<Command>());
-    }
-
-    template <typename C1, typename C2, typename... Tail>
-    inline void add()
-    {
-        add<C1>();
-        add<C2, Tail...>();
-    }
-
-protected:
-    /**
-     * Result for request function.
-     */
-    using result = std::pair<nlohmann::json, std::error_code>;
-
-    boost::asio::io_service service_;
-
-    // daemon stuff.
-    std::unique_ptr<irccd> daemon_;
-
-    // controller stuff.
-    std::unique_ptr<ctl::controller> ctl_;
-
-    command_test();
-
-    template <typename Condition>
-    void wait_for(Condition&& cond)
-    {
-        service_.reset();
-
-        while (!cond())
-            service_.poll();
-    }
-
-    result request(nlohmann::json json)
-    {
-        result r;
-
-        ctl_->write(std::move(json));
-        ctl_->read([&] (auto result, auto message) {
-            r.first = message;
-            r.second = result;
-        });
-        wait_for([&] {
-            return r.second || r.first.is_object();
-        });
-
-        return r;
-    }
-};
-
-template <typename... Commands>
-command_test<Commands...>::command_test()
-    : daemon_(std::make_unique<irccd>(service_))
-{
-    using boost::asio::ip::tcp;
-
-    // Bind to a random port.
-    tcp::endpoint ep(tcp::v4(), 0);
-    tcp::acceptor acc(service_, ep);
-
-    // Create controller and transport server.
-    ctl_ = std::make_unique<ctl::controller>(
-        std::make_unique<io::ip_connector>(service_, acc.local_endpoint()));
-    daemon_->transports().add(std::make_unique<transport_server>(
-        std::make_unique<io::ip_acceptor>(std::move(acc))));
-
-    // Add the server and the command.
-    add<Commands...>();
-    daemon_->set_log(std::make_unique<logger::silent_sink>());
-
-    // Wait for controller to connect.
-    boost::asio::deadline_timer timer(service_);
-
-    timer.expires_from_now(boost::posix_time::seconds(10));
-    timer.async_wait([] (auto code) {
-        if (code && code != boost::asio::error::operation_aborted)
-            throw std::system_error(make_error_code(std::errc::timed_out));
-    });
-
-    bool connected = false;
-
-    ctl_->connect([&] (auto code, auto) {
-        timer.cancel();
-
-        if (code)
-            throw std::system_error(code);
-
-        connected = true;
-    });
-
-    /**
-     * Irccd will block indefinitely since transport_service will wait for any
-     * new client again, so we need to check with a boolean.
-     */
-    while (!connected)
-        service_.poll();
-}
-
-} // !irccd
-
-#endif // !IRCCD_TEST_COMMAND_TEST_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/irccd_fixture.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -0,0 +1,30 @@
+/*
+ * irccd_fixture.cpp -- test fixture for irccd
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include <irccd/daemon/logger.hpp>
+
+#include "irccd_fixture.hpp"
+
+namespace irccd::test {
+
+irccd_fixture::irccd_fixture()
+{
+    irccd_.set_log(std::make_unique<logger::silent_sink>());
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/irccd_fixture.hpp	Mon Aug 06 21:27:00 2018 +0200
@@ -0,0 +1,56 @@
+/*
+ * irccd_fixture.hpp -- test fixture for irccd
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_TEST_IRCCD_FIXTURE_HPP
+#define IRCCD_TEST_IRCCD_FIXTURE_HPP
+
+/**
+ * \file irccd_fixture.hpp
+ * \brief Test fixture for irccd.
+ */
+
+#include <irccd/daemon/irccd.hpp>
+
+namespace irccd::test {
+
+/**
+ * \brief Test fixture for irccd.
+ */
+class irccd_fixture {
+protected:
+    /**
+     * \brief Boost.Asio context.
+     */
+    boost::asio::io_context ctx_;
+
+    /**
+     * \brief Main irccd daemon.
+     */
+    irccd irccd_{ctx_};
+
+    /**
+     * Default constructor.
+     *
+     * Initialize the logger with a silent sink.
+     */
+    irccd_fixture();
+};
+
+} // !irccd::test
+
+#endif // !IRCCD_TEST_IRCCD_FIXTURE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/javascript_fixture.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -0,0 +1,33 @@
+/*
+ * javascript_fixture.cpp -- test fixture helper for Javascript modules
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#include "javascript_fixture.hpp"
+
+namespace irccd::test {
+
+javascript_fixture::javascript_fixture(const std::string& path)
+    : plugin_(new js_plugin("test", path))
+{
+    for (const auto& f : jsapi::registry)
+        f()->load(irccd_, plugin_);
+
+    if (!path.empty())
+        plugin_->open();
+}
+
+} // !irccd::test
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd-test/irccd/test/javascript_fixture.hpp	Mon Aug 06 21:27:00 2018 +0200
@@ -0,0 +1,69 @@
+/*
+ * javascript_fixture.hpp -- test fixture helper for Javascript modules
+ *
+ * Copyright (c) 2013-2018 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.
+ */
+
+#ifndef IRCCD_TEST_JAVASCRIPT_FIXTURE_HPP
+#define IRCCD_TEST_JAVASCRIPT_FIXTURE_HPP
+
+/**
+ * \file javascript_fixture.hpp
+ * \brief Test fixture helper for Javascript modules.
+ */
+
+#include "irccd_fixture.hpp"
+
+#include <irccd/js/js_plugin.hpp>
+#include <irccd/js/jsapi.hpp>
+
+#include <irccd/js/directory_jsapi.hpp>
+#include <irccd/js/elapsed_timer_jsapi.hpp>
+#include <irccd/js/file_jsapi.hpp>
+#include <irccd/js/irccd_jsapi.hpp>
+#include <irccd/js/jsapi.hpp>
+#include <irccd/js/logger_jsapi.hpp>
+#include <irccd/js/plugin_jsapi.hpp>
+#include <irccd/js/server_jsapi.hpp>
+#include <irccd/js/system_jsapi.hpp>
+#include <irccd/js/timer_jsapi.hpp>
+#include <irccd/js/unicode_jsapi.hpp>
+#include <irccd/js/util_jsapi.hpp>
+
+namespace irccd::test {
+
+/**
+ * \brief Test fixture helper for Javascript modules.
+ */
+class javascript_fixture : public irccd_fixture {
+protected:
+    /**
+     * \brief Javascript plugin.
+     */
+    std::shared_ptr<js_plugin> plugin_;
+
+    /**
+     * Constructor.
+     *
+     * Initialize a Javascript plugin with all Javascript API modules.
+     *
+     * \param path the path to a Javascript file if required
+     */
+    javascript_fixture(const std::string& path = "");
+};
+
+} // !irccd::test
+
+#endif // !IRCCD_TEST_JAVASCRIPT_FIXTURE_HPP
--- a/libirccd-test/irccd/test/js_test.hpp	Mon Aug 06 12:40:00 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-/*
- * js_test.hpp -- test fixture helper for Javascript modules
- *
- * Copyright (c) 2013-2018 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.
- */
-
-#ifndef IRCCD_TEST_JS_TEST_HPP
-#define IRCCD_TEST_JS_TEST_HPP
-
-/**
- * \file js_test.hpp
- * \brief Test fixture helper for Javascript modules.
- */
-
-#include <boost/asio.hpp>
-
-#include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/logger.hpp>
-
-#include <irccd/js/js_plugin.hpp>
-#include <irccd/js/irccd_jsapi.hpp>
-
-#include "mock_server.hpp"
-
-namespace irccd {
-
-/**
- * \brief Test fixture helper for Javascript modules.
- */
-template <typename... Modules>
-class js_test {
-private:
-    template <typename Module>
-    void add();
-
-    template <typename M1, typename M2, typename... Tail>
-    void add();
-
-protected:
-    boost::asio::io_service service_;
-    irccd irccd_{service_};                     //!< Irccd instance.
-    std::shared_ptr<js_plugin> plugin_;         //!< Javascript plugin.
-    std::shared_ptr<mock_server> server_;       //!< A mock server.
-
-    /**
-     * Constructor.
-     *
-     * Create a server and a test plugin.
-     */
-    js_test(const std::string& plugin_path = "");
-};
-
-template <typename... Modules>
-template <typename Module>
-void js_test<Modules...>::add()
-{
-    Module().load(irccd_, plugin_);
-}
-
-template <typename... Modules>
-template <typename M1, typename M2, typename... Tail>
-void js_test<Modules...>::add()
-{
-    add<M1>();
-    add<M2, Tail...>();
-}
-
-template <typename... Modules>
-js_test<Modules...>::js_test(const std::string& plugin_path)
-    : plugin_(new js_plugin("test", plugin_path))
-    , server_(new mock_server(service_, "test", "localhost"))
-{
-    irccd_.set_log(std::make_unique<logger::silent_sink>());
-
-    // Irccd is mandatory at the moment.
-    add<irccd_jsapi>();
-    add<Modules...>();
-
-    // Add some CMake variables.
-    duk_push_string(plugin_->get_context(), CMAKE_BINARY_DIR);
-    duk_put_global_string(plugin_->get_context(), "CMAKE_BINARY_DIR");
-    duk_push_string(plugin_->get_context(), CMAKE_SOURCE_DIR);
-    duk_put_global_string(plugin_->get_context(), "CMAKE_SOURCE_DIR");
-    duk_push_string(plugin_->get_context(), CMAKE_CURRENT_BINARY_DIR);
-    duk_put_global_string(plugin_->get_context(), "CMAKE_CURRENT_BINARY_DIR");
-    duk_push_string(plugin_->get_context(), CMAKE_CURRENT_SOURCE_DIR);
-    duk_put_global_string(plugin_->get_context(), "CMAKE_CURRENT_SOURCE_DIR");
-
-    if (!plugin_path.empty())
-        plugin_->open();
-}
-
-} // !irccd
-
-#endif // !IRCCD_TEST_JS_TEST_HPP
--- a/libirccd/irccd/daemon/command.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd/irccd/daemon/command.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -78,8 +78,47 @@
     });
 }
 
+template <typename T>
+auto bind() noexcept -> command::constructor
+{
+    return [] () noexcept {
+        return std::make_unique<T>();
+    };
+}
+
 } // !namespace
 
+const std::vector<command::constructor> command::registry{
+    bind<plugin_config_command>(),
+    bind<plugin_info_command>(),
+    bind<plugin_list_command>(),
+    bind<plugin_load_command>(),
+    bind<plugin_reload_command>(),
+    bind<plugin_unload_command>(),
+    bind<rule_add_command>(),
+    bind<rule_edit_command>(),
+    bind<rule_info_command>(),
+    bind<rule_info_command>(),
+    bind<rule_list_command>(),
+    bind<rule_move_command>(),
+    bind<rule_remove_command>(),
+    bind<server_connect_command>(),
+    bind<server_disconnect_command>(),
+    bind<server_info_command>(),
+    bind<server_invite_command>(),
+    bind<server_join_command>(),
+    bind<server_kick_command>(),
+    bind<server_list_command>(),
+    bind<server_me_command>(),
+    bind<server_message_command>(),
+    bind<server_mode_command>(),
+    bind<server_nick_command>(),
+    bind<server_notice_command>(),
+    bind<server_part_command>(),
+    bind<server_reconnect_command>(),
+    bind<server_topic_command>(),
+};
+
 // {{{ plugin_config_command
 
 auto plugin_config_command::get_name() const noexcept -> std::string_view
--- a/libirccd/irccd/daemon/command.hpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd/irccd/daemon/command.hpp	Mon Aug 06 21:27:00 2018 +0200
@@ -26,7 +26,10 @@
 
 #include <irccd/sysconfig.hpp>
 
+#include <functional>
+#include <memory>
 #include <string_view>
+#include <vector>
 
 #include <irccd/json_util.hpp>
 
@@ -43,11 +46,21 @@
 class command {
 public:
     /**
-     * Convenient alias.
+     * \brief Convenient alias.
      */
     using document = json_util::document;
 
     /**
+     * \brief Command constructor factory.
+     */
+    using constructor = std::function<auto () -> std::unique_ptr<command>>;
+
+    /**
+     * \brief Registry of all commands.
+     */
+    static const std::vector<constructor> registry;
+
+    /**
      * Default destructor virtual.
      */
     virtual ~command() = default;
--- a/libirccd/irccd/daemon/irccd.hpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd/irccd/daemon/irccd.hpp	Mon Aug 06 21:27:00 2018 +0200
@@ -87,10 +87,14 @@
 
 public:
     /**
-     * Prepare standard services.
+     * Constructor.
+     *
+     * This only create a barebone irccd instance.
      *
      * \param service the service
      * \param config the optional path to the configuration.
+     * \see load_all
+     * \see load_config
      */
     irccd(boost::asio::io_service& service, std::string config = "");
 
--- a/libirccd/irccd/daemon/plugin_service.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd/irccd/daemon/plugin_service.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -224,6 +224,19 @@
     exec(save, &plugin::handle_unload, irccd_);
 }
 
+void plugin_service::clear() noexcept
+{
+    while (plugins_.size() > 0) {
+        const auto plugin = plugins_[0];
+
+        try {
+            unload(plugin->get_id());
+        } catch (const std::exception& ex) {
+            irccd_.get_log().warning(*plugin) << ex.what() << std::endl;
+        }
+    }
+}
+
 void plugin_service::load(const config& cfg) noexcept
 {
     for (const auto& option : cfg.get("plugins")) {
--- a/libirccd/irccd/daemon/plugin_service.hpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd/irccd/daemon/plugin_service.hpp	Mon Aug 06 21:27:00 2018 +0200
@@ -238,6 +238,11 @@
     }
 
     /**
+     * Remove all plugins.
+     */
+    void clear() noexcept;
+
+    /**
      * Load all plugins.
      *
      * \param cfg the config
--- a/libirccd/irccd/daemon/transport_service.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/libirccd/irccd/daemon/transport_service.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -22,15 +22,13 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/command.hpp>
-#include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/logger.hpp>
-#include <irccd/daemon/transport_util.hpp>
-#include <irccd/daemon/transport_client.hpp>
-#include <irccd/daemon/transport_server.hpp>
-
-
+#include "command.hpp"
+#include "irccd.hpp"
+#include "logger.hpp"
+#include "transport_client.hpp"
+#include "transport_server.hpp"
 #include "transport_service.hpp"
+#include "transport_util.hpp"
 
 namespace irccd {
 
--- a/tests/src/libirccd-js/jsapi-directory/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-directory/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,15 +19,22 @@
 #define BOOST_TEST_MODULE "Directory Javascript API"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/js/directory_jsapi.hpp>
+#include <irccd/test/javascript_fixture.hpp>
 
-#include <irccd/test/js_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(directory_jsapi_suite, js_test<directory_jsapi>)
+class directory_javascript_fixture : public javascript_fixture {
+public:
+    directory_javascript_fixture()
+    {
+        dukx_push(plugin_->get_context(), CMAKE_SOURCE_DIR);
+        duk_put_global_string(plugin_->get_context(), "CMAKE_SOURCE_DIR");
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(directory_jsapi_suite, directory_javascript_fixture)
 
 BOOST_AUTO_TEST_CASE(constructor)
 {
@@ -48,4 +55,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-elapsedtimer/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-elapsedtimer/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,17 +21,15 @@
 
 #include <thread>
 
-#include <irccd/js/elapsed_timer_jsapi.hpp>
-
-#include <irccd/test/js_test.hpp>
+#include <irccd/test/javascript_fixture.hpp>
 
 using namespace std::chrono_literals;
 
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(elapsed_timer_jsapi_suite, js_test<elapsed_timer_jsapi>)
+BOOST_FIXTURE_TEST_SUITE(elapsed_timer_jsapi_suite, javascript_fixture)
 
 BOOST_AUTO_TEST_CASE(standard)
 {
@@ -52,4 +50,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-file/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-file/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -22,15 +22,23 @@
 #include <fstream>
 
 #include <irccd/js/duktape_vector.hpp>
-#include <irccd/js/file_jsapi.hpp>
 
-#include <irccd/test/js_test.hpp>
+#include <irccd/test/javascript_fixture.hpp>
 
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(file_jsapi_suite, js_test<file_jsapi>)
+class file_javascript_fixture : public javascript_fixture {
+public:
+    file_javascript_fixture()
+    {
+        dukx_push(plugin_->get_context(), CMAKE_SOURCE_DIR);
+        duk_put_global_string(plugin_->get_context(), "CMAKE_SOURCE_DIR");
+    }
+};
+
+BOOST_FIXTURE_TEST_SUITE(file_jsapi_suite, file_javascript_fixture)
 
 BOOST_AUTO_TEST_CASE(function_basename)
 {
@@ -83,7 +91,7 @@
 
 BOOST_AUTO_TEST_CASE(method_basename)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "result = f.basename();"
     );
@@ -97,7 +105,7 @@
 
 BOOST_AUTO_TEST_CASE(method_basename_closed)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "f.close();"
         "result = f.basename();"
@@ -112,7 +120,7 @@
 
 BOOST_AUTO_TEST_CASE(method_dirname)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "result = f.dirname();"
     );
@@ -126,7 +134,7 @@
 
 BOOST_AUTO_TEST_CASE(method_dirname_closed)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "f.close();"
         "result = f.dirname();"
@@ -141,7 +149,7 @@
 
 BOOST_AUTO_TEST_CASE(method_lines)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "result = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r').lines();"
     );
 
@@ -156,7 +164,7 @@
 
 BOOST_AUTO_TEST_CASE(method_seek1)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "f.seek(Irccd.File.SeekSet, 6);"
         "result = f.read(1);"
@@ -171,7 +179,7 @@
 
 BOOST_AUTO_TEST_CASE(method_seek1_closed)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "f.close();"
         "f.seek(Irccd.File.SeekSet, 4);"
@@ -188,7 +196,7 @@
 
 BOOST_AUTO_TEST_CASE(method_seek2)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_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);"
@@ -204,7 +212,7 @@
 
 BOOST_AUTO_TEST_CASE(method_seek2c_losed)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "f.close();"
         "f.seek(Irccd.File.SeekSet, 2);"
@@ -222,7 +230,7 @@
 
 BOOST_AUTO_TEST_CASE(method_seek3)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "f.seek(Irccd.File.SeekEnd, -2);"
         "result = f.read(1);"
@@ -237,7 +245,7 @@
 
 BOOST_AUTO_TEST_CASE(method_seek3_closed)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "f.close();"
         "f.seek(Irccd.File.SeekEnd, -2);"
@@ -254,7 +262,7 @@
 
 BOOST_AUTO_TEST_CASE(method_read1)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/file-1.txt', 'r');"
         "result = f.read();"
     );
@@ -268,7 +276,7 @@
 
 BOOST_AUTO_TEST_CASE(method_readline)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "result = [];"
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
         "for (var s; s = f.readline(); ) {"
@@ -287,7 +295,7 @@
 
 BOOST_AUTO_TEST_CASE(method_readline_closed)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "result = [];"
         "f = new Irccd.File(CMAKE_SOURCE_DIR + '/tests/root/lines.txt', 'r');"
         "f.close();"
@@ -309,4 +317,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-irccd/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-irccd/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,17 +19,17 @@
 #define BOOST_TEST_MODULE "Irccd Javascript API"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/test/js_test.hpp>
+#include <irccd/test/javascript_fixture.hpp>
 
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(irccd_jsapi_suite, js_test<irccd_jsapi>)
+BOOST_FIXTURE_TEST_SUITE(irccd_jsapi_suite, javascript_fixture)
 
 BOOST_AUTO_TEST_CASE(version)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "major = Irccd.version.major;"
         "minor = Irccd.version.minor;"
         "patch = Irccd.version.patch;"
@@ -48,7 +48,7 @@
 
 BOOST_AUTO_TEST_CASE(from_javascript)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "try {"
         "  throw new Irccd.SystemError(1, 'test');"
         "} catch (e) {"
@@ -85,7 +85,7 @@
 
     duk_put_global_string(plugin_->get_context(), "f");
 
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "try {"
         "  f();"
         "} catch (e) {"
@@ -113,4 +113,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-logger/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-logger/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,16 +21,13 @@
 
 #include <irccd/daemon/logger.hpp>
 
-#include <irccd/js/logger_jsapi.hpp>
-#include <irccd/js/plugin_jsapi.hpp>
+#include <irccd/test/javascript_fixture.hpp>
 
-#include <irccd/test/js_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class logger_test : public js_test<logger_jsapi, plugin_jsapi> {
+class logger_fixture : public javascript_fixture {
 protected:
     std::string line_info;
     std::string line_warning;
@@ -38,10 +35,10 @@
 
     class sample_sink : public logger::sink {
     private:
-        logger_test& test_;
+        logger_fixture& test_;
 
     public:
-        inline sample_sink(logger_test& test) noexcept
+        sample_sink(logger_fixture& test) noexcept
             : test_(test)
         {
         }
@@ -62,14 +59,14 @@
         }
     };
 
-    logger_test()
+    logger_fixture()
     {
         irccd_.set_log(std::make_unique<sample_sink>(*this));
         irccd_.get_log().set_verbose(true);
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(logger_jsapi_suite, logger_test)
+BOOST_FIXTURE_TEST_SUITE(logger_jsapi_suite, logger_fixture)
 
 BOOST_AUTO_TEST_CASE(info)
 {
@@ -103,4 +100,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-system/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-system/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,20 +21,13 @@
 
 #include <irccd/system.hpp>
 
-#include <irccd/daemon/irccd.hpp>
+#include <irccd/test/javascript_fixture.hpp>
 
-#include <irccd/js/file_jsapi.hpp>
-#include <irccd/js/system_jsapi.hpp>
-
-#include <irccd/test/js_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-using fixture = js_test<file_jsapi, system_jsapi>;
-
-BOOST_FIXTURE_TEST_SUITE(system_jsapi_suite, fixture)
+BOOST_FIXTURE_TEST_SUITE(system_jsapi_suite, javascript_fixture)
 
 BOOST_AUTO_TEST_CASE(home)
 {
@@ -66,4 +59,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-timer/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-timer/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -20,21 +20,16 @@
 #include <boost/test/unit_test.hpp>
 #include <boost/timer/timer.hpp>
 
-#include <irccd/js/plugin_jsapi.hpp>
-#include <irccd/js/timer_jsapi.hpp>
+#include <irccd/test/javascript_fixture.hpp>
 
-#include <irccd/test/js_test.hpp>
-
-#include <irccd/js/logger_jsapi.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class js_timer_test : public js_test<plugin_jsapi, timer_jsapi> {
+class javascript_timer_fixture : public javascript_fixture {
 public:
-    js_timer_test()
-        : js_test(CMAKE_CURRENT_SOURCE_DIR "/timer.js")
+    javascript_timer_fixture()
+        : javascript_fixture(CMAKE_CURRENT_SOURCE_DIR "/timer.js")
     {
     }
 
@@ -51,7 +46,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(js_timer_test_suite, js_timer_test)
+BOOST_FIXTURE_TEST_SUITE(js_timer_test_suite, javascript_timer_fixture)
 
 BOOST_AUTO_TEST_CASE(single)
 {
@@ -60,8 +55,8 @@
     set_type("Single");
 
     while (timer.elapsed().wall / 1000000LL < 3000) {
-        service_.reset();
-        service_.poll();
+        ctx_.reset();
+        ctx_.poll();
     }
 
     BOOST_TEST(duk_get_global_string(plugin_->get_context(), "count"));
@@ -75,8 +70,8 @@
     set_type("Repeat");
 
     while (timer.elapsed().wall / 1000000LL < 3000) {
-        service_.reset();
-        service_.poll();
+        ctx_.reset();
+        ctx_.poll();
     }
 
     BOOST_TEST(duk_get_global_string(plugin_->get_context(), "count"));
@@ -87,4 +82,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-unicode/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-unicode/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -23,15 +23,13 @@
 #define BOOST_TEST_MODULE "Unicode Javascript API"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/js/unicode_jsapi.hpp>
+#include <irccd/test/javascript_fixture.hpp>
 
-#include <irccd/test/js_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(unicode_jsapi_suite, js_test<unicode_jsapi>)
+BOOST_FIXTURE_TEST_SUITE(unicode_jsapi_suite, javascript_fixture)
 
 BOOST_AUTO_TEST_CASE(is_letter)
 {
@@ -70,4 +68,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd-js/jsapi-util/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd-js/jsapi-util/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,15 +19,13 @@
 #define BOOST_TEST_MODULE "Unicode Javascript API"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/js/util_jsapi.hpp>
+#include <irccd/test/javascript_fixture.hpp>
 
-#include <irccd/test/js_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(util_jsapi_suite, js_test<util_jsapi>)
+BOOST_FIXTURE_TEST_SUITE(util_jsapi_suite, javascript_fixture)
 
 /*
  * Irccd.Util misc.
@@ -36,7 +34,7 @@
 
 BOOST_AUTO_TEST_CASE(format_simple)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "result = Irccd.Util.format(\"#{target}\", { target: \"markand\" })"
     );
 
@@ -72,7 +70,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_string_simple)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "lines = Irccd.Util.cut('hello world');\n"
         "line0 = lines[0];\n"
     );
@@ -86,7 +84,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_string_double)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "lines = Irccd.Util.cut('hello world', 5);\n"
         "line0 = lines[0];\n"
         "line1 = lines[1];\n"
@@ -103,7 +101,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_string_dirty)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "lines = Irccd.Util.cut('     hello    world     ', 5);\n"
         "line0 = lines[0];\n"
         "line1 = lines[1];\n"
@@ -120,7 +118,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_string_too_much_lines)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "lines = Irccd.Util.cut('abc def ghi jkl', 3, 3);\n"
     );
 
@@ -133,7 +131,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_string_token_too_big)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "try {\n"
         "  lines = Irccd.Util.cut('hello world', 3);\n"
         "} catch (e) {\n"
@@ -153,7 +151,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_string_negative_maxc)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "try {\n"
         "  lines = Irccd.Util.cut('hello world', -3);\n"
         "} catch (e) {\n"
@@ -173,7 +171,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_string_negative_maxl)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "try {\n"
         "  lines = Irccd.Util.cut('hello world', undefined, -1);\n"
         "} catch (e) {\n"
@@ -193,7 +191,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_array_simple)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "lines = Irccd.Util.cut([ 'hello', 'world' ]);\n"
         "line0 = lines[0];\n"
     );
@@ -207,7 +205,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_array_double)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "lines = Irccd.Util.cut([ 'hello', 'world' ], 5);\n"
         "line0 = lines[0];\n"
         "line1 = lines[1];\n"
@@ -224,7 +222,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_array_dirty)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "lines = Irccd.Util.cut([ '   ', ' hello  ', '  world ', '    '], 5);\n"
         "line0 = lines[0];\n"
         "line1 = lines[1];\n"
@@ -241,7 +239,7 @@
 
 BOOST_AUTO_TEST_CASE(cut_invalid_data)
 {
-    auto ret = duk_peval_string(plugin_->get_context(),
+    const auto ret = duk_peval_string(plugin_->get_context(),
         "try {\n"
         "  lines = Irccd.Util.cut(123);\n"
         "} catch (e) {\n"
@@ -261,4 +259,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-plugin-config/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-plugin-config/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,21 +19,16 @@
 #define BOOST_TEST_MODULE "plugin-config"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_plugin.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(plugin_config_test_suite, command_test<plugin_config_command>)
+BOOST_FIXTURE_TEST_SUITE(plugin_config_test_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(set)
 {
-    daemon_->plugins().add(std::make_unique<mock_plugin>("test"));
-
     const auto [json, code] = request({
         { "command",    "plugin-config" },
         { "plugin",     "test"          },
@@ -41,7 +36,7 @@
         { "value",      "falsy"         }
     });
 
-    const auto config = daemon_->plugins().require("test")->get_options();
+    const auto config = irccd_.plugins().require("test")->get_options();
 
     BOOST_TEST(!code);
     BOOST_TEST(!config.empty());
@@ -56,7 +51,8 @@
         { "x1", "10" },
         { "x2", "20" }
     });
-    daemon_->plugins().add(std::move(plugin));
+    irccd_.plugins().clear();
+    irccd_.plugins().add(std::move(plugin));
 
     const auto [json, code] = request({
         { "command",    "plugin-config" },
@@ -77,7 +73,8 @@
         { "x1", "10" },
         { "x2", "20" }
     });
-    daemon_->plugins().add(std::move(plugin));
+    irccd_.plugins().clear();
+    irccd_.plugins().add(std::move(plugin));
 
     const auto [json, code] = request({
         { "command",    "plugin-config" },
@@ -120,4 +117,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-plugin-info/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-plugin-info/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,21 +19,16 @@
 #define BOOST_TEST_MODULE "plugin-info"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_plugin.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(plugin_info_test_suite, command_test<plugin_info_command>)
+BOOST_FIXTURE_TEST_SUITE(plugin_info_test_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    daemon_->plugins().add(std::make_unique<mock_plugin>("test"));
-
     const auto [json, code] = request({
         { "command",    "plugin-info"       },
         { "plugin",     "test"              },
@@ -77,4 +72,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-plugin-list/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-plugin-list/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,25 +19,23 @@
 #define BOOST_TEST_MODULE "plugin-list"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_plugin.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class plugin_list_test : public command_test<plugin_list_command> {
+class plugin_list_fixture : public command_fixture {
 public:
-    plugin_list_test()
+    plugin_list_fixture()
     {
-        daemon_->plugins().add(std::make_unique<mock_plugin>("t1"));
-        daemon_->plugins().add(std::make_unique<mock_plugin>("t2"));
+        irccd_.plugins().clear();
+        irccd_.plugins().add(std::make_unique<mock_plugin>("t1"));
+        irccd_.plugins().add(std::make_unique<mock_plugin>("t2"));
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(plugin_list_test_suite, plugin_list_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_list_fixture_suite, plugin_list_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -55,4 +53,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-plugin-load/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-plugin-load/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,12 +19,9 @@
 #define BOOST_TEST_MODULE "plugin-load"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_plugin.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
@@ -88,19 +85,20 @@
     }
 };
 
-class plugin_load_test : public command_test<plugin_load_command> {
+class plugin_load_fixture : public command_fixture {
 public:
-    plugin_load_test()
+    plugin_load_fixture()
     {
-        daemon_->plugins().add_loader(std::make_unique<sample_loader>());
-        daemon_->plugins().add_loader(std::make_unique<broken_loader>());
-        daemon_->plugins().add(std::make_unique<mock_plugin>("already"));
+        irccd_.plugins().add_loader(std::make_unique<sample_loader>());
+        irccd_.plugins().add_loader(std::make_unique<broken_loader>());
+        irccd_.plugins().clear();
+        irccd_.plugins().add(std::make_unique<mock_plugin>("already"));
     }
 };
 
 } // !namespace
 
-BOOST_FIXTURE_TEST_SUITE(plugin_load_test_suite, plugin_load_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_load_fixture_suite, plugin_load_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -110,7 +108,7 @@
     });
 
     BOOST_TEST(!code);
-    BOOST_TEST(daemon_->plugins().has("test"));
+    BOOST_TEST(irccd_.plugins().has("test"));
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
@@ -166,4 +164,4 @@
 
 BOOST_AUTO_TEST_SUITE_END()
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-plugin-reload/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-plugin-reload/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,12 +19,9 @@
 #define BOOST_TEST_MODULE "plugin-reload"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_plugin.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
@@ -46,21 +43,20 @@
     }
 };
 
-class plugin_reload_test : public command_test<plugin_reload_command> {
+class plugin_reload_fixture : public command_fixture {
 protected:
     std::shared_ptr<mock_plugin> plugin_;
 
-    plugin_reload_test()
+    plugin_reload_fixture()
         : plugin_(std::make_shared<mock_plugin>("test"))
     {
-        daemon_->plugins().add(plugin_);
-        daemon_->plugins().add(std::make_unique<broken_plugin>());
+        irccd_.plugins().clear();
+        irccd_.plugins().add(plugin_);
+        irccd_.plugins().add(std::make_unique<broken_plugin>());
     }
 };
 
-} // !namespace
-
-BOOST_FIXTURE_TEST_SUITE(plugin_reload_test_suite, plugin_reload_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_reload_fixture_suite, plugin_reload_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -114,4 +110,6 @@
 
 BOOST_AUTO_TEST_SUITE_END()
 
-} // !irccd
+} // !namespace
+
+} // !irccd::test
--- a/tests/src/libirccd/command-plugin-unload/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-plugin-unload/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,12 +19,9 @@
 #define BOOST_TEST_MODULE "plugin-unload"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_plugin.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
@@ -46,19 +43,20 @@
     }
 };
 
-class plugin_unload_test : public command_test<plugin_unload_command> {
+class plugin_unload_fixture : public command_fixture {
 protected:
     std::shared_ptr<mock_plugin> plugin_;
 
-    plugin_unload_test()
+    plugin_unload_fixture()
         : plugin_(std::make_shared<mock_plugin>("test"))
     {
-        daemon_->plugins().add(plugin_);
-        daemon_->plugins().add(std::make_unique<broken_plugin>());
+        irccd_.plugins().clear();
+        irccd_.plugins().add(plugin_);
+        irccd_.plugins().add(std::make_unique<broken_plugin>());
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(plugin_unload_test_suite, plugin_unload_test)
+BOOST_FIXTURE_TEST_SUITE(plugin_unload_fixture_suite, plugin_unload_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -106,7 +104,7 @@
     BOOST_TEST(code == plugin_error::exec_error);
     BOOST_TEST(json["error"].get<int>() == plugin_error::exec_error);
     BOOST_TEST(json["errorCategory"].get<std::string>() == "plugin");
-    BOOST_TEST(!daemon_->plugins().has("broken"));
+    BOOST_TEST(!irccd_.plugins().has("broken"));
 }
 
 BOOST_AUTO_TEST_SUITE_END()
@@ -115,4 +113,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-rule-add/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-rule-add/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,17 +21,13 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-using rule_add_test = command_test<rule_add_command, rule_list_command>;
-
-BOOST_FIXTURE_TEST_SUITE(rule_add_test_suite, rule_add_test)
+BOOST_FIXTURE_TEST_SUITE(rule_add_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -146,4 +142,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-rule-edit/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-rule-edit/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,19 +21,17 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class rule_edit_test : public command_test<rule_edit_command, rule_info_command> {
+class rule_edit_fixture : public command_fixture {
 public:
-    rule_edit_test()
+    rule_edit_fixture()
     {
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s1", "s2" },
             { "c1", "c2" },
             { "o1", "o2" },
@@ -44,7 +42,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(rule_edit_test_suite, rule_edit_test)
+BOOST_FIXTURE_TEST_SUITE(rule_edit_fixture_suite, rule_edit_fixture)
 
 BOOST_AUTO_TEST_CASE(add_server)
 {
@@ -400,4 +398,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-rule-info/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-rule-info/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,19 +21,17 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class rule_info_test : public command_test<rule_info_command> {
+class rule_info_fixture : public command_fixture {
 public:
-    rule_info_test()
+    rule_info_fixture()
     {
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s1", "s2" },
             { "c1", "c2" },
             { "o1", "o2" },
@@ -41,7 +39,7 @@
             { "onMessage", "onCommand" },
             rule::action::drop
         ));
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s1", },
             { "c1", },
             { "o1", },
@@ -52,7 +50,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(rule_info_test_suite, rule_info_test)
+BOOST_FIXTURE_TEST_SUITE(rule_info_fixture_suite, rule_info_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -122,4 +120,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-rule-list/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-rule-list/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,19 +21,17 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class rule_list_test : public command_test<rule_list_command> {
+class rule_list_fixture : public command_fixture {
 public:
-    rule_list_test()
+    rule_list_fixture()
     {
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s1", "s2" },
             { "c1", "c2" },
             { "o1", "o2" },
@@ -41,7 +39,7 @@
             { "onMessage", "onCommand" },
             rule::action::drop
         ));
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s1", },
             { "c1", },
             { "o1", },
@@ -52,7 +50,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(rule_list_test_suite, rule_list_test)
+BOOST_FIXTURE_TEST_SUITE(rule_list_fixture_suite, rule_list_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -98,8 +96,8 @@
 
 BOOST_AUTO_TEST_CASE(empty)
 {
-    daemon_->rules().remove(0);
-    daemon_->rules().remove(0);
+    irccd_.rules().remove(0);
+    irccd_.rules().remove(0);
 
     const auto [json, code] = request({{ "command", "rule-list" }});
 
@@ -113,4 +111,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-rule-move/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-rule-move/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,19 +21,17 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class rule_move_test : public command_test<rule_move_command, rule_list_command> {
+class rule_move_fixture : public command_fixture {
 public:
-    rule_move_test()
+    rule_move_fixture()
     {
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s0" },
             { "c0" },
             { "o0" },
@@ -41,7 +39,7 @@
             { "onMessage" },
             rule::action::drop
         ));
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s1", },
             { "c1", },
             { "o1", },
@@ -49,7 +47,7 @@
             { "onMessage", },
             rule::action::accept
         ));
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s2", },
             { "c2", },
             { "o2", },
@@ -60,7 +58,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(rule_move_test_suite, rule_move_test)
+BOOST_FIXTURE_TEST_SUITE(rule_move_fixture_suite, rule_move_fixture)
 
 BOOST_AUTO_TEST_CASE(backward)
 {
@@ -359,4 +357,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-rule-remove/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-rule-remove/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -21,19 +21,17 @@
 
 #include <irccd/json_util.hpp>
 
-#include <irccd/daemon/rule_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class rule_remove_test : public command_test<rule_remove_command, rule_list_command> {
+class rule_remove_fixture : public command_fixture {
 public:
-    rule_remove_test()
+    rule_remove_fixture()
     {
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s1", "s2" },
             { "c1", "c2" },
             { "o1", "o2" },
@@ -41,7 +39,7 @@
             { "onMessage", "onCommand" },
             rule::action::drop
         ));
-        daemon_->rules().add(rule(
+        irccd_.rules().add(rule(
             { "s1", },
             { "c1", },
             { "o1", },
@@ -52,7 +50,7 @@
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(rule_remove_test_suite, rule_remove_test)
+BOOST_FIXTURE_TEST_SUITE(rule_remove_fixture_suite, rule_remove_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -127,4 +125,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-connect/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-connect/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,16 +19,13 @@
 #define BOOST_TEST_MODULE "server-connect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_connect_test_suite, command_test<server_connect_command>)
+BOOST_FIXTURE_TEST_SUITE(server_connect_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(minimal)
 {
@@ -38,7 +35,7 @@
         { "host",       "irc.example.org"   }
     });
 
-    const auto s = daemon_->servers().get("local");
+    const auto s = irccd_.servers().get("local");
 
     BOOST_TEST(!code);
     BOOST_TEST(s);
@@ -68,7 +65,7 @@
         { "joinInvite", true                }
     });
 
-    const auto s = daemon_->servers().get("local2");
+    const auto s = irccd_.servers().get("local2");
 
     BOOST_TEST(!code);
     BOOST_TEST(s);
@@ -93,7 +90,7 @@
 
 BOOST_AUTO_TEST_CASE(already_exists)
 {
-    daemon_->servers().add(std::make_unique<mock_server>(service_, "local"));
+    irccd_.servers().add(std::make_unique<mock_server>(ctx_, "local"));
 
     const auto [json, code] = request({
         { "command",    "server-connect"    },
@@ -223,4 +220,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-disconnect/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-disconnect/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,30 +19,27 @@
 #define BOOST_TEST_MODULE "server-disconnect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/mock_server.hpp>
-#include <irccd/test/command_test.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_disconnect_test : public command_test<server_disconnect_command> {
+class server_disconnect_fixture : public command_fixture {
 protected:
     std::shared_ptr<mock_server> s1_;
     std::shared_ptr<mock_server> s2_;
 
-    server_disconnect_test()
-        : s1_(new mock_server(service_, "s1", "localhost"))
-        , s2_(new mock_server(service_, "s2", "localhost"))
+    server_disconnect_fixture()
+        : s1_(new mock_server(ctx_, "s1", "localhost"))
+        , s2_(new mock_server(ctx_, "s2", "localhost"))
     {
-        daemon_->servers().add(s1_);
-        daemon_->servers().add(s2_);
+        irccd_.servers().add(s1_);
+        irccd_.servers().add(s2_);
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(server_disconnect_test_suite, server_disconnect_test)
+BOOST_FIXTURE_TEST_SUITE(server_disconnect_fixture_suite, server_disconnect_fixture)
 
 BOOST_AUTO_TEST_CASE(one)
 {
@@ -54,8 +51,8 @@
     BOOST_TEST(!code);
     BOOST_TEST(json["command"].get<std::string>() == "server-disconnect");
     BOOST_TEST(s1_->find("disconnect").size() == 1U);
-    BOOST_TEST(!daemon_->servers().has("s1"));
-    BOOST_TEST(daemon_->servers().has("s2"));
+    BOOST_TEST(!irccd_.servers().has("s1"));
+    BOOST_TEST(irccd_.servers().has("s2"));
 }
 
 BOOST_AUTO_TEST_CASE(all)
@@ -66,8 +63,8 @@
     BOOST_TEST(json["command"].get<std::string>() == "server-disconnect");
     BOOST_TEST(s1_->find("disconnect").size() == 1U);
     BOOST_TEST(s2_->find("disconnect").size() == 1U);
-    BOOST_TEST(!daemon_->servers().has("s1"));
-    BOOST_TEST(!daemon_->servers().has("s2"));
+    BOOST_TEST(!irccd_.servers().has("s1"));
+    BOOST_TEST(!irccd_.servers().has("s2"));
 }
 
 BOOST_AUTO_TEST_SUITE(errors)
@@ -102,4 +99,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-info/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-info/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,20 +19,17 @@
 #define BOOST_TEST_MODULE "server-info"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-BOOST_FIXTURE_TEST_SUITE(server_info_test_suite, command_test<server_info_command>)
+BOOST_FIXTURE_TEST_SUITE(server_info_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
-    auto server = std::make_unique<mock_server>(service_, "test", "example.org");
+    auto server = std::make_unique<mock_server>(ctx_, "test", "example.org");
 
     server->set_port(8765);
     server->set_password("none");
@@ -43,7 +40,8 @@
     server->set_command_char("@");
     server->set_ping_timeout(20000);
 
-    daemon_->servers().add(std::move(server));
+    irccd_.servers().clear();
+    irccd_.servers().add(std::move(server));
 
     const auto [json, code] = request({
         { "command",    "server-info"       },
@@ -104,4 +102,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-invite/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-invite/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-invite"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_invite_test : public command_test<server_invite_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_invite_test()
-        : server_(new mock_server(service_, "test", "localhost"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_invite_test_suite, server_invite_test)
+BOOST_FIXTURE_TEST_SUITE(server_invite_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -164,4 +149,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-join/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-join/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-join"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_join_test : public command_test<server_join_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_join_test()
-        : server_(new mock_server(service_, "test", "localhost"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_join_test_suite, server_join_test)
+BOOST_FIXTURE_TEST_SUITE(server_join_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -160,4 +145,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-kick/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-kick/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-kick"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_kick_test : public command_test<server_kick_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_kick_test()
-        : server_(new mock_server(service_, "test", "localhost"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_kick_test_suite, server_kick_test)
+BOOST_FIXTURE_TEST_SUITE(server_kick_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -198,4 +183,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-list/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-list/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,25 +19,23 @@
 #define BOOST_TEST_MODULE "server-list"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_list_test : public command_test<server_list_command> {
+class server_list_fixture : public command_fixture {
 protected:
-    server_list_test()
+    server_list_fixture()
     {
-        daemon_->servers().add(std::make_unique<mock_server>(service_, "s1", "localhost"));
-        daemon_->servers().add(std::make_unique<mock_server>(service_, "s2", "localhost"));
+        irccd_.servers().clear();
+        irccd_.servers().add(std::make_unique<mock_server>(ctx_, "s1", "localhost"));
+        irccd_.servers().add(std::make_unique<mock_server>(ctx_, "s2", "localhost"));
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(server_list_test_suite, server_list_test)
+BOOST_FIXTURE_TEST_SUITE(server_list_fixture_suite, server_list_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -57,4 +55,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-me/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-me/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-me"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_me_test : public command_test<server_me_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_me_test()
-        : server_(new mock_server(service_, "test", "localhost"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_me_test_suite, server_me_test)
+BOOST_FIXTURE_TEST_SUITE(server_me_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -136,4 +121,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-message/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-message/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-message"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_message_test : public command_test<server_message_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_message_test()
-        : server_(new mock_server(service_, "test"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_message_test_suite, server_message_test)
+BOOST_FIXTURE_TEST_SUITE(server_message_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -136,4 +121,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-mode/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-mode/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-mode"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_mode_test : public command_test<server_mode_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_mode_test()
-        : server_(new mock_server(service_, "test", "localhost"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_mode_test_suite, server_mode_test)
+BOOST_FIXTURE_TEST_SUITE(server_mode_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -163,4 +148,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-nick/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-nick/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-nick"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_nick_test : public command_test<server_nick_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_nick_test()
-        : server_(new mock_server(service_, "test", "localhost"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_nick_test_suite, server_nick_test)
+BOOST_FIXTURE_TEST_SUITE(server_nick_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -127,4 +112,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-notice/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-notice/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-notice"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_notice_test : public command_test<server_notice_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_notice_test()
-        : server_(new mock_server(service_, "test", "localhost"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_notice_test_suite, server_notice_test)
+BOOST_FIXTURE_TEST_SUITE(server_notice_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -136,4 +121,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-part/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-part/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-part"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_part_test : public command_test<server_part_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_part_test()
-        : server_(new mock_server(service_, "test"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_part_test_suite, server_part_test)
+BOOST_FIXTURE_TEST_SUITE(server_part_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -146,4 +131,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-reconnect/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-reconnect/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,32 +19,30 @@
 #define BOOST_TEST_MODULE "server-reconnect"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_reconnect_test : public command_test<server_reconnect_command> {
+class server_reconnect_fixture : public command_fixture {
 protected:
     std::shared_ptr<mock_server> s1_;
     std::shared_ptr<mock_server> s2_;
 
-    server_reconnect_test()
-        : s1_(new mock_server(service_, "s1", "localhost"))
-        , s2_(new mock_server(service_, "s2", "localhost"))
+    server_reconnect_fixture()
+        : s1_(new mock_server(ctx_, "s1", "localhost"))
+        , s2_(new mock_server(ctx_, "s2", "localhost"))
     {
-        daemon_->servers().add(s1_);
-        daemon_->servers().add(s2_);
+        irccd_.servers().clear();
+        irccd_.servers().add(s1_);
+        irccd_.servers().add(s2_);
         s1_->clear();
         s2_->clear();
     }
 };
 
-BOOST_FIXTURE_TEST_SUITE(server_reconnect_test_suite, server_reconnect_test)
+BOOST_FIXTURE_TEST_SUITE(server_reconnect_fixture_suite, server_reconnect_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -114,4 +112,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test
--- a/tests/src/libirccd/command-server-topic/main.cpp	Mon Aug 06 12:40:00 2018 +0200
+++ b/tests/src/libirccd/command-server-topic/main.cpp	Mon Aug 06 21:27:00 2018 +0200
@@ -19,28 +19,13 @@
 #define BOOST_TEST_MODULE "server-topic"
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/daemon/server_service.hpp>
+#include <irccd/test/command_fixture.hpp>
 
-#include <irccd/test/command_test.hpp>
-#include <irccd/test/mock_server.hpp>
-
-namespace irccd {
+namespace irccd::test {
 
 namespace {
 
-class server_topic_test : public command_test<server_topic_command> {
-protected:
-    std::shared_ptr<mock_server> server_;
-
-    server_topic_test()
-        : server_(new mock_server(service_, "test", "localhost"))
-    {
-        daemon_->servers().add(server_);
-        server_->clear();
-    }
-};
-
-BOOST_FIXTURE_TEST_SUITE(server_topic_test_suite, server_topic_test)
+BOOST_FIXTURE_TEST_SUITE(server_topic_fixture_suite, command_fixture)
 
 BOOST_AUTO_TEST_CASE(basic)
 {
@@ -136,4 +121,4 @@
 
 } // !namespace
 
-} // !irccd
+} // !irccd::test