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 "../../test_runner.h"
31 : #include "account_const.h"
32 : #include "archiver.h"
33 : #include "base64.h"
34 : #include "common.h"
35 : #include "conversation/conversationcommon.h"
36 : #include "fileutils.h"
37 : #include "jami.h"
38 : #include "manager.h"
39 : #include <dhtnet/certstore.h>
40 :
41 : using namespace std::string_literals;
42 : using namespace std::literals::chrono_literals;
43 : using namespace libjami::Account;
44 :
45 : struct ConvInfoTest
46 : {
47 : std::string id {};
48 : time_t created {0};
49 : time_t removed {0};
50 : time_t erased {0};
51 :
52 2 : MSGPACK_DEFINE_MAP(id, created, removed, erased)
53 : };
54 :
55 : namespace jami {
56 : namespace test {
57 :
58 : struct UserData {
59 : std::string conversationId;
60 : bool removed {false};
61 : bool requestReceived {false};
62 : bool errorDetected {false};
63 : bool registered {false};
64 : bool stopped {false};
65 : bool deviceAnnounced {false};
66 : bool sending {false};
67 : bool sent {false};
68 : bool searchFinished {false};
69 : std::string profilePath;
70 : std::string payloadTrustRequest;
71 : std::map<std::string, std::string> profile;
72 : std::vector<libjami::SwarmMessage> messages;
73 : std::vector<libjami::SwarmMessage> messagesUpdated;
74 : std::vector<std::map<std::string, std::string>> reactions;
75 : std::vector<std::map<std::string, std::string>> messagesFound;
76 : std::vector<std::string> reactionRemoved;
77 : std::map<std::string, std::string> preferences;
78 : };
79 :
80 : class ConversationTest : public CppUnit::TestFixture
81 : {
82 : public:
83 102 : ~ConversationTest() { libjami::fini(); }
84 2 : static std::string name() { return "Conversation"; }
85 : void setUp();
86 : void tearDown();
87 : std::string createFakeConversation(std::shared_ptr<JamiAccount> account,
88 : const std::string& fakeCert = "");
89 :
90 : std::string aliceId;
91 : UserData aliceData;
92 : std::string alice2Id;
93 : UserData alice2Data;
94 : std::string bobId;
95 : UserData bobData;
96 : std::string bob2Id;
97 : UserData bob2Data;
98 : std::string carlaId;
99 : UserData carlaData;
100 :
101 : std::mutex mtx;
102 : std::unique_lock<std::mutex> lk {mtx};
103 : std::condition_variable cv;
104 :
105 : void connectSignals();
106 :
107 : private:
108 : void testCreateConversation();
109 : void testOfflineConvModule();
110 : void testCreateConversationInvalidDisplayName();
111 : void testGetConversation();
112 : void testGetConversationsAfterRm();
113 : void testRemoveInvalidConversation();
114 : void testSendMessage();
115 : void testSendMessageWithBadDisplayName();
116 : void testReplaceWithBadCertificate();
117 : void testSendMessageTriggerMessageReceived();
118 : void testMergeTwoDifferentHeads();
119 : void testSendMessageToMultipleParticipants();
120 : void testPingPongMessages();
121 : void testSetMessageDisplayedTwice();
122 : void testSetMessageDisplayedPreference();
123 : void testSetMessageDisplayedAfterClone();
124 : void testSendMessageWithLotOfKnownDevices();
125 : void testVoteNonEmpty();
126 : void testNoBadFileInInitialCommit();
127 : void testNoBadCertInInitialCommit();
128 : void testPlainTextNoBadFile();
129 : void testVoteNoBadFile();
130 : void testETooBigClone();
131 : void testETooBigFetch();
132 : void testUnknownModeDetected();
133 : void testUpdateProfile();
134 : void testGetProfileRequest();
135 : void testCheckProfileInConversationRequest();
136 : void testCheckProfileInTrustRequest();
137 : void testMemberCannotUpdateProfile();
138 : void testUpdateProfileWithBadFile();
139 : void testFetchProfileUnauthorized();
140 : void testSyncingWhileAccepting();
141 : void testCountInteractions();
142 : void testReplayConversation();
143 : void testSyncWithoutPinnedCert();
144 : void testImportMalformedContacts();
145 : void testCloneFromMultipleDevice();
146 : void testSendReply();
147 : void testSearchInConv();
148 : void testConversationPreferences();
149 : void testConversationPreferencesBeforeClone();
150 : void testConversationPreferencesMultiDevices();
151 : void testFixContactDetails();
152 : void testRemoveOneToOneNotInDetails();
153 : void testMessageEdition();
154 : void testMessageReaction();
155 : void testMessageEditionWithReaction();
156 : void testLoadPartiallyRemovedConversation();
157 : void testReactionsOnEditedMessage();
158 : void testUpdateProfileMultiDevice();
159 :
160 2 : CPPUNIT_TEST_SUITE(ConversationTest);
161 1 : CPPUNIT_TEST(testCreateConversation);
162 1 : CPPUNIT_TEST(testOfflineConvModule);
163 1 : CPPUNIT_TEST(testCreateConversationInvalidDisplayName);
164 1 : CPPUNIT_TEST(testGetConversation);
165 1 : CPPUNIT_TEST(testGetConversationsAfterRm);
166 1 : CPPUNIT_TEST(testRemoveInvalidConversation);
167 1 : CPPUNIT_TEST(testSendMessage);
168 1 : CPPUNIT_TEST(testSendMessageWithBadDisplayName);
169 1 : CPPUNIT_TEST(testReplaceWithBadCertificate);
170 1 : CPPUNIT_TEST(testSendMessageTriggerMessageReceived);
171 1 : CPPUNIT_TEST(testMergeTwoDifferentHeads);
172 1 : CPPUNIT_TEST(testSendMessageToMultipleParticipants);
173 1 : CPPUNIT_TEST(testPingPongMessages);
174 1 : CPPUNIT_TEST(testSetMessageDisplayedTwice);
175 1 : CPPUNIT_TEST(testSetMessageDisplayedPreference);
176 1 : CPPUNIT_TEST(testSetMessageDisplayedAfterClone);
177 1 : CPPUNIT_TEST(testSendMessageWithLotOfKnownDevices);
178 1 : CPPUNIT_TEST(testVoteNonEmpty);
179 1 : CPPUNIT_TEST(testNoBadFileInInitialCommit);
180 1 : CPPUNIT_TEST(testNoBadCertInInitialCommit);
181 1 : CPPUNIT_TEST(testPlainTextNoBadFile);
182 1 : CPPUNIT_TEST(testVoteNoBadFile);
183 1 : CPPUNIT_TEST(testETooBigClone);
184 1 : CPPUNIT_TEST(testETooBigFetch);
185 1 : CPPUNIT_TEST(testUnknownModeDetected);
186 1 : CPPUNIT_TEST(testUpdateProfile);
187 1 : CPPUNIT_TEST(testGetProfileRequest);
188 1 : CPPUNIT_TEST(testCheckProfileInConversationRequest);
189 1 : CPPUNIT_TEST(testCheckProfileInTrustRequest);
190 1 : CPPUNIT_TEST(testMemberCannotUpdateProfile);
191 1 : CPPUNIT_TEST(testUpdateProfileWithBadFile);
192 1 : CPPUNIT_TEST(testFetchProfileUnauthorized);
193 1 : CPPUNIT_TEST(testSyncingWhileAccepting);
194 1 : CPPUNIT_TEST(testCountInteractions);
195 1 : CPPUNIT_TEST(testReplayConversation);
196 1 : CPPUNIT_TEST(testSyncWithoutPinnedCert);
197 1 : CPPUNIT_TEST(testImportMalformedContacts);
198 1 : CPPUNIT_TEST(testCloneFromMultipleDevice);
199 1 : CPPUNIT_TEST(testSendReply);
200 1 : CPPUNIT_TEST(testSearchInConv);
201 1 : CPPUNIT_TEST(testConversationPreferences);
202 1 : CPPUNIT_TEST(testConversationPreferencesBeforeClone);
203 1 : CPPUNIT_TEST(testConversationPreferencesMultiDevices);
204 1 : CPPUNIT_TEST(testFixContactDetails);
205 1 : CPPUNIT_TEST(testRemoveOneToOneNotInDetails);
206 1 : CPPUNIT_TEST(testMessageEdition);
207 1 : CPPUNIT_TEST(testMessageReaction);
208 1 : CPPUNIT_TEST(testMessageEditionWithReaction);
209 1 : CPPUNIT_TEST(testLoadPartiallyRemovedConversation);
210 1 : CPPUNIT_TEST(testReactionsOnEditedMessage);
211 1 : CPPUNIT_TEST(testUpdateProfileMultiDevice);
212 4 : CPPUNIT_TEST_SUITE_END();
213 : };
214 :
215 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConversationTest, ConversationTest::name());
216 :
217 : void
218 51 : ConversationTest::setUp()
219 : {
220 : // Init daemon
221 51 : libjami::init(
222 : libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
223 51 : if (not Manager::instance().initialized)
224 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
225 :
226 51 : auto actors = load_actors("actors/alice-bob-carla.yml");
227 51 : aliceId = actors["alice"];
228 51 : bobId = actors["bob"];
229 51 : carlaId = actors["carla"];
230 :
231 51 : aliceData = {};
232 51 : alice2Data = {};
233 51 : bobData = {};
234 51 : bob2Data = {};
235 51 : carlaData = {};
236 :
237 51 : Manager::instance().sendRegister(carlaId, false);
238 153 : wait_for_announcement_of({aliceId, bobId});
239 51 : }
240 :
241 : void
242 44 : ConversationTest::connectSignals()
243 : {
244 44 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
245 44 : confHandlers.insert(
246 88 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
247 57 : [&](const std::string& accountId, const std::map<std::string, std::string>&) {
248 57 : if (accountId == aliceId) {
249 13 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
250 13 : auto details = aliceAccount->getVolatileAccountDetails();
251 26 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
252 13 : if (daemonStatus == "REGISTERED") {
253 5 : aliceData.registered = true;
254 8 : } else if (daemonStatus == "UNREGISTERED") {
255 5 : aliceData.stopped = true;
256 : }
257 26 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
258 13 : aliceData.deviceAnnounced = deviceAnnounced == "true";
259 57 : } else if (accountId == bobId) {
260 0 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
261 0 : auto details = bobAccount->getVolatileAccountDetails();
262 0 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
263 0 : if (daemonStatus == "REGISTERED") {
264 0 : bobData.registered = true;
265 0 : } else if (daemonStatus == "UNREGISTERED") {
266 0 : bobData.stopped = true;
267 : }
268 0 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
269 0 : bobData.deviceAnnounced = deviceAnnounced == "true";
270 44 : } else if (accountId == bob2Id) {
271 21 : auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
272 21 : auto details = bob2Account->getVolatileAccountDetails();
273 42 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
274 21 : if (daemonStatus == "REGISTERED") {
275 9 : bob2Data.registered = true;
276 12 : } else if (daemonStatus == "UNREGISTERED") {
277 6 : bob2Data.stopped = true;
278 : }
279 42 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
280 21 : bob2Data.deviceAnnounced = deviceAnnounced == "true";
281 44 : } else if (accountId == carlaId) {
282 14 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
283 14 : auto details = carlaAccount->getVolatileAccountDetails();
284 28 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
285 14 : if (daemonStatus == "REGISTERED") {
286 9 : carlaData.registered = true;
287 5 : } else if (daemonStatus == "UNREGISTERED") {
288 0 : carlaData.stopped = true;
289 : }
290 28 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
291 14 : carlaData.deviceAnnounced = deviceAnnounced == "true";
292 14 : }
293 57 : cv.notify_one();
294 57 : }));
295 44 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
296 79 : [&](const std::string& accountId, const std::string& conversationId) {
297 79 : if (accountId == aliceId) {
298 46 : aliceData.conversationId = conversationId;
299 33 : } else if (accountId == alice2Id) {
300 1 : alice2Data.conversationId = conversationId;
301 32 : } else if (accountId == bobId) {
302 25 : bobData.conversationId = conversationId;
303 7 : } else if (accountId == bob2Id) {
304 5 : bob2Data.conversationId = conversationId;
305 2 : } else if (accountId == carlaId) {
306 2 : carlaData.conversationId = conversationId;
307 : }
308 79 : cv.notify_one();
309 79 : }));
310 44 : confHandlers.insert(
311 88 : libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
312 11 : [&](const std::string& account_id,
313 : const std::string& /*from*/,
314 : const std::string& /*conversationId*/,
315 : const std::vector<uint8_t>& payload,
316 : time_t /*received*/) {
317 11 : auto payloadStr = std::string(payload.data(), payload.data() + payload.size());
318 11 : if (account_id == aliceId)
319 0 : aliceData.payloadTrustRequest = payloadStr;
320 11 : else if (account_id == bobId)
321 9 : bobData.payloadTrustRequest = payloadStr;
322 11 : cv.notify_one();
323 11 : }));
324 44 : confHandlers.insert(
325 88 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
326 38 : [&](const std::string& accountId,
327 : const std::string& /* conversationId */,
328 : std::map<std::string, std::string> /*metadatas*/) {
329 38 : if (accountId == aliceId) {
330 2 : aliceData.requestReceived = true;
331 36 : } else if (accountId == bobId) {
332 32 : bobData.requestReceived = true;
333 4 : } else if (accountId == bob2Id) {
334 2 : bob2Data.requestReceived = true;
335 2 : } else if (accountId == carlaId) {
336 2 : carlaData.requestReceived = true;
337 : }
338 38 : cv.notify_one();
339 38 : }));
340 44 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
341 114 : [&](const std::string& accountId,
342 : const std::string& /* conversationId */,
343 : libjami::SwarmMessage message) {
344 114 : if (accountId == aliceId) {
345 83 : aliceData.messages.emplace_back(message);
346 31 : } else if (accountId == bobId) {
347 23 : bobData.messages.emplace_back(message);
348 8 : } else if (accountId == carlaId) {
349 7 : carlaData.messages.emplace_back(message);
350 : }
351 114 : cv.notify_one();
352 114 : }));
353 44 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
354 4 : [&](const std::string& accountId,
355 : const std::string& /* conversationId */,
356 : libjami::SwarmMessage message) {
357 4 : if (accountId == aliceId) {
358 3 : aliceData.messagesUpdated.emplace_back(message);
359 1 : } else if (accountId == bobId) {
360 1 : bobData.messagesUpdated.emplace_back(message);
361 0 : } else if (accountId == carlaId) {
362 0 : carlaData.messagesUpdated.emplace_back(message);
363 : }
364 4 : cv.notify_one();
365 4 : }));
366 44 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ReactionAdded>(
367 3 : [&](const std::string& accountId,
368 : const std::string& /* conversationId */,
369 : const std::string& /* messageId */,
370 : std::map<std::string, std::string> reaction) {
371 3 : if (accountId == aliceId) {
372 3 : aliceData.reactions.emplace_back(reaction);
373 0 : } else if (accountId == bobId) {
374 0 : bobData.reactions.emplace_back(reaction);
375 0 : } else if (accountId == carlaId) {
376 0 : carlaData.reactions.emplace_back(reaction);
377 : }
378 3 : cv.notify_one();
379 3 : }));
380 44 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ReactionRemoved>(
381 1 : [&](const std::string& accountId,
382 : const std::string& /* conversationId */,
383 : const std::string& /* messageId */,
384 : const std::string& reactionId) {
385 1 : if (accountId == aliceId) {
386 1 : aliceData.reactionRemoved.emplace_back(reactionId);
387 0 : } else if (accountId == bobId) {
388 0 : bobData.reactionRemoved.emplace_back(reactionId);
389 0 : } else if (accountId == carlaId) {
390 0 : carlaData.reactionRemoved.emplace_back(reactionId);
391 : }
392 1 : cv.notify_one();
393 1 : }));
394 44 : confHandlers.insert(
395 88 : libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
396 14 : [&](const std::string& accountId,
397 : const std::string& /* conversationId */,
398 : int /*code*/,
399 : const std::string& /* what */) {
400 14 : if (accountId == aliceId)
401 3 : aliceData.errorDetected = true;
402 11 : else if (accountId == bobId)
403 9 : bobData.errorDetected = true;
404 2 : else if (accountId == carlaId)
405 2 : carlaData.errorDetected = true;
406 14 : cv.notify_one();
407 14 : }));
408 44 : confHandlers.insert(
409 88 : libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
410 399 : [&](const std::string& accountId,
411 : const std::string& /*conversationId*/,
412 : const std::string& /*peer*/,
413 : const std::string& /*msgId*/,
414 : int status) {
415 399 : if (accountId == aliceId) {
416 313 : if (status == 2)
417 85 : aliceData.sending = true;
418 313 : if (status == 3)
419 22 : aliceData.sent = true;
420 86 : } else if (accountId == bobId) {
421 67 : if (status == 2)
422 21 : bobData.sending = true;
423 67 : if (status == 3)
424 5 : bobData.sent = true;
425 : }
426 399 : cv.notify_one();
427 399 : }));
428 44 : confHandlers.insert(
429 88 : libjami::exportable_callback<libjami::ConversationSignal::ConversationProfileUpdated>(
430 5 : [&](const auto& accountId, const auto& /* conversationId */, const auto& profile) {
431 5 : if (accountId == aliceId) {
432 3 : aliceData.profile = profile;
433 2 : } else if (accountId == bobId) {
434 1 : bobData.profile = profile;
435 : }
436 5 : cv.notify_one();
437 5 : }));
438 44 : confHandlers.insert(
439 88 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
440 9 : [&](const std::string& accountId, const std::string&) {
441 9 : if (accountId == aliceId)
442 9 : aliceData.removed = true;
443 0 : else if (accountId == bobId)
444 0 : bobData.removed = true;
445 0 : else if (accountId == bob2Id)
446 0 : bob2Data.removed = true;
447 9 : cv.notify_one();
448 9 : }));
449 44 : confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ProfileReceived>(
450 9 : [&](const std::string& accountId, const std::string& peerId, const std::string& path) {
451 9 : if (accountId == bobId)
452 5 : bobData.profilePath = path;
453 9 : cv.notify_one();
454 9 : }));
455 44 : confHandlers.insert(
456 88 : libjami::exportable_callback<libjami::ConversationSignal::ConversationPreferencesUpdated>(
457 7 : [&](const std::string& accountId,
458 : const std::string& conversationId,
459 : std::map<std::string, std::string> preferences) {
460 7 : if (accountId == bobId)
461 3 : bobData.preferences = preferences;
462 4 : else if (accountId == bob2Id)
463 2 : bob2Data.preferences = preferences;
464 7 : cv.notify_one();
465 7 : }));
466 44 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessagesFound>(
467 7 : [&](uint32_t,
468 : const std::string& accountId,
469 : const std::string& conversationId,
470 : std::vector<std::map<std::string, std::string>> msg) {
471 7 : if (accountId == aliceId) {
472 7 : aliceData.messagesFound.insert(aliceData.messagesFound.end(), msg.begin(), msg.end());
473 7 : aliceData.searchFinished = conversationId.empty();
474 : }
475 7 : cv.notify_one();
476 7 : }));
477 44 : libjami::registerSignalHandlers(confHandlers);
478 44 : }
479 :
480 : void
481 51 : ConversationTest::tearDown()
482 : {
483 102 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
484 51 : std::remove(bobArchive.c_str());
485 102 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
486 51 : std::remove(aliceArchive.c_str());
487 51 : if (!alice2Id.empty()) {
488 2 : wait_for_removal_of(alice2Id);
489 : }
490 :
491 51 : if (bob2Id.empty()) {
492 180 : wait_for_removal_of({aliceId, bobId, carlaId});
493 : } else {
494 30 : wait_for_removal_of({aliceId, bobId, carlaId, bob2Id});
495 : }
496 51 : }
497 :
498 : void
499 1 : ConversationTest::testCreateConversation()
500 : {
501 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
502 1 : connectSignals();
503 :
504 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
505 1 : auto aliceDeviceId = aliceAccount->currentDeviceId();
506 1 : auto uri = aliceAccount->getUsername();
507 :
508 : // Start conversation
509 1 : auto convId = libjami::startConversation(aliceId);
510 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
511 1 : ConversationRepository repo(aliceAccount, convId);
512 1 : CPPUNIT_ASSERT(repo.mode() == ConversationMode::INVITES_ONLY);
513 :
514 : // Assert that repository exists
515 2 : auto repoPath = fileutils::get_data_dir() / aliceId
516 4 : / "conversations" / convId;
517 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
518 : // Check created files
519 2 : auto adminCrt = repoPath / "admins" / (uri + ".crt");
520 1 : CPPUNIT_ASSERT(std::filesystem::is_regular_file(adminCrt));
521 1 : auto crt = std::ifstream(adminCrt);
522 1 : std::string adminCrtStr((std::istreambuf_iterator<char>(crt)), std::istreambuf_iterator<char>());
523 1 : auto cert = aliceAccount->identity().second;
524 1 : auto deviceCert = cert->toString(false);
525 1 : auto parentCert = cert->issuer->toString(true);
526 1 : CPPUNIT_ASSERT(adminCrtStr == parentCert);
527 2 : auto deviceCrt = repoPath / "devices" / (aliceDeviceId + ".crt");
528 1 : CPPUNIT_ASSERT(std::filesystem::is_regular_file(deviceCrt));
529 1 : crt = std::ifstream(deviceCrt);
530 : std::string deviceCrtStr((std::istreambuf_iterator<char>(crt)),
531 1 : std::istreambuf_iterator<char>());
532 1 : CPPUNIT_ASSERT(deviceCrtStr == deviceCert);
533 1 : }
534 :
535 : void
536 1 : ConversationTest::testOfflineConvModule()
537 : {
538 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
539 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
540 1 : CPPUNIT_ASSERT(carlaAccount->convModule() != nullptr);
541 1 : }
542 :
543 : void
544 1 : ConversationTest::testCreateConversationInvalidDisplayName()
545 : {
546 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
547 :
548 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
549 :
550 1 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
551 1 : bool conversationReady = false;
552 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
553 1 : [&](const std::string& accountId, const std::string& /* conversationId */) {
554 1 : if (accountId == aliceId) {
555 1 : conversationReady = true;
556 1 : cv.notify_one();
557 : }
558 1 : }));
559 1 : bool aliceRegistered = false;
560 1 : confHandlers.insert(
561 2 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
562 3 : [&](const std::string&, const std::map<std::string, std::string>&) {
563 3 : auto details = aliceAccount->getVolatileAccountDetails();
564 6 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
565 3 : if (daemonStatus == "REGISTERED") {
566 1 : aliceRegistered = true;
567 1 : cv.notify_one();
568 : }
569 3 : }));
570 1 : auto messageAliceReceived = 0;
571 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
572 1 : [&](const std::string& accountId,
573 : const std::string& /* conversationId */,
574 : std::map<std::string, std::string> /*message*/) {
575 1 : if (accountId == aliceId) {
576 1 : messageAliceReceived += 1;
577 : }
578 1 : cv.notify_one();
579 1 : }));
580 1 : libjami::registerSignalHandlers(confHandlers);
581 :
582 :
583 1 : std::map<std::string, std::string> details;
584 1 : details[ConfProperties::DISPLAYNAME] = " ";
585 1 : libjami::setAccountDetails(aliceId, details);
586 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceRegistered; }));
587 :
588 : // Start conversation
589 1 : auto convId = libjami::startConversation(aliceId);
590 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
591 1 : messageAliceReceived = 0;
592 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
593 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 1; }));
594 1 : }
595 :
596 : void
597 1 : ConversationTest::testGetConversation()
598 : {
599 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
600 :
601 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
602 1 : auto uri = aliceAccount->getUsername();
603 1 : auto convId = libjami::startConversation(aliceId);
604 :
605 1 : auto conversations = libjami::getConversations(aliceId);
606 1 : CPPUNIT_ASSERT(conversations.size() == 1);
607 1 : CPPUNIT_ASSERT(conversations.front() == convId);
608 1 : }
609 :
610 : void
611 1 : ConversationTest::testGetConversationsAfterRm()
612 : {
613 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
614 1 : connectSignals();
615 :
616 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
617 1 : auto uri = aliceAccount->getUsername();
618 :
619 : // Start conversation
620 1 : auto convId = libjami::startConversation(aliceId);
621 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
622 :
623 1 : auto conversations = libjami::getConversations(aliceId);
624 1 : CPPUNIT_ASSERT(conversations.size() == 1);
625 1 : CPPUNIT_ASSERT(libjami::removeConversation(aliceId, convId));
626 1 : conversations = libjami::getConversations(aliceId);
627 1 : CPPUNIT_ASSERT(conversations.size() == 0);
628 1 : }
629 :
630 : void
631 1 : ConversationTest::testRemoveInvalidConversation()
632 : {
633 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
634 1 : connectSignals();
635 :
636 : // Start conversation
637 1 : auto convId = libjami::startConversation(aliceId);
638 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
639 :
640 1 : auto conversations = libjami::getConversations(aliceId);
641 1 : CPPUNIT_ASSERT(conversations.size() == 1);
642 1 : CPPUNIT_ASSERT(!libjami::removeConversation(aliceId, "foo"));
643 1 : conversations = libjami::getConversations(aliceId);
644 1 : CPPUNIT_ASSERT(conversations.size() == 1);
645 1 : }
646 :
647 : void
648 1 : ConversationTest::testSendMessage()
649 : {
650 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
651 1 : connectSignals();
652 :
653 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
654 1 : auto bobUri = bobAccount->getUsername();
655 :
656 1 : auto convId = libjami::startConversation(aliceId);
657 1 : libjami::addConversationMember(aliceId, convId, bobUri);
658 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
659 :
660 1 : libjami::acceptConversationRequest(bobId, convId);
661 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
662 :
663 : // Assert that repository exists
664 2 : auto repoPath = fileutils::get_data_dir() / bobId
665 4 : / "conversations" / convId;
666 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
667 : // Wait that alice sees Bob
668 3 : cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == 2; });
669 :
670 1 : auto bobMsgSize = bobData.messages.size();
671 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
672 5 : cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 1; });
673 1 : }
674 :
675 : void
676 1 : ConversationTest::testSendMessageWithBadDisplayName()
677 : {
678 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
679 1 : connectSignals();
680 :
681 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
682 1 : auto bobUri = bobAccount->getUsername();
683 :
684 1 : std::map<std::string, std::string> details;
685 1 : details[ConfProperties::DISPLAYNAME] = "<o>";
686 1 : libjami::setAccountDetails(aliceId, details);
687 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.registered; }));
688 :
689 1 : auto convId = libjami::startConversation(aliceId);
690 1 : libjami::addConversationMember(aliceId, convId, bobUri);
691 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
692 :
693 1 : libjami::acceptConversationRequest(bobId, convId);
694 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
695 :
696 : // Assert that repository exists
697 2 : auto repoPath = fileutils::get_data_dir() / bobId
698 4 : / "conversations" / convId;
699 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
700 : // Wait that alice sees Bob
701 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == 2; }));
702 :
703 1 : auto bobMsgSize = bobData.messages.size();
704 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
705 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 1; }));
706 1 : }
707 :
708 : void
709 1 : ConversationTest::testReplaceWithBadCertificate()
710 : {
711 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
712 1 : connectSignals();
713 :
714 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
715 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
716 1 : auto bobUri = bobAccount->getUsername();
717 :
718 1 : auto convId = libjami::startConversation(aliceId);
719 :
720 1 : libjami::addConversationMember(aliceId, convId, bobUri);
721 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
722 :
723 1 : libjami::acceptConversationRequest(bobId, convId);
724 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
725 :
726 : // Wait that alice sees Bob
727 3 : cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == 2; });
728 :
729 : // Replace alice's certificate with a bad one.
730 2 : auto repoPath = fileutils::get_data_dir() / aliceId / "conversations" / convId;
731 3 : auto aliceDevicePath = repoPath / "devices" / fmt::format("{}.crt", aliceAccount->currentDeviceId());
732 3 : auto bobDevicePath = repoPath / "devices" / fmt::format("{}.crt", bobAccount->currentDeviceId());
733 1 : std::filesystem::copy(bobDevicePath,
734 : aliceDevicePath,
735 : std::filesystem::copy_options::overwrite_existing);
736 1 : addAll(aliceAccount, convId);
737 :
738 : // Note: Do not use libjami::sendMessage as it will replace the invalid certificate by a valid one
739 1 : Json::Value root;
740 1 : root["type"] = "text/plain";
741 1 : root["body"] = "hi";
742 1 : Json::StreamWriterBuilder wbuilder;
743 1 : wbuilder["commentStyle"] = "None";
744 1 : wbuilder["indentation"] = "";
745 1 : auto message = Json::writeString(wbuilder, root);
746 1 : commitInRepo(repoPath, aliceAccount, message);
747 : // now we need to sync!
748 1 : bobData.errorDetected = false;
749 1 : libjami::sendMessage(aliceId, convId, "trigger sync!"s, "");
750 : // We should detect the incorrect commit!
751 5 : cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; });
752 1 : }
753 :
754 : void
755 1 : ConversationTest::testSendMessageTriggerMessageReceived()
756 : {
757 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
758 1 : connectSignals();
759 :
760 1 : auto convId = libjami::startConversation(aliceId);
761 2 : cv.wait_for(lk, 30s, [&] { return !aliceData.conversationId.empty(); });
762 :
763 1 : auto msgSize = aliceData.messages.size();
764 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
765 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.messages.size() == msgSize + 1; }));
766 1 : }
767 :
768 : void
769 1 : ConversationTest::testMergeTwoDifferentHeads()
770 : {
771 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
772 1 : connectSignals();
773 :
774 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
775 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
776 1 : auto aliceUri = aliceAccount->getUsername();
777 1 : auto carlaUri = carlaAccount->getUsername();
778 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
779 1 : carlaAccount->trackBuddyPresence(aliceUri, true);
780 1 : auto convId = libjami::startConversation(aliceId);
781 :
782 1 : auto msgSize = aliceData.messages.size();
783 1 : aliceAccount->convModule()->addConversationMember(convId, dht::InfoHash(carlaUri), false);
784 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == msgSize + 1; }));
785 :
786 : // Cp conversations & convInfo
787 2 : auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations";
788 2 : auto repoPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID() / "conversations";
789 1 : std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
790 2 : auto ciPathAlice = fileutils::get_data_dir() / aliceId
791 2 : / "convInfo";
792 2 : auto ciPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID()
793 2 : / "convInfo";
794 1 : std::filesystem::remove_all(ciPathCarla);
795 1 : std::filesystem::copy(ciPathAlice, ciPathCarla);
796 1 : carlaAccount->convModule()->loadConversations(); // necessary to load conversation
797 :
798 : // Accept for alice and makes different heads
799 1 : ConversationRepository repo(carlaAccount, convId);
800 1 : repo.join();
801 :
802 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
803 1 : libjami::sendMessage(aliceId, convId, "sup"s, "");
804 1 : libjami::sendMessage(aliceId, convId, "jami"s, "");
805 :
806 : // Start Carla, should merge and all messages should be there
807 1 : Manager::instance().sendRegister(carlaId, true);
808 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return !carlaData.messages.empty(); }));
809 1 : }
810 :
811 : void
812 1 : ConversationTest::testSendMessageToMultipleParticipants()
813 : {
814 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
815 :
816 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
817 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
818 1 : auto bobUri = bobAccount->getUsername();
819 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
820 1 : auto carlaUri = carlaAccount->getUsername();
821 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
822 :
823 : // Enable carla
824 1 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
825 1 : bool carlaConnected = false;
826 1 : confHandlers.insert(
827 2 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
828 3 : [&](const std::string&, const std::map<std::string, std::string>&) {
829 3 : auto details = carlaAccount->getVolatileAccountDetails();
830 : auto deviceAnnounced
831 6 : = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
832 3 : if (deviceAnnounced == "true") {
833 1 : carlaConnected = true;
834 1 : cv.notify_one();
835 : }
836 3 : }));
837 1 : libjami::registerSignalHandlers(confHandlers);
838 :
839 1 : Manager::instance().sendRegister(carlaId, true);
840 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaConnected; }));
841 1 : confHandlers.clear();
842 1 : libjami::unregisterSignalHandlers();
843 :
844 1 : auto messageReceivedAlice = 0;
845 1 : auto messageReceivedBob = 0;
846 1 : auto messageReceivedCarla = 0;
847 1 : auto requestReceived = 0;
848 1 : auto conversationReady = 0;
849 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
850 14 : [&](const std::string& accountId,
851 : const std::string& /* conversationId */,
852 : std::map<std::string, std::string> /*message*/) {
853 14 : if (accountId == aliceId)
854 7 : messageReceivedAlice += 1;
855 14 : if (accountId == bobId)
856 3 : messageReceivedBob += 1;
857 14 : if (accountId == carlaId)
858 4 : messageReceivedCarla += 1;
859 14 : cv.notify_one();
860 14 : }));
861 :
862 1 : confHandlers.insert(
863 2 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
864 2 : [&](const std::string& /*accountId*/,
865 : const std::string& /* conversationId */,
866 : std::map<std::string, std::string> /*metadatas*/) {
867 2 : requestReceived += 1;
868 2 : cv.notify_one();
869 2 : }));
870 :
871 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
872 3 : [&](const std::string& /*accountId*/, const std::string& /* conversationId */) {
873 3 : conversationReady += 1;
874 3 : cv.notify_one();
875 3 : }));
876 1 : libjami::registerSignalHandlers(confHandlers);
877 :
878 1 : auto convId = libjami::startConversation(aliceId);
879 :
880 1 : libjami::addConversationMember(aliceId, convId, bobUri);
881 1 : libjami::addConversationMember(aliceId, convId, carlaUri);
882 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return requestReceived == 2; }));
883 :
884 1 : messageReceivedAlice = 0;
885 1 : libjami::acceptConversationRequest(bobId, convId);
886 1 : libjami::acceptConversationRequest(carlaId, convId);
887 : // >= because we can have merges cause the accept commits
888 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
889 : return conversationReady == 3 && messageReceivedAlice >= 2;
890 : }));
891 :
892 : // Assert that repository exists
893 2 : auto repoPath = fileutils::get_data_dir() / bobId
894 4 : / "conversations" / convId;
895 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
896 2 : repoPath = fileutils::get_data_dir() / carlaAccount->getAccountID()
897 3 : / "conversations" / convId;
898 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
899 :
900 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
901 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
902 : return messageReceivedBob >= 1 && messageReceivedCarla >= 1;
903 : }));
904 1 : libjami::unregisterSignalHandlers();
905 1 : }
906 :
907 : void
908 1 : ConversationTest::testPingPongMessages()
909 : {
910 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
911 1 : connectSignals();
912 :
913 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
914 1 : auto bobUri = bobAccount->getUsername();
915 1 : auto convId = libjami::startConversation(aliceId);
916 1 : libjami::addConversationMember(aliceId, convId, bobUri);
917 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
918 1 : auto aliceMsgSize = aliceData.messages.size();
919 1 : libjami::acceptConversationRequest(bobId, convId);
920 5 : CPPUNIT_ASSERT(
921 : cv.wait_for(lk, 60s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
922 : // Assert that repository exists
923 2 : auto repoPath = fileutils::get_data_dir() / bobId
924 4 : / "conversations" / convId;
925 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
926 1 : aliceMsgSize = aliceData.messages.size();
927 1 : auto bobMsgSize = bobData.messages.size();
928 1 : libjami::sendMessage(aliceId, convId, "ping"s, "");
929 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
930 : return bobMsgSize + 1 == bobData.messages.size() && aliceMsgSize + 1 == aliceData.messages.size();
931 : }));
932 1 : libjami::sendMessage(bobId, convId, "pong"s, "");
933 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
934 : return bobMsgSize + 2 == bobData.messages.size() && aliceMsgSize + 2 == aliceData.messages.size();
935 : }));
936 1 : libjami::sendMessage(bobId, convId, "ping"s, "");
937 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
938 : return bobMsgSize + 3 == bobData.messages.size() && aliceMsgSize + 3 == aliceData.messages.size();
939 : }));
940 1 : libjami::sendMessage(aliceId, convId, "pong"s, "");
941 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
942 : return bobMsgSize + 4 == bobData.messages.size() && aliceMsgSize + 4 == aliceData.messages.size();
943 : }));
944 1 : }
945 :
946 : void
947 1 : ConversationTest::testSetMessageDisplayedTwice()
948 : {
949 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
950 1 : connectSignals();
951 :
952 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
953 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
954 1 : auto bobUri = bobAccount->getUsername();
955 1 : auto convId = libjami::startConversation(aliceId);
956 1 : auto aliceMsgSize = aliceData.messages.size();
957 1 : libjami::addConversationMember(aliceId, convId, bobUri);
958 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
959 : // Assert that repository exists
960 2 : auto repoPath = fileutils::get_data_dir() / aliceId
961 4 : / "conversations" / convId;
962 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
963 : // Check created files
964 2 : auto bobInvited = repoPath / "invited" / bobUri;
965 1 : CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvited));
966 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
967 1 : libjami::acceptConversationRequest(bobId, convId);
968 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
969 :
970 1 : bobData.sent = false;
971 1 : aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
972 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.sent; }));
973 :
974 1 : bobData.sent = false;
975 1 : aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
976 3 : CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobData.sent; }));
977 1 : }
978 :
979 : void
980 1 : ConversationTest::testSetMessageDisplayedPreference()
981 : {
982 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
983 1 : connectSignals();
984 :
985 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
986 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
987 1 : auto aliceUri = aliceAccount->getUsername();
988 1 : auto bobUri = bobAccount->getUsername();
989 1 : auto convId = libjami::startConversation(aliceId);
990 :
991 1 : auto details = aliceAccount->getAccountDetails();
992 1 : CPPUNIT_ASSERT(details[ConfProperties::SENDREADRECEIPT] == "true");
993 1 : details[ConfProperties::SENDREADRECEIPT] = "false";
994 1 : libjami::setAccountDetails(aliceId, details);
995 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.registered; }));
996 :
997 1 : auto aliceMsgSize = aliceData.messages.size();
998 1 : libjami::addConversationMember(aliceId, convId, bobUri);
999 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
1000 :
1001 1 : libjami::acceptConversationRequest(bobId, convId);
1002 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
1003 :
1004 1 : aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
1005 : // Bob should not receive anything here, as sendMessageDisplayed is disabled for Alice
1006 4 : CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobData.sent; }));
1007 1 : }
1008 :
1009 : void
1010 1 : ConversationTest::testSetMessageDisplayedAfterClone()
1011 : {
1012 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1013 1 : connectSignals();
1014 :
1015 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1016 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1017 1 : auto aliceUri = aliceAccount->getUsername();
1018 1 : auto bobUri = bobAccount->getUsername();
1019 1 : auto convId = libjami::startConversation(aliceId);
1020 :
1021 1 : auto aliceMsgSize = aliceData.messages.size();
1022 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1023 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
1024 1 : libjami::acceptConversationRequest(bobId, convId);
1025 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
1026 :
1027 1 : aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
1028 :
1029 : // Alice creates a second device
1030 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
1031 1 : std::remove(aliceArchive.c_str());
1032 1 : aliceAccount->exportArchive(aliceArchive);
1033 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
1034 1 : details[ConfProperties::TYPE] = "RING";
1035 1 : details[ConfProperties::DISPLAYNAME] = "alice2";
1036 1 : details[ConfProperties::ALIAS] = "alice2";
1037 1 : details[ConfProperties::UPNP_ENABLED] = "true";
1038 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
1039 1 : details[ConfProperties::ARCHIVE_PIN] = "";
1040 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
1041 1 : alice2Id = Manager::instance().addAccount(details);
1042 :
1043 : // Disconnect alice2, to create a valid conv betwen Alice and alice1
1044 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !alice2Data.conversationId.empty(); }));
1045 :
1046 : // Assert that message is set as displayed for self (for the read status)
1047 1 : auto membersInfos = libjami::getConversationMembers(aliceId, convId);
1048 2 : CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
1049 : membersInfos.end(),
1050 : [&](auto infos) {
1051 : return infos["uri"] == aliceUri
1052 : && infos["lastDisplayed"] == convId;
1053 : })
1054 : != membersInfos.end());
1055 1 : }
1056 :
1057 : void
1058 1 : ConversationTest::testSendMessageWithLotOfKnownDevices()
1059 : {
1060 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1061 :
1062 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1063 :
1064 : // Alice creates a second device
1065 2 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
1066 1 : std::remove(aliceArchive.c_str());
1067 1 : aliceAccount->exportArchive(aliceArchive);
1068 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
1069 1 : details[ConfProperties::TYPE] = "RING";
1070 1 : details[ConfProperties::DISPLAYNAME] = "alice2";
1071 1 : details[ConfProperties::ALIAS] = "alice2";
1072 1 : details[ConfProperties::UPNP_ENABLED] = "true";
1073 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
1074 1 : details[ConfProperties::ARCHIVE_PIN] = "";
1075 1 : details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
1076 1 : alice2Id = Manager::instance().addAccount(details);
1077 1 : auto alice2Account = Manager::instance().getAccount<JamiAccount>(alice2Id);
1078 :
1079 1 : bool conversationAlice2Ready = false;
1080 1 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
1081 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
1082 2 : [&](const std::string& accountId, const std::string& conversationId) {
1083 2 : if (accountId == alice2Id) {
1084 1 : conversationAlice2Ready = true;
1085 : }
1086 2 : cv.notify_one();
1087 2 : }));
1088 1 : bool alice2Registered = false;
1089 1 : confHandlers.insert(
1090 2 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1091 3 : [&](const std::string&, const std::map<std::string, std::string>&) {
1092 3 : auto details = alice2Account->getVolatileAccountDetails();
1093 6 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
1094 3 : if (daemonStatus == "REGISTERED") {
1095 1 : alice2Registered = true;
1096 1 : cv.notify_one();
1097 : }
1098 3 : }));
1099 1 : libjami::registerSignalHandlers(confHandlers);
1100 :
1101 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return alice2Registered; }));
1102 :
1103 : // Add a lot of known devices
1104 1001 : for (auto i = 0; i < 1000; ++i) {
1105 1000 : dht::Hash<32> h = dht::Hash<32>::get(std::to_string(i));
1106 1000 : aliceAccount->accountManager()->getInfo()->contacts->foundAccountDevice(h);
1107 1000 : alice2Account->accountManager()->getInfo()->contacts->foundAccountDevice(h);
1108 : }
1109 :
1110 1 : auto bootstraped = std::make_shared<bool>(false);
1111 1 : alice2Account->convModule()->onBootstrapStatus(
1112 2 : [=](std::string /*convId*/, Conversation::BootstrapStatus status) {
1113 2 : *bootstraped = status == Conversation::BootstrapStatus::SUCCESS;
1114 2 : cv.notify_one();
1115 2 : });
1116 :
1117 1 : auto convId = libjami::startConversation(aliceId);
1118 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationAlice2Ready; }));
1119 :
1120 : // Should bootstrap successfully
1121 1 : *bootstraped = false;
1122 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return *bootstraped; }));
1123 1 : libjami::unregisterSignalHandlers();
1124 1 : }
1125 :
1126 : std::string
1127 2 : ConversationTest::createFakeConversation(std::shared_ptr<JamiAccount> account,
1128 : const std::string& fakeCert)
1129 : {
1130 4 : auto repoPath = fileutils::get_data_dir() / account->getAccountID()
1131 8 : / "conversations" / "tmp";
1132 :
1133 2 : git_repository* repo_ptr = nullptr;
1134 : git_repository_init_options opts;
1135 2 : git_repository_init_options_init(&opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION);
1136 2 : opts.flags |= GIT_REPOSITORY_INIT_MKPATH;
1137 2 : opts.initial_head = "main";
1138 2 : if (git_repository_init_ext(&repo_ptr, repoPath.c_str(), &opts) < 0) {
1139 0 : JAMI_ERR("Unable to create a git repository in %s", repoPath.c_str());
1140 : }
1141 2 : GitRepository repo {std::move(repo_ptr), git_repository_free};
1142 :
1143 : // Add files
1144 2 : auto deviceId = std::string(account->currentDeviceId());
1145 :
1146 2 : repoPath = git_repository_workdir(repo.get());
1147 2 : auto adminsPath = repoPath / "admins";
1148 2 : auto devicesPath = repoPath / "devices";
1149 4 : auto crlsPath = repoPath / "CRLs" / deviceId;
1150 :
1151 2 : if (!dhtnet::fileutils::recursive_mkdir(adminsPath, 0700)) {
1152 0 : JAMI_ERROR("Error when creating %s. Abort create conversations", adminsPath.c_str());
1153 : }
1154 :
1155 2 : auto cert = account->identity().second;
1156 2 : auto deviceCert = cert->toString(false);
1157 2 : auto parentCert = cert->issuer;
1158 2 : if (!parentCert) {
1159 0 : JAMI_ERR("Parent cert is null!");
1160 : }
1161 :
1162 : // /admins
1163 6 : auto adminPath = adminsPath / fmt::format("{}.crt", parentCert->getId());
1164 2 : std::ofstream file(adminPath, std::ios::trunc | std::ios::binary);
1165 2 : if (!file.is_open()) {
1166 0 : JAMI_ERROR("Unable to write data to %s", adminPath.c_str());
1167 : }
1168 2 : file << parentCert->toString(true);
1169 2 : file.close();
1170 :
1171 2 : if (!dhtnet::fileutils::recursive_mkdir(devicesPath, 0700)) {
1172 0 : JAMI_ERR("Error when creating %s. Abort create conversations", devicesPath.c_str());
1173 : }
1174 :
1175 : // /devices
1176 6 : auto devicePath = devicesPath / fmt::format("{}.crt", cert->getLongId());
1177 2 : file = std::ofstream(devicePath, std::ios::trunc | std::ios::binary);
1178 2 : if (!file.is_open()) {
1179 0 : JAMI_ERR("Unable to write data to %s", devicePath.c_str());
1180 : }
1181 2 : file << (fakeCert.empty() ? deviceCert : fakeCert);
1182 2 : file.close();
1183 :
1184 2 : if (!dhtnet::fileutils::recursive_mkdir(crlsPath, 0700)) {
1185 0 : JAMI_ERR("Error when creating %s. Abort create conversations", crlsPath.c_str());
1186 : }
1187 :
1188 2 : if (fakeCert.empty()) {
1189 : // Add a unwanted file
1190 1 : auto badFile = repoPath / "BAD";
1191 1 : file = std::ofstream(badFile, std::ios::trunc | std::ios::binary);
1192 1 : }
1193 :
1194 2 : addAll(account, "tmp");
1195 :
1196 2 : JAMI_INFO("Initial files added in %s", repoPath.c_str());
1197 :
1198 2 : std::string name = account->getDisplayName();
1199 2 : if (name.empty())
1200 0 : name = deviceId;
1201 :
1202 2 : git_signature* sig_ptr = nullptr;
1203 2 : git_index* index_ptr = nullptr;
1204 : git_oid tree_id, commit_id;
1205 2 : git_tree* tree_ptr = nullptr;
1206 2 : git_buf to_sign = {};
1207 :
1208 : // Sign commit's buffer
1209 2 : if (git_signature_new(&sig_ptr, name.c_str(), deviceId.c_str(), std::time(nullptr), 0) < 0) {
1210 0 : JAMI_ERR("Unable to create a commit signature.");
1211 : }
1212 2 : GitSignature sig {sig_ptr, git_signature_free};
1213 :
1214 2 : if (git_repository_index(&index_ptr, repo.get()) < 0) {
1215 0 : JAMI_ERR("Unable to open repository index");
1216 : }
1217 2 : GitIndex index {index_ptr, git_index_free};
1218 :
1219 2 : if (git_index_write_tree(&tree_id, index.get()) < 0) {
1220 0 : JAMI_ERR("Unable to write initial tree from index");
1221 : }
1222 :
1223 2 : if (git_tree_lookup(&tree_ptr, repo.get(), &tree_id) < 0) {
1224 0 : JAMI_ERR("Unable to look up initial tree");
1225 : }
1226 2 : GitTree tree = {tree_ptr, git_tree_free};
1227 :
1228 2 : Json::Value json;
1229 2 : json["mode"] = 1;
1230 2 : json["type"] = "initial";
1231 2 : Json::StreamWriterBuilder wbuilder;
1232 2 : wbuilder["commentStyle"] = "None";
1233 2 : wbuilder["indentation"] = "";
1234 :
1235 4 : if (git_commit_create_buffer(&to_sign,
1236 : repo.get(),
1237 2 : sig.get(),
1238 2 : sig.get(),
1239 : nullptr,
1240 4 : Json::writeString(wbuilder, json).c_str(),
1241 2 : tree.get(),
1242 : 0,
1243 : nullptr)
1244 2 : < 0) {
1245 0 : JAMI_ERR("Unable to create initial buffer");
1246 0 : return {};
1247 : }
1248 :
1249 2 : auto to_sign_vec = std::vector<uint8_t>(to_sign.ptr, to_sign.ptr + to_sign.size);
1250 2 : auto signed_buf = account->identity().first->sign(to_sign_vec);
1251 2 : std::string signed_str = base64::encode(signed_buf);
1252 :
1253 : // git commit -S
1254 4 : if (git_commit_create_with_signature(&commit_id,
1255 : repo.get(),
1256 2 : to_sign.ptr,
1257 : signed_str.c_str(),
1258 : "signature")
1259 2 : < 0) {
1260 0 : JAMI_ERR("Unable to sign initial commit");
1261 0 : return {};
1262 : }
1263 :
1264 : // Move commit to main branch
1265 2 : git_commit* commit = nullptr;
1266 2 : if (git_commit_lookup(&commit, repo.get(), &commit_id) == 0) {
1267 2 : git_reference* ref = nullptr;
1268 2 : git_branch_create(&ref, repo.get(), "main", commit, true);
1269 2 : git_commit_free(commit);
1270 2 : git_reference_free(ref);
1271 : }
1272 :
1273 2 : auto commit_str = git_oid_tostr_s(&commit_id);
1274 :
1275 4 : auto finalRepo = fileutils::get_data_dir() / account->getAccountID()
1276 8 : / "conversations" / commit_str;
1277 2 : std::rename(repoPath.c_str(), finalRepo.c_str());
1278 :
1279 10 : file = std::ofstream(fileutils::get_data_dir() / account->getAccountID()
1280 8 : / "convInfo",
1281 2 : std::ios::trunc | std::ios::binary);
1282 :
1283 2 : std::vector<ConvInfoTest> test;
1284 2 : test.emplace_back(ConvInfoTest {commit_str, std::time(nullptr), 0, 0});
1285 2 : msgpack::pack(file, test);
1286 :
1287 2 : account->convModule()->loadConversations(); // necessary to load fake conv
1288 :
1289 2 : return commit_str;
1290 2 : }
1291 :
1292 : void
1293 1 : ConversationTest::testVoteNonEmpty()
1294 : {
1295 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1296 1 : connectSignals();
1297 :
1298 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1299 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1300 1 : auto aliceUri = aliceAccount->getUsername();
1301 1 : auto bobUri = bobAccount->getUsername();
1302 1 : auto convId = libjami::startConversation(aliceId);
1303 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
1304 1 : auto carlaUri = carlaAccount->getUsername();
1305 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
1306 :
1307 1 : Manager::instance().sendRegister(carlaId, true);
1308 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
1309 :
1310 1 : auto aliceMsgSize = aliceData.messages.size();
1311 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1312 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
1313 1 : libjami::acceptConversationRequest(bobId, convId);
1314 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
1315 :
1316 1 : aliceMsgSize = aliceData.messages.size();
1317 1 : auto bobMsgSize = bobData.messages.size();
1318 1 : libjami::addConversationMember(aliceId, convId, carlaUri);
1319 14 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
1320 1 : libjami::acceptConversationRequest(carlaId, convId);
1321 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && bobMsgSize + 2 == bobData.messages.size(); }));
1322 :
1323 : // Now Alice removes Carla with a non empty file
1324 1 : addVote(aliceAccount, convId, carlaUri, "CONTENT");
1325 1 : simulateRemoval(aliceAccount, convId, carlaUri);
1326 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.errorDetected; }));
1327 1 : }
1328 :
1329 : void
1330 1 : ConversationTest::testNoBadFileInInitialCommit()
1331 : {
1332 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1333 1 : connectSignals();
1334 :
1335 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1336 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
1337 1 : auto aliceUri = aliceAccount->getUsername();
1338 :
1339 2 : auto convId = createFakeConversation(carlaAccount);
1340 1 : Manager::instance().sendRegister(carlaId, true);
1341 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
1342 1 : libjami::addConversationMember(carlaId, convId, aliceUri);
1343 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.requestReceived; }));
1344 :
1345 1 : libjami::acceptConversationRequest(aliceId, convId);
1346 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.errorDetected; }));
1347 1 : }
1348 :
1349 : void
1350 1 : ConversationTest::testNoBadCertInInitialCommit()
1351 : {
1352 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1353 1 : connectSignals();
1354 :
1355 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1356 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
1357 1 : auto carlaUri = carlaAccount->getUsername();
1358 1 : auto aliceUri = aliceAccount->getUsername();
1359 1 : auto fakeCert = aliceAccount->certStore().getCertificate(
1360 2 : std::string(aliceAccount->currentDeviceId()));
1361 1 : auto carlaCert = carlaAccount->certStore().getCertificate(
1362 2 : std::string(carlaAccount->currentDeviceId()));
1363 :
1364 1 : CPPUNIT_ASSERT(fakeCert);
1365 : // Create a conversation from Carla with Alice's device
1366 2 : auto convId = createFakeConversation(carlaAccount, fakeCert->toString(false));
1367 :
1368 1 : Manager::instance().sendRegister(carlaId, true);
1369 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
1370 1 : libjami::addConversationMember(carlaId, convId, aliceUri);
1371 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.requestReceived; }));
1372 :
1373 1 : libjami::acceptConversationRequest(aliceId, convId);
1374 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.errorDetected; }));
1375 1 : }
1376 :
1377 : void
1378 1 : ConversationTest::testPlainTextNoBadFile()
1379 : {
1380 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1381 1 : connectSignals();
1382 :
1383 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1384 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1385 1 : auto bobUri = bobAccount->getUsername();
1386 :
1387 1 : std::string convId = libjami::startConversation(aliceId);
1388 1 : auto aliceMsgSize = aliceData.messages.size();
1389 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1390 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
1391 1 : libjami::acceptConversationRequest(bobId, convId);
1392 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
1393 :
1394 1 : addFile(aliceAccount, convId, "BADFILE");
1395 1 : Json::Value root;
1396 1 : root["type"] = "text/plain";
1397 1 : root["body"] = "hi";
1398 1 : commit(aliceAccount, convId, root);
1399 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
1400 : // Check not received due to the unwanted file
1401 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
1402 1 : }
1403 :
1404 : void
1405 1 : ConversationTest::testVoteNoBadFile()
1406 : {
1407 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1408 1 : connectSignals();
1409 :
1410 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1411 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1412 1 : auto aliceUri = aliceAccount->getUsername();
1413 1 : auto bobUri = bobAccount->getUsername();
1414 1 : auto convId = libjami::startConversation(aliceId);
1415 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
1416 1 : auto carlaUri = carlaAccount->getUsername();
1417 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
1418 :
1419 1 : Manager::instance().sendRegister(carlaId, true);
1420 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.deviceAnnounced; }));
1421 :
1422 1 : auto aliceMsgSize = aliceData.messages.size();
1423 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1424 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
1425 1 : libjami::acceptConversationRequest(bobId, convId);
1426 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
1427 :
1428 1 : aliceMsgSize = aliceData.messages.size();
1429 1 : auto bobMsgSize = bobData.messages.size();
1430 1 : libjami::addConversationMember(aliceId, convId, carlaUri);
1431 11 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
1432 1 : libjami::acceptConversationRequest(carlaId, convId);
1433 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && bobMsgSize + 2 == bobData.messages.size(); }));
1434 :
1435 : // Now Alice remove Carla without a vote. Bob will not receive the message
1436 1 : addFile(aliceAccount, convId, "BADFILE");
1437 1 : aliceMsgSize = aliceData.messages.size();
1438 1 : libjami::removeConversationMember(aliceId, convId, bobUri);
1439 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
1440 :
1441 1 : auto carlaMsgSize = carlaData.messages.size();
1442 1 : libjami::sendMessage(bobId, convId, "final"s, "");
1443 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaMsgSize + 1 == carlaData.messages.size(); }));
1444 1 : }
1445 :
1446 : void
1447 1 : ConversationTest::testETooBigClone()
1448 : {
1449 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1450 1 : connectSignals();
1451 :
1452 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1453 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1454 1 : auto bobUri = bobAccount->getUsername();
1455 :
1456 1 : auto convId = libjami::startConversation(aliceId);
1457 :
1458 : // Assert that repository exists
1459 2 : auto repoPath = fileutils::get_data_dir() / aliceId
1460 4 : / "conversations" / convId;
1461 2 : std::ofstream bad(repoPath / "BADFILE");
1462 1 : CPPUNIT_ASSERT(bad.is_open());
1463 314572801 : for (int i = 0; i < 300 * 1024 * 1024; ++i)
1464 314572800 : bad << "A";
1465 1 : bad.close();
1466 :
1467 1 : addAll(aliceAccount, convId);
1468 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1469 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1470 1 : libjami::acceptConversationRequest(bobId, convId);
1471 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
1472 1 : }
1473 :
1474 : void
1475 1 : ConversationTest::testETooBigFetch()
1476 : {
1477 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1478 1 : connectSignals();
1479 :
1480 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1481 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1482 1 : auto bobUri = bobAccount->getUsername();
1483 :
1484 1 : auto convId = libjami::startConversation(aliceId);
1485 :
1486 1 : auto aliceMsgSize = aliceData.messages.size();
1487 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1488 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1489 :
1490 1 : libjami::acceptConversationRequest(bobId, convId);
1491 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
1492 :
1493 : // Wait that alice sees Bob
1494 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
1495 :
1496 2 : auto repoPath = fileutils::get_data_dir() / aliceId
1497 4 : / "conversations" / convId;
1498 2 : std::ofstream bad(repoPath / "BADFILE");
1499 1 : CPPUNIT_ASSERT(bad.is_open());
1500 314572801 : for (int i = 0; i < 300 * 1024 * 1024; ++i)
1501 314572800 : bad << "A";
1502 1 : bad.close();
1503 :
1504 1 : addAll(aliceAccount, convId);
1505 1 : Json::Value json;
1506 1 : json["body"] = "o/";
1507 1 : json["type"] = "text/plain";
1508 1 : commit(aliceAccount, convId, json);
1509 :
1510 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
1511 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
1512 1 : }
1513 :
1514 : void
1515 1 : ConversationTest::testUnknownModeDetected()
1516 : {
1517 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1518 1 : connectSignals();
1519 :
1520 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1521 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1522 1 : auto bobUri = bobAccount->getUsername();
1523 1 : auto convId = libjami::startConversation(aliceId);
1524 1 : ConversationRepository repo(aliceAccount, convId);
1525 1 : Json::Value json;
1526 1 : json["mode"] = 1412;
1527 1 : json["type"] = "initial";
1528 1 : Json::StreamWriterBuilder wbuilder;
1529 1 : wbuilder["commentStyle"] = "None";
1530 1 : wbuilder["indentation"] = "";
1531 1 : repo.amend(convId, Json::writeString(wbuilder, json));
1532 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1533 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1534 1 : libjami::acceptConversationRequest(bobId, convId);
1535 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
1536 1 : }
1537 :
1538 : void
1539 1 : ConversationTest::testUpdateProfile()
1540 : {
1541 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1542 1 : connectSignals();
1543 :
1544 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1545 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1546 1 : auto bobUri = bobAccount->getUsername();
1547 :
1548 1 : auto convId = libjami::startConversation(aliceId);
1549 1 : auto aliceMsgSize = aliceData.messages.size();
1550 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1551 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1552 1 : libjami::acceptConversationRequest(bobId, convId);
1553 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
1554 :
1555 1 : auto bobMsgSize = bobData.messages.size();
1556 2 : aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
1557 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1558 : return bobMsgSize + 1 == bobData.messages.size() && !aliceData.profile.empty() && !bobData.profile.empty();
1559 : }));
1560 :
1561 1 : auto infos = libjami::conversationInfos(bobId, convId);
1562 : // Verify that we have the same profile everywhere
1563 1 : CPPUNIT_ASSERT(infos["title"] == "My awesome swarm");
1564 1 : CPPUNIT_ASSERT(aliceData.profile["title"] == "My awesome swarm");
1565 1 : CPPUNIT_ASSERT(bobData.profile["title"] == "My awesome swarm");
1566 1 : CPPUNIT_ASSERT(infos["description"].empty());
1567 1 : CPPUNIT_ASSERT(aliceData.profile["description"].empty());
1568 1 : CPPUNIT_ASSERT(bobData.profile["description"].empty());
1569 1 : }
1570 :
1571 : void
1572 1 : ConversationTest::testGetProfileRequest()
1573 : {
1574 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1575 1 : connectSignals();
1576 :
1577 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1578 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1579 1 : auto bobUri = bobAccount->getUsername();
1580 :
1581 1 : auto convId = libjami::startConversation(aliceId);
1582 1 : auto aliceMsgSize = aliceData.messages.size();
1583 2 : aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
1584 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
1585 :
1586 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1587 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1588 :
1589 1 : auto infos = libjami::conversationInfos(bobId, convId);
1590 1 : CPPUNIT_ASSERT(infos["title"] == "My awesome swarm");
1591 1 : CPPUNIT_ASSERT(infos["description"].empty());
1592 1 : }
1593 :
1594 : void
1595 1 : ConversationTest::testCheckProfileInConversationRequest()
1596 : {
1597 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1598 1 : connectSignals();
1599 :
1600 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1601 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1602 1 : auto bobUri = bobAccount->getUsername();
1603 :
1604 1 : auto convId = libjami::startConversation(aliceId);
1605 2 : aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
1606 :
1607 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1608 11 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1609 1 : auto requests = libjami::getConversationRequests(bobId);
1610 1 : CPPUNIT_ASSERT(requests.size() == 1);
1611 1 : CPPUNIT_ASSERT(requests.front()["title"] == "My awesome swarm");
1612 1 : }
1613 :
1614 : void
1615 1 : ConversationTest::testCheckProfileInTrustRequest()
1616 : {
1617 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1618 1 : connectSignals();
1619 :
1620 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1621 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1622 1 : auto bobUri = bobAccount->getUsername();
1623 1 : std::string vcard = "BEGIN:VCARD\n\
1624 : VERSION:2.1\n\
1625 : FN:TITLE\n\
1626 : DESCRIPTION:DESC\n\
1627 : END:VCARD";
1628 1 : aliceAccount->addContact(bobUri);
1629 1 : std::vector<uint8_t> payload(vcard.begin(), vcard.end());
1630 1 : aliceAccount->sendTrustRequest(bobUri, payload);
1631 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.payloadTrustRequest == vcard; }));
1632 1 : }
1633 :
1634 : void
1635 1 : ConversationTest::testMemberCannotUpdateProfile()
1636 : {
1637 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1638 1 : connectSignals();
1639 :
1640 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1641 1 : auto bobUri = bobAccount->getUsername();
1642 :
1643 1 : auto convId = libjami::startConversation(aliceId);
1644 1 : auto aliceMsgSize = aliceData.messages.size();
1645 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1646 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1647 1 : libjami::acceptConversationRequest(bobId, convId);
1648 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
1649 :
1650 2 : bobAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
1651 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return bobData.errorDetected; }));
1652 1 : }
1653 :
1654 : void
1655 1 : ConversationTest::testUpdateProfileWithBadFile()
1656 : {
1657 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1658 1 : connectSignals();
1659 :
1660 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1661 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1662 1 : auto bobUri = bobAccount->getUsername();
1663 :
1664 1 : auto convId = libjami::startConversation(aliceId);
1665 1 : auto aliceMsgSize = aliceData.messages.size();
1666 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1667 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1668 1 : libjami::acceptConversationRequest(bobId, convId);
1669 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
1670 :
1671 : // Update profile but with bad file
1672 1 : addFile(aliceAccount, convId, "BADFILE");
1673 1 : std::string vcard = "BEGIN:VCARD\n\
1674 : VERSION:2.1\n\
1675 : FN:TITLE\n\
1676 : DESCRIPTION:DESC\n\
1677 : END:VCARD";
1678 1 : addFile(aliceAccount, convId, "profile.vcf", vcard);
1679 1 : Json::Value root;
1680 1 : root["type"] = "application/update-profile";
1681 1 : commit(aliceAccount, convId, root);
1682 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
1683 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
1684 1 : }
1685 :
1686 : void
1687 1 : ConversationTest::testFetchProfileUnauthorized()
1688 : {
1689 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1690 1 : connectSignals();
1691 :
1692 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1693 1 : auto bobUri = bobAccount->getUsername();
1694 :
1695 1 : auto convId = libjami::startConversation(aliceId);
1696 1 : auto aliceMsgSize = aliceData.messages.size();
1697 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1698 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1699 1 : libjami::acceptConversationRequest(bobId, convId);
1700 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
1701 :
1702 : // Fake realist profile update
1703 1 : std::string vcard = "BEGIN:VCARD\n\
1704 : VERSION:2.1\n\
1705 : FN:TITLE\n\
1706 : DESCRIPTION:DESC\n\
1707 : END:VCARD";
1708 1 : addFile(bobAccount, convId, "profile.vcf", vcard);
1709 1 : Json::Value root;
1710 1 : root["type"] = "application/update-profile";
1711 1 : commit(bobAccount, convId, root);
1712 1 : libjami::sendMessage(bobId, convId, "hi"s, "");
1713 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.errorDetected; }));
1714 1 : }
1715 :
1716 : void
1717 1 : ConversationTest::testSyncingWhileAccepting()
1718 : {
1719 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1720 1 : connectSignals();
1721 :
1722 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1723 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1724 1 : auto bobUri = bobAccount->getUsername();
1725 1 : auto aliceUri = aliceAccount->getUsername();
1726 :
1727 1 : aliceAccount->addContact(bobUri);
1728 1 : aliceAccount->sendTrustRequest(bobUri, {});
1729 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1730 :
1731 1 : Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
1732 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
1733 :
1734 1 : auto convInfos = libjami::conversationInfos(bobId, aliceData.conversationId);
1735 1 : CPPUNIT_ASSERT(convInfos["syncing"] == "true");
1736 1 : CPPUNIT_ASSERT(convInfos.find("created") != convInfos.end());
1737 :
1738 1 : Manager::instance().sendRegister(aliceId, true); // This avoid to sync immediately
1739 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
1740 :
1741 1 : convInfos = libjami::conversationInfos(bobId, bobData.conversationId);
1742 1 : CPPUNIT_ASSERT(convInfos.find("syncing") == convInfos.end());
1743 1 : }
1744 :
1745 : void
1746 1 : ConversationTest::testCountInteractions()
1747 : {
1748 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1749 :
1750 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1751 1 : auto convId = libjami::startConversation(aliceId);
1752 :
1753 3 : std::string msgId1 = "", msgId2 = "", msgId3 = "";
1754 : aliceAccount->convModule()
1755 1 : ->sendMessage(convId, "1"s, "", "text/plain", true, {}, [&](bool, std::string commitId) {
1756 1 : msgId1 = commitId;
1757 1 : cv.notify_one();
1758 1 : });
1759 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !msgId1.empty(); }));
1760 : aliceAccount->convModule()
1761 1 : ->sendMessage(convId, "2"s, "", "text/plain", true, {}, [&](bool, std::string commitId) {
1762 1 : msgId2 = commitId;
1763 1 : cv.notify_one();
1764 1 : });
1765 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !msgId2.empty(); }));
1766 : aliceAccount->convModule()
1767 1 : ->sendMessage(convId, "3"s, "", "text/plain", true, {}, [&](bool, std::string commitId) {
1768 1 : msgId3 = commitId;
1769 1 : cv.notify_one();
1770 1 : });
1771 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !msgId3.empty(); }));
1772 :
1773 1 : CPPUNIT_ASSERT(libjami::countInteractions(aliceId, convId, "", "", "") == 4 /* 3 + initial */);
1774 1 : CPPUNIT_ASSERT(libjami::countInteractions(aliceId, convId, "", "", aliceAccount->getUsername())
1775 : == 0);
1776 1 : CPPUNIT_ASSERT(libjami::countInteractions(aliceId, convId, msgId3, "", "") == 0);
1777 1 : CPPUNIT_ASSERT(libjami::countInteractions(aliceId, convId, msgId2, "", "") == 1);
1778 1 : }
1779 :
1780 : void
1781 1 : ConversationTest::testReplayConversation()
1782 : {
1783 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1784 1 : connectSignals();
1785 :
1786 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1787 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1788 1 : auto bobUri = bobAccount->getUsername();
1789 1 : auto aliceUri = aliceAccount->getUsername();
1790 :
1791 1 : aliceAccount->addContact(bobUri);
1792 1 : aliceAccount->sendTrustRequest(bobUri, {});
1793 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1794 1 : auto aliceMsgSize = aliceData.messages.size();
1795 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
1796 5 : CPPUNIT_ASSERT(
1797 : cv.wait_for(lk, 30s, [&]() {
1798 : return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
1799 : // removeContact
1800 1 : aliceAccount->removeContact(bobUri, false);
1801 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
1802 1 : std::this_thread::sleep_for(5s);
1803 : // re-add
1804 1 : CPPUNIT_ASSERT(bobData.conversationId != "");
1805 1 : auto oldConvId = bobData.conversationId;
1806 1 : aliceData.conversationId = "";
1807 1 : aliceAccount->addContact(bobUri);
1808 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
1809 1 : aliceMsgSize = aliceData.messages.size();
1810 1 : libjami::sendMessage(aliceId, aliceData.conversationId, "foo"s, "");
1811 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
1812 1 : libjami::sendMessage(aliceId, aliceData.conversationId, "bar"s, "");
1813 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
1814 1 : bobData.messages.clear();
1815 1 : aliceAccount->sendTrustRequest(bobUri, {});
1816 : // Should retrieve previous conversation
1817 90 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1818 : return bobData.messages.size() == 2 && bobData.messages[0].body["body"] == "foo" && bobData.messages[1].body["body"] == "bar";
1819 : }));
1820 1 : }
1821 :
1822 : void
1823 1 : ConversationTest::testSyncWithoutPinnedCert()
1824 : {
1825 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1826 1 : connectSignals();
1827 :
1828 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1829 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1830 1 : auto bobUri = bobAccount->getUsername();
1831 1 : auto aliceUri = aliceAccount->getUsername();
1832 :
1833 : // Bob creates a second device
1834 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
1835 1 : std::remove(bobArchive.c_str());
1836 1 : bobAccount->exportArchive(bobArchive);
1837 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
1838 1 : details[ConfProperties::TYPE] = "RING";
1839 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
1840 1 : details[ConfProperties::ALIAS] = "BOB2";
1841 1 : details[ConfProperties::UPNP_ENABLED] = "true";
1842 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
1843 1 : details[ConfProperties::ARCHIVE_PIN] = "";
1844 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
1845 1 : bob2Id = Manager::instance().addAccount(details);
1846 :
1847 : // Disconnect bob2, to create a valid conv betwen Alice and Bob1
1848 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.registered; }));
1849 1 : Manager::instance().sendRegister(bob2Id, false);
1850 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.stopped; }));
1851 :
1852 : // Alice adds bob
1853 1 : aliceAccount->addContact(bobUri);
1854 1 : aliceAccount->sendTrustRequest(bobUri, {});
1855 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1856 1 : auto aliceMsgSize = aliceData.messages.size();
1857 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
1858 5 : CPPUNIT_ASSERT(
1859 : cv.wait_for(lk, 30s, [&]() {
1860 : return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
1861 :
1862 : // Bob send a message
1863 1 : libjami::sendMessage(bobId, bobData.conversationId, "hi"s, "");
1864 5 : cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); });
1865 :
1866 : // Alice off, bob2 On
1867 1 : Manager::instance().sendRegister(aliceId, false);
1868 3 : cv.wait_for(lk, 10s, [&]() { return aliceData.stopped; });
1869 1 : Manager::instance().sendRegister(bob2Id, true);
1870 :
1871 : // Sync + validate
1872 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bob2Data.conversationId.empty(); }));
1873 1 : }
1874 :
1875 : void
1876 1 : ConversationTest::testImportMalformedContacts()
1877 : {
1878 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1879 :
1880 2 : auto malformedContacts = fileutils::loadFile(std::filesystem::current_path().string()
1881 3 : + "/conversation/rsc/incorrectContacts");
1882 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
1883 1 : std::remove(bobArchive.c_str());
1884 1 : archiver::compressGzip(malformedContacts, bobArchive);
1885 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
1886 1 : details[ConfProperties::TYPE] = "RING";
1887 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
1888 1 : details[ConfProperties::ALIAS] = "BOB2";
1889 1 : details[ConfProperties::UPNP_ENABLED] = "true";
1890 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
1891 1 : details[ConfProperties::ARCHIVE_PIN] = "";
1892 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
1893 1 : bob2Id = Manager::instance().addAccount(details);
1894 1 : wait_for_announcement_of({bob2Id});
1895 1 : auto contacts = libjami::getContacts(bob2Id);
1896 1 : CPPUNIT_ASSERT(contacts.size() == 1);
1897 1 : CPPUNIT_ASSERT(contacts[0][libjami::Account::TrustRequest::CONVERSATIONID] == "");
1898 1 : }
1899 :
1900 : void
1901 1 : ConversationTest::testCloneFromMultipleDevice()
1902 : {
1903 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1904 1 : connectSignals();
1905 :
1906 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1907 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1908 1 : auto bobUri = bobAccount->getUsername();
1909 :
1910 : // Bob creates a second device
1911 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
1912 1 : std::remove(bobArchive.c_str());
1913 1 : bobAccount->exportArchive(bobArchive);
1914 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
1915 1 : details[ConfProperties::TYPE] = "RING";
1916 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
1917 1 : details[ConfProperties::ALIAS] = "BOB2";
1918 1 : details[ConfProperties::UPNP_ENABLED] = "true";
1919 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
1920 1 : details[ConfProperties::ARCHIVE_PIN] = "";
1921 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
1922 1 : bob2Id = Manager::instance().addAccount(details);
1923 :
1924 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
1925 :
1926 : // Alice adds bob
1927 1 : aliceAccount->addContact(bobUri);
1928 1 : aliceAccount->sendTrustRequest(bobUri, {});
1929 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
1930 1 : auto aliceMsgSize = aliceData.messages.size();
1931 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
1932 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1933 : return !bobData.conversationId.empty() && !bob2Data.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size();
1934 : }));
1935 :
1936 : // Remove contact
1937 1 : aliceAccount->removeContact(bobUri, false);
1938 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
1939 :
1940 : // wait that connections are closed.
1941 1 : std::this_thread::sleep_for(10s);
1942 :
1943 : // Alice re-adds Bob
1944 1 : auto oldConv = bobData.conversationId;
1945 1 : aliceAccount->addContact(bobUri);
1946 1 : aliceAccount->sendTrustRequest(bobUri, {});
1947 : // This should retrieve the conversation from Bob and don't show any error
1948 6 : CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return aliceData.errorDetected; }));
1949 1 : CPPUNIT_ASSERT(oldConv == aliceData.conversationId); // Check that convId didn't change and conversation is ready.
1950 1 : }
1951 :
1952 : void
1953 1 : ConversationTest::testSendReply()
1954 : {
1955 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1956 1 : connectSignals();
1957 :
1958 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1959 1 : auto bobUri = bobAccount->getUsername();
1960 1 : auto convId = libjami::startConversation(aliceId);
1961 :
1962 1 : libjami::addConversationMember(aliceId, convId, bobUri);
1963 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1964 1 : auto aliceMsgSize = aliceData.messages.size();
1965 1 : libjami::acceptConversationRequest(bobId, convId);
1966 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
1967 :
1968 1 : auto bobMsgSize = bobData.messages.size();
1969 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
1970 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 1; }));
1971 :
1972 1 : auto validId = bobData.messages.at(0).id;
1973 1 : libjami::sendMessage(aliceId, convId, "foo"s, validId);
1974 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.messages.size() == bobMsgSize + 2; }));
1975 1 : CPPUNIT_ASSERT(bobData.messages.rbegin()->body.at("reply-to") == validId);
1976 :
1977 : // Check if parent doesn't exists, no message is generated
1978 1 : libjami::sendMessage(aliceId, convId, "foo"s, "invalid");
1979 3 : CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobData.messages.size() == bobMsgSize + 3; }));
1980 1 : }
1981 :
1982 : void
1983 1 : ConversationTest::testSearchInConv()
1984 : {
1985 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1986 1 : connectSignals();
1987 :
1988 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1989 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1990 1 : auto bobUri = bobAccount->getUsername();
1991 1 : auto aliceUri = aliceAccount->getUsername();
1992 :
1993 1 : auto aliceMsgSize = aliceData.messages.size();
1994 1 : aliceAccount->addContact(bobUri);
1995 1 : aliceAccount->sendTrustRequest(bobUri, {});
1996 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1997 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
1998 5 : CPPUNIT_ASSERT(
1999 : cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
2000 : // Add some messages
2001 1 : auto bobMsgSize = bobData.messages.size();
2002 1 : libjami::sendMessage(aliceId, aliceData.conversationId, "message 1"s, "");
2003 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
2004 1 : libjami::sendMessage(aliceId, aliceData.conversationId, "message 2"s, "");
2005 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 2 == bobData.messages.size(); }));
2006 1 : libjami::sendMessage(aliceId, aliceData.conversationId, "Message 3"s, "");
2007 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 3 == bobData.messages.size(); }));
2008 :
2009 1 : libjami::searchConversation(aliceId, aliceData.conversationId, "", "", "message", "", 0, 0, 0, 0);
2010 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messagesFound.size() == 3 && aliceData.searchFinished; }));
2011 1 : aliceData.messagesFound.clear();
2012 1 : aliceData.searchFinished = false;
2013 1 : libjami::searchConversation(aliceId, aliceData.conversationId, "", "", "Message", "", 0, 0, 0, 1);
2014 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messagesFound.size() == 1 && aliceData.searchFinished; }));
2015 1 : aliceData.messagesFound.clear();
2016 1 : aliceData.searchFinished = false;
2017 1 : libjami::searchConversation(aliceId, aliceData.conversationId, "", "", "message 2", "", 0, 0, 0, 0);
2018 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messagesFound.size() == 1 && aliceData.searchFinished; }));
2019 1 : aliceData.messagesFound.clear();
2020 1 : aliceData.searchFinished = false;
2021 1 : libjami::searchConversation(aliceId, aliceData.conversationId, "", "", "foo", "", 0, 0, 0, 0);
2022 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messagesFound.size() == 0 && aliceData.searchFinished; }));
2023 1 : }
2024 :
2025 : void
2026 1 : ConversationTest::testConversationPreferences()
2027 : {
2028 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2029 1 : connectSignals();
2030 :
2031 : // Start conversation and set preferences
2032 1 : auto convId = libjami::startConversation(aliceId);
2033 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); });
2034 1 : CPPUNIT_ASSERT(libjami::getConversationPreferences(aliceId, convId).size() == 0);
2035 2 : libjami::setConversationPreferences(aliceId, convId, {{"foo", "bar"}});
2036 1 : auto preferences = libjami::getConversationPreferences(aliceId, convId);
2037 1 : CPPUNIT_ASSERT(preferences.size() == 1);
2038 1 : CPPUNIT_ASSERT(preferences["foo"] == "bar");
2039 : // Update
2040 3 : libjami::setConversationPreferences(aliceId, convId, {{"foo", "bar2"}, {"bar", "foo"}});
2041 1 : preferences = libjami::getConversationPreferences(aliceId, convId);
2042 1 : CPPUNIT_ASSERT(preferences.size() == 2);
2043 1 : CPPUNIT_ASSERT(preferences["foo"] == "bar2");
2044 1 : CPPUNIT_ASSERT(preferences["bar"] == "foo");
2045 : // Remove conversations removes its preferences.
2046 1 : CPPUNIT_ASSERT(libjami::removeConversation(aliceId, convId));
2047 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
2048 1 : CPPUNIT_ASSERT(libjami::getConversationPreferences(aliceId, convId).size() == 0);
2049 1 : }
2050 :
2051 : void
2052 1 : ConversationTest::testConversationPreferencesBeforeClone()
2053 : {
2054 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2055 1 : connectSignals();
2056 :
2057 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
2058 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
2059 1 : auto bobUri = bobAccount->getUsername();
2060 : // Bob creates a second device
2061 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
2062 1 : std::remove(bobArchive.c_str());
2063 1 : bobAccount->exportArchive(bobArchive);
2064 : // Alice adds bob
2065 1 : aliceAccount->addContact(bobUri);
2066 1 : aliceAccount->sendTrustRequest(bobUri, {});
2067 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
2068 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
2069 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
2070 :
2071 : // Set preferences
2072 1 : Manager::instance().sendRegister(aliceId, false);
2073 3 : libjami::setConversationPreferences(bobId, bobData.conversationId, {{"foo", "bar"}, {"bar", "foo"}});
2074 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.preferences.size() == 2; }));
2075 1 : CPPUNIT_ASSERT(bobData.preferences["foo"] == "bar" && bobData.preferences["bar"] == "foo");
2076 :
2077 : // Bob2 should sync preferences
2078 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
2079 1 : details[ConfProperties::TYPE] = "RING";
2080 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
2081 1 : details[ConfProperties::ALIAS] = "BOB2";
2082 1 : details[ConfProperties::UPNP_ENABLED] = "true";
2083 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
2084 1 : details[ConfProperties::ARCHIVE_PIN] = "";
2085 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
2086 1 : bob2Id = Manager::instance().addAccount(details);
2087 10 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
2088 : return bob2Data.deviceAnnounced && !bob2Data.conversationId.empty() && !bob2Data.preferences.empty();
2089 : }));
2090 1 : CPPUNIT_ASSERT(bob2Data.preferences["foo"] == "bar" && bob2Data.preferences["bar"] == "foo");
2091 1 : }
2092 :
2093 : void
2094 1 : ConversationTest::testConversationPreferencesMultiDevices()
2095 : {
2096 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2097 1 : connectSignals();
2098 :
2099 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
2100 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
2101 1 : auto bobUri = bobAccount->getUsername();
2102 : // Bob creates a second device
2103 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
2104 1 : std::remove(bobArchive.c_str());
2105 1 : bobAccount->exportArchive(bobArchive);
2106 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
2107 1 : details[ConfProperties::TYPE] = "RING";
2108 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
2109 1 : details[ConfProperties::ALIAS] = "BOB2";
2110 1 : details[ConfProperties::UPNP_ENABLED] = "true";
2111 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
2112 1 : details[ConfProperties::ARCHIVE_PIN] = "";
2113 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
2114 1 : bob2Id = Manager::instance().addAccount(details);
2115 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
2116 : // Alice adds bob
2117 1 : aliceAccount->addContact(bobUri);
2118 1 : aliceAccount->sendTrustRequest(bobUri, {});
2119 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
2120 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
2121 6 : CPPUNIT_ASSERT(
2122 : cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && !bob2Data.conversationId.empty(); }));
2123 3 : libjami::setConversationPreferences(bobId, bobData.conversationId, {{"foo", "bar"}, {"bar", "foo"}});
2124 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
2125 : return bobData.preferences.size() == 2 && bob2Data.preferences.size() == 2;
2126 : }));
2127 1 : CPPUNIT_ASSERT(bobData.preferences["foo"] == "bar" && bobData.preferences["bar"] == "foo");
2128 1 : CPPUNIT_ASSERT(bob2Data.preferences["foo"] == "bar" && bob2Data.preferences["bar"] == "foo");
2129 1 : }
2130 :
2131 : void
2132 1 : ConversationTest::testFixContactDetails()
2133 : {
2134 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2135 1 : connectSignals();
2136 :
2137 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
2138 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
2139 1 : auto bobUri = bobAccount->getUsername();
2140 :
2141 1 : aliceAccount->addContact(bobUri);
2142 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return !aliceData.conversationId.empty(); }));
2143 :
2144 1 : auto details = aliceAccount->getContactDetails(bobUri);
2145 1 : CPPUNIT_ASSERT(details["conversationId"] == aliceData.conversationId);
2146 : // Erase convId from contact details, this should be fixed by next reload.
2147 1 : CPPUNIT_ASSERT(aliceAccount->convModule()->updateConvForContact(bobUri, aliceData.conversationId, ""));
2148 1 : details = aliceAccount->getContactDetails(bobUri);
2149 1 : CPPUNIT_ASSERT(details["conversationId"].empty());
2150 :
2151 1 : aliceAccount->convModule()->loadConversations();
2152 :
2153 1 : std::this_thread::sleep_for(5s); // Let the daemon fix the structures
2154 :
2155 1 : details = aliceAccount->getContactDetails(bobUri);
2156 1 : CPPUNIT_ASSERT(details["conversationId"] == aliceData.conversationId);
2157 1 : }
2158 :
2159 : void
2160 1 : ConversationTest::testRemoveOneToOneNotInDetails()
2161 : {
2162 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2163 1 : connectSignals();
2164 :
2165 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
2166 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
2167 1 : auto bobUri = bobAccount->getUsername();
2168 :
2169 1 : aliceAccount->addContact(bobUri);
2170 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return !aliceData.conversationId.empty(); }));
2171 :
2172 1 : auto details = aliceAccount->getContactDetails(bobUri);
2173 1 : CPPUNIT_ASSERT(details["conversationId"] == aliceData.conversationId);
2174 1 : auto firstConv = aliceData.conversationId;
2175 : // Create a duplicate
2176 1 : std::this_thread::sleep_for(2s); // Avoid to get same id
2177 1 : aliceAccount->convModule()->startConversation(ConversationMode::ONE_TO_ONE, dht::InfoHash(bobUri));
2178 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return firstConv != aliceData.conversationId; }));
2179 :
2180 : // Assert that repository exists
2181 2 : auto repoPath = fileutils::get_data_dir() / aliceId
2182 4 : / "conversations" / aliceData.conversationId;
2183 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
2184 :
2185 1 : aliceAccount->convModule()->loadConversations();
2186 :
2187 : // Check that conv is removed
2188 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
2189 1 : }
2190 :
2191 : void
2192 1 : ConversationTest::testMessageEdition()
2193 : {
2194 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2195 1 : connectSignals();
2196 :
2197 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
2198 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
2199 1 : auto bobUri = bobAccount->getUsername();
2200 1 : auto convId = libjami::startConversation(aliceId);
2201 1 : libjami::addConversationMember(aliceId, convId, bobUri);
2202 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
2203 :
2204 1 : auto aliceMsgSize = aliceData.messages.size();
2205 1 : libjami::acceptConversationRequest(bobId, convId);
2206 5 : CPPUNIT_ASSERT(
2207 : cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceData.messages.size() == aliceMsgSize + 1; }));
2208 :
2209 1 : auto bobMsgSize = bobData.messages.size();
2210 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
2211 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 1; }));
2212 1 : auto editedId = bobData.messages.rbegin()->id;
2213 : // Should trigger MessageUpdated
2214 1 : bobMsgSize = bobData.messagesUpdated.size();
2215 1 : libjami::sendMessage(aliceId, convId, "New body"s, editedId, 1);
2216 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.messagesUpdated.size() == bobMsgSize + 1; }));
2217 1 : CPPUNIT_ASSERT(bobData.messagesUpdated.rbegin()->body.at("body") == "New body");
2218 : // Not an existing message
2219 1 : bobMsgSize = bobData.messagesUpdated.size();
2220 1 : libjami::sendMessage(aliceId, convId, "New body"s, "invalidId", 1);
2221 3 : CPPUNIT_ASSERT(
2222 : !cv.wait_for(lk, 10s, [&]() { return bobData.messagesUpdated.size() == bobMsgSize + 1; }));
2223 : // Invalid author
2224 1 : libjami::sendMessage(aliceId, convId, "New body"s, convId, 1);
2225 3 : CPPUNIT_ASSERT(
2226 : !cv.wait_for(lk, 10s, [&]() { return bobData.messagesUpdated.size() == bobMsgSize + 1; }));
2227 : // Add invalid edition
2228 1 : Json::Value root;
2229 1 : root["type"] = "text/plain";
2230 1 : root["edit"] = convId;
2231 1 : root["body"] = "new";
2232 1 : Json::StreamWriterBuilder wbuilder;
2233 1 : wbuilder["commentStyle"] = "None";
2234 1 : wbuilder["indentation"] = "";
2235 2 : auto repoPath = fileutils::get_data_dir() / aliceId
2236 4 : / "conversations" / convId;
2237 1 : auto message = Json::writeString(wbuilder, root);
2238 1 : commitInRepo(repoPath, aliceAccount, message);
2239 1 : bobData.errorDetected = false;
2240 1 : libjami::sendMessage(aliceId, convId, "trigger"s, "");
2241 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
2242 :
2243 1 : }
2244 :
2245 : void
2246 1 : ConversationTest::testMessageReaction()
2247 : {
2248 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2249 1 : connectSignals();
2250 1 : auto convId = libjami::startConversation(aliceId);
2251 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
2252 1 : auto msgSize = aliceData.messages.size();
2253 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
2254 3 : CPPUNIT_ASSERT(
2255 : cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == msgSize + 1; }));
2256 1 : msgSize = aliceData.messages.size();
2257 :
2258 : // Add reaction
2259 1 : auto reactId = aliceData.messages.rbegin()->id;
2260 1 : libjami::sendMessage(aliceId, convId, "👋"s, reactId, 2);
2261 3 : CPPUNIT_ASSERT(
2262 : cv.wait_for(lk, 10s, [&]() { return aliceData.reactions.size() == 1; }));
2263 1 : CPPUNIT_ASSERT(aliceData.reactions.rbegin()->at("react-to") == reactId);
2264 1 : CPPUNIT_ASSERT(aliceData.reactions.rbegin()->at("body") == "👋");
2265 2 : auto emojiId = aliceData.reactions.rbegin()->at("id");
2266 :
2267 : // Remove reaction
2268 1 : libjami::sendMessage(aliceId, convId, ""s, emojiId, 1);
2269 3 : CPPUNIT_ASSERT(
2270 : cv.wait_for(lk, 10s, [&]() { return aliceData.reactionRemoved.size() == 1; }));
2271 1 : CPPUNIT_ASSERT(emojiId == aliceData.reactionRemoved[0]);
2272 1 : }
2273 :
2274 : void
2275 1 : ConversationTest::testMessageEditionWithReaction()
2276 : {
2277 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2278 1 : connectSignals();
2279 1 : auto convId = libjami::startConversation(aliceId);
2280 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
2281 1 : auto msgSize = aliceData.messages.size();
2282 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
2283 3 : CPPUNIT_ASSERT(
2284 : cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == msgSize + 1; }));
2285 1 : msgSize = aliceData.messages.size();
2286 :
2287 : // Add reaction
2288 1 : auto reactId = aliceData.messages.rbegin()->id;
2289 1 : libjami::sendMessage(aliceId, convId, "👋"s, reactId, 2);
2290 3 : CPPUNIT_ASSERT(
2291 : cv.wait_for(lk, 10s, [&]() { return aliceData.reactions.size() == 1; }));
2292 1 : CPPUNIT_ASSERT(aliceData.reactions.rbegin()->at("react-to") == reactId);
2293 1 : CPPUNIT_ASSERT(aliceData.reactions.rbegin()->at("body") == "👋");
2294 2 : auto emojiId = aliceData.reactions.rbegin()->at("id");
2295 :
2296 : // Remove base message should remove reaction
2297 1 : libjami::sendMessage(aliceId, convId, ""s, reactId, 1);
2298 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceData.messagesUpdated.size() == 1; }));
2299 : // Reaction is deleted
2300 1 : CPPUNIT_ASSERT(aliceData.messagesUpdated[0].reactions.empty());
2301 1 : }
2302 :
2303 : void
2304 1 : ConversationTest::testLoadPartiallyRemovedConversation()
2305 : {
2306 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2307 1 : connectSignals();
2308 :
2309 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
2310 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
2311 1 : auto bobUri = bobAccount->getUsername();
2312 :
2313 1 : aliceAccount->addContact(bobUri);
2314 1 : aliceAccount->sendTrustRequest(bobUri, {});
2315 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
2316 :
2317 : // Copy alice's conversation temporary
2318 2 : auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations" / aliceData.conversationId;
2319 2 : std::filesystem::copy(repoPathAlice, fmt::format("./{}", aliceData.conversationId), std::filesystem::copy_options::recursive);
2320 :
2321 : // removeContact
2322 1 : aliceAccount->removeContact(bobUri, false);
2323 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
2324 1 : std::this_thread::sleep_for(10s); // Wait for connection to close and async tasks to finish
2325 :
2326 : // Copy back alice's conversation
2327 2 : std::filesystem::copy(fmt::format("./{}", aliceData.conversationId), repoPathAlice, std::filesystem::copy_options::recursive);
2328 2 : std::filesystem::remove_all(fmt::format("./{}", aliceData.conversationId));
2329 :
2330 : // Reloading conversation should remove directory
2331 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPathAlice));
2332 1 : aliceAccount->convModule()->loadConversations();
2333 1 : std::this_thread::sleep_for(5s); // Let the daemon the time to fix structures
2334 1 : CPPUNIT_ASSERT(!std::filesystem::is_directory(repoPathAlice));
2335 1 : }
2336 :
2337 : void
2338 1 : ConversationTest::testReactionsOnEditedMessage()
2339 : {
2340 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2341 1 : connectSignals();
2342 1 : auto convId = libjami::startConversation(aliceId);
2343 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
2344 1 : auto msgSize = aliceData.messages.size();
2345 1 : libjami::sendMessage(aliceId, convId, "hi"s, "");
2346 3 : CPPUNIT_ASSERT(
2347 : cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == msgSize + 1; }));
2348 1 : msgSize = aliceData.messages.size();
2349 :
2350 : // Add reaction
2351 1 : auto reactId = aliceData.messages.rbegin()->id;
2352 1 : libjami::sendMessage(aliceId, convId, "👋"s, reactId, 2);
2353 3 : CPPUNIT_ASSERT(
2354 : cv.wait_for(lk, 10s, [&]() { return aliceData.reactions.size() == 1; }));
2355 1 : CPPUNIT_ASSERT(aliceData.reactions.rbegin()->at("react-to") == reactId);
2356 1 : CPPUNIT_ASSERT(aliceData.reactions.rbegin()->at("body") == "👋");
2357 2 : auto emojiId = aliceData.reactions.rbegin()->at("id");
2358 :
2359 : // Edit message
2360 1 : aliceData.messagesUpdated.clear();
2361 1 : libjami::sendMessage(aliceId, convId, "EDITED"s, reactId, 1);
2362 :
2363 3 : CPPUNIT_ASSERT(
2364 : cv.wait_for(lk, 10s, [&]() { return aliceData.messagesUpdated.size() == 1; }));
2365 :
2366 : // Reaction is kept
2367 1 : CPPUNIT_ASSERT(emojiId == aliceData.messagesUpdated[0].reactions[0]["id"]);
2368 1 : }
2369 :
2370 : void
2371 1 : ConversationTest::testUpdateProfileMultiDevice()
2372 : {
2373 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
2374 1 : connectSignals();
2375 :
2376 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
2377 :
2378 : // Bob creates a second device
2379 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
2380 1 : std::remove(bobArchive.c_str());
2381 1 : bobAccount->exportArchive(bobArchive);
2382 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
2383 1 : details[ConfProperties::TYPE] = "RING";
2384 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
2385 1 : details[ConfProperties::ALIAS] = "BOB2";
2386 1 : details[ConfProperties::UPNP_ENABLED] = "true";
2387 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
2388 1 : details[ConfProperties::ARCHIVE_PIN] = "";
2389 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
2390 1 : bob2Id = Manager::instance().addAccount(details);
2391 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.registered; }));
2392 :
2393 : // Bob creates a conversation
2394 1 : auto convId = libjami::startConversation(bobId);
2395 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bob2Data.conversationId.empty(); }));
2396 :
2397 :
2398 1 : auto bobMsgSize = bobData.messages.size();
2399 1 : auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
2400 2 : bob2Account->convModule()->updateConversationInfos(bob2Data.conversationId, {{"title", "My awesome swarm"}});
2401 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
2402 :
2403 1 : }
2404 :
2405 : } // namespace test
2406 : } // namespace jami
2407 :
2408 1 : RING_TEST_RUNNER(jami::test::ConversationTest::name())
|