changeset 665:7ed23c858694

Tests: test irccdctl (rule-* commands) #785 While here, fix rule-edit which were triggering invalid_index because it was not added to the request. Also fix missing events from configuration file because they were forgotten. Finally, use std::set instead of std::unordered_set for a sorted output in rule-list and rule-info to ensure long-term compatibility in output.
author David Demelier <markand@malikania.fr>
date Thu, 29 Mar 2018 20:01:02 +0200
parents ce2748ffcf36
children c99780476eb7
files irccdctl/rule_edit_cli.cpp libirccd/irccd/daemon/command/rule_edit_command.cpp libirccd/irccd/daemon/rule.hpp libirccd/irccd/daemon/rule_util.cpp tests/CMakeLists.txt tests/data/irccd-multiple-rules.conf tests/data/irccd-plugins.conf tests/data/irccd-rules.conf tests/data/irccdctl.conf tests/src/irccdctl/CMakeLists.txt tests/src/irccdctl/cli-rule-add/CMakeLists.txt tests/src/irccdctl/cli-rule-add/main.cpp tests/src/irccdctl/cli-rule-edit/CMakeLists.txt tests/src/irccdctl/cli-rule-edit/main.cpp tests/src/irccdctl/cli-rule-info/CMakeLists.txt tests/src/irccdctl/cli-rule-info/main.cpp tests/src/irccdctl/cli-rule-list/CMakeLists.txt tests/src/irccdctl/cli-rule-list/main.cpp tests/src/irccdctl/cli-rule-move/CMakeLists.txt tests/src/irccdctl/cli-rule-move/main.cpp
diffstat 20 files changed, 816 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/irccdctl/rule_edit_cli.cpp	Thu Mar 29 19:42:16 2018 +0200
+++ b/irccdctl/rule_edit_cli.cpp	Thu Mar 29 20:01:02 2018 +0200
@@ -99,6 +99,8 @@
     if (!index)
         throw std::invalid_argument("invalid index argument");
 
+    json["index"] = *index;
+
     request(ctl, json);
 }
 
--- a/libirccd/irccd/daemon/command/rule_edit_command.cpp	Thu Mar 29 19:42:16 2018 +0200
+++ b/libirccd/irccd/daemon/command/rule_edit_command.cpp	Thu Mar 29 20:01:02 2018 +0200
@@ -48,12 +48,12 @@
         }
     };
 
-    // Create a copy to avoid incomplete edition in case of errors.
     const auto index = json_util::parser(args).get<unsigned>("index");
 
     if (!index)
         throw rule_error(rule_error::invalid_index);
 
+    // Create a copy to avoid incomplete edition in case of errors.
     auto rule = irccd.rules().require(*index);
 
     updateset(rule.get_channels(), args, "channels");
--- a/libirccd/irccd/daemon/rule.hpp	Thu Mar 29 19:42:16 2018 +0200
+++ b/libirccd/irccd/daemon/rule.hpp	Thu Mar 29 20:01:02 2018 +0200
@@ -27,8 +27,8 @@
 #include <irccd/sysconfig.hpp>
 
 #include <cassert>
+#include <set>
 #include <string>
-#include <unordered_set>
 
 #include <boost/system/system_error.hpp>
 
@@ -42,7 +42,7 @@
     /**
      * List of criterias.
      */
-    using set = std::unordered_set<std::string>;
+    using set = std::set<std::string>;
 
     /**
      * \brief Rule action type.
--- a/libirccd/irccd/daemon/rule_util.cpp	Thu Mar 29 19:42:16 2018 +0200
+++ b/libirccd/irccd/daemon/rule_util.cpp	Thu Mar 29 20:01:02 2018 +0200
@@ -16,8 +16,6 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <unordered_set>
-
 #include <irccd/ini.hpp>
 
 #include <irccd/daemon/rule.hpp>
@@ -32,7 +30,7 @@
 {
     // Simple converter from std::vector to std::unordered_set.
     const auto toset = [] (const auto& v) {
-        return std::unordered_set<std::string>(v.begin(), v.end());
+        return std::set<std::string>(v.begin(), v.end());
     };
 
     rule::set servers, channels, origins, plugins, events;
@@ -51,6 +49,8 @@
         plugins = toset(*it);
     if ((it = sc.find("channels")) != sc.end())
         channels = toset(*it);
+    if ((it = sc.find("events")) != sc.end())
+        events = toset(*it);
 
     // Get the action.
     auto actionstr = sc.get("action").value();
--- a/tests/CMakeLists.txt	Thu Mar 29 19:42:16 2018 +0200
+++ b/tests/CMakeLists.txt	Thu Mar 29 20:01:02 2018 +0200
@@ -16,14 +16,14 @@
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #
 
-# Project
 project(tests)
 
-# Configuration files for irccd/irccdctl
 set(
     CONFIGS
+    irccdctl.conf
+    irccd-multiple-rules.conf
     irccd-plugins.conf
-    irccdctl.conf
+    irccd-rules.conf
 )
 
 foreach (c ${CONFIGS})
@@ -34,13 +34,9 @@
     )
 endforeach ()
 
-# libirccd
 add_subdirectory(src/libirccd)
-
-# irccdctl
 add_subdirectory(src/irccdctl)
 
-# Javascript API and plugins.
 if (HAVE_JS)
     add_subdirectory(src/plugins)
     add_subdirectory(src/libirccd-js)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/data/irccd-multiple-rules.conf	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,26 @@
+[transport]
+type        = "unix"
+path        = "@CMAKE_BINARY_DIR@/tmp/irccd.sock"
+
+[rule]
+servers     = "s1"
+channels    = "c1"
+plugins     = "p1"
+events      = "onTopic"
+action      = "accept"
+
+[rule]
+servers     = "s2"
+channels    = "c2"
+plugins     = "p2"
+events      = "onCommand"
+action      = "drop"
+
+[rule]
+servers     = "s3"
+channels    = "c3"
+plugins     = "p3"
+events      = "onMessage"
+action      = "accept"
+
+# vim: ft=cfg:
--- a/tests/data/irccd-plugins.conf	Thu Mar 29 19:42:16 2018 +0200
+++ b/tests/data/irccd-plugins.conf	Thu Mar 29 20:01:02 2018 +0200
@@ -1,11 +1,13 @@
 [transport]
-type = "unix"
-path = "@CMAKE_BINARY_DIR@/tmp/irccd.sock"
+type    = "unix"
+path    = "@CMAKE_BINARY_DIR@/tmp/irccd.sock"
 
 [plugins]
-foo = "@tests_SOURCE_DIR@/data/foo.js"
-bar = "@tests_SOURCE_DIR@/data/bar.js"
+foo     = "@tests_SOURCE_DIR@/data/foo.js"
+bar     = "@tests_SOURCE_DIR@/data/bar.js"
 
 [plugin.bar]
-v1 = "123"
-v2 = "456"
+v1      = "123"
+v2      = "456"
+
+# vim: ft=cfg:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/data/irccd-rules.conf	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,12 @@
+[transport]
+type        = "unix"
+path        = "@CMAKE_BINARY_DIR@/tmp/irccd.sock"
+
+[rule]
+servers     = ( "s1", "s2" )
+channels    = ( "c1", "c2" )
+plugins     = ( "p1", "p2" )
+events      = ( "onCommand", "onMessage" )
+action      = "drop"
+
+# vim: ft=cfg:
--- a/tests/data/irccdctl.conf	Thu Mar 29 19:42:16 2018 +0200
+++ b/tests/data/irccdctl.conf	Thu Mar 29 20:01:02 2018 +0200
@@ -1,3 +1,5 @@
 [connect]
 type = "unix"
 path = "@CMAKE_BINARY_DIR@/tmp/irccd.sock"
+
+# vim: ft=cfg:
--- a/tests/src/irccdctl/CMakeLists.txt	Thu Mar 29 19:42:16 2018 +0200
+++ b/tests/src/irccdctl/CMakeLists.txt	Thu Mar 29 20:01:02 2018 +0200
@@ -24,3 +24,9 @@
     add_subdirectory(cli-plugin-reload)
     add_subdirectory(cli-plugin-unload)
 endif ()
+
+add_subdirectory(cli-rule-add)
+add_subdirectory(cli-rule-edit)
+add_subdirectory(cli-rule-info)
+add_subdirectory(cli-rule-list)
+add_subdirectory(cli-rule-move)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-add/CMakeLists.txt	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,24 @@
+#
+# CMakeLists.txt -- CMake build system 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.
+#
+
+irccd_define_test(
+    NAME cli-rule-add
+    SOURCES main.cpp
+    LIBRARIES libcommon
+    DEPENDS irccd irccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-add/main.cpp	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,196 @@
+/*
+ * main.cpp -- test irccdctl rule-add
+ *
+ * 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.
+ */
+
+#define BOOST_TEST_MODULE "irccdctl rule-add"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/test/cli_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(rule_add_suite, cli_test)
+
+BOOST_AUTO_TEST_CASE(all)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-add",
+            "-c tc1",       "--add-channel tc2",
+            "-e onMessage", "--add-event onCommand",
+            "-p tp1",       "--add-plugin tp2",
+            "-s ts1",       "--add-server ts2",
+            "drop"
+        });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 15U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+        BOOST_TEST(result.first[6]  == "");
+        BOOST_TEST(result.first[7]  == "rule:        1");
+        BOOST_TEST(result.first[8]  == "servers:     ts1 ts2 ");
+        BOOST_TEST(result.first[9]  == "channels:    tc1 tc2 ");
+        BOOST_TEST(result.first[10] == "plugins:     tp1 tp2 ");
+        BOOST_TEST(result.first[11] == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[12] == "action:      drop");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(server)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-add", "-s ts1", "--add-server ts2", "drop" });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 15U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+        BOOST_TEST(result.first[6]  == "");
+        BOOST_TEST(result.first[7]  == "rule:        1");
+        BOOST_TEST(result.first[8]  == "servers:     ts1 ts2 ");
+        BOOST_TEST(result.first[9]  == "channels:    ");
+        BOOST_TEST(result.first[10] == "plugins:     ");
+        BOOST_TEST(result.first[11] == "events:      ");
+        BOOST_TEST(result.first[12] == "action:      drop");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(channel)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-add", "-c tc1", "--add-channel tc2", "drop" });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 15U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+        BOOST_TEST(result.first[6]  == "");
+        BOOST_TEST(result.first[7]  == "rule:        1");
+        BOOST_TEST(result.first[8]  == "servers:     ");
+        BOOST_TEST(result.first[9]  == "channels:    tc1 tc2 ");
+        BOOST_TEST(result.first[10] == "plugins:     ");
+        BOOST_TEST(result.first[11] == "events:      ");
+        BOOST_TEST(result.first[12] == "action:      drop");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(plugin)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-add", "-p tp1", "--add-plugin tp2", "drop" });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 15U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+        BOOST_TEST(result.first[6]  == "");
+        BOOST_TEST(result.first[7]  == "rule:        1");
+        BOOST_TEST(result.first[8]  == "servers:     ");
+        BOOST_TEST(result.first[9]  == "channels:    ");
+        BOOST_TEST(result.first[10] == "plugins:     tp1 tp2 ");
+        BOOST_TEST(result.first[11] == "events:      ");
+        BOOST_TEST(result.first[12] == "action:      drop");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(event)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-add", "-e onMessage", "--add-event onCommand", "drop" });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 15U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+        BOOST_TEST(result.first[6]  == "");
+        BOOST_TEST(result.first[7]  == "rule:        1");
+        BOOST_TEST(result.first[8]  == "servers:     ");
+        BOOST_TEST(result.first[9]  == "channels:    ");
+        BOOST_TEST(result.first[10] == "plugins:     ");
+        BOOST_TEST(result.first[11] == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[12] == "action:      drop");
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-edit/CMakeLists.txt	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,24 @@
+#
+# CMakeLists.txt -- CMake build system 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.
+#
+
+irccd_define_test(
+    NAME cli-rule-edit
+    SOURCES main.cpp
+    LIBRARIES libcommon
+    DEPENDS irccd irccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-edit/main.cpp	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,197 @@
+/*
+ * main.cpp -- test irccdctl rule-edit
+ *
+ * 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.
+ */
+
+#define BOOST_TEST_MODULE "irccdctl rule-edit"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/test/cli_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(rule_edit_suite, cli_test)
+
+BOOST_AUTO_TEST_CASE(server)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-edit",
+            "-s ts1",  "--add-server ts2",
+            "-S s1",    "--remove-server s2",
+            "0"
+        });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 8U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     ts1 ts2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(channel)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-edit",
+            "-c tc1",   "--add-channel tc2",
+            "-C c1",    "--remove-channel c2",
+            "0"
+        });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 8U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    tc1 tc2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(plugin)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-edit",
+            "-p tp1",   "--add-plugin tp2",
+            "-P p1",    "--remove-plugin p2",
+            "0"
+        });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 8U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     tp1 tp2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(event)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-edit",
+            "-e onKick",    "--add-event onNickname",
+            "-E onMessage", "--remove-event onCommand",
+            "0"
+        });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 8U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onKick onNickname ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(action_1)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-edit", "-a accept", "0" });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 8U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      accept");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(action_2)
+{
+    run_irccd("irccd-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-edit", "--action accept", "0" });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 8U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      accept");
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-info/CMakeLists.txt	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,24 @@
+#
+# CMakeLists.txt -- CMake build system 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.
+#
+
+irccd_define_test(
+    NAME cli-rule-info
+    SOURCES main.cpp
+    LIBRARIES libcommon
+    DEPENDS irccd irccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-info/main.cpp	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,44 @@
+/*
+ * main.cpp -- test irccdctl rule-info
+ *
+ * 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.
+ */
+
+#define BOOST_TEST_MODULE "irccdctl rule-info"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/test/cli_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(rule_info_suite, cli_test)
+
+BOOST_AUTO_TEST_CASE(info)
+{
+    const auto result = run("irccd-rules.conf", { "rule-info", "0" });
+
+    BOOST_TEST(result.first.size() == 8U);
+    BOOST_TEST(result.second.size() == 0U);
+    BOOST_TEST(result.first[0]  == "rule:        0");
+    BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+    BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+    BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+    BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+    BOOST_TEST(result.first[5]  == "action:      drop");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-list/CMakeLists.txt	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,24 @@
+#
+# CMakeLists.txt -- CMake build system 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.
+#
+
+irccd_define_test(
+    NAME cli-rule-list
+    SOURCES main.cpp
+    LIBRARIES libcommon
+    DEPENDS irccd irccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-list/main.cpp	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,44 @@
+/*
+ * main.cpp -- test irccdctl rule-list
+ *
+ * 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.
+ */
+
+#define BOOST_TEST_MODULE "irccdctl rule-list"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/test/cli_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(rule_list_suite, cli_test)
+
+BOOST_AUTO_TEST_CASE(simple)
+{
+    const auto result = run("irccd-rules.conf", { "rule-list" });
+
+    BOOST_TEST(result.first.size() == 8U);
+    BOOST_TEST(result.second.size() == 0U);
+    BOOST_TEST(result.first[0]  == "rule:        0");
+    BOOST_TEST(result.first[1]  == "servers:     s1 s2 ");
+    BOOST_TEST(result.first[2]  == "channels:    c1 c2 ");
+    BOOST_TEST(result.first[3]  == "plugins:     p1 p2 ");
+    BOOST_TEST(result.first[4]  == "events:      onCommand onMessage ");
+    BOOST_TEST(result.first[5]  == "action:      drop");
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-move/CMakeLists.txt	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,24 @@
+#
+# CMakeLists.txt -- CMake build system 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.
+#
+
+irccd_define_test(
+    NAME cli-rule-move
+    SOURCES main.cpp
+    LIBRARIES libcommon
+    DEPENDS irccd irccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/src/irccdctl/cli-rule-move/main.cpp	Thu Mar 29 20:01:02 2018 +0200
@@ -0,0 +1,150 @@
+/*
+ * main.cpp -- test irccdctl rule-move
+ *
+ * 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.
+ */
+
+#define BOOST_TEST_MODULE "irccdctl rule-move"
+#include <boost/test/unit_test.hpp>
+
+#include <irccd/test/cli_test.hpp>
+
+namespace irccd {
+
+BOOST_FIXTURE_TEST_SUITE(rule_move_suite, cli_test)
+
+BOOST_AUTO_TEST_CASE(from_0_to_1)
+{
+    run_irccd("irccd-multiple-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-move", "0", "1" });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 22U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s2 ");
+        BOOST_TEST(result.first[2]  == "channels:    c2 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p2 ");
+        BOOST_TEST(result.first[4]  == "events:      onCommand ");
+        BOOST_TEST(result.first[5]  == "action:      drop");
+        BOOST_TEST(result.first[6]  == "");
+        BOOST_TEST(result.first[7]  == "rule:        1");
+        BOOST_TEST(result.first[8]  == "servers:     s1 ");
+        BOOST_TEST(result.first[9]  == "channels:    c1 ");
+        BOOST_TEST(result.first[10] == "plugins:     p1 ");
+        BOOST_TEST(result.first[11] == "events:      onTopic ");
+        BOOST_TEST(result.first[12] == "action:      accept");
+        BOOST_TEST(result.first[13] == "");
+        BOOST_TEST(result.first[14] == "rule:        2");
+        BOOST_TEST(result.first[15] == "servers:     s3 ");
+        BOOST_TEST(result.first[16] == "channels:    c3 ");
+        BOOST_TEST(result.first[17] == "plugins:     p3 ");
+        BOOST_TEST(result.first[18] == "events:      onMessage ");
+        BOOST_TEST(result.first[19] == "action:      accept");
+        BOOST_TEST(result.first[20] == "");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(from_2_to_0)
+{
+    run_irccd("irccd-multiple-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-move", "2", "0" });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 22U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s3 ");
+        BOOST_TEST(result.first[2]  == "channels:    c3 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p3 ");
+        BOOST_TEST(result.first[4]  == "events:      onMessage ");
+        BOOST_TEST(result.first[5]  == "action:      accept");
+        BOOST_TEST(result.first[6]  == "");
+        BOOST_TEST(result.first[7]  == "rule:        1");
+        BOOST_TEST(result.first[8]  == "servers:     s1 ");
+        BOOST_TEST(result.first[9]  == "channels:    c1 ");
+        BOOST_TEST(result.first[10] == "plugins:     p1 ");
+        BOOST_TEST(result.first[11] == "events:      onTopic ");
+        BOOST_TEST(result.first[12] == "action:      accept");
+        BOOST_TEST(result.first[13] == "");
+        BOOST_TEST(result.first[14] == "rule:        2");
+        BOOST_TEST(result.first[15] == "servers:     s2 ");
+        BOOST_TEST(result.first[16] == "channels:    c2 ");
+        BOOST_TEST(result.first[17] == "plugins:     p2 ");
+        BOOST_TEST(result.first[18] == "events:      onCommand ");
+        BOOST_TEST(result.first[19] == "action:      drop");
+        BOOST_TEST(result.first[20] == "");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(same)
+{
+    run_irccd("irccd-multiple-rules.conf");
+
+    {
+        const auto result = run_irccdctl({ "rule-move", "2", "2" });
+
+        BOOST_TEST(result.first.size() == 0U);
+        BOOST_TEST(result.second.size() == 0U);
+    }
+
+    {
+        const auto result = run_irccdctl({ "rule-list" });
+
+        BOOST_TEST(result.first.size() == 22U);
+        BOOST_TEST(result.second.size() == 0U);
+        BOOST_TEST(result.first[0]  == "rule:        0");
+        BOOST_TEST(result.first[1]  == "servers:     s1 ");
+        BOOST_TEST(result.first[2]  == "channels:    c1 ");
+        BOOST_TEST(result.first[3]  == "plugins:     p1 ");
+        BOOST_TEST(result.first[4]  == "events:      onTopic ");
+        BOOST_TEST(result.first[5]  == "action:      accept");
+        BOOST_TEST(result.first[6]  == "");
+        BOOST_TEST(result.first[7]  == "rule:        1");
+        BOOST_TEST(result.first[8]  == "servers:     s2 ");
+        BOOST_TEST(result.first[9]  == "channels:    c2 ");
+        BOOST_TEST(result.first[10] == "plugins:     p2 ");
+        BOOST_TEST(result.first[11] == "events:      onCommand ");
+        BOOST_TEST(result.first[12] == "action:      drop");
+        BOOST_TEST(result.first[13] == "");
+        BOOST_TEST(result.first[14] == "rule:        2");
+        BOOST_TEST(result.first[15] == "servers:     s3 ");
+        BOOST_TEST(result.first[16] == "channels:    c3 ");
+        BOOST_TEST(result.first[17] == "plugins:     p3 ");
+        BOOST_TEST(result.first[18] == "events:      onMessage ");
+        BOOST_TEST(result.first[19] == "action:      accept");
+        BOOST_TEST(result.first[20] == "");
+    }
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+} // !irccd