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