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 <filesystem>
24 : #include <string>
25 :
26 : #include "../../test_runner.h"
27 : #include "account_const.h"
28 : #include "common.h"
29 : #include "conversation/conversationcommon.h"
30 : #include "manager.h"
31 : #include "media_const.h"
32 : #include "sip/sipcall.h"
33 :
34 : using namespace std::literals::chrono_literals;
35 :
36 : namespace jami {
37 : namespace test {
38 :
39 : struct UserData
40 : {
41 : std::map<std::string, int> status;
42 : std::map<std::string, std::string> statusNote;
43 :
44 : std::string conversationId;
45 : bool requestReceived {false};
46 : };
47 :
48 : class PresenceTest : public CppUnit::TestFixture
49 : {
50 : public:
51 10 : ~PresenceTest() { libjami::fini(); }
52 2 : static std::string name() { return "PresenceTest"; }
53 : void setUp();
54 : void tearDown();
55 :
56 : std::string aliceId;
57 : std::string bobId;
58 : std::string carlaId;
59 : UserData aliceData_;
60 : UserData bobData_;
61 : UserData carlaData_;
62 :
63 : std::mutex mtx;
64 : std::unique_lock<std::mutex> lk {mtx};
65 : std::condition_variable cv;
66 :
67 : private:
68 : void connectSignals();
69 : void enableCarla();
70 :
71 : void testGetSetSubscriptions();
72 : void testPresenceStatus();
73 : void testPresenceStatusNote();
74 : void testPresenceInvalidStatusNote();
75 : void testPresenceStatusNoteBeforeConnection();
76 :
77 2 : CPPUNIT_TEST_SUITE(PresenceTest);
78 1 : CPPUNIT_TEST(testGetSetSubscriptions);
79 1 : CPPUNIT_TEST(testPresenceStatus);
80 1 : CPPUNIT_TEST(testPresenceStatusNote);
81 1 : CPPUNIT_TEST(testPresenceInvalidStatusNote);
82 1 : CPPUNIT_TEST(testPresenceStatusNoteBeforeConnection);
83 4 : CPPUNIT_TEST_SUITE_END();
84 : };
85 :
86 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(PresenceTest, PresenceTest::name());
87 :
88 : void
89 5 : PresenceTest::setUp()
90 : {
91 : // Init daemon
92 5 : libjami::init(
93 : libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
94 5 : if (not Manager::instance().initialized)
95 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
96 :
97 5 : auto actors = load_actors("actors/alice-bob-carla.yml");
98 5 : aliceId = actors["alice"];
99 5 : bobId = actors["bob"];
100 5 : carlaId = actors["carla"];
101 5 : aliceData_ = {};
102 5 : bobData_ = {};
103 5 : carlaData_ = {};
104 :
105 5 : Manager::instance().sendRegister(carlaId, false);
106 15 : wait_for_announcement_of({aliceId, bobId});
107 5 : }
108 :
109 : void
110 5 : PresenceTest::tearDown()
111 : {
112 20 : wait_for_removal_of({aliceId, bobId, carlaId});
113 5 : }
114 :
115 : void
116 5 : PresenceTest::connectSignals()
117 : {
118 5 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
119 5 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
120 6 : [&](const std::string& accountId, const std::string& conversationId) {
121 6 : if (accountId == aliceId)
122 4 : aliceData_.conversationId = conversationId;
123 2 : else if (accountId == bobId)
124 1 : bobData_.conversationId = conversationId;
125 1 : else if (accountId == carlaId)
126 1 : carlaData_.conversationId = conversationId;
127 6 : cv.notify_one();
128 6 : }));
129 5 : confHandlers.insert(
130 10 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
131 8 : [&](const std::string& accountId,
132 : const std::string& /*conversationId*/,
133 : std::map<std::string, std::string> /*metadatas*/) {
134 8 : if (accountId == aliceId)
135 0 : aliceData_.requestReceived = true;
136 8 : if (accountId == bobId)
137 4 : bobData_.requestReceived = true;
138 8 : if (accountId == carlaId)
139 4 : carlaData_.requestReceived = true;
140 8 : cv.notify_one();
141 8 : }));
142 5 : confHandlers.insert(
143 10 : libjami::exportable_callback<libjami::PresenceSignal::NewBuddyNotification>(
144 37 : [&](const std::string& accountId,
145 : const std::string& peerId,
146 : int status,
147 : const std::string& note) {
148 37 : if (accountId == aliceId) {
149 17 : aliceData_.status[peerId] = status;
150 17 : aliceData_.statusNote[peerId] = note;
151 20 : } else if (accountId == bobId) {
152 10 : bobData_.status[peerId] = status;
153 10 : bobData_.statusNote[peerId] = note;
154 10 : } else if (accountId == carlaId) {
155 10 : carlaData_.status[peerId] = status;
156 10 : carlaData_.statusNote[peerId] = note;
157 : }
158 37 : cv.notify_one();
159 37 : }));
160 :
161 5 : libjami::registerSignalHandlers(confHandlers);
162 5 : }
163 :
164 : void
165 3 : PresenceTest::enableCarla()
166 : {
167 3 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
168 : // Enable carla
169 3 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
170 3 : bool carlaConnected = false;
171 3 : confHandlers.insert(
172 6 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
173 9 : [&](const std::string&, const std::map<std::string, std::string>&) {
174 9 : auto details = carlaAccount->getVolatileAccountDetails();
175 : auto deviceAnnounced
176 18 : = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
177 9 : if (deviceAnnounced == "true") {
178 3 : carlaConnected = true;
179 3 : cv.notify_one();
180 : }
181 9 : }));
182 3 : libjami::registerSignalHandlers(confHandlers);
183 :
184 3 : Manager::instance().sendRegister(carlaId, true);
185 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaConnected; }));
186 3 : confHandlers.clear();
187 3 : libjami::unregisterSignalHandlers();
188 3 : }
189 :
190 : void
191 1 : PresenceTest::testGetSetSubscriptions()
192 : {
193 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
194 1 : auto bobUri = bobAccount->getUsername();
195 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
196 1 : auto carlaUri = carlaAccount->getUsername();
197 1 : connectSignals();
198 :
199 1 : CPPUNIT_ASSERT(libjami::getSubscriptions(aliceId).empty());
200 3 : libjami::setSubscriptions(aliceId, {bobUri, carlaUri});
201 1 : auto subscriptions = libjami::getSubscriptions(aliceId);
202 1 : CPPUNIT_ASSERT(subscriptions.size() == 2);
203 2 : auto checkSub = [&](const std::string& uri) {
204 2 : return std::find_if(subscriptions.begin(), subscriptions.end(), [&](const auto& sub) {
205 3 : return sub.at("Buddy") == uri;
206 4 : }) != subscriptions.end();
207 1 : };
208 1 : CPPUNIT_ASSERT(checkSub(bobUri) && checkSub(carlaUri));
209 1 : }
210 :
211 : void
212 1 : PresenceTest::testPresenceStatus()
213 : {
214 1 : connectSignals();
215 :
216 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
217 1 : auto aliceUri = aliceAccount->getUsername();
218 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
219 1 : auto bobUri = bobAccount->getUsername();
220 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
221 1 : auto carlaUri = carlaAccount->getUsername();
222 :
223 : // Track Presence
224 1 : aliceAccount->trackBuddyPresence(bobUri, true);
225 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
226 :
227 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
228 : return aliceData_.status.find(bobUri) != aliceData_.status.end() && aliceData_.status[bobUri] == 1;
229 : }));
230 1 : CPPUNIT_ASSERT(aliceData_.status.find(carlaUri) == aliceData_.status.end()); // For now, still offline so no change
231 :
232 : // Carla is now online
233 1 : Manager::instance().sendRegister(carlaId, true);
234 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
235 : return aliceData_.status.find(carlaUri) != aliceData_.status.end() && aliceData_.status[carlaUri] == 1;
236 : }));
237 :
238 : // Start conversation
239 1 : libjami::startConversation(aliceId);
240 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.conversationId.empty(); });
241 :
242 1 : libjami::addConversationMember(aliceId, aliceData_.conversationId, bobUri);
243 1 : libjami::addConversationMember(aliceId, aliceData_.conversationId, carlaUri);
244 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
245 : return bobData_.requestReceived && carlaData_.requestReceived;
246 : }));
247 1 : libjami::acceptConversationRequest(bobId, aliceData_.conversationId);
248 1 : libjami::acceptConversationRequest(carlaId, aliceData_.conversationId);
249 :
250 : // Should connect to peers
251 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
252 : return aliceData_.status[bobUri] == 2 && aliceData_.status[carlaUri] == 2;
253 : }));
254 :
255 : // Carla disconnects, should just let the presence on the DHT for a few minutes
256 1 : Manager::instance().sendRegister(carlaId, false);
257 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
258 : return aliceData_.status[carlaUri] == 1;
259 : }));
260 1 : }
261 :
262 : void
263 1 : PresenceTest::testPresenceStatusNote()
264 : {
265 1 : enableCarla();
266 1 : connectSignals();
267 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
268 1 : auto aliceUri = aliceAccount->getUsername();
269 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
270 1 : auto bobUri = bobAccount->getUsername();
271 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
272 1 : auto carlaUri = carlaAccount->getUsername();
273 :
274 : // Track Presence
275 1 : aliceAccount->trackBuddyPresence(bobUri, true);
276 1 : bobAccount->trackBuddyPresence(aliceUri, true);
277 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
278 1 : carlaAccount->trackBuddyPresence(aliceUri, true);
279 :
280 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
281 : return aliceData_.status.find(bobUri) != aliceData_.status.end() && aliceData_.status[bobUri] == 1
282 : && aliceData_.status.find(carlaUri) != aliceData_.status.end() && aliceData_.status[carlaUri] == 1;
283 : }));
284 :
285 : // Start conversation
286 1 : libjami::startConversation(aliceId);
287 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.conversationId.empty(); });
288 :
289 1 : libjami::addConversationMember(aliceId, aliceData_.conversationId, bobUri);
290 1 : libjami::addConversationMember(aliceId, aliceData_.conversationId, carlaUri);
291 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
292 : return bobData_.requestReceived && carlaData_.requestReceived;
293 : }));
294 1 : libjami::acceptConversationRequest(bobId, aliceData_.conversationId);
295 1 : libjami::acceptConversationRequest(carlaId, aliceData_.conversationId);
296 :
297 : // Should connect to peers
298 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
299 : return aliceData_.status[bobUri] == 2 && aliceData_.status[carlaUri] == 2;
300 : }));
301 :
302 : // Alice sends a status note, should be received by Bob and Carla
303 1 : libjami::publish(aliceId, true, "Testing Jami");
304 :
305 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
306 : return bobData_.statusNote.find(aliceUri) != bobData_.statusNote.end() && bobData_.statusNote[aliceUri] == "Testing Jami"
307 : && carlaData_.statusNote.find(aliceUri) != carlaData_.statusNote.end() && carlaData_.statusNote[aliceUri] == "Testing Jami";
308 : }));
309 1 : }
310 :
311 :
312 : void
313 1 : PresenceTest::testPresenceInvalidStatusNote()
314 : {
315 1 : enableCarla();
316 1 : connectSignals();
317 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
318 1 : auto aliceUri = aliceAccount->getUsername();
319 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
320 1 : auto bobUri = bobAccount->getUsername();
321 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
322 1 : auto carlaUri = carlaAccount->getUsername();
323 :
324 : // Track Presence
325 1 : aliceAccount->trackBuddyPresence(bobUri, true);
326 1 : bobAccount->trackBuddyPresence(aliceUri, true);
327 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
328 1 : carlaAccount->trackBuddyPresence(aliceUri, true);
329 :
330 6 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
331 : return aliceData_.status.find(bobUri) != aliceData_.status.end() && aliceData_.status[bobUri] == 1
332 : && aliceData_.status.find(carlaUri) != aliceData_.status.end() && aliceData_.status[carlaUri] == 1;
333 : }));
334 :
335 : // Start conversation
336 1 : libjami::startConversation(aliceId);
337 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.conversationId.empty(); });
338 :
339 1 : libjami::addConversationMember(aliceId, aliceData_.conversationId, bobUri);
340 1 : libjami::addConversationMember(aliceId, aliceData_.conversationId, carlaUri);
341 7 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
342 : return bobData_.requestReceived && carlaData_.requestReceived;
343 : }));
344 1 : libjami::acceptConversationRequest(bobId, aliceData_.conversationId);
345 1 : libjami::acceptConversationRequest(carlaId, aliceData_.conversationId);
346 :
347 : // Should connect to peers
348 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
349 : return aliceData_.status[bobUri] == 2 && aliceData_.status[carlaUri] == 2;
350 : }));
351 :
352 : // Alice sends a status note that will generate an invalid XML message
353 1 : libjami::publish(aliceId, true, "Testing<BAD>");
354 :
355 7 : CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() {
356 : return bobData_.statusNote.find(aliceUri) != bobData_.statusNote.end() && bobData_.statusNote[aliceUri] == "Testing<BAD>"
357 : && carlaData_.statusNote.find(aliceUri) != carlaData_.statusNote.end() && carlaData_.statusNote[aliceUri] == "Testing<BAD>";
358 : }));
359 1 : }
360 :
361 : void
362 1 : PresenceTest::testPresenceStatusNoteBeforeConnection()
363 : {
364 1 : enableCarla();
365 1 : connectSignals();
366 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
367 1 : auto aliceUri = aliceAccount->getUsername();
368 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
369 1 : auto bobUri = bobAccount->getUsername();
370 1 : auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
371 1 : auto carlaUri = carlaAccount->getUsername();
372 :
373 : // Track Presence
374 1 : aliceAccount->trackBuddyPresence(bobUri, true);
375 1 : bobAccount->trackBuddyPresence(aliceUri, true);
376 1 : aliceAccount->trackBuddyPresence(carlaUri, true);
377 1 : carlaAccount->trackBuddyPresence(aliceUri, true);
378 1 : libjami::publish(aliceId, true, "Testing Jami");
379 :
380 4 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
381 : return aliceData_.status.find(bobUri) != aliceData_.status.end() && aliceData_.status[bobUri] == 1
382 : && aliceData_.status.find(carlaUri) != aliceData_.status.end() && aliceData_.status[carlaUri] == 1;
383 : }));
384 :
385 : // Start conversation
386 1 : libjami::startConversation(aliceId);
387 2 : cv.wait_for(lk, 30s, [&]() { return !aliceData_.conversationId.empty(); });
388 :
389 1 : libjami::addConversationMember(aliceId, aliceData_.conversationId, bobUri);
390 1 : libjami::addConversationMember(aliceId, aliceData_.conversationId, carlaUri);
391 9 : CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
392 : return bobData_.requestReceived && carlaData_.requestReceived;
393 : }));
394 1 : libjami::acceptConversationRequest(bobId, aliceData_.conversationId);
395 1 : libjami::acceptConversationRequest(carlaId, aliceData_.conversationId);
396 :
397 : // Should connect to peers
398 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
399 : return aliceData_.status[bobUri] == 2 && aliceData_.status[carlaUri] == 2;
400 : }));
401 :
402 : // Alice sends a status note, should be received by Bob and Carla
403 2 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
404 : return bobData_.statusNote.find(aliceUri) != bobData_.statusNote.end() && bobData_.statusNote[aliceUri] == "Testing Jami"
405 : && carlaData_.statusNote.find(aliceUri) != carlaData_.statusNote.end() && carlaData_.statusNote[aliceUri] == "Testing Jami";
406 : }));
407 :
408 1 : }
409 :
410 : } // namespace test
411 : } // namespace jami
412 :
413 1 : RING_TEST_RUNNER(jami::test::PresenceTest::name())
|