# HG changeset patch # User David Demelier # Date 1500584194 -7200 # Node ID 2170aa0e38aab04c87e6dd4a5562c3c9078129ff # Parent 1fdedd2977d23f6d2827a218c643f84cf1249275 Irccdctl: implement rule-add diff -r 1fdedd2977d2 -r 2170aa0e38aa doc/html/CMakeLists.txt --- a/doc/html/CMakeLists.txt Fri Jul 07 18:03:18 2017 +0200 +++ b/doc/html/CMakeLists.txt Thu Jul 20 22:56:34 2017 +0200 @@ -152,6 +152,7 @@ ${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-add.md ${html_SOURCE_DIR}/irccdctl/command/rule-info.md ${html_SOURCE_DIR}/irccdctl/command/rule-list.md ${html_SOURCE_DIR}/irccdctl/command/rule-move.md diff -r 1fdedd2977d2 -r 2170aa0e38aa doc/html/irccdctl/command/index.md --- a/doc/html/irccdctl/command/index.md Fri Jul 07 18:03:18 2017 +0200 +++ b/doc/html/irccdctl/command/index.md Thu Jul 20 22:56:34 2017 +0200 @@ -12,6 +12,7 @@ - [plugin-load](plugin-load.html) - [plugin-reload](plugin-reload.html) - [plugin-unload](plugin-unload.html) + - [rule-add](rule-add.html) - [rule-info](rule-info.html) - [rule-list](rule-list.html) - [rule-move](rule-move.html) diff -r 1fdedd2977d2 -r 2170aa0e38aa doc/html/irccdctl/command/rule-add.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/html/irccdctl/command/rule-add.md Thu Jul 20 22:56:34 2017 +0200 @@ -0,0 +1,31 @@ +--- +title: rule-add +guide: yes +--- + +# rule-add + +Add a new rule to irccd. + +If no index is specified, the rule is added to the end. + +## Usage + +````nohighlight +$ irccdctl rule-add [options] accept|drop +```` + +Available options: + + - **-c, --add-channel**: match a channel + - **-e, --add-event**: match an event + - **-i, --index**: rule position + - **-p, --add-plugin**: match a plugin + - **-s, --add-server**: match a server + +## Example + +````nohighlight +$ irccdctl rule-add -p hangman drop +$ irccdctl rule-add -s localhost -c #games -p hangman accept +```` diff -r 1fdedd2977d2 -r 2170aa0e38aa irccd/main.cpp --- a/irccd/main.cpp Fri Jul 07 18:03:18 2017 +0200 +++ b/irccd/main.cpp Thu Jul 20 22:56:34 2017 +0200 @@ -320,6 +320,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()); instance->commands().add(std::make_unique()); instance->commands().add(std::make_unique()); diff -r 1fdedd2977d2 -r 2170aa0e38aa irccdctl/cli.cpp --- a/irccdctl/cli.cpp Fri Jul 07 18:03:18 2017 +0200 +++ b/irccdctl/cli.cpp Thu Jul 20 22:56:34 2017 +0200 @@ -29,6 +29,8 @@ #include "options.hpp" #include "util.hpp" +using namespace std::string_literals; + namespace irccd { /* @@ -842,6 +844,85 @@ } /* + * RuleAddCli. + * ------------------------------------------------------------------ + */ + +RuleAddCli::RuleAddCli() + : Cli("rule-add", + "add a new rule", + "rule-add [options] accept|drop", + "Add a new rule to irccd.\n\n" + "If no index is specified, the rule is added to the end.\n\n" + "Available options:\n" + " -c, --add-channel\t\tmatch a channel\n" + " -e, --add-event\t\tmatch an event\n" + " -i, --index\t\t\trule position\n" + " -p, --add-plugin\t\tmatch a plugin\n" + " -s, --add-server\t\tmatch a server\n\n" + "Example:\n" + "\tirccdctl rule-add -p hangman drop\n" + "\tirccdctl rule-add -s localhost -c #games -p hangman accept") +{ +} + +void RuleAddCli::exec(Irccdctl &irccdctl, const std::vector &args) +{ + static const option::Options options{ + { "-c", true }, + { "--add-channel", true }, + { "-e", true }, + { "--add-event", true }, + { "-i", true }, + { "--index", true }, + { "-p", true }, + { "--add-plugin", true }, + { "-s", true }, + { "--add-server", true } + }; + + auto copy = args; + auto result = option::read(copy, options); + + if (copy.size() < 1) + throw std::invalid_argument("rule-add requires at least 1 argument"); + + auto json = nlohmann::json::object({ + { "command", "rule-add" }, + { "channels", nlohmann::json::array() }, + { "events", nlohmann::json::array() }, + { "plugins", nlohmann::json::array() }, + { "servers", nlohmann::json::array() } + }); + + // All sets. + for (const auto& pair : result) { + if (pair.first == "-c" || pair.first == "--add-channel") + json["channels"].push_back(pair.second); + if (pair.first == "-e" || pair.first == "--add-event") + json["events"].push_back(pair.second); + if (pair.first == "-p" || pair.first == "--add-plugin") + json["plugins"].push_back(pair.second); + if (pair.first == "-s" || pair.first == "--add-server") + json["servers"].push_back(pair.second); + } + + // Index. + if (result.count("-i") > 0) + json["index"] = util::toNumber(result.find("-i")->second); + if (result.count("--index") > 0) + json["index"] = util::toNumber(result.find("--index")->second); + + // And action. + if (copy[0] != "accept" && copy[0] != "drop") + throw std::runtime_error("invalid action '"s + copy[0] + "'"); + + json["action"] = copy[0]; + + check(request(irccdctl, json)); +} + +/* * RuleListCli. * ------------------------------------------------------------------ */ diff -r 1fdedd2977d2 -r 2170aa0e38aa irccdctl/cli.hpp --- a/irccdctl/cli.hpp Fri Jul 07 18:03:18 2017 +0200 +++ b/irccdctl/cli.hpp Thu Jul 20 22:56:34 2017 +0200 @@ -586,6 +586,27 @@ }; /* + * RuleAddCli. + * ------------------------------------------------------------------ + */ + +/** + * \brief Implementation of irccdctl rule-add. + */ +class RuleAddCli : public Cli { +public: + /** + * Default constructor. + */ + RuleAddCli(); + + /** + * \copydoc Cli::exec + */ + void exec(Irccdctl &client, const std::vector &args) override; +}; + +/* * RuleListCli. * ------------------------------------------------------------------ */ diff -r 1fdedd2977d2 -r 2170aa0e38aa irccdctl/main.cpp --- a/irccdctl/main.cpp Fri Jul 07 18:03:18 2017 +0200 +++ b/irccdctl/main.cpp Thu Jul 20 22:56:34 2017 +0200 @@ -522,6 +522,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()); commands.push_back(std::make_unique()); diff -r 1fdedd2977d2 -r 2170aa0e38aa libirccd/irccd/command.cpp --- a/libirccd/irccd/command.cpp Fri Jul 07 18:03:18 2017 +0200 +++ b/libirccd/irccd/command.cpp Thu Jul 20 22:56:34 2017 +0200 @@ -22,6 +22,8 @@ #include "transport.hpp" #include "util.hpp" +using namespace std::string_literals; + namespace irccd { namespace command { @@ -98,6 +100,42 @@ }; } +Rule fromJson(const nlohmann::json &json) +{ + auto toset = [] (auto object, auto name) -> RuleSet { + RuleSet result; + + for (const auto &s : object[name]) + if (s.is_string()) + result.insert(s.template get()); + + return result; + }; + auto toaction = [] (auto object, auto name) -> RuleAction { + auto v = object[name]; + + if (!v.is_string()) + throw std::runtime_error("no action given"); + + auto s = v.template get(); + if (s == "accept") + return RuleAction::Accept; + if (s == "drop") + return RuleAction::Drop; + + throw std::runtime_error("unknown action '"s + s + "' given"); + }; + + return { + toset(json, "servers"), + toset(json, "channels"), + toset(json, "origins"), + toset(json, "plugins"), + toset(json, "events"), + toaction(json, "action") + }; +} + } // !namespace PluginConfigCommand::PluginConfigCommand() @@ -547,6 +585,24 @@ } } +RuleAddCommand::RuleAddCommand() + : Command("rule-add") +{ +} + +void RuleAddCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) +{ + auto index = util::json::getUint(args, "index", irccd.rules().length()); + auto rule = fromJson(args); + + if (index > irccd.rules().length()) + client.error("rule-add", "index is out of range"); + else { + irccd.rules().insert(rule, index); + client.success("rule-add"); + } +} + } // !command } // !irccd diff -r 1fdedd2977d2 -r 2170aa0e38aa libirccd/irccd/command.hpp --- a/libirccd/irccd/command.hpp Fri Jul 07 18:03:18 2017 +0200 +++ b/libirccd/irccd/command.hpp Thu Jul 20 22:56:34 2017 +0200 @@ -522,6 +522,22 @@ void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override; }; +/** + * \brief Implementation of rule-add transport command. + */ +class IRCCD_EXPORT RuleAddCommand : public Command { +public: + /** + * Constructor. + */ + RuleAddCommand(); + + /** + * \copydoc Command::exec + */ + void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override; +}; + } // !command } // !irccd diff -r 1fdedd2977d2 -r 2170aa0e38aa tests/CMakeLists.txt --- a/tests/CMakeLists.txt Fri Jul 07 18:03:18 2017 +0200 +++ b/tests/CMakeLists.txt Thu Jul 20 22:56:34 2017 +0200 @@ -26,6 +26,7 @@ add_subdirectory(cmd-plugin-load) add_subdirectory(cmd-plugin-reload) add_subdirectory(cmd-plugin-unload) + add_subdirectory(cmd-rule-add) add_subdirectory(cmd-rule-info) add_subdirectory(cmd-rule-list) add_subdirectory(cmd-rule-move) diff -r 1fdedd2977d2 -r 2170aa0e38aa tests/cmd-rule-add/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/cmd-rule-add/CMakeLists.txt Thu Jul 20 22:56:34 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-add + SOURCES main.cpp + LIBRARIES libirccd libirccdctl +) diff -r 1fdedd2977d2 -r 2170aa0e38aa tests/cmd-rule-add/main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/cmd-rule-add/main.cpp Thu Jul 20 22:56:34 2017 +0200 @@ -0,0 +1,188 @@ +/* + * main.cpp -- test rule-add 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 RuleAddCommandTest : 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: + RuleAddCommandTest() + : CommandTester(std::make_unique()) + { + m_irccd.commands().add(std::make_unique()); + m_irccdctl.client().onMessage.connect([&] (auto result) { + m_result = result; + }); + } +}; + +TEST_F(RuleAddCommandTest, basic) +{ + m_irccdctl.client().request({ + { "command", "rule-add" }, + { "servers", { "s1", "s2" } }, + { "channels", { "c1", "c2" } }, + { "plugins", { "p1", "p2" } }, + { "events", { "onMessage" } }, + { "action", "accept" }, + { "index", 0 } + }); + + try { + 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()); + + 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(servers, "s2")); + ASSERT_TRUE(contains(channels, "c1")); + ASSERT_TRUE(contains(channels, "c2")); + ASSERT_TRUE(contains(plugins, "p1")); + ASSERT_TRUE(contains(plugins, "p2")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][0]["action"]); + } catch (const std::exception &ex) { + FAIL() << ex.what(); + } +} + +TEST_F(RuleAddCommandTest, append) +{ + try { + m_irccdctl.client().request({ + { "command", "rule-add" }, + { "servers", { "s1" } }, + { "channels", { "c1" } }, + { "plugins", { "p1" } }, + { "events", { "onMessage" } }, + { "action", "accept" }, + { "index", 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-add" }, + { "servers", { "s2" } }, + { "channels", { "c2" } }, + { "plugins", { "p2" } }, + { "events", { "onMessage" } }, + { "action", "drop" }, + { "index", 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()); + ASSERT_EQ(2U, m_result["list"].size()); + + // 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, "s1")); + ASSERT_TRUE(contains(channels, "c1")); + ASSERT_TRUE(contains(plugins, "p1")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("accept", m_result["list"][0]["action"]); + } + + // 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, "s2")); + ASSERT_TRUE(contains(channels, "c2")); + ASSERT_TRUE(contains(plugins, "p2")); + ASSERT_TRUE(contains(events, "onMessage")); + ASSERT_EQ("drop", m_result["list"][1]["action"]); + } + } catch (const std::exception &ex) { + FAIL() << ex.what(); + } +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}