LCOV - code coverage report
Current view: top level - test/unitTest/swarm - bootstrap.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 191 207 92.3 %
Date: 2024-05-10 07:56:25 Functions: 39 40 97.5 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2024 Savoir-faire Linux Inc.
       3             :  *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
       4             :  *
       5             :  *  This program is free software; you can redistribute it and/or modify
       6             :  *  it under the terms of the GNU General Public License as published by
       7             :  *  the Free Software Foundation; either version 3 of the License, or
       8             :  *  (at your option) any later version.
       9             :  *
      10             :  *  This program is distributed in the hope that it will be useful,
      11             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :  *  GNU General Public License for more details.
      14             :  *
      15             :  *  You should have received a copy of the GNU General Public License
      16             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      17             :  */
      18             : 
      19             : #include <cppunit/TestAssert.h>
      20             : #include <cppunit/TestFixture.h>
      21             : #include <cppunit/extensions/HelperMacros.h>
      22             : 
      23             : #include <condition_variable>
      24             : #include <msgpack.hpp>
      25             : #include <filesystem>
      26             : 
      27             : #include "../../test_runner.h"
      28             : #include "account_const.h"
      29             : #include "common.h"
      30             : #include "conversation_interface.h"
      31             : #include "fileutils.h"
      32             : #include "jami.h"
      33             : #include "jamidht/conversation.h"
      34             : #include "jamidht/jamiaccount.h"
      35             : #include "jamidht/swarm/swarm_channel_handler.h"
      36             : #include "manager.h"
      37             : 
      38             : using namespace std::string_literals;
      39             : using namespace std::literals::chrono_literals;
      40             : using namespace libjami::Account;
      41             : 
      42             : namespace jami {
      43             : 
      44             : struct ConvInfoTest
      45             : {
      46             :     std::string accountId;
      47             :     std::string convId;
      48             :     bool requestReceived = false;
      49             :     bool conversationReady = false;
      50             :     std::vector<std::map<std::string, std::string>> messages;
      51             :     Conversation::BootstrapStatus bootstrap {Conversation::BootstrapStatus::FAILED};
      52             : };
      53             : 
      54             : namespace test {
      55             : 
      56             : class BootstrapTest : public CppUnit::TestFixture
      57             : {
      58             : public:
      59           8 :     ~BootstrapTest() { libjami::fini(); }
      60           2 :     static std::string name() { return "Bootstrap"; }
      61             :     void setUp();
      62             :     void tearDown();
      63             : 
      64             :     ConvInfoTest aliceData;
      65             :     ConvInfoTest bobData;
      66             :     ConvInfoTest bob2Data;
      67             :     ConvInfoTest carlaData;
      68             : 
      69             :     std::mutex mtx;
      70             :     std::condition_variable cv;
      71             : 
      72             : private:
      73             :     void connectSignals();
      74             : 
      75             :     void testBootstrapOk();
      76             :     void testBootstrapFailed();
      77             :     void testBootstrapNeverNewDevice();
      78             :     void testBootstrapCompat();
      79             : 
      80           2 :     CPPUNIT_TEST_SUITE(BootstrapTest);
      81           1 :     CPPUNIT_TEST(testBootstrapOk);
      82           1 :     CPPUNIT_TEST(testBootstrapFailed);
      83           1 :     CPPUNIT_TEST(testBootstrapNeverNewDevice);
      84           1 :     CPPUNIT_TEST(testBootstrapCompat);
      85           4 :     CPPUNIT_TEST_SUITE_END();
      86             : };
      87             : 
      88             : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(BootstrapTest, BootstrapTest::name());
      89             : 
      90             : void
      91           4 : BootstrapTest::setUp()
      92             : {
      93           4 :     aliceData = {};
      94           4 :     bobData = {};
      95           4 :     bob2Data = {};
      96           4 :     carlaData = {};
      97             : 
      98             :     // Init daemon
      99           4 :     libjami::init(
     100             :         libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
     101           4 :     if (not Manager::instance().initialized)
     102           1 :         CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
     103             : 
     104           4 :     auto actors = load_actors("actors/alice-bob-carla.yml");
     105           4 :     aliceData.accountId = actors["alice"];
     106           4 :     bobData.accountId = actors["bob"];
     107           4 :     carlaData.accountId = actors["carla"];
     108             : 
     109           4 :     Manager::instance().sendRegister(carlaData.accountId, false);
     110          12 :     wait_for_announcement_of({aliceData.accountId, bobData.accountId});
     111           4 :     connectSignals();
     112           4 : }
     113             : 
     114             : void
     115           4 : BootstrapTest::tearDown()
     116             : {
     117           4 :     libjami::unregisterSignalHandlers();
     118           8 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     119           4 :     std::remove(bobArchive.c_str());
     120             : 
     121           4 :     if (bob2Data.accountId.empty()) {
     122          12 :         wait_for_removal_of({aliceData.accountId, bobData.accountId, carlaData.accountId});
     123             :     } else {
     124           6 :         wait_for_removal_of(
     125           1 :             {aliceData.accountId, bobData.accountId, carlaData.accountId, bob2Data.accountId});
     126             :     }
     127           4 : }
     128             : 
     129             : void
     130           4 : BootstrapTest::connectSignals()
     131             : {
     132           4 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
     133           4 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
     134           9 :         [&](const std::string& accountId, const std::string& convId) {
     135           9 :             if (accountId == aliceData.accountId) {
     136           4 :                 aliceData.convId = convId;
     137           4 :                 aliceData.conversationReady = true;
     138           5 :             } else if (accountId == bobData.accountId) {
     139           4 :                 bobData.convId = convId;
     140           4 :                 bobData.conversationReady = true;
     141           1 :             } else if (accountId == bob2Data.accountId) {
     142           1 :                 bob2Data.convId = convId;
     143           1 :                 bob2Data.conversationReady = true;
     144           0 :             } else if (accountId == carlaData.accountId) {
     145           0 :                 carlaData.convId = convId;
     146           0 :                 carlaData.conversationReady = true;
     147             :             }
     148           9 :             cv.notify_one();
     149           9 :         }));
     150           4 :     confHandlers.insert(
     151           8 :         libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
     152           4 :             [&](const std::string& accountId,
     153             :                 const std::string& /* conversationId */,
     154             :                 std::map<std::string, std::string> /*metadatas*/) {
     155           4 :                 if (accountId == aliceData.accountId) {
     156           0 :                     aliceData.requestReceived = true;
     157           4 :                 } else if (accountId == bobData.accountId) {
     158           4 :                     bobData.requestReceived = true;
     159           0 :                 } else if (accountId == bob2Data.accountId) {
     160           0 :                     bob2Data.requestReceived = true;
     161           0 :                 } else if (accountId == carlaData.accountId) {
     162           0 :                     carlaData.requestReceived = true;
     163             :                 }
     164           4 :                 cv.notify_one();
     165           4 :             }));
     166           4 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
     167          10 :         [&](const std::string& accountId,
     168             :             const std::string& /*conversationId*/,
     169             :             std::map<std::string, std::string> message) {
     170          10 :             if (accountId == aliceData.accountId) {
     171           9 :                 aliceData.messages.emplace_back(message);
     172           1 :             } else if (accountId == bobData.accountId) {
     173           1 :                 bobData.messages.emplace_back(message);
     174           0 :             } else if (accountId == bob2Data.accountId) {
     175           0 :                 bob2Data.messages.emplace_back(message);
     176           0 :             } else if (accountId == carlaData.accountId) {
     177           0 :                 carlaData.messages.emplace_back(message);
     178             :             }
     179          10 :             cv.notify_one();
     180          10 :         }));
     181           4 :     libjami::registerSignalHandlers(confHandlers);
     182             : 
     183             :     // Link callback for convModule()
     184           4 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
     185           4 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
     186           4 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaData.accountId);
     187             : 
     188           4 :     aliceAccount->convModule()->onBootstrapStatus(
     189           2 :         [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
     190           2 :             aliceData.bootstrap = status;
     191           2 :             cv.notify_one();
     192           2 :         });
     193           4 :     bobAccount->convModule()->onBootstrapStatus(
     194           6 :         [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
     195           6 :             bobData.bootstrap = status;
     196           6 :             cv.notify_one();
     197           6 :         });
     198           4 :     carlaAccount->convModule()->onBootstrapStatus(
     199           0 :         [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
     200           0 :             carlaData.bootstrap = status;
     201           0 :             cv.notify_one();
     202           0 :         });
     203           4 : }
     204             : 
     205             : void
     206           1 : BootstrapTest::testBootstrapOk()
     207             : {
     208           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
     209           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
     210           1 :     auto bobUri = bobAccount->getUsername();
     211             : 
     212           1 :     aliceAccount->convModule()->onBootstrapStatus(
     213           3 :         [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
     214           3 :             aliceData.bootstrap = status;
     215           3 :             cv.notify_one();
     216           3 :         });
     217             : 
     218           1 :     std::unique_lock lk {mtx};
     219           1 :     auto convId = libjami::startConversation(aliceData.accountId);
     220             : 
     221           1 :     libjami::addConversationMember(aliceData.accountId, convId, bobUri);
     222           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     223             : 
     224           1 :     auto aliceMsgSize = aliceData.messages.size();
     225           1 :     libjami::acceptConversationRequest(bobData.accountId, convId);
     226           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
     227             :         return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1
     228             :                && aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS
     229             :                && bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS;
     230             :     }));
     231           1 : }
     232             : 
     233             : void
     234           1 : BootstrapTest::testBootstrapFailed()
     235             : {
     236           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
     237           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
     238           1 :     auto bobUri = bobAccount->getUsername();
     239             : 
     240           1 :     aliceAccount->convModule()->onBootstrapStatus(
     241           5 :         [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
     242           5 :             aliceData.bootstrap = status;
     243           5 :             cv.notify_one();
     244           5 :         });
     245             : 
     246           1 :     std::unique_lock lk {mtx};
     247           1 :     auto convId = libjami::startConversation(aliceData.accountId);
     248             : 
     249           1 :     libjami::addConversationMember(aliceData.accountId, convId, bobUri);
     250           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     251             : 
     252           1 :     auto aliceMsgSize = aliceData.messages.size();
     253           1 :     libjami::acceptConversationRequest(bobData.accountId, convId);
     254           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
     255             :         return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1
     256             :                && aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS
     257             :                && bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS;
     258             :     }));
     259             : 
     260             :     // Now bob goes offline, it should disconnect alice
     261           1 :     Manager::instance().sendRegister(bobData.accountId, false);
     262             :     // Alice will try to maintain before failing (so will take 30secs to fail)
     263           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
     264             :         return aliceData.bootstrap == Conversation::BootstrapStatus::FAILED;
     265             :     }));
     266           1 : }
     267             : 
     268             : void
     269           1 : BootstrapTest::testBootstrapNeverNewDevice()
     270             : {
     271           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
     272           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
     273           1 :     auto bobUri = bobAccount->getUsername();
     274             : 
     275           1 :     aliceAccount->convModule()->onBootstrapStatus(
     276           3 :         [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
     277           3 :             aliceData.bootstrap = status;
     278           3 :             cv.notify_one();
     279           3 :         });
     280             : 
     281           1 :     std::unique_lock lk {mtx};
     282           1 :     auto convId = libjami::startConversation(aliceData.accountId);
     283             : 
     284           1 :     libjami::addConversationMember(aliceData.accountId, convId, bobUri);
     285           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     286             : 
     287           1 :     auto aliceMsgSize = aliceData.messages.size();
     288           1 :     libjami::acceptConversationRequest(bobData.accountId, convId);
     289           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
     290             :         return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1
     291             :                && aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS
     292             :                && bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS;
     293             :     }));
     294             : 
     295             :     // Alice offline
     296           1 :     Manager::instance().sendRegister(aliceData.accountId, false);
     297             :     // Bob will try to maintain before failing (so will take 30secs to fail)
     298           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
     299             :         return bobData.bootstrap == Conversation::BootstrapStatus::FAILED;
     300             :     }));
     301             : 
     302             :     // Create bob2
     303           2 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     304           1 :     std::remove(bobArchive.c_str());
     305           1 :     bobAccount->exportArchive(bobArchive);
     306           2 :     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
     307           1 :     details[ConfProperties::TYPE] = "RING";
     308           1 :     details[ConfProperties::DISPLAYNAME] = "BOB2";
     309           1 :     details[ConfProperties::ALIAS] = "BOB2";
     310           1 :     details[ConfProperties::UPNP_ENABLED] = "true";
     311           1 :     details[ConfProperties::ARCHIVE_PASSWORD] = "";
     312           1 :     details[ConfProperties::ARCHIVE_PIN] = "";
     313           1 :     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     314           1 :     bob2Data.accountId = Manager::instance().addAccount(details);
     315           1 :     auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Data.accountId);
     316             : 
     317           1 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
     318           1 :     bool bob2Connected = false;
     319           1 :     confHandlers.insert(
     320           2 :         libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
     321           6 :             [&](const std::string&, const std::map<std::string, std::string>&) {
     322           6 :                 auto details = bob2Account->getVolatileAccountDetails();
     323          12 :                 auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
     324           6 :                 if (daemonStatus != "UNREGISTERED")
     325           5 :                     bob2Connected = true;
     326           6 :                 cv.notify_one();
     327           6 :             }));
     328           1 :     libjami::registerSignalHandlers(confHandlers);
     329             : 
     330           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Connected; }));
     331           1 :     bob2Account->convModule()->onBootstrapStatus(
     332           2 :         [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
     333           2 :             bob2Data.bootstrap = status;
     334           2 :             cv.notify_one();
     335           2 :         });
     336             : 
     337             :     // Disconnect bob2, to create a valid conv betwen Alice and Bob1
     338           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
     339             :         return bobData.bootstrap == Conversation::BootstrapStatus::SUCCESS
     340             :                && bob2Data.bootstrap == Conversation::BootstrapStatus::SUCCESS;
     341             :     }));
     342             : 
     343             :     // Bob offline
     344           1 :     Manager::instance().sendRegister(bobData.accountId, false);
     345             :     // Bob2 will try to maintain before failing (so will take 30secs to fail)
     346           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
     347             :         return bob2Data.bootstrap == Conversation::BootstrapStatus::FAILED;
     348             :     }));
     349             : 
     350             :     // Alice bootstrap should go to fallback (because bob2 never wrote into the conversation) & Connected
     351           1 :     Manager::instance().sendRegister(aliceData.accountId, true);
     352             :     // Wait for announcement, ICE fallback + delay
     353           2 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
     354             :         return aliceData.bootstrap == Conversation::BootstrapStatus::SUCCESS;
     355             :     }));
     356           1 : }
     357             : 
     358             : void
     359           1 : BootstrapTest::testBootstrapCompat()
     360             : {
     361           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId);
     362           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId);
     363           1 :     auto bobUri = bobAccount->getUsername();
     364             : 
     365           1 :     dynamic_cast<SwarmChannelHandler*>(aliceAccount->channelHandlers()[Uri::Scheme::SWARM].get())
     366             :         ->disableSwarmManager
     367           1 :         = true;
     368             : 
     369           1 :     std::unique_lock lk {mtx};
     370           1 :     auto convId = libjami::startConversation(aliceData.accountId);
     371             : 
     372           1 :     libjami::addConversationMember(aliceData.accountId, convId, bobUri);
     373           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     374             : 
     375           1 :     auto aliceMsgSize = aliceData.messages.size();
     376           1 :     libjami::acceptConversationRequest(bobData.accountId, convId);
     377           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
     378             :         return bobData.conversationReady && aliceData.messages.size() == aliceMsgSize + 1;
     379             :     }));
     380             : 
     381           1 :     auto bobMsgSize = bobData.messages.size();
     382           1 :     libjami::sendMessage(aliceData.accountId, convId, "hi"s, "");
     383           1 :     cv.wait_for(lk, 30s, [&]() {
     384           3 :         return bobData.messages.size() == bobMsgSize + 1
     385           3 :                && bobData.bootstrap == Conversation::BootstrapStatus::FAILED;
     386             :     });
     387           1 : }
     388             : 
     389             : } // namespace test
     390             : } // namespace jami
     391             : 
     392           1 : RING_TEST_RUNNER(jami::test::BootstrapTest::name())

Generated by: LCOV version 1.14