Fixed errors and improve roster tests
All checks were successful
PR Check / on-push-commit-check (push) Successful in 11m21s
All checks were successful
PR Check / on-push-commit-check (push) Successful in 11m21s
This commit is contained in:
parent
7685385559
commit
30a5e69d14
10 changed files with 96 additions and 52 deletions
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -20,7 +20,7 @@
|
||||||
"program": "${workspaceFolder}/build/larra_xmpp_tests",
|
"program": "${workspaceFolder}/build/larra_xmpp_tests",
|
||||||
"args": [
|
"args": [
|
||||||
// --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
|
// --gtest_filter=POSTIVE_PATTERNS[-NEGATIVE_PATTERNS]
|
||||||
"--gtest_filter=Roster*"
|
// "--gtest_filter=Roster*"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"preLaunchTask": "GCC: Build"
|
"preLaunchTask": "GCC: Build"
|
||||||
|
|
|
@ -47,7 +47,4 @@ using SetBind = Set<Bind>;
|
||||||
using ResultBind = Result<Bind>;
|
using ResultBind = Result<Bind>;
|
||||||
using IqBind = Iq<Bind>;
|
using IqBind = Iq<Bind>;
|
||||||
|
|
||||||
inline auto MakeSetBind() {
|
|
||||||
return SetBind{.id = "1", .payload = Bind{}};
|
|
||||||
}
|
|
||||||
} // namespace larra::xmpp::iq
|
} // namespace larra::xmpp::iq
|
||||||
|
|
|
@ -80,21 +80,35 @@ struct Client {
|
||||||
|
|
||||||
auto CreateResourceBind() -> boost::asio::awaitable<void> {
|
auto CreateResourceBind() -> boost::asio::awaitable<void> {
|
||||||
SPDLOG_INFO("Send IQ: Set::Bind");
|
SPDLOG_INFO("Send IQ: Set::Bind");
|
||||||
co_await this->Send(::iq::MakeSetBind());
|
co_await this->Send(::iq::SetBind{.id = "1", .payload = {}});
|
||||||
|
|
||||||
auto bind_result = co_await connection.template Read<::iq::ResultBind>();
|
auto set_bind_response = co_await connection.template Read<Iq<::iq::Bind>>();
|
||||||
jid.resource = std::move(bind_result.payload.jid->resource);
|
std::visit(utempl::Overloaded(
|
||||||
|
[](auto error) {
|
||||||
|
throw "Error response on IQ: Set::Bind: ''"; // TODO(unknown): Add exact error parsing
|
||||||
|
},
|
||||||
|
[&](::iq::ResultBind r) {
|
||||||
|
jid.resource = std::move(r.payload.jid->resource);
|
||||||
|
SPDLOG_INFO("Allocated resource: {}", jid.resource);
|
||||||
|
}),
|
||||||
|
set_bind_response);
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto UpdateListOfContacts() -> boost::asio::awaitable<void> {
|
auto UpdateListOfContacts() -> boost::asio::awaitable<void> {
|
||||||
SPDLOG_INFO("Send IQ: Get::Roster");
|
SPDLOG_INFO("Send IQ: Get::Roster");
|
||||||
co_await this->Send(::iq::MakeGetRoster(jid));
|
co_await this->Send(::iq::GetRoster{.id = "1", .from = std::format("{}@{}", "invalid_user", jid.server), .payload = {}});
|
||||||
|
|
||||||
const auto roster_result = co_await connection.template Read<::iq::ResultRoster>();
|
const auto get_roster_response = co_await connection.template Read<Iq<::iq::Roster>>();
|
||||||
roster = std::move(roster_result.payload);
|
std::visit(utempl::Overloaded(
|
||||||
|
[](auto error) {
|
||||||
SPDLOG_INFO("New roster: {}", ToString(roster));
|
throw "Error response on IQ: Get::Roster: ''"; // TODO(unknown): Add exact error parsing
|
||||||
|
},
|
||||||
|
[&](::iq::ResultRoster r) {
|
||||||
|
roster = std::move(r.payload);
|
||||||
|
SPDLOG_INFO("New roster: {}", ToString(roster));
|
||||||
|
}),
|
||||||
|
get_roster_response);
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,14 +157,15 @@ struct Challenge {
|
||||||
throw std::runtime_error(std::format("Invalid name {} for challenge", node->get_name()));
|
throw std::runtime_error(std::format("Invalid name {} for challenge", node->get_name()));
|
||||||
}
|
}
|
||||||
std::string decoded = DecodeBase64(node->get_first_child_text()->get_content());
|
std::string decoded = DecodeBase64(node->get_first_child_text()->get_content());
|
||||||
auto params = std::views::split(decoded, ',') | std::views::transform([](auto param) {
|
auto params = std::views::split(decoded, ',') //
|
||||||
return std::string_view{param};
|
| std::views::transform([](auto param) { //
|
||||||
}) |
|
return std::string_view{param}; //
|
||||||
std::views::transform([](std::string_view param) -> std::pair<std::string_view, std::string_view> {
|
}) //
|
||||||
auto v = param.find("=");
|
| std::views::transform([](std::string_view param) -> std::pair<std::string_view, std::string_view> { //
|
||||||
return {param.substr(0, v), param.substr(v + 1)};
|
auto v = param.find("="); //
|
||||||
}) |
|
return {param.substr(0, v), param.substr(v + 1)}; //
|
||||||
std::ranges::to<std::unordered_map<std::string_view, std::string_view>>();
|
}) //
|
||||||
|
| std::ranges::to<std::unordered_map<std::string_view, std::string_view>>();
|
||||||
return {.body = std::move(decoded),
|
return {.body = std::move(decoded),
|
||||||
.serverNonce = params.at("r"),
|
.serverNonce = params.at("r"),
|
||||||
.salt = DecodeBase64(params.at("s")),
|
.salt = DecodeBase64(params.at("s")),
|
||||||
|
|
|
@ -37,10 +37,10 @@ struct BaseImplWithPayload {
|
||||||
friend constexpr auto operator<<(xmlpp::Element* element, const BaseImplWithPayload& self) {
|
friend constexpr auto operator<<(xmlpp::Element* element, const BaseImplWithPayload& self) {
|
||||||
element->set_attribute("id", self.id);
|
element->set_attribute("id", self.id);
|
||||||
|
|
||||||
if (self.to) {
|
if(self.to) {
|
||||||
element->set_attribute("to", *self.to);
|
element->set_attribute("to", *self.to);
|
||||||
}
|
}
|
||||||
if (self.from) {
|
if(self.from) {
|
||||||
element->set_attribute("from", *self.from);
|
element->set_attribute("from", *self.from);
|
||||||
}
|
}
|
||||||
element->set_attribute("type", kName);
|
element->set_attribute("type", kName);
|
||||||
|
@ -48,6 +48,19 @@ struct BaseImplWithPayload {
|
||||||
S::Serialize(element->add_child_element(S::kDefaultName, S::kPrefix), self.payload);
|
S::Serialize(element->add_child_element(S::kDefaultName, S::kPrefix), self.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static constexpr auto TryParse(xmlpp::Element* element) -> std::optional<BaseImplWithPayload> {
|
||||||
|
return [&] -> std::optional<BaseImplWithPayload> {
|
||||||
|
auto node = element->get_attribute("type");
|
||||||
|
if(!node) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if(node->get_value() != Name) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return Parse(element);
|
||||||
|
}();
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> BaseImplWithPayload {
|
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> BaseImplWithPayload {
|
||||||
auto node = element->get_attribute("type");
|
auto node = element->get_attribute("type");
|
||||||
if(!node) {
|
if(!node) {
|
||||||
|
@ -63,7 +76,6 @@ struct BaseImplWithPayload {
|
||||||
auto from = element->get_attribute("from");
|
auto from = element->get_attribute("from");
|
||||||
auto to = element->get_attribute("to");
|
auto to = element->get_attribute("to");
|
||||||
|
|
||||||
|
|
||||||
using S = Serialization<PayloadType>;
|
using S = Serialization<PayloadType>;
|
||||||
auto payload = element->get_first_child(S::kDefaultName);
|
auto payload = element->get_first_child(S::kDefaultName);
|
||||||
if(!payload) {
|
if(!payload) {
|
||||||
|
@ -73,13 +85,13 @@ struct BaseImplWithPayload {
|
||||||
if(!payload2) {
|
if(!payload2) {
|
||||||
throw std::runtime_error("Invalid payload for parse Iq");
|
throw std::runtime_error("Invalid payload for parse Iq");
|
||||||
}
|
}
|
||||||
return {
|
return {.id = idNode->get_value(),
|
||||||
.id = idNode->get_value(),
|
.from = (from ? std::optional{from->get_value()} : std::nullopt),
|
||||||
.from = (from ? std::optional{from->get_value()} : std::nullopt),
|
.to = (to ? std::optional{to->get_value()} : std::nullopt),
|
||||||
.to = (to ? std::optional{to->get_value()} : std::nullopt),
|
.payload = S::Parse(payload2)};
|
||||||
.payload = S::Parse(payload2)};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr auto kGetName = "get";
|
static constexpr auto kGetName = "get";
|
||||||
|
|
||||||
template <typename Payload>
|
template <typename Payload>
|
||||||
|
|
|
@ -54,9 +54,10 @@ struct FullJid {
|
||||||
struct BareJid {
|
struct BareJid {
|
||||||
std::string username;
|
std::string username;
|
||||||
std::string server;
|
std::string server;
|
||||||
constexpr operator FullJid() const {
|
constexpr operator FullJid(this auto&& self) {
|
||||||
return FullJid{.username = username, .server = server, .resource = ""};
|
return {.username = std::forward_like<decltype(self)>(self.username), .server = std::forward_like<decltype(self)>(self.server)};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] static auto Parse(std::string_view jid) -> BareJid;
|
[[nodiscard]] static auto Parse(std::string_view jid) -> BareJid;
|
||||||
friend auto ToString(const BareJid& jid) -> std::string;
|
friend auto ToString(const BareJid& jid) -> std::string;
|
||||||
|
|
||||||
|
|
|
@ -38,9 +38,9 @@ struct Roster {
|
||||||
}
|
}
|
||||||
friend constexpr auto operator<<(xmlpp::Element* element, const Roster& roster) {
|
friend constexpr auto operator<<(xmlpp::Element* element, const Roster& roster) {
|
||||||
element->set_attribute("xmlns", Roster::kDefaultNamespace);
|
element->set_attribute("xmlns", Roster::kDefaultNamespace);
|
||||||
std::ranges::for_each(roster.items, [element](const auto& item) {
|
for(const auto& item : roster.items) {
|
||||||
element->add_child_element("item")->set_attribute("jid", ToString(item));
|
element->add_child_element("item")->set_attribute("jid", ToString(item));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> Roster {
|
[[nodiscard]] static constexpr auto Parse(xmlpp::Element* element) -> Roster {
|
||||||
const auto& item_nodes = element->get_children("item");
|
const auto& item_nodes = element->get_children("item");
|
||||||
|
@ -69,8 +69,4 @@ using GetRoster = Get<Roster>;
|
||||||
using ResultRoster = Result<Roster>;
|
using ResultRoster = Result<Roster>;
|
||||||
using IqRoster = Iq<Roster>;
|
using IqRoster = Iq<Roster>;
|
||||||
|
|
||||||
inline auto MakeGetRoster(const FullJid& jid) {
|
|
||||||
return GetRoster{.id = "1", .from = ToString(jid), .payload = Roster{}};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace larra::xmpp::iq
|
} // namespace larra::xmpp::iq
|
||||||
|
|
|
@ -73,6 +73,25 @@ struct ErrorImpl : BaseError {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper class to prevent parsing response stream into an expected return type if its name is a 'stream:error'
|
||||||
|
struct UnknownXmppError : BaseError {
|
||||||
|
static constexpr auto kDefaultName = "stream:error";
|
||||||
|
static constexpr std::string_view kErrorMessage = "Unknown XMPP stream error";
|
||||||
|
|
||||||
|
static constexpr auto TryParse(xmlpp::Element* element) {
|
||||||
|
return std::optional{UnknownXmppError{}};
|
||||||
|
}
|
||||||
|
static constexpr auto Parse(xmlpp::Element* element) {
|
||||||
|
return TryParse(element).value();
|
||||||
|
}
|
||||||
|
friend constexpr auto operator<<(xmlpp::Element* element, const UnknownXmppError& obj) -> void {
|
||||||
|
throw std::format("'{}' must never be written into the real stream!", kErrorMessage);
|
||||||
|
}
|
||||||
|
[[nodiscard]] constexpr auto what() const noexcept -> const char* override {
|
||||||
|
return kErrorMessage.data();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct BadFormat : ErrorImpl<BadFormat> {};
|
struct BadFormat : ErrorImpl<BadFormat> {};
|
||||||
struct BadNamespacePrefix : ErrorImpl<BadNamespacePrefix> {};
|
struct BadNamespacePrefix : ErrorImpl<BadNamespacePrefix> {};
|
||||||
struct Conflict : ErrorImpl<Conflict> {};
|
struct Conflict : ErrorImpl<Conflict> {};
|
||||||
|
@ -81,7 +100,7 @@ struct HostGone : ErrorImpl<HostGone> {};
|
||||||
struct HostUnknown : ErrorImpl<HostUnknown> {};
|
struct HostUnknown : ErrorImpl<HostUnknown> {};
|
||||||
struct ImproperAdressing : ErrorImpl<ImproperAdressing> {};
|
struct ImproperAdressing : ErrorImpl<ImproperAdressing> {};
|
||||||
struct InternalServerError : ErrorImpl<InternalServerError> {};
|
struct InternalServerError : ErrorImpl<InternalServerError> {};
|
||||||
struct InvalidForm : ErrorImpl<InvalidForm> {};
|
struct InvalidFrom : ErrorImpl<InvalidFrom> {};
|
||||||
struct InvalidNamespace : ErrorImpl<InvalidNamespace> {};
|
struct InvalidNamespace : ErrorImpl<InvalidNamespace> {};
|
||||||
struct InvalidXml : ErrorImpl<InvalidXml> {};
|
struct InvalidXml : ErrorImpl<InvalidXml> {};
|
||||||
struct NotAuthorized : ErrorImpl<NotAuthorized> {};
|
struct NotAuthorized : ErrorImpl<NotAuthorized> {};
|
||||||
|
@ -109,7 +128,7 @@ using StreamError = std::variant<error::stream::BadFormat,
|
||||||
error::stream::HostUnknown,
|
error::stream::HostUnknown,
|
||||||
error::stream::ImproperAdressing,
|
error::stream::ImproperAdressing,
|
||||||
error::stream::InternalServerError,
|
error::stream::InternalServerError,
|
||||||
error::stream::InvalidForm,
|
error::stream::InvalidFrom,
|
||||||
error::stream::InvalidNamespace,
|
error::stream::InvalidNamespace,
|
||||||
error::stream::InvalidXml,
|
error::stream::InvalidXml,
|
||||||
error::stream::NotAuthorized,
|
error::stream::NotAuthorized,
|
||||||
|
@ -123,6 +142,10 @@ using StreamError = std::variant<error::stream::BadFormat,
|
||||||
error::stream::UnsupportedEncoding,
|
error::stream::UnsupportedEncoding,
|
||||||
error::stream::UnsupportedFeature,
|
error::stream::UnsupportedFeature,
|
||||||
error::stream::UnsupportedStanzaType,
|
error::stream::UnsupportedStanzaType,
|
||||||
error::stream::UnsupportedVersion>;
|
error::stream::UnsupportedVersion,
|
||||||
|
error::stream::UnknownXmppError>;
|
||||||
|
|
||||||
|
static_assert(!std::is_same_v<typename std::variant_alternative_t<std::variant_size_v<StreamError> - 1, StreamError>, StreamError>,
|
||||||
|
"'UnknownXmppError' must be at the end of 'StreamError' variant");
|
||||||
|
|
||||||
} // namespace larra::xmpp
|
} // namespace larra::xmpp
|
||||||
|
|
|
@ -297,7 +297,7 @@ struct RangeToWrapper : T {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept LengthCalculatable = requires(const T& obj) {
|
concept LengthCalculatable = requires(const T& obj) {
|
||||||
{ obj.length() } -> std::convertible_to<std::size_t>; // Checks if obj has a length() method returning a type convertible to std::size_t
|
{ obj.length() } -> std::convertible_to<std::size_t>;
|
||||||
} || std::convertible_to<T, std::string>;
|
} || std::convertible_to<T, std::string>;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <larra/jid.hpp>
|
||||||
#include <larra/roster.hpp>
|
#include <larra/roster.hpp>
|
||||||
|
|
||||||
#include "larra/jid.hpp"
|
|
||||||
|
|
||||||
namespace larra::xmpp {
|
namespace larra::xmpp {
|
||||||
|
|
||||||
TEST(Roster, SerializeAndParse) {
|
TEST(Roster, SerializeAndParse) {
|
||||||
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
||||||
auto roster = iq::MakeGetRoster(jid);
|
auto roster = iq::GetRoster{.id = "1", .from = ToString(jid), .payload = iq::Roster{.items = {{"u1", "s1"}, {"u2", "s2"}, {"u3", "s3"}}}};
|
||||||
roster.payload.items.emplace_back("u1", "s1");
|
|
||||||
roster.payload.items.emplace_back("u2", "s2");
|
|
||||||
roster.payload.items.emplace_back("u3", "s3");
|
|
||||||
|
|
||||||
xmlpp::Document doc;
|
xmlpp::Document doc;
|
||||||
auto node = doc.create_root_node("iq");
|
auto node = doc.create_root_node("iq");
|
||||||
|
@ -22,18 +18,19 @@ TEST(Roster, SerializeAndParse) {
|
||||||
ASSERT_EQ(roster.payload.items.size(), parse_res.payload.items.size());
|
ASSERT_EQ(roster.payload.items.size(), parse_res.payload.items.size());
|
||||||
for(const auto& [idx, expect_el, parsed_el] : std::views::zip(std::views::iota(0), roster.payload.items, parse_res.payload.items)) {
|
for(const auto& [idx, expect_el, parsed_el] : std::views::zip(std::views::iota(0), roster.payload.items, parse_res.payload.items)) {
|
||||||
EXPECT_EQ(expect_el, parsed_el) << "Mismatched on idx: " << idx;
|
EXPECT_EQ(expect_el, parsed_el) << "Mismatched on idx: " << idx;
|
||||||
// std::cerr << " " << "idx: " << idx << "; expect_el: " << expect_el << "; parsed_el: " << parsed_el << '\n';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr std::string_view kRosterPrintExpectedData = "Roster: [\n\tu1@s1\n\tu2@s2\n\tu3@s3\n\t]";
|
||||||
TEST(Roster, Print) {
|
TEST(Roster, Print) {
|
||||||
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
FullJid jid{.username = "test", .server = "server", .resource = "res"}; // NOLINT
|
||||||
auto roster = iq::MakeGetRoster(jid);
|
auto roster = iq::GetRoster{.id = "1", .from = ToString(jid), .payload = iq::Roster{.items = {{"u1", "s1"}, {"u2", "s2"}, {"u3", "s3"}}}};
|
||||||
roster.payload.items.emplace_back("u1", "s1");
|
|
||||||
roster.payload.items.emplace_back("u2", "s2");
|
|
||||||
roster.payload.items.emplace_back("u3", "s3");
|
|
||||||
|
|
||||||
EXPECT_NO_THROW({ std::cerr << "[ ] Roster payload: " << ToString(roster.payload) << '\n'; });
|
EXPECT_NO_THROW({
|
||||||
|
auto roster_str = ToString(roster.payload);
|
||||||
|
EXPECT_EQ(kRosterPrintExpectedData.length(), roster_str.capacity());
|
||||||
|
EXPECT_EQ(kRosterPrintExpectedData, roster_str);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace larra::xmpp
|
} // namespace larra::xmpp
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
#include <larra/serialization/auto.hpp>
|
#include <larra/serialization/auto.hpp>
|
||||||
#include <larra/serialization/error.hpp>
|
#include <larra/serialization/error.hpp>
|
||||||
#include <larra/stream_error.hpp>
|
#include <larra/stream_error.hpp>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include "utempl/utils.hpp"
|
||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue