+/***************************************************************************
+ * Copyright (C) 2005-2019 by the Quassel Project *
+ * devel@quassel-irc.org *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) version 3. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#include <ostream>
+
+#include "testglobal.h"
+#include "ircencoder.h"
+#include "irctag.h"
+
+struct IrcMessage
+{
+ std::map<IrcTagKey, std::string> tags;
+ std::string prefix;
+ std::string cmd;
+ std::vector<std::string> params;
+
+ explicit IrcMessage(std::map<IrcTagKey, std::string> tags, std::string prefix, std::string cmd, std::vector<std::string> params = {})
+ : tags(std::move(tags)), prefix(std::move(prefix)), cmd(std::move(cmd)), params(std::move(params)) {}
+
+ explicit IrcMessage(std::initializer_list<std::pair<const IrcTagKey, std::string>> tags, std::string prefix, std::string cmd, std::vector<std::string> params = {})
+ : tags(tags), prefix(std::move(prefix)), cmd(std::move(cmd)), params(std::move(params)) {}
+
+ explicit IrcMessage(std::string prefix, std::string cmd, std::vector<std::string> params = {})
+ : tags({}), prefix(std::move(prefix)), cmd(std::move(cmd)), params(std::move(params)) {}
+
+ friend bool operator==(const IrcMessage& a, const IrcMessage& b)
+ {
+ return a.tags == b.tags &&
+ a.prefix == b.prefix &&
+ a.cmd == b.cmd &&
+ a.params == b.params;
+ }
+
+ friend std::ostream& operator<<(std::ostream& o, const IrcMessage& m)
+ {
+ o << "(tags={";
+ for (const std::pair<IrcTagKey, std::string> entry: m.tags) {
+ o << entry.first << "='" << entry.second << "', ";
+ }
+ o << "}, prefix=" << m.prefix << ", cmd=" << m.cmd << ", params=[";
+ for (const std::string& param : m.params) {
+ o << "'" << param << "', ";
+ }
+ o << "])";
+ return o;
+ }
+};
+
+
+std::string write(const IrcMessage& message)
+{
+ QHash<IrcTagKey, QString> tags;
+ QByteArray prefix = QByteArray::fromStdString(message.prefix);
+ QByteArray cmd = QByteArray::fromStdString(message.cmd);
+ QList<QByteArray> params;
+
+ for (const auto& pair : message.tags) {
+ tags[pair.first] = QString::fromStdString(pair.second);
+ }
+
+ for (const std::string& param : message.params) {
+ params += QByteArray::fromStdString(param);
+ }
+
+ return IrcEncoder::writeMessage(tags, prefix, cmd, params).toStdString();
+}
+
+TEST(IrcEncoderTest, simple_test_with_verb_and_params)
+{
+ EXPECT_STRCASEEQ(
+ "foo bar baz asdf",
+ write(IrcMessage("",
+ "foo",
+ {"bar", "baz", "asdf"})).data());
+}
+
+TEST(IrcEncoderTest, simple_test_with_source_and_no_params)
+{
+ EXPECT_STRCASEEQ(
+ ":src AWAY",
+ write(IrcMessage("src",
+ "AWAY")).data());
+}
+
+TEST(IrcEncoderTest, simple_test_with_source_and_empty_trailing_param)
+{
+ EXPECT_STRCASEEQ(
+ ":src AWAY :",
+ write(IrcMessage("src",
+ "AWAY",
+ {""})).data());
+}
+
+TEST(IrcEncoderTest, simple_test_with_source)
+{
+ EXPECT_STRCASEEQ(
+ ":coolguy foo bar baz asdf",
+ write(IrcMessage("coolguy",
+ "foo",
+ {"bar", "baz", "asdf"})).data());
+}
+
+TEST(IrcEncoderTest, simple_test_with_trailing_param)
+{
+ EXPECT_STRCASEEQ(
+ "foo bar baz :asdf quux",
+ write(IrcMessage("",
+ "foo",
+ {"bar", "baz", "asdf quux"})).data());
+}
+
+TEST(IrcEncoderTest, simple_test_with_empty_trailing_param)
+{
+ EXPECT_STRCASEEQ(
+ "foo bar baz :",
+ write(IrcMessage("",
+ "foo",
+ {"bar", "baz", ""})).data());
+}
+
+TEST(IrcEncoderTest, simple_test_with_trailing_param_containing_colon)
+{
+ EXPECT_STRCASEEQ(
+ "foo bar baz ::asdf",
+ write(IrcMessage("",
+ "foo",
+ {"bar", "baz", ":asdf"})).data());
+}
+
+TEST(IrcEncoderTest, test_with_source_and_trailing_param)
+{
+ EXPECT_STRCASEEQ(
+ ":coolguy foo bar baz :asdf quux",
+ write(IrcMessage("coolguy",
+ "foo",
+ {"bar", "baz", "asdf quux"})).data());
+}
+
+TEST(IrcEncoderTest, test_with_trailing_containing_beginning_end_whitespace)
+{
+ EXPECT_STRCASEEQ(
+ ":coolguy foo bar baz : asdf quux ",
+ write(IrcMessage("coolguy",
+ "foo",
+ {"bar", "baz", " asdf quux "})).data());
+}
+
+TEST(IrcEncoderTest, test_with_trailing_containing_what_looks_like_another_trailing_param)
+{
+ EXPECT_STRCASEEQ(
+ ":coolguy PRIVMSG bar :lol :) ",
+ write(IrcMessage("coolguy",
+ "PRIVMSG",
+ {"bar", "lol :) "})).data());
+}
+
+TEST(IrcEncoderTest, simple_test_with_source_and_empty_trailing)
+{
+ EXPECT_STRCASEEQ(
+ ":coolguy foo bar baz :",
+ write(IrcMessage("coolguy",
+ "foo",
+ {"bar", "baz", ""})).data());
+}
+
+TEST(IrcEncoderTest, trailing_contains_only_spaces)
+{
+ EXPECT_STRCASEEQ(
+ ":coolguy foo bar baz : ",
+ write(IrcMessage("coolguy",
+ "foo",
+ {"bar", "baz", " "})).data());
+}
+
+TEST(IrcEncoderTest, param_containing_tab_tab_is_not_considered_SPACE_for_message_splitting)
+{
+ EXPECT_STRCASEEQ(
+ ":coolguy foo b\tar baz",
+ write(IrcMessage("coolguy",
+ "foo",
+ {"b\tar", "baz"})).data());
+}
+
+TEST(IrcEncoderTest, tags_with_no_value_and_space_filled_trailing)
+{
+ EXPECT_STRCASEEQ(
+ "@asd :coolguy foo bar baz : ",
+ write(IrcMessage({{IrcTagKey("asd"), ""}},
+ "coolguy",
+ "foo",
+ {"bar", "baz", " "})).data());
+}
+
+TEST(IrcEncoderTest, tags_with_escaped_values)
+{
+ std::vector<std::string> expected{
+ R"(@d=gh\:764;a=b\\and\nk foo)",
+ R"(@a=b\\and\nk;d=gh\:764 foo)",
+ };
+ EXPECT_THAT(expected, testing::Contains(testing::StrCaseEq(
+ write(IrcMessage({{IrcTagKey("a"), "b\\and\nk"},
+ {IrcTagKey("d"), "gh;764"}},
+ "",
+ "foo")))));
+}
+
+TEST(IrcEncoderTest, tags_with_escaped_values_and_params)
+{
+ std::vector<std::string> expected{
+ R"(@d=gh\:764;a=b\\and\nk foo par1 par2)",
+ R"(@a=b\\and\nk;d=gh\:764 foo par1 par2)",
+ };
+ EXPECT_THAT(expected, testing::Contains(testing::StrCaseEq(
+ write(IrcMessage({{IrcTagKey("a"), "b\\and\nk"},
+ {IrcTagKey("d"), "gh;764"}},
+ "",
+ "foo",
+ {"par1", "par2"})))));
+}
+
+TEST(IrcEncoderTest, tags_with_long_strange_values)
+{
+ EXPECT_STRCASEEQ(
+ R"(@foo=\\\\\:\\s\s\r\n COMMAND)",
+ write(IrcMessage({{IrcTagKey("foo"), "\\\\;\\s \r\n"}},
+ "",
+ "COMMAND")).data());
+}