Line data Source code
1 : /*
2 : * Copyright (C) 2021-2024 Savoir-faire Linux Inc.
3 : * Author: Sébastien Blin <sebastien.blin@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 <string>
25 :
26 : #include "manager.h"
27 : #include "client/videomanager.h"
28 : #include "jamidht/jamiaccount.h"
29 : #include "../../test_runner.h"
30 : #include "jami.h"
31 : #include "account_const.h"
32 : #include "common.h"
33 : #include "media_const.h"
34 : #include "media/video/sinkclient.h"
35 : #include "sip/sipcall.h"
36 : #include "sip/siptransport.h"
37 :
38 : #include <dhtnet/connectionmanager.h>
39 :
40 : using namespace libjami::Account;
41 : using namespace std::literals::chrono_literals;
42 :
43 : namespace jami {
44 : namespace test {
45 :
46 : struct CallData
47 : {
48 : std::string callId {};
49 : std::string state {};
50 : std::string device {};
51 : std::string streamId {};
52 : std::string hostState {};
53 : std::atomic_bool moderatorMuted {false};
54 : std::atomic_bool raisedHand {false};
55 : std::atomic_bool active {false};
56 : std::atomic_bool recording {false};
57 :
58 72 : void reset()
59 : {
60 72 : callId = "";
61 72 : state = "";
62 72 : device = "";
63 72 : streamId = "";
64 72 : hostState = "";
65 72 : moderatorMuted = false;
66 72 : active = false;
67 72 : raisedHand = false;
68 72 : recording = false;
69 72 : }
70 : };
71 :
72 : class ConferenceTest : public CppUnit::TestFixture
73 : {
74 : public:
75 23 : ConferenceTest()
76 23 : {
77 : // Init daemon
78 23 : libjami::init(
79 : libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
80 23 : if (not Manager::instance().initialized)
81 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
82 23 : }
83 46 : ~ConferenceTest() { libjami::fini(); }
84 2 : static std::string name() { return "Conference"; }
85 : void setUp();
86 : void tearDown();
87 :
88 : private:
89 : void testGetConference();
90 : void testModeratorMuteUpdateParticipantsInfos();
91 : void testUnauthorizedMute();
92 : void testAudioVideoMutedStates();
93 : void testMuteStatusAfterAdd();
94 : void testCreateParticipantsSinks();
95 : void testMuteStatusAfterRemove();
96 : void testActiveStatusAfterRemove();
97 : void testHandsUp();
98 : void testPeerLeaveConference();
99 : void testJoinCallFromOtherAccount();
100 : void testDevices();
101 : void testUnauthorizedSetActive();
102 : void testHangup();
103 : void testIsConferenceParticipant();
104 : void testAudioConferenceConfInfo();
105 : void testHostAddRmSecondVideo();
106 : void testParticipantAddRmSecondVideo();
107 : void testPropagateRecording();
108 : void testBrokenParticipantAudioAndVideo();
109 : void testBrokenParticipantAudioOnly();
110 : void testAudioOnlyLeaveLayout();
111 : void testRemoveConferenceInOneOne();
112 :
113 2 : CPPUNIT_TEST_SUITE(ConferenceTest);
114 1 : CPPUNIT_TEST(testGetConference);
115 1 : CPPUNIT_TEST(testModeratorMuteUpdateParticipantsInfos);
116 1 : CPPUNIT_TEST(testUnauthorizedMute);
117 1 : CPPUNIT_TEST(testAudioVideoMutedStates);
118 1 : CPPUNIT_TEST(testMuteStatusAfterAdd);
119 1 : CPPUNIT_TEST(testCreateParticipantsSinks);
120 1 : CPPUNIT_TEST(testMuteStatusAfterRemove);
121 1 : CPPUNIT_TEST(testActiveStatusAfterRemove);
122 1 : CPPUNIT_TEST(testHandsUp);
123 1 : CPPUNIT_TEST(testPeerLeaveConference);
124 1 : CPPUNIT_TEST(testJoinCallFromOtherAccount);
125 1 : CPPUNIT_TEST(testDevices);
126 1 : CPPUNIT_TEST(testUnauthorizedSetActive);
127 1 : CPPUNIT_TEST(testHangup);
128 1 : CPPUNIT_TEST(testIsConferenceParticipant);
129 1 : CPPUNIT_TEST(testAudioConferenceConfInfo);
130 1 : CPPUNIT_TEST(testHostAddRmSecondVideo);
131 1 : CPPUNIT_TEST(testParticipantAddRmSecondVideo);
132 1 : CPPUNIT_TEST(testPropagateRecording);
133 1 : CPPUNIT_TEST(testBrokenParticipantAudioAndVideo);
134 1 : CPPUNIT_TEST(testBrokenParticipantAudioOnly);
135 1 : CPPUNIT_TEST(testAudioOnlyLeaveLayout);
136 1 : CPPUNIT_TEST(testRemoveConferenceInOneOne);
137 4 : CPPUNIT_TEST_SUITE_END();
138 :
139 : // Common parts
140 : std::string aliceId;
141 : std::atomic_bool hostRecording {false};
142 : std::string bobId;
143 : std::string carlaId;
144 : std::string daviId;
145 : std::string confId {};
146 : std::mutex pInfosMtx_ {};
147 : std::vector<std::map<std::string, std::string>> pInfos_ {};
148 : bool confChanged {false};
149 :
150 : CallData bobCall {};
151 : CallData carlaCall {};
152 : CallData daviCall {};
153 :
154 : std::mutex mtx;
155 : std::unique_lock<std::mutex> lk {mtx};
156 : std::condition_variable cv;
157 :
158 : void registerSignalHandlers();
159 : void startConference(bool audioOnly = false, bool addDavi = false);
160 : void hangupConference();
161 : };
162 :
163 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConferenceTest, ConferenceTest::name());
164 :
165 : void
166 23 : ConferenceTest::setUp()
167 : {
168 46 : auto actors = load_actors_and_wait_for_announcement("actors/alice-bob-carla-davi.yml");
169 23 : aliceId = actors["alice"];
170 23 : bobId = actors["bob"];
171 23 : carlaId = actors["carla"];
172 23 : daviId = actors["davi"];
173 :
174 23 : bobCall.reset();
175 23 : carlaCall.reset();
176 23 : daviCall.reset();
177 23 : confId = {};
178 23 : confChanged = false;
179 23 : hostRecording = false;
180 23 : }
181 :
182 : void
183 23 : ConferenceTest::tearDown()
184 : {
185 115 : wait_for_removal_of({aliceId, bobId, carlaId, daviId});
186 23 : }
187 :
188 : void
189 23 : ConferenceTest::registerSignalHandlers()
190 : {
191 23 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
192 23 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
193 23 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
194 23 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
195 23 : auto aliceUri = aliceAccount->getUsername();
196 23 : auto bobUri = bobAccount->getUsername();
197 23 : auto carlaUri = carlaAccount->getUsername();
198 23 : auto daviUri = daviAccount->getUsername();
199 :
200 23 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
201 : // Watch signals
202 23 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::IncomingCallWithMedia>(
203 58 : [=](const std::string& accountId,
204 : const std::string& callId,
205 : const std::string&,
206 : const std::vector<std::map<std::string, std::string>>&) {
207 58 : if (accountId == bobId) {
208 23 : bobCall.callId = callId;
209 35 : } else if (accountId == carlaId) {
210 23 : carlaCall.callId = callId;
211 12 : } else if (accountId == daviId) {
212 12 : daviCall.callId = callId;
213 : }
214 58 : cv.notify_one();
215 58 : }));
216 23 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::StateChange>(
217 621 : [=](const std::string& accountId,
218 : const std::string& callId,
219 : const std::string& state,
220 : signed) {
221 621 : if (accountId == aliceId) {
222 281 : auto details = libjami::getCallDetails(aliceId, callId);
223 281 : if (details["PEER_NUMBER"].find(bobUri) != std::string::npos)
224 112 : bobCall.hostState = state;
225 169 : else if (details["PEER_NUMBER"].find(carlaUri) != std::string::npos)
226 109 : carlaCall.hostState = state;
227 60 : else if (details["PEER_NUMBER"].find(daviUri) != std::string::npos)
228 60 : daviCall.hostState = state;
229 621 : } else if (bobCall.callId == callId)
230 67 : bobCall.state = state;
231 273 : else if (carlaCall.callId == callId)
232 63 : carlaCall.state = state;
233 210 : else if (daviCall.callId == callId)
234 36 : daviCall.state = state;
235 621 : cv.notify_one();
236 621 : }));
237 23 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceCreated>(
238 23 : [=](const std::string&, const std::string& conferenceId) {
239 23 : confId = conferenceId;
240 23 : cv.notify_one();
241 23 : }));
242 23 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceRemoved>(
243 22 : [=](const std::string&, const std::string& conferenceId) {
244 22 : if (confId == conferenceId)
245 22 : confId = "";
246 22 : cv.notify_one();
247 22 : }));
248 23 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceChanged>(
249 89 : [=](const std::string&, const std::string& conferenceId, const std::string&) {
250 89 : if (confId == conferenceId)
251 68 : confChanged = true;
252 89 : cv.notify_one();
253 89 : }));
254 23 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::OnConferenceInfosUpdated>(
255 256 : [=](const std::string&,
256 : const std::vector<std::map<std::string, std::string>> participantsInfos) {
257 256 : std::lock_guard lock(pInfosMtx_);
258 256 : pInfos_ = participantsInfos;
259 1040 : for (const auto& infos : participantsInfos) {
260 784 : if (infos.at("uri").find(bobUri) != std::string::npos) {
261 254 : bobCall.active = infos.at("active") == "true";
262 254 : bobCall.recording = infos.at("recording") == "true";
263 254 : bobCall.moderatorMuted = infos.at("audioModeratorMuted") == "true";
264 254 : bobCall.raisedHand = infos.at("handRaised") == "true";
265 254 : bobCall.device = infos.at("device");
266 254 : bobCall.streamId = infos.at("sinkId");
267 530 : } else if (infos.at("uri").find(carlaUri) != std::string::npos) {
268 198 : carlaCall.active = infos.at("active") == "true";
269 198 : carlaCall.recording = infos.at("recording") == "true";
270 198 : carlaCall.moderatorMuted = infos.at("audioModeratorMuted") == "true";
271 198 : carlaCall.raisedHand = infos.at("handRaised") == "true";
272 198 : carlaCall.device = infos.at("device");
273 198 : carlaCall.streamId = infos.at("sinkId");
274 332 : } else if (infos.at("uri").find(daviUri) != std::string::npos) {
275 74 : daviCall.active = infos.at("active") == "true";
276 74 : daviCall.recording = infos.at("recording") == "true";
277 74 : daviCall.moderatorMuted = infos.at("audioModeratorMuted") == "true";
278 74 : daviCall.raisedHand = infos.at("handRaised") == "true";
279 74 : daviCall.device = infos.at("device");
280 74 : daviCall.streamId = infos.at("sinkId");
281 258 : } else if (infos.at("uri").find(aliceUri) != std::string::npos) {
282 175 : hostRecording = infos.at("recording") == "true";
283 : }
284 : }
285 256 : cv.notify_one();
286 256 : }));
287 :
288 23 : libjami::registerSignalHandlers(confHandlers);
289 23 : }
290 :
291 : void
292 21 : ConferenceTest::startConference(bool audioOnly, bool addDavi)
293 : {
294 21 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
295 21 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
296 21 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
297 21 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
298 21 : auto bobUri = bobAccount->getUsername();
299 21 : auto carlaUri = carlaAccount->getUsername();
300 21 : auto daviUri = daviAccount->getUsername();
301 :
302 21 : std::vector<std::map<std::string, std::string>> mediaList;
303 21 : if (audioOnly) {
304 : std::map<std::string, std::string> mediaAttribute
305 : = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
306 : libjami::Media::MediaAttributeValue::AUDIO},
307 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
308 : {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
309 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
310 21 : {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}};
311 3 : mediaList.emplace_back(mediaAttribute);
312 3 : }
313 :
314 21 : JAMI_INFO("Start call between Alice and Bob");
315 21 : auto call1 = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
316 147 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
317 21 : Manager::instance().answerCall(bobId, bobCall.callId);
318 64 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return bobCall.hostState == "CURRENT"; }));
319 :
320 21 : JAMI_INFO("Start call between Alice and Carla");
321 21 : auto call2 = libjami::placeCallWithMedia(aliceId, carlaUri, mediaList);
322 147 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !carlaCall.callId.empty(); }));
323 21 : Manager::instance().answerCall(carlaId, carlaCall.callId);
324 63 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return carlaCall.hostState == "CURRENT"; }));
325 :
326 21 : JAMI_INFO("Start conference");
327 21 : confChanged = false;
328 21 : Manager::instance().joinParticipant(aliceId, call1, aliceId, call2);
329 : // ConfChanged is the signal emitted when the 2 calls will be added to the conference
330 : // Also, wait that participants appears in conf info to get all good informations
331 117 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] {
332 : return !confId.empty() && confChanged && !carlaCall.device.empty()
333 : && !bobCall.device.empty();
334 : }));
335 :
336 21 : if (addDavi) {
337 7 : JAMI_INFO("Start call between Alice and Davi");
338 7 : auto call1 = libjami::placeCallWithMedia(aliceId, daviUri, mediaList);
339 57 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
340 7 : Manager::instance().answerCall(daviId, daviCall.callId);
341 21 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
342 7 : Manager::instance().addParticipant(aliceId, call1, aliceId, confId);
343 21 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
344 7 : }
345 21 : }
346 :
347 : void
348 19 : ConferenceTest::hangupConference()
349 : {
350 19 : JAMI_INFO("Stop conference");
351 19 : Manager::instance().hangupConference(aliceId, confId);
352 51 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] {
353 : return bobCall.state == "OVER" && carlaCall.state == "OVER" && confId.empty();
354 : }));
355 40 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] {
356 : return daviCall.callId.empty() ? true : daviCall.state == "OVER";
357 : }));
358 19 : }
359 :
360 : void
361 1 : ConferenceTest::testGetConference()
362 : {
363 1 : registerSignalHandlers();
364 :
365 1 : CPPUNIT_ASSERT(libjami::getConferenceList(aliceId).size() == 0);
366 :
367 1 : startConference();
368 :
369 1 : CPPUNIT_ASSERT(libjami::getConferenceList(aliceId).size() == 1);
370 1 : CPPUNIT_ASSERT(libjami::getConferenceList(aliceId)[0] == confId);
371 :
372 1 : hangupConference();
373 :
374 1 : CPPUNIT_ASSERT(libjami::getConferenceList(aliceId).size() == 0);
375 :
376 1 : libjami::unregisterSignalHandlers();
377 1 : }
378 :
379 : void
380 1 : ConferenceTest::testModeratorMuteUpdateParticipantsInfos()
381 : {
382 1 : registerSignalHandlers();
383 :
384 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
385 1 : auto bobUri = bobAccount->getUsername();
386 :
387 1 : startConference();
388 :
389 1 : JAMI_INFO("Play with mute from the moderator");
390 1 : libjami::muteStream(aliceId, confId, bobUri, bobCall.device, bobCall.streamId, true);
391 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return bobCall.moderatorMuted.load(); }));
392 :
393 1 : libjami::muteStream(aliceId, confId, bobUri, bobCall.device, bobCall.streamId, false);
394 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !bobCall.moderatorMuted.load(); }));
395 :
396 1 : hangupConference();
397 :
398 1 : libjami::unregisterSignalHandlers();
399 1 : }
400 :
401 : void
402 1 : ConferenceTest::testUnauthorizedMute()
403 : {
404 1 : registerSignalHandlers();
405 :
406 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
407 1 : auto bobUri = bobAccount->getUsername();
408 :
409 1 : startConference();
410 :
411 1 : JAMI_INFO("Play with mute from unauthorized");
412 1 : libjami::muteStream(carlaId, confId, bobUri, bobCall.device, bobCall.streamId, true);
413 5 : CPPUNIT_ASSERT(!cv.wait_for(lk, 15s, [&] { return bobCall.moderatorMuted.load(); }));
414 :
415 1 : hangupConference();
416 :
417 1 : libjami::unregisterSignalHandlers();
418 1 : }
419 :
420 : void
421 1 : ConferenceTest::testAudioVideoMutedStates()
422 : {
423 1 : registerSignalHandlers();
424 :
425 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
426 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
427 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
428 1 : auto bobUri = bobAccount->getUsername();
429 1 : auto carlaUri = carlaAccount->getUsername();
430 :
431 1 : JAMI_INFO("Start call between Alice and Bob");
432 1 : auto call1Id = libjami::placeCallWithMedia(aliceId, bobUri, {});
433 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
434 1 : Manager::instance().answerCall(bobId, bobCall.callId);
435 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return bobCall.hostState == "CURRENT"; }));
436 1 : auto call1 = aliceAccount->getCall(call1Id);
437 1 : call1->muteMedia(libjami::Media::MediaAttributeValue::AUDIO, true);
438 1 : call1->muteMedia(libjami::Media::MediaAttributeValue::VIDEO, true);
439 :
440 1 : JAMI_INFO("Start call between Alice and Carla");
441 1 : auto call2Id = libjami::placeCallWithMedia(aliceId, carlaUri, {});
442 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !carlaCall.callId.empty(); }));
443 1 : Manager::instance().answerCall(carlaId, carlaCall.callId);
444 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return carlaCall.hostState == "CURRENT"; }));
445 :
446 1 : auto call2 = aliceAccount->getCall(call2Id);
447 1 : call2->muteMedia(libjami::Media::MediaAttributeValue::AUDIO, true);
448 1 : call2->muteMedia(libjami::Media::MediaAttributeValue::VIDEO, true);
449 :
450 1 : JAMI_INFO("Start conference");
451 1 : Manager::instance().joinParticipant(aliceId, call1Id, aliceId, call2Id);
452 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !confId.empty(); }));
453 :
454 1 : auto conf = aliceAccount->getConference(confId);
455 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
456 : return conf->isMediaSourceMuted(jami::MediaType::MEDIA_AUDIO);
457 : }));
458 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
459 : return conf->isMediaSourceMuted(jami::MediaType::MEDIA_VIDEO);
460 : }));
461 :
462 1 : hangupConference();
463 :
464 1 : libjami::unregisterSignalHandlers();
465 1 : }
466 :
467 : void
468 1 : ConferenceTest::testMuteStatusAfterAdd()
469 : {
470 1 : registerSignalHandlers();
471 :
472 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
473 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
474 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
475 1 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
476 1 : auto bobUri = bobAccount->getUsername();
477 1 : auto carlaUri = carlaAccount->getUsername();
478 1 : auto daviUri = daviAccount->getUsername();
479 :
480 1 : std::vector<std::map<std::string, std::string>> mediaList;
481 : std::map<std::string, std::string> mediaAttribute
482 : = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
483 : libjami::Media::MediaAttributeValue::AUDIO},
484 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
485 : {libjami::Media::MediaAttributeKey::MUTED, TRUE_STR},
486 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
487 7 : {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}};
488 1 : mediaList.emplace_back(mediaAttribute);
489 :
490 1 : JAMI_INFO("Start call between Alice and Bob");
491 1 : auto call1 = libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
492 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !bobCall.callId.empty(); }));
493 1 : Manager::instance().answerCall(bobId, bobCall.callId);
494 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return bobCall.hostState == "CURRENT"; }));
495 :
496 1 : JAMI_INFO("Start call between Alice and Carla");
497 1 : auto call2 = libjami::placeCallWithMedia(aliceId, carlaUri, mediaList);
498 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !carlaCall.callId.empty(); }));
499 1 : Manager::instance().answerCall(carlaId, carlaCall.callId);
500 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return carlaCall.hostState == "CURRENT"; }));
501 :
502 1 : JAMI_INFO("Start conference");
503 1 : Manager::instance().joinParticipant(aliceId, call1, aliceId, call2);
504 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !confId.empty(); }));
505 :
506 1 : JAMI_INFO("Add Davi");
507 1 : auto call3 = libjami::placeCallWithMedia(aliceId, daviUri, {});
508 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
509 1 : Manager::instance().answerCall(daviId, daviCall.callId);
510 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
511 1 : Manager::instance().addParticipant(aliceId, call3, aliceId, confId);
512 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
513 :
514 1 : auto aliceConf = aliceAccount->getConference(confId);
515 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
516 : return aliceConf->isMediaSourceMuted(jami::MediaType::MEDIA_AUDIO);
517 : }));
518 :
519 1 : hangupConference();
520 :
521 1 : libjami::unregisterSignalHandlers();
522 1 : }
523 :
524 : void
525 1 : ConferenceTest::testCreateParticipantsSinks()
526 : {
527 1 : registerSignalHandlers();
528 :
529 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
530 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
531 1 : auto bobUri = bobAccount->getUsername();
532 1 : auto carlaUri = carlaAccount->getUsername();
533 :
534 1 : startConference();
535 :
536 1 : auto expectedNumberOfParticipants = 3u;
537 :
538 : // Check participants number
539 2 : CPPUNIT_ASSERT(
540 : cv.wait_for(lk, 30s, [&] { return pInfos_.size() == expectedNumberOfParticipants; }));
541 :
542 1 : if (not jami::getVideoDeviceMonitor().getDeviceList().empty()) {
543 0 : JAMI_INFO() << "Check sinks if video device available.";
544 0 : std::lock_guard lock(pInfosMtx_);
545 0 : for (auto& info : pInfos_) {
546 0 : auto uri = string_remove_suffix(info["uri"], '@');
547 0 : if (uri == bobUri) {
548 0 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
549 : return Manager::instance().getSinkClient(info["sinkId"]) != nullptr;
550 : }));
551 0 : } else if (uri == carlaUri) {
552 0 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
553 : return Manager::instance().getSinkClient(info["sinkId"]) != nullptr;
554 : }));
555 : }
556 : }
557 0 : } else {
558 1 : JAMI_INFO() << "Check sinks if no video device available.";
559 1 : std::lock_guard lock(pInfosMtx_);
560 4 : for (auto& info : pInfos_) {
561 3 : auto uri = string_remove_suffix(info["uri"], '@');
562 3 : if (uri == bobUri) {
563 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
564 : return Manager::instance().getSinkClient(info["sinkId"]) == nullptr;
565 : }));
566 2 : } else if (uri == carlaUri) {
567 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
568 : return Manager::instance().getSinkClient(info["sinkId"]) == nullptr;
569 : }));
570 : }
571 : }
572 1 : }
573 :
574 1 : hangupConference();
575 :
576 1 : libjami::unregisterSignalHandlers();
577 1 : }
578 :
579 : void
580 1 : ConferenceTest::testMuteStatusAfterRemove()
581 : {
582 1 : registerSignalHandlers();
583 :
584 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
585 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
586 1 : auto bobUri = bobAccount->getUsername();
587 1 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
588 1 : auto daviUri = daviAccount->getUsername();
589 :
590 1 : startConference(false, true);
591 :
592 1 : libjami::muteStream(aliceId, confId, daviUri, daviCall.device, daviCall.streamId, true);
593 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return daviCall.moderatorMuted.load(); }));
594 :
595 1 : Manager::instance().hangupCall(daviId, daviCall.callId);
596 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
597 1 : daviCall.reset();
598 :
599 1 : auto call2 = libjami::placeCallWithMedia(aliceId, daviUri, {});
600 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
601 1 : Manager::instance().answerCall(daviId, daviCall.callId);
602 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
603 1 : Manager::instance().addParticipant(aliceId, call2, aliceId, confId);
604 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
605 :
606 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !daviCall.moderatorMuted.load(); }));
607 :
608 1 : Manager::instance().hangupCall(daviId, daviCall.callId);
609 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
610 1 : hangupConference();
611 :
612 1 : libjami::unregisterSignalHandlers();
613 1 : }
614 :
615 : void
616 1 : ConferenceTest::testActiveStatusAfterRemove()
617 : {
618 1 : registerSignalHandlers();
619 :
620 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
621 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
622 1 : auto bobUri = bobAccount->getUsername();
623 1 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
624 1 : auto daviUri = daviAccount->getUsername();
625 :
626 1 : startConference(false, true);
627 :
628 1 : MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
629 1 : defaultAudio.label_ = "audio_0";
630 1 : defaultAudio.enabled_ = true;
631 :
632 1 : libjami::setActiveStream(aliceId, confId, daviUri, daviCall.device, daviCall.streamId, true);
633 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return daviCall.active.load(); }));
634 :
635 1 : Manager::instance().hangupCall(daviId, daviCall.callId);
636 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
637 1 : daviCall.reset();
638 :
639 1 : auto call2 = libjami::placeCallWithMedia(aliceId,
640 : daviUri,
641 3 : MediaAttribute::mediaAttributesToMediaMaps(
642 1 : {defaultAudio}));
643 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
644 1 : Manager::instance().answerCall(daviId, daviCall.callId);
645 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
646 1 : Manager::instance().addParticipant(aliceId, call2, aliceId, confId);
647 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
648 :
649 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !daviCall.active.load(); }));
650 :
651 1 : Manager::instance().hangupCall(daviId, daviCall.callId);
652 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
653 1 : hangupConference();
654 :
655 1 : libjami::unregisterSignalHandlers();
656 1 : }
657 :
658 : void
659 1 : ConferenceTest::testHandsUp()
660 : {
661 1 : registerSignalHandlers();
662 :
663 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
664 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
665 1 : auto bobUri = bobAccount->getUsername();
666 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
667 1 : auto carlaUri = carlaAccount->getUsername();
668 1 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
669 1 : auto daviUri = daviAccount->getUsername();
670 :
671 1 : startConference(false, true);
672 :
673 1 : JAMI_INFO("Play with raise hand");
674 1 : libjami::raiseHand(bobId, bobCall.callId, bobUri, bobCall.device, true);
675 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return bobCall.raisedHand.load(); }));
676 :
677 1 : libjami::raiseHand(bobId, bobCall.callId, bobUri, bobCall.device, false);
678 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !bobCall.raisedHand.load(); }));
679 :
680 : // Remove davi from moderators
681 1 : libjami::setModerator(aliceId, confId, daviUri, false);
682 :
683 : // Test to raise hand
684 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
685 1 : libjami::raiseHand(daviId, daviCall.callId, daviUri, daviCall.device, true);
686 11 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return daviCall.raisedHand.load(); }));
687 :
688 : // Test to raise hand for another one (should fail)
689 1 : libjami::raiseHand(bobId, bobCall.callId, carlaUri, carlaCall.device, true);
690 6 : CPPUNIT_ASSERT(!cv.wait_for(lk, 5s, [&] { return carlaCall.raisedHand.load(); }));
691 :
692 : // However, a moderator should be able to lower the hand (but not a non moderator)
693 1 : libjami::raiseHand(carlaId, carlaCall.callId, carlaUri, carlaCall.device, true);
694 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return carlaCall.raisedHand.load(); }));
695 :
696 1 : libjami::raiseHand(daviId, carlaCall.callId, carlaUri, carlaCall.device, false);
697 6 : CPPUNIT_ASSERT(!cv.wait_for(lk, 5s, [&] { return !carlaCall.raisedHand.load(); }));
698 :
699 1 : libjami::raiseHand(bobId, bobCall.callId, carlaUri, carlaCall.device, false);
700 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !carlaCall.raisedHand.load(); }));
701 :
702 1 : Manager::instance().hangupCall(daviId, daviCall.callId);
703 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
704 1 : daviCall.reset();
705 :
706 1 : auto call2 = libjami::placeCallWithMedia(aliceId, daviUri, {});
707 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
708 1 : Manager::instance().answerCall(daviId, daviCall.callId);
709 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
710 1 : Manager::instance().addParticipant(aliceId, call2, aliceId, confId);
711 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.device.empty(); }));
712 :
713 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !daviCall.raisedHand.load(); }));
714 :
715 1 : Manager::instance().hangupCall(daviId, daviCall.callId);
716 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.state == "OVER"; }));
717 1 : hangupConference();
718 :
719 1 : libjami::unregisterSignalHandlers();
720 1 : }
721 :
722 : void
723 1 : ConferenceTest::testPeerLeaveConference()
724 : {
725 1 : registerSignalHandlers();
726 :
727 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
728 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
729 1 : auto bobUri = bobAccount->getUsername();
730 :
731 1 : startConference();
732 1 : Manager::instance().hangupCall(bobId, bobCall.callId);
733 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return bobCall.state == "OVER" && confId.empty(); }));
734 :
735 1 : libjami::unregisterSignalHandlers();
736 1 : }
737 :
738 : void
739 1 : ConferenceTest::testJoinCallFromOtherAccount()
740 : {
741 1 : registerSignalHandlers();
742 :
743 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
744 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
745 1 : auto bobUri = bobAccount->getUsername();
746 1 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
747 1 : auto daviUri = daviAccount->getUsername();
748 :
749 1 : startConference();
750 :
751 1 : JAMI_INFO("Play with raise hand");
752 1 : libjami::raiseHand(aliceId, confId, bobUri, bobCall.device, true);
753 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return bobCall.raisedHand.load(); }));
754 :
755 1 : libjami::raiseHand(aliceId, confId, bobUri, bobCall.device, false);
756 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !bobCall.raisedHand.load(); }));
757 :
758 1 : JAMI_INFO("Start call between Alice and Davi");
759 1 : auto call1 = libjami::placeCallWithMedia(aliceId, daviUri, {});
760 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
761 1 : Manager::instance().answerCall(daviId, daviCall.callId);
762 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
763 1 : CPPUNIT_ASSERT(Manager::instance().addParticipant(daviId, daviCall.callId, aliceId, confId));
764 1 : hangupConference();
765 :
766 1 : libjami::unregisterSignalHandlers();
767 1 : }
768 :
769 : void
770 1 : ConferenceTest::testDevices()
771 : {
772 1 : registerSignalHandlers();
773 :
774 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
775 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
776 1 : auto bobDevice = std::string(bobAccount->currentDeviceId());
777 1 : auto carlaDevice = std::string(carlaAccount->currentDeviceId());
778 :
779 1 : startConference();
780 :
781 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
782 : return bobCall.device == bobDevice && carlaDevice == carlaCall.device;
783 : }));
784 :
785 1 : hangupConference();
786 :
787 1 : libjami::unregisterSignalHandlers();
788 1 : }
789 :
790 : void
791 1 : ConferenceTest::testUnauthorizedSetActive()
792 : {
793 1 : registerSignalHandlers();
794 :
795 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
796 1 : auto bobUri = bobAccount->getUsername();
797 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
798 1 : auto carlaUri = carlaAccount->getUsername();
799 :
800 1 : startConference();
801 :
802 1 : libjami::setActiveStream(carlaId, confId, bobUri, bobCall.device, bobCall.streamId, true);
803 5 : CPPUNIT_ASSERT(!cv.wait_for(lk, 15s, [&] { return bobCall.active.load(); }));
804 :
805 1 : hangupConference();
806 :
807 1 : libjami::unregisterSignalHandlers();
808 1 : }
809 :
810 : void
811 1 : ConferenceTest::testHangup()
812 : {
813 1 : registerSignalHandlers();
814 :
815 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
816 1 : auto bobUri = bobAccount->getUsername();
817 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
818 1 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
819 1 : auto daviUri = daviAccount->getUsername();
820 :
821 1 : startConference(false, true);
822 :
823 1 : libjami::hangupParticipant(carlaId, confId, daviUri, daviCall.device); // Unauthorized
824 6 : CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&] { return daviCall.state == "OVER"; }));
825 1 : libjami::hangupParticipant(aliceId, confId, daviUri, daviCall.device);
826 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return daviCall.state == "OVER"; }));
827 :
828 1 : hangupConference();
829 :
830 1 : libjami::unregisterSignalHandlers();
831 1 : }
832 :
833 : void
834 1 : ConferenceTest::testIsConferenceParticipant()
835 : {
836 1 : registerSignalHandlers();
837 :
838 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
839 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
840 1 : auto bobUri = bobAccount->getUsername();
841 :
842 1 : startConference();
843 :
844 : // is Conference participant should be true for Carla
845 1 : auto participants = aliceAccount->getConference(confId)->getParticipantList();
846 1 : CPPUNIT_ASSERT(participants.size() == 2);
847 1 : auto call1 = *participants.begin();
848 1 : auto call2 = *participants.rbegin();
849 1 : CPPUNIT_ASSERT(aliceAccount->getCall(call1)->isConferenceParticipant());
850 1 : CPPUNIT_ASSERT(aliceAccount->getCall(call2)->isConferenceParticipant());
851 :
852 : // hangup bob will stop the conference
853 1 : Manager::instance().hangupCall(aliceId, call1);
854 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return confId.empty(); }));
855 1 : CPPUNIT_ASSERT(!aliceAccount->getCall(call2)->isConferenceParticipant());
856 1 : Manager::instance().hangupCall(aliceId, call2);
857 :
858 1 : libjami::unregisterSignalHandlers();
859 1 : }
860 :
861 : void
862 1 : ConferenceTest::testHostAddRmSecondVideo()
863 : {
864 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
865 1 : auto aliceUri = aliceAccount->getUsername();
866 1 : registerSignalHandlers();
867 :
868 1 : startConference();
869 :
870 : // Alice adds new media
871 1 : pInfos_.clear();
872 : std::vector<std::map<std::string, std::string>> mediaList
873 : = {{{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
874 : libjami::Media::MediaAttributeValue::AUDIO},
875 : {libjami::Media::MediaAttributeKey::ENABLED, "true"},
876 : {libjami::Media::MediaAttributeKey::MUTED, "false"},
877 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
878 : {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}},
879 : {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
880 : libjami::Media::MediaAttributeValue::VIDEO},
881 : {libjami::Media::MediaAttributeKey::ENABLED, "true"},
882 : {libjami::Media::MediaAttributeKey::MUTED, "false"},
883 : {libjami::Media::MediaAttributeKey::SOURCE, "bar"},
884 : {libjami::Media::MediaAttributeKey::LABEL, "video_0"}},
885 : {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
886 : libjami::Media::MediaAttributeValue::VIDEO},
887 : {libjami::Media::MediaAttributeKey::ENABLED, "true"},
888 : {libjami::Media::MediaAttributeKey::MUTED, "false"},
889 : {libjami::Media::MediaAttributeKey::SOURCE, "foo"},
890 23 : {libjami::Media::MediaAttributeKey::LABEL, "video_1"}}};
891 1 : libjami::requestMediaChange(aliceId, confId, mediaList);
892 :
893 : // Check that alice has two videos attached to the conference
894 6 : auto aliceVideos = [&]() {
895 6 : int result = 0;
896 6 : std::lock_guard lock(pInfosMtx_);
897 27 : for (auto i = 0u; i < pInfos_.size(); ++i)
898 21 : if (pInfos_[i]["uri"].find(aliceUri) != std::string::npos)
899 6 : result += 1;
900 6 : return result;
901 6 : };
902 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceVideos() == 2; }));
903 :
904 : // Alice removes her second video
905 : {
906 1 : std::lock_guard lock(pInfosMtx_);
907 1 : pInfos_.clear();
908 1 : }
909 1 : mediaList.pop_back();
910 1 : libjami::requestMediaChange(aliceId, confId, mediaList);
911 :
912 : // Check that alice has ont video attached to the conference
913 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceVideos() == 1; }));
914 :
915 1 : hangupConference();
916 :
917 1 : libjami::unregisterSignalHandlers();
918 1 : }
919 :
920 : void
921 1 : ConferenceTest::testAudioConferenceConfInfo()
922 : {
923 1 : registerSignalHandlers();
924 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
925 1 : auto aliceUri = aliceAccount->getUsername();
926 :
927 1 : startConference(true);
928 :
929 : // Check that alice's video is muted
930 2 : auto aliceVideoMuted = [&]() {
931 2 : int result = 0;
932 2 : std::lock_guard lock(pInfosMtx_);
933 8 : for (auto i = 0u; i < pInfos_.size(); ++i) {
934 18 : if (pInfos_[i]["uri"].find(aliceUri) != std::string::npos
935 18 : && pInfos_[i]["videoMuted"] == "true" && pInfos_[i]["sinkId"] == "host_video_0")
936 1 : result += 1;
937 : }
938 2 : return result;
939 2 : };
940 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceVideoMuted() == 1; }));
941 :
942 1 : libjami::unregisterSignalHandlers();
943 1 : }
944 :
945 : void
946 1 : ConferenceTest::testParticipantAddRmSecondVideo()
947 : {
948 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
949 1 : auto bobUri = bobAccount->getUsername();
950 1 : registerSignalHandlers();
951 :
952 1 : startConference();
953 :
954 : // Bob adds new media
955 1 : pInfos_.clear();
956 : std::vector<std::map<std::string, std::string>> mediaList
957 : = {{{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
958 : libjami::Media::MediaAttributeValue::AUDIO},
959 : {libjami::Media::MediaAttributeKey::ENABLED, "true"},
960 : {libjami::Media::MediaAttributeKey::MUTED, "false"},
961 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
962 : {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}},
963 : {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
964 : libjami::Media::MediaAttributeValue::VIDEO},
965 : {libjami::Media::MediaAttributeKey::ENABLED, "true"},
966 : {libjami::Media::MediaAttributeKey::MUTED, "false"},
967 : {libjami::Media::MediaAttributeKey::SOURCE, "bar"},
968 : {libjami::Media::MediaAttributeKey::LABEL, "video_0"}},
969 : {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
970 : libjami::Media::MediaAttributeValue::VIDEO},
971 : {libjami::Media::MediaAttributeKey::ENABLED, "true"},
972 : {libjami::Media::MediaAttributeKey::MUTED, "false"},
973 : {libjami::Media::MediaAttributeKey::SOURCE, "foo"},
974 23 : {libjami::Media::MediaAttributeKey::LABEL, "video_1"}}};
975 1 : libjami::requestMediaChange(bobId, bobCall.callId, mediaList);
976 :
977 : // Check that bob has two videos attached to the conference
978 12 : auto bobVideos = [&]() {
979 12 : std::lock_guard lock(pInfosMtx_);
980 12 : int result = 0;
981 51 : for (auto i = 0u; i < pInfos_.size(); ++i)
982 39 : if (pInfos_[i]["uri"].find(bobUri) != std::string::npos)
983 15 : result += 1;
984 12 : return result;
985 12 : };
986 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return bobVideos() == 2; }));
987 :
988 : // Bob removes his second video
989 : {
990 1 : std::lock_guard lock(pInfosMtx_);
991 1 : pInfos_.clear();
992 1 : }
993 1 : mediaList.pop_back();
994 1 : libjami::requestMediaChange(bobId, bobCall.callId, mediaList);
995 :
996 : // Check that bob has ont video attached to the conference
997 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return bobVideos() == 1; }));
998 :
999 1 : hangupConference();
1000 :
1001 1 : libjami::unregisterSignalHandlers();
1002 1 : }
1003 :
1004 : void
1005 1 : ConferenceTest::testPropagateRecording()
1006 : {
1007 1 : registerSignalHandlers();
1008 :
1009 1 : startConference();
1010 :
1011 1 : JAMI_INFO("Play with recording state");
1012 1 : libjami::toggleRecording(bobId, bobCall.callId);
1013 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return bobCall.recording.load(); }));
1014 :
1015 1 : libjami::toggleRecording(bobId, bobCall.callId);
1016 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !bobCall.recording.load(); }));
1017 :
1018 1 : libjami::toggleRecording(aliceId, confId);
1019 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return hostRecording.load(); }));
1020 :
1021 1 : libjami::toggleRecording(aliceId, confId);
1022 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return !hostRecording.load(); }));
1023 :
1024 1 : hangupConference();
1025 :
1026 1 : libjami::unregisterSignalHandlers();
1027 1 : }
1028 :
1029 : void
1030 1 : ConferenceTest::testBrokenParticipantAudioAndVideo()
1031 : {
1032 1 : registerSignalHandlers();
1033 :
1034 : // Start conference with four participants
1035 1 : startConference(false, true);
1036 1 : auto expectedNumberOfParticipants = 4u;
1037 :
1038 : // Check participants number
1039 2 : CPPUNIT_ASSERT(
1040 : cv.wait_for(lk, 30s, [&] { return pInfos_.size() == expectedNumberOfParticipants; }));
1041 :
1042 : // Crash participant
1043 1 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
1044 1 : auto call2Crash = std::dynamic_pointer_cast<SIPCall>(daviAccount->getCall(daviCall.callId));
1045 1 : pjsip_transport_shutdown(call2Crash->getTransport()->get());
1046 :
1047 : // Check participants number
1048 : // It should have one less participant than in the conference start
1049 8 : CPPUNIT_ASSERT(
1050 : cv.wait_for(lk, 30s, [&] { return expectedNumberOfParticipants - 1 == pInfos_.size(); }));
1051 :
1052 1 : hangupConference();
1053 :
1054 1 : libjami::unregisterSignalHandlers();
1055 1 : }
1056 :
1057 : void
1058 1 : ConferenceTest::testBrokenParticipantAudioOnly()
1059 : {
1060 1 : registerSignalHandlers();
1061 :
1062 : // Start conference with four participants
1063 1 : startConference(true, true);
1064 1 : auto expectedNumberOfParticipants = 4u;
1065 :
1066 : // Check participants number
1067 2 : CPPUNIT_ASSERT(
1068 : cv.wait_for(lk, 30s, [&] { return pInfos_.size() == expectedNumberOfParticipants; }));
1069 :
1070 : // Crash participant
1071 1 : auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
1072 1 : auto call2Crash = std::dynamic_pointer_cast<SIPCall>(daviAccount->getCall(daviCall.callId));
1073 1 : pjsip_transport_shutdown(call2Crash->getTransport()->get());
1074 :
1075 : // Check participants number
1076 : // It should have one less participant than in the conference start
1077 7 : CPPUNIT_ASSERT(
1078 : cv.wait_for(lk, 30s, [&] { return expectedNumberOfParticipants - 1 == pInfos_.size(); }));
1079 :
1080 1 : hangupConference();
1081 1 : libjami::unregisterSignalHandlers();
1082 1 : }
1083 :
1084 : void
1085 1 : ConferenceTest::testAudioOnlyLeaveLayout()
1086 : {
1087 1 : registerSignalHandlers();
1088 :
1089 : // Start conference with four participants
1090 1 : startConference(true, true);
1091 1 : auto expectedNumberOfParticipants = 4u;
1092 :
1093 : // Check participants number
1094 2 : CPPUNIT_ASSERT(
1095 : cv.wait_for(lk, 30s, [&] { return pInfos_.size() == expectedNumberOfParticipants; }));
1096 :
1097 : // Carla Leave
1098 1 : Manager::instance().hangupCall(carlaId, carlaCall.callId);
1099 :
1100 : // Check participants number
1101 : // It should have one less participant than in the conference start
1102 4 : CPPUNIT_ASSERT(
1103 : cv.wait_for(lk, 30s, [&] { return expectedNumberOfParticipants - 1 == pInfos_.size(); }));
1104 :
1105 1 : hangupConference();
1106 1 : libjami::unregisterSignalHandlers();
1107 1 : }
1108 :
1109 : void
1110 1 : ConferenceTest::testRemoveConferenceInOneOne()
1111 : {
1112 1 : registerSignalHandlers();
1113 1 : startConference();
1114 : // Here it's 1:1 calls we merged, so we can close the conference
1115 1 : JAMI_INFO("Hangup Bob");
1116 1 : Manager::instance().hangupCall(bobId, bobCall.callId);
1117 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return confId.empty() && bobCall.state == "OVER"; }));
1118 1 : Manager::instance().hangupCall(carlaId, carlaCall.callId);
1119 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaCall.state == "OVER"; }));
1120 1 : libjami::unregisterSignalHandlers();
1121 1 : }
1122 :
1123 : } // namespace test
1124 : } // namespace jami
1125 :
1126 1 : RING_TEST_RUNNER(jami::test::ConferenceTest::name())
|