LCOV - code coverage report
Current view: top level - test/unitTest/conversation - call.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 731 741 98.7 %
Date: 2024-12-21 08:56:24 Functions: 133 133 100.0 %

          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         125 :         [&](const std::string& accountId,
     230             :             const std::string& callId,
     231             :             const std::string& state,
     232             :             signed) {
     233         125 :             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          54 :                         bobData_.hostState = state;
     243          14 :                     else if (details["PEER_NUMBER"].find(carlaUri) != std::string::npos)
     244          12 :                         carlaData_.hostState = state;
     245          68 :                 }
     246         125 :             } else if (accountId == bobId) {
     247          47 :                 bobData_.callId = callId;
     248          47 :                 bobData_.state = state;
     249          10 :             } else if (accountId == carlaId) {
     250          10 :                 carlaData_.state = state;
     251             :             }
     252         125 :             cv.notify_one();
     253         125 :         }));
     254          15 :     confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::OnConferenceInfosUpdated>(
     255           8 :         [=](const std::string&,
     256             :             const std::vector<std::map<std::string, std::string>> participantsInfos) {
     257           8 :             pInfos_ = participantsInfos;
     258           8 :             cv.notify_one();
     259           8 :         }));
     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          10 :     auto lastCommitIsCall = [&](const auto& data) {
     367          20 :         return !data.messages.empty()
     368          10 :                && 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           9 :     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          10 :     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           8 :         return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
     461           8 :                && 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           9 :         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           9 :         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           2 :     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           4 :         return !aliceData_.messages.empty() && !bobData_.messages.empty()
     637           4 :                && !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           8 :         return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
     718           8 :                && 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           4 :         return !aliceData_.messages.empty() && !bobData_.messages.empty()
     767           4 :                && !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           5 :     auto lastCommitIsCall = [&](const auto& data) {
    1185           5 :         return !data.messages.empty()
    1186           5 :                && data.messages.rbegin()->type == "application/call-history+json";
    1187             :     };
    1188             :     // should get message
    1189           6 :     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())

Generated by: LCOV version 1.14