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-12-21 08:56:24 Functions: 39 40 97.5 %

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

Generated by: LCOV version 1.14