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 "manager.h"
32 : #include "jamidht/conversation.h"
33 : #include "jamidht/conversationrepository.h"
34 : #include "jamidht/jamiaccount.h"
35 : #include "../../test_runner.h"
36 : #include "jami.h"
37 : #include "base64.h"
38 : #include "fileutils.h"
39 : #include "account_const.h"
40 : #include "common.h"
41 :
42 : using namespace std::string_literals;
43 : using namespace std::literals::chrono_literals;
44 : using namespace libjami::Account;
45 :
46 : namespace jami {
47 : namespace test {
48 :
49 : struct UserData {
50 : std::string conversationId;
51 : bool removed {false};
52 : bool requestReceived {false};
53 : bool requestRemoved {false};
54 : bool registered {false};
55 : bool stopped {false};
56 : bool deviceAnnounced {false};
57 : bool contactAdded {false};
58 : bool contactRemoved {false};
59 : std::string profilePath;
60 : std::string payloadTrustRequest;
61 : std::vector<libjami::SwarmMessage> messages;
62 : std::vector<libjami::SwarmMessage> messagesUpdated;
63 : };
64 :
65 : class ConversationRequestTest : public CppUnit::TestFixture
66 : {
67 : public:
68 52 : ~ConversationRequestTest() { libjami::fini(); }
69 2 : static std::string name() { return "ConversationRequest"; }
70 : void setUp();
71 : void tearDown();
72 :
73 : void testAcceptTrustRemoveConvReq();
74 : void acceptConvReqAlsoAddContact();
75 : void testGetRequests();
76 : void testDeclineRequest();
77 : void testAddContact();
78 : void testDeclineConversationRequestRemoveTrustRequest();
79 : void testMalformedTrustRequest();
80 : void testAddContactDeleteAndReAdd();
81 : void testRemoveContact();
82 : void testRemoveContactMultiDevice();
83 : void testRemoveSelfDoesntRemoveConversation();
84 : void testRemoveConversationUpdateContactDetails();
85 : void testBanContact();
86 : void testBanContactRestartAccount();
87 : void testBanContactRemoveTrustRequest();
88 : void testAddOfflineContactThenConnect();
89 : void testDeclineTrustRequestDoNotGenerateAnother();
90 : void testRemoveContactRemoveSyncing();
91 : void testRemoveConversationRemoveSyncing();
92 : void testCacheRequestFromClient();
93 : void testNeedsSyncingWithForCloning();
94 : void testRemoveContactRemoveTrustRequest();
95 : void testAddConversationNoPresenceThenConnects();
96 : void testRequestBigPayload();
97 : void testBothRemoveReadd();
98 : void doNotLooseMetadata();
99 : std::string aliceId;
100 : UserData aliceData;
101 : std::string bobId;
102 : UserData bobData;
103 : std::string bob2Id;
104 : UserData bob2Data;
105 : std::string carlaId;
106 : UserData carlaData;
107 :
108 : std::mutex mtx;
109 : std::unique_lock<std::mutex> lk {mtx};
110 : std::condition_variable cv;
111 :
112 : void connectSignals();
113 :
114 : private:
115 2 : CPPUNIT_TEST_SUITE(ConversationRequestTest);
116 1 : CPPUNIT_TEST(testAcceptTrustRemoveConvReq);
117 1 : CPPUNIT_TEST(acceptConvReqAlsoAddContact);
118 1 : CPPUNIT_TEST(testGetRequests);
119 1 : CPPUNIT_TEST(testDeclineRequest);
120 1 : CPPUNIT_TEST(testAddContact);
121 1 : CPPUNIT_TEST(testDeclineConversationRequestRemoveTrustRequest);
122 1 : CPPUNIT_TEST(testMalformedTrustRequest);
123 1 : CPPUNIT_TEST(testAddContactDeleteAndReAdd);
124 1 : CPPUNIT_TEST(testRemoveContact);
125 1 : CPPUNIT_TEST(testRemoveContactMultiDevice);
126 1 : CPPUNIT_TEST(testRemoveSelfDoesntRemoveConversation);
127 1 : CPPUNIT_TEST(testRemoveConversationUpdateContactDetails);
128 1 : CPPUNIT_TEST(testBanContact);
129 1 : CPPUNIT_TEST(testBanContactRestartAccount);
130 1 : CPPUNIT_TEST(testBanContactRemoveTrustRequest);
131 1 : CPPUNIT_TEST(testAddOfflineContactThenConnect);
132 1 : CPPUNIT_TEST(testDeclineTrustRequestDoNotGenerateAnother);
133 1 : CPPUNIT_TEST(testRemoveContactRemoveSyncing);
134 1 : CPPUNIT_TEST(testRemoveConversationRemoveSyncing);
135 1 : CPPUNIT_TEST(testCacheRequestFromClient);
136 1 : CPPUNIT_TEST(testNeedsSyncingWithForCloning);
137 1 : CPPUNIT_TEST(testRemoveContactRemoveTrustRequest);
138 1 : CPPUNIT_TEST(testAddConversationNoPresenceThenConnects);
139 1 : CPPUNIT_TEST(testRequestBigPayload);
140 1 : CPPUNIT_TEST(testBothRemoveReadd);
141 1 : CPPUNIT_TEST(doNotLooseMetadata);
142 4 : CPPUNIT_TEST_SUITE_END();
143 : };
144 :
145 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConversationRequestTest, ConversationRequestTest::name());
146 :
147 : void
148 26 : ConversationRequestTest::setUp()
149 : {
150 : // Init daemon
151 26 : libjami::init(
152 : libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
153 26 : if (not Manager::instance().initialized)
154 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
155 :
156 26 : auto actors = load_actors("actors/alice-bob-carla.yml");
157 26 : aliceId = actors["alice"];
158 26 : bobId = actors["bob"];
159 26 : carlaId = actors["carla"];
160 26 : aliceData = {};
161 26 : bobData = {};
162 26 : bob2Data = {};
163 26 : carlaData = {};
164 :
165 26 : Manager::instance().sendRegister(carlaId, false);
166 78 : wait_for_announcement_of({aliceId, bobId});
167 26 : }
168 : void
169 24 : ConversationRequestTest::connectSignals()
170 : {
171 24 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
172 24 : confHandlers.insert(
173 48 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
174 21 : [&](const std::string& accountId, const std::map<std::string, std::string>&) {
175 21 : if (accountId == aliceId) {
176 4 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
177 4 : auto details = aliceAccount->getVolatileAccountDetails();
178 8 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
179 4 : if (daemonStatus == "REGISTERED") {
180 0 : aliceData.registered = true;
181 4 : } else if (daemonStatus == "UNREGISTERED") {
182 4 : aliceData.stopped = true;
183 : }
184 8 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
185 4 : aliceData.deviceAnnounced = deviceAnnounced == "true";
186 21 : } else if (accountId == bobId) {
187 4 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
188 4 : auto details = bobAccount->getVolatileAccountDetails();
189 8 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
190 4 : if (daemonStatus == "REGISTERED") {
191 2 : bobData.registered = true;
192 2 : } else if (daemonStatus == "UNREGISTERED") {
193 1 : bobData.stopped = true;
194 : }
195 8 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
196 4 : bobData.deviceAnnounced = deviceAnnounced == "true";
197 17 : } else if (accountId == bob2Id) {
198 8 : auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
199 8 : auto details = bob2Account->getVolatileAccountDetails();
200 16 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
201 8 : if (daemonStatus == "REGISTERED") {
202 4 : bob2Data.registered = true;
203 4 : } else if (daemonStatus == "UNREGISTERED") {
204 2 : bob2Data.stopped = true;
205 : }
206 16 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
207 8 : bob2Data.deviceAnnounced = deviceAnnounced == "true";
208 13 : } else if (accountId == carlaId) {
209 3 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
210 3 : auto details = carlaAccount->getVolatileAccountDetails();
211 6 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
212 3 : if (daemonStatus == "REGISTERED") {
213 2 : carlaData.registered = true;
214 1 : } else if (daemonStatus == "UNREGISTERED") {
215 0 : carlaData.stopped = true;
216 : }
217 6 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
218 3 : carlaData.deviceAnnounced = deviceAnnounced == "true";
219 3 : }
220 21 : cv.notify_one();
221 21 : }));
222 24 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
223 44 : [&](const std::string& accountId, const std::string& conversationId) {
224 44 : if (accountId == aliceId) {
225 28 : aliceData.conversationId = conversationId;
226 16 : } else if (accountId == bobId) {
227 14 : bobData.conversationId = conversationId;
228 2 : } else if (accountId == bob2Id) {
229 1 : bob2Data.conversationId = conversationId;
230 1 : } else if (accountId == carlaId) {
231 1 : carlaData.conversationId = conversationId;
232 : }
233 44 : cv.notify_one();
234 44 : }));
235 24 : confHandlers.insert(
236 48 : libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
237 24 : [&](const std::string& account_id,
238 : const std::string& /*from*/,
239 : const std::string& /*conversationId*/,
240 : const std::vector<uint8_t>& payload,
241 : time_t /*received*/) {
242 24 : auto payloadStr = std::string(payload.data(), payload.data() + payload.size());
243 24 : if (account_id == aliceId)
244 0 : aliceData.payloadTrustRequest = payloadStr;
245 24 : else if (account_id == bobId)
246 21 : bobData.payloadTrustRequest = payloadStr;
247 24 : cv.notify_one();
248 24 : }));
249 24 : confHandlers.insert(
250 48 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
251 29 : [&](const std::string& accountId,
252 : const std::string& /* conversationId */,
253 : std::map<std::string, std::string> /*metadatas*/) {
254 29 : if (accountId == aliceId) {
255 0 : aliceData.requestReceived = true;
256 29 : } else if (accountId == bobId) {
257 26 : bobData.requestReceived = true;
258 3 : } else if (accountId == bob2Id) {
259 2 : bob2Data.requestReceived = true;
260 1 : } else if (accountId == carlaId) {
261 1 : carlaData.requestReceived = true;
262 : }
263 29 : cv.notify_one();
264 29 : }));
265 24 : confHandlers.insert(
266 48 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
267 8 : [&](const std::string& accountId, const std::string&) {
268 8 : if (accountId == bobId) {
269 7 : bobData.requestRemoved = true;
270 1 : } else if (accountId == bob2Id) {
271 1 : bob2Data.requestRemoved = true;
272 : }
273 8 : cv.notify_one();
274 8 : }));
275 24 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
276 15 : [&](const std::string& accountId,
277 : const std::string& /* conversationId */,
278 : libjami::SwarmMessage message) {
279 15 : if (accountId == aliceId) {
280 15 : aliceData.messages.emplace_back(message);
281 0 : } else if (accountId == bobId) {
282 0 : bobData.messages.emplace_back(message);
283 0 : } else if (accountId == carlaId) {
284 0 : carlaData.messages.emplace_back(message);
285 : }
286 15 : cv.notify_one();
287 15 : }));
288 24 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
289 0 : [&](const std::string& accountId,
290 : const std::string& /* conversationId */,
291 : libjami::SwarmMessage message) {
292 0 : if (accountId == aliceId) {
293 0 : aliceData.messagesUpdated.emplace_back(message);
294 0 : } else if (accountId == bobId) {
295 0 : bobData.messagesUpdated.emplace_back(message);
296 0 : } else if (accountId == carlaId) {
297 0 : carlaData.messagesUpdated.emplace_back(message);
298 : }
299 0 : cv.notify_one();
300 0 : }));
301 24 : confHandlers.insert(
302 48 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
303 12 : [&](const std::string& accountId, const std::string&) {
304 12 : if (accountId == aliceId)
305 4 : aliceData.removed = true;
306 8 : else if (accountId == bobId)
307 6 : bobData.removed = true;
308 2 : else if (accountId == bob2Id)
309 2 : bob2Data.removed = true;
310 12 : cv.notify_one();
311 12 : }));
312 24 : confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactAdded>(
313 56 : [&](const std::string& accountId, const std::string&, bool) {
314 56 : if (accountId == bobId) {
315 16 : bobData.contactAdded = true;
316 : }
317 56 : cv.notify_one();
318 56 : }));
319 24 : confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
320 12 : [&](const std::string& accountId, const std::string&, bool) {
321 12 : if (accountId == bobId) {
322 6 : bobData.contactRemoved = true;
323 6 : } else if (accountId == bob2Id) {
324 2 : bob2Data.contactRemoved = true;
325 : }
326 12 : cv.notify_one();
327 12 : }));
328 :
329 24 : libjami::registerSignalHandlers(confHandlers);
330 24 : }
331 :
332 :
333 : void
334 26 : ConversationRequestTest::tearDown()
335 : {
336 52 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
337 26 : std::remove(bobArchive.c_str());
338 :
339 26 : if (bob2Id.empty()) {
340 96 : wait_for_removal_of({aliceId, bobId, carlaId});
341 : } else {
342 10 : wait_for_removal_of({aliceId, bobId, carlaId, bob2Id});
343 : }
344 26 : }
345 :
346 : void
347 1 : ConversationRequestTest::testAcceptTrustRemoveConvReq()
348 : {
349 1 : connectSignals();
350 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
351 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
352 1 : auto bobUri = bobAccount->getUsername();
353 :
354 1 : aliceAccount->addContact(bobUri);
355 1 : aliceAccount->sendTrustRequest(bobUri, {});
356 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
357 :
358 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 1);
359 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
360 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
361 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 0);
362 1 : }
363 :
364 : void
365 1 : ConversationRequestTest::acceptConvReqAlsoAddContact()
366 : {
367 1 : connectSignals();
368 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
369 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
370 1 : auto bobUri = bobAccount->getUsername();
371 :
372 1 : aliceAccount->addContact(bobUri);
373 1 : aliceAccount->sendTrustRequest(bobUri, {});
374 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
375 :
376 1 : bobData.requestReceived = false;
377 1 : auto convId2 = libjami::startConversation(aliceId);
378 1 : libjami::addConversationMember(aliceId, convId2, bobUri);
379 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
380 :
381 1 : libjami::acceptConversationRequest(bobId, convId2);
382 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
383 1 : std::this_thread::sleep_for(5s);
384 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 0);
385 1 : }
386 :
387 : void
388 1 : ConversationRequestTest::testGetRequests()
389 : {
390 1 : connectSignals();
391 :
392 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
393 1 : auto bobUri = bobAccount->getUsername();
394 :
395 1 : auto convId = libjami::startConversation(aliceId);
396 :
397 1 : libjami::addConversationMember(aliceId, convId, bobUri);
398 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
399 :
400 1 : auto requests = libjami::getConversationRequests(bobId);
401 1 : CPPUNIT_ASSERT(requests.size() == 1);
402 1 : CPPUNIT_ASSERT(requests.front()["id"] == convId);
403 1 : }
404 :
405 : void
406 1 : ConversationRequestTest::testDeclineRequest()
407 : {
408 1 : connectSignals();
409 :
410 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
411 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
412 1 : auto bobUri = bobAccount->getUsername();
413 :
414 1 : auto convId = libjami::startConversation(aliceId);
415 :
416 1 : libjami::addConversationMember(aliceId, convId, bobUri);
417 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
418 :
419 1 : libjami::declineConversationRequest(bobId, convId);
420 : // Decline request
421 1 : auto requests = libjami::getConversationRequests(bobId);
422 1 : CPPUNIT_ASSERT(requests.size() == 0);
423 1 : }
424 :
425 : void
426 1 : ConversationRequestTest::testAddContact()
427 : {
428 1 : connectSignals();
429 :
430 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
431 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
432 1 : auto bobUri = bobAccount->getUsername();
433 1 : auto aliceUri = aliceAccount->getUsername();
434 :
435 1 : aliceAccount->addContact(bobUri);
436 1 : aliceAccount->sendTrustRequest(bobUri, {});
437 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !aliceData.conversationId.empty(); }));
438 1 : ConversationRepository repo(aliceAccount, aliceData.conversationId);
439 : // Mode must be one to one
440 1 : CPPUNIT_ASSERT(repo.mode() == ConversationMode::ONE_TO_ONE);
441 : // Assert that repository exists
442 2 : auto repoPath = fileutils::get_data_dir() / aliceId
443 4 : / "conversations" / aliceData.conversationId;
444 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
445 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
446 1 : auto aliceMsgSize = aliceData.messages.size();
447 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
448 6 : CPPUNIT_ASSERT(
449 : cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
450 2 : auto clonedPath = fileutils::get_data_dir() / bobId
451 4 : / "conversations" / aliceData.conversationId;
452 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(clonedPath));
453 2 : auto bobMember = clonedPath / "members" / (bobUri + ".crt");
454 1 : CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobMember));
455 1 : }
456 :
457 : void
458 1 : ConversationRequestTest::testDeclineConversationRequestRemoveTrustRequest()
459 : {
460 1 : connectSignals();
461 :
462 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
463 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
464 1 : auto bobUri = bobAccount->getUsername();
465 :
466 1 : aliceAccount->addContact(bobUri);
467 1 : aliceAccount->sendTrustRequest(bobUri, {});
468 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
469 :
470 : // Decline request
471 1 : auto requests = libjami::getConversationRequests(bobId);
472 1 : CPPUNIT_ASSERT(requests.size() == 1);
473 1 : auto trustRequests = libjami::getTrustRequests(bobId);
474 1 : CPPUNIT_ASSERT(trustRequests.size() == 1);
475 1 : libjami::declineConversationRequest(bobId, aliceData.conversationId);
476 1 : requests = libjami::getConversationRequests(bobId);
477 1 : CPPUNIT_ASSERT(requests.size() == 0);
478 1 : trustRequests = libjami::getTrustRequests(bobId);
479 1 : CPPUNIT_ASSERT(trustRequests.size() == 0);
480 1 : }
481 :
482 : void
483 1 : ConversationRequestTest::testMalformedTrustRequest()
484 : {
485 1 : connectSignals();
486 :
487 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
488 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
489 1 : auto bobUri = bobAccount->getUsername();
490 :
491 1 : aliceAccount->addContact(bobUri);
492 1 : aliceAccount->sendTrustRequest(bobUri, {});
493 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
494 :
495 : // Decline request
496 1 : auto requests = libjami::getConversationRequests(bobId);
497 1 : CPPUNIT_ASSERT(requests.size() == 1);
498 1 : auto trustRequests = libjami::getTrustRequests(bobId);
499 1 : CPPUNIT_ASSERT(trustRequests.size() == 1);
500 : // This will let the trust request (not libjami::declineConversationRequest)
501 1 : bobAccount->convModule()->declineConversationRequest(aliceData.conversationId);
502 1 : requests = libjami::getConversationRequests(bobId);
503 1 : CPPUNIT_ASSERT(requests.size() == 0);
504 1 : trustRequests = libjami::getTrustRequests(bobId);
505 1 : CPPUNIT_ASSERT(trustRequests.size() == 1);
506 : // Reload conversation will fix the state (the trustRequest is removed in another thread)
507 1 : bobAccount->convModule()->loadConversations();
508 1 : requests = libjami::getConversationRequests(bobId);
509 1 : CPPUNIT_ASSERT(requests.size() == 0);
510 :
511 1 : auto start = std::chrono::steady_clock::now();
512 :
513 1 : auto requestDeclined = false;
514 : do {
515 2 : trustRequests = libjami::getTrustRequests(bobId);
516 2 : requestDeclined = trustRequests.size() == 0;
517 2 : if (!requestDeclined)
518 1 : std::this_thread::sleep_for(1s);
519 2 : } while (not requestDeclined and std::chrono::steady_clock::now() - start < 2s);
520 :
521 1 : CPPUNIT_ASSERT(requestDeclined);
522 1 : }
523 :
524 : void
525 1 : ConversationRequestTest::testAddContactDeleteAndReAdd()
526 : {
527 1 : connectSignals();
528 :
529 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
530 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
531 1 : auto bobUri = bobAccount->getUsername();
532 1 : auto aliceUri = aliceAccount->getUsername();
533 :
534 1 : aliceAccount->addContact(bobUri);
535 1 : aliceAccount->sendTrustRequest(bobUri, {});
536 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
537 1 : auto aliceMsgSize = aliceData.messages.size();
538 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
539 6 : CPPUNIT_ASSERT(
540 : cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
541 :
542 : // removeContact
543 1 : aliceAccount->removeContact(bobUri, false);
544 1 : std::this_thread::sleep_for(5s); // wait a bit that connections are closed
545 :
546 : // re-add
547 1 : CPPUNIT_ASSERT(aliceData.conversationId != "");
548 1 : auto oldConvId = aliceData.conversationId;
549 1 : aliceAccount->addContact(bobUri);
550 1 : aliceAccount->sendTrustRequest(bobUri, {});
551 : // Should retrieve previous conversation
552 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return oldConvId == aliceData.conversationId; }));
553 1 : }
554 :
555 : void
556 1 : ConversationRequestTest::testRemoveContact()
557 : {
558 1 : connectSignals();
559 :
560 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
561 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
562 1 : auto bobUri = bobAccount->getUsername();
563 1 : auto aliceUri = aliceAccount->getUsername();
564 :
565 1 : aliceAccount->addContact(bobUri);
566 1 : aliceAccount->sendTrustRequest(bobUri, {});
567 : // Check created files
568 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
569 1 : auto aliceMsgSize = aliceData.messages.size();
570 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
571 6 : CPPUNIT_ASSERT(
572 : cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
573 :
574 1 : bobAccount->removeContact(aliceUri, false);
575 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
576 :
577 1 : auto details = bobAccount->getContactDetails(aliceUri);
578 1 : CPPUNIT_ASSERT(details.find("removed") != details.end() && details["removed"] != "0");
579 :
580 1 : aliceAccount->removeContact(bobUri, false);
581 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return aliceData.removed; }));
582 :
583 1 : std::this_thread::sleep_for(
584 1 : 10s); // There is no signal, but daemon should then erase the repository
585 :
586 2 : auto repoPath = fileutils::get_data_dir() / aliceId
587 4 : / "conversations" / aliceData.conversationId;
588 1 : CPPUNIT_ASSERT(!std::filesystem::is_directory(repoPath));
589 :
590 2 : repoPath = fileutils::get_data_dir() / bobId
591 3 : / "conversations" / bobData.conversationId;
592 1 : CPPUNIT_ASSERT(!std::filesystem::is_directory(repoPath));
593 1 : }
594 :
595 : void
596 1 : ConversationRequestTest::testRemoveContactMultiDevice()
597 : {
598 1 : connectSignals();
599 :
600 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
601 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
602 1 : auto bobUri = bobAccount->getUsername();
603 1 : auto aliceUri = aliceAccount->getUsername();
604 :
605 : // Add second device for Bob
606 1 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
607 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
608 1 : std::remove(bobArchive.c_str());
609 1 : bobAccount->exportArchive(bobArchive);
610 :
611 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
612 1 : details[ConfProperties::TYPE] = "RING";
613 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
614 1 : details[ConfProperties::ALIAS] = "BOB2";
615 1 : details[ConfProperties::UPNP_ENABLED] = "true";
616 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
617 1 : details[ConfProperties::ARCHIVE_PIN] = "";
618 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
619 :
620 1 : bob2Id = Manager::instance().addAccount(details);
621 :
622 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
623 : return bob2Data.deviceAnnounced;
624 : }));
625 : // First, Alice adds Bob
626 1 : aliceAccount->addContact(bobUri);
627 1 : aliceAccount->sendTrustRequest(bobUri, {});
628 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
629 : return bobData.requestReceived && bob2Data.requestReceived;
630 : }));
631 :
632 : // Bob1 decline via removeContact, both device should remove the request
633 1 : bobAccount->removeContact(aliceUri, false);
634 5 : CPPUNIT_ASSERT(
635 : cv.wait_for(lk, 30s, [&]() { return bobData.requestRemoved && bob2Data.requestRemoved; }));
636 1 : }
637 :
638 : void
639 1 : ConversationRequestTest::testRemoveSelfDoesntRemoveConversation()
640 : {
641 1 : connectSignals();
642 :
643 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
644 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
645 1 : auto bobUri = bobAccount->getUsername();
646 1 : auto aliceUri = aliceAccount->getUsername();
647 :
648 1 : aliceAccount->addContact(bobUri);
649 1 : aliceAccount->sendTrustRequest(bobUri, {});
650 : // Check created files
651 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
652 1 : auto aliceMsgSize = aliceData.messages.size();
653 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
654 6 : CPPUNIT_ASSERT(
655 : cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
656 :
657 1 : aliceAccount->removeContact(aliceUri, false);
658 4 : CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return aliceData.removed; }));
659 2 : auto repoPath = fileutils::get_data_dir() / aliceId
660 4 : / "conversations" / aliceData.conversationId;
661 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
662 1 : }
663 :
664 : void
665 1 : ConversationRequestTest::testRemoveConversationUpdateContactDetails()
666 : {
667 1 : connectSignals();
668 :
669 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
670 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
671 1 : auto bobUri = bobAccount->getUsername();
672 1 : auto aliceUri = aliceAccount->getUsername();
673 :
674 1 : aliceAccount->addContact(bobUri);
675 1 : aliceAccount->sendTrustRequest(bobUri, {});
676 : // Check created files
677 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
678 1 : auto aliceMsgSize = aliceData.messages.size();
679 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
680 6 : CPPUNIT_ASSERT(
681 : cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
682 :
683 1 : libjami::removeConversation(bobId, bobData.conversationId);
684 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
685 :
686 1 : auto details = bobAccount->getContactDetails(aliceUri);
687 1 : CPPUNIT_ASSERT(details[libjami::Account::TrustRequest::CONVERSATIONID] == "");
688 1 : }
689 :
690 : void
691 1 : ConversationRequestTest::testBanContact()
692 : {
693 1 : connectSignals();
694 :
695 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
696 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
697 1 : auto bobUri = bobAccount->getUsername();
698 1 : auto aliceUri = aliceAccount->getUsername();
699 :
700 1 : aliceAccount->addContact(bobUri);
701 1 : aliceAccount->sendTrustRequest(bobUri, {});
702 : // Check created files
703 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
704 1 : auto aliceMsgSize = aliceData.messages.size();
705 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
706 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
707 :
708 1 : bobAccount->removeContact(aliceUri, true);
709 4 : CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
710 2 : auto repoPath = fileutils::get_data_dir() / bobId
711 4 : / "conversations" / bobData.conversationId;
712 1 : CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
713 1 : }
714 :
715 :
716 : void
717 1 : ConversationRequestTest::testBanContactRestartAccount()
718 : {
719 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
720 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
721 1 : auto bobUri = bobAccount->getUsername();
722 1 : auto aliceUri = aliceAccount->getUsername();
723 1 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
724 1 : bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
725 1 : std::string convId = "";
726 1 : confHandlers.insert(
727 2 : libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
728 1 : [&](const std::string& account_id,
729 : const std::string& /*from*/,
730 : const std::string& /*conversationId*/,
731 : const std::vector<uint8_t>& /*payload*/,
732 : time_t /*received*/) {
733 1 : if (account_id == bobId)
734 1 : requestReceived = true;
735 1 : cv.notify_one();
736 1 : }));
737 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
738 2 : [&](const std::string& accountId, const std::string& conversationId) {
739 2 : if (accountId == aliceId) {
740 1 : convId = conversationId;
741 1 : } else if (accountId == bobId) {
742 1 : conversationReady = true;
743 : }
744 2 : cv.notify_one();
745 2 : }));
746 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
747 1 : [&](const std::string& accountId,
748 : const std::string& conversationId,
749 : std::map<std::string, std::string> message) {
750 1 : if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
751 1 : memberMessageGenerated = true;
752 : }
753 1 : cv.notify_one();
754 1 : }));
755 1 : bool contactRemoved = false;
756 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
757 1 : [&](const std::string& accountId, const std::string& uri, bool) {
758 1 : if (accountId == bobId && uri == aliceUri) {
759 1 : contactRemoved = true;
760 : }
761 1 : cv.notify_one();
762 1 : }));
763 1 : auto bobConnected = false;
764 1 : confHandlers.insert(
765 2 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
766 4 : [&](const std::string&, const std::map<std::string, std::string>&) {
767 4 : auto details = bobAccount->getVolatileAccountDetails();
768 8 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
769 4 : if (daemonStatus == "REGISTERED") {
770 2 : bobConnected = true;
771 2 : } else if (daemonStatus == "UNREGISTERED") {
772 1 : bobConnected = false;
773 : }
774 4 : cv.notify_one();
775 4 : }));
776 1 : libjami::registerSignalHandlers(confHandlers);
777 :
778 1 : aliceAccount->addContact(bobUri);
779 1 : aliceAccount->sendTrustRequest(bobUri, {});
780 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !convId.empty(); }));
781 : // Check created files
782 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
783 1 : memberMessageGenerated = false;
784 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
785 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
786 :
787 1 : memberMessageGenerated = false;
788 1 : bobAccount->removeContact(aliceUri, true);
789 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return contactRemoved; }));
790 1 : std::this_thread::sleep_for(10s); // Wait a bit to ensure that everything is updated
791 1 : bobConnected = true;
792 1 : Manager::instance().sendRegister(bobId, false);
793 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobConnected; }));
794 1 : std::this_thread::sleep_for(5s); // Wait a bit to ensure that everything is updated
795 :
796 : // Connect bob, it will trigger the bootstrap
797 1 : auto statusBootstrap = Conversation::BootstrapStatus::SUCCESS;
798 : // alice is banned so bootstrap MUST fail
799 1 : bobAccount->convModule()->onBootstrapStatus(
800 1 : [&](std::string /*convId*/, Conversation::BootstrapStatus status) {
801 1 : statusBootstrap = status;
802 1 : cv.notify_one();
803 1 : });
804 1 : Manager::instance().sendRegister(bobId, true);
805 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return statusBootstrap == Conversation::BootstrapStatus::FAILED; }));
806 :
807 1 : }
808 :
809 : void
810 1 : ConversationRequestTest::testBanContactRemoveTrustRequest()
811 : {
812 1 : connectSignals();
813 :
814 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
815 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
816 1 : auto bobUri = bobAccount->getUsername();
817 1 : auto aliceUri = aliceAccount->getUsername();
818 :
819 1 : aliceAccount->addContact(bobUri);
820 1 : aliceAccount->sendTrustRequest(bobUri, {});
821 : // Check created files
822 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
823 1 : bobAccount->removeContact(aliceUri, true);
824 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return bobData.requestRemoved; }));
825 1 : auto requests = libjami::getConversationRequests(bobId);
826 1 : CPPUNIT_ASSERT(requests.size() == 0);
827 1 : }
828 :
829 : void
830 1 : ConversationRequestTest::testAddOfflineContactThenConnect()
831 : {
832 1 : connectSignals();
833 :
834 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
835 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
836 1 : auto carlaUri = carlaAccount->getUsername();
837 1 : auto aliceUri = aliceAccount->getUsername();
838 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
839 :
840 1 : aliceAccount->addContact(carlaUri);
841 1 : aliceAccount->sendTrustRequest(carlaUri, {});
842 1 : cv.wait_for(lk, 5s); // Wait 5 secs for the put to happen
843 1 : Manager::instance().sendRegister(carlaId, true);
844 4 : CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return carlaData.requestReceived; }));
845 1 : auto aliceMsgSize = aliceData.messages.size();
846 1 : CPPUNIT_ASSERT(carlaAccount->acceptTrustRequest(aliceUri));
847 7 : CPPUNIT_ASSERT(
848 : cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 1; }));
849 1 : }
850 :
851 : void
852 1 : ConversationRequestTest::testDeclineTrustRequestDoNotGenerateAnother()
853 : {
854 1 : connectSignals();
855 :
856 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
857 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
858 1 : auto bobUri = bobAccount->getUsername();
859 1 : auto aliceUri = aliceAccount->getUsername();
860 1 : aliceAccount->trackBuddyPresence(bobUri, true);
861 :
862 1 : aliceAccount->addContact(bobUri);
863 1 : aliceAccount->sendTrustRequest(bobUri, {});
864 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
865 1 : CPPUNIT_ASSERT(bobAccount->discardTrustRequest(aliceUri));
866 1 : cv.wait_for(lk, 10s); // Wait a bit
867 1 : Manager::instance().sendRegister(bobId, false);
868 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.stopped; }));
869 : // Trigger on peer online
870 1 : bobData.deviceAnnounced = false; bobData.requestReceived = false;
871 1 : Manager::instance().sendRegister(bobId, true);
872 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.deviceAnnounced; }));
873 3 : CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
874 1 : }
875 :
876 : void
877 1 : ConversationRequestTest::testRemoveContactRemoveSyncing()
878 : {
879 1 : connectSignals();
880 :
881 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
882 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
883 1 : auto bobUri = bobAccount->getUsername();
884 1 : auto aliceUri = aliceAccount->getUsername();
885 :
886 1 : aliceAccount->addContact(bobUri);
887 1 : aliceAccount->sendTrustRequest(bobUri, {});
888 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
889 :
890 1 : Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
891 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
892 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactAdded; }));
893 :
894 1 : CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 1);
895 1 : bobAccount->removeContact(aliceUri, false);
896 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactRemoved; }));
897 :
898 1 : CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 0);
899 1 : }
900 :
901 : void
902 1 : ConversationRequestTest::testRemoveConversationRemoveSyncing()
903 : {
904 1 : connectSignals();
905 :
906 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
907 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
908 1 : auto bobUri = bobAccount->getUsername();
909 1 : auto aliceUri = aliceAccount->getUsername();
910 :
911 1 : aliceAccount->addContact(bobUri);
912 1 : aliceAccount->sendTrustRequest(bobUri, {});
913 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
914 :
915 1 : Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
916 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
917 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactAdded; }));
918 : // At this point the conversation should be there and syncing.
919 :
920 1 : CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 1);
921 1 : libjami::removeConversation(bobId, aliceData.conversationId);
922 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
923 :
924 1 : CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 0);
925 1 : }
926 :
927 : void
928 1 : ConversationRequestTest::testCacheRequestFromClient()
929 : {
930 1 : connectSignals();
931 :
932 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
933 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
934 1 : auto bobUri = bobAccount->getUsername();
935 :
936 1 : aliceAccount->addContact(bobUri);
937 1 : std::vector<uint8_t> payload = {0x64, 0x64, 0x64};
938 1 : aliceAccount->sendTrustRequest(bobUri,
939 : payload); // Random payload, just care with the file
940 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
941 2 : auto cachedPath = fileutils::get_cache_dir() / aliceId
942 4 : / "requests" / bobUri;
943 1 : CPPUNIT_ASSERT(std::filesystem::is_regular_file(cachedPath));
944 1 : CPPUNIT_ASSERT(fileutils::loadFile(cachedPath) == payload);
945 :
946 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 1);
947 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
948 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
949 : // cachedPath is removed on confirmation (from the DHT), so this can take a few secs to come
950 1 : auto removed = false;
951 1 : for (int i = 0; i <= 10; ++i) {
952 1 : if (!std::filesystem::is_regular_file(cachedPath)) {
953 1 : removed = true;
954 1 : break;
955 : }
956 0 : std::this_thread::sleep_for(1s);
957 : }
958 1 : CPPUNIT_ASSERT(removed);
959 1 : }
960 :
961 : void
962 1 : ConversationRequestTest::testNeedsSyncingWithForCloning()
963 : {
964 1 : connectSignals();
965 :
966 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
967 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
968 1 : auto bobUri = bobAccount->getUsername();
969 1 : auto aliceUri = aliceAccount->getUsername();
970 1 : auto aliceDevice = std::string(aliceAccount->currentDeviceId());
971 :
972 1 : CPPUNIT_ASSERT(!bobAccount->convModule()->needsSyncingWith(aliceUri, aliceDevice));
973 1 : aliceAccount->addContact(bobUri);
974 1 : aliceAccount->sendTrustRequest(bobUri, {});
975 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
976 :
977 1 : Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
978 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
979 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactAdded; }));
980 : // At this point the conversation should be there and syncing.
981 :
982 1 : CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 1);
983 1 : CPPUNIT_ASSERT(bobAccount->convModule()->needsSyncingWith(aliceUri, aliceDevice));
984 1 : }
985 :
986 : void
987 1 : ConversationRequestTest::testRemoveContactRemoveTrustRequest()
988 : {
989 1 : connectSignals();
990 :
991 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
992 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
993 1 : auto bobUri = bobAccount->getUsername();
994 1 : auto aliceUri = aliceAccount->getUsername();
995 :
996 : // Add second device for Bob
997 1 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
998 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
999 1 : std::remove(bobArchive.c_str());
1000 1 : bobAccount->exportArchive(bobArchive);
1001 :
1002 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
1003 1 : details[ConfProperties::TYPE] = "RING";
1004 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
1005 1 : details[ConfProperties::ALIAS] = "BOB2";
1006 1 : details[ConfProperties::UPNP_ENABLED] = "true";
1007 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
1008 1 : details[ConfProperties::ARCHIVE_PIN] = "";
1009 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
1010 :
1011 1 : bob2Id = Manager::instance().addAccount(details);
1012 :
1013 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1014 : return bob2Data.deviceAnnounced;
1015 : }));
1016 1 : auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
1017 :
1018 : // First, Alice adds Bob
1019 1 : aliceAccount->addContact(bobUri);
1020 1 : aliceAccount->sendTrustRequest(bobUri, {});
1021 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1022 : return bobData.requestReceived && bob2Data.requestReceived;
1023 : }));
1024 :
1025 : // Bob1 accepts, both device should get it
1026 1 : auto aliceMsgSize = aliceData.messages.size();
1027 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
1028 8 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1029 : return !bobData.conversationId.empty() && !bob2Data.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size();
1030 : }));
1031 :
1032 : // Bob2 remove Alice ; Bob1 should not have any trust requests
1033 1 : bob2Account->removeContact(aliceUri, false);
1034 6 : CPPUNIT_ASSERT(
1035 : cv.wait_for(lk, 30s, [&]() { return bobData.contactRemoved && bob2Data.contactRemoved; }));
1036 1 : std::this_thread::sleep_for(10s); // Wait a bit to ensure that everything is update (via synced)
1037 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 0);
1038 1 : CPPUNIT_ASSERT(bob2Account->getTrustRequests().size() == 0);
1039 1 : }
1040 :
1041 : void
1042 1 : ConversationRequestTest::testAddConversationNoPresenceThenConnects()
1043 : {
1044 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1045 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
1046 1 : auto carlaUri = carlaAccount->getUsername();
1047 1 : auto aliceUri = aliceAccount->getUsername();
1048 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
1049 1 : carlaAccount->publishPresence(false);
1050 :
1051 1 : std::mutex mtx;
1052 1 : std::unique_lock lk {mtx};
1053 1 : std::condition_variable cv;
1054 1 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
1055 1 : std::string convId = "";
1056 1 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
1057 3 : [&](const std::string& accountId, const std::string& conversationId) {
1058 3 : if (accountId == aliceId) {
1059 2 : convId = conversationId;
1060 : }
1061 3 : cv.notify_one();
1062 3 : }));
1063 1 : auto carlaConnected = false;
1064 1 : confHandlers.insert(
1065 2 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
1066 7 : [&](const std::string&, const std::map<std::string, std::string>&) {
1067 7 : auto details = carlaAccount->getVolatileAccountDetails();
1068 14 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
1069 7 : if (daemonStatus == "REGISTERED") {
1070 4 : carlaConnected = true;
1071 3 : } else if (daemonStatus == "UNREGISTERED") {
1072 1 : carlaConnected = false;
1073 : }
1074 7 : cv.notify_one();
1075 7 : }));
1076 1 : libjami::registerSignalHandlers(confHandlers);
1077 1 : aliceAccount->addContact(carlaUri);
1078 1 : cv.wait_for(lk, 5s); // Wait 5 secs for the put to happen
1079 1 : Manager::instance().sendRegister(carlaId, true);
1080 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaConnected; }));
1081 1 : carlaAccount->addContact(aliceUri);
1082 1 : cv.wait_for(lk, 5s); // Wait 5 secs for the put to happen
1083 1 : carlaAccount->publishPresence(true);
1084 :
1085 1 : Manager::instance().sendRegister(carlaId, false);
1086 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !carlaConnected; }));
1087 1 : convId.clear();
1088 1 : Manager::instance().sendRegister(carlaId, true);
1089 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaConnected; }));
1090 4 : CPPUNIT_ASSERT(
1091 : cv.wait_for(lk, 30s, [&]() { return !convId.empty(); }));
1092 :
1093 1 : auto carlaDetails = carlaAccount->getContactDetails(aliceUri);
1094 1 : auto aliceDetails = aliceAccount->getContactDetails(carlaUri);
1095 1 : CPPUNIT_ASSERT(carlaDetails["conversationId"] == aliceDetails["conversationId"] && carlaDetails["conversationId"] == convId);
1096 1 : }
1097 :
1098 : void
1099 1 : ConversationRequestTest::testRequestBigPayload()
1100 : {
1101 1 : connectSignals();
1102 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1103 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1104 1 : auto bobUri = bobAccount->getUsername();
1105 :
1106 1 : aliceAccount->addContact(bobUri);
1107 :
1108 1 : auto data = std::string(64000, 'A');
1109 1 : std::vector<uint8_t> payload(data.begin(), data.end());
1110 1 : aliceAccount->sendTrustRequest(bobUri, payload);
1111 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1112 :
1113 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 1);
1114 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
1115 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
1116 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 0);
1117 1 : }
1118 :
1119 : void
1120 1 : ConversationRequestTest::testBothRemoveReadd()
1121 : {
1122 1 : connectSignals();
1123 :
1124 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1125 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1126 1 : auto bobUri = bobAccount->getUsername();
1127 1 : auto aliceUri = aliceAccount->getUsername();
1128 :
1129 1 : aliceAccount->addContact(bobUri);
1130 1 : aliceAccount->sendTrustRequest(bobUri, {});
1131 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1132 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
1133 5 : CPPUNIT_ASSERT(
1134 : cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
1135 :
1136 : // removeContact
1137 1 : aliceAccount->removeContact(bobUri, false);
1138 1 : bobAccount->removeContact(aliceUri, false);
1139 1 : std::this_thread::sleep_for(5s); // wait a bit that connections are closed
1140 :
1141 : // re-add
1142 1 : aliceData.conversationId.clear();
1143 1 : bobData.requestReceived = false;
1144 1 : aliceAccount->addContact(bobUri);
1145 1 : aliceAccount->sendTrustRequest(bobUri, {});
1146 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1147 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
1148 : // Should retrieve previous conversation
1149 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1150 : return !aliceData.conversationId.empty() && bobData.conversationId == aliceData.conversationId; }));
1151 1 : }
1152 :
1153 : void
1154 1 : ConversationRequestTest::doNotLooseMetadata()
1155 : {
1156 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
1157 1 : connectSignals();
1158 :
1159 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
1160 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
1161 1 : auto bobUri = bobAccount->getUsername();
1162 :
1163 1 : libjami::startConversation(aliceId);
1164 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.conversationId != ""; }));
1165 :
1166 1 : auto aliceMsgSize = aliceData.messages.size();
1167 2 : aliceAccount->convModule()->updateConversationInfos(aliceData.conversationId, {{"title", "My awesome swarm"}});
1168 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
1169 : return aliceMsgSize + 1 == aliceData.messages.size();
1170 : }));
1171 :
1172 1 : libjami::addConversationMember(aliceId, aliceData.conversationId, bobUri);
1173 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
1174 :
1175 1 : Manager::instance().sendRegister(aliceId, false);
1176 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.stopped; }));
1177 :
1178 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
1179 3 : CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobData.conversationId != ""; }));
1180 :
1181 : // Force reset
1182 1 : bobAccount->convModule()->loadConversations();
1183 1 : auto infos = libjami::conversationInfos(bobId, aliceData.conversationId);
1184 1 : CPPUNIT_ASSERT(infos["syncing"] == "true");
1185 1 : CPPUNIT_ASSERT(infos["title"] == "My awesome swarm");
1186 1 : }
1187 :
1188 : } // namespace test
1189 : } // namespace jami
1190 :
1191 1 : RING_TEST_RUNNER(jami::test::ConversationRequestTest::name())
|