Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include <cppunit/TestAssert.h>
19 : #include <cppunit/TestFixture.h>
20 : #include <cppunit/extensions/HelperMacros.h>
21 :
22 : #include <condition_variable>
23 : #include <string>
24 : #include <fstream>
25 : #include <streambuf>
26 : #include <git2.h>
27 : #include <filesystem>
28 : #include <msgpack.hpp>
29 :
30 : #include "../../test_runner.h"
31 : #include "account_const.h"
32 : #include "archiver.h"
33 : #include "base64.h"
34 : #include "common.h"
35 : #include "conversation/conversationcommon.h"
36 : #include "fileutils.h"
37 : #include "jami.h"
38 : #include "manager.h"
39 : #include <dhtnet/certstore.h>
40 :
41 : using namespace std::string_literals;
42 : using namespace std::literals::chrono_literals;
43 : using namespace libjami::Account;
44 :
45 : namespace jami {
46 : namespace test {
47 :
48 : struct UserData {
49 : std::string conversationId;
50 : bool requestReceived {false};
51 : bool registered {false};
52 : bool stopped {false};
53 : bool deviceAnnounced {false};
54 : std::vector<libjami::SwarmMessage> messages;
55 : };
56 :
57 : class ConversationFetchSentTest : public CppUnit::TestFixture
58 : {
59 : public:
60 6 : ~ConversationFetchSentTest() { libjami::fini(); }
61 2 : static std::string name() { return "Conversation"; }
62 : void setUp();
63 : void tearDown();
64 : std::string createFakeConversation(std::shared_ptr<JamiAccount> account,
65 : const std::string& fakeCert = "");
66 :
67 : std::string aliceId;
68 : UserData aliceData;
69 : std::string alice2Id;
70 : UserData alice2Data;
71 : std::string bobId;
72 : UserData bobData;
73 : std::string bob2Id;
74 : UserData bob2Data;
75 : std::string carlaId;
76 : UserData carlaData;
77 :
78 : std::mutex mtx;
79 : std::unique_lock<std::mutex> lk {mtx};
80 : std::condition_variable cv;
81 :
82 : void connectSignals();
83 :
84 : private:
85 : void testSyncFetch();
86 : void testSyncAfterDisconnection();
87 : void testDisplayedOnLoad();
88 :
89 2 : CPPUNIT_TEST_SUITE(ConversationFetchSentTest);
90 1 : CPPUNIT_TEST(testSyncFetch);
91 1 : CPPUNIT_TEST(testSyncAfterDisconnection);
92 1 : CPPUNIT_TEST(testDisplayedOnLoad);
93 4 : CPPUNIT_TEST_SUITE_END();
94 : };
95 :
96 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConversationFetchSentTest, ConversationFetchSentTest::name());
97 :
98 : void
99 3 : ConversationFetchSentTest::setUp()
100 : {
101 : // Init daemon
102 3 : libjami::init(
103 : libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
104 3 : if (not Manager::instance().initialized)
105 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
106 :
107 3 : auto actors = load_actors("actors/alice-bob-carla.yml");
108 3 : aliceId = actors["alice"];
109 3 : bobId = actors["bob"];
110 3 : carlaId = actors["carla"];
111 :
112 3 : aliceData = {};
113 3 : alice2Data = {};
114 3 : bobData = {};
115 3 : bob2Data = {};
116 3 : carlaData = {};
117 :
118 3 : Manager::instance().sendRegister(carlaId, false);
119 9 : wait_for_announcement_of({aliceId, bobId});
120 3 : }
121 :
122 : void
123 3 : ConversationFetchSentTest::connectSignals()
124 : {
125 3 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
126 3 : confHandlers.insert(
127 6 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
128 15 : [&](const std::string& accountId, const std::map<std::string, std::string>&) {
129 15 : if (accountId == aliceId) {
130 2 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
131 2 : auto details = aliceAccount->getVolatileAccountDetails();
132 4 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
133 2 : if (daemonStatus == "REGISTERED") {
134 0 : aliceData.registered = true;
135 2 : } else if (daemonStatus == "UNREGISTERED") {
136 2 : aliceData.stopped = true;
137 : }
138 4 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
139 2 : aliceData.deviceAnnounced = deviceAnnounced == "true";
140 15 : } else if (accountId == bobId) {
141 3 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
142 3 : auto details = bobAccount->getVolatileAccountDetails();
143 6 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
144 3 : if (daemonStatus == "REGISTERED") {
145 1 : bobData.registered = true;
146 2 : } else if (daemonStatus == "UNREGISTERED") {
147 1 : bobData.stopped = true;
148 : }
149 6 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
150 3 : bobData.deviceAnnounced = deviceAnnounced == "true";
151 13 : } else if (accountId == bob2Id) {
152 8 : auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
153 8 : auto details = bob2Account->getVolatileAccountDetails();
154 16 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
155 8 : if (daemonStatus == "REGISTERED") {
156 4 : bob2Data.registered = true;
157 4 : } else if (daemonStatus == "UNREGISTERED") {
158 2 : bob2Data.stopped = true;
159 : }
160 16 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
161 8 : bob2Data.deviceAnnounced = deviceAnnounced == "true";
162 10 : } else if (accountId == carlaId) {
163 0 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
164 0 : auto details = carlaAccount->getVolatileAccountDetails();
165 0 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
166 0 : if (daemonStatus == "REGISTERED") {
167 0 : carlaData.registered = true;
168 0 : } else if (daemonStatus == "UNREGISTERED") {
169 0 : carlaData.stopped = true;
170 : }
171 0 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
172 0 : carlaData.deviceAnnounced = deviceAnnounced == "true";
173 0 : }
174 15 : cv.notify_one();
175 15 : }));
176 3 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmLoaded>(
177 1 : [&](uint32_t, const std::string& accountId,
178 : const std::string& /* conversationId */,
179 : std::vector<libjami::SwarmMessage> messages) {
180 1 : if (accountId == aliceId) {
181 0 : aliceData.messages.insert(aliceData.messages.end(), messages.begin(), messages.end());
182 1 : } else if (accountId == bobId) {
183 1 : bobData.messages.insert(bobData.messages.end(), messages.begin(), messages.end());
184 0 : } else if (accountId == bob2Id) {
185 0 : bob2Data.messages.insert(bob2Data.messages.end(), messages.begin(), messages.end());
186 : }
187 1 : cv.notify_one();
188 1 : }));
189 3 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
190 8 : [&](const std::string& accountId, const std::string& conversationId) {
191 8 : if (accountId == aliceId) {
192 3 : aliceData.conversationId = conversationId;
193 5 : } else if (accountId == alice2Id) {
194 0 : alice2Data.conversationId = conversationId;
195 5 : } else if (accountId == bobId) {
196 3 : bobData.conversationId = conversationId;
197 2 : } else if (accountId == bob2Id) {
198 2 : bob2Data.conversationId = conversationId;
199 0 : } else if (accountId == carlaId) {
200 0 : carlaData.conversationId = conversationId;
201 : }
202 8 : cv.notify_one();
203 8 : }));
204 3 : confHandlers.insert(
205 6 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
206 5 : [&](const std::string& accountId,
207 : const std::string& /* conversationId */,
208 : std::map<std::string, std::string> /*metadatas*/) {
209 5 : if (accountId == aliceId) {
210 0 : aliceData.requestReceived = true;
211 5 : } else if (accountId == bobId) {
212 3 : bobData.requestReceived = true;
213 2 : } else if (accountId == bob2Id) {
214 2 : bob2Data.requestReceived = true;
215 0 : } else if (accountId == carlaId) {
216 0 : carlaData.requestReceived = true;
217 : }
218 5 : cv.notify_one();
219 5 : }));
220 3 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
221 35 : [&](const std::string& accountId,
222 : const std::string& /* conversationId */,
223 : libjami::SwarmMessage message) {
224 35 : if (accountId == aliceId) {
225 13 : aliceData.messages.emplace_back(message);
226 22 : } else if (accountId == bobId) {
227 12 : bobData.messages.emplace_back(message);
228 10 : } else if (accountId == bob2Id) {
229 10 : bob2Data.messages.emplace_back(message);
230 0 : } else if (accountId == carlaId) {
231 0 : carlaData.messages.emplace_back(message);
232 : }
233 35 : cv.notify_one();
234 35 : }));
235 3 : confHandlers.insert(
236 6 : libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
237 33 : [&](const std::string& accountId,
238 : const std::string& /*conversationId*/,
239 : const std::string& peer,
240 : const std::string& msgId,
241 : int status) {
242 33 : auto placeMessage = [&](auto& data) {
243 64 : for (auto& dataMsg : data.messages) {
244 64 : if (dataMsg.id == msgId) {
245 33 : dataMsg.status[peer] = status;
246 33 : return;
247 : }
248 : }
249 33 : };
250 33 : if (accountId == aliceId) {
251 9 : placeMessage(aliceData);
252 24 : } else if (accountId == bobId) {
253 18 : placeMessage(bobData);
254 6 : } else if (accountId == bob2Id) {
255 6 : placeMessage(bob2Data);
256 : }
257 33 : cv.notify_one();
258 33 : }));
259 3 : libjami::registerSignalHandlers(confHandlers);
260 3 : }
261 :
262 : void
263 3 : ConversationFetchSentTest::tearDown()
264 : {
265 6 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
266 3 : std::remove(bobArchive.c_str());
267 6 : auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
268 3 : std::remove(aliceArchive.c_str());
269 3 : if (!alice2Id.empty()) {
270 0 : wait_for_removal_of(alice2Id);
271 : }
272 :
273 3 : if (bob2Id.empty()) {
274 4 : wait_for_removal_of({aliceId, bobId, carlaId});
275 : } else {
276 10 : wait_for_removal_of({aliceId, bobId, carlaId, bob2Id});
277 : }
278 3 : }
279 :
280 : void
281 1 : ConversationFetchSentTest::testSyncAfterDisconnection()
282 : {
283 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
284 1 : connectSignals();
285 :
286 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
287 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
288 1 : auto bobUri = bobAccount->getUsername();
289 1 : auto aliceUri = aliceAccount->getUsername();
290 :
291 : // Bob creates a second device
292 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
293 1 : std::remove(bobArchive.c_str());
294 1 : bobAccount->exportArchive(bobArchive);
295 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
296 1 : details[ConfProperties::TYPE] = "RING";
297 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
298 1 : details[ConfProperties::ALIAS] = "BOB2";
299 1 : details[ConfProperties::UPNP_ENABLED] = "true";
300 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
301 1 : details[ConfProperties::ARCHIVE_PIN] = "";
302 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
303 1 : bob2Id = Manager::instance().addAccount(details);
304 :
305 : // Disconnect bob2, to create a valid conv betwen Alice and Bob1
306 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
307 :
308 : // Create conversation between alice and bob
309 1 : aliceAccount->addContact(bobUri);
310 1 : aliceAccount->sendTrustRequest(bobUri, {});
311 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
312 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
313 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && !bob2Data.conversationId.empty(); }));
314 :
315 1 : std::this_thread::sleep_for(5s); // Wait for all join messages to be received
316 :
317 : // bob send 4 messages
318 1 : auto aliceMsgSize = aliceData.messages.size(), bob2MsgSize = bob2Data.messages.size(), bobMsgSize = bobData.messages.size();
319 1 : libjami::sendMessage(bobId, bobData.conversationId, "1"s, "");
320 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
321 : return aliceData.messages.size() == aliceMsgSize + 1
322 : && bobData.messages.size() == bobMsgSize + 1
323 : && bob2Data.messages.size() == bob2MsgSize + 1; }));
324 1 : auto msgId1 = aliceData.messages.rbegin()->id;
325 19 : auto getMsgStatus = [&](const auto& data, const auto& id, const auto& peer) {
326 33 : for (const auto& msg : data.messages) {
327 33 : if (msg.id == id && msg.status.find(peer) != msg.status.end()) {
328 19 : return static_cast<libjami::Account::MessageStates>(msg.status.at(peer));
329 : }
330 : }
331 0 : return libjami::Account::MessageStates::UNKNOWN;
332 : };
333 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId1, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId1, aliceUri) == libjami::Account::MessageStates::SENT; }));
334 1 : libjami::sendMessage(bobId, bobData.conversationId, "2"s, "");
335 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 2
336 : && bobData.messages.size() == bobMsgSize + 2
337 : && bob2Data.messages.size() == bob2MsgSize + 2;}));
338 1 : auto msgId2 = aliceData.messages.rbegin()->id;
339 : // Because bob2Data.status is here only on update, but msgReceived can be good directly at first
340 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId2, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId2, aliceUri) == libjami::Account::MessageStates::SENT; }));
341 1 : libjami::sendMessage(bobId, bobData.conversationId, "3"s, "");
342 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 3
343 : && bobData.messages.size() == bobMsgSize + 3
344 : && bob2Data.messages.size() == bob2MsgSize + 3; }));
345 1 : auto msgId3 = aliceData.messages.rbegin()->id;
346 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId3, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId3, aliceUri) == libjami::Account::MessageStates::SENT; }));
347 1 : libjami::sendMessage(bobId, bobData.conversationId, "4"s, "");
348 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 4
349 : && bobData.messages.size() == bobMsgSize + 4
350 : && bob2Data.messages.size() == bob2MsgSize + 4; }));
351 1 : auto msgId4 = aliceData.messages.rbegin()->id;
352 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId4, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId4, aliceUri) == libjami::Account::MessageStates::SENT; }));
353 :
354 :
355 : // Bob is disabled. Bob2 will get the infos
356 1 : Manager::instance().sendRegister(bobId, false);
357 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.stopped; }));
358 :
359 : // Second message is set to displayed by alice
360 1 : aliceAccount->setMessageDisplayed("swarm:" + aliceData.conversationId, msgId2, 3);
361 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bob2Data, msgId1, aliceUri) == libjami::Account::MessageStates::DISPLAYED
362 : && getMsgStatus(bob2Data, msgId2, aliceUri) == libjami::Account::MessageStates::DISPLAYED; }));
363 1 : CPPUNIT_ASSERT(getMsgStatus(bobData, msgId1, aliceUri) != libjami::Account::MessageStates::DISPLAYED);
364 :
365 : // Alice is disabled so she will not sync
366 1 : Manager::instance().sendRegister(aliceId, false);
367 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.stopped; }));
368 :
369 : // Bob is enabled again, should sync infos with bob2
370 1 : Manager::instance().sendRegister(bobId, true);
371 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId1, aliceUri) == libjami::Account::MessageStates::DISPLAYED
372 : && getMsgStatus(bobData, msgId2, aliceUri) == libjami::Account::MessageStates::DISPLAYED; }));
373 1 : }
374 :
375 : void
376 1 : ConversationFetchSentTest::testSyncFetch()
377 : {
378 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
379 1 : connectSignals();
380 :
381 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
382 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
383 1 : auto bobUri = bobAccount->getUsername();
384 1 : auto aliceUri = aliceAccount->getUsername();
385 :
386 : // Bob creates a second device
387 2 : auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
388 1 : std::remove(bobArchive.c_str());
389 1 : bobAccount->exportArchive(bobArchive);
390 2 : std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
391 1 : details[ConfProperties::TYPE] = "RING";
392 1 : details[ConfProperties::DISPLAYNAME] = "BOB2";
393 1 : details[ConfProperties::ALIAS] = "BOB2";
394 1 : details[ConfProperties::UPNP_ENABLED] = "true";
395 1 : details[ConfProperties::ARCHIVE_PASSWORD] = "";
396 1 : details[ConfProperties::ARCHIVE_PIN] = "";
397 1 : details[ConfProperties::ARCHIVE_PATH] = bobArchive;
398 1 : bob2Id = Manager::instance().addAccount(details);
399 :
400 : // Disconnect bob2, to create a valid conv betwen Alice and Bob1
401 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
402 :
403 : // Create conversation between alice and bob
404 1 : aliceAccount->addContact(bobUri);
405 1 : aliceAccount->sendTrustRequest(bobUri, {});
406 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
407 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
408 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && !bob2Data.conversationId.empty(); }));
409 :
410 1 : std::this_thread::sleep_for(5s); // Wait for all join messages to be received
411 :
412 : // bob send 4 messages
413 1 : auto aliceMsgSize = aliceData.messages.size(), bob2MsgSize = bob2Data.messages.size(), bobMsgSize = bobData.messages.size();
414 1 : libjami::sendMessage(bobId, bobData.conversationId, "1"s, "");
415 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
416 : return aliceData.messages.size() == aliceMsgSize + 1
417 : && bobData.messages.size() == bobMsgSize + 1
418 : && bob2Data.messages.size() == bob2MsgSize + 1; }));
419 1 : auto msgId1 = aliceData.messages.rbegin()->id;
420 26 : auto getMsgStatus = [&](const auto& data, const auto& id, const auto& peer) {
421 74 : for (const auto& msg : data.messages) {
422 70 : if (msg.id == id && msg.status.find(peer) != msg.status.end()) {
423 22 : return static_cast<libjami::Account::MessageStates>(msg.status.at(peer));
424 : }
425 : }
426 4 : return libjami::Account::MessageStates::UNKNOWN;
427 : };
428 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId1, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId1, aliceUri) == libjami::Account::MessageStates::SENT; }));
429 1 : libjami::sendMessage(bobId, bobData.conversationId, "2"s, "");
430 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 2
431 : && bobData.messages.size() == bobMsgSize + 2
432 : && bob2Data.messages.size() == bob2MsgSize + 2;}));
433 1 : auto msgId2 = aliceData.messages.rbegin()->id;
434 : // Because bob2Data.status is here only on update, but msgReceived can be good directly at first
435 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId2, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId2, aliceUri) == libjami::Account::MessageStates::SENT; }));
436 1 : libjami::sendMessage(bobId, bobData.conversationId, "3"s, "");
437 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 3
438 : && bobData.messages.size() == bobMsgSize + 3
439 : && bob2Data.messages.size() == bob2MsgSize + 3; }));
440 1 : auto msgId3 = aliceData.messages.rbegin()->id;
441 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId3, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId3, aliceUri) == libjami::Account::MessageStates::SENT; }));
442 1 : libjami::sendMessage(bobId, bobData.conversationId, "4"s, "");
443 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 4
444 : && bobData.messages.size() == bobMsgSize + 4
445 : && bob2Data.messages.size() == bob2MsgSize + 4; }));
446 1 : auto msgId4 = aliceData.messages.rbegin()->id;
447 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId4, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId4, aliceUri) == libjami::Account::MessageStates::SENT; }));
448 :
449 : // Second message is set to displayed by alice
450 1 : aliceAccount->setMessageDisplayed("swarm:" + aliceData.conversationId, msgId2, 3);
451 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId1, aliceUri) == libjami::Account::MessageStates::DISPLAYED && getMsgStatus(bob2Data, msgId1, aliceUri) == libjami::Account::MessageStates::DISPLAYED
452 : && getMsgStatus(bobData, msgId2, aliceUri) == libjami::Account::MessageStates::DISPLAYED && getMsgStatus(bob2Data, msgId2, aliceUri) == libjami::Account::MessageStates::DISPLAYED; }));
453 : // Other messages are still set to received
454 1 : CPPUNIT_ASSERT(getMsgStatus(bobData, msgId3, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId3, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bobData, msgId4, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bob2Data, msgId4, aliceUri) == libjami::Account::MessageStates::SENT);
455 :
456 : // Get conversation members should show the same information
457 1 : auto membersInfos = libjami::getConversationMembers(bobId, bobData.conversationId);
458 2 : CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
459 : membersInfos.end(),
460 : [&](auto infos) {
461 : return infos["uri"] == aliceUri
462 : && infos["lastDisplayed"] == msgId2;
463 : })
464 : != membersInfos.end());
465 :
466 : // Alice is disabled
467 1 : Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
468 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.stopped; }));
469 :
470 : // Bob send 2 more messages
471 1 : bob2MsgSize = bob2Data.messages.size();
472 1 : libjami::sendMessage(bobId, bobData.conversationId, "5"s, "");
473 1 : libjami::sendMessage(bobId, bobData.conversationId, "6"s, "");
474 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.messages.size() == bob2MsgSize + 2; }));
475 1 : auto msgId5 = bobData.messages.rbegin()->id;
476 1 : auto msgId6 = (bobData.messages.rbegin()+1)->id;
477 : // No update
478 3 : CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId5, aliceUri) == libjami::Account::MessageStates::SENT && getMsgStatus(bobData, msgId6, aliceUri) == libjami::Account::MessageStates::SENT; }));
479 : // SwarmMessage will not get any status because nobody got the message
480 1 : CPPUNIT_ASSERT(getMsgStatus(bobData, msgId5, aliceUri) == libjami::Account::MessageStates::UNKNOWN);
481 1 : CPPUNIT_ASSERT(getMsgStatus(bobData, msgId6, aliceUri) == libjami::Account::MessageStates::UNKNOWN);
482 1 : }
483 :
484 : void
485 1 : ConversationFetchSentTest::testDisplayedOnLoad()
486 : {
487 1 : std::cout << "\nRunning test: " << __func__ << std::endl;
488 1 : connectSignals();
489 :
490 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
491 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
492 1 : auto bobUri = bobAccount->getUsername();
493 1 : auto aliceUri = aliceAccount->getUsername();
494 :
495 : // Create conversation between alice and bob
496 1 : aliceAccount->addContact(bobUri);
497 1 : aliceAccount->sendTrustRequest(bobUri, {});
498 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
499 1 : CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
500 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
501 :
502 1 : std::this_thread::sleep_for(5s); // Wait for all join messages to be received
503 :
504 : // bob send 2 messages
505 1 : auto aliceMsgSize = aliceData.messages.size(), bobMsgSize = bobData.messages.size();
506 1 : libjami::sendMessage(bobId, bobData.conversationId, "1"s, "");
507 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
508 : return aliceData.messages.size() == aliceMsgSize + 1
509 : && bobData.messages.size() == bobMsgSize + 1; }));
510 1 : auto msgId1 = aliceData.messages.rbegin()->id;
511 10 : auto getMsgStatus = [&](const auto& data, const auto& id, const auto& peer) {
512 12 : for (const auto& msg : data.messages) {
513 10 : if (msg.id == id && msg.status.find(peer) != msg.status.end()) {
514 8 : return static_cast<libjami::Account::MessageStates>(msg.status.at(peer));
515 : }
516 : }
517 2 : return libjami::Account::MessageStates::UNKNOWN;
518 : };
519 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId1, aliceUri) == libjami::Account::MessageStates::SENT; }));
520 1 : libjami::sendMessage(bobId, bobData.conversationId, "2"s, "");
521 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 2
522 : && bobData.messages.size() == bobMsgSize + 2;}));
523 1 : auto msgId2 = aliceData.messages.rbegin()->id;
524 :
525 : // Second message is set to displayed by alice
526 1 : aliceAccount->setMessageDisplayed("swarm:" + aliceData.conversationId, msgId2, 3);
527 5 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return getMsgStatus(bobData, msgId1, aliceUri) == libjami::Account::MessageStates::DISPLAYED
528 : && getMsgStatus(bobData, msgId2, aliceUri) == libjami::Account::MessageStates::DISPLAYED; }));
529 :
530 1 : bobAccount->convModule()->loadConversations(); // Reset data
531 1 : bobData.messages.clear();
532 : // Load messages, messages should be displayed
533 1 : CPPUNIT_ASSERT(getMsgStatus(bobData, msgId1, aliceUri) != libjami::Account::MessageStates::DISPLAYED);
534 1 : libjami::loadConversation(bobId, bobData.conversationId, "", 0);
535 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
536 : return getMsgStatus(bobData, msgId1, aliceUri) == libjami::Account::MessageStates::DISPLAYED
537 : && getMsgStatus(bobData, msgId2, aliceUri) == libjami::Account::MessageStates::DISPLAYED; }));
538 :
539 1 : }
540 :
541 :
542 : } // namespace test
543 : } // namespace jami
544 :
545 1 : RING_TEST_RUNNER(jami::test::ConversationFetchSentTest::name())
|