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