changeset 578:a8b892177909

Tests: add error code support in cmd-server-* tests
author David Demelier <markand@malikania.fr>
date Fri, 01 Dec 2017 21:34:21 +0100
parents 3d0dbc0bee7d
children 84ea13c850f4
files libirccd/irccd/command.cpp libirccd/irccd/command.hpp libirccd/irccd/server.cpp libirccd/irccd/server.hpp tests/cmd-server-connect/main.cpp tests/cmd-server-disconnect/main.cpp tests/cmd-server-info/main.cpp tests/cmd-server-invite/main.cpp tests/cmd-server-join/main.cpp tests/cmd-server-kick/main.cpp tests/cmd-server-me/main.cpp tests/cmd-server-message/main.cpp tests/cmd-server-mode/main.cpp tests/cmd-server-nick/main.cpp tests/cmd-server-notice/main.cpp tests/cmd-server-part/main.cpp tests/cmd-server-reconnect/main.cpp tests/cmd-server-topic/main.cpp
diffstat 18 files changed, 1794 insertions(+), 67 deletions(-) [+]
line wrap: on
line diff
--- a/libirccd/irccd/command.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/libirccd/irccd/command.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -153,12 +153,12 @@
     auto id = json_util::get_string(args, "server");
 
     if (!string_util::is_identifier(id))
-        throw server_error::invalid_identifier;
+        throw server_error(server_error::invalid_identifier);
 
     auto server = daemon.servers().get(id);
 
     if (!server)
-        throw server_error::not_found;
+        throw server_error(server_error::not_found);
 
     return server;
 }
@@ -278,9 +278,12 @@
         irccd.servers().clear();
     else {
         if (!it->is_string())
-            throw server_error(server_error::error::invalid_identifier);
-        if (!irccd.servers().has(it->get<std::string>()))
-            throw server_error(server_error::error::not_found);
+            throw server_error(server_error::invalid_identifier);
+
+        auto s = irccd.servers().get(it->get<std::string>());
+
+        if (!s)
+            throw server_error(server_error::not_found);
 
         irccd.servers().remove(it->get<std::string>());
     }
@@ -296,7 +299,7 @@
 void server_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
     auto response = nlohmann::json::object();
-    auto server = irccd.servers().require(json_util::require_identifier(args, "server"));
+    auto server = get_server(irccd, args);
 
     // General stuff.
     response.push_back({"command", "server-info"});
@@ -326,10 +329,15 @@
 
 void server_invite_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(json_util::require_identifier(args, "server"))->invite(
-        json_util::require_string(args, "target"),
-        json_util::require_string(args, "channel")
-    );
+    auto target = json_util::get_string(args, "target");
+    auto channel = json_util::get_string(args, "channel");
+
+    if (target.empty())
+        throw server_error(server_error::invalid_nickname);
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel);
+
+    get_server(irccd, args)->invite(target, channel);
     client.success("server-invite");
 }
 
@@ -340,10 +348,13 @@
 
 void server_join_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(json_util::require_identifier(args, "server"))->join(
-        json_util::require_string(args, "channel"),
-        json_util::get_string(args, "password")
-    );
+    auto channel = json_util::get_string(args, "channel");
+    auto password = json_util::get_string(args, "password");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel);
+
+    get_server(irccd, args)->join(channel, password);
     client.success("server-join");
 }
 
@@ -354,11 +365,16 @@
 
 void server_kick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(json_util::require_identifier(args, "server"))->kick(
-        json_util::require_string(args, "target"),
-        json_util::require_string(args, "channel"),
-        json_util::get_string(args, "reason")
-    );
+    auto target = json_util::get_string(args, "target");
+    auto channel = json_util::get_string(args, "channel");
+    auto reason = json_util::get_string(args, "reason");
+
+    if (target.empty())
+        throw server_error(server_error::invalid_nickname);
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel);
+
+    get_server(irccd, args)->kick(target, channel, reason);
     client.success("server-kick");
 }
 
@@ -388,10 +404,13 @@
 
 void server_me_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(json_util::require_identifier(args, "server"))->me(
-        json_util::require_string(args, "target"),
-        json_util::require_string(args, "message")
-    );
+    auto channel = json_util::get_string(args, "target");
+    auto message = json_util::get_string(args, "message");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel);
+
+    get_server(irccd, args)->me(channel, message);
     client.success("server-me");
 }
 
@@ -402,10 +421,13 @@
 
 void server_message_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(json_util::require_identifier(args, "server"))->message(
-        json_util::require_string(args, "target"),
-        json_util::require_string(args, "message")
-    );
+    auto channel = json_util::get_string(args, "target");
+    auto message = json_util::get_string(args, "message");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel);
+
+    get_server(irccd, args)->message(channel, message);
     client.success("server-message");
 }
 
@@ -439,9 +461,12 @@
 
 void server_nick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(json_util::require_identifier(args, "server"))->set_nickname(
-        json_util::require_string(args, "nickname")
-    );
+    auto nick = json_util::get_string(args, "nickname");
+
+    if (nick.empty())
+        throw server_error(server_error::invalid_nickname);
+
+    get_server(irccd, args)->set_nickname(nick);
     client.success("server-nick");
 }
 
@@ -452,10 +477,13 @@
 
 void server_notice_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(json_util::require_identifier(args, "server"))->notice(
-        json_util::require_string(args, "target"),
-        json_util::require_string(args, "message")
-    );
+    auto channel = json_util::get_string(args, "target");
+    auto message = json_util::get_string(args, "message");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel);
+
+    get_server(irccd, args)->notice(channel, message);
     client.success("server-notice");
 }
 
@@ -466,10 +494,13 @@
 
 void server_part_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(json_util::require_identifier(args, "server"))->part(
-        json_util::require_string(args, "channel"),
-        json_util::get_string(args, "reason")
-    );
+    auto channel = json_util::get_string(args, "channel");
+    auto reason = json_util::get_string(args, "reason");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel);
+
+    get_server(irccd, args)->part(channel, reason);
     client.success("server-part");
 }
 
@@ -482,11 +513,20 @@
 {
     auto server = args.find("server");
 
-    if (server != args.end() && server->is_string())
-        irccd.servers().require(*server)->reconnect();
-    else
+    if (server == args.end()) {
         for (auto& server : irccd.servers().servers())
             server->reconnect();
+    } else {
+        if (!server->is_string() || !string_util::is_identifier(server->get<std::string>()))
+            throw server_error(server_error::invalid_identifier);
+
+        auto s = irccd.servers().get(server->get<std::string>());
+
+        if (!s)
+            throw server_error(server_error::not_found);
+
+        s->reconnect();
+    }
 
     client.success("server-reconnect");
 }
@@ -498,10 +538,13 @@
 
 void server_topic_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    irccd.servers().require(json_util::require_identifier(args, "server"))->topic(
-        json_util::require_string(args, "channel"),
-        json_util::require_string(args, "topic")
-    );
+    auto channel = json_util::get_string(args, "channel");
+    auto topic = json_util::get_string(args, "topic");
+
+    if (channel.empty())
+        throw server_error(server_error::invalid_channel);
+
+    get_server(irccd, args)->topic(channel, topic);
     client.success("server-topic");
 }
 
--- a/libirccd/irccd/command.hpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/libirccd/irccd/command.hpp	Fri Dec 01 21:34:21 2017 +0100
@@ -210,6 +210,14 @@
 
 /**
  * \brief Implementation of server-connect transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::already_exists,
+ *   - server_error::invalid_hostname,
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_port_number,
+ *   - server_error::ssl_disabled.
  */
 class server_connect_command : public command {
 public:
@@ -226,6 +234,11 @@
 
 /**
  * \brief Implementation of server-disconnect transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
  */
 class server_disconnect_command : public command {
 public:
@@ -242,6 +255,11 @@
 
 /**
  * \brief Implementation of server-info transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
  */
 class server_info_command : public command {
 public:
@@ -258,6 +276,13 @@
 
 /**
  * \brief Implementation of server-invite transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_nickname,
+ *   - server_error::not_found.
  */
 class server_invite_command : public command {
 public:
@@ -274,6 +299,12 @@
 
 /**
  * \brief Implementation of server-join transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
  */
 class server_join_command : public command {
 public:
@@ -290,6 +321,13 @@
 
 /**
  * \brief Implementation of server-kick transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_nickname,
+ *   - server_error::not_found.
  */
 class server_kick_command : public command {
 public:
@@ -322,6 +360,12 @@
 
 /**
  * \brief Implementation of server-me transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
  */
 class server_me_command : public command {
 public:
@@ -338,6 +382,12 @@
 
 /**
  * \brief Implementation of server-message transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
  */
 class server_message_command : public command {
 public:
@@ -354,6 +404,13 @@
 
 /**
  * \brief Implementation of server-mode transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_mode,
+ *   - server_error::not_found.
  */
 class server_mode_command : public command {
 public:
@@ -370,6 +427,12 @@
 
 /**
  * \brief Implementation of server-nick transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_identifier,
+ *   - server_error::invalid_nickname,
+ *   - server_error::not_found.
  */
 class server_nick_command : public command {
 public:
@@ -386,6 +449,12 @@
 
 /**
  * \brief Implementation of server-notice transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
  */
 class server_notice_command : public command {
 public:
@@ -402,6 +471,12 @@
 
 /**
  * \brief Implementation of server-part transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
  */
 class server_part_command : public command {
 public:
@@ -418,6 +493,11 @@
 
 /**
  * \brief Implementation of server-reconnect transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
  */
 class server_reconnect_command : public command {
 public:
@@ -434,6 +514,12 @@
 
 /**
  * \brief Implementation of server-topic transport command.
+ *
+ * Replies:
+ *
+ *   - server_error::invalid_channel,
+ *   - server_error::invalid_identifier,
+ *   - server_error::not_found.
  */
 class server_topic_command : public command {
 public:
--- a/libirccd/irccd/server.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/libirccd/irccd/server.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -104,6 +104,48 @@
     return modes;
 }
 
+std::string from_json_get_id(const nlohmann::json& json)
+{
+    auto id = json_util::get_string(json, "name");
+
+    if (!string_util::is_identifier(id))
+        throw server_error(server_error::invalid_identifier);
+
+    return id;
+}
+
+std::string from_json_get_host(const nlohmann::json& json)
+{
+    auto host = json_util::get_string(json, "host");
+
+    if (host.empty())
+        throw server_error(server_error::invalid_hostname);
+
+    return host;
+}
+
+template <typename T>
+T from_json_get_uint(const nlohmann::json& json,
+                     const std::string& key,
+                     server_error::error error,
+                     T fallback)
+
+{
+    auto v = json.find(key);
+
+    if (v == json.end())
+        return fallback;
+    if (!v->is_number_integer())
+        throw server_error(error);
+
+    auto n = v->get<unsigned>();
+
+    if (n > std::numeric_limits<T>::max())
+        throw server_error(error);
+
+    return static_cast<T>(n);
+}
+
 } // !namespace
 
 void server::remove_joined_channel(const std::string& channel)
@@ -113,9 +155,12 @@
 
 std::shared_ptr<server> server::from_json(boost::asio::io_service& service, const nlohmann::json& object)
 {
-    auto sv = std::make_shared<server>(service, json_util::require_identifier(object, "name"));
+    // TODO: move this function in server_service.
+    auto sv = std::make_shared<server>(service, from_json_get_id(object));
 
-    sv->set_host(json_util::require_string(object, "host"));
+    sv->set_host(from_json_get_host(object));
+    sv->set_port(from_json_get_uint(object, "port",
+        server_error::invalid_port_number, sv->port()));
     sv->set_password(json_util::get_string(object, "password"));
     sv->set_nickname(json_util::get_string(object, "nickname", sv->nickname()));
     sv->set_realname(json_util::get_string(object, "realname", sv->realname()));
@@ -123,21 +168,22 @@
     sv->set_ctcp_version(json_util::get_string(object, "ctcpVersion", sv->ctcp_version()));
     sv->set_command_char(json_util::get_string(object, "commandChar", sv->command_char()));
 
-    if (object.find("port") != object.end())
-        sv->set_port(json_util::get_uint(object, "port"));
     if (json_util::get_bool(object, "ipv6"))
         sv->set_flags(sv->flags() | server::ipv6);
-#if defined(HAVE_SSL)
-    if (json_util::get_bool(object, "ssl"))
-        sv->set_flags(sv->flags() | server::ssl);
     if (json_util::get_bool(object, "sslVerify"))
         sv->set_flags(sv->flags() | server::ssl_verify);
-#endif
     if (json_util::get_bool(object, "autoRejoin"))
         sv->set_flags(sv->flags() | server::auto_rejoin);
     if (json_util::get_bool(object, "joinInvite"))
         sv->set_flags(sv->flags() | server::join_invite);
 
+    if (json_util::get_bool(object, "ssl"))
+#if defined(HAVE_SSL)
+        sv->set_flags(sv->flags() | server::ssl);
+#else
+        throw server_error(server_error::ssl_disabled);
+#endif
+
     return sv;
 }
 
@@ -729,12 +775,14 @@
                 return "invalid number of reconnection tries";
             case server_error::invalid_reconnect_timeout_number:
                 return "invalid reconnect timeout number";
-            case server_error::invalid_host:
+            case server_error::invalid_hostname:
                 return "invalid hostname";
             case server_error::invalid_channel:
                 return "invalid or empty channel";
             case server_error::invalid_mode:
                 return "invalid or empty mode";
+            case server_error::invalid_nickname:
+                return "invalid nickname";
             case server_error::ssl_disabled:
                 return "ssl is not enabled";
             default:
--- a/libirccd/irccd/server.hpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/libirccd/irccd/server.hpp	Fri Dec 01 21:34:21 2017 +0100
@@ -487,6 +487,16 @@
     virtual ~server();
 
     /**
+     * Get the current server state.
+     *
+     * \return the state
+     */
+    inline state_t state() const noexcept
+    {
+        return state_;
+    }
+
+    /**
      * Get the server identifier.
      *
      * \return the id
@@ -925,7 +935,7 @@
         invalid_reconnect_timeout_number,
 
         //!< The specified host was invalid.
-        invalid_host,
+        invalid_hostname,
 
         //!< The channel was empty or invalid.
         invalid_channel,
@@ -933,6 +943,9 @@
         //!< The mode given was empty.
         invalid_mode,
 
+        //!< The nickname was empty or invalid.
+        invalid_nickname,
+
         //!< SSL was requested but is disabled.
         ssl_disabled,
     };
--- a/tests/cmd-server-connect/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-connect/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -21,6 +21,7 @@
 
 #include <irccd/server_service.hpp>
 
+#include <journal_server.hpp>
 #include <command_test.hpp>
 
 namespace irccd {
@@ -102,6 +103,198 @@
 
 #endif // !HAVE_SSL
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(already_exists)
+{
+    boost::system::error_code result;
+
+    daemon_->servers().add(std::make_unique<journal_server>(service_, "local"));
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "local"             },
+        { "host",       "127.0.0.1"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::already_exists);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_hostname_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_hostname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_hostname_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       123456              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_hostname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       ""                  },
+        { "host",       "127.0.0.1"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       123456              },
+        { "host",       "127.0.0.1"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_port_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       "127.0.0.1"         },
+        { "port",       "notaint"           }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_port_number);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_port_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       "127.0.0.1"         },
+        { "port",       -123                }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_port_number);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_port_3)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       "127.0.0.1"         },
+        { "port",       1000000             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_port_number);
+}
+
+#if !defined(HAVE_SSL)
+
+BOOST_AUTO_TEST_CASE(ssl_disabled)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-connect"    },
+        { "name",       "new"               },
+        { "host",       "127.0.0.1"         },
+        { "ssl",        true                }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::ssl_disabled);
+}
+
+#endif
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-disconnect/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-disconnect/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -80,6 +80,48 @@
     BOOST_TEST(!daemon_->servers().has("s2"));
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-disconnect" },
+        { "server",     123456              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-disconnect" },
+        { "server",     "unknown"           }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-info/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-info/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -67,27 +67,67 @@
     BOOST_TEST(result["username"].get<std::string>() == "psc");
 }
 
-BOOST_AUTO_TEST_CASE(notfound)
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
 {
-    nlohmann::json result;
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-info"   },
+        { "server",     123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
 
     ctl_->send({
-        { "command",    "server-info"       },
-        { "server",     "test"              },
+        { "command",    "server-info"   },
+        { "server",     ""              }
     });
-    ctl_->recv([&] (auto, auto msg) {
-        result = msg;
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
     });
 
-    wait_for([&] () {
-        return result.is_object();
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-info"   },
+        { "server",     "unknown"       }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
     });
 
-    // TODO: error code
-    BOOST_TEST(result.is_object());
-    BOOST_TEST(result.count("error"));
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
 
+BOOST_AUTO_TEST_SUITE_END()
+
 } // !irccd
--- a/tests/cmd-server-invite/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-invite/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -62,6 +62,157 @@
     BOOST_TEST(cmd["target"].get<std::string>() == "francis");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     123456          },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     ""              },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "test"          },
+        { "target",     ""              },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "test"          },
+        { "target",     123456          },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "test"          },
+        { "target",     "jean"          },
+        { "channel",    ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "test"          },
+        { "target",     "jean"          },
+        { "channel",    123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-invite" },
+        { "server",     "unknown"       },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-join/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-join/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -81,6 +81,110 @@
     BOOST_TEST(cmd["password"].get<std::string>() == "");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     123456          },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     ""              },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     "test"          },
+        { "channel",    ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     "test"          },
+        { "channel",    123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-join"   },
+        { "server",     "unknown"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-kick/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-kick/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -85,6 +85,157 @@
     BOOST_TEST(cmd["reason"].get<std::string>() == "");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     123456          },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     ""              },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "test"          },
+        { "target",     ""              },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "test"          },
+        { "target",     123456          },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "test"          },
+        { "target",     "jean"          },
+        { "channel",    ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "test"          },
+        { "target",     "jean"          },
+        { "channel",    123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-kick"   },
+        { "server",     "unknown"       },
+        { "target",     "francis"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-me/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-me/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -62,6 +62,115 @@
     BOOST_TEST(cmd["target"].get<std::string>() == "jean");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     123456      },
+        { "target",     "#music"    },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     ""          },
+        { "target",     "#music"    },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     "test"      },
+        { "target",     ""          },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     "test"      },
+        { "target",     123456      },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-me" },
+        { "server",     "unknown"   },
+        { "target",     "#music"    },
+        { "message",    "hello!"    }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-message/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-message/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -62,6 +62,115 @@
     BOOST_TEST(cmd["target"].get<std::string>() == "#staff");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     123456              },
+        { "target",     "#music"            },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     ""                  },
+        { "target",     "#music"            },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     "test"              },
+        { "target",     ""                  },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     "test"              },
+        { "target",     123456              },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-message"    },
+        { "server",     "unknown"           },
+        { "target",     "#music"            },
+        { "message",    "plop!"             }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-mode/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-mode/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -47,6 +47,7 @@
     ctl_->send({
         { "command",    "server-mode"   },
         { "server",     "test"          },
+        { "channel",    "#irccd"        },
         { "mode",       "+t"            }
     });
 
@@ -57,9 +58,160 @@
     auto cmd = server_->cqueue().back();
 
     BOOST_TEST(cmd["command"].get<std::string>() == "mode");
+    BOOST_TEST(cmd["channel"].get<std::string>() == "#irccd");
     BOOST_TEST(cmd["mode"].get<std::string>() == "+t");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     123456          },
+        { "channel",    "#music"        },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     ""              },
+        { "channel",    "#music"        },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "test"          },
+        { "channel",    ""              },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "test"          },
+        { "channel",    123456          },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_mode_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "test"          },
+        { "channel",    "#music"        },
+        { "mode",       ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_mode);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_mode_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "test"          },
+        { "channel",    "#music"        },
+        { "mode",       123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_mode);
+}
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-mode"   },
+        { "server",     "unknown"       },
+        { "channel",    "#music"        },
+        { "mode",       "+i"            }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
+BOOST_AUTO_TEST_SUITE_END()
+
 } // !irccd
--- a/tests/cmd-server-nick/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-nick/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -60,9 +60,112 @@
     });
 
     BOOST_TEST(result.is_object());
-    BOOST_TEST(result.count("error") == 0U);
+    BOOST_TEST(server_->nickname() == "chris");
+}
+
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     123456          },
+        { "nickname",   "chris"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     ""              },
+        { "nickname",   "chris"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     "test"          },
+        { "nickname",   ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_nickname_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     "test"          },
+        { "nickname",   123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_nickname);
+}
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-nick"   },
+        { "server",     "unknown"       },
+        { "nickname",   "chris"         }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
 }
 
 BOOST_AUTO_TEST_SUITE_END()
 
+BOOST_AUTO_TEST_SUITE_END()
+
 } // !irccd
--- a/tests/cmd-server-notice/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-notice/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -62,6 +62,115 @@
     BOOST_TEST(cmd["target"].get<std::string>() == "#staff");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     123456          },
+        { "target",     "#music"        },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     ""              },
+        { "target",     "#music"        },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     "test"          },
+        { "target",     ""              },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     "test"          },
+        { "target",     123456          },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-notice" },
+        { "server",     "unknown"       },
+        { "target",     "#music"        },
+        { "message",    "quiet!"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-part/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-part/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -81,6 +81,110 @@
     BOOST_TEST(cmd["reason"].get<std::string>() == "");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     123456          },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     ""              },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     "test"          },
+        { "channel",    ""              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     "test"          },
+        { "channel",    123456          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-part"   },
+        { "server",     "unknown"       },
+        { "channel",    "#music"        }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-reconnect/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-reconnect/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -76,6 +76,67 @@
     BOOST_TEST(cmd2["command"].get<std::string>() == "reconnect");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-reconnect"  },
+        { "server",     123456              }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-reconnect"  },
+        { "server",     ""                  }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-reconnect"  },
+        { "server",     "unknown"           }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd
--- a/tests/cmd-server-topic/main.cpp	Thu Nov 30 13:19:38 2017 +0100
+++ b/tests/cmd-server-topic/main.cpp	Fri Dec 01 21:34:21 2017 +0100
@@ -62,6 +62,115 @@
     BOOST_TEST(cmd["topic"].get<std::string>() == "new version");
 }
 
+BOOST_AUTO_TEST_SUITE(errors)
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     123456          },
+        { "channel",    "#music"        },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_identifier_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     ""              },
+        { "channel",    "#music"        },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_identifier);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_1)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     "test"          },
+        { "channel",    ""              },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_channel_2)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     "test"          },
+        { "channel",    123456          },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::invalid_channel);
+}
+
+BOOST_AUTO_TEST_CASE(not_found)
+{
+    boost::system::error_code result;
+
+    ctl_->send({
+        { "command",    "server-topic"  },
+        { "server",     "unknown"       },
+        { "channel",    "#music"        },
+        { "topic",      "plop"          }
+    });
+    ctl_->recv([&] (auto code, auto) {
+        result = code;
+    });
+
+    wait_for([&] {
+        return result;
+    });
+
+    BOOST_ASSERT(result == server_error::not_found);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // !irccd