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