Newer
Older
#include <boost/algorithm/string.hpp>
#include <nlohmann/json.hpp>
#include "mtx/events/collections.hpp"
#include "mtx/events/encrypted.hpp"
#include "mtx/requests.hpp"
#include "mtx/responses.hpp"
#include "mtxclient/http/client.hpp"
#include "test_helpers.hpp"
using namespace mtx::http;
using namespace mtx::events::collections;
using namespace mtx::requests;
TEST(ClientAPI, Register)
{
auto user = std::make_shared<Client>("localhost");
user->registration("alice", "secret", [](const mtx::responses::Register &, RequestErr err) {
ASSERT_TRUE(err);
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode), "M_USER_IN_USE");
});
auto username = utils::random_token(10, false);
// Synapse converts the username to lowercase.
boost::algorithm::to_lower(username);
user->registration(
username, "secret", [user, username](const mtx::responses::Register &, RequestErr err) {
if (!err || err->matrix_error.unauthorized.flows.size() == 0)
EXPECT_EQ(err->matrix_error.unauthorized.flows.size(), 1);
EXPECT_EQ(err->matrix_error.unauthorized.flows[0].stages[0], "m.login.dummy");
{err->matrix_error.unauthorized.session, mtx::user_interactive::auth::Dummy{}},
[username](const mtx::responses::Register &res, RequestErr err) {
const auto user_id = "@" + username + ":localhost";
check_error(err);
EXPECT_EQ(res.user_id.to_string(), user_id);
});
TEST(ClientAPI, LoginSuccess)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->login("alice", "secret", [](const mtx::responses::Login &res, RequestErr err) {
mtx_client->login("bob", "secret", [](const mtx::responses::Login &res, RequestErr err) {
mtx_client->login("carl", "secret", [](const mtx::responses::Login &res, RequestErr err) {
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, RequestErr 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.device_id, "");
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, RequestErr 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.device_id, "");
EXPECT_EQ(res.access_token, "");
});
mtx_client->close();
}
TEST(ClientAPI, LoginFlows)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->get_login([](const mtx::responses::LoginFlows &res, RequestErr err) {
ASSERT_FALSE(err);
EXPECT_EQ(res.flows[0].type, mtx::user_interactive::auth_types::password);
});
mtx_client->close();
}
TEST(ClientAPI, SSORedirect)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
EXPECT_EQ(mtx_client->login_sso_redirect("http://aaa:555/sso"),
"https://localhost:443/_matrix/client/r0/login/sso/"
"redirect?redirectUrl=http%3A%2F%2Faaa%3A555%2Fsso");
TEST(ClientAPI, EmptyUserAvatar)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &res, RequestErr err) {
ASSERT_FALSE(err);
auto const alice_id = res.user_id;
alice->set_avatar_url("", [alice, alice_id](RequestErr err) {
ASSERT_FALSE(err);
auto done = false;
alice->get_profile(
alice_id.to_string(),
[&done](const mtx::responses::Profile &res, RequestErr err) {
ASSERT_FALSE(err);
ASSERT_TRUE(res.avatar_url.size() == 0);
done = true;
});
while (!done)
alice->get_avatar_url(
alice_id.to_string(),
[](const mtx::responses::AvatarUrl &res, RequestErr 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, RequestErr err) {
ASSERT_FALSE(err);
auto const alice_id = res.user_id;
auto const avatar_url = "mxc://matrix.org/wefh34uihSDRGhw34";
alice->set_avatar_url(avatar_url, [alice, alice_id, avatar_url](RequestErr err) {
ASSERT_FALSE(err);
auto done = false;
alice->get_profile(
[avatar_url, &done](const mtx::responses::Profile &res, RequestErr err) {
ASSERT_FALSE(err);
ASSERT_TRUE(res.avatar_url == avatar_url);
done = true;
});
while (!done)
alice->get_avatar_url(
[avatar_url](const mtx::responses::AvatarUrl &res, RequestErr 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 &, RequestErr err) {
// Change the display name to Arthur Dent and verify its success through the lack
// of an error
mtx_client->set_displayname("Arthur Dent",
[](RequestErr 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 &, RequestErr err) {
// Change the display name to an empty string and verify its success through the
// lack of an error
mtx_client->set_displayname("", [](RequestErr 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 &, RequestErr err) {
while (mtx_client->access_token().empty())
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
mtx_client->create_room(req, [](const mtx::responses::CreateRoom &res, RequestErr err) {
ASSERT_TRUE(res.room_id.localpart().size() > 10);
EXPECT_EQ(res.room_id.hostname(), "localhost");
});
mtx_client->close();
}
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
TEST(ClientAPI, TagRoom)
{
std::shared_ptr<Client> mtx_client = std::make_shared<Client>("localhost");
mtx_client->login(
"alice", "secret", [mtx_client](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (mtx_client->access_token().empty())
sleep();
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
mtx_client->create_room(
req, [mtx_client](const mtx::responses::CreateRoom &res, RequestErr err) {
auto room_id = res.room_id;
check_error(err);
mtx_client->put_tag(
room_id.to_string(), "u.Test", {0.5}, [mtx_client, room_id](RequestErr err) {
check_error(err);
mtx_client->get_tags(
room_id.to_string(),
[mtx_client, room_id](mtx::events::account_data::Tags tags,
RequestErr err) {
check_error(err);
EXPECT_EQ(tags.tags.at("u.Test").order, 0.5);
mtx_client->delete_tag(
room_id.to_string(),
"u.Test",
[mtx_client, room_id](RequestErr err) {
check_error(err);
mtx_client->get_tags(
room_id.to_string(),
[mtx_client,
room_id](mtx::events::account_data::Tags tags,
RequestErr err) {
check_error(err);
EXPECT_EQ(tags.tags.count("u.Test"), 0);
});
});
});
});
});
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, RequestErr err) {
while (token.empty())
sleep();
mtx_client->set_access_token(token);
mtx::requests::CreateRoom req;
req.name = "Test1";
req.topic = "Topic1";
req, [](const mtx::responses::CreateRoom &, RequestErr 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 &, RequestErr err) {
while (token.size())
sleep();
// 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 &, RequestErr err) {
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode), "M_UNRECOGNIZED");
});
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, RequestErr err) {
while (token.empty())
sleep();
mtx_client->set_access_token(token);
mtx::requests::CreateRoom req;
req.name = "Test1";
req.topic = "Topic1";
req, [](const mtx::responses::CreateRoom &, RequestErr 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 &, RequestErr err) {
mtx_client->set_access_token(token);
token.clear();
});
while (token.size())
sleep();
// 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 &, RequestErr err) {
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode), "M_UNRECOGNIZED");
});
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 &, RequestErr err) {
bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
carl->login("carl", "secret", [carl](const mtx::responses::Login &, RequestErr err) {
while (alice->access_token().empty() || bob->access_token().empty() ||
carl->access_token().empty())
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, RequestErr err) {
auto room_id = res.room_id.to_string();
bob->join_room(room_id, [](const mtx::responses::RoomId &, RequestErr err) {
check_error(err);
});
carl->join_room(room_id, [](const mtx::responses::RoomId &, RequestErr 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 &, RequestErr err) {
bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty() || bob->access_token().empty())
// 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, RequestErr err) {
check_error(err);
auto room_id = res.room_id.to_string();
bob->join_room(room_id, [](const mtx::responses::RoomId &, RequestErr err) {
check_error(err);
});
using namespace mtx::identifiers;
bob->join_room("!random_room_id:localhost",
[](const mtx::responses::RoomId &, RequestErr err) {
ASSERT_TRUE(err);
EXPECT_EQ(
mtx::errors::to_string(err->matrix_error.errcode),
"M_UNKNOWN");
});
// Join the room using an alias.
bob->join_room(
"#" + alias + ":localhost",
[](const mtx::responses::RoomId &, RequestErr err) { check_error(err); });
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 &, RequestErr err) {
bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty() || bob->access_token().empty())
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
req.invite = {"@bob:localhost"};
alice->create_room(req, [bob](const mtx::responses::CreateRoom &res, RequestErr err) {
bob->join_room(res.room_id.to_string(),
[room_id, bob](const mtx::responses::RoomId &, RequestErr err) {
check_error(err);
bob->leave_room(room_id.to_string(),
[](mtx::responses::Empty, RequestErr err) {
check_error(err);
});
});
// Trying to leave a non-existent room should fail.
bob->leave_room("!random_room_id:localhost", [](mtx::responses::Empty, RequestErr err) {
ASSERT_TRUE(err);
EXPECT_EQ(mtx::errors::to_string(err->matrix_error.errcode), "M_UNKNOWN");
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 &, RequestErr err) {
bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty() || bob->access_token().empty())
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
req.invite = {};
alice->create_room(
req, [alice, bob](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
auto room_id = res.room_id.to_string();
alice->invite_user(room_id,
"@bob:localhost",
[room_id, bob](const mtx::responses::Empty &, RequestErr err) {
check_error(err);
bob->join_room(
room_id,
[](const mtx::responses::RoomId &, RequestErr err) {
check_error(err);
});
});
});
alice->close();
bob->close();
}
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
TEST(ClientAPI, KickRoom)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty() || bob->access_token().empty())
sleep();
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
req.invite = {};
alice->create_room(
req, [alice, bob](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
auto room_id = res.room_id.to_string();
alice->invite_user(
room_id,
"@bob:localhost",
[room_id, alice, bob](const mtx::responses::Empty &, RequestErr err) {
check_error(err);
bob->join_room(
room_id,
[alice, room_id](const mtx::responses::RoomId &, RequestErr err) {
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
check_error(err);
alice->kick_user(room_id,
"@bob:localhost",
[](const mtx::responses::Empty &,
RequestErr err) { check_error(err); });
});
});
});
alice->close();
bob->close();
}
TEST(ClientAPI, BanRoom)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty() || bob->access_token().empty())
sleep();
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
req.invite = {};
alice->create_room(
req, [alice, bob](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
auto room_id = res.room_id.to_string();
alice->invite_user(
room_id,
"@bob:localhost",
[room_id, alice, bob](const mtx::responses::Empty &, RequestErr err) {
check_error(err);
bob->join_room(
room_id,
[alice, room_id](const mtx::responses::RoomId &, RequestErr err) {
check_error(err);
alice->ban_user(
room_id,
"@bob:localhost",
[alice, room_id](const mtx::responses::Empty &,
RequestErr err) {
check_error(err);
alice->unban_user(
room_id,
"@bob:localhost",
[](const mtx::responses::Empty &,
RequestErr err) { check_error(err); },
"You not bad anymore!");
},
"You bad!");
});
});
});
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 &, RequestErr err) {
bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty() || bob->access_token().empty())
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
req.invite = {};
alice->create_room(
req, [alice, bob](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
auto room_id = res.room_id.to_string();
bob->invite_user(room_id,
"@carl:localhost",
[room_id, bob](const mtx::responses::Empty &, RequestErr 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 &, RequestErr err) {
while (mtx_client->access_token().empty())
mtx::requests::CreateRoom req;
req.name = "Name";
req.topic = "Topic";
mtx_client->create_room(
req, [mtx_client](const mtx::responses::CreateRoom &, RequestErr err) {
check_error(err);
SyncOpts opts;
opts.timeout = 0;
mtx_client->sync(opts, [](const mtx::responses::Sync &res, RequestErr err) {
check_error(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, RequestErr err) {
EXPECT_EQ(res.versions.size(), 7);
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");
EXPECT_EQ(res.versions.at(4), "r0.4.0");
EXPECT_EQ(res.versions.at(5), "r0.5.0");
EXPECT_EQ(res.versions.at(6), "r0.6.0");
TEST(ClientAPI, Typing)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty())
mtx::requests::CreateRoom req;
alice->create_room(req, [alice](const mtx::responses::CreateRoom &res, RequestErr err) {
alice->start_typing(res.room_id.to_string(), 10000, [alice, res](RequestErr err) {
const auto room_id = res.room_id.to_string();
SyncOpts opts;
opts.timeout = 0;
alice->sync(opts,
[room_id, &can_continue](const mtx::responses::Sync &res,
RequestErr err) {
check_error(err);
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)
alice->stop_typing(
res.room_id.to_string(), [alice, room_id](RequestErr err) {
check_error(err);
SyncOpts opts;
opts.timeout = 0;
alice->sync(
opts,
[room_id](const mtx::responses::Sync &res, RequestErr err) {
check_error(err);
auto room = res.rooms.join.at(room_id);
EXPECT_EQ(room.ephemeral.typing.size(), 0);
});
});
});
});
alice->close();
}
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
TEST(ClientAPI, Presence)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty())
sleep();
alice->put_presence_status(
mtx::presence::unavailable, "Is this thing on?", [alice](RequestErr err) {
check_error(err);
alice->presence_status(
alice->user_id().to_string(),
[alice](const mtx::events::presence::Presence &presence, RequestErr err) {
check_error(err);
EXPECT_EQ(presence.presence, mtx::presence::unavailable);
EXPECT_EQ(presence.status_msg, "Is this thing on?");
alice->put_presence_status(
mtx::presence::offline, std::nullopt, [alice](RequestErr err) {
check_error(err);
alice->presence_status(
alice->user_id().to_string(),
[alice](const mtx::events::presence::Presence &presence,
RequestErr err) {
check_error(err);
EXPECT_EQ(presence.presence,
mtx::presence::offline);
EXPECT_TRUE(presence.status_msg.empty());
});
});
});
});
alice->close();
}
TEST(ClientAPI, PresenceOverSync)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
bob->login(
"bob", "secret", [](const mtx::responses::Login &, RequestErr err) { check_error(err); });
while (alice->access_token().empty() && bob->access_token().empty())
sleep();
mtx::requests::CreateRoom req;
req.invite = {"@bob:localhost"};
alice->create_room(
req, [alice, bob](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
auto room_id = res.room_id.to_string();
bob->join_room(
room_id, [alice, bob, room_id](const mtx::responses::RoomId &, RequestErr err) {
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
check_error(err);
alice->put_presence_status(
mtx::presence::unavailable,
"Is this thing on?",
[alice, bob](RequestErr err) {
check_error(err);
SyncOpts opts;
opts.timeout = 10;
opts.set_presence = mtx::presence::online;
alice->sync(
opts,
[bob, opts](const mtx::responses::Sync &, RequestErr err) {
check_error(err);
bob->sync(
opts,
[bob](const mtx::responses::Sync &s,
RequestErr err) {
check_error(err);
ASSERT_GE(s.presence.size(), 1);
bool found = false;
for (const auto &p : s.presence) {
if (p.sender ==
"@alice:localhost") {
found = true;
EXPECT_EQ(
p.content.presence,
mtx::presence::online);
EXPECT_EQ(
p.content.status_msg,
"Is this thing "
"on?");
}
EXPECT_TRUE(found);
}
});
});
});
});
});
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 &, RequestErr err) {
bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty() || bob->access_token().empty())
mtx::requests::CreateRoom req;
req.invite = {"@bob:localhost"};
alice->create_room(
req, [alice, bob](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
auto room_id = res.room_id.to_string();
room_id, [alice, bob, room_id](const mtx::responses::RoomId &, RequestErr 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>(
room_id,
text,
[&event_ids](const mtx::responses::EventId &res, RequestErr err) {
event_ids.push_back(res.event_id.to_string());
check_error(err);
});
mtx::events::msg::Emote emote;
emote.body = "*bob tests";
bob->send_room_message<mtx::events::msg::Emote>(
room_id,
emote,
[&event_ids](const mtx::responses::EventId &res, RequestErr err) {
event_ids.push_back(res.event_id.to_string());
check_error(err);
});
while (event_ids.size() != 2)
SyncOpts opts;
opts.timeout = 0;
alice->sync(opts,
[room_id, event_ids](const mtx::responses::Sync &res,
RequestErr err) {
check_error(err);
auto ids = get_event_ids<TimelineEvents>(
res.rooms.join.at(room_id).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();
}
TEST(ClientAPI, RedactEvent)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", check_login);
while (alice->access_token().empty())
sleep();
mtx::requests::CreateRoom req;
alice->create_room(req, [alice](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
auto room_id = res.room_id.to_string();
mtx::events::msg::Text text;
text.body = "hello alice!";
alice->send_room_message<mtx::events::msg::Text>(
room_id,
text,
[room_id, alice](const mtx::responses::EventId &res, RequestErr err) {
check_error(err);
alice->redact_event(
room_id,
res.event_id.to_string(),
[](const mtx::responses::EventId &res, RequestErr err) {
check_error(err);
ASSERT_FALSE(res.event_id.to_string().empty());
});
});
});
alice->close();
}
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 &, RequestErr err) {
check_error(err);
});
bob->login("bob", "secret", [bob](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty() || bob->access_token().empty())
mtx::requests::CreateRoom req;
req.invite = {"@bob:localhost"};
alice->create_room(
req, [alice, bob](const mtx::responses::CreateRoom &res, RequestErr 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>(
room_id.to_string(),
event,
[](const mtx::responses::EventId &, RequestErr 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>(
room_id.to_string(),
name_event,
[&event_ids](const mtx::responses::EventId &res, RequestErr err) {
check_error(err);
event_ids.push_back(res.event_id.to_string());
});
mtx::events::state::Avatar avatar;
avatar.url = "mxc://localhost/random";
alice->send_state_event<mtx::events::state::Avatar>(
room_id.to_string(),
avatar,
[&event_ids](const mtx::responses::EventId &res, RequestErr err) {
check_error(err);
event_ids.push_back(res.event_id.to_string());
});
while (event_ids.size() != 2)
SyncOpts opts;
opts.timeout = 0;
opts, [room_id, event_ids](const mtx::responses::Sync &res, RequestErr err) {
auto ids = get_event_ids<TimelineEvents>(
res.rooms.join.at(room_id.to_string()).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();
}
TEST(ClientAPI, Pagination)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty())
mtx::requests::CreateRoom req;
alice->create_room(req, [alice](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
auto room_id = res.room_id;
MessagesOpts opts;
opts.room_id = res.room_id.to_string();
opts, [room_id, alice](const mtx::responses::Messages &res, RequestErr err) {
check_error(err);
ASSERT_TRUE(res.chunk.size() > 5);
ASSERT_NE(res.start, res.end);
MessagesOpts opts;
opts.from = res.end;
opts.room_id = room_id.to_string();
alice->messages(opts,
[](const mtx::responses::Messages &res, RequestErr err) {
check_error(err);
// We reached the start of the timeline.
EXPECT_EQ(res.start, res.end);
EXPECT_EQ(res.chunk.size(), 0);
});
});
alice->close();
}
TEST(ClientAPI, UploadFilter)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty())
nlohmann::json j = {
{"room", {{"include_leave", true}, {"account_data", {{"not_types", {"*"}}}}}},
{"account_data", {{"not_types", {"*"}}}},
{"presence", {{"not_types", {"*"}}}}};
alice->upload_filter(j, [](const mtx::responses::FilterId &res, RequestErr err) {
check_error(err);
ASSERT_TRUE(res.filter_id.size() > 0);
});
alice->close();
}
TEST(ClientAPI, ReadMarkers)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", [alice](const mtx::responses::Login &, RequestErr err) {
check_error(err);
});
while (alice->access_token().empty())
mtx::requests::CreateRoom req;
alice->create_room(req, [alice](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
string event_id;
mtx::events::msg::Text text;
text.body = "hello alice!";
const auto room_id = res.room_id;
alice->send_room_message<mtx::events::msg::Text>(
room_id.to_string(),
text,
[alice, &event_id, room_id](const mtx::responses::EventId &res, RequestErr err) {
check_error(err);
alice->read_event(room_id.to_string(),
res.event_id.to_string(),
[&event_id, res](RequestErr err) {
check_error(err);
event_id = res.event_id.to_string();
});
});
SyncOpts opts;
opts.timeout = 0;
alice->sync(
opts, [room_id, event_id](const mtx::responses::Sync &res, RequestErr err) {
check_error(err);
auto receipts = res.rooms.join.at(room_id.to_string()).ephemeral.receipts;
EXPECT_EQ(receipts.size(), 1);
auto users = receipts[event_id];
EXPECT_EQ(users.size(), 1);
ASSERT_TRUE(users["@alice:localhost"] > 0);
});
});
alice->close();
}
TEST(ClientAPI, SendToDevice)
{
auto alice = std::make_shared<Client>("localhost");
auto bob = std::make_shared<Client>("localhost");
alice->login("alice", "secret", &check_login);
bob->login("bob", "secret", &check_login);
while (alice->access_token().empty() || bob->access_token().empty())
sleep();
json body{{"messages",
{{bob->user_id().to_string(),
{{bob->device_id(),
{
{"action", "request"},
{"body",
{{"sender_key", "test"},
{"algorithm", "test_algo"},
{"room_id", "test_room_id"},
{"session_id", "test_session_id"}}},
{"request_id", "test_request_id"},
{"requesting_device_id", "test_req_id"},
}}}}}}};
alice->send_to_device("m.room_key_request", body, [bob](RequestErr err) {
SyncOpts opts;
opts.timeout = 0;
bob->sync(opts, [](const mtx::responses::Sync &res, RequestErr err) {
auto event = std::get<mtx::events::DeviceEvent<msgs::KeyRequest>>(
res.to_device.events[0]);
EXPECT_EQ(event.content.action, mtx::events::msg::RequestAction::Request);
EXPECT_EQ(event.content.sender_key, "test");
EXPECT_EQ(event.content.algorithm, "test_algo");
EXPECT_EQ(event.content.room_id, "test_room_id");
EXPECT_EQ(event.content.session_id, "test_session_id");
EXPECT_EQ(event.content.request_id, "test_request_id");
EXPECT_EQ(event.content.requesting_device_id, "test_req_id");
EXPECT_EQ(event.type, mtx::events::EventType::RoomKeyRequest);
EXPECT_EQ(event.sender, "@alice:localhost");
});
});
alice->close();
bob->close();
}
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
TEST(ClientAPI, NewSendToDevice)
{
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", &check_login);
bob->login("bob", "secret", &check_login);
carl->login("carl", "secret", &check_login);
while (alice->access_token().empty() || bob->access_token().empty() ||
carl->access_token().empty())
sleep();
ToDeviceMessages<msgs::KeyRequest> body1;
ToDeviceMessages<msgs::KeyRequest> body2;
msgs::KeyRequest request1;
request1.action = mtx::events::msg::RequestAction::Request;
request1.sender_key = "test";
request1.algorithm = "m.megolm.v1.aes-sha2";
request1.room_id = "test_room_id";
request1.session_id = "test_session_id";
request1.request_id = "test_request_id";
request1.requesting_device_id = "test_req_id";
body1[bob->user_id()][bob->device_id()] = request1;
msgs::KeyRequest request2;
request2.action = mtx::events::msg::RequestAction::Cancellation;
request2.request_id = "test_request_id_1";
request2.requesting_device_id = "test_req_id_1";
body2[bob->user_id()][bob->device_id()] = request2;
"m.room.key_request", body1, [bob](RequestErr err) { check_error(err); });
alice->send_to_device("m.room_key_request", body2, [bob](RequestErr err) {
check_error(err);
SyncOpts opts;
opts.timeout = 0;
bob->sync(opts, [](const mtx::responses::Sync &res, RequestErr err) {
check_error(err);
EXPECT_EQ(res.to_device.events.size(), 2);
auto event = std::get<mtx::events::DeviceEvent<msgs::KeyRequest>>(
res.to_device.events[0]);
});
});
alice->close();
bob->close();
carl->close();
}
TEST(ClientAPI, RetrieveSingleEvent)
{
auto bob = std::make_shared<Client>("localhost");
bob->login("bob", "secret", check_login);
while (bob->access_token().empty())
sleep();
mtx::requests::CreateRoom req;
bob->create_room(req, [bob](const mtx::responses::CreateRoom &res, RequestErr err) {
check_error(err);
auto room_id = res.room_id.to_string();
mtx::events::msg::Text text;
text.body = "Hello Alice!";
bob->send_room_message<mtx::events::msg::Text>(
room_id,
text,
[room_id, bob](const mtx::responses::EventId &res, RequestErr err) {
check_error(err);
bob->get_event(
room_id,
res.event_id.to_string(),
[event_id = res.event_id.to_string()](
const mtx::events::collections::TimelineEvents &res, RequestErr err) {
check_error(err);
ASSERT_TRUE(
std::holds_alternative<
mtx::events::RoomEvent<mtx::events::msg::Text>>(res));
std::get<mtx::events::RoomEvent<mtx::events::msg::Text>>(res);
EXPECT_EQ(e.content.body, "Hello Alice!");
EXPECT_EQ(e.sender, "@bob:localhost");
EXPECT_EQ(e.event_id, event_id);
});
bob->get_event(
room_id,
"$random_event:localhost",
[event_id = res.event_id.to_string()](
const mtx::events::collections::TimelineEvents &, RequestErr err) {
ASSERT_TRUE(err);
EXPECT_EQ(static_cast<int>(err->status_code), 404);
});
});
});
bob->close();
}
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
TEST(Groups, Rooms)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", check_login);
WAIT_UNTIL(!alice->access_token().empty())
std::atomic<int> rooms_created(0);
std::vector<std::string> rooms_ids;
auto create_room_cb = [&rooms_created, &rooms_ids](const mtx::responses::CreateRoom &res,
RequestErr err) {
check_error(err);
rooms_created += 1;
rooms_ids.push_back(res.room_id.to_string());
};
mtx::requests::CreateRoom req;
alice->create_room(req, create_room_cb);
alice->create_room(req, create_room_cb);
WAIT_UNTIL(rooms_created == 2)
auto random_group_id = to_string(random_number());
alice->create_group(
random_group_id,
[alice, rooms_ids, random_group_id](const mtx::responses::GroupId &res, RequestErr err) {
check_error(err);
std::atomic<int> rooms_added(0);
alice->add_room_to_group(
rooms_ids.at(0), res.group_id, [&rooms_added](RequestErr err) {
check_error(err);
rooms_added += 1;
});
alice->add_room_to_group(
rooms_ids.at(1), res.group_id, [&rooms_added](RequestErr err) {
check_error(err);
rooms_added += 1;
});
WAIT_UNTIL(rooms_added == 2)
alice->joined_groups(
[random_group_id](const mtx::responses::JoinedGroups &res, RequestErr err) {
check_error(err);
ASSERT_GE(res.groups.size(), 1);
for (const auto &g : res.groups) {
if (g == std::string("+" + random_group_id + ":localhost"))
return;
}
FAIL();
});
alice->group_rooms("+" + random_group_id + ":localhost",
[](const nlohmann::json &res, RequestErr err) {
check_error(err);
EXPECT_GE(res.at("chunk").size(), 2);
});
});
alice->close();
}
TEST(Groups, Profiles)
{
auto alice = std::make_shared<Client>("localhost");
alice->login("alice", "secret", check_login);
WAIT_UNTIL(!alice->access_token().empty())
auto random_group_id = to_string(random_number());
alice->create_group(
random_group_id,
[alice, random_group_id](const mtx::responses::GroupId &res, RequestErr err) {
check_error(err);
EXPECT_GE(res.group_id.size(), random_group_id.size());
json profile;
profile["name"] = "Name";
alice->set_group_profile(
"+" + random_group_id + ":localhost",
profile,
[alice, random_group_id](const nlohmann::json &, RequestErr err) {
check_error(err);
alice->group_profile(
"+" + random_group_id + ":localhost",
[](const mtx::responses::GroupProfile &res, RequestErr err) {
check_error(err);
EXPECT_EQ(res.name, "Name");
EXPECT_EQ(res.avatar_url, "");
});
});
});
alice->close();
}