diff --git a/src/observer.h b/src/observer.h index 03619f0516aba8463ac01356cc69ad9002186e5c..e32cfe789aa4de8d0ce360b3141a037517826508 100644 --- a/src/observer.h +++ b/src/observer.h @@ -31,7 +31,9 @@ #include <mutex> #include <functional> #include <ciso646> // fix windows compiler bug +#ifndef __DEBUG__ // this is only defined on plugins build for debugging #include "logger.h" +#endif namespace jami { @@ -127,7 +129,9 @@ protected: try { so->update(this, data); } catch (std::exception& e) { +#ifndef __DEBUG__ JAMI_ERR() << e.what(); +#endif } } else { it = priority_observers_.erase(it); diff --git a/src/plugin/streamdata.h b/src/plugin/streamdata.h index e0d51bd40efd93929f77e5150ee563887e1ba5b6..3cd7f572a380aac7becaf08ea40ccee6c5f9fe12 100644 --- a/src/plugin/streamdata.h +++ b/src/plugin/streamdata.h @@ -70,7 +70,7 @@ struct JamiMessage /** * @param accId AccountId * @param pId peerId - * @param isReceived False if local audio/video streams + * @param isReceived True if received message, False if sent * @param dataMap Message contents * @param pPlugin True if message is created/modified by plugin code */ diff --git a/test/meson.build b/test/meson.build index cce4ceb02c0cba2ba465fcb65084342b6c5663b3..edb5421e6a163b8f69f7e32b74fac719af2cf7a2 100644 --- a/test/meson.build +++ b/test/meson.build @@ -452,4 +452,15 @@ if conf.get('ENABLE_VIDEO') test('video_scaler', ut_video_scaler, workdir: ut_workdir, is_parallel: false, timeout: 1800 ) + + + ut_plugins = executable('ut_plugins', + sources: files('unitTest/plugins/plugins.cpp'), + include_directories: ut_includedirs, + dependencies: ut_dependencies, + link_with: ut_library + ) + test('plugins', ut_plugins, + workdir: ut_workdir, is_parallel: false, timeout: 1800 + ) endif diff --git a/test/unitTest/Makefile.am b/test/unitTest/Makefile.am index 05c9e46666536a45f57e5064b188458ecd605983..7e87bbf788610ae66c1054b4a7c83f935d97b18d 100644 --- a/test/unitTest/Makefile.am +++ b/test/unitTest/Makefile.am @@ -226,5 +226,10 @@ ut_sip_empty_offer_SOURCES = sip_account/sip_empty_offer.cpp check_PROGRAMS += ut_sip_srtp ut_sip_srtp_SOURCES = sip_account/sip_srtp.cpp +# +# Plugins +# +check_PROGRAMS += ut_plugins +ut_plugins_SOURCES = plugins/plugins.cpp common.cpp TESTS = $(check_PROGRAMS) diff --git a/test/unitTest/plugins/README b/test/unitTest/plugins/README new file mode 100644 index 0000000000000000000000000000000000000000..61f536ca1aa35aabd3a703aed6f9b1ff0ecfef1d --- /dev/null +++ b/test/unitTest/plugins/README @@ -0,0 +1,42 @@ +COPYRIGHT NOTICE + +Copyright (C) 2022 Savoir-faire Linux Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +plugin.yml +---------- + +The test configuration file can specify: + +* jplDirectory - relative to the test executable or a full path; + +* plugin - the plugin name; + +* mediaHandlers - present media handlers names in the plugin; + +* chatHandlers - present chat handlers names in the plugin; + +The default test plugin is TestSuite. + +Test plugin build +----------------- + +For the CI tests, which uses a Ubuntu 20.04 docker, the test suite must be build with an appropriate glibc version, meaning a glib 3.31 or older. + +Jami supports systems from Ubuntu 18.04, which uses glib 2.27. + +If the plugin is build within a Ubuntu 18.04, it should work on all Jami supported platforms. + +TO check your system glib version: `ldd --version` diff --git a/test/unitTest/plugins/TestSuite.jpl b/test/unitTest/plugins/TestSuite.jpl new file mode 100644 index 0000000000000000000000000000000000000000..2fa78b959f8594ead7c23597401688ed5832199f Binary files /dev/null and b/test/unitTest/plugins/TestSuite.jpl differ diff --git a/test/unitTest/plugins/plugin.yml b/test/unitTest/plugins/plugin.yml new file mode 100644 index 0000000000000000000000000000000000000000..3971061408614eea6e783329691f107764311ab9 --- /dev/null +++ b/test/unitTest/plugins/plugin.yml @@ -0,0 +1,9 @@ +jplDirectory: + "plugins" +plugin: + "TestSuite" +mediaHandlers: + - "AudioHandlerTester" + - "VideoHandlerTester" +chatHandlers: + - "ChatHandlerTester" diff --git a/test/unitTest/plugins/plugins.cpp b/test/unitTest/plugins/plugins.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a74a1b87becb2a074c94c27f828a149ce99b7950 --- /dev/null +++ b/test/unitTest/plugins/plugins.cpp @@ -0,0 +1,665 @@ +/* + * Copyright (C) 2022 Savoir-faire Linux Inc. + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <cppunit/TestAssert.h> +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +#include <condition_variable> +#include <filesystem> +#include <string> + +#include "manager.h" +#include "plugin/jamipluginmanager.h" +#include "jamidht/jamiaccount.h" +#include "../../test_runner.h" +#include "jami.h" +#include "fileutils.h" +#include "jami/media_const.h" +#include "account_const.h" +#include "sip/sipcall.h" +#include "call_const.h" + +#include "common.h" + +using namespace DRing::Account; + +namespace jami { +namespace test { + +struct CallData +{ + struct Signal + { + Signal(const std::string& name, const std::string& event = {}) + : name_(std::move(name)) + , event_(std::move(event)) {}; + + std::string name_ {}; + std::string event_ {}; + }; + + CallData() = default; + CallData(CallData&& other) = delete; + CallData(const CallData& other) + { + accountId_ = std::move(other.accountId_); + listeningPort_ = other.listeningPort_; + userName_ = std::move(other.userName_); + alias_ = std::move(other.alias_); + callId_ = std::move(other.callId_); + signals_ = std::move(other.signals_); + }; + + std::string accountId_ {}; + std::string userName_ {}; + std::string alias_ {}; + uint16_t listeningPort_ {0}; + std::string toUri_ {}; + std::string callId_ {}; + std::vector<Signal> signals_; + std::condition_variable cv_ {}; + std::mutex mtx_; +}; + +class PluginsTest : public CppUnit::TestFixture +{ +public: + PluginsTest() + { + // Init daemon + DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG)); + if (not Manager::instance().initialized) + CPPUNIT_ASSERT(DRing::start("jami-sample.yml")); + } + ~PluginsTest() { DRing::fini(); } + static std::string name() { return "Plugins"; } + void setUp(); + void tearDown(); + + CallData aliceData; + CallData bobData; + +private: + static bool waitForSignal(CallData& callData, + const std::string& signal, + const std::string& expectedEvent = {}); + // Event/Signal handlers + static void onCallStateChange(const std::string& accountId, + const std::string& callId, + const std::string& state, + CallData& callData); + static void onIncomingCallWithMedia(const std::string& accountId, + const std::string& callId, + const std::vector<DRing::MediaMap> mediaList, + CallData& callData); + + std::string name_{}; + std::string jplPath_{}; + std::string installationPath_{}; + std::vector<std::string> mediaHandlers_{}; + std::vector<std::string> chatHandlers_{}; + + void testEnable(); + void testInstallAndLoad(); + void testHandlers(); + void testDetailsAndPreferences(); + void testCall(); + void testMessage(); + + CPPUNIT_TEST_SUITE(PluginsTest); + CPPUNIT_TEST(testEnable); + CPPUNIT_TEST(testInstallAndLoad); + CPPUNIT_TEST(testHandlers); + CPPUNIT_TEST(testDetailsAndPreferences); + CPPUNIT_TEST(testCall); + CPPUNIT_TEST(testMessage); + CPPUNIT_TEST_SUITE_END(); +}; + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(PluginsTest, PluginsTest::name()); + +void +PluginsTest::onIncomingCallWithMedia(const std::string& accountId, + const std::string& callId, + const std::vector<DRing::MediaMap> mediaList, + CallData& callData) +{ + CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId); + + JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]", + DRing::CallSignal::IncomingCallWithMedia::name, + callData.alias_.c_str(), + callId.c_str(), + mediaList.size()); + + // NOTE. + // We shouldn't access shared_ptr<Call> as this event is supposed to mimic + // the client, and the client have no access to this type. But here, we only + // needed to check if the call exists. This is the most straightforward and + // reliable way to do it until we add a new API (like hasCall(id)). + if (not Manager::instance().getCallFromCallID(callId)) { + JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str()); + callData.callId_ = {}; + return; + } + + std::unique_lock<std::mutex> lock {callData.mtx_}; + callData.callId_ = callId; + callData.signals_.emplace_back(CallData::Signal(DRing::CallSignal::IncomingCallWithMedia::name)); + + callData.cv_.notify_one(); +} + +void +PluginsTest::onCallStateChange(const std::string& accountId, + const std::string& callId, + const std::string& state, + CallData& callData) +{ + JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]", + DRing::CallSignal::StateChange::name, + callData.alias_.c_str(), + callId.c_str(), + state.c_str()); + + CPPUNIT_ASSERT(accountId == callData.accountId_); + + { + std::unique_lock<std::mutex> lock {callData.mtx_}; + callData.signals_.emplace_back( + CallData::Signal(DRing::CallSignal::StateChange::name, state)); + } + // NOTE. Only states that we are interested in will notify the CV. + // If this unit test is modified to process other states, they must + // be added here. + if (state == "CURRENT" or state == "OVER" or state == "HUNGUP" or state == "RINGING") { + callData.cv_.notify_one(); + } +} + +void +PluginsTest::setUp() +{ + auto actors = load_actors_and_wait_for_announcement("actors/alice-bob-no-upnp.yml"); + + aliceData.accountId_ = actors["alice"]; + bobData.accountId_ = actors["bob"]; + + // Configure Alice + { + CPPUNIT_ASSERT(not aliceData.accountId_.empty()); + auto const& account = Manager::instance().getAccount<Account>( + aliceData.accountId_); + aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; + aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; + account->enableIceForMedia(true); + } + + // Configure Bob + { + CPPUNIT_ASSERT(not bobData.accountId_.empty()); + auto const& account = Manager::instance().getAccount<Account>( + bobData.accountId_); + bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME]; + bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS]; + account->enableIceForMedia(true); + } + + std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> signalHandlers; + + // Insert needed signal handlers. + signalHandlers.insert(DRing::exportable_callback<DRing::CallSignal::IncomingCallWithMedia>( + [&](const std::string& accountId, + const std::string& callId, + const std::string&, + const std::vector<DRing::MediaMap> mediaList) { + if (aliceData.accountId_ == accountId) + onIncomingCallWithMedia(accountId, callId, mediaList, aliceData); + else if (bobData.accountId_ == accountId) + onIncomingCallWithMedia(accountId, callId, mediaList, bobData); + })); + + signalHandlers.insert( + DRing::exportable_callback<DRing::CallSignal::StateChange>([&](const std::string& accountId, + const std::string& callId, + const std::string& state, + signed) { + if (aliceData.accountId_ == accountId) + onCallStateChange(accountId, callId, state, aliceData); + else if (bobData.accountId_ == accountId) + onCallStateChange(accountId, callId, state, bobData); + })); + + DRing::registerSignalHandlers(signalHandlers); + + std::ifstream file = jami::fileutils::ifstream("plugins/plugin.yml"); + assert(file.is_open()); + YAML::Node node = YAML::Load(file); + + assert(node.IsMap()); + + name_ = node["plugin"].as<std::string>(); + jplPath_ = node["jplDirectory"].as<std::string>() + DIR_SEPARATOR_CH + name_ + ".jpl"; + installationPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_CH + "plugins" + DIR_SEPARATOR_CH + name_; + mediaHandlers_ = node["mediaHandlers"].as<std::vector<std::string>>(); + chatHandlers_ = node["chatHandlers"].as<std::vector<std::string>>(); +} + +void +PluginsTest::tearDown() +{ + DRing::unregisterSignalHandlers(); + wait_for_removal_of({aliceData.accountId_, bobData.accountId_}); +} + +void +PluginsTest::testEnable() +{ + Manager::instance().pluginPreferences.setPluginsEnabled(true); + CPPUNIT_ASSERT(Manager::instance().pluginPreferences.getPluginsEnabled()); + Manager::instance().pluginPreferences.setPluginsEnabled(false); + CPPUNIT_ASSERT(!Manager::instance().pluginPreferences.getPluginsEnabled()); +} + +void +PluginsTest::testInstallAndLoad() +{ + Manager::instance().pluginPreferences.setPluginsEnabled(true); + + CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true)); + auto installedPlugins = Manager::instance().getJamiPluginManager().getInstalledPlugins(); + CPPUNIT_ASSERT(!installedPlugins.empty()); + CPPUNIT_ASSERT(std::find(installedPlugins.begin(), + installedPlugins.end(), + installationPath_) + != installedPlugins.end()); + + auto loadedPlugins = Manager::instance().getJamiPluginManager().getLoadedPlugins(); + CPPUNIT_ASSERT(!loadedPlugins.empty()); + CPPUNIT_ASSERT(std::find(loadedPlugins.begin(), + loadedPlugins.end(), + installationPath_) + != loadedPlugins.end()); + + CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().unloadPlugin(installationPath_)); + loadedPlugins = Manager::instance().getJamiPluginManager().getLoadedPlugins(); + CPPUNIT_ASSERT(std::find(loadedPlugins.begin(), + loadedPlugins.end(), + installationPath_) + == loadedPlugins.end()); + + CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_)); + installedPlugins = Manager::instance().getJamiPluginManager().getInstalledPlugins(); + CPPUNIT_ASSERT(std::find(installedPlugins.begin(), + installedPlugins.end(), + installationPath_) + == installedPlugins.end()); + +} + +void +PluginsTest::testHandlers() +{ + Manager::instance().pluginPreferences.setPluginsEnabled(true); + + Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true); + + auto mediaHandlers = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlers(); + auto chatHandlers = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlers(); + + auto handlerLoaded = mediaHandlers_.size() + chatHandlers_.size(); // number of handlers expected + for (auto handler : mediaHandlers) + { + auto details = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlerDetails(handler); + // check details expected for the test plugin + if(std::find(mediaHandlers_.begin(), + mediaHandlers_.end(), + details["name"]) + != mediaHandlers_.end()) { + handlerLoaded--; + } + } + for (auto handler : chatHandlers) + { + auto details = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlerDetails(handler); + // check details expected for the test plugin + if(std::find(chatHandlers_.begin(), + chatHandlers_.end(), + details["name"]) + != chatHandlers_.end()) { + handlerLoaded--; + } + } + + CPPUNIT_ASSERT(!handlerLoaded); // All expected handlers were found + CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_)); +} + +void +PluginsTest::testDetailsAndPreferences() +{ + Manager::instance().pluginPreferences.setPluginsEnabled(true); + Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true); + // Unload now to avoid reloads when changing the preferences + Manager::instance().getJamiPluginManager().unloadPlugin(installationPath_); + + // Details + auto details = Manager::instance().getJamiPluginManager().getPluginDetails(installationPath_); + CPPUNIT_ASSERT(details["name"] == name_); + + // Get-set-reset - no account + auto preferences = Manager::instance().getJamiPluginManager().getPluginPreferences(installationPath_, ""); + CPPUNIT_ASSERT(!preferences.empty()); + auto preferencesValuesOrig = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, ""); + + std::string preferenceNewValue = aliceData.accountId_; + auto key = preferences[0]["key"]; + CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().setPluginPreference(installationPath_, "", key, preferenceNewValue)); + + // Test global preference change + auto preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, ""); + CPPUNIT_ASSERT(preferencesValuesOrig[key] != preferencesValuesNew[key]); + CPPUNIT_ASSERT(preferencesValuesNew[key] == preferenceNewValue); + + // Test global preference change in an account + preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_); + CPPUNIT_ASSERT(preferencesValuesOrig[key] != preferencesValuesNew[key]); + CPPUNIT_ASSERT(preferencesValuesNew[key] == preferenceNewValue); + + // Test reset global preference change + Manager::instance().getJamiPluginManager().resetPluginPreferencesValuesMap(installationPath_, ""); + preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, ""); + CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesNew[key]); + CPPUNIT_ASSERT(preferencesValuesNew[key] != preferenceNewValue); + + + + // Get-set-reset - alice account + preferences = Manager::instance().getJamiPluginManager().getPluginPreferences(installationPath_, aliceData.accountId_); + CPPUNIT_ASSERT(!preferences.empty()); + preferencesValuesOrig = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_); + auto preferencesValuesBobOrig = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, bobData.accountId_); + + key = preferences[0]["key"]; + CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().setPluginPreference(installationPath_, aliceData.accountId_, key, preferenceNewValue)); + + // Test account preference change + preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_); + auto preferencesValuesBobNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, bobData.accountId_); + CPPUNIT_ASSERT(preferencesValuesBobNew[key] == preferencesValuesBobOrig[key]); + CPPUNIT_ASSERT(preferencesValuesOrig[key] != preferencesValuesNew[key]); + CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesBobOrig[key]); + CPPUNIT_ASSERT(preferencesValuesNew[key] != preferencesValuesBobOrig[key]); + CPPUNIT_ASSERT(preferencesValuesNew[key] == preferenceNewValue); + + // Test account preference change with global preference reset + Manager::instance().getJamiPluginManager().resetPluginPreferencesValuesMap(installationPath_, ""); + preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_); + preferencesValuesBobNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, bobData.accountId_); + CPPUNIT_ASSERT(preferencesValuesBobNew[key] == preferencesValuesBobOrig[key]); + CPPUNIT_ASSERT(preferencesValuesOrig[key] != preferencesValuesNew[key]); + CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesBobOrig[key]); + CPPUNIT_ASSERT(preferencesValuesNew[key] != preferencesValuesBobOrig[key]); + CPPUNIT_ASSERT(preferencesValuesNew[key] == preferenceNewValue); + + // Test account preference reset + Manager::instance().getJamiPluginManager().resetPluginPreferencesValuesMap(installationPath_, aliceData.accountId_); + preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_); + preferencesValuesBobNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, bobData.accountId_); + CPPUNIT_ASSERT(preferencesValuesBobNew[key] == preferencesValuesBobOrig[key]); + CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesNew[key]); + CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesBobOrig[key]); + CPPUNIT_ASSERT(preferencesValuesNew[key] == preferencesValuesBobOrig[key]); + CPPUNIT_ASSERT(preferencesValuesNew[key] != preferenceNewValue); + + // Test translations + + CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_)); +} + + +bool +PluginsTest::waitForSignal(CallData& callData, + const std::string& expectedSignal, + const std::string& expectedEvent) +{ + const std::chrono::seconds TIME_OUT {30}; + std::unique_lock<std::mutex> lock {callData.mtx_}; + + // Combined signal + event (if any). + std::string sigEvent(expectedSignal); + if (not expectedEvent.empty()) + sigEvent += "::" + expectedEvent; + + JAMI_INFO("[%s] is waiting for [%s] signal/event", callData.alias_.c_str(), sigEvent.c_str()); + + auto res = callData.cv_.wait_for(lock, TIME_OUT, [&] { + // Search for the expected signal in list of received signals. + bool pred = false; + for (auto it = callData.signals_.begin(); it != callData.signals_.end(); it++) { + // The predicate is true if the signal names match, and if the + // expectedEvent is not empty, the events must also match. + if (it->name_ == expectedSignal + and (expectedEvent.empty() or it->event_ == expectedEvent)) { + pred = true; + // Done with this signal. + callData.signals_.erase(it); + break; + } + } + + return pred; + }); + + if (not res) { + JAMI_ERR("[%s] waiting for signal/event [%s] timed-out!", + callData.alias_.c_str(), + sigEvent.c_str()); + + JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str()); + + for (auto const& sig : callData.signals_) { + JAMI_INFO() << "\tSignal [" << sig.name_ + << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]"; + } + } + + return res; +} + +void +PluginsTest::testCall() +{ + Manager::instance().pluginPreferences.setPluginsEnabled(true); + Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true); + + // alice calls bob + // for handler available, toggle - check status - untoggle - checkstatus + // end call + + MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO); + defaultAudio.label_ = "audio_0"; + defaultAudio.enabled_ = true; + + MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO); + defaultVideo.label_ = "video_0"; + defaultVideo.enabled_ = true; + + std::vector<MediaAttribute> request; + std::vector<MediaAttribute> answer; + // First offer/answer + request.emplace_back(MediaAttribute(defaultAudio)); + request.emplace_back(MediaAttribute(defaultVideo)); + answer.emplace_back(MediaAttribute(defaultAudio)); + answer.emplace_back(MediaAttribute(defaultVideo)); + + JAMI_INFO("Start call between alice and Bob"); + aliceData.callId_ = DRing::placeCallWithMedia(aliceData.accountId_, bobData.userName_, MediaAttribute::mediaAttributesToMediaMaps(request)); + CPPUNIT_ASSERT(not aliceData.callId_.empty()); + + auto aliceCall = std::static_pointer_cast<SIPCall>( + Manager::instance().getCallFromCallID(aliceData.callId_)); + CPPUNIT_ASSERT(aliceCall); + + aliceData.callId_ = aliceCall->getCallId(); + + JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer", + aliceData.accountId_.c_str(), + bobData.accountId_.c_str()); + + // Wait for incoming call signal. + CPPUNIT_ASSERT(waitForSignal(bobData, DRing::CallSignal::IncomingCallWithMedia::name)); + + // Answer the call. + { + DRing::acceptWithMedia(bobData.accountId_, bobData.callId_, MediaAttribute::mediaAttributesToMediaMaps(answer)); + } + + CPPUNIT_ASSERT_EQUAL(true, + waitForSignal(bobData, + DRing::CallSignal::StateChange::name, + DRing::Call::StateEvent::CURRENT)); + + JAMI_INFO("BOB answered the call [%s]", bobData.callId_.c_str()); + + std::this_thread::sleep_for(std::chrono::seconds(3)); + auto mediaHandlers = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlers(); + + for (auto handler : mediaHandlers) + { + auto details = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlerDetails(handler); + // check details expected for the test plugin + if(std::find(mediaHandlers_.begin(), + mediaHandlers_.end(), + details["name"]) + != mediaHandlers_.end()) { + Manager::instance().getJamiPluginManager().getCallServicesManager().toggleCallMediaHandler(handler, aliceData.callId_, true); + auto statusMap = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlerStatus(aliceData.callId_); + CPPUNIT_ASSERT(std::find(statusMap.begin(), statusMap.end(), handler) != statusMap.end()); + + Manager::instance().getJamiPluginManager().getCallServicesManager().toggleCallMediaHandler(handler, aliceData.callId_, false); + statusMap = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlerStatus(aliceData.callId_); + CPPUNIT_ASSERT(std::find(statusMap.begin(), statusMap.end(), handler) == statusMap.end()); + } + } + + std::this_thread::sleep_for(std::chrono::seconds(3)); + // Bob hang-up. + JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up"); + DRing::hangUp(bobData.accountId_, bobData.callId_); + + CPPUNIT_ASSERT_EQUAL(true, + waitForSignal(aliceData, + DRing::CallSignal::StateChange::name, + DRing::Call::StateEvent::HUNGUP)); + + JAMI_INFO("Call terminated on both sides"); + CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_)); +} + +void +PluginsTest::testMessage() +{ + Manager::instance().pluginPreferences.setPluginsEnabled(true); + Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true); + + // alice and bob chat + // for handler available, toggle - check status - untoggle - checkstatus + // end call + + std::mutex mtx; + std::unique_lock<std::mutex> lk {mtx}; + std::condition_variable cv; + std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; + auto messageBobReceived = 0, messageAliceReceived = 0; + bool requestReceived = false; + bool conversationReady = false; + confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( + [&](const std::string& accountId, + const std::string& /* conversationId */, + std::map<std::string, std::string> /*message*/) { + if (accountId == bobData.accountId_) { + messageBobReceived += 1; + } else { + messageAliceReceived += 1; + } + cv.notify_one(); + })); + confHandlers.insert( + DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>( + [&](const std::string& /*accountId*/, + const std::string& /* conversationId */, + std::map<std::string, std::string> /*metadatas*/) { + requestReceived = true; + cv.notify_one(); + })); + confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>( + [&](const std::string& accountId, const std::string& /* conversationId */) { + if (accountId == bobData.accountId_) { + conversationReady = true; + cv.notify_one(); + } + })); + DRing::registerSignalHandlers(confHandlers); + + auto convId = DRing::startConversation(aliceData.accountId_); + + DRing::addConversationMember(aliceData.accountId_, convId, bobData.userName_); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; })); + + DRing::acceptConversationRequest(bobData.accountId_, convId); + CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; })); + + // Assert that repository exists + auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobData.accountId_ + + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId; + CPPUNIT_ASSERT(fileutils::isDirectory(repoPath)); + // Wait that alice sees Bob + cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 2; }); + + auto chatHandlers = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlers(); + + for (auto handler : chatHandlers) + { + auto details = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlerDetails(handler); + // check details expected for the test plugin + if(std::find(chatHandlers_.begin(), + chatHandlers_.end(), + details["name"]) + != chatHandlers_.end()) { + Manager::instance().getJamiPluginManager().getChatServicesManager().toggleChatHandler(handler, aliceData.accountId_, convId, true); + auto statusMap = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlerStatus(aliceData.accountId_, convId); + CPPUNIT_ASSERT(std::find(statusMap.begin(), statusMap.end(), handler) != statusMap.end()); + + DRing::sendMessage(aliceData.accountId_, convId, "hi"s, ""); + cv.wait_for(lk, 30s, [&]() { return messageBobReceived == 1; }); + + Manager::instance().getJamiPluginManager().getChatServicesManager().toggleChatHandler(handler, aliceData.accountId_, convId, false); + statusMap = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlerStatus(aliceData.accountId_, convId); + CPPUNIT_ASSERT(std::find(statusMap.begin(), statusMap.end(), handler) == statusMap.end()); + } + } + + CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_)); +} + +} // namespace test +} // namespace jami + +RING_TEST_RUNNER(jami::test::PluginsTest::name())