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 "fileutils.h"
19 : #include "manager.h"
20 : #include "jamidht/jamiaccount.h"
21 : #include "../../test_runner.h"
22 : #include "jami.h"
23 : #include "account_const.h"
24 : #include "common.h"
25 :
26 : #include <dhtnet/connectionmanager.h>
27 : #include <dhtnet/multiplexed_socket.h>
28 :
29 : #include <cppunit/TestAssert.h>
30 : #include <cppunit/TestFixture.h>
31 : #include <cppunit/extensions/HelperMacros.h>
32 :
33 : #include <chrono>
34 : #include <condition_variable>
35 : #include <filesystem>
36 :
37 : using namespace libjami::Account;
38 : using namespace std::literals::chrono_literals;
39 :
40 : namespace jami {
41 : namespace test {
42 :
43 : struct UserData {
44 : std::string conversationId;
45 : bool removed {false};
46 : bool requestReceived {false};
47 : bool requestRemoved {false};
48 : bool errorDetected {false};
49 : bool registered {false};
50 : bool stopped {false};
51 : bool deviceAnnounced {false};
52 : bool sending {false};
53 : bool sent {false};
54 : std::string profilePath;
55 : std::string payloadTrustRequest;
56 : std::vector<libjami::SwarmMessage> messages;
57 : std::vector<libjami::SwarmMessage> messagesLoaded;
58 : std::vector<libjami::SwarmMessage> messagesUpdated;
59 : std::map<std::string, int> members;
60 : };
61 :
62 : class SyncHistoryTest : public CppUnit::TestFixture
63 : {
64 : public:
65 14 : SyncHistoryTest()
66 14 : {
67 : // Init daemon
68 14 : libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
69 14 : if (not Manager::instance().initialized)
70 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
71 14 : }
72 28 : ~SyncHistoryTest() { libjami::fini(); }
73 2 : static std::string name() { return "SyncHistory"; }
74 : void setUp();
75 : void tearDown();
76 :
77 : std::string aliceId;
78 : UserData aliceData;
79 : std::string bobId;
80 : UserData bobData;
81 : std::string alice2Id;
82 : UserData alice2Data;
83 :
84 : std::mutex mtx;
85 : std::unique_lock<std::mutex> lk {mtx};
86 : std::condition_variable cv;
87 : void connectSignals();
88 :
89 : private:
90 : void testCreateConversationThenSync();
91 : void testCreateConversationWithOnlineDevice();
92 : void testCreateConversationWithMessagesThenAddDevice();
93 : void testCreateMultipleConversationThenAddDevice();
94 : void testReceivesInviteThenAddDevice();
95 : void testRemoveConversationOnAllDevices();
96 : void testSyncCreateAccountExportDeleteReimportOldBackup();
97 : void testSyncCreateAccountExportDeleteReimportWithConvId();
98 : void testSyncCreateAccountExportDeleteReimportWithConvReq();
99 : void testSyncOneToOne();
100 : void testConversationRequestRemoved();
101 : void testProfileReceivedMultiDevice();
102 : void testLastInteractionAfterClone();
103 : void testLastInteractionAfterSomeMessages();
104 :
105 2 : CPPUNIT_TEST_SUITE(SyncHistoryTest);
106 1 : CPPUNIT_TEST(testCreateConversationThenSync);
107 1 : CPPUNIT_TEST(testCreateConversationWithOnlineDevice);
108 1 : CPPUNIT_TEST(testCreateConversationWithMessagesThenAddDevice);
109 1 : CPPUNIT_TEST(testCreateMultipleConversationThenAddDevice);
110 1 : CPPUNIT_TEST(testReceivesInviteThenAddDevice);
111 1 : CPPUNIT_TEST(testRemoveConversationOnAllDevices);
112 1 : CPPUNIT_TEST(testSyncCreateAccountExportDeleteReimportOldBackup);
113 1 : CPPUNIT_TEST(testSyncCreateAccountExportDeleteReimportWithConvId);
114 1 : CPPUNIT_TEST(testSyncCreateAccountExportDeleteReimportWithConvReq);
115 1 : CPPUNIT_TEST(testSyncOneToOne);
116 1 : CPPUNIT_TEST(testConversationRequestRemoved);
117 1 : CPPUNIT_TEST(testProfileReceivedMultiDevice);
118 1 : CPPUNIT_TEST(testLastInteractionAfterClone);
119 1 : CPPUNIT_TEST(testLastInteractionAfterSomeMessages);
120 4 : CPPUNIT_TEST_SUITE_END();
121 : };
122 :
123 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SyncHistoryTest, SyncHistoryTest::name());
124 :
125 : void
126 14 : SyncHistoryTest::setUp()
127 : {
128 28 : auto actors = load_actors_and_wait_for_announcement("actors/alice-bob.yml");
129 14 : aliceId = actors["alice"];
130 14 : bobId = actors["bob"];
131 14 : alice2Id = "";
132 14 : aliceData = {};
133 14 : bobData = {};
134 14 : alice2Data = {};
135 14 : }
136 :
137 : void
138 14 : SyncHistoryTest::tearDown()
139 : {
140 28 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
141 14 : std::remove(aliceArchive.c_str());
142 14 : if (alice2Id.empty()) {
143 0 : wait_for_removal_of({aliceId, bobId});
144 : } else {
145 56 : wait_for_removal_of({aliceId, bobId, alice2Id});
146 : }
147 14 : }
148 :
149 : void
150 13 : SyncHistoryTest::connectSignals()
151 : {
152 13 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
153 13 : confHandlers.insert(
154 26 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
155 60 : [&](const std::string& accountId, const std::map<std::string, std::string>&) {
156 60 : if (accountId == aliceId) {
157 3 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
158 3 : auto details = aliceAccount->getVolatileAccountDetails();
159 6 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
160 3 : if (daemonStatus == "REGISTERED") {
161 0 : aliceData.registered = true;
162 3 : } else if (daemonStatus == "UNREGISTERED") {
163 3 : aliceData.stopped = true;
164 : }
165 6 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
166 3 : aliceData.deviceAnnounced = deviceAnnounced == "true";
167 60 : } else if (accountId == bobId) {
168 0 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
169 0 : auto details = bobAccount->getVolatileAccountDetails();
170 0 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
171 0 : if (daemonStatus == "REGISTERED") {
172 0 : bobData.registered = true;
173 0 : } else if (daemonStatus == "UNREGISTERED") {
174 0 : bobData.stopped = true;
175 : }
176 0 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
177 0 : bobData.deviceAnnounced = deviceAnnounced == "true";
178 57 : } else if (accountId == alice2Id) {
179 44 : auto alice2Account = Manager::instance().getAccount<JamiAccount>(alice2Id);
180 44 : auto details = alice2Account->getVolatileAccountDetails();
181 88 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
182 44 : if (daemonStatus == "REGISTERED") {
183 18 : alice2Data.registered = true;
184 26 : } else if (daemonStatus == "UNREGISTERED") {
185 13 : alice2Data.stopped = true;
186 : }
187 88 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
188 44 : alice2Data.deviceAnnounced = deviceAnnounced == "true";
189 44 : }
190 60 : cv.notify_one();
191 60 : }));
192 13 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
193 29 : [&](const std::string& accountId, const std::string& conversationId) {
194 29 : if (accountId == aliceId) {
195 10 : aliceData.conversationId = conversationId;
196 19 : } else if (accountId == bobId) {
197 8 : bobData.conversationId = conversationId;
198 11 : } else if (accountId == alice2Id) {
199 11 : alice2Data.conversationId = conversationId;
200 : }
201 29 : cv.notify_one();
202 29 : }));
203 13 : confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ProfileReceived>(
204 9 : [&](const std::string& accountId, const std::string& peerId, const std::string& path) {
205 9 : if (accountId == bobId)
206 5 : bobData.profilePath = path;
207 4 : else if (accountId == aliceId)
208 1 : aliceData.profilePath = path;
209 3 : else if (accountId == alice2Id)
210 3 : alice2Data.profilePath = path;
211 9 : cv.notify_one();
212 9 : }));
213 13 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationMemberEvent>(
214 12 : [&](const std::string& accountId, const std::string& conversationId, const auto& member, auto status) {
215 12 : if (accountId == aliceId) {
216 7 : aliceData.members[member] = status;
217 5 : } else if (accountId == bobId) {
218 4 : bobData.members[member] = status;
219 1 : } else if (accountId == alice2Id) {
220 1 : alice2Data.members[member] = status;
221 : }
222 12 : cv.notify_one();
223 12 : }));
224 13 : confHandlers.insert(
225 26 : libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
226 86 : [&](const std::string& accountId,
227 : const std::string& /*conversationId*/,
228 : const std::string& /*peer*/,
229 : const std::string& /*msgId*/,
230 : int status) {
231 86 : if (accountId == aliceId) {
232 36 : if (status == 2)
233 11 : aliceData.sending = true;
234 36 : if (status == 3)
235 10 : aliceData.sent = true;
236 50 : } else if (accountId == alice2Id) {
237 7 : if (status == 2)
238 4 : alice2Data.sending = true;
239 7 : if (status == 3)
240 1 : alice2Data.sent = true;
241 43 : } else if (accountId == bobId) {
242 43 : if (status == 2)
243 17 : bobData.sending = true;
244 43 : if (status == 3)
245 7 : bobData.sent = true;
246 : }
247 86 : cv.notify_one();
248 86 : }));
249 13 : confHandlers.insert(
250 26 : libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
251 3 : [&](const std::string& account_id,
252 : const std::string& /*from*/,
253 : const std::string& /*conversationId*/,
254 : const std::vector<uint8_t>& payload,
255 : time_t /*received*/) {
256 3 : auto payloadStr = std::string(payload.data(), payload.data() + payload.size());
257 3 : if (account_id == aliceId)
258 0 : aliceData.payloadTrustRequest = payloadStr;
259 3 : else if (account_id == bobId)
260 3 : bobData.payloadTrustRequest = payloadStr;
261 3 : cv.notify_one();
262 3 : }));
263 13 : confHandlers.insert(
264 26 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
265 10 : [&](const std::string& accountId,
266 : const std::string& /* conversationId */,
267 : std::map<std::string, std::string> /*metadatas*/) {
268 10 : if (accountId == aliceId) {
269 3 : aliceData.requestReceived = true;
270 7 : } else if (accountId == bobId) {
271 5 : bobData.requestReceived = true;
272 2 : } else if (accountId == alice2Id) {
273 2 : alice2Data.requestReceived = true;
274 : }
275 10 : cv.notify_one();
276 10 : }));
277 13 : confHandlers.insert(
278 26 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
279 2 : [&](const std::string& accountId, const std::string&) {
280 2 : if (accountId == bobId) {
281 0 : bobData.requestRemoved = true;
282 2 : } else if (accountId == aliceId) {
283 1 : aliceData.requestRemoved = true;
284 1 : } else if (accountId == alice2Id) {
285 1 : alice2Data.requestRemoved = true;
286 : }
287 2 : cv.notify_one();
288 2 : }));
289 13 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
290 35 : [&](const std::string& accountId,
291 : const std::string& /* conversationId */,
292 : libjami::SwarmMessage message) {
293 35 : if (accountId == aliceId) {
294 16 : aliceData.messages.emplace_back(message);
295 19 : } else if (accountId == bobId) {
296 13 : bobData.messages.emplace_back(message);
297 6 : } else if (accountId == alice2Id) {
298 6 : alice2Data.messages.emplace_back(message);
299 : }
300 35 : cv.notify_one();
301 35 : }));
302 13 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmLoaded>(
303 1 : [&](uint32_t, const std::string& accountId,
304 : const std::string& /* conversationId */,
305 : std::vector<libjami::SwarmMessage> messages) {
306 1 : if (accountId == aliceId) {
307 0 : aliceData.messagesLoaded.insert(aliceData.messagesLoaded.end(), messages.begin(), messages.end());
308 1 : } else if (accountId == alice2Id) {
309 1 : alice2Data.messagesLoaded.insert(alice2Data.messagesLoaded.end(), messages.begin(), messages.end());
310 0 : } else if (accountId == bobId) {
311 0 : bobData.messagesLoaded.insert(bobData.messagesLoaded.end(), messages.begin(), messages.end());
312 : }
313 1 : cv.notify_one();
314 1 : }));
315 13 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
316 0 : [&](const std::string& accountId,
317 : const std::string& /* conversationId */,
318 : libjami::SwarmMessage message) {
319 0 : if (accountId == aliceId) {
320 0 : aliceData.messagesUpdated.emplace_back(message);
321 0 : } else if (accountId == bobId) {
322 0 : bobData.messagesUpdated.emplace_back(message);
323 : }
324 0 : cv.notify_one();
325 0 : }));
326 13 : confHandlers.insert(
327 26 : libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
328 0 : [&](const std::string& accountId,
329 : const std::string& /* conversationId */,
330 : int /*code*/,
331 : const std::string& /* what */) {
332 0 : if (accountId == aliceId)
333 0 : aliceData.errorDetected = true;
334 0 : else if (accountId == bobId)
335 0 : bobData.errorDetected = true;
336 0 : cv.notify_one();
337 0 : }));
338 13 : confHandlers.insert(
339 26 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
340 2 : [&](const std::string& accountId, const std::string&) {
341 2 : if (accountId == aliceId)
342 1 : aliceData.removed = true;
343 1 : else if (accountId == bobId)
344 0 : bobData.removed = true;
345 1 : else if (accountId == alice2Id)
346 1 : alice2Data.removed = true;
347 2 : cv.notify_one();
348 2 : }));
349 13 : libjami::registerSignalHandlers(confHandlers);
350 13 : }
351 :
352 : void
353 1 : SyncHistoryTest::testCreateConversationThenSync()
354 : {
355 1 : connectSignals();
356 :
357 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
358 : // Start conversation
359 1 : auto convId = libjami::startConversation(aliceId);
360 :
361 : // Now create alice2
362 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
363 1 : aliceAccount->exportArchive(aliceArchive);
364 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
365 1 : details[ConfProperties::TYPE] = "RING";
366 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
367 1 : details[ConfProperties::ALIAS] = "ALICE2";
368 1 : details[ConfProperties::UPNP_ENABLED] = "true";
369 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
370 1 : details[ConfProperties::ARCHIVE_PIN] = "";
371 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
372 :
373 1 : alice2Id = Manager::instance().addAccount(details);
374 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
375 1 : }
376 :
377 : void
378 1 : SyncHistoryTest::testCreateConversationWithOnlineDevice()
379 : {
380 1 : connectSignals();
381 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
382 :
383 : // Now create alice2
384 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
385 1 : aliceAccount->exportArchive(aliceArchive);
386 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
387 1 : details[ConfProperties::TYPE] = "RING";
388 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
389 1 : details[ConfProperties::ALIAS] = "ALICE2";
390 1 : details[ConfProperties::UPNP_ENABLED] = "true";
391 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
392 1 : details[ConfProperties::ARCHIVE_PIN] = "";
393 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
394 1 : auto convId = libjami::startConversation(aliceId);
395 1 : alice2Id = Manager::instance().addAccount(details);
396 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
397 1 : }
398 :
399 : void
400 1 : SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice()
401 : {
402 1 : connectSignals();
403 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
404 1 : auto convId = libjami::startConversation(aliceId);
405 :
406 : // Start conversation
407 1 : auto aliceMsgSize = aliceData.messages.size();
408 1 : libjami::sendMessage(aliceId, convId, std::string("Message 1"), "");
409 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 1 == aliceData.messages.size(); }));
410 1 : libjami::sendMessage(aliceId, convId, std::string("Message 2"), "");
411 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 2 == aliceData.messages.size(); }));
412 1 : libjami::sendMessage(aliceId, convId, std::string("Message 3"), "");
413 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 3 == aliceData.messages.size(); }));
414 :
415 : // Now create alice2
416 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
417 1 : aliceAccount->exportArchive(aliceArchive);
418 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
419 1 : details[ConfProperties::TYPE] = "RING";
420 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
421 1 : details[ConfProperties::ALIAS] = "ALICE2";
422 1 : details[ConfProperties::UPNP_ENABLED] = "true";
423 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
424 1 : details[ConfProperties::ARCHIVE_PIN] = "";
425 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
426 1 : alice2Id = Manager::instance().addAccount(details);
427 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
428 :
429 1 : libjami::loadConversation(alice2Id, convId, "", 0);
430 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.messagesLoaded.size() == 4; }));
431 :
432 : // Check messages
433 1 : CPPUNIT_ASSERT(alice2Data.messagesLoaded[0].body["body"] == "Message 3");
434 1 : CPPUNIT_ASSERT(alice2Data.messagesLoaded[1].body["body"] == "Message 2");
435 1 : CPPUNIT_ASSERT(alice2Data.messagesLoaded[2].body["body"] == "Message 1");
436 1 : }
437 :
438 : void
439 1 : SyncHistoryTest::testCreateMultipleConversationThenAddDevice()
440 : {
441 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
442 : // Start conversation
443 1 : auto convId = libjami::startConversation(aliceId);
444 1 : libjami::sendMessage(aliceId, convId, std::string("Message 1"), "");
445 1 : libjami::sendMessage(aliceId, convId, std::string("Message 2"), "");
446 1 : libjami::sendMessage(aliceId, convId, std::string("Message 3"), "");
447 1 : std::this_thread::sleep_for(1s);
448 1 : auto convId2 = libjami::startConversation(aliceId);
449 1 : libjami::sendMessage(aliceId, convId2, std::string("Message 1"), "");
450 1 : libjami::sendMessage(aliceId, convId2, std::string("Message 2"), "");
451 1 : libjami::sendMessage(aliceId, convId2, std::string("Message 3"), "");
452 1 : std::this_thread::sleep_for(1s);
453 1 : auto convId3 = libjami::startConversation(aliceId);
454 1 : libjami::sendMessage(aliceId, convId3, std::string("Message 1"), "");
455 1 : libjami::sendMessage(aliceId, convId3, std::string("Message 2"), "");
456 1 : libjami::sendMessage(aliceId, convId3, std::string("Message 3"), "");
457 1 : std::this_thread::sleep_for(1s);
458 1 : auto convId4 = libjami::startConversation(aliceId);
459 1 : libjami::sendMessage(aliceId, convId4, std::string("Message 1"), "");
460 1 : libjami::sendMessage(aliceId, convId4, std::string("Message 2"), "");
461 1 : libjami::sendMessage(aliceId, convId4, std::string("Message 3"), "");
462 :
463 : // Now create alice2
464 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
465 1 : aliceAccount->exportArchive(aliceArchive);
466 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
467 1 : details[ConfProperties::TYPE] = "RING";
468 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
469 1 : details[ConfProperties::ALIAS] = "ALICE2";
470 1 : details[ConfProperties::UPNP_ENABLED] = "true";
471 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
472 1 : details[ConfProperties::ARCHIVE_PIN] = "";
473 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
474 1 : alice2Id = Manager::instance().addAccount(details);
475 :
476 1 : std::mutex mtx;
477 1 : std::unique_lock lk {mtx};
478 1 : std::condition_variable cv;
479 1 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
480 1 : std::atomic_int conversationReady = 0;
481 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
482 4 : [&](const std::string& accountId, const std::string&) {
483 4 : if (accountId == alice2Id) {
484 4 : conversationReady += 1;
485 4 : cv.notify_one();
486 : }
487 4 : }));
488 1 : libjami::registerSignalHandlers(confHandlers);
489 1 : confHandlers.clear();
490 :
491 : // Check if conversation is ready
492 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady == 4; }));
493 1 : }
494 :
495 : void
496 1 : SyncHistoryTest::testReceivesInviteThenAddDevice()
497 : {
498 1 : connectSignals();
499 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
500 :
501 : // Export alice
502 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
503 1 : aliceAccount->exportArchive(aliceArchive);
504 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
505 1 : auto uri = aliceAccount->getUsername();
506 :
507 : // Start conversation for Alice
508 1 : auto convId = libjami::startConversation(bobId);
509 :
510 1 : libjami::addConversationMember(bobId, convId, uri);
511 11 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.requestReceived; }));
512 :
513 : // Now create alice2
514 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
515 1 : details[ConfProperties::TYPE] = "RING";
516 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
517 1 : details[ConfProperties::ALIAS] = "ALICE2";
518 1 : details[ConfProperties::UPNP_ENABLED] = "true";
519 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
520 1 : details[ConfProperties::ARCHIVE_PIN] = "";
521 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
522 1 : alice2Id = Manager::instance().addAccount(details);
523 :
524 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.requestReceived; }));
525 1 : }
526 :
527 : void
528 1 : SyncHistoryTest::testRemoveConversationOnAllDevices()
529 : {
530 1 : connectSignals();
531 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
532 :
533 : // Now create alice2
534 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
535 1 : aliceAccount->exportArchive(aliceArchive);
536 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
537 1 : details[ConfProperties::TYPE] = "RING";
538 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
539 1 : details[ConfProperties::ALIAS] = "ALICE2";
540 1 : details[ConfProperties::UPNP_ENABLED] = "true";
541 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
542 1 : details[ConfProperties::ARCHIVE_PIN] = "";
543 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
544 1 : auto convId = libjami::startConversation(aliceId);
545 :
546 1 : alice2Id = Manager::instance().addAccount(details);
547 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return !alice2Data.conversationId.empty(); }));
548 1 : libjami::removeConversation(aliceId, aliceData.conversationId);
549 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.removed; }));
550 1 : }
551 :
552 : void
553 1 : SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup()
554 : {
555 1 : connectSignals();
556 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
557 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
558 1 : auto bobUri = bobAccount->getUsername();
559 :
560 : // Backup alice before start conversation, worst scenario for invites
561 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
562 1 : aliceAccount->exportArchive(aliceArchive);
563 :
564 : // Start conversation
565 1 : auto convId = libjami::startConversation(aliceId);
566 :
567 1 : libjami::addConversationMember(aliceId, convId, bobUri);
568 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
569 1 : auto aliceMsgSize = aliceData.messages.size();
570 1 : libjami::acceptConversationRequest(bobId, convId);
571 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
572 :
573 : // disable account (same as removed)
574 1 : Manager::instance().sendRegister(aliceId, false);
575 1 : std::this_thread::sleep_for(5s);
576 :
577 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
578 1 : details[ConfProperties::TYPE] = "RING";
579 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
580 1 : details[ConfProperties::ALIAS] = "ALICE2";
581 1 : details[ConfProperties::UPNP_ENABLED] = "true";
582 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
583 1 : details[ConfProperties::ARCHIVE_PIN] = "";
584 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
585 1 : alice2Id = Manager::instance().addAccount(details);
586 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.deviceAnnounced; }));
587 :
588 : // This will trigger a conversation request. Cause alice2 is unable to know of first conversation
589 1 : libjami::sendMessage(bobId, convId, std::string("hi"), "");
590 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.requestReceived; }));
591 :
592 1 : libjami::acceptConversationRequest(alice2Id, convId);
593 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
594 :
595 1 : auto bobMsgSize = bobData.messages.size();
596 1 : libjami::sendMessage(alice2Id, convId, std::string("hi"), "");
597 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
598 1 : }
599 :
600 : void
601 1 : SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId()
602 : {
603 1 : connectSignals();
604 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
605 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
606 1 : auto aliceUri = aliceAccount->getUsername();
607 1 : auto bobUri = bobAccount->getUsername();
608 :
609 : // Start conversation
610 1 : auto convId = libjami::startConversation(aliceId);
611 :
612 1 : libjami::addConversationMember(aliceId, convId, bobUri);
613 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
614 :
615 1 : auto aliceMsgSize = aliceData.messages.size();
616 1 : libjami::acceptConversationRequest(bobId, convId);
617 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
618 :
619 : // We need to track presence to know when to sync
620 1 : bobAccount->trackBuddyPresence(aliceUri, true);
621 :
622 : // Backup alice after startConversation with member
623 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
624 1 : aliceAccount->exportArchive(aliceArchive);
625 :
626 : // disable account (same as removed)
627 1 : Manager::instance().sendRegister(aliceId, false);
628 1 : std::this_thread::sleep_for(5s);
629 :
630 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
631 1 : details[ConfProperties::TYPE] = "RING";
632 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
633 1 : details[ConfProperties::ALIAS] = "ALICE2";
634 1 : details[ConfProperties::UPNP_ENABLED] = "true";
635 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
636 1 : details[ConfProperties::ARCHIVE_PIN] = "";
637 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
638 1 : alice2Id = Manager::instance().addAccount(details);
639 : // Should retrieve conversation, no need for action as the convInfos is in the archive
640 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
641 :
642 1 : auto bobMsgSize = bobData.messages.size();
643 1 : libjami::sendMessage(alice2Id, convId, std::string("hi"), "");
644 5 : cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); });
645 1 : }
646 :
647 : void
648 1 : SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvReq()
649 : {
650 1 : connectSignals();
651 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
652 1 : auto aliceUri = aliceAccount->getUsername();
653 :
654 : // Start conversation
655 1 : auto convId = libjami::startConversation(bobId);
656 :
657 1 : libjami::addConversationMember(bobId, convId, aliceUri);
658 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.requestReceived; }));
659 :
660 : // Backup alice after startConversation with member
661 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
662 1 : aliceAccount->exportArchive(aliceArchive);
663 :
664 : // disable account (same as removed)
665 1 : Manager::instance().sendRegister(aliceId, false);
666 1 : std::this_thread::sleep_for(5s);
667 :
668 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
669 1 : details[ConfProperties::TYPE] = "RING";
670 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
671 1 : details[ConfProperties::ALIAS] = "ALICE2";
672 1 : details[ConfProperties::UPNP_ENABLED] = "true";
673 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
674 1 : details[ConfProperties::ARCHIVE_PIN] = "";
675 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
676 1 : alice2Id = Manager::instance().addAccount(details);
677 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.deviceAnnounced; }));
678 :
679 : // Should get the same request as before.
680 1 : auto bobMsgSize = bobData.messages.size();
681 1 : libjami::acceptConversationRequest(alice2Id, convId);
682 5 : CPPUNIT_ASSERT(
683 : cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
684 1 : }
685 :
686 : void
687 1 : SyncHistoryTest::testSyncOneToOne()
688 : {
689 1 : connectSignals();
690 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
691 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
692 :
693 1 : aliceAccount->addContact(bobAccount->getUsername());
694 : // Now create alice2
695 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
696 1 : aliceAccount->exportArchive(aliceArchive);
697 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
698 1 : details[ConfProperties::TYPE] = "RING";
699 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
700 1 : details[ConfProperties::ALIAS] = "ALICE2";
701 1 : details[ConfProperties::UPNP_ENABLED] = "true";
702 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
703 1 : details[ConfProperties::ARCHIVE_PIN] = "";
704 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
705 :
706 1 : alice2Id = Manager::instance().addAccount(details);
707 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
708 1 : }
709 :
710 : void
711 1 : SyncHistoryTest::testConversationRequestRemoved()
712 : {
713 1 : connectSignals();
714 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
715 1 : auto uri = aliceAccount->getUsername();
716 :
717 : // Export alice
718 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
719 1 : aliceAccount->exportArchive(aliceArchive);
720 :
721 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
722 :
723 : // Start conversation for Alice
724 1 : auto convId = libjami::startConversation(bobId);
725 :
726 : // Check that alice receives the request
727 1 : libjami::addConversationMember(bobId, convId, uri);
728 11 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.requestReceived; }));
729 :
730 : // Now create alice2
731 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
732 1 : details[ConfProperties::TYPE] = "RING";
733 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
734 1 : details[ConfProperties::ALIAS] = "ALICE2";
735 1 : details[ConfProperties::UPNP_ENABLED] = "true";
736 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
737 1 : details[ConfProperties::ARCHIVE_PIN] = "";
738 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
739 1 : alice2Id = Manager::instance().addAccount(details);
740 :
741 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.requestReceived; }));
742 : // Now decline trust request, this should trigger ConversationRequestDeclined both sides for Alice
743 1 : libjami::declineConversationRequest(aliceId, convId);
744 :
745 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.requestRemoved && alice2Data.requestRemoved; }));
746 1 : }
747 :
748 : void
749 1 : SyncHistoryTest::testProfileReceivedMultiDevice()
750 : {
751 1 : connectSignals();
752 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
753 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
754 1 : auto aliceUri = aliceAccount->getUsername();
755 1 : auto bobUri = bobAccount->getUsername();
756 :
757 : // Export alice
758 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
759 1 : aliceAccount->exportArchive(aliceArchive);
760 :
761 : // Set VCards
762 1 : std::string vcard = "BEGIN:VCARD\n\
763 : VERSION:2.1\n\
764 : FN:TITLE\n\
765 : DESCRIPTION:DESC\n\
766 : END:VCARD";
767 2 : auto alicePath = fileutils::get_data_dir() / aliceId
768 2 : / "profile.vcf";
769 2 : auto bobPath = fileutils::get_data_dir() / bobId
770 2 : / "profile.vcf";
771 : // Save VCard
772 1 : auto p = std::filesystem::path(alicePath);
773 1 : dhtnet::fileutils::recursive_mkdir(p.parent_path());
774 1 : std::ofstream aliceFile(alicePath);
775 1 : if (aliceFile.is_open()) {
776 1 : aliceFile << vcard;
777 1 : aliceFile.close();
778 : }
779 1 : p = std::filesystem::path(bobPath);
780 1 : dhtnet::fileutils::recursive_mkdir(p.parent_path());
781 1 : std::ofstream bobFile(bobPath);
782 1 : if (bobFile.is_open()) {
783 1 : bobFile << vcard;
784 1 : bobFile.close();
785 : }
786 :
787 1 : aliceAccount->addContact(bobUri);
788 1 : aliceAccount->sendTrustRequest(bobUri, {});
789 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
790 :
791 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
792 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
793 : return !bobData.profilePath.empty() && !aliceData.profilePath.empty() && !bobData.conversationId.empty();
794 : }));
795 1 : CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobData.profilePath));
796 :
797 : // Now create alice2
798 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
799 1 : details[ConfProperties::TYPE] = "RING";
800 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
801 1 : details[ConfProperties::ALIAS] = "ALICE2";
802 1 : details[ConfProperties::UPNP_ENABLED] = "true";
803 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
804 1 : details[ConfProperties::ARCHIVE_PIN] = "";
805 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
806 1 : bobData.profilePath = {};
807 1 : alice2Data.profilePath = {};
808 1 : alice2Id = Manager::instance().addAccount(details);
809 :
810 16 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] {
811 : return alice2Data.deviceAnnounced && !bobData.profilePath.empty() && !alice2Data.profilePath.empty(); }));
812 1 : }
813 :
814 : void
815 1 : SyncHistoryTest::testLastInteractionAfterClone()
816 : {
817 1 : connectSignals();
818 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
819 1 : auto aliceUri = aliceAccount->getUsername();
820 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
821 1 : auto bobUri = bobAccount->getUsername();
822 :
823 1 : aliceAccount->addContact(bobUri);
824 1 : aliceAccount->sendTrustRequest(bobUri, {});
825 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
826 :
827 1 : auto aliceMsgSize = aliceData.messages.size();
828 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
829 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
830 :
831 : // Start conversation
832 1 : libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 1"), "");
833 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 2 == aliceData.messages.size(); }));
834 1 : libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 2"), "");
835 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 3 == aliceData.messages.size(); }));
836 1 : libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 3"), "");
837 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 4 == aliceData.messages.size(); }));
838 :
839 1 : auto msgId = aliceData.messages.rbegin()->id;
840 1 : libjami::setMessageDisplayed(aliceId, "swarm:" + aliceData.conversationId, msgId, 3);
841 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceData.sent; }));
842 :
843 : // Now create alice2
844 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
845 1 : aliceAccount->exportArchive(aliceArchive);
846 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
847 1 : details[ConfProperties::TYPE] = "RING";
848 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
849 1 : details[ConfProperties::ALIAS] = "ALICE2";
850 1 : details[ConfProperties::UPNP_ENABLED] = "true";
851 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
852 1 : details[ConfProperties::ARCHIVE_PIN] = "";
853 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
854 1 : alice2Id = Manager::instance().addAccount(details);
855 :
856 : // Check if conversation is ready
857 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return !alice2Data.conversationId.empty(); }));
858 : // Check that last displayed is synched
859 1 : auto membersInfos = libjami::getConversationMembers(alice2Id, alice2Data.conversationId);
860 2 : CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
861 : membersInfos.end(),
862 : [&](auto infos) {
863 : return infos["uri"] == aliceUri
864 : && infos["lastDisplayed"] == msgId;
865 : })
866 : != membersInfos.end());
867 1 : }
868 :
869 : void
870 1 : SyncHistoryTest::testLastInteractionAfterSomeMessages()
871 : {
872 1 : connectSignals();
873 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
874 1 : auto aliceUri = aliceAccount->getUsername();
875 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
876 1 : auto bobUri = bobAccount->getUsername();
877 :
878 : // Creates alice2
879 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
880 1 : aliceAccount->exportArchive(aliceArchive);
881 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
882 1 : details[ConfProperties::TYPE] = "RING";
883 1 : details[ConfProperties::DISPLAYNAME] = "ALICE2";
884 1 : details[ConfProperties::ALIAS] = "ALICE2";
885 1 : details[ConfProperties::UPNP_ENABLED] = "true";
886 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
887 1 : details[ConfProperties::ARCHIVE_PIN] = "";
888 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
889 1 : alice2Id = Manager::instance().addAccount(details);
890 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {return alice2Data.deviceAnnounced; }));
891 9 : auto getMessageFromBody = [](const auto& data, const auto& body) -> std::string {
892 9 : auto it = std::find_if(data.messages.begin(), data.messages.end(), [&](auto& msg) {
893 6 : return msg.body.find("body") != msg.body.end() && msg.body.at("body") == body; });
894 9 : if (it != data.messages.end()) {
895 6 : return it->id;
896 : }
897 3 : return {};
898 : };
899 18 : auto getMessage = [](const auto& data, const auto& mid) -> bool {
900 63 : return std::find_if(data.messages.begin(), data.messages.end(), [&](auto& msg) { return msg.id == mid; }) != data.messages.end();
901 : };
902 :
903 1 : aliceAccount->addContact(bobUri);
904 1 : aliceAccount->sendTrustRequest(bobUri, {});
905 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && !alice2Data.conversationId.empty(); }));
906 :
907 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
908 6 : CPPUNIT_ASSERT(
909 : cv.wait_for(lk, 30s, [&]() { return aliceData.members[bobUri] == 1 && alice2Data.members[bobUri] == 1; }));
910 :
911 : // Start conversation
912 1 : bobData.messages.clear();
913 1 : libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 1"), "");
914 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !getMessageFromBody(bobData, "Message 1").empty(); }));
915 1 : auto msgId = getMessageFromBody(bobData, "Message 1");
916 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return getMessage(aliceData, msgId) && getMessage(alice2Data, msgId); }));
917 :
918 1 : bobData.messages.clear();
919 1 : libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 2"), "");
920 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !getMessageFromBody(bobData, "Message 2").empty(); }));
921 1 : msgId = getMessageFromBody(bobData, "Message 2");
922 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return getMessage(aliceData, msgId) && getMessage(alice2Data, msgId); }));
923 :
924 1 : bobData.messages.clear();
925 1 : libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 3"), "");
926 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !getMessageFromBody(bobData, "Message 3").empty(); }));
927 1 : msgId = getMessageFromBody(bobData, "Message 3");
928 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return getMessage(aliceData, msgId) && getMessage(alice2Data, msgId); }));
929 :
930 1 : libjami::setMessageDisplayed(aliceId, "swarm:" + aliceData.conversationId, msgId, 3);
931 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] {
932 : return aliceData.sent && alice2Data.sent; }));
933 :
934 1 : auto membersInfos = libjami::getConversationMembers(alice2Id, alice2Data.conversationId);
935 2 : CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
936 : membersInfos.end(),
937 : [&](auto infos) {
938 : return infos["uri"] == aliceUri
939 : && infos["lastDisplayed"] == msgId;
940 : })
941 : != membersInfos.end());
942 1 : }
943 :
944 : } // namespace test
945 : } // namespace jami
946 :
947 1 : RING_TEST_RUNNER(jami::test::SyncHistoryTest::name())
|