Newer
Older
#include <thread>
#include <gtest/gtest.h>
#include "client.hpp"
#include "mtx/requests.hpp"
#include "mtx/responses.hpp"
using namespace mtx::events::collections;
using namespace std;
using ErrType = std::experimental::optional<errors::ClientError>;
void
check_error(ErrType err)
{
if (err) {
cout << "matrix (error) : " << err->matrix_error.error << "\n";
cout << "matrix (errcode): " << mtx::errors::to_string(err->matrix_error.errcode)
<< "\n";
cout << "error_code : " << err->error_code << "\n";
cout << "status_code : " << err->status_code << "\n";
}
ASSERT_FALSE(err);
}
void
validate_login(const std::string &user, const mtx::responses::Login &res)
{
EXPECT_EQ(res.user_id.toString(), user);
EXPECT_EQ(res.home_server, "localhost");
ASSERT_TRUE(res.access_token.size() > 100);
ASSERT_TRUE(res.device_id.size() > 5);
}
get_event_ids(const std::vector<Collection> &events)
{
vector<string> ids;
for (const auto &e : events)
ids.push_back(mpark::visit([](auto msg) { return msg.event_id; }, e));
return ids;
}
TEST(ClientAPI, LoginSuccess)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->login("alice", "secret", [](const mtx::responses::Login &res, ErrType err) {
validate_login("@alice:localhost", res);
});
mtx_client->login("bob", "secret", [](const mtx::responses::Login &res, ErrType err) {
validate_login("@bob:localhost", res);
});
mtx_client->login("carl", "secret", [](const mtx::responses::Login &res, ErrType err) {
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
validate_login("@carl:localhost", res);
});
mtx_client->close();
}
TEST(ClientAPI, LoginWrongPassword)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->login(
"alice", "wrong_password", [](const mtx::responses::Login &res, ErrType err) {
ASSERT_TRUE(err);
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode), "M_FORBIDDEN");
EXPECT_EQ(err->status_code, boost::beast::http::status::forbidden);
EXPECT_EQ(res.user_id.toString(), "");
EXPECT_EQ(res.device_id, "");
EXPECT_EQ(res.home_server, "");
EXPECT_EQ(res.access_token, "");
});
mtx_client->close();
}
TEST(ClientAPI, LoginWrongUsername)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->login("john", "secret", [](const mtx::responses::Login &res, ErrType err) {
ASSERT_TRUE(err);
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode), "M_FORBIDDEN");
EXPECT_EQ(err->status_code, boost::beast::http::status::forbidden);
EXPECT_EQ(res.user_id.toString(), "");
EXPECT_EQ(res.device_id, "");
EXPECT_EQ(res.home_server, "");
EXPECT_EQ(res.access_token, "");
});
mtx_client->close();
}
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
TEST(ClientAPI, EmptyUserAvatar)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &res, ErrType err) {
ASSERT_FALSE(err);
auto const alice_id = res.user_id;
validate_login(alice_id.toString(), res);
alice->set_avatar_url("", [alice, alice_id](ErrType err) {
ASSERT_FALSE(err);
auto done = false;
alice->get_profile(
alice_id, [&done](const mtx::responses::Profile &res, ErrType err) {
ASSERT_FALSE(err);
ASSERT_TRUE(res.avatar_url.size() == 0);
done = true;
});
while (!done)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
alice->get_avatar_url(
alice_id, [](const mtx::responses::AvatarUrl &res, ErrType err) {
ASSERT_FALSE(err);
ASSERT_TRUE(res.avatar_url.size() == 0);
});
});
});
alice->close();
}
TEST(ClientAPI, RealUserAvatar)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &res, ErrType err) {
ASSERT_FALSE(err);
auto const alice_id = res.user_id;
auto const avatar_url = "mxc://matrix.org/wefh34uihSDRGhw34";
validate_login(alice_id.toString(), res);
alice->set_avatar_url(avatar_url, [alice, alice_id, avatar_url](ErrType err) {
ASSERT_FALSE(err);
auto done = false;
alice->get_profile(
alice_id,
[avatar_url, &done](const mtx::responses::Profile &res, ErrType err) {
ASSERT_FALSE(err);
ASSERT_TRUE(res.avatar_url == avatar_url);
done = true;
});
while (!done)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
alice->get_avatar_url(
alice_id,
[avatar_url](const mtx::responses::AvatarUrl &res, ErrType err) {
ASSERT_FALSE(err);
ASSERT_TRUE(res.avatar_url == avatar_url);
});
});
});
alice->close();
}
TEST(ClientAPI, ChangeDisplayName)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->login(
"alice", "secret", [mtx_client](const mtx::responses::Login &, ErrType err) {
check_error(err);
// Change the display name to Arthur Dent and verify its success through the lack
// of an error
mtx_client->set_displayname("Arthur Dent", [](ErrType err) { check_error(err); });
});
mtx_client->close();
}
TEST(ClientAPI, EmptyDisplayName)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->login(
"alice", "secret", [mtx_client](const mtx::responses::Login &, ErrType err) {
check_error(err);
// Change the display name to an empty string and verify its success through the
// lack of an error
mtx_client->set_displayname("", [](ErrType err) { check_error(err); });
});
mtx_client->close();
}
TEST(ClientAPI, CreateRoom)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->login(
"alice", "secret", [mtx_client](const mtx::responses::Login &, ErrType err) {
check_error(err);
while (mtx_client->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
mtx_client->create_room(req, [](const mtx::responses::CreateRoom &res, ErrType err) {
ASSERT_TRUE(res.room_id.localpart().size() > 10);
EXPECT_EQ(res.room_id.hostname(), "localhost");
});
mtx_client->close();
}
TEST(ClientAPI, LogoutSuccess)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
std::string token;
// Login and prove that login was successful by creating a room
mtx_client->login(
"alice", "secret", [&token](const mtx::responses::Login &res, ErrType err) {
token = res.access_token;
});
while (token.empty()) {
// Block while we are logging in
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
mtx_client->set_access_token(token);
mtx::requests::CreateRoom req;
req.name = "Test1";
req.topic = "Topic1";
mtx_client->create_room(
req, [](const mtx::responses::CreateRoom &, ErrType err) { check_error(err); });
// Logout and prove that logout was successful and deleted the access_token_ for the client
mtx_client->logout([mtx_client, &token](const mtx::responses::Logout &, ErrType err) {
check_error(err);
token.clear();
});
while (token.size()) {
// Block while we are logging out
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
// Verify that sending requests with this mtx_client fails after logout
mtx::requests::CreateRoom failReq;
failReq.name = "42";
failReq.topic = "LifeUniverseEverything";
mtx_client->create_room(failReq, [](const mtx::responses::CreateRoom &, ErrType err) {
ASSERT_TRUE(err);
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode), "M_MISSING_TOKEN");
EXPECT_EQ(err->status_code, boost::beast::http::status::forbidden);
});
mtx_client->close();
}
TEST(ClientAPI, LogoutInvalidatesTokenOnServer)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
std::string token;
// Login and prove that login was successful by creating a room
mtx_client->login(
"alice", "secret", [&token](const mtx::responses::Login &res, ErrType err) {
token = res.access_token;
});
while (token.empty()) {
// Block while we are logging in
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
mtx_client->set_access_token(token);
mtx::requests::CreateRoom req;
req.name = "Test1";
req.topic = "Topic1";
mtx_client->create_room(
req, [](const mtx::responses::CreateRoom &, ErrType err) { check_error(err); });
// Logout and prove that logout was successful by verifying the old access_token_ is no
// longer valid
mtx_client->logout([mtx_client, &token](const mtx::responses::Logout &, ErrType err) {
check_error(err);
mtx_client->set_access_token(token);
token.clear();
});
while (token.size()) {
// Block while we are logging out
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
// Verify that creating a room with the old access_token_ no longer succeeds after logout
mtx::requests::CreateRoom failReq;
failReq.name = "42";
failReq.topic = "LifeUniverseEverything";
mtx_client->create_room(failReq, [](const mtx::responses::CreateRoom &, ErrType err) {
ASSERT_TRUE(err);
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode), "M_UNKNOWN_TOKEN");
EXPECT_EQ(err->status_code, boost::beast::http::status::forbidden);
});
mtx_client->close();
}
TEST(ClientAPI, CreateRoomInvites)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
auto carl = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, ErrType err) {
check_error(err);
bob->login(
"bob", "secret", [bob](const mtx::responses::Login &, ErrType err) { check_error(err); });
carl->login("carl", "secret", [carl](const mtx::responses::Login &, ErrType err) {
check_error(err);
while (alice->access_token().empty() || bob->access_token().empty() ||
carl->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
req.invite = {"@bob:localhost", "@carl:localhost"};
alice->create_room(req, [bob, carl](const mtx::responses::CreateRoom &res, ErrType err) {
auto room_id = res.room_id;
bob->join_room(res.room_id,
[](const nlohmann::json &, ErrType err) { check_error(err); });
[](const nlohmann::json &, ErrType err) { check_error(err); });
});
alice->close();
bob->close();
carl->close();
}
TEST(ClientAPI, JoinRoom)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, ErrType err) {
check_error(err);
bob->login(
"bob", "secret", [bob](const mtx::responses::Login &, ErrType err) { check_error(err); });
while (alice->access_token().empty() || bob->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// Creating a random room alias.
// TODO: add a type for room aliases.
const auto alias = utils::random_token(20, false);
req.name = "Name";
req.topic = "Topic";
req.invite = {"@bob:localhost"};
req.room_alias_name = alias;
alice->create_room(req, [bob, alias](const mtx::responses::CreateRoom &res, ErrType err) {
auto room_id = res.room_id;
bob->join_room(res.room_id,
[](const nlohmann::json &, ErrType err) { check_error(err); });
using namespace mtx::identifiers;
bob->join_room(parse<Room>("!random_room_id:localhost"),
[](const nlohmann::json &, ErrType err) {
ASSERT_TRUE(err);
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode),
"M_UNRECOGNIZED");
});
// Join the room using an alias.
bob->join_room("#" + alias + ":localhost",
[](const nlohmann::json &, ErrType err) { check_error(err); });
});
alice->close();
bob->close();
}
TEST(ClientAPI, LeaveRoom)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, ErrType err) {
check_error(err);
bob->login(
"bob", "secret", [bob](const mtx::responses::Login &, ErrType err) { check_error(err); });
while (alice->access_token().empty() || bob->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
req.invite = {"@bob:localhost"};
alice->create_room(req, [bob](const mtx::responses::CreateRoom &res, ErrType err) {
auto room_id = res.room_id;
bob->join_room(res.room_id, [room_id, bob](const nlohmann::json &, ErrType err) {
room_id, [](const nlohmann::json &, ErrType err) { check_error(err); });
});
});
// Trying to leave a non-existent room should fail.
bob->leave_room(
parse<Room>("!random_room_id:localhost"), [](const nlohmann::json &, ErrType err) {
ASSERT_TRUE(err);
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode), "M_UNRECOGNIZED");
EXPECT_EQ(err->matrix_error.error, "Not a known room");
});
alice->close();
bob->close();
}
TEST(ClientAPI, InviteRoom)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, ErrType err) {
check_error(err);
bob->login(
"bob", "secret", [bob](const mtx::responses::Login &, ErrType err) { check_error(err); });
while (alice->access_token().empty() || bob->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
req.invite = {};
alice->create_room(req, [alice, bob](const mtx::responses::CreateRoom &res, ErrType err) {
auto room_id = res.room_id;
alice->invite_user(room_id,
"@bob:localhost",
[room_id, bob](const mtx::responses::Empty &, ErrType err) {
check_error(err);
bob->join_room(room_id,
[](const nlohmann::json &, ErrType err) {
check_error(err);
});
});
});
alice->close();
bob->close();
}
TEST(ClientAPI, InvalidInvite)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, ErrType err) {
check_error(err);
bob->login(
"bob", "secret", [bob](const mtx::responses::Login &, ErrType err) { check_error(err); });
while (alice->access_token().empty() || bob->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
req.invite = {};
alice->create_room(req, [alice, bob](const mtx::responses::CreateRoom &res, ErrType err) {
auto room_id = res.room_id;
bob->invite_user(room_id,
"@carl:localhost",
[room_id, bob](const mtx::responses::Empty &, ErrType err) {
ASSERT_TRUE(err);
EXPECT_EQ(
mtx::errors::to_string(err->matrix_error.errcode),
"M_FORBIDDEN");
});
});
alice->close();
bob->close();
}
TEST(ClientAPI, Sync)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->login(
"alice", "secret", [mtx_client](const mtx::responses::Login &, ErrType err) {
check_error(err);
while (mtx_client->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
mtx_client->create_room(req, [mtx_client](const mtx::responses::CreateRoom &, ErrType err) {
mtx_client->sync(
"", "", false, 0, [](const mtx::responses::Sync &res, ErrType err) {
ASSERT_TRUE(res.rooms.join.size() > 0);
ASSERT_TRUE(res.next_batch.size() > 0);
});
TEST(ClientAPI, Versions)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->versions([](const mtx::responses::Versions &res, ErrType err) {
EXPECT_EQ(res.versions.size(), 4);
EXPECT_EQ(res.versions.at(0), "r0.0.1");
EXPECT_EQ(res.versions.at(1), "r0.1.0");
EXPECT_EQ(res.versions.at(2), "r0.2.0");
EXPECT_EQ(res.versions.at(3), "r0.3.0");
});
mtx_client->close();
}
TEST(ClientAPI, Typing)
{
auto alice = std::make_shared<Client>("localhost");
alice->login(
"alice", "secret", [](const mtx::responses::Login &, ErrType err) { check_error(err); });
while (alice->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx::requests::CreateRoom req;
alice->create_room(req, [alice](const mtx::responses::CreateRoom &res, ErrType err) {
alice->start_typing(res.room_id, 10000, [alice, res](ErrType err) {
const auto room_id = res.room_id.toString();
atomic_bool can_continue(false);
alice->sync(
"",
"",
false,
0,
[room_id, &can_continue](const mtx::responses::Sync &res, ErrType err) {
can_continue = true;
auto room = res.rooms.join.at(room_id);
EXPECT_EQ(room.ephemeral.typing.size(), 1);
EXPECT_EQ(room.ephemeral.typing.front(), "@alice:localhost");
});
while (!can_continue)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
alice->stop_typing(res.room_id, [alice, room_id](ErrType err) {
alice->sync(
"",
"",
false,
0,
[room_id](const mtx::responses::Sync &res, ErrType err) {
auto room = res.rooms.join.at(room_id);
EXPECT_EQ(room.ephemeral.typing.size(), 0);
});
});
});
});
alice->close();
}
TEST(ClientAPI, SendMessages)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, ErrType err) {
check_error(err);
bob->login(
"bob", "secret", [bob](const mtx::responses::Login &, ErrType err) { check_error(err); });
while (alice->access_token().empty() || bob->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx::requests::CreateRoom req;
req.invite = {"@bob:localhost"};
alice->create_room(req, [alice, bob](const mtx::responses::CreateRoom &res, ErrType err) {
auto room_id = res.room_id;
bob->join_room(
res.room_id, [alice, bob, room_id](const nlohmann::json &, ErrType err) {
// Flag to indicate when those messages would be ready to be read by
// alice.
vector<string> event_ids;
mtx::events::msg::Text text;
text.body = "hello alice!";
bob->send_room_message<mtx::events::msg::Text,
mtx::events::EventType::RoomMessage>(
room_id,
text,
[&event_ids](const mtx::responses::EventId &res, ErrType err) {
event_ids.push_back(res.event_id.toString());
});
mtx::events::msg::Emote emote;
emote.body = "*bob tests";
bob->send_room_message<mtx::events::msg::Emote,
mtx::events::EventType::RoomMessage>(
room_id,
emote,
[&event_ids](const mtx::responses::EventId &res, ErrType err) {
event_ids.push_back(res.event_id.toString());
});
while (event_ids.size() != 2)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
alice->sync(
"",
"",
false,
0,
[room_id, event_ids](const mtx::responses::Sync &res, ErrType err) {
auto ids = get_event_ids<TimelineEvents>(
res.rooms.join.at(room_id.toString()).timeline.events);
// The sent event ids should be visible in the timeline.
for (const auto &event_id : event_ids)
ASSERT_TRUE(std::find(ids.begin(),
ids.end(),
event_id) != std::end(ids));
});
});
});
alice->close();
bob->close();
}
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
TEST(ClientAPI, SendStateEvents)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, ErrType err) {
check_error(err);
});
bob->login(
"bob", "secret", [bob](const mtx::responses::Login &, ErrType err) { check_error(err); });
while (alice->access_token().empty() || bob->access_token().empty())
std::this_thread::sleep_for(std::chrono::milliseconds(100));
mtx::requests::CreateRoom req;
req.invite = {"@bob:localhost"};
alice->create_room(req, [alice, bob](const mtx::responses::CreateRoom &res, ErrType err) {
check_error(err);
auto room_id = res.room_id;
// Flag to indicate when those messages would be ready to be read by
// alice.
vector<string> event_ids;
mtx::events::state::Name event;
event.name = "Bob's room";
bob->send_state_event<mtx::events::state::Name, mtx::events::EventType::RoomName>(
room_id, event, [](const mtx::responses::EventId &, ErrType err) {
ASSERT_TRUE(err);
ASSERT_EQ("M_FORBIDDEN",
mtx::errors::to_string(err->matrix_error.errcode));
});
mtx::events::state::Name name_event;
name_event.name = "Alice's room";
alice->send_state_event<mtx::events::state::Name, mtx::events::EventType::RoomName>(
room_id,
name_event,
[&event_ids](const mtx::responses::EventId &res, ErrType err) {
check_error(err);
event_ids.push_back(res.event_id.toString());
});
mtx::events::state::Avatar avatar;
avatar.url = "mxc://localhost/random";
alice->send_state_event<mtx::events::state::Avatar,
mtx::events::EventType::RoomAvatar>(
room_id, avatar, [&event_ids](const mtx::responses::EventId &res, ErrType err) {
check_error(err);
event_ids.push_back(res.event_id.toString());
});
while (event_ids.size() != 2)
std::this_thread::sleep_for(std::chrono::milliseconds(500));
alice->sync("",
"",
false,
0,
[room_id, event_ids](const mtx::responses::Sync &res, ErrType err) {
check_error(err);
auto ids = get_event_ids<TimelineEvents>(
res.rooms.join.at(room_id.toString()).timeline.events);
// The sent event ids should be visible in the timeline.
for (const auto &event_id : event_ids)
ASSERT_TRUE(std::find(ids.begin(),
ids.end(),
event_id) != std::end(ids));
});
});
alice->close();
bob->close();
}