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 <filesystem>
24 : #include <string>
25 :
26 : #include "../../test_runner.h"
27 : #include "account_const.h"
28 : #include "common.h"
29 : #include "conversation/conversationcommon.h"
30 : #include "manager.h"
31 : #include "media_const.h"
32 : #include "sip/sipcall.h"
33 :
34 : using namespace std::literals::chrono_literals;
35 :
36 : namespace jami {
37 : namespace test {
38 :
39 : struct ConvData
40 : {
41 : std::string id {};
42 : std::string callId {};
43 : std::string confId {};
44 : bool requestReceived {false};
45 : bool needsHost {false};
46 : bool conferenceChanged {false};
47 : bool conferenceRemoved {false};
48 : std::string hostState {};
49 : std::string state {};
50 : std::vector<libjami::SwarmMessage> messages {};
51 : };
52 :
53 : class ConversationCallTest : public CppUnit::TestFixture
54 : {
55 : public:
56 30 : ~ConversationCallTest() { libjami::fini(); }
57 2 : static std::string name() { return "ConversationCallTest"; }
58 : void setUp();
59 : void tearDown();
60 :
61 : std::string aliceId;
62 : std::string bobId;
63 : std::string bob2Id;
64 : std::string carlaId;
65 : ConvData aliceData_;
66 : ConvData bobData_;
67 : ConvData bob2Data_;
68 : ConvData carlaData_;
69 : std::vector<std::map<std::string, std::string>> pInfos_ {};
70 :
71 : std::mutex mtx;
72 : std::unique_lock<std::mutex> lk {mtx};
73 : std::condition_variable cv;
74 :
75 : private:
76 : void connectSignals();
77 : void enableCarla();
78 :
79 : void testActiveCalls();
80 : void testActiveCalls3Peers();
81 : void testRejoinCall();
82 : void testParticipantHangupConfNotRemoved();
83 : void testJoinFinishedCall();
84 : void testJoinFinishedCallForbidden();
85 : void testUsePreference();
86 : void testJoinWhileActiveCall();
87 : void testCallSelfIfDefaultHost();
88 : void testNeedsHost();
89 : void testAudioOnly();
90 : void testJoinAfterMuteHost();
91 : void testBusy();
92 : void testDecline();
93 : void testNoDevice();
94 :
95 2 : CPPUNIT_TEST_SUITE(ConversationCallTest);
96 1 : CPPUNIT_TEST(testActiveCalls);
97 1 : CPPUNIT_TEST(testActiveCalls3Peers);
98 1 : CPPUNIT_TEST(testRejoinCall);
99 1 : CPPUNIT_TEST(testParticipantHangupConfNotRemoved);
100 1 : CPPUNIT_TEST(testJoinFinishedCall);
101 1 : CPPUNIT_TEST(testJoinFinishedCallForbidden);
102 1 : CPPUNIT_TEST(testUsePreference);
103 1 : CPPUNIT_TEST(testJoinWhileActiveCall);
104 1 : CPPUNIT_TEST(testCallSelfIfDefaultHost);
105 1 : CPPUNIT_TEST(testNeedsHost);
106 1 : CPPUNIT_TEST(testAudioOnly);
107 1 : CPPUNIT_TEST(testJoinAfterMuteHost);
108 1 : CPPUNIT_TEST(testBusy);
109 1 : CPPUNIT_TEST(testDecline);
110 1 : CPPUNIT_TEST(testNoDevice);
111 4 : CPPUNIT_TEST_SUITE_END();
112 : };
113 :
114 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConversationCallTest, ConversationCallTest::name());
115 :
116 : void
117 15 : ConversationCallTest::setUp()
118 : {
119 : // Init daemon
120 15 : libjami::init(
121 : libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
122 15 : if (not Manager::instance().initialized)
123 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
124 :
125 15 : auto actors = load_actors("actors/alice-bob-carla.yml");
126 15 : aliceId = actors["alice"];
127 15 : bobId = actors["bob"];
128 15 : carlaId = actors["carla"];
129 15 : aliceData_ = {};
130 15 : bobData_ = {};
131 15 : bob2Data_ = {};
132 15 : carlaData_ = {};
133 :
134 15 : Manager::instance().sendRegister(carlaId, false);
135 45 : wait_for_announcement_of({aliceId, bobId});
136 15 : }
137 :
138 : void
139 15 : ConversationCallTest::tearDown()
140 : {
141 30 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
142 15 : std::remove(bobArchive.c_str());
143 :
144 15 : if (bob2Id.empty()) {
145 60 : wait_for_removal_of({aliceId, bobId, carlaId});
146 : } else {
147 0 : wait_for_removal_of({aliceId, bobId, carlaId, bob2Id});
148 : }
149 15 : }
150 :
151 : void
152 15 : ConversationCallTest::connectSignals()
153 : {
154 15 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
155 15 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
156 31 : [&](const std::string& accountId, const std::string& conversationId) {
157 31 : if (accountId == aliceId)
158 15 : aliceData_.id = conversationId;
159 16 : else if (accountId == bobId)
160 12 : bobData_.id = conversationId;
161 4 : else if (accountId == bob2Id)
162 0 : bob2Data_.id = conversationId;
163 4 : else if (accountId == carlaId)
164 4 : carlaData_.id = conversationId;
165 31 : cv.notify_one();
166 31 : }));
167 15 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
168 95 : [&](const std::string& accountId,
169 : const std::string& conversationId,
170 : const libjami::SwarmMessage& message) {
171 95 : if (accountId == aliceId && aliceData_.id == conversationId)
172 58 : aliceData_.messages.emplace_back(message);
173 95 : if (accountId == bobId && bobData_.id == conversationId)
174 25 : bobData_.messages.emplace_back(message);
175 95 : if (accountId == bob2Id && bob2Data_.id == conversationId)
176 0 : bob2Data_.messages.emplace_back(message);
177 95 : if (accountId == carlaId && carlaData_.id == conversationId)
178 12 : carlaData_.messages.emplace_back(message);
179 95 : cv.notify_one();
180 95 : }));
181 15 : confHandlers.insert(
182 30 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
183 16 : [&](const std::string& accountId,
184 : const std::string& /*conversationId*/,
185 : std::map<std::string, std::string> /*metadatas*/) {
186 16 : if (accountId == aliceId)
187 0 : aliceData_.requestReceived = true;
188 16 : if (accountId == bobId)
189 12 : bobData_.requestReceived = true;
190 16 : if (accountId == bob2Id)
191 0 : bob2Data_.requestReceived = true;
192 16 : if (accountId == carlaId)
193 4 : carlaData_.requestReceived = true;
194 16 : cv.notify_one();
195 16 : }));
196 15 : confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::NeedsHost>(
197 1 : [&](const std::string& accountId, const std::string& /*conversationId*/) {
198 1 : if (accountId == aliceId)
199 0 : aliceData_.needsHost = true;
200 1 : if (accountId == bobId)
201 1 : bobData_.needsHost = true;
202 1 : if (accountId == bob2Id)
203 0 : bob2Data_.needsHost = true;
204 1 : if (accountId == carlaId)
205 0 : carlaData_.needsHost = true;
206 1 : cv.notify_one();
207 1 : }));
208 15 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceChanged>(
209 15 : [&](const std::string& accountId, const std::string&, const std::string&) {
210 15 : if (accountId == aliceId)
211 15 : aliceData_.conferenceChanged = true;
212 15 : cv.notify_one();
213 15 : }));
214 15 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceCreated>(
215 13 : [&](const std::string& accountId, const std::string&, const std::string& confId) {
216 13 : if (accountId == aliceId)
217 13 : aliceData_.confId = confId;
218 0 : else if (accountId == bobId)
219 0 : bobData_.confId = confId;
220 13 : cv.notify_one();
221 13 : }));
222 15 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceRemoved>(
223 10 : [&](const std::string& accountId, const std::string&) {
224 10 : if (accountId == aliceId)
225 10 : aliceData_.conferenceRemoved = true;
226 10 : cv.notify_one();
227 10 : }));
228 15 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::StateChange>(
229 123 : [&](const std::string& accountId,
230 : const std::string& callId,
231 : const std::string& state,
232 : signed) {
233 123 : if (accountId == aliceId) {
234 68 : auto details = libjami::getCallDetails(aliceId, callId);
235 68 : if (details.find("PEER_NUMBER") != details.end()) {
236 68 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
237 68 : auto bobUri = bobAccount->getUsername();
238 68 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
239 68 : auto carlaUri = carlaAccount->getUsername();
240 :
241 68 : if (details["PEER_NUMBER"].find(bobUri) != std::string::npos)
242 53 : bobData_.hostState = state;
243 15 : else if (details["PEER_NUMBER"].find(carlaUri) != std::string::npos)
244 13 : carlaData_.hostState = state;
245 68 : }
246 123 : } else if (accountId == bobId) {
247 47 : bobData_.callId = callId;
248 47 : bobData_.state = state;
249 8 : } else if (accountId == carlaId) {
250 8 : carlaData_.state = state;
251 : }
252 123 : cv.notify_one();
253 123 : }));
254 15 : confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::OnConferenceInfosUpdated>(
255 12 : [=](const std::string&,
256 : const std::vector<std::map<std::string, std::string>> participantsInfos) {
257 12 : pInfos_ = participantsInfos;
258 12 : cv.notify_one();
259 12 : }));
260 :
261 15 : libjami::registerSignalHandlers(confHandlers);
262 15 : }
263 :
264 : void
265 7 : ConversationCallTest::enableCarla()
266 : {
267 7 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
268 : // Enable carla
269 7 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
270 7 : bool carlaConnected = false;
271 7 : confHandlers.insert(
272 14 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
273 21 : [&](const std::string&, const std::map<std::string, std::string>&) {
274 21 : auto details = carlaAccount->getVolatileAccountDetails();
275 : auto deviceAnnounced
276 42 : = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
277 21 : if (deviceAnnounced == "true") {
278 7 : carlaConnected = true;
279 7 : cv.notify_one();
280 : }
281 21 : }));
282 7 : libjami::registerSignalHandlers(confHandlers);
283 :
284 7 : Manager::instance().sendRegister(carlaId, true);
285 21 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaConnected; }));
286 7 : confHandlers.clear();
287 7 : libjami::unregisterSignalHandlers();
288 7 : }
289 :
290 : void
291 1 : ConversationCallTest::testActiveCalls()
292 : {
293 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
294 1 : auto bobUri = bobAccount->getUsername();
295 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
296 1 : auto carlaUri = carlaAccount->getUsername();
297 1 : connectSignals();
298 :
299 : // Start conversation
300 1 : libjami::startConversation(aliceId);
301 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
302 :
303 : // get active calls = 0
304 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
305 :
306 : // start call
307 1 : aliceData_.messages.clear();
308 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
309 : // should get message
310 3 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.conferenceChanged && !aliceData_.messages.empty(); });
311 1 : CPPUNIT_ASSERT(aliceData_.messages.rbegin()->type == "application/call-history+json");
312 :
313 : // get active calls = 1
314 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 1);
315 :
316 : // hangup
317 1 : aliceData_.messages.clear();
318 1 : Manager::instance().hangupConference(aliceId, aliceData_.confId);
319 :
320 : // should get message
321 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData_.messages.empty(); }));
322 1 : CPPUNIT_ASSERT(aliceData_.messages.rbegin()->body.find("duration") != aliceData_.messages.rbegin()->body.end());
323 :
324 : // get active calls = 0
325 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
326 1 : }
327 :
328 : void
329 1 : ConversationCallTest::testActiveCalls3Peers()
330 : {
331 1 : enableCarla();
332 1 : connectSignals();
333 :
334 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
335 1 : auto aliceUri = aliceAccount->getUsername();
336 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
337 1 : auto bobUri = bobAccount->getUsername();
338 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
339 1 : auto carlaUri = carlaAccount->getUsername();
340 : // Start conversation
341 1 : libjami::startConversation(aliceId);
342 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
343 :
344 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
345 1 : libjami::addConversationMember(aliceId, aliceData_.id, carlaUri);
346 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
347 : return bobData_.requestReceived && carlaData_.requestReceived;
348 : }));
349 :
350 1 : aliceData_.messages.clear();
351 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
352 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
353 : return !bobData_.id.empty() && !aliceData_.messages.empty();
354 : }));
355 1 : aliceData_.messages.clear();
356 1 : libjami::acceptConversationRequest(carlaId, aliceData_.id);
357 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
358 : return !carlaData_.id.empty() && !aliceData_.messages.empty();
359 : }));
360 :
361 : // start call
362 1 : aliceData_.messages.clear();
363 1 : bobData_.messages.clear();
364 1 : carlaData_.messages.clear();
365 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
366 11 : auto lastCommitIsCall = [&](const auto& data) {
367 22 : return !data.messages.empty()
368 11 : && data.messages.rbegin()->body.at("type") == "application/call-history+json";
369 : };
370 : // should get message
371 1 : cv.wait_for(lk, 30s, [&]() {
372 9 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
373 9 : && lastCommitIsCall(carlaData_);
374 : });
375 2 : auto confId = bobData_.messages.rbegin()->body.at("confId");
376 : auto destination = fmt::format("rdv:{}/{}/{}/{}",
377 1 : bobData_.id,
378 2 : bobData_.messages.rbegin()->body.at("uri"),
379 2 : bobData_.messages.rbegin()->body.at("device"),
380 1 : confId);
381 :
382 1 : aliceData_.conferenceChanged = false;
383 1 : libjami::placeCallWithMedia(bobId, destination, {});
384 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
385 : return aliceData_.conferenceChanged && bobData_.hostState == "CURRENT";
386 : }));
387 1 : aliceData_.conferenceChanged = false;
388 : // get 2 other participants
389 1 : libjami::placeCallWithMedia(carlaId, destination, {});
390 11 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
391 : return aliceData_.conferenceChanged && carlaData_.hostState == "CURRENT" && libjami::getParticipantList(aliceId, confId).size() == 2;
392 : }));
393 :
394 : // get active calls = 1
395 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(bobId, bobData_.id).size() == 1);
396 :
397 : // hangup
398 1 : aliceData_.messages.clear();
399 1 : bobData_.messages.clear();
400 1 : carlaData_.messages.clear();
401 1 : Manager::instance().hangupConference(aliceId, confId);
402 :
403 : // should get message
404 1 : cv.wait_for(lk, 30s, [&]() {
405 4 : return !aliceData_.messages.empty() && !bobData_.messages.empty()
406 4 : && !carlaData_.messages.empty();
407 : });
408 1 : CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
409 1 : CPPUNIT_ASSERT(bobData_.messages[0].body.find("duration") != bobData_.messages[0].body.end());
410 1 : CPPUNIT_ASSERT(carlaData_.messages[0].body.find("duration") != carlaData_.messages[0].body.end());
411 :
412 : // get active calls = 0
413 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
414 1 : }
415 :
416 : void
417 1 : ConversationCallTest::testRejoinCall()
418 : {
419 1 : enableCarla();
420 1 : connectSignals();
421 :
422 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
423 1 : auto aliceUri = aliceAccount->getUsername();
424 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
425 1 : auto bobUri = bobAccount->getUsername();
426 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
427 1 : auto carlaUri = carlaAccount->getUsername();
428 : // Start conversation
429 1 : libjami::startConversation(aliceId);
430 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
431 :
432 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
433 1 : libjami::addConversationMember(aliceId, aliceData_.id, carlaUri);
434 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
435 : return bobData_.requestReceived && carlaData_.requestReceived;
436 : }));
437 :
438 1 : aliceData_.messages.clear();
439 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
440 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
441 : return !bobData_.id.empty() && !aliceData_.messages.empty();
442 : }));
443 1 : aliceData_.messages.clear();
444 1 : libjami::acceptConversationRequest(carlaId, aliceData_.id);
445 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
446 : return !carlaData_.id.empty() && !aliceData_.messages.empty();
447 : }));
448 :
449 : // start call
450 1 : aliceData_.messages.clear();
451 1 : bobData_.messages.clear();
452 1 : carlaData_.messages.clear();
453 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
454 10 : auto lastCommitIsCall = [&](const auto& data) {
455 20 : return !data.messages.empty()
456 10 : && data.messages.rbegin()->body.at("type") == "application/call-history+json";
457 : };
458 : // should get message
459 1 : cv.wait_for(lk, 30s, [&]() {
460 9 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
461 9 : && lastCommitIsCall(carlaData_);
462 : });
463 :
464 2 : auto confId = bobData_.messages.rbegin()->body.at("confId");
465 : auto destination = fmt::format("rdv:{}/{}/{}/{}",
466 1 : bobData_.id,
467 2 : bobData_.messages.rbegin()->body.at("uri"),
468 2 : bobData_.messages.rbegin()->body.at("device"),
469 1 : confId);
470 :
471 1 : aliceData_.conferenceChanged = false;
472 1 : libjami::placeCallWithMedia(bobId, destination, {});
473 1 : cv.wait_for(lk, 30s, [&]() {
474 8 : return aliceData_.conferenceChanged && bobData_.hostState == "CURRENT" && bobData_.state == "CURRENT";
475 : });
476 1 : aliceData_.conferenceChanged = false;
477 1 : libjami::placeCallWithMedia(carlaId, destination, {});
478 1 : cv.wait_for(lk, 30s, [&]() {
479 9 : return aliceData_.conferenceChanged && carlaData_.hostState == "CURRENT" && carlaData_.state == "CURRENT";
480 : });
481 :
482 1 : CPPUNIT_ASSERT(libjami::getParticipantList(aliceId, confId).size() == 2);
483 :
484 : // hangup 1 participant and rejoin
485 1 : aliceData_.messages.clear();
486 1 : bobData_.messages.clear();
487 1 : aliceData_.conferenceChanged = false;
488 1 : Manager::instance().hangupCall(bobId, bobData_.callId);
489 1 : cv.wait_for(lk, 30s, [&]() {
490 5 : return aliceData_.conferenceChanged && bobData_.hostState == "OVER";
491 : });
492 :
493 1 : CPPUNIT_ASSERT(libjami::getParticipantList(aliceId, confId).size() == 1);
494 :
495 1 : aliceData_.conferenceChanged = false;
496 1 : libjami::placeCallWithMedia(bobId, destination, {});
497 1 : cv.wait_for(lk, 30s, [&]() {
498 11 : return aliceData_.conferenceChanged && bobData_.hostState == "CURRENT";
499 : });
500 :
501 1 : CPPUNIT_ASSERT(libjami::getParticipantList(aliceId, confId).size() == 2);
502 1 : CPPUNIT_ASSERT(aliceData_.messages.empty());
503 1 : CPPUNIT_ASSERT(bobData_.messages.empty());
504 :
505 : // hangup
506 1 : aliceData_.messages.clear();
507 1 : bobData_.messages.clear();
508 1 : carlaData_.messages.clear();
509 1 : Manager::instance().hangupConference(aliceId, confId);
510 :
511 : // should get message
512 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
513 : return !aliceData_.messages.empty() && !bobData_.messages.empty()
514 : && !carlaData_.messages.empty();
515 : }));
516 1 : CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
517 1 : CPPUNIT_ASSERT(bobData_.messages[0].body.find("duration") != bobData_.messages[0].body.end());
518 1 : CPPUNIT_ASSERT(carlaData_.messages[0].body.find("duration") != carlaData_.messages[0].body.end());
519 :
520 : // get active calls = 0
521 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
522 1 : }
523 :
524 : void
525 1 : ConversationCallTest::testParticipantHangupConfNotRemoved()
526 : {
527 1 : connectSignals();
528 :
529 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
530 1 : auto aliceUri = aliceAccount->getUsername();
531 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
532 1 : auto bobUri = bobAccount->getUsername();
533 : // Start conversation
534 1 : libjami::startConversation(aliceId);
535 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
536 :
537 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
538 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return bobData_.requestReceived; }));
539 :
540 1 : aliceData_.messages.clear();
541 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
542 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
543 : return !bobData_.id.empty() && !aliceData_.messages.empty();
544 : }));
545 :
546 : // start call
547 1 : aliceData_.messages.clear();
548 1 : bobData_.messages.clear();
549 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
550 5 : auto lastCommitIsCall = [&](const auto& data) {
551 10 : return !data.messages.empty()
552 5 : && data.messages.rbegin()->body.at("type") == "application/call-history+json";
553 : };
554 : // should get message
555 1 : cv.wait_for(lk, 30s, [&]() {
556 3 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_);
557 : });
558 :
559 : auto destination = fmt::format("rdv:{}/{}/{}/{}",
560 1 : bobData_.id,
561 2 : bobData_.messages.rbegin()->body.at("uri"),
562 2 : bobData_.messages.rbegin()->body.at("device"),
563 3 : bobData_.messages.rbegin()->body.at("confId"));
564 :
565 1 : aliceData_.conferenceChanged = false;
566 1 : auto bobCallId = libjami::placeCallWithMedia(bobId, destination, {});
567 1 : cv.wait_for(lk, 30s, [&]() {
568 9 : return aliceData_.conferenceChanged && bobData_.hostState == "CURRENT";
569 : });
570 :
571 : // hangup bob MUST NOT stop the conference
572 1 : aliceData_.messages.clear();
573 1 : bobData_.messages.clear();
574 1 : aliceData_.conferenceChanged = false;
575 1 : Manager::instance().hangupCall(bobId, bobCallId);
576 :
577 8 : CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return aliceData_.conferenceRemoved; }));
578 1 : }
579 :
580 : void
581 1 : ConversationCallTest::testJoinFinishedCall()
582 : {
583 1 : enableCarla();
584 1 : connectSignals();
585 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
586 1 : auto aliceUri = aliceAccount->getUsername();
587 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
588 1 : auto bobUri = bobAccount->getUsername();
589 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
590 1 : auto carlaUri = carlaAccount->getUsername();
591 : // Start conversation
592 1 : libjami::startConversation(aliceId);
593 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
594 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
595 1 : libjami::addConversationMember(aliceId, aliceData_.id, carlaUri);
596 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
597 : return bobData_.requestReceived && carlaData_.requestReceived;
598 : }));
599 1 : aliceData_.messages.clear();
600 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
601 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
602 : return !bobData_.id.empty() && !aliceData_.messages.empty();
603 : }));
604 1 : aliceData_.messages.clear();
605 1 : libjami::acceptConversationRequest(carlaId, aliceData_.id);
606 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
607 : return !carlaData_.id.empty() && !aliceData_.messages.empty();
608 : }));
609 : // start call
610 1 : aliceData_.messages.clear();
611 1 : bobData_.messages.clear();
612 1 : carlaData_.messages.clear();
613 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
614 27 : auto lastCommitIsCall = [&](const auto& data) {
615 54 : return !data.messages.empty()
616 27 : && data.messages.rbegin()->body.at("type") == "application/call-history+json";
617 : };
618 : // should get message
619 1 : cv.wait_for(lk, 30s, [&]() {
620 9 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
621 9 : && lastCommitIsCall(carlaData_);
622 : });
623 2 : auto confId = bobData_.messages.rbegin()->body.at("confId");
624 : auto destination = fmt::format("rdv:{}/{}/{}/{}",
625 1 : bobData_.id,
626 2 : bobData_.messages.rbegin()->body.at("uri"),
627 2 : bobData_.messages.rbegin()->body.at("device"),
628 3 : bobData_.messages.rbegin()->body.at("confId"));
629 : // hangup
630 1 : aliceData_.messages.clear();
631 1 : bobData_.messages.clear();
632 1 : carlaData_.messages.clear();
633 1 : Manager::instance().hangupConference(aliceId, aliceData_.confId);
634 : // should get message
635 1 : cv.wait_for(lk, 30s, [&]() {
636 6 : return !aliceData_.messages.empty() && !bobData_.messages.empty()
637 6 : && !carlaData_.messages.empty();
638 : });
639 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
640 1 : aliceData_.messages.clear();
641 1 : bobData_.messages.clear();
642 1 : carlaData_.messages.clear();
643 : // If bob try to join the call, it will re-host a new conference
644 : // and commit a new active call.
645 1 : auto bobCall = libjami::placeCallWithMedia(bobId, destination, {});
646 : // should get message
647 1 : cv.wait_for(lk, 30s, [&]() {
648 15 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
649 15 : && lastCommitIsCall(carlaData_) && bobData_.hostState == "CURRENT";
650 : });
651 1 : confId = bobData_.messages.rbegin()->body.at("confId");
652 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 1);
653 : // hangup
654 1 : aliceData_.messages.clear();
655 1 : bobData_.messages.clear();
656 1 : carlaData_.messages.clear();
657 1 : Manager::instance().hangupConference(aliceId, confId);
658 : // should get message
659 1 : cv.wait_for(lk, 30s, [&]() {
660 6 : return !aliceData_.messages.empty() && !bobData_.messages.empty()
661 6 : && !carlaData_.messages.empty();
662 : });
663 1 : CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
664 1 : CPPUNIT_ASSERT(bobData_.messages[0].body.find("duration") != bobData_.messages[0].body.end());
665 1 : CPPUNIT_ASSERT(carlaData_.messages[0].body.find("duration") != carlaData_.messages[0].body.end());
666 : // get active calls = 0
667 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
668 1 : }
669 :
670 : void
671 1 : ConversationCallTest::testJoinFinishedCallForbidden()
672 : {
673 1 : enableCarla();
674 1 : connectSignals();
675 :
676 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
677 1 : auto aliceUri = aliceAccount->getUsername();
678 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
679 1 : auto bobUri = bobAccount->getUsername();
680 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
681 1 : auto carlaUri = carlaAccount->getUsername();
682 : // Start conversation
683 1 : libjami::startConversation(aliceId);
684 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
685 :
686 : // Do not host conference for others
687 2 : libjami::setConversationPreferences(aliceId, aliceData_.id, {{"hostConference", "false"}});
688 :
689 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
690 1 : libjami::addConversationMember(aliceId, aliceData_.id, carlaUri);
691 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
692 : return bobData_.requestReceived && carlaData_.requestReceived;
693 : }));
694 :
695 1 : aliceData_.messages.clear();
696 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
697 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
698 : return !bobData_.id.empty() && !aliceData_.messages.empty();
699 : }));
700 1 : aliceData_.messages.clear();
701 1 : libjami::acceptConversationRequest(carlaId, aliceData_.id);
702 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
703 : return !carlaData_.id.empty() && !aliceData_.messages.empty();
704 : }));
705 :
706 : // start call
707 1 : aliceData_.messages.clear();
708 1 : bobData_.messages.clear();
709 1 : carlaData_.messages.clear();
710 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
711 27 : auto lastCommitIsCall = [&](const auto& data) {
712 54 : return !data.messages.empty()
713 27 : && data.messages.rbegin()->body.at("type") == "application/call-history+json";
714 : };
715 : // should get message
716 1 : cv.wait_for(lk, 30s, [&]() {
717 9 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
718 9 : && lastCommitIsCall(carlaData_);
719 : });
720 :
721 2 : auto confId = bobData_.messages.rbegin()->body.at("confId");
722 : auto destination = fmt::format("rdv:{}/{}/{}/{}",
723 1 : bobData_.id,
724 2 : bobData_.messages.rbegin()->body.at("uri"),
725 2 : bobData_.messages.rbegin()->body.at("device"),
726 3 : bobData_.messages.rbegin()->body.at("confId"));
727 :
728 : // hangup
729 1 : aliceData_.messages.clear();
730 1 : bobData_.messages.clear();
731 1 : carlaData_.messages.clear();
732 1 : Manager::instance().hangupConference(aliceId, aliceData_.confId);
733 :
734 : // should get message
735 1 : cv.wait_for(lk, 30s, [&]() {
736 6 : return !aliceData_.messages.empty() && !bobData_.messages.empty()
737 6 : && !carlaData_.messages.empty();
738 : });
739 :
740 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
741 :
742 1 : aliceData_.messages.clear();
743 1 : bobData_.messages.clear();
744 1 : carlaData_.messages.clear();
745 : // If bob try to join the call, it will re-host a new conference
746 : // and commit a new active call.
747 1 : auto bobCall = libjami::placeCallWithMedia(bobId, destination, {});
748 : // should get message
749 1 : cv.wait_for(lk, 30s, [&]() {
750 15 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
751 15 : && lastCommitIsCall(carlaData_) && bobData_.hostState == "CURRENT";
752 : });
753 :
754 1 : confId = bobData_.messages.rbegin()->body.at("confId");
755 :
756 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 1);
757 :
758 : // hangup
759 1 : aliceData_.messages.clear();
760 1 : bobData_.messages.clear();
761 1 : carlaData_.messages.clear();
762 1 : Manager::instance().hangupConference(aliceId, confId);
763 :
764 : // should get message
765 1 : cv.wait_for(lk, 30s, [&]() {
766 6 : return !aliceData_.messages.empty() && !bobData_.messages.empty()
767 6 : && !carlaData_.messages.empty();
768 : });
769 1 : CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
770 1 : CPPUNIT_ASSERT(bobData_.messages[0].body.find("duration") != bobData_.messages[0].body.end());
771 1 : CPPUNIT_ASSERT(carlaData_.messages[0].body.find("duration") != carlaData_.messages[0].body.end());
772 :
773 : // get active calls = 0
774 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
775 1 : }
776 :
777 : void
778 1 : ConversationCallTest::testUsePreference()
779 : {
780 1 : enableCarla();
781 1 : connectSignals();
782 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
783 1 : auto aliceUri = aliceAccount->getUsername();
784 1 : auto aliceDevice = std::string(aliceAccount->currentDeviceId());
785 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
786 1 : auto bobUri = bobAccount->getUsername();
787 : // Start conversation
788 1 : libjami::startConversation(aliceId);
789 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
790 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
791 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData_.requestReceived; }));
792 1 : aliceData_.messages.clear();
793 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
794 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
795 : return !bobData_.id.empty() && !aliceData_.messages.empty();
796 : }));
797 :
798 : // Update preferences
799 1 : aliceData_.messages.clear();
800 1 : bobData_.messages.clear();
801 5 : auto lastCommitIsProfile = [&](const auto& data) {
802 10 : return !data.messages.empty()
803 5 : && data.messages.rbegin()->body.at("type") == "application/update-profile";
804 : };
805 1 : libjami::updateConversationInfos(aliceId,
806 1 : aliceData_.id,
807 5 : std::map<std::string, std::string> {
808 : {"rdvAccount", aliceUri},
809 : {"rdvDevice", aliceDevice},
810 3 : });
811 : // should get message
812 1 : cv.wait_for(lk, 30s, [&]() {
813 3 : return lastCommitIsProfile(aliceData_) && lastCommitIsProfile(bobData_);
814 : });
815 :
816 : // start call
817 1 : aliceData_.messages.clear();
818 1 : bobData_.messages.clear();
819 1 : libjami::placeCallWithMedia(bobId, "swarm:" + aliceData_.id, {});
820 13 : auto lastCommitIsCall = [&](const auto& data) {
821 26 : return !data.messages.empty()
822 13 : && data.messages.rbegin()->body.at("type") == "application/call-history+json";
823 : };
824 : // should get message
825 1 : cv.wait_for(lk, 30s, [&]() {
826 11 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_);
827 : });
828 2 : auto confId = bobData_.messages.rbegin()->body.at("confId");
829 :
830 : // Alice should be the host
831 1 : CPPUNIT_ASSERT(aliceAccount->getConference(confId));
832 : // Bob should be the only participant
833 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return pInfos_.size() == 1; }));
834 1 : auto uri = string_remove_suffix(pInfos_[0]["uri"], '@');
835 1 : CPPUNIT_ASSERT(uri == bobUri);
836 1 : Manager::instance().hangupConference(bobId, bobData_.confId);
837 1 : }
838 :
839 : void
840 1 : ConversationCallTest::testJoinWhileActiveCall()
841 : {
842 1 : connectSignals();
843 :
844 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
845 1 : auto aliceUri = aliceAccount->getUsername();
846 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
847 1 : auto bobUri = bobAccount->getUsername();
848 : // Start conversation
849 1 : libjami::startConversation(aliceId);
850 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
851 :
852 : // start call
853 1 : aliceData_.messages.clear();
854 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
855 2 : auto lastCommitIsCall = [&](const auto& data) {
856 4 : return !data.messages.empty()
857 2 : && data.messages.rbegin()->body.at("type") == "application/call-history+json";
858 : };
859 : // should get message
860 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return lastCommitIsCall(aliceData_); }));
861 :
862 2 : auto confId = aliceData_.messages.rbegin()->body.at("confId");
863 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
864 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return bobData_.requestReceived; }));
865 :
866 1 : aliceData_.messages.clear();
867 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
868 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
869 : return !bobData_.id.empty() && !aliceData_.messages.empty();
870 : }));
871 :
872 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(bobId, bobData_.id).size() == 1);
873 :
874 1 : aliceData_.messages.clear();
875 1 : bobData_.messages.clear();
876 1 : aliceData_.conferenceChanged = false;
877 1 : Manager::instance().hangupConference(aliceId, confId);
878 :
879 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceData_.conferenceRemoved; }));
880 1 : }
881 :
882 : void
883 1 : ConversationCallTest::testCallSelfIfDefaultHost()
884 : {
885 1 : enableCarla();
886 1 : connectSignals();
887 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
888 1 : auto aliceUri = aliceAccount->getUsername();
889 1 : auto aliceDevice = std::string(aliceAccount->currentDeviceId());
890 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
891 1 : auto bobUri = bobAccount->getUsername();
892 : // Start conversation
893 1 : libjami::startConversation(aliceId);
894 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
895 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
896 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData_.requestReceived; }));
897 1 : aliceData_.messages.clear();
898 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
899 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
900 : return !bobData_.id.empty() && !aliceData_.messages.empty();
901 : }));
902 : // Update preferences
903 1 : aliceData_.messages.clear();
904 1 : bobData_.messages.clear();
905 5 : auto lastCommitIsProfile = [&](const auto& data) {
906 10 : return !data.messages.empty()
907 5 : && data.messages.rbegin()->body.at("type") == "application/update-profile";
908 : };
909 1 : libjami::updateConversationInfos(aliceId,
910 1 : aliceData_.id,
911 5 : std::map<std::string, std::string> {
912 : {"rdvAccount", aliceUri},
913 : {"rdvDevice", aliceDevice},
914 3 : });
915 : // should get message
916 1 : cv.wait_for(lk, 30s, [&]() {
917 3 : return lastCommitIsProfile(aliceData_) && lastCommitIsProfile(bobData_);
918 : });
919 : // start call
920 1 : aliceData_.messages.clear();
921 1 : bobData_.messages.clear();
922 1 : pInfos_.clear();
923 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
924 5 : auto lastCommitIsCall = [&](const auto& data) {
925 10 : return !data.messages.empty()
926 5 : && data.messages.rbegin()->body.at("type") == "application/call-history+json";
927 : };
928 : // should get message
929 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
930 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_);
931 : }));
932 2 : auto confId = aliceData_.messages.rbegin()->body.at("confId");
933 : // Alice should be the host
934 1 : CPPUNIT_ASSERT(aliceAccount->getConference(confId));
935 1 : Manager::instance().hangupConference(aliceId, confId);
936 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceData_.conferenceRemoved; }));
937 :
938 1 : }
939 :
940 : void
941 1 : ConversationCallTest::testNeedsHost()
942 : {
943 1 : enableCarla();
944 1 : connectSignals();
945 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
946 1 : auto aliceUri = aliceAccount->getUsername();
947 1 : auto aliceDevice = std::string(aliceAccount->currentDeviceId());
948 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
949 1 : auto bobUri = bobAccount->getUsername();
950 : // Start conversation
951 1 : libjami::startConversation(aliceId);
952 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
953 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
954 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData_.requestReceived; }));
955 1 : aliceData_.messages.clear();
956 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
957 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
958 : return !bobData_.id.empty() && !aliceData_.messages.empty();
959 : }));
960 : // Update preferences
961 1 : aliceData_.messages.clear();
962 1 : bobData_.messages.clear();
963 5 : auto lastCommitIsProfile = [&](const auto& data) {
964 10 : return !data.messages.empty()
965 5 : && data.messages.rbegin()->body.at("type") == "application/update-profile";
966 : };
967 1 : libjami::updateConversationInfos(aliceId,
968 1 : aliceData_.id,
969 5 : std::map<std::string, std::string> {
970 : {"rdvAccount", aliceUri},
971 : {"rdvDevice", aliceDevice},
972 3 : });
973 : // should get message
974 1 : cv.wait_for(lk, 30s, [&]() {
975 3 : return lastCommitIsProfile(aliceData_) && lastCommitIsProfile(bobData_);
976 : });
977 : // Disable Host
978 1 : Manager::instance().sendRegister(aliceId, false);
979 : // start call
980 1 : libjami::placeCallWithMedia(bobId, "swarm:" + aliceData_.id, {});
981 : // Can fail after 30 seconds if it triggers a new ICE request (before No response from DHT)
982 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 40s, [&]() { return bobData_.needsHost; }));
983 1 : }
984 :
985 : void
986 1 : ConversationCallTest::testAudioOnly()
987 : {
988 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
989 1 : auto bobUri = bobAccount->getUsername();
990 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
991 1 : auto carlaUri = carlaAccount->getUsername();
992 1 : connectSignals();
993 :
994 : // Start conversation
995 1 : libjami::startConversation(aliceId);
996 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
997 :
998 : // get active calls = 0
999 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
1000 :
1001 : // start call
1002 1 : aliceData_.messages.clear();
1003 1 : std::vector<std::map<std::string, std::string>> mediaList;
1004 : std::map<std::string, std::string> mediaAttribute
1005 : = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
1006 : libjami::Media::MediaAttributeValue::AUDIO},
1007 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
1008 : {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
1009 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
1010 7 : {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}};
1011 1 : mediaList.emplace_back(mediaAttribute);
1012 1 : pInfos_.clear();
1013 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, mediaList);
1014 : // should get message
1015 4 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.messages.empty() && !pInfos_.empty(); });
1016 1 : CPPUNIT_ASSERT(aliceData_.messages[0].type == "application/call-history+json");
1017 1 : CPPUNIT_ASSERT(pInfos_.size() == 1);
1018 1 : CPPUNIT_ASSERT(pInfos_[0]["videoMuted"] == "true");
1019 :
1020 : // hangup
1021 1 : aliceData_.messages.clear();
1022 1 : Manager::instance().hangupConference(aliceId, aliceData_.confId);
1023 :
1024 : // should get message
1025 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.messages.empty(); });
1026 1 : CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
1027 :
1028 : // get active calls = 0
1029 1 : CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
1030 1 : }
1031 :
1032 : void
1033 1 : ConversationCallTest::testJoinAfterMuteHost()
1034 : {
1035 1 : connectSignals();
1036 :
1037 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1038 1 : auto aliceUri = aliceAccount->getUsername();
1039 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1040 1 : auto bobUri = bobAccount->getUsername();
1041 : // Start conversation
1042 1 : libjami::startConversation(aliceId);
1043 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); }));
1044 :
1045 1 : libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
1046 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
1047 : return bobData_.requestReceived;
1048 : }));
1049 :
1050 1 : aliceData_.messages.clear();
1051 1 : libjami::acceptConversationRequest(bobId, aliceData_.id);
1052 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1053 : return !bobData_.id.empty() && !aliceData_.messages.empty();
1054 : }));
1055 :
1056 : // start call
1057 1 : aliceData_.messages.clear();
1058 1 : bobData_.messages.clear();
1059 1 : carlaData_.messages.clear();
1060 1 : std::vector<std::map<std::string, std::string>> mediaList;
1061 : std::map<std::string, std::string> mediaAttribute
1062 : = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
1063 : libjami::Media::MediaAttributeValue::AUDIO},
1064 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
1065 : {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
1066 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
1067 7 : {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}};
1068 1 : mediaList.emplace_back(mediaAttribute);
1069 1 : libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, mediaList);
1070 7 : auto lastCommitIsCall = [&](const auto& data) {
1071 7 : return !data.messages.empty()
1072 7 : && data.messages.rbegin()->type == "application/call-history+json";
1073 : };
1074 : // should get message
1075 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1076 : return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_) && !pInfos_.empty();
1077 : }));
1078 2 : auto confId = bobData_.messages.rbegin()->body.at("confId");
1079 :
1080 : // Mute host
1081 1 : auto conference = aliceAccount->getConference(aliceData_.confId);
1082 1 : auto proposedList = conference->currentMediaList();
1083 2 : for (auto& media : proposedList)
1084 1 : if (media["LABEL"] == "audio_0")
1085 1 : media["MUTED"] = "true";
1086 1 : libjami::requestMediaChange(aliceId, confId, proposedList);
1087 :
1088 : // Bob join, alice must stay muted
1089 : auto destination = fmt::format("rdv:{}/{}/{}/{}",
1090 1 : bobData_.id,
1091 2 : bobData_.messages.rbegin()->body.at("uri"),
1092 2 : bobData_.messages.rbegin()->body.at("device"),
1093 1 : confId);
1094 :
1095 1 : aliceData_.conferenceChanged = false;
1096 1 : pInfos_.clear();
1097 1 : auto bobCall = libjami::placeCallWithMedia(bobId, destination, mediaList);
1098 11 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1099 : return aliceData_.conferenceChanged && bobData_.hostState == "CURRENT" && bobData_.state == "CURRENT" && pInfos_.size() == 2;
1100 : }));
1101 : // Audio of the host is still muted
1102 1 : CPPUNIT_ASSERT(pInfos_[0]["audioLocalMuted"] == "true");
1103 :
1104 1 : }
1105 :
1106 : void
1107 1 : ConversationCallTest::testBusy()
1108 : {
1109 1 : connectSignals();
1110 :
1111 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1112 1 : auto aliceUri = aliceAccount->getUsername();
1113 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1114 1 : auto bobUri = bobAccount->getUsername();
1115 :
1116 1 : aliceAccount->addContact(bobUri);
1117 1 : aliceAccount->sendTrustRequest(bobUri, {});
1118 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData_.requestReceived; }));
1119 :
1120 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
1121 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData_.id.empty(); }));
1122 :
1123 :
1124 : // start call
1125 1 : aliceData_.messages.clear();
1126 1 : bobData_.messages.clear();
1127 1 : carlaData_.messages.clear();
1128 1 : std::vector<std::map<std::string, std::string>> mediaList;
1129 : std::map<std::string, std::string> mediaAttribute
1130 : = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
1131 : libjami::Media::MediaAttributeValue::AUDIO},
1132 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
1133 : {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
1134 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
1135 7 : {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}};
1136 1 : mediaList.emplace_back(mediaAttribute);
1137 1 : libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
1138 12 : auto lastCommitIsCall = [&](const auto& data) {
1139 12 : return !data.messages.empty()
1140 12 : && data.messages.rbegin()->type == "application/call-history+json";
1141 : };
1142 : // should get message
1143 13 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
1144 : return lastCommitIsCall(aliceData_);
1145 : }));
1146 2 : auto reason = aliceData_.messages.rbegin()->body.at("reason");
1147 1 : CPPUNIT_ASSERT(reason == "busy");
1148 1 : }
1149 : void
1150 1 : ConversationCallTest::testDecline()
1151 : {
1152 1 : connectSignals();
1153 :
1154 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1155 1 : auto aliceUri = aliceAccount->getUsername();
1156 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1157 1 : auto bobUri = bobAccount->getUsername();
1158 :
1159 1 : aliceAccount->addContact(bobUri);
1160 1 : aliceAccount->sendTrustRequest(bobUri, {});
1161 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData_.requestReceived; }));
1162 :
1163 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
1164 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData_.id.empty(); }));
1165 :
1166 : // start call
1167 1 : aliceData_.messages.clear();
1168 1 : bobData_.messages.clear();
1169 1 : carlaData_.messages.clear();
1170 1 : std::vector<std::map<std::string, std::string>> mediaList;
1171 : std::map<std::string, std::string> mediaAttribute
1172 : = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
1173 : libjami::Media::MediaAttributeValue::AUDIO},
1174 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
1175 : {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
1176 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
1177 7 : {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}};
1178 1 : mediaList.emplace_back(mediaAttribute);
1179 1 : libjami::placeCallWithMedia(aliceId, bobUri, mediaList);
1180 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return !bobData_.callId.empty() && bobData_.state == "INCOMING"; }));
1181 :
1182 1 : libjami::refuse(bobId, bobData_.callId);
1183 :
1184 4 : auto lastCommitIsCall = [&](const auto& data) {
1185 4 : return !data.messages.empty()
1186 4 : && data.messages.rbegin()->type == "application/call-history+json";
1187 : };
1188 : // should get message
1189 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
1190 : return lastCommitIsCall(aliceData_);
1191 : }));
1192 2 : auto reason = aliceData_.messages.rbegin()->body.at("reason");
1193 1 : CPPUNIT_ASSERT(reason == "declined");
1194 1 : }
1195 :
1196 : void
1197 1 : ConversationCallTest::testNoDevice()
1198 : {
1199 1 : connectSignals();
1200 :
1201 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1202 1 : auto aliceUri = aliceAccount->getUsername();
1203 :
1204 1 : aliceAccount->addContact("e2eb225c76be68713d4874d290200849436c6355");
1205 1 : aliceAccount->sendTrustRequest("e2eb225c76be68713d4874d290200849436c6355", {});
1206 :
1207 : // start call
1208 1 : aliceData_.messages.clear();
1209 1 : bobData_.messages.clear();
1210 1 : carlaData_.messages.clear();
1211 1 : std::vector<std::map<std::string, std::string>> mediaList;
1212 : std::map<std::string, std::string> mediaAttribute
1213 : = {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
1214 : libjami::Media::MediaAttributeValue::AUDIO},
1215 : {libjami::Media::MediaAttributeKey::ENABLED, TRUE_STR},
1216 : {libjami::Media::MediaAttributeKey::MUTED, FALSE_STR},
1217 : {libjami::Media::MediaAttributeKey::SOURCE, ""},
1218 7 : {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}};
1219 1 : mediaList.emplace_back(mediaAttribute);
1220 1 : libjami::placeCallWithMedia(aliceId, "e2eb225c76be68713d4874d290200849436c6355", mediaList);
1221 :
1222 4 : auto lastCommitIsCall = [&](const auto& data) {
1223 4 : return !data.messages.empty()
1224 4 : && data.messages.rbegin()->type == "application/call-history+json";
1225 : };
1226 : // should get message
1227 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
1228 : return lastCommitIsCall(aliceData_);
1229 : }));
1230 2 : auto reason = aliceData_.messages.rbegin()->body.at("reason");
1231 1 : CPPUNIT_ASSERT(reason == "no_device");
1232 1 : }
1233 :
1234 : } // namespace test
1235 : } // namespace jami
1236 :
1237 1 : RING_TEST_RUNNER(jami::test::ConversationCallTest::name())
|