Page MenuHomePhorge

local-echo-test.cpp
No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None

local-echo-test.cpp

/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <tuple>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_predicate.hpp>
#include <lager/event_loop/boost_asio.hpp>
#include <asio-promise-handler.hpp>
#include <cursorutil.hpp>
#include <sdk-model.hpp>
#include <client/client.hpp>
#include <crypto-util.hpp>
#include <cprjobhandler.hpp>
#include <lagerstoreeventemitter.hpp>
#include <debug.hpp>
#include <sdk.hpp>
#include "client-test-util.hpp"
using namespace Kazv;
using Catch::Matchers::Predicate;
inline auto eventJson = json{
{"content", {
{"foo", "bar"},
}},
{"type", "m.room.message"},
};
template<class Store, class Func>
static auto getMockContext(SingleTypePromiseInterface<EffectStatus> &ph, Store &store, Func func)
{
return typename Client::ContextT([&ph, &store, func](const auto &action) {
kzo.client.dbg() << "dispatched: index " << action.index() << std::endl;
func(action);
if (std::holds_alternative<GetRoomStatesAction>(action)
|| std::holds_alternative<QueryKeysAction>(action)
|| std::holds_alternative<SendToDeviceMessageAction>(action)
|| std::holds_alternative<SendMessageAction>(action)) {
return ph.createResolved(EffectStatus(true, json::object()));
} else if (std::holds_alternative<ClaimKeysAction>(action)) {
return ph.createResolved(EffectStatus(true, json::object({{"keyEvent", json::object({})}})));
} else if (std::holds_alternative<EncryptOlmEventAction>(action)) {
return ph.createResolved(EffectStatus{true, json::object({{"encrypted", json::object({{"type", ""}})}})});
} else if (std::holds_alternative<SaveLocalEchoAction>(action)
|| std::holds_alternative<EncryptMegOlmEventAction>(action)) {
return store.dispatch(action);
} else {
kzo.client.err() << "Unhandled action: index " << action.index();
throw std::runtime_error{"unhandled action"};
}
}, ph, lager::deps<>{});
}
TEST_CASE("Local echo", "[client][room]")
{
RoomModel r;
auto next = RoomModel::update(r, AddLocalEchoAction{{"txnId1", Event(eventJson)}});
REQUIRE(!(next == r));
next = RoomModel::update(next, AddLocalEchoAction{{"txnId2", Event(eventJson)}});
REQUIRE(!(next == r));
REQUIRE(next.localEchoes.size() == 2);
REQUIRE_THAT(next.localEchoes[0], Predicate<LocalEchoDesc>([](const auto &desc) {
return desc.txnId == "txnId1";
}));
REQUIRE_THAT(next.localEchoes[1], Predicate<LocalEchoDesc>([](const auto &desc) {
return desc.txnId == "txnId2";
}));
}
TEST_CASE("Remove local echo", "[client][room]")
{
RoomModel r;
r.localEchoes = immer::flex_vector<LocalEchoDesc>{
{"txnId1", Event(eventJson)},
{"txnId2", Event(eventJson)},
};
auto next = RoomModel::update(r, RemoveLocalEchoAction{"txnId1"});
REQUIRE(next.localEchoes.size() == 1);
REQUIRE_THAT(next.localEchoes[0], Predicate<LocalEchoDesc>([](const auto &desc) {
return desc.txnId == "txnId2";
}));
}
TEST_CASE("getLocalEchoByTxnId()", "[client][room]")
{
RoomModel r;
r.localEchoes = immer::flex_vector<LocalEchoDesc>{
{"txnId1", Event(eventJson)},
{"txnId2", Event(eventJson)},
};
REQUIRE(r.getLocalEchoByTxnId("txnId1").value() == r.localEchoes[0]);
REQUIRE(!r.getLocalEchoByTxnId("txnId3").has_value());
}
TEST_CASE("Sending a message leaves a local echo", "[client][room]")
{
ClientModel m;
const auto roomId = "!foo:tusooa.xyz"s;
m.roomList.rooms = m.roomList.rooms.set(roomId, RoomModel{});
auto [next, dontCareEffect] = ClientModel::update(m, SendMessageAction{roomId, Event(eventJson)});
auto localEchoes = next.roomList.rooms[roomId].localEchoes;
REQUIRE(localEchoes.size() == 1);
assert1Job(next);
for1stJob(next, [localEchoes](const auto &job) {
REQUIRE(job.dataStr("txnId") == localEchoes[0].txnId);
});
}
TEST_CASE("Failed send changes the status of the local echo", "[client][room]")
{
ClientModel m;
const auto roomId = "!foo:tusooa.xyz"s;
m.roomList.rooms = m.roomList.rooms.set(roomId, RoomModel{});
auto [next, dontCareEffect] = ClientModel::update(m, SendMessageAction{roomId, Event(eventJson)});
auto resp = createResponse("SendMessage", json::object({}), json{
{"roomId", roomId},
{"txnId", next.roomList.rooms[roomId].localEchoes[0].txnId},
});
resp.statusCode = 500;
std::tie(next, dontCareEffect) = ClientModel::update(next, ProcessResponseAction{resp});
auto localEchoes = next.roomList.rooms[roomId].localEchoes;
REQUIRE(localEchoes.size() == 1);
REQUIRE(localEchoes[0].status == LocalEchoDesc::Failed);
}
TEST_CASE("Local echo with encrypted event", "[client][room]")
{
boost::asio::io_context io;
SingleTypePromiseInterface<EffectStatus> sgph{AsioPromiseHandler{io.get_executor()}};
ClientModel m;
m.crypto = Crypto(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
RoomModel room;
room.encrypted = true;
room.roomId = "!exampleroomid:example.com";
m.roomList.rooms = m.roomList.rooms.set("!exampleroomid:example.com", room);
auto jh = Kazv::CprJobHandler{io.get_executor()};
auto ee = Kazv::LagerStoreEventEmitter(lager::with_boost_asio_event_loop{io.get_executor()});
auto sdk = Kazv::makeSdk(
SdkModel{m},
jh,
ee,
Kazv::AsioPromiseHandler{io.get_executor()},
zug::identity
);
auto ctx = sdk.context();
auto saveLocalEchoCalled = 0;
auto sendMessageCalled = 0;
auto txnId = std::string();
auto mockContext = getMockContext(sgph, ctx, [&saveLocalEchoCalled, &sendMessageCalled, &txnId](const auto &a) {
if (std::holds_alternative<SaveLocalEchoAction>(a)) {
++saveLocalEchoCalled;
} else if (std::holds_alternative<SendMessageAction>(a)) {
++sendMessageCalled;
REQUIRE(std::get<SendMessageAction>(a).txnId.has_value());
txnId = std::get<SendMessageAction>(a).txnId.value();
}
});
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!exampleroomid:example.com");
REQUIRE(r.encrypted().make().get());
r.sendTextMessage("test")
.then([&io](auto) {
kzo.client.dbg() << "ended" << std::endl;
io.stop();
});
io.run();
REQUIRE(saveLocalEchoCalled == 2);
REQUIRE(sendMessageCalled == 1);
REQUIRE(r.localEchoes().make().get().size() == 1);
REQUIRE(r.localEchoes().make().get()[0].txnId == txnId);
}
TEST_CASE("Encrypted room: Resend encrypted local echo", "[client][room]")
{
boost::asio::io_context io;
SingleTypePromiseInterface<EffectStatus> sgph{AsioPromiseHandler{io.get_executor()}};
ClientModel m;
m.crypto = Crypto(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
RoomModel room;
room.encrypted = true;
room.roomId = "!exampleroomid:example.com";
auto encryptedEvent = Event{json{
{"content", {{"foo", "bar"}}},
{"type", "m.room.encrypted"},
}};
auto decryptedJson = json{
{"content", {{"dec-foo", "dec-bar"}}},
{"type", "m.room.message"},
};
auto event = encryptedEvent.setDecryptedJson(decryptedJson, Event::Decrypted);
room.localEchoes = immer::flex_vector<LocalEchoDesc>{
{"some-txn-id", event, LocalEchoDesc::Failed},
};
m.roomList.rooms = m.roomList.rooms.set("!exampleroomid:example.com", room);
auto jh = Kazv::CprJobHandler{io.get_executor()};
auto ee = Kazv::LagerStoreEventEmitter(lager::with_boost_asio_event_loop{io.get_executor()});
auto sdk = Kazv::makeSdk(
SdkModel{m},
jh,
ee,
Kazv::AsioPromiseHandler{io.get_executor()},
zug::identity
);
auto ctx = sdk.context();
auto saveLocalEchoCalled = 0;
auto sendMessageCalled = 0;
auto megOlmEncryptCalled = 0;
auto mockContext = getMockContext(sgph, ctx, [&saveLocalEchoCalled, &sendMessageCalled, &megOlmEncryptCalled](const auto &a) {
if (std::holds_alternative<SaveLocalEchoAction>(a)) {
++saveLocalEchoCalled;
} else if (std::holds_alternative<EncryptMegOlmEventAction>(a)) {
++megOlmEncryptCalled;
} else if (std::holds_alternative<SendMessageAction>(a)) {
++sendMessageCalled;
REQUIRE(std::get<SendMessageAction>(a).txnId.has_value());
REQUIRE(std::get<SendMessageAction>(a).txnId.value() == "some-txn-id");
}
});
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!exampleroomid:example.com");
REQUIRE(r.encrypted().make().get());
r.resendMessage("some-txn-id")
.then([&io](auto) {
kzo.client.dbg() << "ended" << std::endl;
io.stop();
});
io.run();
REQUIRE(saveLocalEchoCalled == 0);
REQUIRE(megOlmEncryptCalled == 0);
REQUIRE(sendMessageCalled == 1);
REQUIRE(r.localEchoes().make().get().size() == 1);
REQUIRE(r.localEchoes().make().get()[0].txnId == "some-txn-id");
}
TEST_CASE("Unencrypted room: Resend unencrypted local echo", "[client][room]")
{
boost::asio::io_context io;
SingleTypePromiseInterface<EffectStatus> sgph{AsioPromiseHandler{io.get_executor()}};
ClientModel m;
m.crypto = Crypto(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
RoomModel room;
room.encrypted = false;
room.roomId = "!exampleroomid:example.com";
auto event = Event{json{
{"content", {{"dec-foo", "dec-bar"}}},
{"type", "m.room.message"},
}};
room.localEchoes = immer::flex_vector<LocalEchoDesc>{
{"some-txn-id", event, LocalEchoDesc::Failed},
};
m.roomList.rooms = m.roomList.rooms.set("!exampleroomid:example.com", room);
auto jh = Kazv::CprJobHandler{io.get_executor()};
auto ee = Kazv::LagerStoreEventEmitter(lager::with_boost_asio_event_loop{io.get_executor()});
auto sdk = Kazv::makeSdk(
SdkModel{m},
jh,
ee,
Kazv::AsioPromiseHandler{io.get_executor()},
zug::identity
);
auto ctx = sdk.context();
auto saveLocalEchoCalled = 0;
auto sendMessageCalled = 0;
auto megOlmEncryptCalled = 0;
auto mockContext = getMockContext(sgph, ctx, [&saveLocalEchoCalled, &sendMessageCalled, &megOlmEncryptCalled](const auto &a) {
if (std::holds_alternative<SaveLocalEchoAction>(a)) {
++saveLocalEchoCalled;
} else if (std::holds_alternative<EncryptMegOlmEventAction>(a)) {
++megOlmEncryptCalled;
} else if (std::holds_alternative<SendMessageAction>(a)) {
++sendMessageCalled;
REQUIRE(std::get<SendMessageAction>(a).txnId.has_value());
REQUIRE(std::get<SendMessageAction>(a).txnId.value() == "some-txn-id");
}
});
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!exampleroomid:example.com");
REQUIRE(!r.encrypted().make().get());
r.resendMessage("some-txn-id")
.then([&io](auto) {
kzo.client.dbg() << "ended" << std::endl;
io.stop();
});
io.run();
REQUIRE(saveLocalEchoCalled == 0);
REQUIRE(megOlmEncryptCalled == 0);
REQUIRE(sendMessageCalled == 1);
REQUIRE(r.localEchoes().make().get().size() == 1);
REQUIRE(r.localEchoes().make().get()[0].txnId == "some-txn-id");
}

File Metadata

Mime Type
text/x-c++
Expires
Fri, Jul 18, 7:39 AM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
261437
Default Alt Text
local-echo-test.cpp (11 KB)

Event Timeline