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())
|