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