LCOV - code coverage report
Current view: top level - test/unitTest/conversation - conversationMembersEvent.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 1173 1195 98.2 %
Date: 2024-11-15 09:04:49 Functions: 204 206 99.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include <cppunit/TestAssert.h>
      19             : #include <cppunit/TestFixture.h>
      20             : #include <cppunit/extensions/HelperMacros.h>
      21             : 
      22             : #include <condition_variable>
      23             : #include <string>
      24             : #include <fstream>
      25             : #include <streambuf>
      26             : #include <git2.h>
      27             : #include <filesystem>
      28             : #include <msgpack.hpp>
      29             : 
      30             : #include "manager.h"
      31             : #include "../../test_runner.h"
      32             : #include "jami.h"
      33             : #include "base64.h"
      34             : #include "fileutils.h"
      35             : #include "account_const.h"
      36             : #include "common.h"
      37             : #include "conversation/conversationcommon.h"
      38             : 
      39             : using namespace std::string_literals;
      40             : using namespace std::literals::chrono_literals;
      41             : using namespace libjami::Account;
      42             : 
      43             : namespace jami {
      44             : namespace test {
      45             : 
      46             : struct UserData {
      47             :     std::string conversationId;
      48             :     bool removed {false};
      49             :     bool requestReceived {false};
      50             :     bool requestRemoved {false};
      51             :     bool errorDetected {false};
      52             :     bool registered {false};
      53             :     bool stopped {false};
      54             :     bool deviceAnnounced {false};
      55             :     bool contactRemoved {false};
      56             :     bool contactAdded {false};
      57             :     Conversation::BootstrapStatus bootstrap {Conversation::BootstrapStatus::FAILED};
      58             :     std::string payloadTrustRequest;
      59             :     std::vector<libjami::SwarmMessage> messages;
      60             :     std::vector<libjami::SwarmMessage> messagesUpdated;
      61             :     std::map<std::string, int> members;
      62             : };
      63             : 
      64             : class ConversationMembersEventTest : public CppUnit::TestFixture
      65             : {
      66             : public:
      67          74 :     ~ConversationMembersEventTest() { libjami::fini(); }
      68           2 :     static std::string name() { return "ConversationMembersEventTest"; }
      69             :     void setUp();
      70             :     void tearDown();
      71             :     void generateFakeInvite(std::shared_ptr<JamiAccount> account,
      72             :                             const std::string& convId,
      73             :                             const std::string& uri);
      74             : 
      75             :     void testAddInvalidUri();
      76             :     void testRemoveConversationNoMember();
      77             :     void testRemoveConversationWithMember();
      78             :     void testAddMember();
      79             :     void testMemberAddedNoBadFile();
      80             :     void testAddOfflineMemberThenConnects();
      81             :     void testAddAcceptOfflineThenConnects();
      82             :     void testGetMembers();
      83             :     void testRemoveMember();
      84             :     void testRemovedMemberDoesNotReceiveMessageFromAdmin();
      85             :     void testRemovedMemberDoesNotReceiveMessageFromPeer();
      86             :     void testRemoveInvitedMember();
      87             :     void testMemberBanNoBadFile();
      88             :     void testMemberTryToRemoveAdmin();
      89             :     void testBannedMemberCannotSendMessage();
      90             :     void testAdminCanReAddMember();
      91             :     void testMemberCannotBanOther();
      92             :     void testMemberCannotUnBanOther();
      93             :     void testCheckAdminFakeAVoteIsDetected();
      94             :     void testAdminCannotKickTheirself();
      95             :     void testCommitUnauthorizedUser();
      96             :     void testMemberJoinsNoBadFile();
      97             :     void testMemberAddedNoCertificate();
      98             :     void testMemberJoinsInviteRemoved();
      99             :     void testFailAddMemberInOneToOne();
     100             :     void testOneToOneFetchWithNewMemberRefused();
     101             :     void testConversationMemberEvent();
     102             :     void testGetConversationsMembersWhileSyncing();
     103             :     void testGetConversationMembersWithSelfOneOne();
     104             :     void testAvoidTwoOneToOne();
     105             :     void testAvoidTwoOneToOneMultiDevices();
     106             :     void testRemoveRequestBannedMultiDevices();
     107             :     void testBanUnbanMultiDevice();
     108             :     void testBanUnbanGotFirstConv();
     109             :     void testBanHostWhileHosting();
     110             :     void testAddContactTwice();
     111             :     void testBanFromNewDevice();
     112             : 
     113             :     std::string aliceId;
     114             :     UserData aliceData;
     115             :     std::string bobId;
     116             :     UserData bobData;
     117             :     std::string bob2Id;
     118             :     UserData bob2Data;
     119             :     std::string carlaId;
     120             :     UserData carlaData;
     121             :     std::mutex mtx;
     122             :     std::unique_lock<std::mutex> lk {mtx};
     123             :     std::condition_variable cv;
     124             : 
     125             :     void connectSignals();
     126             : 
     127             : private:
     128           2 :     CPPUNIT_TEST_SUITE(ConversationMembersEventTest);
     129           1 :     CPPUNIT_TEST(testAddInvalidUri);
     130           1 :     CPPUNIT_TEST(testRemoveConversationNoMember);
     131           1 :     CPPUNIT_TEST(testRemoveConversationWithMember);
     132           1 :     CPPUNIT_TEST(testAddMember);
     133           1 :     CPPUNIT_TEST(testMemberAddedNoBadFile);
     134           1 :     CPPUNIT_TEST(testAddOfflineMemberThenConnects);
     135           1 :     CPPUNIT_TEST(testAddAcceptOfflineThenConnects);
     136           1 :     CPPUNIT_TEST(testGetMembers);
     137           1 :     CPPUNIT_TEST(testRemoveMember);
     138           1 :     CPPUNIT_TEST(testRemovedMemberDoesNotReceiveMessageFromAdmin);
     139           1 :     CPPUNIT_TEST(testRemovedMemberDoesNotReceiveMessageFromPeer);
     140           1 :     CPPUNIT_TEST(testRemoveInvitedMember);
     141           1 :     CPPUNIT_TEST(testMemberBanNoBadFile);
     142           1 :     CPPUNIT_TEST(testMemberTryToRemoveAdmin);
     143           1 :     CPPUNIT_TEST(testBannedMemberCannotSendMessage);
     144           1 :     CPPUNIT_TEST(testAdminCanReAddMember);
     145           1 :     CPPUNIT_TEST(testMemberCannotBanOther);
     146           1 :     CPPUNIT_TEST(testMemberCannotUnBanOther);
     147           1 :     CPPUNIT_TEST(testCheckAdminFakeAVoteIsDetected);
     148           1 :     CPPUNIT_TEST(testAdminCannotKickTheirself);
     149           1 :     CPPUNIT_TEST(testCommitUnauthorizedUser);
     150           1 :     CPPUNIT_TEST(testMemberJoinsNoBadFile);
     151           1 :     CPPUNIT_TEST(testMemberAddedNoCertificate);
     152           1 :     CPPUNIT_TEST(testMemberJoinsInviteRemoved);
     153           1 :     CPPUNIT_TEST(testFailAddMemberInOneToOne);
     154           1 :     CPPUNIT_TEST(testOneToOneFetchWithNewMemberRefused);
     155           1 :     CPPUNIT_TEST(testConversationMemberEvent);
     156           1 :     CPPUNIT_TEST(testGetConversationsMembersWhileSyncing);
     157           1 :     CPPUNIT_TEST(testGetConversationMembersWithSelfOneOne);
     158           1 :     CPPUNIT_TEST(testAvoidTwoOneToOne);
     159           1 :     CPPUNIT_TEST(testAvoidTwoOneToOneMultiDevices);
     160           1 :     CPPUNIT_TEST(testRemoveRequestBannedMultiDevices);
     161           1 :     CPPUNIT_TEST(testBanUnbanMultiDevice);
     162           1 :     CPPUNIT_TEST(testBanUnbanGotFirstConv);
     163           1 :     CPPUNIT_TEST(testBanHostWhileHosting);
     164           1 :     CPPUNIT_TEST(testAddContactTwice);
     165           1 :     CPPUNIT_TEST(testBanFromNewDevice);
     166           4 :     CPPUNIT_TEST_SUITE_END();
     167             : };
     168             : 
     169             : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConversationMembersEventTest,
     170             :                                       ConversationMembersEventTest::name());
     171             : 
     172             : void
     173          37 : ConversationMembersEventTest::setUp()
     174             : {
     175          37 :     connectSignals();
     176             : 
     177             :     // Init daemon
     178          37 :     libjami::init(
     179             :         libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
     180          37 :     if (not Manager::instance().initialized)
     181           1 :         CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
     182             : 
     183          37 :     auto actors = load_actors("actors/alice-bob-carla.yml");
     184          37 :     aliceId = actors["alice"];
     185          37 :     bobId = actors["bob"];
     186          37 :     carlaId = actors["carla"];
     187             : 
     188          37 :     aliceData = {};
     189          37 :     bobData = {};
     190          37 :     bob2Data = {};
     191          37 :     carlaData = {};
     192             : 
     193          37 :     Manager::instance().sendRegister(carlaId, false);
     194         111 :     wait_for_announcement_of({aliceId, bobId});
     195          37 : }
     196             : 
     197             : void
     198          37 : ConversationMembersEventTest::tearDown()
     199             : {
     200          37 :     connectSignals();
     201             : 
     202          74 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     203          37 :     std::remove(bobArchive.c_str());
     204          37 :     if (bob2Id.empty()) {
     205         128 :         wait_for_removal_of({aliceId, bobId, carlaId});
     206             :     } else {
     207          25 :         wait_for_removal_of({aliceId, bobId, carlaId, bob2Id});
     208             :     }
     209          37 : }
     210             : 
     211             : void
     212         111 : ConversationMembersEventTest::connectSignals()
     213             : {
     214         111 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
     215         111 :     confHandlers.insert(
     216         222 :         libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
     217         177 :             [&](const std::string& accountId, const std::map<std::string, std::string>&) {
     218         177 :                 if (accountId == aliceId) {
     219           4 :                     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     220           4 :                     auto details = aliceAccount->getVolatileAccountDetails();
     221           8 :                     auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
     222           4 :                     if (daemonStatus == "REGISTERED") {
     223           1 :                         aliceData.registered = true;
     224           3 :                     } else if (daemonStatus == "UNREGISTERED") {
     225           2 :                         aliceData.stopped = true;
     226             :                     }
     227           8 :                     auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
     228           4 :                     aliceData.deviceAnnounced = deviceAnnounced == "true";
     229         177 :                 } else if (accountId == bobId) {
     230           0 :                     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     231           0 :                     auto details = bobAccount->getVolatileAccountDetails();
     232           0 :                     auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
     233           0 :                     if (daemonStatus == "REGISTERED") {
     234           0 :                         bobData.registered = true;
     235           0 :                     } else if (daemonStatus == "UNREGISTERED") {
     236           0 :                         bobData.stopped = true;
     237             :                     }
     238           0 :                     auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
     239           0 :                     bobData.deviceAnnounced = deviceAnnounced == "true";
     240         173 :                 } else if (accountId == bob2Id) {
     241          21 :                     auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
     242          21 :                     auto details = bob2Account->getVolatileAccountDetails();
     243          42 :                     auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
     244          21 :                     if (daemonStatus == "REGISTERED") {
     245          10 :                         bob2Data.registered = true;
     246          11 :                     } else if (daemonStatus == "UNREGISTERED") {
     247           6 :                         bob2Data.stopped = true;
     248             :                     }
     249          42 :                     auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
     250          21 :                     bob2Data.deviceAnnounced = deviceAnnounced == "true";
     251         173 :                 } else if (accountId == carlaId) {
     252          36 :                     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     253          36 :                     auto details = carlaAccount->getVolatileAccountDetails();
     254          72 :                     auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
     255          36 :                     if (daemonStatus == "REGISTERED") {
     256          23 :                         carlaData.registered = true;
     257          13 :                     } else if (daemonStatus == "UNREGISTERED") {
     258           1 :                         carlaData.stopped = true;
     259             :                     }
     260          72 :                     auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
     261          36 :                     carlaData.deviceAnnounced = deviceAnnounced == "true";
     262          36 :                 }
     263         177 :                 cv.notify_one();
     264         177 :             }));
     265         111 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
     266          78 :         [&](const std::string& accountId, const std::string& conversationId) {
     267          78 :             if (accountId == aliceId) {
     268          35 :                 aliceData.conversationId = conversationId;
     269          43 :             } else if (accountId == bobId) {
     270          28 :                 bobData.conversationId = conversationId;
     271          15 :             } else if (accountId == bob2Id) {
     272           6 :                 bob2Data.conversationId = conversationId;
     273           9 :             } else if (accountId == carlaId) {
     274           9 :                 carlaData.conversationId = conversationId;
     275             :             }
     276          78 :             cv.notify_one();
     277          78 :         }));
     278         111 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationMemberEvent>(
     279          92 :         [&](const std::string& accountId, const std::string& conversationId, const auto& member, auto status) {
     280          92 :             if (accountId == aliceId) {
     281          68 :                 aliceData.members[member] = status;
     282          24 :             } else if (accountId == bobId) {
     283          15 :                 bobData.members[member] = status;
     284           9 :             } else if (accountId == bob2Id) {
     285           1 :                 bob2Data.members[member] = status;
     286           8 :             } else if (accountId == carlaId) {
     287           8 :                 carlaData.members[member] = status;
     288             :             }
     289          92 :             cv.notify_one();
     290          92 :         }));
     291         111 :     confHandlers.insert(
     292         222 :         libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
     293          15 :             [&](const std::string& account_id,
     294             :                 const std::string& /*from*/,
     295             :                 const std::string& /*conversationId*/,
     296             :                 const std::vector<uint8_t>& payload,
     297             :                 time_t /*received*/) {
     298          15 :                 auto payloadStr = std::string(payload.data(), payload.data() + payload.size());
     299          15 :                 if (account_id == aliceId)
     300           0 :                     aliceData.payloadTrustRequest = payloadStr;
     301          15 :                 else if (account_id == bobId)
     302          12 :                     bobData.payloadTrustRequest = payloadStr;
     303          15 :                 cv.notify_one();
     304          15 :             }));
     305         111 :     confHandlers.insert(
     306         222 :         libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
     307          46 :             [&](const std::string& accountId,
     308             :                 const std::string& /* conversationId */,
     309             :                 std::map<std::string, std::string> /*metadatas*/) {
     310          46 :                 if (accountId == aliceId) {
     311           1 :                     aliceData.requestReceived = true;
     312          45 :                 } else if (accountId == bobId) {
     313          33 :                     bobData.requestReceived = true;
     314          12 :                 } else if (accountId == bob2Id) {
     315           4 :                     bob2Data.requestReceived = true;
     316           8 :                 } else if (accountId == carlaId) {
     317           8 :                     carlaData.requestReceived = true;
     318             :                 }
     319          46 :                 cv.notify_one();
     320          46 :             }));
     321         111 :     confHandlers.insert(
     322         222 :         libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
     323           4 :             [&](const std::string& accountId, const std::string&) {
     324           4 :                 if (accountId == bobId) {
     325           2 :                     bobData.requestRemoved = true;
     326           2 :                 } else if (accountId == bob2Id) {
     327           2 :                     bob2Data.requestRemoved = true;
     328             :                 }
     329           4 :                 cv.notify_one();
     330           4 :             }));
     331         111 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
     332         125 :         [&](const std::string& accountId,
     333             :             const std::string& /* conversationId */,
     334             :             libjami::SwarmMessage message) {
     335         125 :             if (accountId == aliceId) {
     336          87 :                 aliceData.messages.emplace_back(message);
     337          38 :             } else if (accountId == bobId) {
     338          19 :                 bobData.messages.emplace_back(message);
     339          19 :             } else if (accountId == bob2Id) {
     340           2 :                 bob2Data.messages.emplace_back(message);
     341          17 :             } else if (accountId == carlaId) {
     342          17 :                 carlaData.messages.emplace_back(message);
     343             :             }
     344         125 :             cv.notify_one();
     345         125 :         }));
     346         111 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
     347           0 :         [&](const std::string& accountId,
     348             :             const std::string& /* conversationId */,
     349             :             libjami::SwarmMessage message) {
     350           0 :             if (accountId == aliceId) {
     351           0 :                 aliceData.messagesUpdated.emplace_back(message);
     352           0 :             } else if (accountId == bobId) {
     353           0 :                 bobData.messagesUpdated.emplace_back(message);
     354           0 :             } else if (accountId == carlaId) {
     355           0 :                 carlaData.messagesUpdated.emplace_back(message);
     356             :             }
     357           0 :             cv.notify_one();
     358           0 :         }));
     359         111 :     confHandlers.insert(
     360         222 :         libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
     361          10 :             [&](const std::string& accountId,
     362             :                 const std::string& /* conversationId */,
     363             :                 int /*code*/,
     364             :                 const std::string& /* what */) {
     365          10 :                 if (accountId == aliceId)
     366           4 :                     aliceData.errorDetected = true;
     367           6 :                 else if (accountId == bobId)
     368           5 :                     bobData.errorDetected = true;
     369           1 :                 else if (accountId == carlaId)
     370           1 :                     carlaData.errorDetected = true;
     371          10 :                 cv.notify_one();
     372          10 :             }));
     373         111 :     confHandlers.insert(
     374         222 :         libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
     375          11 :             [&](const std::string& accountId, const std::string&) {
     376          11 :                 if (accountId == aliceId)
     377           3 :                     aliceData.removed = true;
     378           8 :                 else if (accountId == bobId)
     379           5 :                     bobData.removed = true;
     380           3 :                 else if (accountId == bob2Id)
     381           3 :                     bob2Data.removed = true;
     382          11 :                 cv.notify_one();
     383          11 :             }));
     384         111 :     confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
     385          10 :         [&](const std::string& accountId, const std::string&, bool) {
     386          10 :             if (accountId == bobId) {
     387           6 :                 bobData.contactRemoved = true;
     388           4 :             } else if (accountId == bob2Id) {
     389           3 :                 bob2Data.contactRemoved = true;
     390             :             }
     391          10 :             cv.notify_one();
     392          10 :         }));
     393         111 :     confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactAdded>(
     394          31 :         [&](const std::string& accountId, const std::string&, bool) {
     395          31 :             if (accountId == aliceId) {
     396          13 :                 aliceData.contactAdded = true;
     397          18 :             } else if (accountId == bobId) {
     398          12 :                 bobData.contactAdded = true;
     399           6 :             } else if (accountId == bob2Id) {
     400           4 :                 bob2Data.contactAdded = true;
     401             :             }
     402          31 :             cv.notify_one();
     403          31 :         }));
     404         111 :     libjami::registerSignalHandlers(confHandlers);
     405         111 : }
     406             : 
     407             : void
     408           2 : ConversationMembersEventTest::generateFakeInvite(std::shared_ptr<JamiAccount> account,
     409             :                                                  const std::string& convId,
     410             :                                                  const std::string& uri)
     411             : {
     412           4 :     auto repoPath = fileutils::get_data_dir() / account->getAccountID()
     413           8 :                     / "conversations" / convId;
     414             :     // remove from member & add into banned without voting for the ban
     415           4 :     auto memberFile = repoPath / "invited" / uri;
     416           2 :     std::ofstream file(memberFile);
     417           2 :     if (file.is_open()) {
     418           0 :         file.close();
     419             :     }
     420             : 
     421           2 :     git_repository* repo = nullptr;
     422           2 :     if (git_repository_open(&repo, repoPath.c_str()) != 0)
     423           0 :         return;
     424           2 :     GitRepository rep = {std::move(repo), git_repository_free};
     425             : 
     426             :     // git add -A
     427           2 :     git_index* index_ptr = nullptr;
     428           2 :     if (git_repository_index(&index_ptr, repo) < 0)
     429           0 :         return;
     430           2 :     GitIndex index {index_ptr, git_index_free};
     431           2 :     git_strarray array = {nullptr, 0};
     432           2 :     git_index_add_all(index.get(), &array, 0, nullptr, nullptr);
     433           2 :     git_index_write(index.get());
     434           2 :     git_strarray_dispose(&array);
     435             : 
     436           2 :     ConversationRepository cr(account, convId);
     437             : 
     438           2 :     Json::Value json;
     439           2 :     json["action"] = "add";
     440           2 :     json["uri"] = uri;
     441           2 :     json["type"] = "member";
     442           2 :     Json::StreamWriterBuilder wbuilder;
     443           2 :     wbuilder["commentStyle"] = "None";
     444           2 :     wbuilder["indentation"] = "";
     445           2 :     cr.commitMessage(Json::writeString(wbuilder, json));
     446             : 
     447           6 :     libjami::sendMessage(account->getAccountID(),
     448             :                          convId,
     449           4 :                          "trigger the fake history to be pulled"s,
     450             :                          "");
     451           2 : }
     452             : 
     453             : void
     454           1 : ConversationMembersEventTest::testAddInvalidUri()
     455             : {
     456           1 :     connectSignals();
     457             : 
     458           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     459           1 :     aliceAccount->addContact("Shitty/Uri");
     460           3 :     CPPUNIT_ASSERT(!cv.wait_for(lk, 5s, [&]() { return aliceData.contactAdded; }));
     461           1 :     CPPUNIT_ASSERT(aliceData.conversationId.empty());
     462           1 : }
     463             : 
     464             : void
     465           1 : ConversationMembersEventTest::testRemoveConversationNoMember()
     466             : {
     467           1 :     connectSignals();
     468             : 
     469             :     // Start conversation
     470           1 :     auto convId = libjami::startConversation(aliceId);
     471           2 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
     472             : 
     473             :     // Assert that repository exists
     474           2 :     auto repoPath = fileutils::get_data_dir() / aliceId
     475           4 :                     / "conversations" / convId;
     476           2 :     auto dataPath = fileutils::get_data_dir() / aliceId
     477           4 :                     / "conversation_data" / convId;
     478           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     479           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(dataPath));
     480             : 
     481           1 :     auto conversations = libjami::getConversations(aliceId);
     482           1 :     CPPUNIT_ASSERT(conversations.size() == 1);
     483             :     // Removing the conversation will erase all related files
     484           1 :     CPPUNIT_ASSERT(libjami::removeConversation(aliceId, convId));
     485           1 :     conversations = libjami::getConversations(aliceId);
     486           1 :     CPPUNIT_ASSERT(conversations.size() == 0);
     487           1 :     CPPUNIT_ASSERT(!std::filesystem::is_directory(repoPath));
     488           1 :     CPPUNIT_ASSERT(!std::filesystem::is_directory(dataPath));
     489           1 : }
     490             : 
     491             : void
     492           1 : ConversationMembersEventTest::testRemoveConversationWithMember()
     493             : {
     494           1 :     connectSignals();
     495             : 
     496           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     497           1 :     auto bobUri = bobAccount->getUsername();
     498           1 :     auto convId = libjami::startConversation(aliceId);
     499             : 
     500           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     501           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     502             : 
     503             :     // Assert that repository exists
     504           2 :     auto repoPath = fileutils::get_data_dir() / aliceId
     505           4 :                     / "conversations" / convId;
     506           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     507             :     // Check created files
     508           2 :     auto bobInvitedFile = repoPath / "invited" / bobUri;
     509           1 :     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvitedFile));
     510             : 
     511           1 :     auto aliceMsgSize = aliceData.messages.size();
     512           1 :     libjami::acceptConversationRequest(bobId, convId);
     513           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     514           2 :     auto clonedPath = fileutils::get_data_dir() / bobId
     515           4 :                       / "conversations" / convId;
     516           1 :     bobInvitedFile = clonedPath / "invited" / bobUri;
     517           1 :     CPPUNIT_ASSERT(!std::filesystem::is_regular_file(bobInvitedFile));
     518             :     // Remove conversation from alice once member confirmed
     519             : 
     520           1 :     auto bobMsgSize = bobData.messages.size();
     521           1 :     libjami::removeConversation(aliceId, convId);
     522           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size() && bobData.messages.rbegin()->type == "member"; }));
     523           1 :     std::this_thread::sleep_for(3s);
     524           1 :     CPPUNIT_ASSERT(!std::filesystem::is_directory(repoPath));
     525           1 : }
     526             : 
     527             : void
     528           1 : ConversationMembersEventTest::testAddMember()
     529             : {
     530           1 :     connectSignals();
     531             : 
     532           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     533           1 :     auto bobUri = bobAccount->getUsername();
     534           1 :     auto convId = libjami::startConversation(aliceId);
     535             : 
     536           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     537           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     538             :     // Assert that repository exists
     539           2 :     auto repoPath = fileutils::get_data_dir() / aliceId
     540           4 :                     / "conversations" / convId;
     541           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     542             :     // Check created files
     543           2 :     auto bobInvited = repoPath / "invited" / bobUri;
     544           1 :     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvited));
     545           1 :     libjami::acceptConversationRequest(bobId, convId);
     546           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
     547           2 :     auto clonedPath = fileutils::get_data_dir() / bobId
     548           4 :                       / "conversations" / convId;
     549           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(clonedPath));
     550           1 :     bobInvited = clonedPath / "invited" / bobUri;
     551           1 :     CPPUNIT_ASSERT(!std::filesystem::is_regular_file(bobInvited));
     552           2 :     auto bobMember = clonedPath / "members" / (bobUri + ".crt");
     553           1 :     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobMember));
     554           1 : }
     555             : 
     556             : void
     557           1 : ConversationMembersEventTest::testMemberAddedNoBadFile()
     558             : {
     559           1 :     connectSignals();
     560             : 
     561           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     562           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     563           1 :     auto bobUri = bobAccount->getUsername();
     564           1 :     auto convId = libjami::startConversation(aliceId);
     565             : 
     566           1 :     addFile(aliceAccount, convId, "BADFILE");
     567             :     // NOTE: Add certificate because no DHT lookup
     568           1 :     aliceAccount->certStore().pinCertificate(bobAccount->identity().second);
     569           1 :     generateFakeInvite(aliceAccount, convId, bobUri);
     570             :     // Generate conv request
     571           5 :     aliceAccount->sendTextMessage(bobUri,
     572           2 :                                   std::string(bobAccount->currentDeviceId()),
     573             :                                   {{"application/invite+json",
     574           2 :                                     "{\"conversationId\":\"" + convId + "\"}"}});
     575           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     576           1 :     libjami::acceptConversationRequest(bobId, convId);
     577           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
     578           1 : }
     579             : 
     580             : void
     581           1 : ConversationMembersEventTest::testAddOfflineMemberThenConnects()
     582             : {
     583           1 :     connectSignals();
     584             : 
     585           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     586           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     587           1 :     auto carlaUri = carlaAccount->getUsername();
     588           1 :     aliceAccount->trackBuddyPresence(carlaUri, true);
     589           1 :     auto convId = libjami::startConversation(aliceId);
     590           2 :     CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return !aliceData.conversationId.empty(); }));
     591             : 
     592           1 :     auto aliceMsgSize = aliceData.messages.size();
     593           1 :     libjami::addConversationMember(aliceId, convId, carlaUri);
     594           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     595           4 :     CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&] { return carlaData.requestReceived; }));
     596             : 
     597           1 :     Manager::instance().sendRegister(carlaId, true);
     598           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.requestReceived; }));
     599             : 
     600           1 :     libjami::acceptConversationRequest(carlaId, convId);
     601           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !carlaData.conversationId.empty(); }));
     602           2 :     auto clonedPath = fileutils::get_data_dir() / carlaId
     603           4 :                       / "conversations" / convId;
     604           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(clonedPath));
     605           1 : }
     606             : 
     607             : void
     608           1 : ConversationMembersEventTest::testAddAcceptOfflineThenConnects()
     609             : {
     610           1 :     connectSignals();
     611             : 
     612           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     613           1 :     auto bobUri = bobAccount->getUsername();
     614             : 
     615           1 :     libjami::startConversation(aliceId);
     616           2 :     CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return !aliceData.conversationId.empty(); }));
     617             : 
     618           1 :     libjami::addConversationMember(aliceId, aliceData.conversationId, bobUri);
     619           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return bobData.requestReceived; }));
     620             : 
     621           1 :     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
     622           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return aliceData.stopped; }));
     623             : 
     624             :     // Accepts
     625           1 :     libjami::acceptConversationRequest(bobId, aliceData.conversationId);
     626             : 
     627           3 :     CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
     628             : 
     629           1 :     Manager::instance().sendRegister(aliceId, true); // This avoid to sync immediately
     630           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return !bobData.conversationId.empty(); }));
     631           1 : }
     632             : 
     633             : void
     634           1 : ConversationMembersEventTest::testGetMembers()
     635             : {
     636           1 :     connectSignals();
     637             : 
     638           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     639           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     640           1 :     auto bobUri = bobAccount->getUsername();
     641             :     // Start a conversation and add member
     642           1 :     auto convId = libjami::startConversation(aliceId);
     643             : 
     644           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     645           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return bobData.requestReceived; }));
     646             : 
     647             :     // Assert that repository exists
     648           2 :     auto repoPath = fileutils::get_data_dir() / aliceId
     649           4 :                     / "conversations" / convId;
     650           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     651             : 
     652           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
     653           1 :     CPPUNIT_ASSERT(members.size() == 2);
     654           1 :     CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername());
     655           1 :     CPPUNIT_ASSERT(members[0]["role"] == "admin");
     656           1 :     CPPUNIT_ASSERT(members[1]["uri"] == bobUri);
     657           1 :     CPPUNIT_ASSERT(members[1]["role"] == "invited");
     658             : 
     659           1 :     auto aliceMsgSize = aliceData.messages.size();
     660           1 :     libjami::acceptConversationRequest(bobId, convId);
     661           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
     662           1 :     members = libjami::getConversationMembers(bobId, convId);
     663           1 :     CPPUNIT_ASSERT(members.size() == 2);
     664           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     665           1 :     members = libjami::getConversationMembers(aliceId, convId);
     666           1 :     CPPUNIT_ASSERT(members.size() == 2);
     667           1 :     CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername());
     668           1 :     CPPUNIT_ASSERT(members[0]["role"] == "admin");
     669           1 :     CPPUNIT_ASSERT(members[1]["uri"] == bobUri);
     670           1 :     CPPUNIT_ASSERT(members[1]["role"] == "member");
     671           1 : }
     672             : 
     673             : void
     674           1 : ConversationMembersEventTest::testRemoveMember()
     675             : {
     676           1 :     connectSignals();
     677             : 
     678           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     679           1 :     auto bobUri = bobAccount->getUsername();
     680           1 :     auto convId = libjami::startConversation(aliceId);
     681             : 
     682           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     683           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     684           1 :     auto aliceMsgSize = aliceData.messages.size();
     685           1 :     libjami::acceptConversationRequest(bobId, convId);
     686           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     687             : 
     688             :     // Now check that alice, has the only admin, can remove bob
     689           1 :     libjami::removeConversationMember(aliceId, convId, bobUri);
     690           4 :     CPPUNIT_ASSERT(
     691             :         cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 3 /* vote + ban */; }));
     692           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
     693           1 :     auto bobBanned = false;
     694           3 :     for (auto& member : members) {
     695           2 :         if (member["uri"] == bobUri)
     696           1 :             bobBanned = member["role"] == "banned";
     697             :     }
     698           1 :     CPPUNIT_ASSERT(bobBanned);
     699           1 : }
     700             : 
     701             : void
     702           1 : ConversationMembersEventTest::testRemovedMemberDoesNotReceiveMessageFromAdmin()
     703             : {
     704           1 :     connectSignals();
     705             : 
     706           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     707           1 :     auto bobUri = bobAccount->getUsername();
     708           1 :     auto convId = libjami::startConversation(aliceId);
     709             : 
     710           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     711           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     712           1 :     auto aliceMsgSize = aliceData.messages.size();
     713           1 :     libjami::acceptConversationRequest(bobId, convId);
     714           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     715             : 
     716             :     // Now check that alice, as the only admin, can remove bob
     717           1 :     libjami::removeConversationMember(aliceId, convId, bobUri);
     718           3 :     CPPUNIT_ASSERT(
     719             :         cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 3 /* vote + ban */; }));
     720             : 
     721             :     // Now, bob is banned so they shoud not receive any message
     722           1 :     auto bobMsgSize = bobData.messages.size();
     723           1 :     libjami::sendMessage(aliceId, convId, "hi"s, "");
     724           4 :     CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
     725           1 : }
     726             : 
     727             : void
     728           1 : ConversationMembersEventTest::testRemovedMemberDoesNotReceiveMessageFromPeer()
     729             : {
     730           1 :     connectSignals();
     731             : 
     732           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     733           1 :     auto bobUri = bobAccount->getUsername();
     734           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     735           1 :     auto carlaUri = carlaAccount->getUsername();
     736           1 :     Manager::instance().sendRegister(carlaId, true);
     737           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.deviceAnnounced; }));
     738             : 
     739             :     // Make Carla send a contact request to Bob and wait until she receives a message from
     740             :     // him confirming that he has accepted the request. The point of this is to make sure
     741             :     // that Bob and Carla are connected; otherwise the test below where we check that Bob
     742             :     // hasn't received a message from Carla could pass for the wrong reason.
     743           1 :     carlaAccount->addContact(bobUri);
     744           1 :     carlaAccount->sendTrustRequest(bobUri, {});
     745           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     746           1 :     auto carlaMsgSize = carlaData.messages.size();
     747           1 :     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(carlaUri));
     748           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaMsgSize + 1 == carlaData.messages.size(); }));
     749             : 
     750             :     // Alice creates a swarm and adds Bob and Carla to it
     751           1 :     bobData.requestReceived = false;
     752           1 :     auto convId = libjami::startConversation(aliceId);
     753           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     754           1 :     libjami::addConversationMember(aliceId, convId, carlaUri);
     755           7 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && carlaData.requestReceived; }));
     756             : 
     757           1 :     libjami::acceptConversationRequest(bobId, convId);
     758           1 :     libjami::acceptConversationRequest(carlaId, convId);
     759          17 :     auto messageReceived = [](UserData& userData, const std::string& action, const std::string& uri) {
     760          48 :         for (auto& message : userData.messages) {
     761          35 :             if (message.type == "member" && message.body["action"] == action && message.body["uri"] == uri)
     762           4 :                 return true;
     763             :         }
     764          13 :         return false;
     765             :     };
     766           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived(aliceData, "join", bobUri); }));
     767           2 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived(carlaData, "join", bobUri); }));
     768             : 
     769             :     // Alice bans Bob
     770           1 :     libjami::removeConversationMember(aliceId, convId, bobUri);
     771           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived(aliceData, "ban", bobUri); }));
     772          10 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived(carlaData, "ban", bobUri); }));
     773             : 
     774             :     // Carla's messages should now be received by Alice, but not Bob
     775           1 :     auto aliceMsgSize = aliceData.messages.size();
     776           1 :     auto bobMsgSize = bobData.messages.size();
     777           1 :     libjami::sendMessage(carlaId, convId, "hello"s, "");
     778           7 :     CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobMsgSize < bobData.messages.size(); }));
     779           2 :     CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     780           1 : }
     781             : 
     782             : void
     783           1 : ConversationMembersEventTest::testRemoveInvitedMember()
     784             : {
     785           1 :     connectSignals();
     786             : 
     787           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     788           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     789           1 :     auto bobUri = bobAccount->getUsername();
     790           1 :     auto carlaUri = carlaAccount->getUsername();
     791           1 :     auto convId = libjami::startConversation(aliceId);
     792             : 
     793             :     // Add carla
     794           1 :     Manager::instance().sendRegister(carlaId, true);
     795           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.deviceAnnounced; }));
     796             : 
     797           1 :     auto aliceMsgSize = aliceData.messages.size();
     798           1 :     libjami::addConversationMember(aliceId, convId, carlaUri);
     799           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
     800           1 :     libjami::acceptConversationRequest(carlaId, convId);
     801           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !carlaData.conversationId.empty() && aliceMsgSize + 2 == aliceData.messages.size(); }));
     802             : 
     803             :     // Invite Alice
     804           1 :     auto carlaMsgSize = carlaData.messages.size();
     805           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     806           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 3 == aliceData.messages.size()
     807             :                                                     && carlaMsgSize + 1 == carlaData.messages.size()
     808             :                                                     && bobData.requestReceived; }));
     809           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
     810           1 :     CPPUNIT_ASSERT(members.size() == 3);
     811           1 :     members = libjami::getConversationMembers(carlaId, convId);
     812           1 :     CPPUNIT_ASSERT(members.size() == 3);
     813             : 
     814             :     // Now check that alice, has the only admin, can remove bob
     815           1 :     aliceMsgSize = aliceData.messages.size();
     816           1 :     carlaMsgSize = carlaData.messages.size();
     817           1 :     libjami::removeConversationMember(aliceId, convId, bobUri);
     818           6 :     CPPUNIT_ASSERT(
     819             :         cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && carlaMsgSize + 2 == carlaData.messages.size(); }));
     820           1 :     members = libjami::getConversationMembers(aliceId, convId);
     821           1 :     auto bobBanned = false;
     822           4 :     for (auto& member : members) {
     823           3 :         if (member["uri"] == bobUri)
     824           1 :             bobBanned = member["role"] == "banned";
     825             :     }
     826           1 :     CPPUNIT_ASSERT(bobBanned);
     827           1 :     members = libjami::getConversationMembers(carlaId, convId);
     828           1 :     bobBanned = false;
     829           4 :     for (auto& member : members) {
     830           3 :         if (member["uri"] == bobUri)
     831           1 :             bobBanned = member["role"] == "banned";
     832             :     }
     833           1 :     CPPUNIT_ASSERT(bobBanned);
     834             : 
     835             :     // Check that Carla is still able to sync
     836           1 :     carlaMsgSize = carlaData.messages.size();
     837           1 :     libjami::sendMessage(aliceId, convId, "hi"s, "");
     838           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaMsgSize + 1 == carlaData.messages.size(); }));
     839           1 : }
     840             : 
     841             : void
     842           1 : ConversationMembersEventTest::testMemberBanNoBadFile()
     843             : {
     844           1 :     connectSignals();
     845             : 
     846           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     847           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     848           1 :     auto aliceUri = aliceAccount->getUsername();
     849           1 :     auto bobUri = bobAccount->getUsername();
     850           1 :     auto convId = libjami::startConversation(aliceId);
     851           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     852           1 :     auto carlaUri = carlaAccount->getUsername();
     853           1 :     aliceAccount->trackBuddyPresence(carlaUri, true);
     854             : 
     855           1 :     Manager::instance().sendRegister(carlaId, true);
     856           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
     857           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     858           5 :     CPPUNIT_ASSERT(
     859             :         cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     860           1 :     auto aliceMsgSize = aliceData.messages.size();
     861           1 :     libjami::acceptConversationRequest(bobId, convId);
     862           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     863           1 :     libjami::addConversationMember(aliceId, convId, carlaUri);
     864           7 :     CPPUNIT_ASSERT(
     865             :         cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived; }));
     866           1 :     aliceMsgSize = aliceData.messages.size();
     867           1 :     auto bobMsgSize = bobData.messages.size();
     868           1 :     libjami::acceptConversationRequest(carlaId, convId);
     869           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
     870             : 
     871           1 :     addFile(aliceAccount, convId, "BADFILE");
     872           1 :     libjami::removeConversationMember(aliceId, convId, carlaUri);
     873           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
     874           1 : }
     875             : 
     876             : void
     877           1 : ConversationMembersEventTest::testMemberTryToRemoveAdmin()
     878             : {
     879           1 :     connectSignals();
     880             : 
     881           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     882           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     883           1 :     auto aliceUri = aliceAccount->getUsername();
     884           1 :     auto bobUri = bobAccount->getUsername();
     885           1 :     auto convId = libjami::startConversation(aliceId);
     886           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     887           5 :     CPPUNIT_ASSERT(
     888             :         cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     889           1 :     auto aliceMsgSize = aliceData.messages.size();
     890           1 :     libjami::acceptConversationRequest(bobId, convId);
     891           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     892             : 
     893             :     // Now check that alice, has the only admin, can remove bob
     894           1 :     libjami::removeConversationMember(bobId, convId, aliceUri);
     895           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
     896           1 :     CPPUNIT_ASSERT(members.size() == 2 && aliceMsgSize + 2 != aliceData.messages.size());
     897           1 : }
     898             : 
     899             : void
     900           1 : ConversationMembersEventTest::testBannedMemberCannotSendMessage()
     901             : {
     902           1 :     connectSignals();
     903             : 
     904           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     905           1 :     auto bobUri = bobAccount->getUsername();
     906           1 :     auto convId = libjami::startConversation(aliceId);
     907             : 
     908           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     909           5 :     CPPUNIT_ASSERT(
     910             :         cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     911           1 :     auto aliceMsgSize = aliceData.messages.size();
     912           1 :     libjami::acceptConversationRequest(bobId, convId);
     913           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     914             : 
     915           1 :     libjami::removeConversationMember(aliceId, convId, bobUri);
     916           4 :     CPPUNIT_ASSERT(
     917             :         cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 3 == aliceData.messages.size(); }));
     918           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
     919             : 
     920           1 :     auto bobBanned = false;
     921           3 :     for (auto& member : members) {
     922           2 :         if (member["uri"] == bobUri)
     923           1 :             bobBanned = member["role"] == "banned";
     924             :     }
     925           1 :     CPPUNIT_ASSERT(bobBanned);
     926             : 
     927             :     // Now check that alice doesn't receive a message from Bob
     928           1 :     libjami::sendMessage(bobId, convId, "hi"s, "");
     929           4 :     CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 4 == aliceData.messages.size(); }));
     930           1 : }
     931             : 
     932             : void
     933           1 : ConversationMembersEventTest::testAdminCanReAddMember()
     934             : {
     935           1 :     connectSignals();
     936             : 
     937           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     938           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     939           1 :     auto aliceUri = aliceAccount->getUsername();
     940           1 :     auto bobUri = bobAccount->getUsername();
     941           1 :     auto convId = libjami::startConversation(aliceId);
     942             : 
     943           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     944           5 :     CPPUNIT_ASSERT(
     945             :         cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     946           1 :     auto aliceMsgSize = aliceData.messages.size();
     947           1 :     libjami::acceptConversationRequest(bobId, convId);
     948           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     949             : 
     950             :     // Now check that alice, has the only admin, can remove bob
     951           1 :     libjami::removeConversationMember(aliceId, convId, bobUri);
     952           3 :     CPPUNIT_ASSERT(
     953             :         cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 3 == aliceData.messages.size(); }));
     954             : 
     955           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
     956             : 
     957           1 :     auto bobBanned = false;
     958           3 :     for (auto& member : members) {
     959           2 :         if (member["uri"] == bobUri)
     960           1 :             bobBanned = member["role"] == "banned";
     961             :     }
     962           1 :     CPPUNIT_ASSERT(bobBanned);
     963             : 
     964             :     // Then check that bobUri can be re-added
     965           1 :     aliceMsgSize = aliceData.messages.size();
     966           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     967           3 :     CPPUNIT_ASSERT(
     968             :         cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
     969             : 
     970           1 :     members = libjami::getConversationMembers(aliceId, convId);
     971           1 :     CPPUNIT_ASSERT(members.size() == 2);
     972           1 : }
     973             : 
     974             : void
     975           1 : ConversationMembersEventTest::testMemberCannotBanOther()
     976             : {
     977           1 :     connectSignals();
     978             : 
     979           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     980           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     981           1 :     auto bobUri = bobAccount->getUsername();
     982           1 :     auto convId = libjami::startConversation(aliceId);
     983           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     984           1 :     auto carlaUri = carlaAccount->getUsername();
     985           1 :     aliceAccount->trackBuddyPresence(carlaUri, true);
     986             : 
     987           1 :     Manager::instance().sendRegister(carlaId, true);
     988           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
     989           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
     990           4 :     CPPUNIT_ASSERT(
     991             :         cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     992           1 :     libjami::acceptConversationRequest(bobId, convId);
     993           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceData.members[bobUri] == 1; }));
     994           1 :     libjami::addConversationMember(aliceId, convId, carlaUri);
     995           6 :     CPPUNIT_ASSERT(
     996             :         cv.wait_for(lk, 30s, [&]() {
     997             :             return aliceData.members.find(carlaUri) != aliceData.members.end() && bobData.members.find(carlaUri) != bobData.members.end() && carlaData.requestReceived; }));
     998           1 :     libjami::acceptConversationRequest(carlaId, convId);
     999           7 :     CPPUNIT_ASSERT(
    1000             :         cv.wait_for(lk, 30s, [&]() { return !carlaData.conversationId.empty() && aliceData.members[carlaUri] == 1 && bobData.members[carlaUri] == 1; }));
    1001             : 
    1002             :     // Now Carla remove Bob as a member
    1003             :     // remove from member & add into banned without voting for the ban
    1004           1 :     simulateRemoval(carlaAccount, convId, bobUri);
    1005             :     // Note: it may be possible that alice doesn't get the error if they got messages from bob (and bob rejects due to an error)
    1006           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
    1007             :         return aliceData.errorDetected || bobData.errorDetected; }));
    1008             : 
    1009           1 :     auto bobMsgSize = bobData.messages.size();
    1010           1 :     libjami::sendMessage(aliceId, convId, "hi"s, "");
    1011           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
    1012           1 : }
    1013             : 
    1014             : void
    1015           1 : ConversationMembersEventTest::testMemberCannotUnBanOther()
    1016             : {
    1017           1 :     connectSignals();
    1018             : 
    1019           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1020           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1021           1 :     auto bobUri = bobAccount->getUsername();
    1022           1 :     auto convId = libjami::startConversation(aliceId);
    1023           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
    1024           1 :     auto carlaUri = carlaAccount->getUsername();
    1025           1 :     aliceAccount->trackBuddyPresence(carlaUri, true);
    1026             : 
    1027           1 :     Manager::instance().sendRegister(carlaId, true);
    1028           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
    1029           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
    1030           5 :     CPPUNIT_ASSERT(
    1031             :         cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
    1032           1 :     auto aliceMsgSize = aliceData.messages.size();
    1033           1 :     libjami::acceptConversationRequest(bobId, convId);
    1034           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
    1035           1 :     libjami::addConversationMember(aliceId, convId, carlaUri);
    1036           5 :     CPPUNIT_ASSERT(
    1037             :         cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived; }));
    1038           1 :     aliceMsgSize = aliceData.messages.size();
    1039           1 :     libjami::acceptConversationRequest(carlaId, convId);
    1040           7 :     CPPUNIT_ASSERT(
    1041             :         cv.wait_for(lk, 30s, [&]() { return !carlaData.conversationId.empty()
    1042             :                                         && aliceData.members[carlaUri] == 1
    1043             :                                         && bobData.members[carlaUri] == 1; }));
    1044           1 :     std::this_thread::sleep_for(3s); // Wait that carla finish the clone
    1045             :     // Now check that alice, has the only admin, can remove bob
    1046           1 :     libjami::removeConversationMember(aliceId, convId, bobUri);
    1047           6 :     CPPUNIT_ASSERT(
    1048             :         cv.wait_for(lk, 30s, [&]() { return aliceData.members[bobUri] == 3
    1049             :                                         && carlaData.members[bobUri] == 3; }));
    1050             : 
    1051           1 :     libjami::addConversationMember(carlaId, convId, bobUri);
    1052           3 :     CPPUNIT_ASSERT(
    1053             :         !cv.wait_for(lk, 10s, [&]() { return aliceData.members[bobUri] == 1
    1054             :                                         && carlaData.members[bobUri] == 1; }));
    1055           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
    1056           1 :     auto bobBanned = false;
    1057           4 :     for (auto& member : members) {
    1058           3 :         if (member["uri"] == bobUri)
    1059           1 :             bobBanned = member["role"] == "banned";
    1060             :     }
    1061           1 :     CPPUNIT_ASSERT(bobBanned);
    1062           1 : }
    1063             : 
    1064             : void
    1065           1 : ConversationMembersEventTest::testCheckAdminFakeAVoteIsDetected()
    1066             : {
    1067           1 :     connectSignals();
    1068             : 
    1069           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1070           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1071           1 :     auto bobUri = bobAccount->getUsername();
    1072           1 :     auto convId = libjami::startConversation(aliceId);
    1073           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
    1074           1 :     auto carlaUri = carlaAccount->getUsername();
    1075           1 :     aliceAccount->trackBuddyPresence(carlaUri, true);
    1076             : 
    1077           1 :     Manager::instance().sendRegister(carlaId, true);
    1078           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
    1079           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
    1080           4 :     CPPUNIT_ASSERT(
    1081             :         cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
    1082           1 :     auto aliceMsgSize = aliceData.messages.size();
    1083           1 :     libjami::acceptConversationRequest(bobId, convId);
    1084           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
    1085           1 :     libjami::addConversationMember(aliceId, convId, carlaUri);
    1086           5 :     CPPUNIT_ASSERT(
    1087             :         cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived; }));
    1088           1 :     aliceMsgSize = aliceData.messages.size();
    1089           1 :     auto bobMsgSize = bobData.messages.size();
    1090           1 :     libjami::acceptConversationRequest(carlaId, convId);
    1091           6 :     CPPUNIT_ASSERT(
    1092             :         cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
    1093             : 
    1094             :     // Now Alice remove Carla without a vote. Bob will not receive the message
    1095           1 :     simulateRemoval(aliceAccount, convId, carlaUri);
    1096           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
    1097           1 : }
    1098             : 
    1099             : void
    1100           1 : ConversationMembersEventTest::testAdminCannotKickTheirself()
    1101             : {
    1102           1 :     connectSignals();
    1103             : 
    1104           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1105           1 :     auto aliceUri = aliceAccount->getUsername();
    1106           1 :     auto convId = libjami::startConversation(aliceId);
    1107           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
    1108           1 :     CPPUNIT_ASSERT(members.size() == 1);
    1109           1 :     libjami::removeConversationMember(aliceId, convId, aliceUri);
    1110           1 :     members = libjami::getConversationMembers(aliceId, convId);
    1111           1 :     CPPUNIT_ASSERT(members.size() == 1);
    1112           1 : }
    1113             : 
    1114             : void
    1115           1 : ConversationMembersEventTest::testCommitUnauthorizedUser()
    1116             : {
    1117           1 :     connectSignals();
    1118             : 
    1119           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1120           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1121           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
    1122           1 :     auto bobUri = bobAccount->getUsername();
    1123             : 
    1124           1 :     auto convId = libjami::startConversation(aliceId);
    1125             : 
    1126           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
    1127           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
    1128             : 
    1129           1 :     auto aliceMsgSize = aliceData.messages.size();
    1130           1 :     libjami::acceptConversationRequest(bobId, convId);
    1131           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size();; }));
    1132             : 
    1133             :     // Assert that repository exists
    1134           2 :     auto repoPath = fileutils::get_data_dir() / bobId
    1135           4 :                     / "conversations" / convId;
    1136           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
    1137             : 
    1138             :     // Add commit from invalid user
    1139           1 :     Json::Value root;
    1140           1 :     root["type"] = "text/plain";
    1141           1 :     root["body"] = "hi";
    1142           1 :     Json::StreamWriterBuilder wbuilder;
    1143           1 :     wbuilder["commentStyle"] = "None";
    1144           1 :     wbuilder["indentation"] = "";
    1145           1 :     auto message = Json::writeString(wbuilder, root);
    1146           1 :     commitInRepo(repoPath, carlaAccount, message);
    1147             : 
    1148           1 :     libjami::sendMessage(bobId, convId, "hi"s, "");
    1149           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.errorDetected; }));
    1150           1 : }
    1151             : 
    1152             : void
    1153           1 : ConversationMembersEventTest::testMemberJoinsNoBadFile()
    1154             : {
    1155           1 :     connectSignals();
    1156             : 
    1157           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1158           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
    1159           1 :     auto carlaUri = carlaAccount->getUsername();
    1160           1 :     aliceAccount->trackBuddyPresence(carlaUri, true);
    1161           1 :     auto convId = libjami::startConversation(aliceId);
    1162             : 
    1163           1 :     auto aliceMsgSize = aliceData.messages.size();
    1164           1 :     aliceAccount->convModule()->addConversationMember(convId, dht::InfoHash(carlaUri), false);
    1165           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return aliceMsgSize + 1 == aliceData.messages.size(); }));
    1166             : 
    1167             :     // Cp conversations & convInfo
    1168           2 :     auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations";
    1169           2 :     auto repoPathCarla = fileutils::get_data_dir() / carlaId / "conversations";
    1170           1 :     std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
    1171           2 :     auto ciPathAlice = fileutils::get_data_dir() / aliceId
    1172           2 :                        / "convInfo";
    1173           2 :     auto ciPathCarla = fileutils::get_data_dir() / carlaId
    1174           2 :                        / "convInfo";
    1175           1 :     std::remove(ciPathCarla.c_str());
    1176           1 :     std::filesystem::copy(ciPathAlice, ciPathCarla);
    1177             : 
    1178             :     // Accept for alice and makes different heads
    1179           1 :     addFile(carlaAccount, convId, "BADFILE");
    1180             :     // add /members + /devices
    1181           1 :     auto cert = carlaAccount->identity().second;
    1182           1 :     auto parentCert = cert->issuer;
    1183           1 :     auto uri = parentCert->getId().toString();
    1184           2 :     auto membersPath = repoPathCarla / convId / "members";
    1185           2 :     auto devicesPath = repoPathCarla / convId / "devices";
    1186           2 :     auto memberFile = membersPath / fmt::format("{}.crt", carlaUri);
    1187             :     // Add members/uri.crt
    1188           1 :     dhtnet::fileutils::recursive_mkdir(membersPath, 0700);
    1189           1 :     dhtnet::fileutils::recursive_mkdir(devicesPath, 0700);
    1190           1 :     std::ofstream file(memberFile, std::ios::trunc | std::ios::binary);
    1191           1 :     file << parentCert->toString(true);
    1192           1 :     file.close();
    1193           2 :     auto invitedPath = repoPathCarla / convId / "invited" / carlaUri;
    1194           1 :     dhtnet::fileutils::remove(invitedPath);
    1195           3 :     auto devicePath = devicesPath / fmt::format("{}.crt", carlaAccount->currentDeviceId());
    1196           1 :     file = std::ofstream(devicePath, std::ios::trunc | std::ios::binary);
    1197           1 :     file << cert->toString(false);
    1198           1 :     addAll(carlaAccount, convId);
    1199           1 :     ConversationRepository repo(carlaAccount, convId);
    1200             : 
    1201             :     // Start Carla, should merge and all messages should be there
    1202           1 :     carlaAccount->convModule()->loadConversations(); // Because of the copy
    1203           1 :     Manager::instance().sendRegister(carlaId, true);
    1204           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
    1205             : 
    1206           1 :     libjami::sendMessage(carlaId, convId, "hi"s, "");
    1207             : 
    1208           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return aliceData.errorDetected; }));
    1209           1 : }
    1210             : 
    1211             : void
    1212           1 : ConversationMembersEventTest::testMemberAddedNoCertificate()
    1213             : {
    1214           1 :     connectSignals();
    1215             : 
    1216           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1217           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
    1218           1 :     auto carlaUri = carlaAccount->getUsername();
    1219           1 :     aliceAccount->trackBuddyPresence(carlaUri, true);
    1220           1 :     auto convId = libjami::startConversation(aliceId);
    1221             : 
    1222           1 :     auto aliceMsgSize = aliceData.messages.size();
    1223           1 :     aliceAccount->convModule()->addConversationMember(convId, dht::InfoHash(carlaUri), false);
    1224           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return aliceMsgSize + 1 == aliceData.messages.size(); }));
    1225             : 
    1226             :     // Cp conversations & convInfo
    1227           2 :     auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations";
    1228           2 :     auto repoPathCarla = fileutils::get_data_dir() / carlaId / "conversations";
    1229           1 :     std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
    1230           2 :     auto ciPathAlice = fileutils::get_data_dir() / aliceId
    1231           2 :                        / "convInfo";
    1232           2 :     auto ciPathCarla = fileutils::get_data_dir() / carlaId
    1233           2 :                        / "convInfo";
    1234           1 :     std::remove(ciPathCarla.c_str());
    1235           1 :     std::filesystem::copy(ciPathAlice, ciPathCarla);
    1236             : 
    1237             :     // Remove invite but do not add member certificate
    1238           2 :     std::string invitedPath = repoPathCarla / "invited";
    1239           1 :     dhtnet::fileutils::remove(fileutils::getFullPath(invitedPath, carlaUri));
    1240             : 
    1241           1 :     Json::Value json;
    1242           1 :     json["action"] = "join";
    1243           1 :     json["uri"] = carlaUri;
    1244           1 :     json["type"] = "member";
    1245           1 :     Json::StreamWriterBuilder wbuilder;
    1246           1 :     wbuilder["commentStyle"] = "None";
    1247           1 :     wbuilder["indentation"] = "";
    1248           1 :     ConversationRepository cr(carlaAccount, convId);
    1249           1 :     cr.commitMessage(Json::writeString(wbuilder, json), false);
    1250             : 
    1251             :     // Start Carla, should merge and all messages should be there
    1252           1 :     carlaAccount->convModule()->loadConversations(); // Because of the copy
    1253           1 :     Manager::instance().sendRegister(carlaId, true);
    1254           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
    1255             : 
    1256           1 :     libjami::sendMessage(carlaId, convId, "hi"s, "");
    1257             : 
    1258           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return aliceData.errorDetected; }));
    1259           1 : }
    1260             : 
    1261             : void
    1262           1 : ConversationMembersEventTest::testMemberJoinsInviteRemoved()
    1263             : {
    1264           1 :     connectSignals();
    1265             : 
    1266           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1267           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
    1268           1 :     auto carlaUri = carlaAccount->getUsername();
    1269           1 :     aliceAccount->trackBuddyPresence(carlaUri, true);
    1270           1 :     auto convId = libjami::startConversation(aliceId);
    1271             : 
    1272           1 :     auto aliceMsgSize = aliceData.messages.size();
    1273           1 :     aliceAccount->convModule()->addConversationMember(convId, dht::InfoHash(carlaUri), false);
    1274           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return aliceMsgSize + 1 == aliceData.messages.size(); }));
    1275             : 
    1276             :     // Cp conversations & convInfo
    1277           2 :     auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations";
    1278           2 :     auto repoPathCarla = fileutils::get_data_dir() / carlaId / "conversations";
    1279           1 :     std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
    1280           2 :     auto ciPathAlice = fileutils::get_data_dir() / aliceId / "convInfo";
    1281           2 :     auto ciPathCarla = fileutils::get_data_dir() / carlaId / "convInfo";
    1282           1 :     std::remove(ciPathCarla.c_str());
    1283           1 :     std::filesystem::copy(ciPathAlice, ciPathCarla);
    1284             : 
    1285             :     // add /members + /devices
    1286           1 :     auto cert = carlaAccount->identity().second;
    1287           1 :     auto parentCert = cert->issuer;
    1288           1 :     auto uri = parentCert->getId().toString();
    1289           2 :     auto membersPath = repoPathCarla / convId / "members";
    1290           2 :     auto devicesPath = repoPathCarla / convId / "devices";
    1291           2 :     auto memberFile = membersPath / fmt::format("{}.crt", carlaUri);
    1292             :     // Add members/uri.crt
    1293           1 :     dhtnet::fileutils::recursive_mkdir(membersPath, 0700);
    1294           1 :     dhtnet::fileutils::recursive_mkdir(devicesPath, 0700);
    1295           1 :     std::ofstream file(memberFile, std::ios::trunc | std::ios::binary);
    1296           1 :     file << parentCert->toString(true);
    1297           1 :     file.close();
    1298           3 :     auto devicePath = devicesPath / fmt::format("{}.crt", carlaAccount->currentDeviceId());
    1299           1 :     file = std::ofstream(devicePath, std::ios::trunc | std::ios::binary);
    1300           1 :     file << cert->toString(false);
    1301           1 :     addAll(carlaAccount, convId);
    1302           1 :     Json::Value json;
    1303           1 :     json["action"] = "join";
    1304           1 :     json["uri"] = carlaUri;
    1305           1 :     json["type"] = "member";
    1306           1 :     commit(carlaAccount, convId, json);
    1307             : 
    1308             :     // Start Carla, should merge and all messages should be there
    1309           1 :     carlaAccount->convModule()->loadConversations(); // Because of the copy
    1310           1 :     Manager::instance().sendRegister(carlaId, true);
    1311           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
    1312             : 
    1313           1 :     libjami::sendMessage(carlaId, convId, "hi"s, "");
    1314           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return aliceData.errorDetected; }));
    1315           1 : }
    1316             : 
    1317             : void
    1318           1 : ConversationMembersEventTest::testFailAddMemberInOneToOne()
    1319             : {
    1320           1 :     connectSignals();
    1321             : 
    1322           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1323           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1324           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
    1325           1 :     auto bobUri = bobAccount->getUsername();
    1326           1 :     auto carlaUri = carlaAccount->getUsername();
    1327             : 
    1328           1 :     aliceAccount->addContact(bobUri);
    1329           1 :     aliceAccount->sendTrustRequest(bobUri, {});
    1330           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return bobData.requestReceived; }));
    1331           1 :     auto aliceMsgSize = aliceData.messages.size();
    1332           1 :     libjami::addConversationMember(aliceId, aliceData.conversationId, carlaUri);
    1333           3 :     CPPUNIT_ASSERT(!cv.wait_for(lk, 5s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
    1334           1 : }
    1335             : 
    1336             : void
    1337           1 : ConversationMembersEventTest::testOneToOneFetchWithNewMemberRefused()
    1338             : {
    1339           1 :     connectSignals();
    1340             : 
    1341           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1342           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1343           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
    1344           1 :     auto bobUri = bobAccount->getUsername();
    1345           1 :     auto aliceUri = aliceAccount->getUsername();
    1346           1 :     auto carlaUri = carlaAccount->getUsername();
    1347             : 
    1348           1 :     aliceAccount->addContact(bobUri);
    1349           1 :     aliceAccount->sendTrustRequest(bobUri, {});
    1350           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
    1351           1 :     auto aliceMsgSize = aliceData.messages.size();
    1352           1 :     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
    1353           5 :     CPPUNIT_ASSERT(
    1354             :         cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size();; }));
    1355             : 
    1356             :     // NOTE: Add certificate because no DHT lookup
    1357           1 :     aliceAccount->certStore().pinCertificate(carlaAccount->identity().second);
    1358           1 :     generateFakeInvite(aliceAccount, aliceData.conversationId, carlaUri);
    1359           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
    1360           1 : }
    1361             : 
    1362             : void
    1363           1 : ConversationMembersEventTest::testConversationMemberEvent()
    1364             : {
    1365           1 :     connectSignals();
    1366             : 
    1367           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1368           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1369           1 :     auto bobUri = bobAccount->getUsername();
    1370           1 :     auto convId = libjami::startConversation(aliceId);
    1371           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
    1372           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
    1373             :     // Assert that repository exists
    1374           2 :     auto repoPath = fileutils::get_data_dir() / aliceId
    1375           4 :                     / "conversations" / convId;
    1376           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
    1377             :     // Check created files
    1378           2 :     auto bobInvited = repoPath / "invited" / bobUri;
    1379           1 :     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvited));
    1380           1 :     libjami::acceptConversationRequest(bobId, convId);
    1381           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
    1382           2 :     auto clonedPath = fileutils::get_data_dir() / bobId
    1383           4 :                       / "conversations" / convId;
    1384           1 :     CPPUNIT_ASSERT(std::filesystem::is_directory(clonedPath));
    1385           1 :     bobInvited = clonedPath / "invited" / bobUri;
    1386           1 :     CPPUNIT_ASSERT(!std::filesystem::is_regular_file(bobInvited));
    1387           2 :     auto bobMember = clonedPath / "members" / (bobUri + ".crt");
    1388           1 :     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobMember));
    1389           1 : }
    1390             : 
    1391             : void
    1392           1 : ConversationMembersEventTest::testGetConversationsMembersWhileSyncing()
    1393             : {
    1394           1 :     connectSignals();
    1395             : 
    1396           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1397           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1398           1 :     auto bobUri = bobAccount->getUsername();
    1399           1 :     auto aliceUri = aliceAccount->getUsername();
    1400           1 :     aliceAccount->addContact(bobUri);
    1401           1 :     aliceAccount->sendTrustRequest(bobUri, {});
    1402           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
    1403             : 
    1404           1 :     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
    1405           1 :     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
    1406             : 
    1407           1 :     auto members = libjami::getConversationMembers(bobId, aliceData.conversationId);
    1408           3 :     CPPUNIT_ASSERT(std::find_if(members.begin(),
    1409             :                                 members.end(),
    1410             :                                 [&](auto memberInfo) { return memberInfo["uri"] == aliceUri; })
    1411             :                    != members.end());
    1412           2 :     CPPUNIT_ASSERT(std::find_if(members.begin(),
    1413             :                                 members.end(),
    1414             :                                 [&](auto memberInfo) { return memberInfo["uri"] == bobUri; })
    1415             :                    != members.end());
    1416           1 : }
    1417             : 
    1418             : void
    1419           1 : ConversationMembersEventTest::testGetConversationMembersWithSelfOneOne()
    1420             : {
    1421           1 :     connectSignals();
    1422             : 
    1423           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1424           1 :     auto aliceUri = aliceAccount->getUsername();
    1425           1 :     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
    1426           1 :     std::string convId = "";
    1427           1 :     confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
    1428           1 :         [&](const std::string& accountId, const std::string& conversationId) {
    1429           1 :             if (accountId == aliceId)
    1430           1 :                 convId = conversationId;
    1431           1 :             cv.notify_one();
    1432           1 :         }));
    1433           1 :     libjami::registerSignalHandlers(confHandlers);
    1434           1 :     aliceAccount->addContact(aliceUri);
    1435           2 :     CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !convId.empty(); }));
    1436             : 
    1437           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
    1438           1 :     CPPUNIT_ASSERT(members.size() == 1);
    1439           1 :     CPPUNIT_ASSERT(members[0]["uri"] == aliceUri);
    1440           1 : }
    1441             : 
    1442             : void
    1443           1 : ConversationMembersEventTest::testAvoidTwoOneToOne()
    1444             : {
    1445           1 :     connectSignals();
    1446             : 
    1447           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1448           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1449           1 :     auto bobUri = bobAccount->getUsername();
    1450           1 :     auto aliceUri = aliceAccount->getUsername();
    1451             : 
    1452             :     // Alice adds bob
    1453           1 :     aliceAccount->addContact(bobUri);
    1454           1 :     aliceAccount->sendTrustRequest(bobUri, {});
    1455           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
    1456           1 :     auto aliceMsgSize = aliceData.messages.size();
    1457           1 :     libjami::acceptConversationRequest(bobId, aliceData.conversationId);
    1458           5 :     CPPUNIT_ASSERT(
    1459             :         cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 1; }));
    1460             : 
    1461             :     // Remove contact
    1462           1 :     bobAccount->removeContact(aliceUri, false);
    1463           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
    1464             : 
    1465             :     // wait that connections are closed.
    1466           1 :     std::this_thread::sleep_for(10s);
    1467             : 
    1468             :     // Bob add Alice, this should re-add old conversation
    1469           1 :     bobAccount->addContact(aliceUri);
    1470           1 :     bobAccount->sendTrustRequest(aliceUri, {});
    1471           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.conversationId == aliceData.conversationId; }));
    1472           1 : }
    1473             : 
    1474             : void
    1475           1 : ConversationMembersEventTest::testAvoidTwoOneToOneMultiDevices()
    1476             : {
    1477           1 :     connectSignals();
    1478             : 
    1479           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1480           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1481           1 :     auto bobUri = bobAccount->getUsername();
    1482           1 :     auto aliceUri = aliceAccount->getUsername();
    1483             : 
    1484             :     // Bob creates a second device
    1485           2 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
    1486           1 :     std::remove(bobArchive.c_str());
    1487           1 :     bobAccount->exportArchive(bobArchive);
    1488           2 :     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
    1489           1 :     details[ConfProperties::TYPE] = "RING";
    1490           1 :     details[ConfProperties::DISPLAYNAME] = "BOB2";
    1491           1 :     details[ConfProperties::ALIAS] = "BOB2";
    1492           1 :     details[ConfProperties::UPNP_ENABLED] = "true";
    1493           1 :     details[ConfProperties::ARCHIVE_PASSWORD] = "";
    1494           1 :     details[ConfProperties::ARCHIVE_PIN] = "";
    1495           1 :     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
    1496           1 :     bob2Id = Manager::instance().addAccount(details);
    1497             : 
    1498           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
    1499             : 
    1500             :     // Alice adds bob
    1501           1 :     aliceAccount->addContact(bobUri);
    1502           1 :     aliceAccount->sendTrustRequest(bobUri, {});
    1503           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
    1504           1 :     auto aliceMsgSize = aliceData.messages.size();
    1505           1 :     libjami::acceptConversationRequest(bobId, aliceData.conversationId);
    1506           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
    1507             :         return !bobData.conversationId.empty() && !bob2Data.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size();
    1508             :     }));
    1509             : 
    1510             :     // Remove contact
    1511           1 :     bobAccount->removeContact(aliceUri, false);
    1512           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed && bob2Data.removed; }));
    1513             : 
    1514             :     // wait that connections are closed.
    1515           1 :     std::this_thread::sleep_for(10s);
    1516             : 
    1517             :     // Bob add Alice, this should re-add old conversation
    1518           1 :     bobAccount->addContact(aliceUri);
    1519           1 :     bobAccount->sendTrustRequest(aliceUri, {});
    1520          12 :     CPPUNIT_ASSERT(
    1521             :         cv.wait_for(lk, 30s, [&]() {
    1522             :             return bobData.conversationId == aliceData.conversationId && bob2Data.conversationId == aliceData.conversationId; }));
    1523           1 : }
    1524             : 
    1525             : void
    1526           1 : ConversationMembersEventTest::testRemoveRequestBannedMultiDevices()
    1527             : {
    1528           1 :     connectSignals();
    1529             : 
    1530           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1531           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1532           1 :     auto bobUri = bobAccount->getUsername();
    1533           1 :     auto aliceUri = aliceAccount->getUsername();
    1534             : 
    1535             :     // Bob creates a second device
    1536           2 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
    1537           1 :     std::remove(bobArchive.c_str());
    1538           1 :     bobAccount->exportArchive(bobArchive);
    1539           2 :     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
    1540           1 :     details[ConfProperties::TYPE] = "RING";
    1541           1 :     details[ConfProperties::DISPLAYNAME] = "BOB2";
    1542           1 :     details[ConfProperties::ALIAS] = "BOB2";
    1543           1 :     details[ConfProperties::UPNP_ENABLED] = "true";
    1544           1 :     details[ConfProperties::ARCHIVE_PASSWORD] = "";
    1545           1 :     details[ConfProperties::ARCHIVE_PIN] = "";
    1546           1 :     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
    1547           1 :     bob2Id = Manager::instance().addAccount(details);
    1548             : 
    1549           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
    1550             : 
    1551             :     // Alice adds bob
    1552           1 :     aliceAccount->addContact(bobUri);
    1553           1 :     aliceAccount->sendTrustRequest(bobUri, {});
    1554           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
    1555           1 :     CPPUNIT_ASSERT(libjami::getConversationRequests(bob2Id).size() == 1);
    1556             : 
    1557             :     // Bob bans alice, should update bob2
    1558           1 :     bobAccount->removeContact(aliceUri, true);
    1559           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.contactRemoved; }));
    1560           1 :     CPPUNIT_ASSERT(libjami::getConversationRequests(bob2Id).size() == 0);
    1561           1 : }
    1562             : 
    1563             : void
    1564           1 : ConversationMembersEventTest::testBanUnbanMultiDevice()
    1565             : {
    1566           1 :     connectSignals();
    1567             : 
    1568           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1569           1 :     auto bobUri = bobAccount->getUsername();
    1570           1 :     auto convId = libjami::startConversation(aliceId);
    1571             : 
    1572             :     // Bob creates a second device
    1573           2 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
    1574           1 :     std::remove(bobArchive.c_str());
    1575           1 :     bobAccount->exportArchive(bobArchive);
    1576           2 :     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
    1577           1 :     details[ConfProperties::TYPE] = "RING";
    1578           1 :     details[ConfProperties::DISPLAYNAME] = "BOB2";
    1579           1 :     details[ConfProperties::ALIAS] = "BOB2";
    1580           1 :     details[ConfProperties::UPNP_ENABLED] = "true";
    1581           1 :     details[ConfProperties::ARCHIVE_PASSWORD] = "";
    1582           1 :     details[ConfProperties::ARCHIVE_PIN] = "";
    1583           1 :     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
    1584           1 :     bob2Id = Manager::instance().addAccount(details);
    1585             : 
    1586           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
    1587             : 
    1588             :     // Alice adds bob
    1589           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
    1590           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
    1591             : 
    1592             :     // Alice kick Bob while invited
    1593           1 :     auto aliceMsgSize = aliceData.messages.size();
    1594           1 :     libjami::removeConversationMember(aliceId, convId, bobUri);
    1595           3 :     CPPUNIT_ASSERT(
    1596             :         cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
    1597             : 
    1598             :     // Alice re-add Bob while invited
    1599           1 :     aliceMsgSize = aliceData.messages.size();
    1600           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
    1601           4 :     CPPUNIT_ASSERT(
    1602             :         cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
    1603             : 
    1604             :     // bob accepts
    1605           1 :     libjami::acceptConversationRequest(bobId, convId);
    1606           5 :     CPPUNIT_ASSERT(
    1607             :         cv.wait_for(lk, 30s, [&]() { return bobData.conversationId == aliceData.conversationId && bob2Data.conversationId == aliceData.conversationId; }));
    1608           1 : }
    1609             : 
    1610             : void
    1611           1 : ConversationMembersEventTest::testBanUnbanGotFirstConv()
    1612             : {
    1613           1 :     connectSignals();
    1614             : 
    1615           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1616           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1617           1 :     auto bobUri = bobAccount->getUsername();
    1618           1 :     auto aliceUri = aliceAccount->getUsername();
    1619             : 
    1620             :     // Bob creates a second device
    1621           2 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
    1622           1 :     std::remove(bobArchive.c_str());
    1623           1 :     bobAccount->exportArchive(bobArchive);
    1624           2 :     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
    1625           1 :     details[ConfProperties::TYPE] = "RING";
    1626           1 :     details[ConfProperties::DISPLAYNAME] = "BOB2";
    1627           1 :     details[ConfProperties::ALIAS] = "BOB2";
    1628           1 :     details[ConfProperties::UPNP_ENABLED] = "true";
    1629           1 :     details[ConfProperties::ARCHIVE_PASSWORD] = "";
    1630           1 :     details[ConfProperties::ARCHIVE_PIN] = "";
    1631           1 :     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
    1632           1 :     bob2Id = Manager::instance().addAccount(details);
    1633             : 
    1634           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
    1635             : 
    1636             :     // Alice adds bob
    1637           1 :     aliceAccount->addContact(bobUri);
    1638           1 :     aliceAccount->sendTrustRequest(bobUri, {});
    1639           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
    1640           1 :     CPPUNIT_ASSERT(libjami::getConversationRequests(bob2Id).size() == 1);
    1641             : 
    1642             :     // Accepts requests
    1643           1 :     auto aliceMsgSize = aliceData.messages.size();
    1644           1 :     libjami::acceptConversationRequest(bobId, aliceData.conversationId);
    1645           7 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
    1646             :         return !bobData.conversationId.empty() && !bob2Data.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size();
    1647             :     }));
    1648           1 :     bobAccount->removeContact(aliceUri, true);
    1649           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactRemoved && bob2Data.contactRemoved; }));
    1650             : 
    1651             :     // Alice sends messages, bob & bob2 should not get it!
    1652           1 :     aliceMsgSize = aliceData.messages.size();
    1653           1 :     libjami::sendMessage(aliceId, aliceData.conversationId, "hi"s, "");
    1654           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize != aliceData.messages.size(); }));
    1655           1 :     auto msgId = aliceData.messages.rbegin()->id;
    1656           2 :     auto getMessage = [](const auto& data, const auto& mid) -> bool {
    1657           2 :         return std::find_if(data.messages.begin(), data.messages.end(), [&](auto& msg) { return msg.id == mid; }) != data.messages.end();
    1658             :     };
    1659             :     // Connection MUST fail
    1660           3 :     CPPUNIT_ASSERT(!cv.wait_for(lk, 20s, [&]() { return getMessage(bobData, msgId) && getMessage(bob2Data, msgId); }));
    1661             : 
    1662             :     // Bobs re-add Alice
    1663           1 :     bobData.contactAdded = false; bob2Data.contactAdded = false;
    1664           1 :     bobAccount->addContact(aliceUri);
    1665           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactAdded
    1666             :                                                     && bob2Data.contactAdded
    1667             :                                                     && bobData.conversationId == bob2Data.conversationId
    1668             :                                                     && bobData.conversationId == aliceData.conversationId; }));
    1669           1 : }
    1670             : 
    1671             : void
    1672           1 : ConversationMembersEventTest::testBanHostWhileHosting()
    1673             : {
    1674           1 :     connectSignals();
    1675             : 
    1676           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1677           1 :     auto bobUri = bobAccount->getUsername();
    1678           1 :     auto convId = libjami::startConversation(aliceId);
    1679             : 
    1680           1 :     libjami::addConversationMember(aliceId, convId, bobUri);
    1681           5 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
    1682           1 :     auto aliceMsgSize = aliceData.messages.size();
    1683           1 :     libjami::acceptConversationRequest(bobId, convId);
    1684           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
    1685             : 
    1686             :     // Now, Bob starts a call
    1687           1 :     aliceMsgSize = aliceData.messages.size();
    1688           2 :     auto callId = libjami::placeCallWithMedia(bobId, "swarm:" + convId, {});
    1689             :     // should get message
    1690           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize != aliceData.messages.size(); }));
    1691             : 
    1692             :     // get active calls = 1
    1693           1 :     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, convId).size() == 1);
    1694             : 
    1695             :     // Now check that alice, has the only admin, can remove bob
    1696           1 :     aliceMsgSize = aliceData.messages.size();
    1697           1 :     libjami::removeConversationMember(aliceId, convId, bobUri);
    1698           4 :     CPPUNIT_ASSERT(
    1699             :         cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
    1700           1 :     auto members = libjami::getConversationMembers(aliceId, convId);
    1701           1 :     auto bobBanned = false;
    1702           3 :     for (auto& member : members) {
    1703           2 :         if (member["uri"] == bobUri)
    1704           1 :             bobBanned = member["role"] == "banned";
    1705             :     }
    1706           1 :     CPPUNIT_ASSERT(bobBanned);
    1707           1 : }
    1708             : 
    1709             : void
    1710           1 : ConversationMembersEventTest::testAddContactTwice()
    1711             : {
    1712           1 :     connectSignals();
    1713           1 :     std::cout << "\nRunning test: " << __func__ << std::endl;
    1714             : 
    1715           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1716           1 :     auto aliceUri = aliceAccount->getUsername();
    1717           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1718           1 :     auto bobUri = bobAccount->getUsername();
    1719             : 
    1720             :     // Add contact
    1721           1 :     aliceAccount->addContact(bobUri);
    1722           1 :     aliceAccount->sendTrustRequest(bobUri, {});
    1723           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.requestReceived; }));
    1724           1 :     auto oldConversationId = aliceData.conversationId;
    1725           1 :     CPPUNIT_ASSERT(!oldConversationId.empty());
    1726             : 
    1727             :     // Check that the trust request's data is correct
    1728           1 :     auto bobTrustRequests = bobAccount->getTrustRequests();
    1729           1 :     CPPUNIT_ASSERT(bobTrustRequests.size() == 1);
    1730           1 :     auto request = bobTrustRequests[0];
    1731           1 :     CPPUNIT_ASSERT(request["from"] == aliceUri);
    1732           1 :     CPPUNIT_ASSERT(request["conversationId"] == oldConversationId);
    1733             : 
    1734             :     // Remove and re-add contact
    1735           1 :     aliceAccount->removeContact(bobUri, false);
    1736           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceData.removed; }));
    1737             :     // wait that connections are closed.
    1738           1 :     std::this_thread::sleep_for(10s);
    1739           1 :     bobData.requestReceived = false;
    1740           1 :     aliceAccount->addContact(bobUri);
    1741           1 :     aliceAccount->sendTrustRequest(bobUri, {});
    1742           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.requestRemoved && bobData.requestReceived; }));
    1743           1 :     auto newConversationId = aliceData.conversationId;
    1744           1 :     CPPUNIT_ASSERT(!newConversationId.empty());
    1745           1 :     CPPUNIT_ASSERT(newConversationId != oldConversationId);
    1746             : 
    1747             :     // Check that the trust request's data was correctly
    1748             :     // updated when we received the second request
    1749           1 :     bobTrustRequests = bobAccount->getTrustRequests();
    1750           1 :     CPPUNIT_ASSERT(bobTrustRequests.size() == 1);
    1751           1 :     request = bobTrustRequests[0];
    1752           1 :     CPPUNIT_ASSERT(request["from"] == aliceUri);
    1753           1 :     CPPUNIT_ASSERT(request["conversationId"] == newConversationId);
    1754           1 : }
    1755             : 
    1756             : void
    1757           1 : ConversationMembersEventTest::testBanFromNewDevice()
    1758             : {
    1759           1 :     connectSignals();
    1760             : 
    1761           1 :     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
    1762           1 :     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
    1763           1 :     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
    1764           1 :     auto aliceUri = aliceAccount->getUsername();
    1765           1 :     auto bobUri = bobAccount->getUsername();
    1766           1 :     auto carlaUri = carlaAccount->getUsername();
    1767             : 
    1768           1 :     Manager::instance().sendRegister(carlaId, true);
    1769           4 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
    1770             : 
    1771           1 :     auto convId = libjami::startConversation(bobId);
    1772             : 
    1773           1 :     libjami::addConversationMember(bobId, convId, aliceUri);
    1774           1 :     libjami::addConversationMember(bobId, convId, carlaUri);
    1775           6 :     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.requestReceived; }));
    1776             : 
    1777           1 :     libjami::acceptConversationRequest(carlaId, convId);
    1778           3 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !carlaData.conversationId.empty(); }));
    1779             : 
    1780           1 :     Manager::instance().sendRegister(carlaId, false);
    1781             : 
    1782             :     // Bob creates a second device
    1783           2 :     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
    1784           1 :     std::remove(bobArchive.c_str());
    1785           1 :     bobAccount->exportArchive(bobArchive);
    1786           2 :     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
    1787           1 :     details[ConfProperties::TYPE] = "RING";
    1788           1 :     details[ConfProperties::DISPLAYNAME] = "BOB2";
    1789           1 :     details[ConfProperties::ALIAS] = "BOB2";
    1790           1 :     details[ConfProperties::UPNP_ENABLED] = "true";
    1791           1 :     details[ConfProperties::ARCHIVE_PASSWORD] = "";
    1792           1 :     details[ConfProperties::ARCHIVE_PIN] = "";
    1793           1 :     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
    1794           1 :     bob2Id = Manager::instance().addAccount(details);
    1795             : 
    1796           7 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
    1797           2 :     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bob2Data.conversationId.empty(); }));
    1798             : 
    1799           1 :     auto bobMsgSize = bobData.messages.size();
    1800           1 :     libjami::removeConversationMember(bob2Id, convId, aliceUri);
    1801           4 :     CPPUNIT_ASSERT(
    1802             :         cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 2 /* vote + ban */; }));
    1803             : 
    1804           1 :     Manager::instance().sendRegister(bob2Id, false);
    1805           1 :     auto carlaMsgSize = carlaData.messages.size();
    1806           1 :     Manager::instance().sendRegister(carlaId, true);
    1807             :     // Should sync!
    1808           6 :     CPPUNIT_ASSERT(
    1809             :         cv.wait_for(lk, 30s, [&]() { return carlaData.messages.size() > carlaMsgSize; }));
    1810           1 : }
    1811             : 
    1812             : } // namespace test
    1813             : } // namespace jami
    1814             : 
    1815           1 : RING_TEST_RUNNER(jami::test::ConversationMembersEventTest::name())

Generated by: LCOV version 1.14