# HG changeset patch # User David Demelier # Date 1499443398 -7200 # Node ID 1fdedd2977d23f6d2827a218c643f84cf1249275 # Parent c8c68d4bf5559c2e544b5f3113efdbbe08d4bf66 Irccdctl: implement rule-move diff -r c8c68d4bf555 -r 1fdedd2977d2 doc/html/CMakeLists.txt --- a/doc/html/CMakeLists.txt Fri Jul 07 12:22:17 2017 +0200 +++ b/doc/html/CMakeLists.txt Fri Jul 07 18:03:18 2017 +0200 @@ -152,8 +152,9 @@ ${html_SOURCE_DIR}/irccdctl/command/plugin-load.md ${html_SOURCE_DIR}/irccdctl/command/plugin-reload.md ${html_SOURCE_DIR}/irccdctl/command/plugin-unload.md + ${html_SOURCE_DIR}/irccdctl/command/rule-info.md ${html_SOURCE_DIR}/irccdctl/command/rule-list.md - ${html_SOURCE_DIR}/irccdctl/command/rule-info.md + ${html_SOURCE_DIR}/irccdctl/command/rule-move.md ${html_SOURCE_DIR}/irccdctl/command/rule-remove.md ${html_SOURCE_DIR}/irccdctl/command/server-cmode.md ${html_SOURCE_DIR}/irccdctl/command/server-cnotice.md diff -r c8c68d4bf555 -r 1fdedd2977d2 doc/html/irccdctl/command/index.md --- a/doc/html/irccdctl/command/index.md Fri Jul 07 12:22:17 2017 +0200 +++ b/doc/html/irccdctl/command/index.md Fri Jul 07 18:03:18 2017 +0200 @@ -12,8 +12,9 @@ - [plugin-load](plugin-load.html) - [plugin-reload](plugin-reload.html) - [plugin-unload](plugin-unload.html) + - [rule-info](rule-info.html) - [rule-list](rule-list.html) - - [rule-info](rule-info.html) + - [rule-move](rule-move.html) - [rule-remove](rule-remove.html) - [server-cmode](server-cmode.html) - [server-cnotice](server-cnotice.html) diff -r c8c68d4bf555 -r 1fdedd2977d2 doc/html/irccdctl/command/rule-move.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/html/irccdctl/command/rule-move.md Fri Jul 07 18:03:18 2017 +0200 @@ -0,0 +1,25 @@ +--- +title: rule-move +guide: yes +--- + +# rule-move + +Move a rule from the given source at the specified destination index. + +The rule will replace the existing one at the given destination moving +down every other rules. If destination is greater or equal the number of rules, +the rule is moved to the end. + +## Usage + +````nohighlight +irccdctl rule-move source destination +```` + +## Example + +````nohighlight +irccdctl rule-move 0 5 +irccdctl rule-move 4 3 +```` diff -r c8c68d4bf555 -r 1fdedd2977d2 irccd/main.cpp --- a/irccd/main.cpp Fri Jul 07 12:22:17 2017 +0200 +++ b/irccd/main.cpp Fri Jul 07 18:03:18 2017 +0200 @@ -322,6 +322,7 @@ instance->commands().add(std::make_unique()); instance->commands().add(std::make_unique()); instance->commands().add(std::make_unique()); + instance->commands().add(std::make_unique()); instance->commands().add(std::make_unique()); // Load Javascript API and plugin loader. diff -r c8c68d4bf555 -r 1fdedd2977d2 irccdctl/cli.cpp --- a/irccdctl/cli.cpp Fri Jul 07 12:22:17 2017 +0200 +++ b/irccdctl/cli.cpp Fri Jul 07 18:03:18 2017 +0200 @@ -983,6 +983,47 @@ } /* + * RuleMoveCli. + * ------------------------------------------------------------------ + */ + +RuleMoveCli::RuleMoveCli() + : Cli("rule-move", + "move a rule to a new position", + "rule-move source destination", + "Move a rule from the given source at the specified destination index.\n\n" + "The rule will replace the existing one at the given destination moving\ndown every " + "other rules. If destination is greater or equal the number of rules,\nthe rule " + "is moved to the end.\n\n" + "Example:\n" + "\tirccdctl rule-move 0 5\n" + "\tirccdctl rule-move 4 3") +{ +} + +void RuleMoveCli::exec(Irccdctl &irccdctl, const std::vector &args) +{ + if (args.size() < 2) + throw std::invalid_argument("rule-move requires 2 arguments"); + + int from = 0; + int to = 0; + + try { + from = std::stoi(args[0]); + to = std::stoi(args[1]); + } catch (...) { + throw std::invalid_argument("invalid number"); + } + + check(request(irccdctl, { + { "command", "rule-move" }, + { "from", from }, + { "to", to } + })); +} + +/* * WatchCli. * ------------------------------------------------------------------ */ diff -r c8c68d4bf555 -r 1fdedd2977d2 irccdctl/cli.hpp --- a/irccdctl/cli.hpp Fri Jul 07 12:22:17 2017 +0200 +++ b/irccdctl/cli.hpp Fri Jul 07 18:03:18 2017 +0200 @@ -648,6 +648,26 @@ void exec(Irccdctl &client, const std::vector &args) override; }; +/* + * RuleMoveCli + * ------------------------------------------------------------------ + */ + +/** + * \brief Implementation of irccdctl rule-move. + */ +class RuleMoveCli : public Cli { +public: + /** + * Default constructor. + */ + RuleMoveCli(); + + /** + * \copydoc Cli::exec + */ + void exec(Irccdctl &client, const std::vector &args) override; +}; /* * WatchCli. diff -r c8c68d4bf555 -r 1fdedd2977d2 irccdctl/main.cpp --- a/irccdctl/main.cpp Fri Jul 07 12:22:17 2017 +0200 +++ b/irccdctl/main.cpp Fri Jul 07 18:03:18 2017 +0200 @@ -524,6 +524,7 @@ commands.push_back(std::make_unique()); commands.push_back(std::make_unique()); commands.push_back(std::make_unique()); + commands.push_back(std::make_unique()); commands.push_back(std::make_unique()); commands.push_back(std::make_unique()); } diff -r c8c68d4bf555 -r 1fdedd2977d2 libirccd/irccd/command.cpp --- a/libirccd/irccd/command.cpp Fri Jul 07 12:22:17 2017 +0200 +++ b/libirccd/irccd/command.cpp Fri Jul 07 18:03:18 2017 +0200 @@ -493,6 +493,60 @@ } } +RuleMoveCommand::RuleMoveCommand() + : Command("rule-move") +{ +} + +void RuleMoveCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) +{ + auto from = util::json::requireUint(args, "from"); + auto to = util::json::requireUint(args, "to"); + + /* + * Examples of moves + * -------------------------------------------------------------- + * + * Before: [0] [1] [2] + * + * from = 0 + * to = 2 + * + * After: [1] [2] [0] + * + * -------------------------------------------------------------- + * + * Before: [0] [1] [2] + * + * from = 2 + * to = 0 + * + * After: [2] [0] [1] + * + * -------------------------------------------------------------- + * + * Before: [0] [1] [2] + * + * from = 0 + * to = 123 + * + * After: [1] [2] [0] + */ + + // Ignore dump input. + if (from == to) + client.success("rule-move"); + else if (from >= irccd.rules().length()) + client.error("rule-move", "rule source index is out of range"); + else { + auto save = irccd.rules().list()[from]; + + irccd.rules().remove(from); + irccd.rules().insert(save, to > irccd.rules().length() ? irccd.rules().length() : to); + client.success("rule-move"); + } +} + } // !command } // !irccd diff -r c8c68d4bf555 -r 1fdedd2977d2 libirccd/irccd/command.hpp --- a/libirccd/irccd/command.hpp Fri Jul 07 12:22:17 2017 +0200 +++ b/libirccd/irccd/command.hpp Fri Jul 07 18:03:18 2017 +0200 @@ -506,6 +506,22 @@ void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override; }; +/** + * \brief Implementation of rule-move transport command. + */ +class IRCCD_EXPORT RuleMoveCommand : public Command { +public: + /** + * Constructor. + */ + RuleMoveCommand(); + + /** + * \copydoc Command::exec + */ + void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override; +}; + } // !command } // !irccd diff -r c8c68d4bf555 -r 1fdedd2977d2 tests/CMakeLists.txt --- a/tests/CMakeLists.txt Fri Jul 07 12:22:17 2017 +0200 +++ b/tests/CMakeLists.txt Fri Jul 07 18:03:18 2017 +0200 @@ -26,8 +26,9 @@ add_subdirectory(cmd-plugin-load) add_subdirectory(cmd-plugin-reload) add_subdirectory(cmd-plugin-unload) + add_subdirectory(cmd-rule-info) add_subdirectory(cmd-rule-list) - add_subdirectory(cmd-rule-info) + add_subdirectory(cmd-rule-move) add_subdirectory(cmd-rule-remove) add_subdirectory(cmd-server-cmode) add_subdirectory(cmd-server-cnotice) diff -r c8c68d4bf555 -r 1fdedd2977d2 tests/cmd-rule-move/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/cmd-rule-move/CMakeLists.txt Fri Jul 07 18:03:18 2017 +0200 @@ -0,0 +1,23 @@ +# +# CMakeLists.txt -- CMake build system for irccd +# +# Copyright (c) 2013-2017 David Demelier +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +irccd_define_test( + NAME cmd-rule-move + SOURCES main.cpp + LIBRARIES libirccd libirccdctl +) diff -r c8c68d4bf555 -r 1fdedd2977d2 tests/cmd-rule-move/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/cmd-rule-move/main.cpp Fri Jul 07 18:03:18 2017 +0200 @@ -0,0 +1,391 @@ +/* + * main.cpp -- test rule-move remote command + * + * Copyright (c) 2013-2017 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +using namespace irccd; +using namespace irccd::command; + +class RuleMoveCommandTest : public CommandTester { +protected: + nlohmann::json m_result; + + /* + * Rule sets are unordered so use this function to search a string in + * the JSON array. + */ + inline bool contains(const nlohmann::json &array, const std::string &str) + { + for (const auto &v : array) + if (v.is_string() && v == str) + return true; + + return false; + } + +public: + RuleMoveCommandTest() + : CommandTester(std::make_unique()) + { + m_irccd.commands().add(std::make_unique()); + m_irccd.rules().add(Rule( + { "s0" }, + { "c0" }, + { "o0" }, + { "p0" }, + { "onMessage" }, + RuleAction::Drop + )); + m_irccd.rules().add(Rule( + { "s1", }, + { "c1", }, + { "o1", }, + { "p1", }, + { "onMessage", }, + RuleAction::Accept + )); + m_irccd.rules().add(Rule( + { "s2", }, + { "c2", }, + { "o2", }, + { "p2", }, + { "onMessage", }, + RuleAction::Accept + )); + m_irccdctl.client().onMessage.connect([&] (auto result) { + m_result = result; + }); + } +}; + +TEST_F(RuleMoveCommandTest, backward) +{ + try { + m_irccdctl.client().request({ + { "command", "rule-move" }, + { "from", 2 }, + { "to", 0 } + }); + + poll([&] () { + return m_result.is_object(); + }); + + ASSERT_TRUE(m_result.is_object()); + ASSERT_TRUE(m_result["status"].get()); + + m_result = nullptr; + m_irccdctl.client().request({{ "command", "rule-list" }}); + + poll([&] () { + return m_result.is_object(); + }); + + ASSERT_TRUE(m_result.is_object()); + ASSERT_TRUE(m_result["status"].get()); + + // Rule 2. + { + auto servers = m_result["list"][0]["servers"]; + auto channels = m_result["list"][0]["channels"]; + auto plugins = m_result["list"][0]["plugins"]; + auto events = m_result["list"][0]["events"]; + + ASSERT_TRUE(contains(servers, "s2")); + ASSERT_TRUE(contains(channels, "c2")); + ASSERT_TRUE(contains(plugins, "p2")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][0]["action"].get()); + } + + // Rule 0. + { + auto servers = m_result["list"][1]["servers"]; + auto channels = m_result["list"][1]["channels"]; + auto plugins = m_result["list"][1]["plugins"]; + auto events = m_result["list"][1]["events"]; + + ASSERT_TRUE(contains(servers, "s0")); + ASSERT_TRUE(contains(channels, "c0")); + ASSERT_TRUE(contains(plugins, "p0")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("drop", m_result["list"][1]["action"].get()); + } + + // Rule 1. + { + auto servers = m_result["list"][2]["servers"]; + auto channels = m_result["list"][2]["channels"]; + auto plugins = m_result["list"][2]["plugins"]; + auto events = m_result["list"][2]["events"]; + + ASSERT_TRUE(contains(servers, "s1")); + ASSERT_TRUE(contains(channels, "c1")); + ASSERT_TRUE(contains(plugins, "p1")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][2]["action"].get()); + } + } catch (const std::exception& ex) { + FAIL() << ex.what(); + } +} + +TEST_F(RuleMoveCommandTest, upward) +{ + try { + m_irccdctl.client().request({ + { "command", "rule-move" }, + { "from", 0 }, + { "to", 2 } + }); + + poll([&] () { + return m_result.is_object(); + }); + + ASSERT_TRUE(m_result.is_object()); + ASSERT_TRUE(m_result["status"].get()); + + m_result = nullptr; + m_irccdctl.client().request({{ "command", "rule-list" }}); + + poll([&] () { + return m_result.is_object(); + }); + + ASSERT_TRUE(m_result.is_object()); + ASSERT_TRUE(m_result["status"].get()); + + // Rule 1. + { + auto servers = m_result["list"][0]["servers"]; + auto channels = m_result["list"][0]["channels"]; + auto plugins = m_result["list"][0]["plugins"]; + auto events = m_result["list"][0]["events"]; + + ASSERT_TRUE(contains(servers, "s1")); + ASSERT_TRUE(contains(channels, "c1")); + ASSERT_TRUE(contains(plugins, "p1")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][0]["action"].get()); + } + + // Rule 2. + { + auto servers = m_result["list"][1]["servers"]; + auto channels = m_result["list"][1]["channels"]; + auto plugins = m_result["list"][1]["plugins"]; + auto events = m_result["list"][1]["events"]; + + ASSERT_TRUE(contains(servers, "s2")); + ASSERT_TRUE(contains(channels, "c2")); + ASSERT_TRUE(contains(plugins, "p2")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][1]["action"].get()); + } + + // Rule 0. + { + auto servers = m_result["list"][2]["servers"]; + auto channels = m_result["list"][2]["channels"]; + auto plugins = m_result["list"][2]["plugins"]; + auto events = m_result["list"][2]["events"]; + + ASSERT_TRUE(contains(servers, "s0")); + ASSERT_TRUE(contains(channels, "c0")); + ASSERT_TRUE(contains(plugins, "p0")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("drop", m_result["list"][2]["action"].get()); + } + } catch (const std::exception& ex) { + FAIL() << ex.what(); + } +} + +TEST_F(RuleMoveCommandTest, same) +{ + try { + m_irccdctl.client().request({ + { "command", "rule-move" }, + { "from", 1 }, + { "to", 1 } + }); + + poll([&] () { + return m_result.is_object(); + }); + + ASSERT_TRUE(m_result.is_object()); + ASSERT_TRUE(m_result["status"].get()); + + m_result = nullptr; + m_irccdctl.client().request({{ "command", "rule-list" }}); + + poll([&] () { + return m_result.is_object(); + }); + + ASSERT_TRUE(m_result.is_object()); + ASSERT_TRUE(m_result["status"].get()); + + // Rule 0. + { + auto servers = m_result["list"][0]["servers"]; + auto channels = m_result["list"][0]["channels"]; + auto plugins = m_result["list"][0]["plugins"]; + auto events = m_result["list"][0]["events"]; + + ASSERT_TRUE(contains(servers, "s0")); + ASSERT_TRUE(contains(channels, "c0")); + ASSERT_TRUE(contains(plugins, "p0")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("drop", m_result["list"][0]["action"].get()); + } + + // Rule 1. + { + auto servers = m_result["list"][1]["servers"]; + auto channels = m_result["list"][1]["channels"]; + auto plugins = m_result["list"][1]["plugins"]; + auto events = m_result["list"][1]["events"]; + + ASSERT_TRUE(contains(servers, "s1")); + ASSERT_TRUE(contains(channels, "c1")); + ASSERT_TRUE(contains(plugins, "p1")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][1]["action"].get()); + } + + // Rule 2. + { + auto servers = m_result["list"][2]["servers"]; + auto channels = m_result["list"][2]["channels"]; + auto plugins = m_result["list"][2]["plugins"]; + auto events = m_result["list"][2]["events"]; + + ASSERT_TRUE(contains(servers, "s2")); + ASSERT_TRUE(contains(channels, "c2")); + ASSERT_TRUE(contains(plugins, "p2")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][2]["action"].get()); + } + } catch (const std::exception& ex) { + FAIL() << ex.what(); + } +} + +TEST_F(RuleMoveCommandTest, beyond) +{ + try { + m_irccdctl.client().request({ + { "command", "rule-move" }, + { "from", 0 }, + { "to", 123 } + }); + + poll([&] () { + return m_result.is_object(); + }); + + ASSERT_TRUE(m_result.is_object()); + ASSERT_TRUE(m_result["status"].get()); + + m_result = nullptr; + m_irccdctl.client().request({{ "command", "rule-list" }}); + + poll([&] () { + return m_result.is_object(); + }); + + ASSERT_TRUE(m_result.is_object()); + ASSERT_TRUE(m_result["status"].get()); + + // Rule 1. + { + auto servers = m_result["list"][0]["servers"]; + auto channels = m_result["list"][0]["channels"]; + auto plugins = m_result["list"][0]["plugins"]; + auto events = m_result["list"][0]["events"]; + + ASSERT_TRUE(contains(servers, "s1")); + ASSERT_TRUE(contains(channels, "c1")); + ASSERT_TRUE(contains(plugins, "p1")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][0]["action"].get()); + } + + // Rule 2. + { + auto servers = m_result["list"][1]["servers"]; + auto channels = m_result["list"][1]["channels"]; + auto plugins = m_result["list"][1]["plugins"]; + auto events = m_result["list"][1]["events"]; + + ASSERT_TRUE(contains(servers, "s2")); + ASSERT_TRUE(contains(channels, "c2")); + ASSERT_TRUE(contains(plugins, "p2")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][1]["action"].get()); + } + + // Rule 0. + { + auto servers = m_result["list"][2]["servers"]; + auto channels = m_result["list"][2]["channels"]; + auto plugins = m_result["list"][2]["plugins"]; + auto events = m_result["list"][2]["events"]; + + ASSERT_TRUE(contains(servers, "s0")); + ASSERT_TRUE(contains(channels, "c0")); + ASSERT_TRUE(contains(plugins, "p0")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("drop", m_result["list"][2]["action"].get()); + } + } catch (const std::exception& ex) { + FAIL() << ex.what(); + } +} + +TEST_F(RuleMoveCommandTest, outOfBounds) +{ + try { + m_irccdctl.client().request({ + { "command", "rule-move" }, + { "from", 1024 }, + { "to", 0 } + }); + + poll([&] () { + return m_result.is_object(); + }); + + ASSERT_TRUE(m_result.is_object()); + ASSERT_FALSE(m_result["status"].get()); + } catch (const std::exception& ex) { + FAIL() << ex.what(); + } +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}