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 <filesystem>
27 : #include <msgpack.hpp>
28 :
29 : #include "../../test_runner.h"
30 : #include "account_const.h"
31 : #include "archiver.h"
32 : #include "base64.h"
33 : #include "common.h"
34 : #include "conversation/conversationcommon.h"
35 : #include "fileutils.h"
36 : #include "jami.h"
37 : #include "manager.h"
38 : #include <dhtnet/certstore.h>
39 :
40 : using namespace std::string_literals;
41 : using namespace std::literals::chrono_literals;
42 : using namespace libjami::Account;
43 :
44 : namespace jami {
45 : namespace test {
46 :
47 : struct UserData {
48 : std::string conversationId;
49 : bool registered {false};
50 : bool stopped {false};
51 : bool requestReceived {false};
52 : bool deviceAnnounced {false};
53 : std::map<std::string, int> composing;
54 : };
55 :
56 : class TypersTest : public CppUnit::TestFixture
57 : {
58 : public:
59 8 : ~TypersTest() { libjami::fini(); }
60 2 : static std::string name() { return "Typers"; }
61 : void setUp();
62 : void tearDown();
63 :
64 : std::string aliceId;
65 : UserData aliceData;
66 : std::string bobId;
67 : UserData bobData;
68 :
69 : std::mutex mtx;
70 : std::unique_lock<std::mutex> lk {mtx};
71 : std::condition_variable cv;
72 :
73 : void connectSignals();
74 :
75 : void testSetIsComposing();
76 : void testTimeout();
77 : void testTypingRemovedOnMemberRemoved();
78 : void testAccountConfig();
79 :
80 : private:
81 2 : CPPUNIT_TEST_SUITE(TypersTest);
82 1 : CPPUNIT_TEST(testSetIsComposing);
83 1 : CPPUNIT_TEST(testTimeout);
84 1 : CPPUNIT_TEST(testTypingRemovedOnMemberRemoved);
85 1 : CPPUNIT_TEST(testAccountConfig);
86 4 : CPPUNIT_TEST_SUITE_END();
87 : };
88 :
89 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(TypersTest, TypersTest::name());
90 :
91 : void
92 4 : TypersTest::setUp()
93 : {
94 : // Init daemon
95 4 : libjami::init(
96 : libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
97 4 : if (not Manager::instance().initialized)
98 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
99 :
100 4 : auto actors = load_actors("actors/alice-bob.yml");
101 4 : aliceId = actors["alice"];
102 4 : bobId = actors["bob"];
103 4 : aliceData = {};
104 4 : bobData = {};
105 :
106 12 : wait_for_announcement_of({aliceId, bobId});
107 4 : }
108 : void
109 4 : TypersTest::connectSignals()
110 : {
111 4 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
112 4 : confHandlers.insert(
113 8 : libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
114 4 : [&](const std::string& accountId, const std::map<std::string, std::string>&) {
115 4 : if (accountId == aliceId) {
116 4 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
117 4 : auto details = aliceAccount->getVolatileAccountDetails();
118 8 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
119 4 : if (daemonStatus == "REGISTERED") {
120 2 : aliceData.registered = true;
121 2 : } else if (daemonStatus == "UNREGISTERED") {
122 1 : aliceData.stopped = true;
123 : }
124 8 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
125 4 : aliceData.deviceAnnounced = deviceAnnounced == "true";
126 4 : } else if (accountId == bobId) {
127 0 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
128 0 : auto details = bobAccount->getVolatileAccountDetails();
129 0 : auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
130 0 : if (daemonStatus == "REGISTERED") {
131 0 : bobData.registered = true;
132 0 : } else if (daemonStatus == "UNREGISTERED") {
133 0 : bobData.stopped = true;
134 : }
135 0 : auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
136 0 : bobData.deviceAnnounced = deviceAnnounced == "true";
137 0 : }
138 4 : cv.notify_one();
139 4 : }));
140 4 : confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
141 8 : [&](const std::string& accountId, const std::string& conversationId) {
142 8 : if (accountId == aliceId) {
143 4 : aliceData.conversationId = conversationId;
144 4 : } else if (accountId == bobId) {
145 4 : bobData.conversationId = conversationId;
146 : }
147 8 : cv.notify_one();
148 8 : }));
149 4 : confHandlers.insert(
150 8 : libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
151 4 : [&](const std::string& accountId,
152 : const std::string& /* conversationId */,
153 : std::map<std::string, std::string> /*metadatas*/) {
154 4 : if (accountId == aliceId) {
155 0 : aliceData.requestReceived = true;
156 4 : } else if (accountId == bobId) {
157 4 : bobData.requestReceived = true;
158 : }
159 4 : cv.notify_one();
160 4 : }));
161 4 : confHandlers.insert(
162 8 : libjami::exportable_callback<libjami::ConfigurationSignal::ComposingStatusChanged>(
163 6 : [&](const std::string& accountId,
164 : const std::string& /* conversationId */,
165 : const std::string& contactUri,
166 : int status) {
167 6 : if (accountId == aliceId) {
168 2 : aliceData.composing[contactUri] = status;
169 4 : } else if (accountId == bobId) {
170 4 : bobData.composing[contactUri] = status;
171 : }
172 6 : cv.notify_one();
173 6 : }));
174 :
175 4 : libjami::registerSignalHandlers(confHandlers);
176 4 : }
177 :
178 : void
179 4 : TypersTest::tearDown()
180 : {
181 12 : wait_for_removal_of({aliceId, bobId});
182 4 : }
183 :
184 : void
185 1 : TypersTest::testSetIsComposing()
186 : {
187 1 : connectSignals();
188 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
189 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
190 1 : auto aliceUri = aliceAccount->getUsername();
191 1 : auto bobUri = bobAccount->getUsername();
192 :
193 1 : aliceAccount->addContact(bobUri);
194 1 : aliceAccount->sendTrustRequest(bobUri, {});
195 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
196 :
197 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 1);
198 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
199 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
200 1 : std::this_thread::sleep_for(5s); // Wait a bit to ensure that everything is updated
201 :
202 1 : libjami::setIsComposing(aliceId, "swarm:" + aliceData.conversationId, true);
203 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() {
204 : return bobData.composing[aliceUri]; }));
205 :
206 1 : libjami::setIsComposing(aliceId, "swarm:" + aliceData.conversationId, false);
207 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 12s, [&]() { return !bobData.composing[aliceUri]; }));
208 1 : }
209 :
210 : void
211 1 : TypersTest::testTimeout()
212 : {
213 1 : connectSignals();
214 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
215 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
216 1 : auto aliceUri = aliceAccount->getUsername();
217 1 : auto bobUri = bobAccount->getUsername();
218 :
219 1 : aliceAccount->addContact(bobUri);
220 1 : aliceAccount->sendTrustRequest(bobUri, {});
221 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
222 :
223 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 1);
224 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
225 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
226 1 : std::this_thread::sleep_for(5s); // Wait a bit to ensure that everything is updated
227 :
228 1 : libjami::setIsComposing(aliceId, "swarm:" + aliceData.conversationId, true);
229 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return bobData.composing[aliceUri]; }));
230 :
231 : // After 12s, it should be false
232 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 12s, [&]() { return !bobData.composing[aliceUri]; }));
233 1 : }
234 :
235 : void
236 1 : TypersTest::testTypingRemovedOnMemberRemoved()
237 : {
238 1 : connectSignals();
239 :
240 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
241 1 : auto bobUri = bobAccount->getUsername();
242 :
243 1 : auto convId = libjami::startConversation(aliceId);
244 :
245 1 : libjami::addConversationMember(aliceId, convId, bobUri);
246 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
247 :
248 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
249 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
250 :
251 1 : std::this_thread::sleep_for(5s); // Wait a bit to ensure that everything is updated
252 :
253 1 : libjami::setIsComposing(bobId, "swarm:" + bobData.conversationId, true);
254 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return aliceData.composing[bobUri]; }));
255 :
256 1 : libjami::removeConversationMember(aliceId, convId, bobUri);
257 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !aliceData.composing[bobUri]; }));
258 1 : }
259 :
260 : void
261 1 : TypersTest::testAccountConfig()
262 : {
263 1 : connectSignals();
264 1 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
265 1 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
266 1 : auto aliceUri = aliceAccount->getUsername();
267 1 : auto bobUri = bobAccount->getUsername();
268 :
269 1 : std::map<std::string, std::string> details;
270 1 : details[ConfProperties::SENDCOMPOSING] = "false";
271 1 : libjami::setAccountDetails(aliceId, details);
272 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.registered; }));
273 :
274 1 : aliceAccount->addContact(bobUri);
275 1 : aliceAccount->sendTrustRequest(bobUri, {});
276 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
277 :
278 1 : CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 1);
279 1 : libjami::acceptConversationRequest(bobId, aliceData.conversationId);
280 3 : CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
281 1 : std::this_thread::sleep_for(5s); // Wait a bit to ensure that everything is updated
282 :
283 : // Should not receive composing status
284 1 : libjami::setIsComposing(aliceId, "swarm:" + aliceData.conversationId, true);
285 3 : CPPUNIT_ASSERT(!cv.wait_for(lk, 5s, [&]() {
286 : return bobData.composing[aliceUri]
287 : ; }));
288 1 : }
289 :
290 :
291 : } // namespace test
292 : } // namespace jami
293 :
294 1 : RING_TEST_RUNNER(jami::test::TypersTest::name())
|