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())
|