LCOV - code coverage report
Current view: top level - test/unitTest/plugins - plugins.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 373 382 97.6 %
Date: 2024-05-11 09:31:29 Functions: 35 35 100.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2022-2024 Savoir-faire Linux Inc.
       3             :  *  Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com>
       4             :  *
       5             :  *  This program is free software; you can redistribute it and/or modify
       6             :  *  it under the terms of the GNU General Public License as published by
       7             :  *  the Free Software Foundation; either version 3 of the License, or
       8             :  *  (at your option) any later version.
       9             :  *
      10             :  *  This program is distributed in the hope that it will be useful,
      11             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :  *  GNU General Public License for more details.
      14             :  *
      15             :  *  You should have received a copy of the GNU General Public License
      16             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      17             :  */
      18             : 
      19             : #include <cppunit/TestAssert.h>
      20             : #include <cppunit/TestFixture.h>
      21             : #include <cppunit/extensions/HelperMacros.h>
      22             : 
      23             : #include <condition_variable>
      24             : #include <opendht/crypto.h>
      25             : #include <filesystem>
      26             : #include <memory>
      27             : #include <string>
      28             : 
      29             : #include "manager.h"
      30             : #include "plugin/jamipluginmanager.h"
      31             : #include "plugin/pluginsutils.h"
      32             : #include "jamidht/jamiaccount.h"
      33             : #include "../../test_runner.h"
      34             : #include "jami.h"
      35             : #include "fileutils.h"
      36             : #include "jami/media_const.h"
      37             : #include "account_const.h"
      38             : #include "sip/sipcall.h"
      39             : #include "call_const.h"
      40             : 
      41             : #include "common.h"
      42             : 
      43             : #include <yaml-cpp/yaml.h>
      44             : 
      45             : using namespace libjami::Account;
      46             : 
      47             : namespace jami {
      48             : namespace test {
      49             : 
      50             : struct CallData
      51             : {
      52             :     struct Signal
      53             :     {
      54          12 :         Signal(const std::string& name, const std::string& event = {})
      55          12 :             : name_(std::move(name))
      56          12 :             , event_(std::move(event)) {};
      57             : 
      58             :         std::string name_ {};
      59             :         std::string event_ {};
      60             :     };
      61             : 
      62          20 :     CallData() = default;
      63             :     CallData(CallData&& other) = delete;
      64             :     CallData(const CallData& other)
      65             :     {
      66             :         accountId_ = std::move(other.accountId_);
      67             :         listeningPort_ = other.listeningPort_;
      68             :         userName_ = std::move(other.userName_);
      69             :         alias_ = std::move(other.alias_);
      70             :         callId_ = std::move(other.callId_);
      71             :         signals_ = std::move(other.signals_);
      72             :     };
      73             : 
      74             :     std::string accountId_ {};
      75             :     std::string userName_ {};
      76             :     std::string alias_ {};
      77             :     uint16_t listeningPort_ {0};
      78             :     std::string toUri_ {};
      79             :     std::string callId_ {};
      80             :     std::vector<Signal> signals_;
      81             :     std::condition_variable cv_ {};
      82             :     std::mutex mtx_;
      83             : };
      84             : 
      85             : class PluginsTest : public CppUnit::TestFixture
      86             : {
      87             : public:
      88          10 :     PluginsTest()
      89          10 :     {
      90             :         // Init daemon
      91          10 :         libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
      92          10 :         if (not Manager::instance().initialized)
      93           1 :             CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
      94          10 :     }
      95          20 :     ~PluginsTest() { libjami::fini(); }
      96           2 :     static std::string name() { return "Plugins"; }
      97             :     void setUp();
      98             :     void tearDown();
      99             : 
     100             :     CallData aliceData;
     101             :     CallData bobData;
     102             : 
     103             : private:
     104             :     static bool waitForSignal(CallData& callData,
     105             :                               const std::string& signal,
     106             :                               const std::string& expectedEvent = {});
     107             :     // Event/Signal handlers
     108             :     static void onCallStateChange(const std::string& accountId,
     109             :                                   const std::string& callId,
     110             :                                   const std::string& state,
     111             :                                   CallData& callData);
     112             :     static void onIncomingCallWithMedia(const std::string& accountId,
     113             :                                         const std::string& callId,
     114             :                                         const std::vector<libjami::MediaMap> mediaList,
     115             :                                         CallData& callData);
     116             : 
     117             :     std::string name_{};
     118             :     std::string jplPath_{};
     119             :     std::string certPath_{};
     120             :     std::string pluginCertNotFound_{};
     121             :     std::string pluginNotSign_{};
     122             :     std::string pluginFileNotSign_{};
     123             :     std::string pluginManifestChanged_{};
     124             :     std::string pluginNotSignByIssuer_{};
     125             :     std::string pluginNotFoundPath_{};
     126             :     std::unique_ptr<dht::crypto::Certificate> cert_{};
     127             :     std::string installationPath_{};
     128             :     std::vector<std::string> mediaHandlers_{};
     129             :     std::vector<std::string> chatHandlers_{};
     130             : 
     131             :     void testEnable();
     132             :     void testCertificateVerification();
     133             :     void testSignatureVerification();
     134             :     void testLoad();
     135             :     void testInstallAndLoad();
     136             :     void testHandlers();
     137             :     void testDetailsAndPreferences();
     138             :     void testTranslations();
     139             :     void testCall();
     140             :     void testMessage();
     141             : 
     142           2 :     CPPUNIT_TEST_SUITE(PluginsTest);
     143           1 :     CPPUNIT_TEST(testEnable);
     144           1 :     CPPUNIT_TEST(testCertificateVerification);
     145           1 :     CPPUNIT_TEST(testSignatureVerification);
     146           1 :     CPPUNIT_TEST(testLoad);
     147           1 :     CPPUNIT_TEST(testInstallAndLoad);
     148           1 :     CPPUNIT_TEST(testHandlers);
     149           1 :     CPPUNIT_TEST(testDetailsAndPreferences);
     150           1 :     CPPUNIT_TEST(testTranslations);
     151           1 :     CPPUNIT_TEST(testCall);
     152           1 :     CPPUNIT_TEST(testMessage);
     153           4 :     CPPUNIT_TEST_SUITE_END();
     154             : };
     155             : 
     156             : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(PluginsTest, PluginsTest::name());
     157             : 
     158             : void
     159           1 : PluginsTest::onIncomingCallWithMedia(const std::string& accountId,
     160             :                                         const std::string& callId,
     161             :                                         const std::vector<libjami::MediaMap> mediaList,
     162             :                                         CallData& callData)
     163             : {
     164           1 :     CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
     165             : 
     166           1 :     JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
     167             :               libjami::CallSignal::IncomingCallWithMedia::name,
     168             :               callData.alias_.c_str(),
     169             :               callId.c_str(),
     170             :               mediaList.size());
     171             : 
     172             :     // NOTE.
     173             :     // We shouldn't access shared_ptr<Call> as this event is supposed to mimic
     174             :     // the client, and the client have no access to this type. But here, we only
     175             :     // needed to check if the call exists. This is the most straightforward and
     176             :     // reliable way to do it until we add a new API (like hasCall(id)).
     177           1 :     if (not Manager::instance().getCallFromCallID(callId)) {
     178           0 :         JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
     179           0 :         callData.callId_ = {};
     180           0 :         return;
     181             :     }
     182             : 
     183           1 :     std::unique_lock lock {callData.mtx_};
     184           1 :     callData.callId_ = callId;
     185           1 :     callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::IncomingCallWithMedia::name));
     186             : 
     187           1 :     callData.cv_.notify_one();
     188           1 : }
     189             : 
     190             : void
     191          11 : PluginsTest::onCallStateChange(const std::string& accountId,
     192             :                                 const std::string& callId,
     193             :                                 const std::string& state,
     194             :                                 CallData& callData)
     195             : {
     196          11 :     JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
     197             :               libjami::CallSignal::StateChange::name,
     198             :               callData.alias_.c_str(),
     199             :               callId.c_str(),
     200             :               state.c_str());
     201             : 
     202          11 :     CPPUNIT_ASSERT(accountId == callData.accountId_);
     203             : 
     204             :     {
     205          11 :         std::unique_lock lock {callData.mtx_};
     206          11 :         callData.signals_.emplace_back(
     207          22 :             CallData::Signal(libjami::CallSignal::StateChange::name, state));
     208          11 :     }
     209             :     // NOTE. Only states that we are interested in will notify the CV.
     210             :     // If this unit test is modified to process other states, they must
     211             :     // be added here.
     212          11 :     if (state == "CURRENT" or state == "OVER" or state == "HUNGUP" or state == "RINGING") {
     213           7 :         callData.cv_.notify_one();
     214             :     }
     215          11 : }
     216             : 
     217             : void
     218          10 : PluginsTest::setUp()
     219             : {
     220          20 :     auto actors = load_actors_and_wait_for_announcement("actors/alice-bob-no-upnp.yml");
     221             : 
     222          10 :     aliceData.accountId_ = actors["alice"];
     223          10 :     bobData.accountId_ = actors["bob"];
     224             : 
     225             :     // Configure Alice
     226             :     {
     227          10 :         CPPUNIT_ASSERT(not aliceData.accountId_.empty());
     228          10 :         auto const& account = Manager::instance().getAccount<Account>(
     229          10 :             aliceData.accountId_);
     230          10 :         aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
     231          10 :         aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
     232          10 :     }
     233             : 
     234             :     // Configure Bob
     235             :     {
     236          10 :         CPPUNIT_ASSERT(not bobData.accountId_.empty());
     237          10 :         auto const& account = Manager::instance().getAccount<Account>(
     238          10 :             bobData.accountId_);
     239          10 :         bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
     240          10 :         bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
     241          10 :     }
     242             : 
     243          10 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> signalHandlers;
     244             : 
     245             :     // Insert needed signal handlers.
     246          10 :     signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::IncomingCallWithMedia>(
     247           1 :         [&](const std::string& accountId,
     248             :             const std::string& callId,
     249             :             const std::string&,
     250             :             const std::vector<libjami::MediaMap> mediaList) {
     251           1 :             if (aliceData.accountId_ == accountId)
     252           0 :                 onIncomingCallWithMedia(accountId, callId, mediaList, aliceData);
     253           1 :             else if (bobData.accountId_ == accountId)
     254           1 :                 onIncomingCallWithMedia(accountId, callId, mediaList, bobData);
     255           1 :         }));
     256             : 
     257          10 :     signalHandlers.insert(
     258          20 :         libjami::exportable_callback<libjami::CallSignal::StateChange>([&](const std::string& accountId,
     259             :                                                                        const std::string& callId,
     260             :                                                                        const std::string& state,
     261             :                                                                        signed) {
     262          11 :             if (aliceData.accountId_ == accountId)
     263           5 :                 onCallStateChange(accountId, callId, state, aliceData);
     264           6 :             else if (bobData.accountId_ == accountId)
     265           6 :                 onCallStateChange(accountId, callId, state, bobData);
     266          11 :         }));
     267             : 
     268          10 :     libjami::registerSignalHandlers(signalHandlers);
     269          10 :     std::ifstream file("plugins/plugin.yml");
     270          10 :     assert(file.is_open());
     271          10 :     YAML::Node node = YAML::Load(file);
     272             : 
     273          10 :     assert(node.IsMap());
     274             : 
     275          10 :     name_ = node["plugin"].as<std::string>();
     276          10 :     certPath_ = node["cert"].as<std::string>();
     277          10 :     cert_ = std::make_unique<dht::crypto::Certificate>(fileutils::loadFile(node["jplDirectory"].as<std::string>() + DIR_SEPARATOR_CH + certPath_));
     278          10 :     dht::crypto::TrustList trust;
     279          10 :     trust.add(*cert_);
     280          10 :     jplPath_ = node["jplDirectory"].as<std::string>() + DIR_SEPARATOR_CH + name_ + ".jpl";
     281          10 :     installationPath_ = fileutils::get_data_dir() / "plugins" / name_;
     282          10 :     mediaHandlers_ = node["mediaHandlers"].as<std::vector<std::string>>();
     283          10 :     chatHandlers_ = node["chatHandlers"].as<std::vector<std::string>>();
     284          10 :     pluginNotFoundPath_ = node["jplDirectory"].as<std::string>() + DIR_SEPARATOR_CH + "fakePlugin.jpl";
     285          10 :     pluginCertNotFound_ = node["jplDirectory"].as<std::string>() + DIR_SEPARATOR_CH + node["pluginCertNotFound"].as<std::string>() + ".jpl";
     286          10 :     pluginNotSign_ = node["jplDirectory"].as<std::string>() + DIR_SEPARATOR_CH + node["pluginNotSign"].as<std::string>() + ".jpl";
     287          10 :     pluginFileNotSign_ = node["jplDirectory"].as<std::string>() + DIR_SEPARATOR_CH + node["pluginFileNotSign"].as<std::string>() + ".jpl";
     288          10 :     pluginManifestChanged_ = node["jplDirectory"].as<std::string>() + DIR_SEPARATOR_CH + node["pluginManifestChanged"].as<std::string>() + ".jpl";
     289          10 :     pluginNotSignByIssuer_ = node["jplDirectory"].as<std::string>() + DIR_SEPARATOR_CH + node["pluginNotSignByIssuer"].as<std::string>() + ".jpl";
     290          10 : }
     291             : 
     292             : void
     293          10 : PluginsTest::tearDown()
     294             : {
     295          10 :     libjami::unregisterSignalHandlers();
     296          30 :     wait_for_removal_of({aliceData.accountId_, bobData.accountId_});
     297          10 : }
     298             : 
     299             : void
     300           1 : PluginsTest::testEnable()
     301             : {
     302           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     303           1 :     CPPUNIT_ASSERT(Manager::instance().pluginPreferences.getPluginsEnabled());
     304           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(false);
     305           1 :     CPPUNIT_ASSERT(!Manager::instance().pluginPreferences.getPluginsEnabled());
     306           1 : }
     307             : 
     308             : void
     309           1 : PluginsTest::testSignatureVerification()
     310             : {
     311             :     // Test valid case
     312           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     313           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().checkPluginSignatureValidity(jplPath_, cert_.get()));
     314           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().checkPluginSignatureFile(jplPath_));
     315           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().checkPluginSignature(jplPath_, cert_.get()));
     316             : 
     317           1 :     std::string pluginNotFoundPath = "fakePlugin.jpl";
     318             :     // Test with a plugin that does not exist
     319           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().checkPluginSignatureFile(pluginNotFoundPath_));
     320           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().checkPluginSignature(pluginNotFoundPath_, cert_.get()));
     321             :     // Test with a plugin that does not have a signature
     322           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().checkPluginSignatureFile(pluginNotSign_));
     323             :     // Test with a plugin that does not have a file not signed
     324           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().checkPluginSignatureFile(pluginFileNotSign_));
     325           1 :     auto notCertSign = std::make_unique<dht::crypto::Certificate>();
     326             :     // Test with wrong certificate
     327           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().checkPluginSignatureValidity(jplPath_, notCertSign.get()));
     328             :     // Test with wrong signature
     329           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().checkPluginSignatureValidity(pluginManifestChanged_, cert_.get()));
     330             : 
     331           1 : }
     332             : 
     333             : void
     334           1 : PluginsTest::testCertificateVerification()
     335             : {
     336             : 
     337           1 :     std::string pluginNotFoundPath = "fakePlugin.jpl";
     338           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     339           1 :     auto pluginCert = PluginUtils::readPluginCertificateFromArchive(jplPath_);
     340           1 :     Manager::instance().getJamiPluginManager().addPluginAuthority(*pluginCert);
     341           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().checkPluginCertificate(jplPath_, true)->toString() == pluginCert->toString());
     342           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().checkPluginCertificate(jplPath_, false)->toString() == pluginCert->toString());
     343             :     // create a plugin with not the same certificate
     344             : 
     345           1 :     auto pluginCertNotSignByIssuer = PluginUtils::readPluginCertificateFromArchive(pluginNotSignByIssuer_);
     346           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().checkPluginCertificate(pluginNotSignByIssuer_, true)->toString() == pluginCertNotSignByIssuer->toString());
     347             :     // Test with a plugin that does not exist
     348           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().checkPluginCertificate(pluginNotFoundPath, false));
     349             :     // Test with a plugin that does not have a certificate
     350           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().checkPluginCertificate(pluginCertNotFound_, false));
     351           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().checkPluginCertificate(pluginNotSignByIssuer_, false));
     352           1 : }
     353             : 
     354             : void
     355           1 : PluginsTest::testLoad()
     356             : {
     357           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     358           1 :     auto loadedPlugins = Manager::instance().getJamiPluginManager().getLoadedPlugins();
     359           1 :     CPPUNIT_ASSERT(loadedPlugins.empty());
     360           1 :     Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true);
     361           1 :     Manager::instance().getJamiPluginManager().loadPlugins();
     362           1 :     loadedPlugins = Manager::instance().getJamiPluginManager().getLoadedPlugins();
     363           1 :     CPPUNIT_ASSERT(!loadedPlugins.empty());
     364           1 : }
     365             : 
     366             : void
     367           1 : PluginsTest::testInstallAndLoad()
     368             : {
     369           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     370             : 
     371           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true));
     372           1 :     auto installedPlugins = Manager::instance().getJamiPluginManager().getInstalledPlugins();
     373           1 :     CPPUNIT_ASSERT(!installedPlugins.empty());
     374           1 :     CPPUNIT_ASSERT(std::find(installedPlugins.begin(),
     375             :                              installedPlugins.end(),
     376             :                              installationPath_)
     377             :                    != installedPlugins.end());
     378             : 
     379           1 :     auto loadedPlugins = Manager::instance().getJamiPluginManager().getLoadedPlugins();
     380           1 :     CPPUNIT_ASSERT(!loadedPlugins.empty());
     381           1 :     CPPUNIT_ASSERT(std::find(loadedPlugins.begin(),
     382             :                              loadedPlugins.end(),
     383             :                              installationPath_)
     384             :                    != loadedPlugins.end());
     385             : 
     386           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().unloadPlugin(installationPath_));
     387           1 :     loadedPlugins = Manager::instance().getJamiPluginManager().getLoadedPlugins();
     388           1 :     CPPUNIT_ASSERT(std::find(loadedPlugins.begin(),
     389             :                              loadedPlugins.end(),
     390             :                              installationPath_)
     391             :                    == loadedPlugins.end());
     392             : 
     393           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_));
     394           1 :     installedPlugins = Manager::instance().getJamiPluginManager().getInstalledPlugins();
     395           1 :     CPPUNIT_ASSERT(std::find(installedPlugins.begin(),
     396             :                              installedPlugins.end(),
     397             :                              installationPath_)
     398             :                    == installedPlugins.end());
     399             : 
     400           1 : }
     401             : 
     402             : void
     403           1 : PluginsTest::testHandlers()
     404             : {
     405           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     406             : 
     407           1 :     Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true);
     408             : 
     409           1 :     auto mediaHandlers = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlers();
     410           1 :     auto chatHandlers = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlers();
     411             : 
     412           1 :     auto handlerLoaded = mediaHandlers_.size() + chatHandlers_.size(); // number of handlers expected
     413           3 :     for (auto handler : mediaHandlers)
     414             :     {
     415           2 :         auto details = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlerDetails(handler);
     416             :         // check details expected for the test plugin
     417           2 :         if(std::find(mediaHandlers_.begin(),
     418             :                         mediaHandlers_.end(),
     419           4 :                         details["name"])
     420           6 :                    != mediaHandlers_.end()) {
     421           2 :             handlerLoaded--;
     422             :         }
     423           2 :     }
     424           2 :     for (auto handler : chatHandlers)
     425             :     {
     426           1 :         auto details = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlerDetails(handler);
     427             :         // check details expected for the test plugin
     428           1 :         if(std::find(chatHandlers_.begin(),
     429             :                         chatHandlers_.end(),
     430           2 :                         details["name"])
     431           3 :                    != chatHandlers_.end()) {
     432           1 :             handlerLoaded--;
     433             :         }
     434           1 :     }
     435             : 
     436           1 :     CPPUNIT_ASSERT(!handlerLoaded); // All expected handlers were found
     437           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_));
     438           1 : }
     439             : 
     440             : void
     441           1 : PluginsTest::testDetailsAndPreferences()
     442             : {
     443           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     444           1 :     Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true);
     445             :     // Unload now to avoid reloads when changing the preferences
     446           1 :     Manager::instance().getJamiPluginManager().unloadPlugin(installationPath_);
     447             : 
     448             :     // Details
     449           1 :     auto details = Manager::instance().getJamiPluginManager().getPluginDetails(installationPath_);
     450           1 :     CPPUNIT_ASSERT(details["name"] == name_);
     451             : 
     452             :     // Get-set-reset - no account
     453           2 :     auto preferences = Manager::instance().getJamiPluginManager().getPluginPreferences(installationPath_, "");
     454           1 :     CPPUNIT_ASSERT(!preferences.empty());
     455           2 :     auto preferencesValuesOrig = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, "");
     456             : 
     457           1 :     std::string preferenceNewValue = aliceData.accountId_;
     458           2 :     auto key = preferences[0]["key"];
     459           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().setPluginPreference(installationPath_, "", key, preferenceNewValue));
     460             : 
     461             :     // Test global preference change
     462           2 :     auto preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, "");
     463           1 :     CPPUNIT_ASSERT(preferencesValuesOrig[key] != preferencesValuesNew[key]);
     464           1 :     CPPUNIT_ASSERT(preferencesValuesNew[key] == preferenceNewValue);
     465             : 
     466             :     // Test global preference change in an account
     467           1 :     preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_);
     468           1 :     CPPUNIT_ASSERT(preferencesValuesOrig[key] != preferencesValuesNew[key]);
     469           1 :     CPPUNIT_ASSERT(preferencesValuesNew[key] == preferenceNewValue);
     470             : 
     471             :     // Test reset global preference change
     472           1 :     Manager::instance().getJamiPluginManager().resetPluginPreferencesValuesMap(installationPath_, "");
     473           1 :     preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, "");
     474           1 :     CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesNew[key]);
     475           1 :     CPPUNIT_ASSERT(preferencesValuesNew[key] != preferenceNewValue);
     476             : 
     477             :     // Get-set-reset - alice account
     478           1 :     preferences = Manager::instance().getJamiPluginManager().getPluginPreferences(installationPath_, aliceData.accountId_);
     479           1 :     CPPUNIT_ASSERT(!preferences.empty());
     480           1 :     preferencesValuesOrig = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_);
     481           1 :     auto preferencesValuesBobOrig = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, bobData.accountId_);
     482             : 
     483           1 :     key = preferences[0]["key"];
     484           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().setPluginPreference(installationPath_, aliceData.accountId_, key, preferenceNewValue));
     485             : 
     486             :     // Test account preference change
     487           1 :     preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_);
     488           1 :     auto preferencesValuesBobNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, bobData.accountId_);
     489           1 :     CPPUNIT_ASSERT(preferencesValuesBobNew[key] == preferencesValuesBobOrig[key]);
     490           1 :     CPPUNIT_ASSERT(preferencesValuesOrig[key] != preferencesValuesNew[key]);
     491           1 :     CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesBobOrig[key]);
     492           1 :     CPPUNIT_ASSERT(preferencesValuesNew[key] != preferencesValuesBobOrig[key]);
     493           1 :     CPPUNIT_ASSERT(preferencesValuesNew[key] == preferenceNewValue);
     494             : 
     495             :     // Test account preference change with global preference reset
     496           1 :     Manager::instance().getJamiPluginManager().resetPluginPreferencesValuesMap(installationPath_, "");
     497           1 :     preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_);
     498           1 :     preferencesValuesBobNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, bobData.accountId_);
     499           1 :     CPPUNIT_ASSERT(preferencesValuesBobNew[key] == preferencesValuesBobOrig[key]);
     500           1 :     CPPUNIT_ASSERT(preferencesValuesOrig[key] != preferencesValuesNew[key]);
     501           1 :     CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesBobOrig[key]);
     502           1 :     CPPUNIT_ASSERT(preferencesValuesNew[key] != preferencesValuesBobOrig[key]);
     503           1 :     CPPUNIT_ASSERT(preferencesValuesNew[key] == preferenceNewValue);
     504             : 
     505             :     // Test account preference reset
     506           1 :     Manager::instance().getJamiPluginManager().resetPluginPreferencesValuesMap(installationPath_, aliceData.accountId_);
     507           1 :     preferencesValuesNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, aliceData.accountId_);
     508           1 :     preferencesValuesBobNew = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, bobData.accountId_);
     509           1 :     CPPUNIT_ASSERT(preferencesValuesBobNew[key] == preferencesValuesBobOrig[key]);
     510           1 :     CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesNew[key]);
     511           1 :     CPPUNIT_ASSERT(preferencesValuesOrig[key] == preferencesValuesBobOrig[key]);
     512           1 :     CPPUNIT_ASSERT(preferencesValuesNew[key] == preferencesValuesBobOrig[key]);
     513           1 :     CPPUNIT_ASSERT(preferencesValuesNew[key] != preferenceNewValue);
     514             : 
     515           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_));
     516           1 : }
     517             : 
     518             : void
     519           1 : PluginsTest::testTranslations()
     520             : {
     521           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     522           1 :     setenv("JAMI_LANG", "en", true);
     523           1 :     Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true);
     524             : 
     525           2 :     auto preferencesValuesEN = Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, "");
     526           1 :     auto detailsValuesEN = Manager::instance().getJamiPluginManager().getPluginDetails(installationPath_, true);
     527             : 
     528           1 :     CPPUNIT_ASSERT(!preferencesValuesEN.empty());
     529           1 :     CPPUNIT_ASSERT(!detailsValuesEN.empty());
     530             : 
     531           1 :     setenv("JAMI_LANG", "fr", true);
     532             : 
     533           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, "") != preferencesValuesEN);
     534           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().getPluginDetails(installationPath_, true) != detailsValuesEN);
     535             : 
     536           1 :     setenv("JAMI_LANG", "en", true);
     537             : 
     538           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().getPluginPreferencesValuesMap(installationPath_, "") == preferencesValuesEN);
     539           1 :     CPPUNIT_ASSERT(Manager::instance().getJamiPluginManager().getPluginDetails(installationPath_, true) == detailsValuesEN);
     540             : 
     541           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_));
     542           1 : }
     543             : 
     544             : bool
     545           3 : PluginsTest::waitForSignal(CallData& callData,
     546             :                             const std::string& expectedSignal,
     547             :                             const std::string& expectedEvent)
     548             : {
     549           3 :     const std::chrono::seconds TIME_OUT {30};
     550           3 :     std::unique_lock lock {callData.mtx_};
     551             : 
     552             :     // Combined signal + event (if any).
     553           3 :     std::string sigEvent(expectedSignal);
     554           3 :     if (not expectedEvent.empty())
     555           2 :         sigEvent += "::" + expectedEvent;
     556             : 
     557           3 :     JAMI_INFO("[%s] is waiting for [%s] signal/event", callData.alias_.c_str(), sigEvent.c_str());
     558             : 
     559           3 :     auto res = callData.cv_.wait_for(lock, TIME_OUT, [&] {
     560             :         // Search for the expected signal in list of received signals.
     561           5 :         bool pred = false;
     562          17 :         for (auto it = callData.signals_.begin(); it != callData.signals_.end(); it++) {
     563             :             // The predicate is true if the signal names match, and if the
     564             :             // expectedEvent is not empty, the events must also match.
     565          15 :             if (it->name_ == expectedSignal
     566          15 :                 and (expectedEvent.empty() or it->event_ == expectedEvent)) {
     567           3 :                 pred = true;
     568             :                 // Done with this signal.
     569           3 :                 callData.signals_.erase(it);
     570           3 :                 break;
     571             :             }
     572             :         }
     573             : 
     574           5 :         return pred;
     575             :     });
     576             : 
     577           3 :     if (not res) {
     578           0 :         JAMI_ERR("[%s] waiting for signal/event [%s] timed-out!",
     579             :                  callData.alias_.c_str(),
     580             :                  sigEvent.c_str());
     581             : 
     582           0 :         JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str());
     583             : 
     584           0 :         for (auto const& sig : callData.signals_) {
     585           0 :             JAMI_INFO() << "\tSignal [" << sig.name_
     586           0 :                         << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]";
     587             :         }
     588             :     }
     589             : 
     590           3 :     return res;
     591           3 : }
     592             : 
     593             : void
     594           1 : PluginsTest::testCall()
     595             : {
     596           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     597           1 :     Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true);
     598             : 
     599             :     // alice calls bob
     600             :     // for handler available, toggle - check status - untoggle - checkstatus
     601             :     // end call
     602             : 
     603           1 :     MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
     604           1 :     defaultAudio.label_ = "audio_0";
     605           1 :     defaultAudio.enabled_ = true;
     606             : 
     607           1 :     MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
     608           1 :     defaultVideo.label_ = "video_0";
     609           1 :     defaultVideo.enabled_ = true;
     610             : 
     611           1 :     std::vector<MediaAttribute> request;
     612           1 :     std::vector<MediaAttribute> answer;
     613             :     // First offer/answer
     614           1 :     request.emplace_back(MediaAttribute(defaultAudio));
     615           1 :     request.emplace_back(MediaAttribute(defaultVideo));
     616           1 :     answer.emplace_back(MediaAttribute(defaultAudio));
     617           1 :     answer.emplace_back(MediaAttribute(defaultVideo));
     618             : 
     619           1 :     JAMI_INFO("Start call between alice and Bob");
     620           1 :     aliceData.callId_ = libjami::placeCallWithMedia(aliceData.accountId_, bobData.userName_, MediaAttribute::mediaAttributesToMediaMaps(request));
     621           1 :     CPPUNIT_ASSERT(not aliceData.callId_.empty());
     622             : 
     623             :     auto aliceCall = std::static_pointer_cast<SIPCall>(
     624           1 :         Manager::instance().getCallFromCallID(aliceData.callId_));
     625           1 :     CPPUNIT_ASSERT(aliceCall);
     626             : 
     627           1 :     aliceData.callId_ = aliceCall->getCallId();
     628             : 
     629           1 :     JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer",
     630             :               aliceData.accountId_.c_str(),
     631             :               bobData.accountId_.c_str());
     632             : 
     633             :     // Wait for incoming call signal.
     634           1 :     CPPUNIT_ASSERT(waitForSignal(bobData, libjami::CallSignal::IncomingCallWithMedia::name));
     635             : 
     636             :     // Answer the call.
     637             :     {
     638           1 :         libjami::acceptWithMedia(bobData.accountId_, bobData.callId_, MediaAttribute::mediaAttributesToMediaMaps(answer));
     639             :     }
     640             : 
     641           1 :     CPPUNIT_ASSERT_EQUAL(true,
     642             :                          waitForSignal(bobData,
     643             :                                        libjami::CallSignal::StateChange::name,
     644             :                                        libjami::Call::StateEvent::CURRENT));
     645             : 
     646           1 :     JAMI_INFO("BOB answered the call [%s]", bobData.callId_.c_str());
     647             : 
     648           1 :     std::this_thread::sleep_for(std::chrono::seconds(3));
     649           1 :     auto mediaHandlers = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlers();
     650             : 
     651           3 :     for (auto handler : mediaHandlers)
     652             :     {
     653           2 :         auto details = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlerDetails(handler);
     654             :         // check details expected for the test plugin
     655           2 :         if(std::find(mediaHandlers_.begin(),
     656             :                         mediaHandlers_.end(),
     657           4 :                         details["name"])
     658           6 :                    != mediaHandlers_.end()) {
     659           2 :             Manager::instance().getJamiPluginManager().getCallServicesManager().toggleCallMediaHandler(handler, aliceData.callId_, true);
     660           2 :             auto statusMap = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlerStatus(aliceData.callId_);
     661           2 :             CPPUNIT_ASSERT(std::find(statusMap.begin(), statusMap.end(), handler) != statusMap.end());
     662             : 
     663           2 :             Manager::instance().getJamiPluginManager().getCallServicesManager().toggleCallMediaHandler(handler, aliceData.callId_, false);
     664           2 :             statusMap = Manager::instance().getJamiPluginManager().getCallServicesManager().getCallMediaHandlerStatus(aliceData.callId_);
     665           2 :             CPPUNIT_ASSERT(std::find(statusMap.begin(), statusMap.end(), handler) == statusMap.end());
     666           2 :         }
     667           2 :     }
     668             : 
     669           1 :     std::this_thread::sleep_for(std::chrono::seconds(3));
     670             :     // Bob hang-up.
     671           1 :     JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up");
     672           1 :     libjami::hangUp(bobData.accountId_, bobData.callId_);
     673             : 
     674           1 :     CPPUNIT_ASSERT_EQUAL(true,
     675             :                          waitForSignal(aliceData,
     676             :                                        libjami::CallSignal::StateChange::name,
     677             :                                        libjami::Call::StateEvent::HUNGUP));
     678             : 
     679           1 :     JAMI_INFO("Call terminated on both sides");
     680           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_));
     681           1 : }
     682             : 
     683             : void
     684           1 : PluginsTest::testMessage()
     685             : {
     686           1 :     Manager::instance().pluginPreferences.setPluginsEnabled(true);
     687           1 :     Manager::instance().getJamiPluginManager().installPlugin(jplPath_, true);
     688             : 
     689             :     // alice and bob chat
     690             :     // for handler available, toggle - check status - untoggle - checkstatus
     691             :     // end call
     692             : 
     693           1 :     std::mutex mtx;
     694           1 :     std::unique_lock lk {mtx};
     695           1 :     std::condition_variable cv;
     696           1 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
     697           1 :     auto messageBobReceived = 0, messageAliceReceived = 0;
     698           1 :     bool requestReceived = false;
     699           1 :     bool conversationReady = false;
     700           1 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
     701           4 :         [&](const std::string& accountId,
     702             :             const std::string& /* conversationId */,
     703             :             std::map<std::string, std::string> /*message*/) {
     704           4 :             if (accountId == bobData.accountId_) {
     705           1 :                 messageBobReceived += 1;
     706             :             } else {
     707           3 :                 messageAliceReceived += 1;
     708             :             }
     709           4 :             cv.notify_one();
     710           4 :         }));
     711           1 :     confHandlers.insert(
     712           2 :         libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
     713           1 :             [&](const std::string& /*accountId*/,
     714             :                 const std::string& /* conversationId */,
     715             :                 std::map<std::string, std::string> /*metadatas*/) {
     716           1 :                 requestReceived = true;
     717           1 :                 cv.notify_one();
     718           1 :             }));
     719           1 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
     720           2 :         [&](const std::string& accountId, const std::string& /* conversationId */) {
     721           2 :             if (accountId == bobData.accountId_) {
     722           1 :                 conversationReady = true;
     723           1 :                 cv.notify_one();
     724             :             }
     725           2 :         }));
     726           1 :     libjami::registerSignalHandlers(confHandlers);
     727             : 
     728           1 :     auto convId = libjami::startConversation(aliceData.accountId_);
     729             : 
     730           1 :     libjami::addConversationMember(aliceData.accountId_, convId, bobData.userName_);
     731           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
     732             : 
     733           1 :     libjami::acceptConversationRequest(bobData.accountId_, convId);
     734           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
     735             : 
     736             :     // Assert that repository exists
     737           2 :     auto repoPath = fileutils::get_data_dir() / bobData.accountId_
     738           4 :                     / "conversations" / convId;
     739           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     740             :     // Wait that alice sees Bob
     741           3 :     cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 2; });
     742             : 
     743           1 :     auto chatHandlers = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlers();
     744             : 
     745           2 :     for (auto handler : chatHandlers)
     746             :     {
     747           1 :         auto details = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlerDetails(handler);
     748             :         // check details expected for the test plugin
     749           1 :         if(std::find(chatHandlers_.begin(),
     750             :                         chatHandlers_.end(),
     751           2 :                         details["name"])
     752           3 :                    != chatHandlers_.end()) {
     753           1 :             Manager::instance().getJamiPluginManager().getChatServicesManager().toggleChatHandler(handler, aliceData.accountId_, convId, true);
     754           1 :             auto statusMap = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlerStatus(aliceData.accountId_, convId);
     755           1 :             CPPUNIT_ASSERT(std::find(statusMap.begin(), statusMap.end(), handler) != statusMap.end());
     756             : 
     757           1 :             libjami::sendMessage(aliceData.accountId_, convId, "hi"s, "");
     758           4 :             cv.wait_for(lk, 30s, [&]() { return messageBobReceived == 1; });
     759             : 
     760           1 :             Manager::instance().getJamiPluginManager().getChatServicesManager().toggleChatHandler(handler, aliceData.accountId_, convId, false);
     761           1 :             statusMap = Manager::instance().getJamiPluginManager().getChatServicesManager().getChatHandlerStatus(aliceData.accountId_, convId);
     762           1 :             CPPUNIT_ASSERT(std::find(statusMap.begin(), statusMap.end(), handler) == statusMap.end());
     763           1 :         }
     764           1 :     }
     765             : 
     766           1 :     CPPUNIT_ASSERT(!Manager::instance().getJamiPluginManager().uninstallPlugin(installationPath_));
     767           1 : }
     768             : 
     769             : } // namespace test
     770             : } // namespace jami
     771             : 
     772           1 : RING_TEST_RUNNER(jami::test::PluginsTest::name())

Generated by: LCOV version 1.14