LCOV - code coverage report
Current view: top level - test/unitTest/media_negotiation - auto_answer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 367 478 76.8 %
Date: 2024-12-21 08:56:24 Functions: 38 50 76.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 "../../test_runner.h"
      19             : 
      20             : #include "manager.h"
      21             : #include "account.h"
      22             : #include "sip/sipaccount.h"
      23             : #include "jami.h"
      24             : #include "jami/media_const.h"
      25             : #include "call_const.h"
      26             : #include "account_const.h"
      27             : #include "sip/sipcall.h"
      28             : #include "sip/sdp.h"
      29             : #include "common.h"
      30             : 
      31             : #include <dhtnet/connectionmanager.h>
      32             : 
      33             : #include <cppunit/TestAssert.h>
      34             : #include <cppunit/TestFixture.h>
      35             : #include <cppunit/extensions/HelperMacros.h>
      36             : 
      37             : #include <condition_variable>
      38             : #include <string>
      39             : 
      40             : using namespace libjami::Account;
      41             : using namespace libjami::Call;
      42             : 
      43             : namespace jami {
      44             : namespace test {
      45             : 
      46             : struct TestScenario
      47             : {
      48             :     TestScenario(const std::vector<MediaAttribute>& offer,
      49             :                  const std::vector<MediaAttribute>& answer,
      50             :                  const std::vector<MediaAttribute>& offerUpdate,
      51             :                  const std::vector<MediaAttribute>& answerUpdate)
      52             :         : offer_(std::move(offer))
      53             :         , answer_(std::move(answer))
      54             :         , offerUpdate_(std::move(offerUpdate))
      55             :         , answerUpdate_(std::move(answerUpdate))
      56             :     {}
      57             : 
      58           4 :     TestScenario() {};
      59             : 
      60             :     std::vector<MediaAttribute> offer_;
      61             :     std::vector<MediaAttribute> answer_;
      62             :     std::vector<MediaAttribute> offerUpdate_;
      63             :     std::vector<MediaAttribute> answerUpdate_;
      64             :     // Determine if we should expect the MediaNegotiationStatus signal.
      65             :     bool expectMediaRenegotiation_ {false};
      66             : };
      67             : 
      68             : struct CallData
      69             : {
      70             :     struct Signal
      71             :     {
      72          56 :         Signal(const std::string& name, const std::string& event = {})
      73          56 :             : name_(std::move(name))
      74          56 :             , event_(std::move(event)) {};
      75             : 
      76             :         std::string name_ {};
      77             :         std::string event_ {};
      78             :     };
      79             : 
      80             :     std::string accountId_ {};
      81             :     std::string userName_ {};
      82             :     std::string alias_ {};
      83             :     std::string callId_ {};
      84             :     uint16_t listeningPort_ {0};
      85             :     std::string toUri_ {};
      86             :     std::vector<Signal> signals_;
      87             :     std::condition_variable cv_ {};
      88             :     std::mutex mtx_;
      89             : };
      90             : 
      91             : class AutoAnswerMediaNegoTest
      92             : {
      93             : public:
      94           4 :     AutoAnswerMediaNegoTest()
      95           4 :     {
      96             :         // Init daemon
      97           4 :         libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
      98           4 :         if (not Manager::instance().initialized)
      99           1 :             CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
     100           4 :     }
     101           4 :     ~AutoAnswerMediaNegoTest() { libjami::fini(); }
     102             : 
     103             : protected:
     104             :     // Test cases.
     105             :     void audio_and_video_then_caller_mute_video();
     106             :     void audio_only_then_caller_add_video();
     107             :     void audio_and_video_then_caller_mute_audio();
     108             :     void audio_and_video_then_change_video_source();
     109             : 
     110             :     // Event/Signal handlers
     111             :     static void onCallStateChange(const std::string& accountId,
     112             :                                   const std::string& callId,
     113             :                                   const std::string& state,
     114             :                                   CallData& callData);
     115             :     static void onIncomingCallWithMedia(const std::string& accountId,
     116             :                                         const std::string& callId,
     117             :                                         const std::vector<libjami::MediaMap> mediaList,
     118             :                                         CallData& callData);
     119             :     static void onMediaChangeRequested(const std::string& accountId,
     120             :                                        const std::string& callId,
     121             :                                        const std::vector<libjami::MediaMap> mediaList,
     122             :                                        CallData& callData);
     123             :     static void onVideoMuted(const std::string& callId, bool muted, CallData& callData);
     124             :     static void onMediaNegotiationStatus(const std::string& callId,
     125             :                                          const std::string& event,
     126             :                                          CallData& callData);
     127             : 
     128             :     // Helpers
     129             :     void configureScenario();
     130             :     void testWithScenario(CallData& aliceData, CallData& bobData, const TestScenario& scenario);
     131             :     static std::string getUserAlias(const std::string& callId);
     132             :     // Infer media direction of an offer.
     133             :     static uint8_t directionToBitset(MediaDirection direction, bool isLocal);
     134             :     static MediaDirection bitsetToDirection(uint8_t val);
     135             :     static MediaDirection inferInitialDirection(const MediaAttribute& offer);
     136             :     // Infer media direction of an answer.
     137             :     static MediaDirection inferNegotiatedDirection(MediaDirection local, MediaDirection answer);
     138             :     // Wait for a signal from the callbacks. Some signals also report the event that
     139             :     // triggered the signal like the StateChange signal.
     140             :     static bool validateMuteState(std::vector<MediaAttribute> expected,
     141             :                                   std::vector<MediaAttribute> actual);
     142             :     static bool validateMediaDirection(std::vector<MediaDescription> descrList,
     143             :                                        std::vector<MediaAttribute> listInOffer,
     144             :                                        std::vector<MediaAttribute> listInAnswer);
     145             :     static bool waitForSignal(CallData& callData,
     146             :                               const std::string& signal,
     147             :                               const std::string& expectedEvent = {});
     148             : 
     149             :     bool isSipAccount_ {false};
     150             :     CallData aliceData_;
     151             :     CallData bobData_;
     152             : };
     153             : 
     154             : class AutoAnswerMediaNegoTestSip : public AutoAnswerMediaNegoTest, public CppUnit::TestFixture
     155             : {
     156             : public:
     157           0 :     AutoAnswerMediaNegoTestSip() { isSipAccount_ = true; };
     158             : 
     159           0 :     ~AutoAnswerMediaNegoTestSip() {};
     160             : 
     161           1 :     static std::string name() { return "AutoAnswerMediaNegoTestSip"; }
     162             :     void setUp() override;
     163             :     void tearDown() override;
     164             : 
     165             : private:
     166           0 :     CPPUNIT_TEST_SUITE(AutoAnswerMediaNegoTestSip);
     167           0 :     CPPUNIT_TEST(audio_and_video_then_caller_mute_video);
     168           0 :     CPPUNIT_TEST(audio_only_then_caller_add_video);
     169           0 :     CPPUNIT_TEST(audio_and_video_then_caller_mute_audio);
     170           0 :     CPPUNIT_TEST(audio_and_video_then_change_video_source);
     171           0 :     CPPUNIT_TEST_SUITE_END();
     172             : };
     173             : 
     174             : void
     175           0 : AutoAnswerMediaNegoTestSip::setUp()
     176             : {
     177           0 :     aliceData_.listeningPort_ = 5080;
     178           0 :     std::map<std::string, std::string> details = libjami::getAccountTemplate("SIP");
     179           0 :     details[ConfProperties::TYPE] = "SIP";
     180           0 :     details[ConfProperties::DISPLAYNAME] = "ALICE";
     181           0 :     details[ConfProperties::ALIAS] = "ALICE";
     182           0 :     details[ConfProperties::LOCAL_PORT] = std::to_string(aliceData_.listeningPort_);
     183           0 :     details[ConfProperties::UPNP_ENABLED] = "false";
     184           0 :     aliceData_.accountId_ = Manager::instance().addAccount(details);
     185             : 
     186           0 :     bobData_.listeningPort_ = 5082;
     187           0 :     details = libjami::getAccountTemplate("SIP");
     188           0 :     details[ConfProperties::TYPE] = "SIP";
     189           0 :     details[ConfProperties::DISPLAYNAME] = "BOB";
     190           0 :     details[ConfProperties::ALIAS] = "BOB";
     191           0 :     details[ConfProperties::AUTOANSWER] = "true";
     192           0 :     details[ConfProperties::LOCAL_PORT] = std::to_string(bobData_.listeningPort_);
     193           0 :     details[ConfProperties::UPNP_ENABLED] = "false";
     194           0 :     bobData_.accountId_ = Manager::instance().addAccount(details);
     195             : 
     196           0 :     JAMI_INFO("Initialize accounts ...");
     197           0 :     auto aliceAccount = Manager::instance().getAccount<Account>(aliceData_.accountId_);
     198           0 :     auto bobAccount = Manager::instance().getAccount<Account>(bobData_.accountId_);
     199           0 : }
     200             : 
     201             : void
     202           0 : AutoAnswerMediaNegoTestSip::tearDown()
     203             : {
     204           0 :     JAMI_INFO("Remove created accounts...");
     205             : 
     206           0 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
     207           0 :     std::mutex mtx;
     208           0 :     std::unique_lock lk {mtx};
     209           0 :     std::condition_variable cv;
     210           0 :     auto currentAccSize = Manager::instance().getAccountList().size();
     211           0 :     std::atomic_bool accountsRemoved {false};
     212           0 :     confHandlers.insert(
     213           0 :         libjami::exportable_callback<libjami::ConfigurationSignal::AccountsChanged>([&]() {
     214           0 :             if (Manager::instance().getAccountList().size() <= currentAccSize - 2) {
     215           0 :                 accountsRemoved = true;
     216           0 :                 cv.notify_one();
     217             :             }
     218           0 :         }));
     219           0 :     libjami::registerSignalHandlers(confHandlers);
     220             : 
     221           0 :     Manager::instance().removeAccount(aliceData_.accountId_, true);
     222           0 :     Manager::instance().removeAccount(bobData_.accountId_, true);
     223           0 :     CPPUNIT_ASSERT(
     224             :         cv.wait_for(lk, std::chrono::seconds(30), [&] { return accountsRemoved.load(); }));
     225             : 
     226           0 :     libjami::unregisterSignalHandlers();
     227           0 : }
     228             : 
     229             : class AutoAnswerMediaNegoTestJami : public AutoAnswerMediaNegoTest, public CppUnit::TestFixture
     230             : {
     231             : public:
     232           4 :     AutoAnswerMediaNegoTestJami() { isSipAccount_ = false; };
     233             : 
     234           8 :     ~AutoAnswerMediaNegoTestJami() {};
     235             : 
     236           2 :     static std::string name() { return "AutoAnswerMediaNegoTestJami"; }
     237             :     void setUp() override;
     238             :     void tearDown() override;
     239             : 
     240             : private:
     241           2 :     CPPUNIT_TEST_SUITE(AutoAnswerMediaNegoTestJami);
     242           1 :     CPPUNIT_TEST(audio_and_video_then_caller_mute_video);
     243           1 :     CPPUNIT_TEST(audio_only_then_caller_add_video);
     244           1 :     CPPUNIT_TEST(audio_and_video_then_caller_mute_audio);
     245           1 :     CPPUNIT_TEST(audio_and_video_then_change_video_source);
     246           4 :     CPPUNIT_TEST_SUITE_END();
     247             : };
     248             : 
     249             : void
     250           4 : AutoAnswerMediaNegoTestJami::setUp()
     251             : {
     252           4 :     auto actors = load_actors("actors/alice-bob-no-upnp.yml");
     253             : 
     254           4 :     aliceData_.accountId_ = actors["alice"];
     255           4 :     bobData_.accountId_ = actors["bob"];
     256             : 
     257           4 :     JAMI_INFO("Initialize account...");
     258           4 :     auto aliceAccount = Manager::instance().getAccount<Account>(aliceData_.accountId_);
     259           4 :     auto bobAccount = Manager::instance().getAccount<Account>(bobData_.accountId_);
     260           4 :     auto details = bobAccount->getAccountDetails();
     261           4 :     details[ConfProperties::AUTOANSWER] = "true";
     262           4 :     bobAccount->setAccountDetails(details);
     263          12 :     wait_for_announcement_of({aliceAccount->getAccountID(), bobAccount->getAccountID()});
     264           4 : }
     265             : 
     266             : void
     267           4 : AutoAnswerMediaNegoTestJami::tearDown()
     268             : {
     269          12 :     wait_for_removal_of({aliceData_.accountId_, bobData_.accountId_});
     270           4 : }
     271             : 
     272             : std::string
     273          60 : AutoAnswerMediaNegoTest::getUserAlias(const std::string& callId)
     274             : {
     275          60 :     auto call = Manager::instance().getCallFromCallID(callId);
     276             : 
     277          60 :     if (not call) {
     278           4 :         JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str());
     279           4 :         return {};
     280             :     }
     281             : 
     282          56 :     auto const& account = call->getAccount().lock();
     283          56 :     if (not account) {
     284           0 :         return {};
     285             :     }
     286             : 
     287          56 :     return account->getAccountDetails()[ConfProperties::ALIAS];
     288          60 : }
     289             : 
     290             : MediaDirection
     291          44 : AutoAnswerMediaNegoTest::inferInitialDirection(const MediaAttribute& mediaAttr)
     292             : {
     293          44 :     if (not mediaAttr.enabled_)
     294           0 :         return MediaDirection::INACTIVE;
     295             : 
     296          44 :     if (mediaAttr.muted_) {
     297           2 :         if (mediaAttr.onHold_)
     298           0 :             return MediaDirection::INACTIVE;
     299           2 :         return MediaDirection::RECVONLY;
     300             :     }
     301             : 
     302          42 :     if (mediaAttr.onHold_)
     303           0 :         return MediaDirection::SENDONLY;
     304             : 
     305          42 :     return MediaDirection::SENDRECV;
     306             : }
     307             : 
     308             : uint8_t
     309          44 : AutoAnswerMediaNegoTest::directionToBitset(MediaDirection direction, bool isLocal)
     310             : {
     311          44 :     if (direction == MediaDirection::SENDRECV)
     312          42 :         return 3;
     313           2 :     if (direction == MediaDirection::RECVONLY)
     314           2 :         return isLocal ? 2 : 1;
     315           0 :     if (direction == MediaDirection::SENDONLY)
     316           0 :         return isLocal ? 1 : 2;
     317           0 :     return 0;
     318             : }
     319             : 
     320             : MediaDirection
     321          22 : AutoAnswerMediaNegoTest::bitsetToDirection(uint8_t val)
     322             : {
     323          22 :     if (val == 3)
     324          20 :         return MediaDirection::SENDRECV;
     325           2 :     if (val == 2)
     326           1 :         return MediaDirection::RECVONLY;
     327           1 :     if (val == 1)
     328           1 :         return MediaDirection::SENDONLY;
     329           0 :     return MediaDirection::INACTIVE;
     330             : }
     331             : 
     332             : MediaDirection
     333          22 : AutoAnswerMediaNegoTest::inferNegotiatedDirection(MediaDirection local, MediaDirection remote)
     334             : {
     335          22 :     uint8_t val = directionToBitset(local, true) & directionToBitset(remote, false);
     336          22 :     auto dir = bitsetToDirection(val);
     337          22 :     return dir;
     338             : }
     339             : 
     340             : bool
     341          12 : AutoAnswerMediaNegoTest::validateMuteState(std::vector<MediaAttribute> expected,
     342             :                                            std::vector<MediaAttribute> actual)
     343             : {
     344          12 :     CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size());
     345             : 
     346          34 :     for (size_t idx = 0; idx < expected.size(); idx++) {
     347          22 :         if (expected[idx].muted_ != actual[idx].muted_)
     348           0 :             return false;
     349             :     }
     350             : 
     351          12 :     return true;
     352             : }
     353             : 
     354             : bool
     355          12 : AutoAnswerMediaNegoTest::validateMediaDirection(std::vector<MediaDescription> descrList,
     356             :                                                 std::vector<MediaAttribute> localMediaList,
     357             :                                                 std::vector<MediaAttribute> remoteMediaList)
     358             : {
     359          12 :     CPPUNIT_ASSERT_EQUAL(descrList.size(), localMediaList.size());
     360          12 :     CPPUNIT_ASSERT_EQUAL(descrList.size(), remoteMediaList.size());
     361             : 
     362          34 :     for (size_t idx = 0; idx < descrList.size(); idx++) {
     363          22 :         auto local = inferInitialDirection(localMediaList[idx]);
     364          22 :         auto remote = inferInitialDirection(remoteMediaList[idx]);
     365          22 :         auto negotiated = inferNegotiatedDirection(local, remote);
     366             : 
     367          22 :         if (descrList[idx].direction_ != negotiated) {
     368           0 :             JAMI_WARN("Media [%lu] direction mismatch: expected %i - found %i",
     369             :                       idx,
     370             :                       static_cast<int>(negotiated),
     371             :                       static_cast<int>(descrList[idx].direction_));
     372           0 :             return false;
     373             :         }
     374             :     }
     375             : 
     376          12 :     return true;
     377             : }
     378             : 
     379             : void
     380           4 : AutoAnswerMediaNegoTest::onIncomingCallWithMedia(const std::string& accountId,
     381             :                                                  const std::string& callId,
     382             :                                                  const std::vector<libjami::MediaMap> mediaList,
     383             :                                                  CallData& callData)
     384             : {
     385           4 :     CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
     386             : 
     387           4 :     JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
     388             :               libjami::CallSignal::IncomingCallWithMedia::name,
     389             :               callData.alias_.c_str(),
     390             :               callId.c_str(),
     391             :               mediaList.size());
     392             : 
     393           4 :     if (not Manager::instance().getCallFromCallID(callId)) {
     394           0 :         JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
     395           0 :         callData.callId_ = {};
     396           0 :         return;
     397             :     }
     398             : 
     399           4 :     std::unique_lock lock {callData.mtx_};
     400           4 :     callData.callId_ = callId;
     401           4 :     callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::IncomingCallWithMedia::name));
     402             : 
     403           4 :     callData.cv_.notify_one();
     404           4 : }
     405             : 
     406             : void
     407           0 : AutoAnswerMediaNegoTest::onMediaChangeRequested(const std::string& accountId,
     408             :                                                 const std::string& callId,
     409             :                                                 const std::vector<libjami::MediaMap> mediaList,
     410             :                                                 CallData& callData)
     411             : {
     412           0 :     CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
     413             : 
     414           0 :     JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
     415             :               libjami::CallSignal::MediaChangeRequested::name,
     416             :               callData.alias_.c_str(),
     417             :               callId.c_str(),
     418             :               mediaList.size());
     419             : 
     420           0 :     if (not Manager::instance().getCallFromCallID(callId)) {
     421           0 :         JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
     422           0 :         callData.callId_ = {};
     423           0 :         return;
     424             :     }
     425             : 
     426           0 :     std::unique_lock lock {callData.mtx_};
     427           0 :     callData.callId_ = callId;
     428           0 :     callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::MediaChangeRequested::name));
     429             : 
     430           0 :     callData.cv_.notify_one();
     431           0 : }
     432             : 
     433             : void
     434          36 : AutoAnswerMediaNegoTest::onCallStateChange(const std::string& accountId UNUSED,
     435             :                                            const std::string& callId,
     436             :                                            const std::string& state,
     437             :                                            CallData& callData)
     438             : {
     439             :     // TODO. rewrite me using accountId.
     440             : 
     441          36 :     auto call = Manager::instance().getCallFromCallID(callId);
     442          36 :     if (not call) {
     443           0 :         JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str());
     444           0 :         return;
     445             :     }
     446             : 
     447          36 :     auto account = call->getAccount().lock();
     448          36 :     if (not account) {
     449           0 :         JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
     450           0 :         return;
     451             :     }
     452             : 
     453          36 :     JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
     454             :               libjami::CallSignal::StateChange::name,
     455             :               callData.alias_.c_str(),
     456             :               callId.c_str(),
     457             :               state.c_str());
     458             : 
     459          36 :     if (account->getAccountID() != callData.accountId_)
     460           0 :         return;
     461             : 
     462             :     {
     463          36 :         std::unique_lock lock {callData.mtx_};
     464          36 :         callData.signals_.emplace_back(
     465          72 :             CallData::Signal(libjami::CallSignal::StateChange::name, state));
     466          36 :     }
     467             : 
     468          36 :     if (state == "CURRENT" or state == "OVER" or state == "HUNGUP") {
     469          16 :         callData.cv_.notify_one();
     470             :     }
     471          36 : }
     472             : 
     473             : void
     474           1 : AutoAnswerMediaNegoTest::onVideoMuted(const std::string& callId, bool muted, CallData& callData)
     475             : {
     476           1 :     auto call = Manager::instance().getCallFromCallID(callId);
     477             : 
     478           1 :     if (not call) {
     479           0 :         JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str());
     480           0 :         return;
     481             :     }
     482             : 
     483           1 :     auto account = call->getAccount().lock();
     484           1 :     if (not account) {
     485           0 :         JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
     486           0 :         return;
     487             :     }
     488             : 
     489           1 :     JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
     490             :               libjami::CallSignal::VideoMuted::name,
     491             :               account->getAccountDetails()[ConfProperties::ALIAS].c_str(),
     492             :               call->getCallId().c_str(),
     493             :               muted ? "Mute" : "Un-mute");
     494             : 
     495           1 :     if (account->getAccountID() != callData.accountId_)
     496           0 :         return;
     497             : 
     498             :     {
     499           1 :         std::unique_lock lock {callData.mtx_};
     500           1 :         callData.signals_.emplace_back(
     501           2 :             CallData::Signal(libjami::CallSignal::VideoMuted::name, muted ? "muted" : "un-muted"));
     502           1 :     }
     503             : 
     504           1 :     callData.cv_.notify_one();
     505           1 : }
     506             : 
     507             : void
     508          15 : AutoAnswerMediaNegoTest::onMediaNegotiationStatus(const std::string& callId,
     509             :                                                   const std::string& event,
     510             :                                                   CallData& callData)
     511             : {
     512          15 :     auto call = Manager::instance().getCallFromCallID(callId);
     513          15 :     if (not call) {
     514           0 :         JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
     515           0 :         return;
     516             :     }
     517             : 
     518          15 :     auto account = call->getAccount().lock();
     519          15 :     if (not account) {
     520           0 :         JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
     521           0 :         return;
     522             :     }
     523             : 
     524          15 :     JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
     525             :               libjami::CallSignal::MediaNegotiationStatus::name,
     526             :               account->getAccountDetails()[ConfProperties::ALIAS].c_str(),
     527             :               call->getCallId().c_str(),
     528             :               event.c_str());
     529             : 
     530          15 :     if (account->getAccountID() != callData.accountId_)
     531           0 :         return;
     532             : 
     533             :     {
     534          15 :         std::unique_lock lock {callData.mtx_};
     535          15 :         callData.signals_.emplace_back(
     536          30 :             CallData::Signal(libjami::CallSignal::MediaNegotiationStatus::name, event));
     537          15 :     }
     538             : 
     539          15 :     callData.cv_.notify_one();
     540          15 : }
     541             : 
     542             : bool
     543          22 : AutoAnswerMediaNegoTest::waitForSignal(CallData& callData,
     544             :                                        const std::string& expectedSignal,
     545             :                                        const std::string& expectedEvent)
     546             : {
     547          22 :     const std::chrono::seconds TIME_OUT {15};
     548          22 :     std::unique_lock lock {callData.mtx_};
     549             : 
     550             :     // Combined signal + event (if any).
     551          22 :     std::string sigEvent(expectedSignal);
     552          22 :     if (not expectedEvent.empty())
     553          18 :         sigEvent += "::" + expectedEvent;
     554             : 
     555          22 :     JAMI_INFO("[%s] is waiting for [%s] signal/event", callData.alias_.c_str(), sigEvent.c_str());
     556             : 
     557          22 :     auto res = callData.cv_.wait_for(lock, TIME_OUT, [&] {
     558             :         // Search for the expected signal in list of received signals.
     559          44 :         bool pred = false;
     560         180 :         for (auto it = callData.signals_.begin(); it != callData.signals_.end(); it++) {
     561             :             // The predicate is true if the signal names match, and if the
     562             :             // expectedEvent is not empty, the events must also match.
     563         158 :             if (it->name_ == expectedSignal
     564         158 :                 and (expectedEvent.empty() or it->event_ == expectedEvent)) {
     565          22 :                 pred = true;
     566             :                 // Done with this signal.
     567          22 :                 callData.signals_.erase(it);
     568          22 :                 break;
     569             :             }
     570             :         }
     571             : 
     572          44 :         return pred;
     573             :     });
     574             : 
     575          22 :     if (not res) {
     576           0 :         JAMI_ERR("[%s] waiting for signal/event [%s] timed-out!",
     577             :                  callData.alias_.c_str(),
     578             :                  sigEvent.c_str());
     579             : 
     580           0 :         JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str());
     581             : 
     582           0 :         for (auto const& sig : callData.signals_) {
     583           0 :             JAMI_INFO() << "Signal [" << sig.name_
     584           0 :                         << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]";
     585             :         }
     586             :     }
     587             : 
     588          22 :     return res;
     589          22 : }
     590             : 
     591             : void
     592           4 : AutoAnswerMediaNegoTest::configureScenario()
     593             : {
     594             :     // Configure Alice
     595             :     {
     596           4 :         CPPUNIT_ASSERT(not aliceData_.accountId_.empty());
     597           4 :         auto const& account = Manager::instance().getAccount<Account>(aliceData_.accountId_);
     598           4 :         aliceData_.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
     599           4 :         aliceData_.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
     600           4 :         if (isSipAccount_) {
     601           0 :             auto sipAccount = std::dynamic_pointer_cast<SIPAccount>(account);
     602           0 :             CPPUNIT_ASSERT(sipAccount);
     603           0 :             sipAccount->setLocalPort(aliceData_.listeningPort_);
     604           0 :         }
     605           4 :     }
     606             : 
     607             :     // Configure Bob
     608             :     {
     609           4 :         CPPUNIT_ASSERT(not bobData_.accountId_.empty());
     610           4 :         auto const& account = Manager::instance().getAccount<Account>(bobData_.accountId_);
     611           4 :         bobData_.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
     612           4 :         bobData_.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
     613           4 :         CPPUNIT_ASSERT(account->isAutoAnswerEnabled());
     614             : 
     615           4 :         if (isSipAccount_) {
     616           0 :             auto sipAccount = std::dynamic_pointer_cast<SIPAccount>(account);
     617           0 :             CPPUNIT_ASSERT(sipAccount);
     618           0 :             sipAccount->setLocalPort(bobData_.listeningPort_);
     619           0 :             bobData_.toUri_ = "127.0.0.1:" + std::to_string(bobData_.listeningPort_);
     620           0 :         }
     621           4 :     }
     622             : 
     623           4 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> signalHandlers;
     624             : 
     625             :     // Insert needed signal handlers.
     626           4 :     signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::IncomingCallWithMedia>(
     627           4 :         [&](const std::string& accountId,
     628             :             const std::string& callId,
     629             :             const std::string&,
     630             :             const std::vector<libjami::MediaMap> mediaList) {
     631           4 :             auto user = getUserAlias(callId);
     632           4 :             if (not user.empty())
     633           8 :                 onIncomingCallWithMedia(accountId,
     634             :                                         callId,
     635             :                                         mediaList,
     636           8 :                                         user == aliceData_.alias_ ? aliceData_ : bobData_);
     637           4 :         }));
     638             : 
     639           4 :     signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaChangeRequested>(
     640           0 :         [&](const std::string& accountId,
     641             :             const std::string& callId,
     642             :             const std::vector<libjami::MediaMap> mediaList) {
     643           0 :             auto user = getUserAlias(callId);
     644           0 :             if (not user.empty())
     645           0 :                 onMediaChangeRequested(accountId,
     646             :                                        callId,
     647             :                                        mediaList,
     648           0 :                                        user == aliceData_.alias_ ? aliceData_ : bobData_);
     649           0 :         }));
     650             : 
     651           4 :     signalHandlers.insert(
     652           8 :         libjami::exportable_callback<libjami::CallSignal::StateChange>([&](const std::string& accountId,
     653             :                                                                        const std::string& callId,
     654             :                                                                        const std::string& state,
     655             :                                                                        signed) {
     656          40 :             auto user = getUserAlias(callId);
     657          40 :             if (not user.empty())
     658          72 :                 onCallStateChange(accountId,
     659             :                                   callId,
     660             :                                   state,
     661          72 :                                   user == aliceData_.alias_ ? aliceData_ : bobData_);
     662          40 :         }));
     663             : 
     664           4 :     signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::VideoMuted>(
     665           1 :         [&](const std::string& callId, bool muted) {
     666           1 :             auto user = getUserAlias(callId);
     667           1 :             if (not user.empty())
     668           1 :                 onVideoMuted(callId, muted, user == aliceData_.alias_ ? aliceData_ : bobData_);
     669           1 :         }));
     670             : 
     671           4 :     signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaNegotiationStatus>(
     672          15 :         [&](const std::string& callId,
     673             :             const std::string& event,
     674             :             const std::vector<std::map<std::string, std::string>>&) {
     675          15 :             auto user = getUserAlias(callId);
     676          15 :             if (not user.empty())
     677          30 :                 onMediaNegotiationStatus(callId,
     678             :                                          event,
     679          30 :                                          user == aliceData_.alias_ ? aliceData_ : bobData_);
     680          15 :         }));
     681             : 
     682           4 :     libjami::registerSignalHandlers(signalHandlers);
     683           4 : }
     684             : 
     685             : void
     686           4 : AutoAnswerMediaNegoTest::testWithScenario(CallData& aliceData,
     687             :                                           CallData& bobData,
     688             :                                           const TestScenario& scenario)
     689             : {
     690           4 :     JAMI_INFO("=== Start a call and validate ===");
     691             : 
     692             :     // The media count of the offer and answer must match (RFC-3264).
     693           4 :     auto mediaCount = scenario.offer_.size();
     694           4 :     CPPUNIT_ASSERT_EQUAL(mediaCount, scenario.answer_.size());
     695             : 
     696           8 :     aliceData.callId_ = libjami::placeCallWithMedia(aliceData.accountId_,
     697           4 :                                                   isSipAccount_ ? bobData.toUri_
     698             :                                                                 : bobData_.userName_,
     699           8 :                                                   MediaAttribute::mediaAttributesToMediaMaps(
     700           8 :                                                       scenario.offer_));
     701           4 :     CPPUNIT_ASSERT(not aliceData.callId_.empty());
     702             :     auto aliceCall = std::static_pointer_cast<SIPCall>(
     703           4 :         Manager::instance().getCallFromCallID(aliceData.callId_));
     704             : 
     705           4 :     CPPUNIT_ASSERT(aliceCall);
     706             : 
     707           4 :     JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer",
     708             :               aliceData.accountId_.c_str(),
     709             :               bobData.accountId_.c_str());
     710             : 
     711             :     // Wait for incoming call signal.
     712           4 :     CPPUNIT_ASSERT(waitForSignal(bobData, libjami::CallSignal::IncomingCallWithMedia::name));
     713             : 
     714             :     // Bob automatically answers the call.
     715             : 
     716             :     // Wait for media negotiation complete signal.
     717           4 :     CPPUNIT_ASSERT_EQUAL(
     718             :         true,
     719             :         waitForSignal(bobData,
     720             :                       libjami::CallSignal::MediaNegotiationStatus::name,
     721             :                       libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
     722             :     // Wait for the StateChange signal.
     723           4 :     CPPUNIT_ASSERT_EQUAL(true,
     724             :                          waitForSignal(bobData,
     725             :                                        libjami::CallSignal::StateChange::name,
     726             :                                        StateEvent::CURRENT));
     727             : 
     728           4 :     JAMI_INFO("BOB answered the call [%s]", bobData.callId_.c_str());
     729             : 
     730             :     // Wait for media negotiation complete signal.
     731           4 :     CPPUNIT_ASSERT_EQUAL(
     732             :         true,
     733             :         waitForSignal(aliceData,
     734             :                       libjami::CallSignal::MediaNegotiationStatus::name,
     735             :                       libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
     736             : 
     737             :     // Validate Alice's media
     738             :     {
     739           4 :         auto mediaList = aliceCall->getMediaAttributeList();
     740           4 :         CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size());
     741             : 
     742             :         // Validate mute state
     743           4 :         CPPUNIT_ASSERT(validateMuteState(scenario.offer_, mediaList));
     744             : 
     745           4 :         auto& sdp = aliceCall->getSDP();
     746             : 
     747             :         // Validate local media direction
     748             :         {
     749           4 :             auto descrList = sdp.getActiveMediaDescription(false);
     750           4 :             CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size());
     751             :             // For Alice, local is the offer and remote is the answer.
     752           4 :             CPPUNIT_ASSERT(validateMediaDirection(descrList, scenario.offer_, scenario.answer_));
     753           4 :         }
     754           4 :     }
     755             : 
     756             :     // Validate Bob's media
     757             :     {
     758             :         auto const& bobCall = std::dynamic_pointer_cast<SIPCall>(
     759           4 :             Manager::instance().getCallFromCallID(bobData.callId_));
     760           4 :         auto mediaList = bobCall->getMediaAttributeList();
     761           4 :         CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size());
     762             : 
     763             :         // Validate mute state
     764           4 :         CPPUNIT_ASSERT(validateMuteState(scenario.answer_, mediaList));
     765             : 
     766           4 :         auto& sdp = bobCall->getSDP();
     767             : 
     768             :         // Validate local media direction
     769             :         {
     770           4 :             auto descrList = sdp.getActiveMediaDescription(false);
     771           4 :             CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size());
     772             :             // For Bob, local is the answer and remote is the offer.
     773           4 :             CPPUNIT_ASSERT(validateMediaDirection(descrList, scenario.answer_, scenario.offer_));
     774           4 :         }
     775           4 :     }
     776             : 
     777           4 :     std::this_thread::sleep_for(std::chrono::seconds(3));
     778             : 
     779           4 :     JAMI_INFO("=== Request Media Change and validate ===");
     780             :     {
     781           4 :         auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(scenario.offerUpdate_);
     782           4 :         libjami::requestMediaChange(aliceData.accountId_, aliceData.callId_, mediaList);
     783           4 :     }
     784             : 
     785             :     // Update and validate media count.
     786           4 :     mediaCount = scenario.offerUpdate_.size();
     787           4 :     CPPUNIT_ASSERT_EQUAL(mediaCount, scenario.answerUpdate_.size());
     788             : 
     789           4 :     if (scenario.expectMediaRenegotiation_) {
     790             :         // Wait for media negotiation complete signal.
     791           2 :         CPPUNIT_ASSERT_EQUAL(
     792             :             true,
     793             :             waitForSignal(aliceData,
     794             :                           libjami::CallSignal::MediaNegotiationStatus::name,
     795             :                           libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
     796             : 
     797             :         // Validate Alice's media
     798             :         {
     799           2 :             auto mediaList = aliceCall->getMediaAttributeList();
     800           2 :             CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size());
     801             : 
     802             :             // Validate mute state
     803           2 :             CPPUNIT_ASSERT(validateMuteState(scenario.offerUpdate_, mediaList));
     804             : 
     805           2 :             auto& sdp = aliceCall->getSDP();
     806             : 
     807             :             // Validate local media direction
     808             :             {
     809           2 :                 auto descrList = sdp.getActiveMediaDescription(false);
     810           2 :                 CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size());
     811           2 :                 CPPUNIT_ASSERT(validateMediaDirection(descrList,
     812             :                                                       scenario.offerUpdate_,
     813             :                                                       scenario.answerUpdate_));
     814           2 :             }
     815             :             // Validate remote media direction
     816             :             {
     817           2 :                 auto descrList = sdp.getActiveMediaDescription(true);
     818           2 :                 CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size());
     819           2 :                 CPPUNIT_ASSERT(validateMediaDirection(descrList,
     820             :                                                       scenario.answerUpdate_,
     821             :                                                       scenario.offerUpdate_));
     822           2 :             }
     823           2 :         }
     824             : 
     825             :         // Validate Bob's media
     826             :         {
     827             :             auto const& bobCall = std::dynamic_pointer_cast<SIPCall>(
     828           2 :                 Manager::instance().getCallFromCallID(bobData.callId_));
     829           2 :             auto mediaList = bobCall->getMediaAttributeList();
     830           2 :             CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size());
     831             : 
     832             :             // Validate mute state
     833           2 :             CPPUNIT_ASSERT(validateMuteState(scenario.answerUpdate_, mediaList));
     834             : 
     835             :             // NOTE:
     836             :             // It should be enough to validate media direction on Alice's side
     837           2 :         }
     838             :     }
     839             : 
     840           4 :     std::this_thread::sleep_for(std::chrono::seconds(3));
     841             : 
     842             :     // Bob hang-up.
     843           4 :     JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up");
     844           4 :     libjami::hangUp(bobData.accountId_, bobData.callId_);
     845             : 
     846           4 :     CPPUNIT_ASSERT_EQUAL(true,
     847             :                          waitForSignal(aliceData,
     848             :                                        libjami::CallSignal::StateChange::name,
     849             :                                        StateEvent::HUNGUP));
     850             : 
     851           4 :     JAMI_INFO("Call terminated on both sides");
     852           4 : }
     853             : 
     854             : void
     855           1 : AutoAnswerMediaNegoTest::audio_and_video_then_caller_mute_video()
     856             : {
     857           1 :     JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
     858             : 
     859           1 :     configureScenario();
     860             : 
     861           1 :     MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
     862           1 :     defaultAudio.label_ = "audio_0";
     863           1 :     defaultAudio.enabled_ = true;
     864             : 
     865           1 :     MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
     866           1 :     defaultVideo.label_ = "video_0";
     867           1 :     defaultVideo.enabled_ = true;
     868             : 
     869           1 :     MediaAttribute audio(defaultAudio);
     870           1 :     MediaAttribute video(defaultVideo);
     871             : 
     872           1 :     TestScenario scenario;
     873             :     // First offer/answer
     874           1 :     scenario.offer_.emplace_back(audio);
     875           1 :     scenario.offer_.emplace_back(video);
     876           1 :     scenario.answer_.emplace_back(audio);
     877           1 :     scenario.answer_.emplace_back(video);
     878             : 
     879             :     // Updated offer/answer
     880           1 :     scenario.offerUpdate_.emplace_back(audio);
     881           1 :     video.muted_ = true;
     882           1 :     scenario.offerUpdate_.emplace_back(video);
     883             : 
     884           1 :     scenario.answerUpdate_.emplace_back(audio);
     885           1 :     video.muted_ = false;
     886           1 :     scenario.answerUpdate_.emplace_back(video);
     887           1 :     scenario.expectMediaRenegotiation_ = true;
     888             : 
     889           1 :     testWithScenario(aliceData_, bobData_, scenario);
     890             : 
     891           1 :     libjami::unregisterSignalHandlers();
     892             : 
     893           1 :     JAMI_INFO("=== End test %s ===", __FUNCTION__);
     894           1 : }
     895             : 
     896             : void
     897           1 : AutoAnswerMediaNegoTest::audio_only_then_caller_add_video()
     898             : {
     899           1 :     JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
     900             : 
     901           1 :     configureScenario();
     902             : 
     903           1 :     MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
     904           1 :     defaultAudio.label_ = "audio_0";
     905           1 :     defaultAudio.enabled_ = true;
     906             : 
     907           1 :     MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
     908           1 :     defaultVideo.label_ = "video_0";
     909           1 :     defaultVideo.enabled_ = true;
     910             : 
     911           1 :     MediaAttribute audio(defaultAudio);
     912           1 :     MediaAttribute video(defaultVideo);
     913             : 
     914           1 :     TestScenario scenario;
     915             :     // First offer/answer
     916           1 :     scenario.offer_.emplace_back(audio);
     917           1 :     scenario.answer_.emplace_back(audio);
     918             : 
     919             :     // Updated offer/answer
     920           1 :     scenario.offerUpdate_.emplace_back(audio);
     921           1 :     scenario.offerUpdate_.emplace_back(video);
     922           1 :     scenario.answerUpdate_.emplace_back(audio);
     923           1 :     scenario.answerUpdate_.emplace_back(video);
     924             : 
     925           1 :     testWithScenario(aliceData_, bobData_, scenario);
     926             : 
     927           1 :     libjami::unregisterSignalHandlers();
     928             : 
     929           1 :     JAMI_INFO("=== End test %s ===", __FUNCTION__);
     930           1 : }
     931             : 
     932             : void
     933           1 : AutoAnswerMediaNegoTest::audio_and_video_then_caller_mute_audio()
     934             : {
     935           1 :     JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
     936             : 
     937           1 :     configureScenario();
     938             : 
     939           1 :     MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
     940           1 :     defaultAudio.label_ = "audio_0";
     941           1 :     defaultAudio.enabled_ = true;
     942             : 
     943           1 :     MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
     944           1 :     defaultVideo.label_ = "video_0";
     945           1 :     defaultVideo.enabled_ = true;
     946             : 
     947           1 :     MediaAttribute audio(defaultAudio);
     948           1 :     MediaAttribute video(defaultVideo);
     949             : 
     950           1 :     TestScenario scenario;
     951             :     // First offer/answer
     952           1 :     scenario.offer_.emplace_back(audio);
     953           1 :     scenario.offer_.emplace_back(video);
     954           1 :     scenario.answer_.emplace_back(audio);
     955           1 :     scenario.answer_.emplace_back(video);
     956             : 
     957             :     // Updated offer/answer
     958           1 :     audio.muted_ = true;
     959           1 :     scenario.offerUpdate_.emplace_back(audio);
     960           1 :     scenario.offerUpdate_.emplace_back(video);
     961             : 
     962           1 :     audio.muted_ = false;
     963           1 :     scenario.answerUpdate_.emplace_back(audio);
     964           1 :     scenario.answerUpdate_.emplace_back(video);
     965             : 
     966           1 :     scenario.expectMediaRenegotiation_ = false;
     967             : 
     968           1 :     testWithScenario(aliceData_, bobData_, scenario);
     969             : 
     970           1 :     libjami::unregisterSignalHandlers();
     971             : 
     972           1 :     JAMI_INFO("=== End test %s ===", __FUNCTION__);
     973           1 : }
     974             : 
     975             : void
     976           1 : AutoAnswerMediaNegoTest::audio_and_video_then_change_video_source()
     977             : {
     978           1 :     JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
     979             : 
     980           1 :     configureScenario();
     981             : 
     982           1 :     MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
     983           1 :     defaultAudio.label_ = "audio_0";
     984           1 :     defaultAudio.enabled_ = true;
     985             : 
     986           1 :     MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
     987           1 :     defaultVideo.label_ = "video_0";
     988           1 :     defaultVideo.enabled_ = true;
     989             : 
     990           1 :     MediaAttribute audio(defaultAudio);
     991           1 :     MediaAttribute video(defaultVideo);
     992             : 
     993           1 :     TestScenario scenario;
     994             :     // First offer/answer
     995           1 :     scenario.offer_.emplace_back(audio);
     996           1 :     scenario.offer_.emplace_back(video);
     997           1 :     scenario.answer_.emplace_back(audio);
     998           1 :     scenario.answer_.emplace_back(video);
     999             : 
    1000             :     // Updated offer/answer
    1001           1 :     scenario.offerUpdate_.emplace_back(audio);
    1002             :     // Just change the media source to validate that a new
    1003             :     // media negotiation (re-invite) will be triggered.
    1004           1 :     video.sourceUri_ = "Fake source";
    1005           1 :     scenario.offerUpdate_.emplace_back(video);
    1006             : 
    1007           1 :     scenario.answerUpdate_.emplace_back(audio);
    1008           1 :     scenario.answerUpdate_.emplace_back(video);
    1009             : 
    1010           1 :     scenario.expectMediaRenegotiation_ = true;
    1011             : 
    1012           1 :     testWithScenario(aliceData_, bobData_, scenario);
    1013             : 
    1014           1 :     libjami::unregisterSignalHandlers();
    1015             : 
    1016           1 :     JAMI_INFO("=== End test %s ===", __FUNCTION__);
    1017           1 : }
    1018             : 
    1019             : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AutoAnswerMediaNegoTestSip,
    1020             :                                       AutoAnswerMediaNegoTestSip::name());
    1021             : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AutoAnswerMediaNegoTestJami,
    1022             :                                       AutoAnswerMediaNegoTestJami::name());
    1023             : 
    1024             : } // namespace test
    1025             : } // namespace jami
    1026             : 
    1027           5 : JAMI_TEST_RUNNER(jami::test::AutoAnswerMediaNegoTestJami::name())

Generated by: LCOV version 1.14