Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F113016
D64.1732365755.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
39 KB
Referenced Files
None
Subscribers
None
D64.1732365755.diff
View Options
diff --git a/src/tests/client/account-data-test.cpp b/src/tests/client/account-data-test.cpp
--- a/src/tests/client/account-data-test.cpp
+++ b/src/tests/client/account-data-test.cpp
@@ -19,7 +19,7 @@
#include <client/client.hpp>
#include "client-test-util.hpp"
-
+#include "action-mock-utils.hpp"
#include "factory.hpp"
using namespace Kazv::Factory;
@@ -104,17 +104,12 @@
);
auto ctx = sdk.context();
- auto setAccountDataCalled = false;
- auto mockContext = typename Client::ContextT([&ph, &setAccountDataCalled](const auto &action) {
- if (std::holds_alternative<SetAccountDataPerRoomAction>(action)) {
- setAccountDataCalled = true;
- auto a = std::get<SetAccountDataPerRoomAction>(action);
- REQUIRE(a.roomId == "!room:example.com");
- REQUIRE(a.accountDataEvent == accountDataEvent);
- return ph.createResolved(EffectStatus(true, json::object()));
- }
- throw std::runtime_error{"unhandled action"};
- }, ph, lager::deps<>{});
+ auto dispatcher = getMockDispatcher(
+ ph,
+ ctx,
+ returnEmpty<SetAccountDataPerRoomAction>()
+ );
+ auto mockContext = getMockContext(ph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!room:example.com");
@@ -125,7 +120,10 @@
io.run();
- REQUIRE(setAccountDataCalled);
+ REQUIRE(dispatcher.template calledTimes<SetAccountDataPerRoomAction>() == 1);
+ auto a = dispatcher.template of<SetAccountDataPerRoomAction>()[0];
+ REQUIRE(a.roomId == "!room:example.com");
+ REQUIRE(a.accountDataEvent == accountDataEvent);
}
Event tagEvent = R"({
@@ -161,18 +159,13 @@
);
auto ctx = sdk.context();
- auto setAccountDataCalled = false;
auto expectedEvent = Event();
- auto mockContext = typename Client::ContextT([&ph, &expectedEvent, &setAccountDataCalled](const auto &action) {
- if (std::holds_alternative<SetAccountDataPerRoomAction>(action)) {
- setAccountDataCalled = true;
- auto a = std::get<SetAccountDataPerRoomAction>(action);
- REQUIRE(a.roomId == "!room:example.com");
- REQUIRE(a.accountDataEvent == expectedEvent);
- return ph.createResolved(EffectStatus(true, json::object()));
- }
- throw std::runtime_error{"unhandled action"};
- }, ph, lager::deps<>{});
+ auto dispatcher = getMockDispatcher(
+ ph,
+ ctx,
+ returnEmpty<SetAccountDataPerRoomAction>()
+ );
+ auto mockContext = getMockContext(ph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!room:example.com");
@@ -227,7 +220,10 @@
io.run();
- REQUIRE(setAccountDataCalled);
+ REQUIRE(dispatcher.template calledTimes<SetAccountDataPerRoomAction>() == 1);
+ auto a = dispatcher.template of<SetAccountDataPerRoomAction>()[0];
+ REQUIRE(a.roomId == "!room:example.com");
+ REQUIRE(a.accountDataEvent == expectedEvent);
}
TEST_CASE("Room::removeTag()", "[client][account-data][tagging]")
@@ -253,19 +249,15 @@
);
auto ctx = sdk.context();
- auto setAccountDataCalled = false;
auto expectedEvent = Event();
- auto mockContext = typename Client::ContextT([&ph, &expectedEvent, &setAccountDataCalled](const auto &action) {
- if (std::holds_alternative<SetAccountDataPerRoomAction>(action)) {
- setAccountDataCalled = true;
- auto a = std::get<SetAccountDataPerRoomAction>(action);
- REQUIRE(a.roomId == "!room:example.com");
- REQUIRE(a.accountDataEvent == expectedEvent);
- return ph.createResolved(EffectStatus(true, json::object()));
- }
- throw std::runtime_error{"unhandled action"};
- }, ph, lager::deps<>{});
+ auto dispatcher = getMockDispatcher(
+ ph,
+ ctx,
+ returnEmpty<SetAccountDataPerRoomAction>()
+ );
+
+ auto mockContext = getMockContext(ph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!room:example.com");
@@ -293,7 +285,10 @@
io.run();
- REQUIRE(setAccountDataCalled);
+ REQUIRE(dispatcher.template calledTimes<SetAccountDataPerRoomAction>() == 1);
+ auto a = dispatcher.template of<SetAccountDataPerRoomAction>()[0];
+ REQUIRE(a.roomId == "!room:example.com");
+ REQUIRE(a.accountDataEvent == expectedEvent);
}
TEST_CASE("Send set account data job", "[client][account-data]")
@@ -380,16 +375,12 @@
);
auto ctx = sdk.context();
- auto setAccountDataCalled = false;
- auto mockContext = typename Client::ContextT([&ph, &setAccountDataCalled](const auto &action) {
- if (std::holds_alternative<SetAccountDataAction>(action)) {
- setAccountDataCalled = true;
- auto a = std::get<SetAccountDataAction>(action);
- REQUIRE(a.accountDataEvent == accountDataEvent);
- return ph.createResolved(EffectStatus(true, json::object()));
- }
- throw std::runtime_error{"unhandled action"};
- }, ph, lager::deps<>{});
+ auto dispatcher = getMockDispatcher(
+ ph,
+ ctx,
+ returnEmpty<SetAccountDataAction>()
+ );
+ auto mockContext = getMockContext(ph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
client.setAccountData(accountDataEvent)
@@ -399,5 +390,7 @@
io.run();
- REQUIRE(setAccountDataCalled);
+ REQUIRE(dispatcher.template calledTimes<SetAccountDataAction>() == 1);
+ auto a = dispatcher.template of<SetAccountDataAction>()[0];
+ REQUIRE(a.accountDataEvent == accountDataEvent);
}
diff --git a/src/tests/client/action-mock-utils.hpp b/src/tests/client/action-mock-utils.hpp
new file mode 100644
--- /dev/null
+++ b/src/tests/client/action-mock-utils.hpp
@@ -0,0 +1,242 @@
+/*
+ * This file is part of libkazv.
+ * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libkazv-config.hpp>
+
+#include <vector>
+#include <boost/hana.hpp>
+#include <boost/core/demangle.hpp>
+#include <zug/into_vector.hpp>
+#include <zug/transducer/map.hpp>
+#include <zug/transducer/filter.hpp>
+#include <store/store.hpp>
+#include <client/client.hpp>
+
+template<class Action>
+struct PassDownTag
+{};
+
+template<class Action>
+constexpr PassDownTag<Action> passDown()
+{
+ return {};
+};
+
+template<class Action, class DataT = Kazv::EffectStatus>
+struct ReturnResolvedTag
+{
+ DataT data;
+};
+
+template<class Action, class DataT = Kazv::EffectStatus>
+constexpr ReturnResolvedTag<Action, DataT> returnResolved(DataT data)
+{
+ return {data};
+}
+
+template<class Action, class DataT = Kazv::EffectStatus>
+constexpr ReturnResolvedTag<Action, DataT> returnEmpty()
+{
+ return {{}};
+}
+
+template<class SubAction, class FuncT>
+struct HandlerTag : public FuncT
+{
+ HandlerTag(FuncT f) : FuncT(std::move(f)) {}
+};
+
+template<class SubAction, class FuncT>
+constexpr auto makeHandler(FuncT f)
+{
+ return HandlerTag<SubAction, FuncT>(std::move(f));
+};
+
+template<class R, class F, class = int, class ...Args>
+struct IsExactInvocableHelper : public std::false_type
+{};
+
+template<class R, class F, class ...Args>
+struct IsExactInvocableHelper<
+ R,
+ F,
+ std::enable_if_t<std::is_same_v<std::invoke_result_t<F, Args...>, R>, int>,
+ Args...> : public std::true_type
+{};
+
+template<class R, class F, class ...Args>
+constexpr auto isExactInvocable = IsExactInvocableHelper<R, F, int, Args...>::value;
+
+static_assert(isExactInvocable<int, std::function<int(long long)>, long long>);
+
+template<class T>
+std::string getTypeName(const T &a)
+{
+ return boost::core::demangle(typeid(a).name());
+}
+
+template<class Variant>
+std::string getVariantTypeName(const Variant &v)
+{
+ return std::visit([](const auto &a) {
+ return getTypeName(a);
+ }, v);
+}
+
+template<class Promise>
+struct HandlerResult
+{
+ std::optional<Promise> retVal;
+};
+
+template<class PH, class Context, class Action = Kazv::Client::ActionT>
+struct MockDispatcher
+{
+ using ContextT = Context;
+ using ActionT = Action;
+ using PromiseT = typename ContextT::PromiseT;
+ using DataT = typename PromiseT::DataT;
+ using HandlerResultT = HandlerResult<PromiseT>;
+
+ struct Handler : public std::function<HandlerResultT(PH &, ContextT &, ActionT)>
+ {
+ using BaseT = std::function<HandlerResultT(PH &, ContextT &, ActionT)>;
+ using BaseT::BaseT;
+ using BaseT::operator();
+
+ template<class SubAction, class FuncT>
+ static BaseT make(FuncT func)
+ {
+ using Func = std::decay_t<FuncT>;
+ if constexpr (isExactInvocable<HandlerResultT, Func, PH &, ContextT &, SubAction>) {
+ return [func](PH &ph, ContextT &ctx, ActionT action) mutable -> HandlerResultT {
+ static_assert(boost::hana::is_valid([]() {
+ return boost::hana::type_c<
+ decltype(std::get<SubAction>(action))>;
+ })(),
+ "SubAction must be a variant alternative of ActionT");
+ if (std::holds_alternative<SubAction>(action)) {
+ return func(ph, ctx, std::get<SubAction>(action));
+ }
+ return {std::nullopt};
+ };
+ } else if constexpr (isExactInvocable<PromiseT, Func, PH &, ContextT &, SubAction>) {
+ return make<SubAction>([func](PH &ph, ContextT &ctx, SubAction subAction) mutable {
+ return HandlerResultT{func(ph, ctx, subAction)};
+ });
+ } else if constexpr (isExactInvocable<HandlerResultT, Func, SubAction>) {
+ return make<SubAction>([func](PH &, ContextT &, SubAction subAction) mutable {
+ return func(subAction);
+ });
+ } else if constexpr (isExactInvocable<PromiseT, Func, SubAction>) {
+ return make<SubAction>([func](PH &, ContextT &, SubAction subAction) mutable {
+ return func(subAction);
+ });
+ } else if constexpr (isExactInvocable<DataT, Func, SubAction>) {
+ return make<SubAction>([func](PH &ph, ContextT &, SubAction subAction) mutable {
+ return ph.createResolved(func(subAction));
+ });
+ } else if constexpr (isExactInvocable<void, Func, SubAction>) {
+ return make<SubAction>([func](PH &ph, ContextT &, SubAction subAction) mutable {
+ func(subAction);
+ return ph.createResolved({});
+ });
+ } else {
+ // This is a trick to avoid compilers reporting failure
+ // even when this branch is never executed for any Func
+ // https://stackoverflow.com/questions/38304847/how-does-a-failed-static-assert-work-in-an-if-constexpr-false-block
+ static_assert(!sizeof(Func), "Function is not convertible to an action handler");
+ return [func](PH &, ContextT &, ActionT) mutable -> HandlerResultT {
+ return {std::nullopt};
+ };
+ }
+ }
+
+ template<class SubAction>
+ Handler(PassDownTag<SubAction>)
+ : BaseT(make<SubAction>([](PH &, ContextT &ctx, SubAction action) mutable {
+ Kazv::kzo.client.dbg() << "PassDown" << std::endl;
+ return ctx.dispatch(action);
+ }))
+ {}
+
+ template<class SubAction>
+ Handler(ReturnResolvedTag<SubAction, DataT> tag)
+ : BaseT(make<SubAction>([data=tag.data](SubAction) mutable {
+ Kazv::kzo.client.dbg() << "ReturnResolved" << std::endl;
+ return data;
+ }))
+ {}
+
+ template<class SubAction, class FuncT>
+ Handler(HandlerTag<SubAction, FuncT> func)
+ : BaseT(make<SubAction>(func))
+ {}
+ };
+
+ PromiseT operator()(ActionT action)
+ {
+ actions->push_back(action);
+ std::string typeName = getVariantTypeName(action);
+ Kazv::kzo.client.dbg() << "Handling action " << typeName << std::endl;
+ for (auto &handler : handlers) {
+ auto res = handler(ph, ctx, action);
+ if (res.retVal.has_value()) {
+ return res.retVal.value();
+ }
+ }
+ throw std::runtime_error{"unhandled action: " + typeName};
+ }
+
+ template<class SubAction>
+ auto calledTimes()
+ {
+ return std::accumulate(actions->begin(), actions->end(),
+ 0,
+ [](auto acc, auto cur) {
+ return acc + (std::holds_alternative<SubAction>(cur) ? 1 : 0);
+ }
+ );
+ }
+
+ template<class SubAction>
+ std::vector<SubAction> of()
+ {
+ return zug::into_vector(
+ zug::filter([](const auto &a) { return std::holds_alternative<SubAction>(a); })
+ | zug::map([](const auto &a) { return std::get<SubAction>(a); }),
+ *actions
+ );
+ }
+
+ auto clear() { actions->clear(); }
+
+ PH &ph;
+ ContextT &ctx;
+ std::vector<Handler> handlers;
+ std::shared_ptr<std::vector<ActionT>> actions{std::make_shared<std::vector<ActionT>>()};
+};
+
+template<class PH, class ContextT, class ...Handlers>
+auto getMockDispatcher(PH &ph, ContextT &ctx, Handlers ...handlers)
+{
+ using ResType = MockDispatcher<PH, ContextT>;
+ return ResType{
+ ph,
+ ctx,
+ std::vector<typename ResType::Handler>{ {handlers...} }
+ };
+}
+
+template<
+ class ContextT = typename Kazv::Client::ContextT,
+ class ActionT = typename Kazv::Client::ActionT,
+ class PH,
+ class Func>
+auto getMockContext(PH &ph, Func &&func)
+{
+ return ContextT(std::forward<Func>(func), ph, lager::deps<>{});
+};
diff --git a/src/tests/client/create-room-test.cpp b/src/tests/client/create-room-test.cpp
--- a/src/tests/client/create-room-test.cpp
+++ b/src/tests/client/create-room-test.cpp
@@ -18,23 +18,12 @@
#include <client/client.hpp>
#include "client-test-util.hpp"
+#include "action-mock-utils.hpp"
#include "factory.hpp"
using namespace Kazv;
using namespace Kazv::Factory;
-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) {
- auto [cont, res] = func(action);
- if (!cont) {
- return ph.createResolved(res);
- }
- throw std::runtime_error{"unhandled action"};
- }, ph, lager::deps<>{});
-}
-
TEST_CASE("Client::createRoom()", "[client][create-room]")
{
ClientModel m = makeClient();
@@ -53,17 +42,14 @@
);
auto ctx = sdk.context();
- auto createRoomCalled = 0;
- std::optional<CreateRoomAction> action = std::nullopt;
- auto mockContext = getMockContext(ph, ctx, [&createRoomCalled, &action](const auto &a) {
- if (std::holds_alternative<CreateRoomAction>(a)) {
- ++createRoomCalled;
- action = std::get<CreateRoomAction>(a);
- return std::make_pair(/* cont = */ false, EffectStatus());
- }
- return std::make_pair(/* cont = */ true, EffectStatus());
- });
+ auto dispatcher = getMockDispatcher(
+ ph,
+ ctx,
+ returnEmpty<CreateRoomAction>()
+ );
+
+ auto mockContext = getMockContext(ph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
@@ -83,15 +69,16 @@
io.run();
- REQUIRE(createRoomCalled == 1);
- REQUIRE(action->visibility == RoomVisibility::Private);
- REQUIRE(action->name == "some name");
- REQUIRE(action->roomAliasName == "alias");
- REQUIRE(action->invite == immer::array<std::string>{"@invited:example.com"});
- REQUIRE(action->isDirect == true);
- REQUIRE(action->creationContent.get() == json::object({
+ REQUIRE(dispatcher.template calledTimes<CreateRoomAction>() == 1);
+ auto action = dispatcher.template of<CreateRoomAction>()[0];
+ REQUIRE(action.visibility == RoomVisibility::Private);
+ REQUIRE(action.name == "some name");
+ REQUIRE(action.roomAliasName == "alias");
+ REQUIRE(action.invite == immer::array<std::string>{"@invited:example.com"});
+ REQUIRE(action.isDirect == true);
+ REQUIRE(action.creationContent.get() == json::object({
{"m.federate", true},
}));
- REQUIRE(action->topic == "some topic");
- REQUIRE(action->preset == CreateRoomPreset::TrustedPrivateChat);
+ REQUIRE(action.topic == "some topic");
+ REQUIRE(action.preset == CreateRoomPreset::TrustedPrivateChat);
}
diff --git a/src/tests/client/redact-test.cpp b/src/tests/client/redact-test.cpp
--- a/src/tests/client/redact-test.cpp
+++ b/src/tests/client/redact-test.cpp
@@ -15,26 +15,12 @@
#include <sdk.hpp>
#include <cprjobhandler.hpp>
#include <lagerstoreeventemitter.hpp>
-
+#include "action-mock-utils.hpp"
#include "client-test-util.hpp"
#include "factory.hpp"
using namespace Kazv::Factory;
-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) {
- auto [cont, res] = func(action);
- if (!cont) {
- return ph.createResolved(res);
- }
-
- kzo.client.err() << "Unhandled action: index " << action.index();
- throw std::runtime_error{"unhandled action"};
- }, ph, lager::deps<>{});
-}
-
TEST_CASE("Redact a message", "[client][redact]")
{
ClientModel loggedInModel = makeClient({});
@@ -85,16 +71,14 @@
);
auto ctx = sdk.context();
- auto redactCalled = 0;
- auto redactAction = RedactEventAction{};
- auto mockContext = getMockContext(sgph, ctx, [&redactCalled, &redactAction](const auto &a) {
- if (std::holds_alternative<RedactEventAction>(a)) {
- ++redactCalled;
- redactAction = std::get<RedactEventAction>(a);
- return std::make_pair(/* cont = */ false, EffectStatus());
- }
- return std::make_pair(/* cont = */ true, EffectStatus());
- });
+
+ auto dispatcher = getMockDispatcher(
+ sgph,
+ ctx,
+ returnEmpty<RedactEventAction>()
+ );
+
+ auto mockContext = getMockContext(sgph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!exampleroomid:example.com");
@@ -107,7 +91,10 @@
io.run();
- REQUIRE(redactCalled == 1);
+ REQUIRE(dispatcher.template calledTimes<RedactEventAction>() == 1);
+
+ auto redactAction = dispatcher.template of<RedactEventAction>()[0];
+
REQUIRE(redactAction.roomId == "!exampleroomid:example.com");
REQUIRE(redactAction.eventId == "$some-event");
REQUIRE(redactAction.reason == std::optional<std::string>("some reason"));
diff --git a/src/tests/client/room/local-echo-test.cpp b/src/tests/client/room/local-echo-test.cpp
--- a/src/tests/client/room/local-echo-test.cpp
+++ b/src/tests/client/room/local-echo-test.cpp
@@ -24,6 +24,7 @@
#include <sdk.hpp>
#include "client-test-util.hpp"
+#include "action-mock-utils.hpp"
#include "factory.hpp"
using namespace Kazv;
@@ -37,28 +38,22 @@
{"type", "m.room.message"},
};
-template<class Store, class Func>
-static auto getMockContext(SingleTypePromiseInterface<EffectStatus> &ph, Store &store, Func func)
+template<class Store, class ...Handlers>
+static auto makeDispatcher(SingleTypePromiseInterface<EffectStatus> &ph, Store &store, Handlers ...handlers)
{
- return typename Client::ContextT([&ph, &store, func](const auto &action) {
- static unsigned long long nextTxnId;
- kzo.client.dbg() << "dispatched: index " << action.index() << std::endl;
- auto [cont, res] = func(action);
- if (!cont) {
- return ph.createResolved(res);
- }
- if (std::holds_alternative<GetRoomStatesAction>(action)
- || std::holds_alternative<QueryKeysAction>(action)
- || std::holds_alternative<SendMessageAction>(action)) {
- return ph.createResolved(EffectStatus(true, json::object()));
- } else if (std::holds_alternative<SendMultipleToDeviceMessagesAction>(action)) {
- auto a = std::get<SendMultipleToDeviceMessagesAction>(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<PrepareForSharingRoomKeyAction>(action)) {
- auto a = std::get<PrepareForSharingRoomKeyAction>(action);
- auto txnId = std::to_string(nextTxnId++);
+ auto nextTxnId = std::make_shared<int>(0);
+ return getMockDispatcher(
+ ph,
+ store,
+ handlers...,
+ returnEmpty<GetRoomStatesAction>(),
+ returnEmpty<QueryKeysAction>(),
+ returnEmpty<SendMessageAction>(),
+ returnEmpty<SendMultipleToDeviceMessagesAction>(),
+ returnResolved<ClaimKeysAction>(EffectStatus(true,
+ json::object({{"keyEvent", json::object({})}}))),
+ makeHandler<PrepareForSharingRoomKeyAction>([nextTxnId]([[maybe_unused]] auto &ph, auto &store, const PrepareForSharingRoomKeyAction &a) {
+ auto txnId = std::to_string((*nextTxnId)++);
return store.dispatch(UpdateRoomAction{
a.roomId,
AddPendingRoomKeyAction{
@@ -72,16 +67,12 @@
}).then([txnId](auto &&) {
return EffectStatus{true, json::object({{"txnId", txnId}})};
});
- } else if (std::holds_alternative<SaveLocalEchoAction>(action)
- || std::holds_alternative<EncryptMegOlmEventAction>(action)
- || std::holds_alternative<RoomListAction>(action)
- || std::holds_alternative<UpdateLocalEchoStatusAction>(action)) {
- return store.dispatch(action);
- } else {
- kzo.client.err() << "Unhandled action: index " << action.index();
- throw std::runtime_error{"unhandled action"};
- }
- }, ph, lager::deps<>{});
+ }),
+ passDown<SaveLocalEchoAction>(),
+ passDown<EncryptMegOlmEventAction>(),
+ passDown<RoomListAction>(),
+ passDown<UpdateLocalEchoStatusAction>()
+ );
}
TEST_CASE("Local echo", "[client][room]")
@@ -188,21 +179,8 @@
);
auto ctx = sdk.context();
- auto saveLocalEchoCalled = 0;
- auto sendMessageCalled = 0;
- auto txnId = std::string();
- auto sendMessageEvent = Event();
- auto mockContext = getMockContext(sgph, ctx, [&saveLocalEchoCalled, &sendMessageCalled, &txnId, &sendMessageEvent](const auto &a) {
- if (std::holds_alternative<SaveLocalEchoAction>(a)) {
- ++saveLocalEchoCalled;
- } else if (std::holds_alternative<SendMessageAction>(a)) {
- ++sendMessageCalled;
- sendMessageEvent = std::get<SendMessageAction>(a).event;
- REQUIRE(std::get<SendMessageAction>(a).txnId.has_value());
- txnId = std::get<SendMessageAction>(a).txnId.value();
- }
- return std::make_pair(/* cont = */ true, EffectStatus());
- });
+ auto dispatcher = makeDispatcher(sgph, ctx);
+ auto mockContext = getMockContext(sgph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
@@ -218,12 +196,13 @@
io.run();
- REQUIRE(saveLocalEchoCalled == 2);
- REQUIRE(sendMessageCalled == 1);
- REQUIRE(sendMessageEvent.encrypted());
- REQUIRE(sendMessageEvent.decrypted());
+ REQUIRE(dispatcher.template calledTimes<SaveLocalEchoAction>() == 2);
+ REQUIRE(dispatcher.template calledTimes<SendMessageAction>() == 1);
+ auto sendMessageAction = dispatcher.template of<SendMessageAction>()[0];
+ REQUIRE(sendMessageAction.event.encrypted());
+ REQUIRE(sendMessageAction.event.decrypted());
REQUIRE(r.localEchoes().make().get().size() == 1);
- REQUIRE(r.localEchoes().make().get()[0].txnId == txnId);
+ REQUIRE(r.localEchoes().make().get()[0].txnId == sendMessageAction.txnId);
}
TEST_CASE("Local echo with encrypted event, loading room member failed", "[client][room]")
@@ -249,23 +228,10 @@
);
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<GetRoomStatesAction>(a)) {
- auto err = json::object({{"errorCode", "400"}, {"error", "Cannot get room states"}});
- return std::make_pair(/* cont = */ false, EffectStatus(false, err));
- } else if (std::holds_alternative<SaveLocalEchoAction>(a)) {
- ++saveLocalEchoCalled;
- } else if (std::holds_alternative<EncryptMegOlmEventAction>(a)) {
- ++megOlmEncryptCalled;
- } else if (std::holds_alternative<SendMessageAction>(a)) {
- ++sendMessageCalled;
- }
- return std::make_pair(/* cont = */ true, EffectStatus());
- });
-
+ auto dispatcher = makeDispatcher(sgph, ctx,
+ returnResolved<GetRoomStatesAction>(EffectStatus(false, json::object({{"errorCode", "400"}, {"error", "Cannot get room states"}})))
+ );
+ auto mockContext = getMockContext(sgph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!exampleroomid:example.com");
@@ -281,9 +247,9 @@
io.run();
- REQUIRE(saveLocalEchoCalled == 1);
- REQUIRE(sendMessageCalled == 0);
- REQUIRE(megOlmEncryptCalled == 0);
+ REQUIRE(dispatcher.template calledTimes<SaveLocalEchoAction>() == 1);
+ REQUIRE(dispatcher.template calledTimes<SendMessageAction>() == 0);
+ REQUIRE(dispatcher.template calledTimes<EncryptMegOlmEventAction>() == 0);
REQUIRE(r.localEchoes().make().get().size() == 1);
REQUIRE(r.localEchoes().make().get()[0].status == LocalEchoDesc::Failed);
}
@@ -311,22 +277,10 @@
);
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<QueryKeysAction>(a)) {
- auto err = json::object({{"errorCode", "400"}, {"error", "Cannot get room states"}});
- return std::make_pair(/* cont = */ false, EffectStatus(false, err));
- } else if (std::holds_alternative<SaveLocalEchoAction>(a)) {
- ++saveLocalEchoCalled;
- } else if (std::holds_alternative<EncryptMegOlmEventAction>(a)) {
- ++megOlmEncryptCalled;
- } else if (std::holds_alternative<SendMessageAction>(a)) {
- ++sendMessageCalled;
- }
- return std::make_pair(/* cont = */ true, EffectStatus());
- });
+ auto dispatcher = makeDispatcher(sgph, ctx,
+ returnResolved<QueryKeysAction>(EffectStatus(false, json::object({{"errorCode", "400"}, {"error", "Cannot get room states"}})))
+ );
+ auto mockContext = getMockContext(sgph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
@@ -343,9 +297,9 @@
io.run();
- REQUIRE(saveLocalEchoCalled == 1);
- REQUIRE(sendMessageCalled == 0);
- REQUIRE(megOlmEncryptCalled == 0);
+ REQUIRE(dispatcher.template calledTimes<SaveLocalEchoAction>() == 1);
+ REQUIRE(dispatcher.template calledTimes<SendMessageAction>() == 0);
+ REQUIRE(dispatcher.template calledTimes<EncryptMegOlmEventAction>() == 0);
REQUIRE(r.localEchoes().make().get().size() == 1);
REQUIRE(r.localEchoes().make().get()[0].status == LocalEchoDesc::Failed);
}
@@ -385,21 +339,9 @@
);
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");
- }
- return std::make_pair(/* cont = */ true, EffectStatus());
- });
+
+ auto dispatcher = makeDispatcher(sgph, ctx);
+ auto mockContext = getMockContext(sgph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
@@ -415,11 +357,14 @@
io.run();
- REQUIRE(saveLocalEchoCalled == 0);
- REQUIRE(megOlmEncryptCalled == 0);
- REQUIRE(sendMessageCalled == 1);
+ REQUIRE(dispatcher.template calledTimes<SaveLocalEchoAction>() == 0);
+ REQUIRE(dispatcher.template calledTimes<SendMessageAction>() == 1);
+ REQUIRE(dispatcher.template calledTimes<EncryptMegOlmEventAction>() == 0);
REQUIRE(r.localEchoes().make().get().size() == 1);
REQUIRE(r.localEchoes().make().get()[0].txnId == "some-txn-id");
+ auto a = dispatcher.template of<SendMessageAction>()[0];
+ REQUIRE(a.txnId.has_value());
+ REQUIRE(a.txnId.value() == "some-txn-id");
}
TEST_CASE("Encrypted room: Resend encrypted local echo AND failed key share event", "[client][room]")
@@ -445,35 +390,23 @@
);
auto ctx = sdk.context();
- auto saveLocalEchoCalled = 0;
- auto sendMessageCalled = 0;
- auto megOlmEncryptCalled = 0;
- auto sendToDeviceCalled = 0;
- auto olmEncryptCalled = 0;
auto resending = false;
-
- auto mockContext = getMockContext(sgph, ctx, [&saveLocalEchoCalled,
- &sendMessageCalled, &megOlmEncryptCalled, &sendToDeviceCalled, &olmEncryptCalled, &resending](const auto &a) {
- if (std::holds_alternative<SendMultipleToDeviceMessagesAction>(a)) {
- auto sendKeyData = json{
- {"error", "Bad request"},
- {"errorCode", "400"},
- };
- ++sendToDeviceCalled;
+ auto dispatcher = makeDispatcher(sgph, ctx,
+ makeHandler<SendMultipleToDeviceMessagesAction>([&resending](auto &&ph, [[maybe_unused]] auto &&store, SendMultipleToDeviceMessagesAction) {
if (!resending) {
- return std::make_pair(/* cont = */ false, EffectStatus(/* succ = */ false, sendKeyData));
+ auto sendKeyData = json{
+ {"error", "Bad request"},
+ {"errorCode", "400"},
+ };
+ return HandlerResult<typename Client::PromiseT>{
+ ph.createResolved(EffectStatus(/* succ = */ false, sendKeyData))
+ };
}
- } else if (std::holds_alternative<SaveLocalEchoAction>(a)) {
- ++saveLocalEchoCalled;
- } else if (std::holds_alternative<EncryptMegOlmEventAction>(a)) {
- ++megOlmEncryptCalled;
- } else if (std::holds_alternative<SendMessageAction>(a)) {
- ++sendMessageCalled;
- } else if (std::holds_alternative<PrepareForSharingRoomKeyAction>(a)) {
- ++olmEncryptCalled;
- }
- return std::make_pair(/* cont = */ true, EffectStatus());
- });
+ return HandlerResult<typename Client::PromiseT>{std::nullopt};
+ })
+ );
+
+ auto mockContext = getMockContext(sgph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
@@ -489,21 +422,18 @@
io.run();
- REQUIRE(saveLocalEchoCalled == 2);
- REQUIRE(megOlmEncryptCalled == 1);
- REQUIRE(sendToDeviceCalled == 1);
- REQUIRE(olmEncryptCalled == 1);
- REQUIRE(sendMessageCalled == 0);
+ REQUIRE(dispatcher.template calledTimes<SaveLocalEchoAction>() == 2);
+ REQUIRE(dispatcher.template calledTimes<EncryptMegOlmEventAction>() == 1);
+ REQUIRE(dispatcher.template calledTimes<SendMultipleToDeviceMessagesAction>() == 1);
+ REQUIRE(dispatcher.template calledTimes<PrepareForSharingRoomKeyAction>() == 1);
+ REQUIRE(dispatcher.template calledTimes<SendMessageAction>() == 0);
+
REQUIRE(r.localEchoes().make().get().size() == 1);
auto savedEvent = r.localEchoes().make().get()[0];
auto txnId = savedEvent.txnId;
REQUIRE(savedEvent.event.encrypted());
- saveLocalEchoCalled = 0;
- megOlmEncryptCalled = 0;
- sendMessageCalled = 0;
- sendToDeviceCalled = 0;
- olmEncryptCalled = 0;
+ dispatcher.clear();
resending = true;
r.resendMessage(txnId)
@@ -515,11 +445,11 @@
io.restart();
io.run();
- REQUIRE(saveLocalEchoCalled == 0);
- REQUIRE(megOlmEncryptCalled == 0);
- REQUIRE(sendMessageCalled == 1);
- REQUIRE(olmEncryptCalled == 0);
- REQUIRE(sendToDeviceCalled == 1);
+ REQUIRE(dispatcher.template calledTimes<SaveLocalEchoAction>() == 0);
+ REQUIRE(dispatcher.template calledTimes<EncryptMegOlmEventAction>() == 0);
+ REQUIRE(dispatcher.template calledTimes<SendMessageAction>() == 1);
+ REQUIRE(dispatcher.template calledTimes<PrepareForSharingRoomKeyAction>() == 0);
+ REQUIRE(dispatcher.template calledTimes<SendMultipleToDeviceMessagesAction>() == 1);
REQUIRE(r.pendingRoomKeyEvents().make().get().size() == 0);
}
@@ -554,26 +484,10 @@
);
auto ctx = sdk.context();
- auto saveLocalEchoCalled = 0;
- auto sendMessageCalled = 0;
- auto megOlmEncryptCalled = 0;
- auto roomListActionCalled = 0;
- auto txnId = std::string();
- auto mockContext = getMockContext(sgph, ctx, [&saveLocalEchoCalled, &sendMessageCalled, &megOlmEncryptCalled, &roomListActionCalled, &txnId](const auto &a) {
- if (std::holds_alternative<SaveLocalEchoAction>(a)) {
- ++saveLocalEchoCalled;
- } else if (std::holds_alternative<EncryptMegOlmEventAction>(a)) {
- ++megOlmEncryptCalled;
- } else if (std::holds_alternative<RoomListAction>(a)) {
- ++roomListActionCalled;
- } else if (std::holds_alternative<SendMessageAction>(a)) {
- ++sendMessageCalled;
- REQUIRE(std::get<SendMessageAction>(a).txnId.has_value());
- txnId = std::get<SendMessageAction>(a).txnId.value();
- }
- return std::make_pair(/* cont = */ true, EffectStatus());
- });
+ auto dispatcher = makeDispatcher(sgph, ctx);
+
+ auto mockContext = getMockContext(sgph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!exampleroomid:example.com");
@@ -589,13 +503,16 @@
io.run();
- REQUIRE(saveLocalEchoCalled == 2);
- REQUIRE(roomListActionCalled == 2); // once for removing the existing local echo, once for removing pending key event
- REQUIRE(megOlmEncryptCalled == 1);
- REQUIRE(sendMessageCalled == 1);
+
+ REQUIRE(dispatcher.template calledTimes<SaveLocalEchoAction>() == 2);
+ REQUIRE(dispatcher.template calledTimes<RoomListAction>() == 2); // once for removing the existing local echo, once for removing pending key event
+ REQUIRE(dispatcher.template calledTimes<EncryptMegOlmEventAction>() == 1);
+ REQUIRE(dispatcher.template calledTimes<SendMessageAction>() == 1);
+ auto sendMessageAction = dispatcher.template of<SendMessageAction>()[0];
+ REQUIRE(sendMessageAction.txnId.has_value());
REQUIRE(r.localEchoes().make().get().size() == 2);
REQUIRE(r.localEchoes().make().get()[0].txnId == "some-other-txn-id");
- REQUIRE(r.localEchoes().make().get()[1].txnId == txnId);
+ REQUIRE(r.localEchoes().make().get()[1].txnId == sendMessageAction.txnId.value());
}
TEST_CASE("Unencrypted room: Resend unencrypted local echo", "[client][room]")
@@ -628,22 +545,8 @@
);
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");
- }
- return std::make_pair(/* cont = */ true, EffectStatus());
- });
-
+ auto dispatcher = makeDispatcher(sgph, ctx);
+ auto mockContext = getMockContext(sgph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto r = client.room("!exampleroomid:example.com");
@@ -658,10 +561,12 @@
io.run();
- REQUIRE(saveLocalEchoCalled == 0);
- REQUIRE(megOlmEncryptCalled == 0);
- REQUIRE(sendMessageCalled == 1);
+ REQUIRE(dispatcher.template calledTimes<SaveLocalEchoAction>() == 0);
+ REQUIRE(dispatcher.template calledTimes<EncryptMegOlmEventAction>() == 0);
+ REQUIRE(dispatcher.template calledTimes<SendMessageAction>() == 1);
REQUIRE(r.localEchoes().make().get().size() == 1);
+ auto a = dispatcher.template of<SendMessageAction>()[0];
+ REQUIRE(a.txnId.value() == "some-txn-id");
REQUIRE(r.localEchoes().make().get()[0].txnId == "some-txn-id");
}
diff --git a/src/tests/client/room/read-receipt-test.cpp b/src/tests/client/room/read-receipt-test.cpp
--- a/src/tests/client/room/read-receipt-test.cpp
+++ b/src/tests/client/room/read-receipt-test.cpp
@@ -24,6 +24,7 @@
#include <testfixtures/factory.hpp>
#include "client-test-util.hpp"
+#include "action-mock-utils.hpp"
using namespace Kazv;
using namespace Kazv::Factory;
@@ -181,18 +182,12 @@
auto ctx = sdk.context();
- auto postReceiptCalled = 0;
- std::string postReceiptRoomId;
- std::string postReceiptEventId;
- auto mockContext = typename Client::ContextT([&sgph, &postReceiptCalled, &postReceiptRoomId, &postReceiptEventId](const auto &action) {
- if (std::holds_alternative<PostReceiptAction>(action)) {
- ++postReceiptCalled;
- postReceiptRoomId = std::get<PostReceiptAction>(action).roomId;
- postReceiptEventId = std::get<PostReceiptAction>(action).eventId;
- return sgph.createResolved(EffectStatus(true, json::object()));
- }
- throw std::runtime_error{"unhandled action"};
- }, sgph, lager::deps<>{});
+ auto dispatcher = getMockDispatcher(
+ sgph,
+ ctx,
+ returnEmpty<PostReceiptAction>()
+ );
+ auto mockContext = getMockContext(sgph, dispatcher);
auto client = Client(Client::InEventLoopTag{}, mockContext, sdk.context());
auto room = client.room(r.roomId);
@@ -203,9 +198,10 @@
});
io.run();
- REQUIRE(postReceiptCalled == 1);
- REQUIRE(postReceiptRoomId == r.roomId);
- REQUIRE(postReceiptEventId == "$1");
+ REQUIRE(dispatcher.template calledTimes<PostReceiptAction>() == 1);
+ auto action = dispatcher.template of<PostReceiptAction>()[0];
+ REQUIRE(action.roomId == r.roomId);
+ REQUIRE(action.eventId == "$1");
}
TEST_CASE("PostReceiptAction", "[client][room][receipt]")
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Nov 23, 4:42 AM (9 h, 29 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39345
Default Alt Text
D64.1732365755.diff (39 KB)
Attached To
Mode
D64: Create a mock helper for contexts
Attached
Detach File
Event Timeline
Log In to Comment