1 /**
2 * @file tests/sdk_test.cpp
3 * @brief Mega SDK test file
4 *
5 * (c) 2015 by Mega Limited, Wellsford, New Zealand
6 *
7 * This file is part of the MEGA SDK - Client Access Engine.
8 *
9 * Applications using the MEGA API must present a valid application key
10 * and comply with the the rules set forth in the Terms of Service.
11 *
12 * The MEGA SDK is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 *
16 * @copyright Simplified (2-clause) BSD License.
17 *
18 * You should have received a copy of the license along with this
19 * program.
20 */
21
22 #include "test.h"
23 #include "SdkTest_test.h"
24 #include "mega/testhooks.h"
25 #include "megaapi_impl.h"
26 #include <algorithm>
27
28 #ifdef WIN32
29 #include <filesystem>
30 namespace fs = ::std::filesystem;
31 #else
32 #include <experimental/filesystem>
33 namespace fs = ::std::experimental::filesystem;
34 #endif
35
36 using namespace std;
37
38 MegaFileSystemAccess fileSystemAccess;
39
40 #ifdef _WIN32
41 #if (__cplusplus >= 201700L)
42 namespace fs = std::filesystem;
43 #else
44 namespace fs = std::experimental::filesystem;
45 #endif
46 #endif
47
48 #ifdef _WIN32
ThreadId()49 DWORD ThreadId()
50 {
51 return GetCurrentThreadId();
52 }
53 #else
ThreadId()54 pthread_t ThreadId()
55 {
56 return pthread_self();
57 }
58 #endif
59
60
61
cwd()62 const char* cwd()
63 {
64 // for windows and linux
65 static char path[1024];
66 const char* ret;
67 #ifdef _WIN32
68 ret = _getcwd(path, sizeof path);
69 #else
70 ret = getcwd(path, sizeof path);
71 #endif
72 assert(ret);
73 return ret;
74 }
75
fileexists(const std::string & fn)76 bool fileexists(const std::string& fn)
77 {
78 #ifdef _WIN32
79 return fs::exists(fn);
80 #else
81 struct stat buffer;
82 return (stat(fn.c_str(), &buffer) == 0);
83 #endif
84 }
85
copyFile(std::string & from,std::string & to)86 void copyFile(std::string& from, std::string& to)
87 {
88 LocalPath f = LocalPath::fromPath(from, fileSystemAccess);
89 LocalPath t = LocalPath::fromPath(to, fileSystemAccess);
90 fileSystemAccess.copylocal(f, t, m_time());
91 }
92
megaApiCacheFolder(int index)93 std::string megaApiCacheFolder(int index)
94 {
95 std::string p(cwd());
96 #ifdef _WIN32
97 p += "\\";
98 #else
99 p += "/";
100 #endif
101 p += "sdk_test_mega_cache_" + to_string(index);
102
103 if (!fileexists(p))
104 {
105
106 #ifdef _WIN32
107 #ifndef NDEBUG
108 bool success =
109 #endif
110 fs::create_directory(p);
111 assert(success);
112 #else
113 mkdir(p.c_str(), S_IRWXU);
114 assert(fileexists(p));
115 #endif
116
117 }
118 return p;
119 }
120
121
WaitMillisec(unsigned n)122 void WaitMillisec(unsigned n)
123 {
124 #ifdef _WIN32
125 Sleep(n);
126 #else
127 usleep(n * 1000);
128 #endif
129 }
130
WaitFor(std::function<bool ()> && f,unsigned millisec)131 bool WaitFor(std::function<bool()>&& f, unsigned millisec)
132 {
133 unsigned waited = 0;
134 for (;;)
135 {
136 if (f()) return true;
137 if (waited >= millisec) return false;
138 WaitMillisec(100);
139 waited += 100;
140 }
141 }
142
143
144 enum { USERALERT_ARRIVAL_MILLISEC = 1000 };
145
146 #ifdef _WIN32
147 #include "mega/autocomplete.h"
148 #include <filesystem>
149 #define getcwd _getcwd
usleep(int n)150 void usleep(int n)
151 {
152 Sleep(n / 1000);
153 }
154 #endif
155
156 // helper functions and struct/classes
157 namespace
158 {
159
buildLocalFolders(fs::path targetfolder,const string & prefix,int n,int recurselevel,int filesperfolder)160 bool buildLocalFolders(fs::path targetfolder, const string& prefix, int n, int recurselevel, int filesperfolder)
161 {
162 fs::path p = targetfolder / fs::u8path(prefix);
163 if (!fs::create_directory(p))
164 return false;
165
166 for (int i = 0; i < filesperfolder; ++i)
167 {
168 string filename = "file" + to_string(i) + "_" + prefix;
169 fs::path fp = p / fs::u8path(filename);
170 #if (__cplusplus >= 201700L)
171 ofstream fs(fp/*, ios::binary*/);
172 #else
173 ofstream fs(fp.u8string()/*, ios::binary*/);
174 #endif
175 fs << filename;
176 }
177
178 if (recurselevel > 0)
179 {
180 for (int i = 0; i < n; ++i)
181 {
182 if (!buildLocalFolders(p, prefix + "_" + to_string(i), n, recurselevel - 1, filesperfolder))
183 return false;
184 }
185 }
186
187 return true;
188 }
189
createLocalFile(fs::path path,const char * name)190 bool createLocalFile(fs::path path, const char *name)
191 {
192 if (!name)
193 {
194 return false;
195 }
196
197 fs::path fp = path / fs::u8path(name);
198 #if (__cplusplus >= 201700L)
199 ofstream fs(fp/*, ios::binary*/);
200 #else
201 ofstream fs(fp.u8string()/*, ios::binary*/);
202 #endif
203 fs << name;
204 return true;
205 }
206 }
207
208
SetUp()209 void SdkTest::SetUp()
210 {
211 // do some initialization
212 if (megaApi.size() < 2)
213 {
214 megaApi.resize(2);
215 mApi.resize(2);
216 }
217 char *buf = getenv("MEGA_EMAIL");
218 if (buf)
219 mApi[0].email.assign(buf);
220 ASSERT_LT((size_t)0, mApi[0].email.length()) << "Set your username at the environment variable $MEGA_EMAIL";
221
222 buf = getenv("MEGA_PWD");
223 if (buf)
224 mApi[0].pwd.assign(buf);
225 ASSERT_LT((size_t)0, mApi[0].pwd.length()) << "Set your password at the environment variable $MEGA_PWD";
226
227 gTestingInvalidArgs = false;
228
229 if (megaApi[0].get() == NULL)
230 {
231 megaApi[0].reset(new MegaApi(APP_KEY.c_str(), megaApiCacheFolder(0).c_str(), USER_AGENT.c_str(), THREADS_PER_MEGACLIENT));
232 mApi[0].megaApi = megaApi[0].get();
233
234 megaApi[0]->setLoggingName("0");
235 megaApi[0]->addListener(this);
236
237 LOG_info << "___ Initializing test (SetUp()) ___";
238
239 ASSERT_NO_FATAL_FAILURE( login(0) );
240 ASSERT_NO_FATAL_FAILURE( fetchnodes(0) );
241 }
242 }
243
TearDown()244 void SdkTest::TearDown()
245 {
246 // do some cleanup
247
248 gTestingInvalidArgs = false;
249
250 deleteFile(UPFILE);
251 deleteFile(DOWNFILE);
252 deleteFile(PUBLICFILE);
253 deleteFile(AVATARDST);
254
255 releaseMegaApi(1);
256 releaseMegaApi(2);
257
258 if (megaApi[0])
259 {
260 LOG_info << "___ Cleaning up test (TearDown()) ___";
261
262 // Remove nodes in Cloud & Rubbish
263 purgeTree(std::unique_ptr<MegaNode>{megaApi[0]->getRootNode()}.get(), false);
264 purgeTree(std::unique_ptr<MegaNode>{megaApi[0]->getRubbishNode()}.get(), false);
265 // megaApi[0]->cleanRubbishBin();
266
267 // Remove auxiliar contact
268 std::unique_ptr<MegaUserList> ul{megaApi[0]->getContacts()};
269 for (int i = 0; i < ul->size(); i++)
270 {
271 removeContact(ul->get(i)->getEmail());
272 }
273
274 // Remove pending contact requests
275 std::unique_ptr<MegaContactRequestList> crl{megaApi[0]->getOutgoingContactRequests()};
276 for (int i = 0; i < crl->size(); i++)
277 {
278 MegaContactRequest *cr = crl->get(i);
279 megaApi[0]->inviteContact(cr->getTargetEmail(), "Removing you", MegaContactRequest::INVITE_ACTION_DELETE);
280 }
281
282 releaseMegaApi(0);
283 }
284 }
285
getApiIndex(MegaApi * api)286 int SdkTest::getApiIndex(MegaApi* api)
287 {
288 int apiIndex = -1;
289 for (int i = int(megaApi.size()); i--; ) if (megaApi[i].get() == api) apiIndex = i;
290 if (apiIndex == -1)
291 {
292 LOG_warn << "Instance of MegaApi not recognized"; // this can occur during MegaApi deletion due to callbacks on shutdown
293 }
294 return apiIndex;
295 }
296
onRequestFinish(MegaApi * api,MegaRequest * request,MegaError * e)297 void SdkTest::onRequestFinish(MegaApi *api, MegaRequest *request, MegaError *e)
298 {
299 if (request->getType() == MegaRequest::TYPE_DELETE)
300 {
301 return;
302 }
303 int apiIndex = getApiIndex(api);
304 if (apiIndex < 0) return;
305 mApi[apiIndex].requestFlags[request->getType()] = true;
306 mApi[apiIndex].lastError = e->getErrorCode();
307
308 // there could be a race on these getting set?
309 LOG_info << "lastError (by request) for MegaApi " << apiIndex << ": " << mApi[apiIndex].lastError;
310
311 switch(request->getType())
312 {
313 case MegaRequest::TYPE_CREATE_FOLDER:
314 mApi[apiIndex].h = request->getNodeHandle();
315 break;
316
317 case MegaRequest::TYPE_COPY:
318 mApi[apiIndex].h = request->getNodeHandle();
319 break;
320
321 case MegaRequest::TYPE_EXPORT:
322 if (mApi[apiIndex].lastError == API_OK)
323 {
324 mApi[apiIndex].h = request->getNodeHandle();
325 if (request->getAccess())
326 {
327 link.assign(request->getLink());
328 }
329 }
330 break;
331
332 case MegaRequest::TYPE_GET_PUBLIC_NODE:
333 if (mApi[apiIndex].lastError == API_OK)
334 {
335 publicNode = request->getPublicMegaNode();
336 }
337 break;
338
339 case MegaRequest::TYPE_IMPORT_LINK:
340 mApi[apiIndex].h = request->getNodeHandle();
341 break;
342
343 case MegaRequest::TYPE_GET_ATTR_USER:
344 if ( (mApi[apiIndex].lastError == API_OK) && (request->getParamType() != MegaApi::USER_ATTR_AVATAR) )
345 {
346 attributeValue = request->getText();
347 }
348
349 if (request->getParamType() == MegaApi::USER_ATTR_AVATAR)
350 {
351 if (mApi[apiIndex].lastError == API_OK)
352 {
353 attributeValue = "Avatar changed";
354 }
355
356 if (mApi[apiIndex].lastError == API_ENOENT)
357 {
358 attributeValue = "Avatar not found";
359 }
360 }
361 break;
362
363 #ifdef ENABLE_CHAT
364
365 case MegaRequest::TYPE_CHAT_CREATE:
366 if (mApi[apiIndex].lastError == API_OK)
367 {
368 MegaTextChat *chat = request->getMegaTextChatList()->get(0)->copy();
369
370 mApi[apiIndex].chatid = chat->getHandle();
371 mApi[apiIndex].chats[mApi[apiIndex].chatid].reset(chat);
372 }
373 break;
374
375 case MegaRequest::TYPE_CHAT_INVITE:
376 if (mApi[apiIndex].lastError == API_OK)
377 {
378 mApi[apiIndex].chatid = request->getNodeHandle();
379 if (mApi[apiIndex].chats.find(mApi[apiIndex].chatid) != mApi[apiIndex].chats.end())
380 {
381 MegaTextChat *chat = mApi[apiIndex].chats[mApi[apiIndex].chatid].get();
382 MegaHandle uh = request->getParentHandle();
383 int priv = request->getAccess();
384 unique_ptr<userpriv_vector> privsbuf{new userpriv_vector};
385
386 const MegaTextChatPeerList *privs = chat->getPeerList();
387 if (privs)
388 {
389 for (int i = 0; i < privs->size(); i++)
390 {
391 if (privs->getPeerHandle(i) != uh)
392 {
393 privsbuf->push_back(userpriv_pair(privs->getPeerHandle(i), (privilege_t) privs->getPeerPrivilege(i)));
394 }
395 }
396 }
397 privsbuf->push_back(userpriv_pair(uh, (privilege_t) priv));
398 privs = new MegaTextChatPeerListPrivate(privsbuf.get());
399 chat->setPeerList(privs);
400 delete privs;
401 }
402 else
403 {
404 LOG_err << "Trying to remove a peer from unknown chat";
405 }
406 }
407 break;
408
409 case MegaRequest::TYPE_CHAT_REMOVE:
410 if (mApi[apiIndex].lastError == API_OK)
411 {
412 mApi[apiIndex].chatid = request->getNodeHandle();
413 if (mApi[apiIndex].chats.find(mApi[apiIndex].chatid) != mApi[apiIndex].chats.end())
414 {
415 MegaTextChat *chat = mApi[apiIndex].chats[mApi[apiIndex].chatid].get();
416 MegaHandle uh = request->getParentHandle();
417 std::unique_ptr<userpriv_vector> privsbuf{new userpriv_vector};
418
419 const MegaTextChatPeerList *privs = chat->getPeerList();
420 if (privs)
421 {
422 for (int i = 0; i < privs->size(); i++)
423 {
424 if (privs->getPeerHandle(i) != uh)
425 {
426 privsbuf->push_back(userpriv_pair(privs->getPeerHandle(i), (privilege_t) privs->getPeerPrivilege(i)));
427 }
428 }
429 }
430 privs = new MegaTextChatPeerListPrivate(privsbuf.get());
431 chat->setPeerList(privs);
432 delete privs;
433 }
434 else
435 {
436 LOG_err << "Trying to remove a peer from unknown chat";
437 }
438 }
439 break;
440
441 case MegaRequest::TYPE_CHAT_URL:
442 if (mApi[apiIndex].lastError == API_OK)
443 {
444 link.assign(request->getLink());
445 }
446 break;
447 #endif
448
449 case MegaRequest::TYPE_CREATE_ACCOUNT:
450 if (mApi[apiIndex].lastError == API_OK)
451 {
452 sid = request->getSessionKey();
453 }
454 break;
455
456 case MegaRequest::TYPE_FETCH_NODES:
457 if (apiIndex == 0)
458 {
459 megaApi[0]->enableTransferResumption();
460 }
461 break;
462
463 case MegaRequest::TYPE_GET_REGISTERED_CONTACTS:
464 if (mApi[apiIndex].lastError == API_OK)
465 {
466 stringTable.reset(request->getMegaStringTable()->copy());
467 }
468 break;
469
470 case MegaRequest::TYPE_GET_COUNTRY_CALLING_CODES:
471 if (mApi[apiIndex].lastError == API_OK)
472 {
473 stringListMap.reset(request->getMegaStringListMap()->copy());
474 }
475 break;
476
477 }
478 }
479
onTransferFinish(MegaApi * api,MegaTransfer * transfer,MegaError * e)480 void SdkTest::onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError* e)
481 {
482 int apiIndex = getApiIndex(api);
483 if (apiIndex < 0) return;
484
485 mApi[apiIndex].transferFlags[transfer->getType()] = true;
486 mApi[apiIndex].lastError = e->getErrorCode(); // todo: change the rest of the transfer test code to use lastTransferError instead.
487 mApi[apiIndex].lastTransferError = e->getErrorCode();
488
489 // there could be a race on these getting set?
490 LOG_info << "lastError (by transfer) for MegaApi " << apiIndex << ": " << mApi[apiIndex].lastError;
491
492 onTranferFinishedCount += 1;
493
494 if (mApi[apiIndex].lastError == MegaError::API_OK)
495 mApi[apiIndex].h = transfer->getNodeHandle();
496 }
497
onTransferUpdate(MegaApi * api,MegaTransfer * transfer)498 void SdkTest::onTransferUpdate(MegaApi *api, MegaTransfer *transfer)
499 {
500 onTransferUpdate_progress = transfer->getTransferredBytes();
501 onTransferUpdate_filesize = transfer->getTotalBytes();
502 }
503
onAccountUpdate(MegaApi * api)504 void SdkTest::onAccountUpdate(MegaApi* api)
505 {
506 int apiIndex = getApiIndex(api);
507 if (apiIndex < 0) return;
508
509 mApi[apiIndex].accountUpdated = true;
510 }
511
onUsersUpdate(MegaApi * api,MegaUserList * users)512 void SdkTest::onUsersUpdate(MegaApi* api, MegaUserList *users)
513 {
514 int apiIndex = getApiIndex(api);
515 if (apiIndex < 0) return;
516
517 if (!users)
518 return;
519
520 for (int i = 0; i < users->size(); i++)
521 {
522 MegaUser *u = users->get(i);
523
524 if (u->hasChanged(MegaUser::CHANGE_TYPE_AVATAR)
525 || u->hasChanged(MegaUser::CHANGE_TYPE_FIRSTNAME)
526 || u->hasChanged(MegaUser::CHANGE_TYPE_LASTNAME))
527 {
528 mApi[apiIndex].userUpdated = true;
529 }
530 else
531 {
532 // Contact is removed from main account
533 mApi[apiIndex].requestFlags[MegaRequest::TYPE_REMOVE_CONTACT] = true;
534 mApi[apiIndex].userUpdated = true;
535 }
536 }
537 }
538
onNodesUpdate(MegaApi * api,MegaNodeList * nodes)539 void SdkTest::onNodesUpdate(MegaApi* api, MegaNodeList *nodes)
540 {
541 int apiIndex = getApiIndex(api);
542 if (apiIndex < 0) return;
543
544 mApi[apiIndex].nodeUpdated = true;
545 }
546
onContactRequestsUpdate(MegaApi * api,MegaContactRequestList * requests)547 void SdkTest::onContactRequestsUpdate(MegaApi* api, MegaContactRequestList* requests)
548 {
549 int apiIndex = getApiIndex(api);
550 if (apiIndex < 0) return;
551
552 mApi[apiIndex].contactRequestUpdated = true;
553 }
554
555 #ifdef ENABLE_CHAT
onChatsUpdate(MegaApi * api,MegaTextChatList * chats)556 void SdkTest::onChatsUpdate(MegaApi *api, MegaTextChatList *chats)
557 {
558 int apiIndex = getApiIndex(api);
559 if (apiIndex < 0) return;
560
561 MegaTextChatList *list = NULL;
562 if (chats)
563 {
564 list = chats->copy();
565 }
566 else
567 {
568 list = megaApi[apiIndex]->getChatList();
569 }
570 for (int i = 0; i < list->size(); i++)
571 {
572 handle chatid = list->get(i)->getHandle();
573 if (mApi[apiIndex].chats.find(chatid) != mApi[apiIndex].chats.end())
574 {
575 mApi[apiIndex].chats[chatid].reset(list->get(i)->copy());
576 }
577 else
578 {
579 mApi[apiIndex].chats[chatid].reset(list->get(i)->copy());
580 }
581 }
582 delete list;
583
584 mApi[apiIndex].chatUpdated = true;
585 }
586
createChat(bool group,MegaTextChatPeerList * peers,int timeout)587 void SdkTest::createChat(bool group, MegaTextChatPeerList *peers, int timeout)
588 {
589 int apiIndex = 0;
590 mApi[apiIndex].requestFlags[MegaRequest::TYPE_CHAT_CREATE] = false;
591 megaApi[0]->createChat(group, peers);
592 waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_CHAT_CREATE], timeout);
593 if (timeout)
594 {
595 ASSERT_TRUE(mApi[apiIndex].requestFlags[MegaRequest::TYPE_CHAT_CREATE]) << "Chat creation not finished after " << timeout << " seconds";
596 }
597
598 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Chat creation failed (error: " << mApi[apiIndex].lastError << ")";
599 }
600
601 #endif
602
onEvent(MegaApi *,MegaEvent * event)603 void SdkTest::onEvent(MegaApi*, MegaEvent *event)
604 {
605 std::lock_guard<std::mutex> lock{lastEventMutex};
606 lastEvent.reset(event->copy());
607 }
608
login(unsigned int apiIndex,int timeout)609 void SdkTest::login(unsigned int apiIndex, int timeout)
610 {
611 mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGIN] = false;
612 mApi[apiIndex].megaApi->login(mApi[apiIndex].email.data(), mApi[apiIndex].pwd.data());
613
614 ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGIN], timeout))
615 << "Login failed after " << timeout << " seconds";
616 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Login failed (error: " << mApi[apiIndex].lastError << ")";
617 ASSERT_TRUE(mApi[apiIndex].megaApi->isLoggedIn());
618 }
619
loginBySessionId(unsigned int apiIndex,const std::string & sessionId,int timeout)620 void SdkTest::loginBySessionId(unsigned int apiIndex, const std::string& sessionId, int timeout)
621 {
622 mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGIN] = false;
623 mApi[apiIndex].megaApi->fastLogin(sessionId.c_str());
624
625 ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGIN], timeout))
626 << "Login failed after " << timeout << " seconds";
627 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Login failed (error: " << mApi[apiIndex].lastError << ")";
628 ASSERT_TRUE(mApi[apiIndex].megaApi->isLoggedIn());
629 }
630
fetchnodes(unsigned int apiIndex,int timeout,bool resumeSyncs)631 void SdkTest::fetchnodes(unsigned int apiIndex, int timeout, bool resumeSyncs)
632 {
633 mApi[apiIndex].requestFlags[MegaRequest::TYPE_FETCH_NODES] = false;
634 if (resumeSyncs)
635 {
636 mApi[apiIndex].megaApi->fetchNodesAndResumeSyncs();
637 }
638 else
639 {
640 mApi[apiIndex].megaApi->fetchNodes();
641 }
642
643 ASSERT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_FETCH_NODES], timeout) )
644 << "Fetchnodes failed after " << timeout << " seconds";
645 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Fetchnodes failed (error: " << mApi[apiIndex].lastError << ")";
646 }
647
logout(unsigned int apiIndex,int timeout)648 void SdkTest::logout(unsigned int apiIndex, int timeout)
649 {
650 mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT] = false;
651 mApi[apiIndex].megaApi->logout(this);
652
653 EXPECT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT], timeout) )
654 << "Logout failed after " << timeout << " seconds";
655
656 // if the connection was closed before the response of the request was received, the result is ESID
657 if (mApi[apiIndex].lastError == MegaError::API_ESID) mApi[apiIndex].lastError = MegaError::API_OK;
658
659 EXPECT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Logout failed (error: " << mApi[apiIndex].lastError << ")";
660 }
661
dumpSession()662 char* SdkTest::dumpSession()
663 {
664 return megaApi[0]->dumpSession();
665 }
666
locallogout(int timeout)667 void SdkTest::locallogout(int timeout)
668 {
669 int apiIndex = 0;
670 mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT] = false;
671 megaApi[apiIndex]->localLogout(this);
672
673 EXPECT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_LOGOUT], timeout) )
674 << "Local logout failed after " << timeout << " seconds";
675 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Local logout failed (error: " << mApi[apiIndex].lastError << ")";
676 }
677
resumeSession(const char * session,int timeout)678 void SdkTest::resumeSession(const char *session, int timeout)
679 {
680 int apiIndex = 0;
681 ASSERT_EQ(MegaError::API_OK, synchronousFastLogin(apiIndex, session, this)) << "Resume session failed (error: " << mApi[apiIndex].lastError << ")";
682 }
683
purgeTree(MegaNode * p,bool depthfirst)684 void SdkTest::purgeTree(MegaNode *p, bool depthfirst)
685 {
686 int apiIndex = 0;
687 std::unique_ptr<MegaNodeList> children{megaApi[0]->getChildren(p)};
688
689 for (int i = 0; i < children->size(); i++)
690 {
691 MegaNode *n = children->get(i);
692
693 // removing the folder removes the children anyway
694 if (depthfirst && n->isFolder())
695 purgeTree(n);
696
697 ASSERT_EQ(MegaError::API_OK, synchronousRemove(apiIndex, n)) << "Remove node operation failed (error: " << mApi[apiIndex].lastError << ")";
698 }
699 }
700
waitForResponse(bool * responseReceived,unsigned int timeout)701 bool SdkTest::waitForResponse(bool *responseReceived, unsigned int timeout)
702 {
703 timeout *= 1000000; // convert to micro-seconds
704 unsigned int tWaited = 0; // microseconds
705 bool connRetried = false;
706 while(!(*responseReceived))
707 {
708 WaitMillisec(pollingT / 1000);
709
710 if (timeout)
711 {
712 tWaited += pollingT;
713 if (tWaited >= timeout)
714 {
715 return false; // timeout is expired
716 }
717 // if no response after 2 minutes...
718 else if (!connRetried && tWaited > (pollingT * 240))
719 {
720 megaApi[0]->retryPendingConnections(true);
721 if (megaApi[1] && megaApi[1]->isLoggedIn())
722 {
723 megaApi[1]->retryPendingConnections(true);
724 }
725 connRetried = true;
726 }
727 }
728 }
729
730 return true; // response is received
731 }
732
synchronousTransfer(unsigned apiIndex,int type,std::function<void ()> f,unsigned int timeout)733 bool SdkTest::synchronousTransfer(unsigned apiIndex, int type, std::function<void()> f, unsigned int timeout)
734 {
735 auto& flag = mApi[apiIndex].transferFlags[type];
736 flag = false;
737 f();
738 auto result = waitForResponse(&flag, timeout);
739 EXPECT_TRUE(result) << "Transfer (type " << type << ") not finished yet after " << timeout << " seconds";
740 if (!result) mApi[apiIndex].lastError = -999; // local timeout
741 if (!result) mApi[apiIndex].lastTransferError = -999; // local timeout TODO: switch all transfer code to use lastTransferError . Some still uses lastError
742 return result;
743 }
744
synchronousRequest(unsigned apiIndex,int type,std::function<void ()> f,unsigned int timeout)745 bool SdkTest::synchronousRequest(unsigned apiIndex, int type, std::function<void()> f, unsigned int timeout)
746 {
747 auto& flag = mApi[apiIndex].requestFlags[type];
748 flag = false;
749 f();
750 auto result = waitForResponse(&flag, timeout);
751 EXPECT_TRUE(result) << "Request (type " << type << ") failed after " << timeout << " seconds";
752 if (!result) mApi[apiIndex].lastError = -999;
753 return result;
754 }
755
createFile(string filename,bool largeFile)756 void SdkTest::createFile(string filename, bool largeFile)
757 {
758 FILE *fp;
759 fp = fopen(filename.c_str(), "w");
760
761 if (fp)
762 {
763 int limit = 2000;
764
765 // create a file large enough for long upload/download times (5-10MB)
766 if (largeFile)
767 limit = 1000000 + rand() % 1000000;
768
769 for (int i = 0; i < limit; i++)
770 {
771 fprintf(fp, "test ");
772 }
773
774 fclose(fp);
775 }
776 }
777
getFilesize(string filename)778 int64_t SdkTest::getFilesize(string filename)
779 {
780 struct stat stat_buf;
781 int rc = stat(filename.c_str(), &stat_buf);
782
783 return rc == 0 ? int64_t(stat_buf.st_size) : int64_t(-1);
784 }
785
deleteFile(string filename)786 void SdkTest::deleteFile(string filename)
787 {
788 remove(filename.c_str());
789 }
790
791
getMegaApiAux(unsigned index)792 void SdkTest::getMegaApiAux(unsigned index)
793 {
794 if (index >= megaApi.size())
795 {
796 megaApi.resize(index + 1);
797 mApi.resize(index + 1);
798 }
799 if (megaApi[index].get() == NULL)
800 {
801 string strIndex = index > 1 ? to_string(index) : "";
802 if (const char *buf = getenv(("MEGA_EMAIL_AUX" + strIndex).c_str()))
803 {
804 mApi[index].email.assign(buf);
805 }
806 ASSERT_LT((size_t) 0, mApi[index].email.length()) << "Set auxiliar username at the environment variable $MEGA_EMAIL_AUX" << strIndex;
807
808 if (const char* buf = getenv(("MEGA_PWD_AUX" + strIndex).c_str()))
809 {
810 mApi[index].pwd.assign(buf);
811 }
812 ASSERT_LT((size_t) 0, mApi[index].pwd.length()) << "Set the auxiliar password at the environment variable $MEGA_PWD_AUX" << strIndex;
813
814 megaApi[index].reset(new MegaApi(APP_KEY.c_str(), megaApiCacheFolder(index).c_str(), USER_AGENT.c_str(), THREADS_PER_MEGACLIENT));
815 mApi[index].megaApi = megaApi[index].get();
816
817 megaApi[index]->setLoggingName(to_string(index).c_str());
818 megaApi[index]->setLogLevel(MegaApi::LOG_LEVEL_DEBUG);
819 megaApi[index]->addListener(this); // TODO: really should be per api
820
821 ASSERT_NO_FATAL_FAILURE( login(index) );
822 ASSERT_NO_FATAL_FAILURE( fetchnodes(index) );
823 }
824 }
825
releaseMegaApi(unsigned int apiIndex)826 void SdkTest::releaseMegaApi(unsigned int apiIndex)
827 {
828 if (mApi.size() <= apiIndex)
829 {
830 return;
831 }
832
833 assert(megaApi[apiIndex].get() == mApi[apiIndex].megaApi);
834 if (mApi[apiIndex].megaApi)
835 {
836 if (mApi[apiIndex].megaApi->isLoggedIn())
837 {
838 ASSERT_NO_FATAL_FAILURE( logout(apiIndex) );
839 }
840
841 megaApi[apiIndex].reset();
842 mApi[apiIndex].megaApi = NULL;
843 }
844 }
845
inviteContact(string email,string message,int action)846 void SdkTest::inviteContact(string email, string message, int action)
847 {
848 int apiIndex = 0;
849 ASSERT_EQ(MegaError::API_OK, synchronousInviteContact(apiIndex, email.data(), message.data(), action)) << "Contact invitation failed";
850 }
851
replyContact(MegaContactRequest * cr,int action)852 void SdkTest::replyContact(MegaContactRequest *cr, int action)
853 {
854 int apiIndex = 1;
855 ASSERT_EQ(MegaError::API_OK, synchronousReplyContactRequest(apiIndex, cr, action)) << "Contact reply failed";
856 }
857
removeContact(string email,int timeout)858 void SdkTest::removeContact(string email, int timeout)
859 {
860 int apiIndex = 0;
861 MegaUser *u = megaApi[apiIndex]->getContact(email.data());
862 bool null_pointer = (u == NULL);
863 ASSERT_FALSE(null_pointer) << "Cannot find the specified contact (" << email << ")";
864
865 if (u->getVisibility() != MegaUser::VISIBILITY_VISIBLE)
866 {
867 mApi[apiIndex].userUpdated = true; // nothing to do
868 delete u;
869 return;
870 }
871
872 ASSERT_EQ(MegaError::API_OK, synchronousRemoveContact(apiIndex, u)) << "Contact deletion failed";
873
874 delete u;
875 }
876
shareFolder(MegaNode * n,const char * email,int action,int timeout)877 void SdkTest::shareFolder(MegaNode *n, const char *email, int action, int timeout)
878 {
879 int apiIndex = 0;
880 ASSERT_EQ(MegaError::API_OK, synchronousShare(apiIndex, n, email, action)) << "Folder sharing failed" << endl << "User: " << email << " Action: " << action;
881 }
882
createPublicLink(unsigned apiIndex,MegaNode * n,m_time_t expireDate,int timeout)883 void SdkTest::createPublicLink(unsigned apiIndex, MegaNode *n, m_time_t expireDate, int timeout)
884 {
885 mApi[apiIndex].requestFlags[MegaRequest::TYPE_EXPORT] = false;
886
887 auto err = synchronousExportNode(apiIndex, n, expireDate);
888
889 if (!expireDate)
890 {
891 ASSERT_EQ(MegaError::API_OK, err) << "Public link creation failed (error: " << mApi[apiIndex].lastError << ")";
892 }
893 else
894 {
895 bool res = MegaError::API_OK != err && err != -999;
896 ASSERT_TRUE(res) << "Public link creation with expire time on free account (" << mApi[apiIndex].email << ") succeed, and it mustn't";
897 }
898 }
899
importPublicLink(unsigned apiIndex,string link,MegaNode * parent,int timeout)900 void SdkTest::importPublicLink(unsigned apiIndex, string link, MegaNode *parent, int timeout)
901 {
902 mApi[apiIndex].requestFlags[MegaRequest::TYPE_IMPORT_LINK] = false;
903 mApi[apiIndex].megaApi->importFileLink(link.data(), parent);
904
905 ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_IMPORT_LINK], timeout) )
906 << "Public link import not finished after " << timeout << " seconds";
907 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Public link import failed (error: " << mApi[apiIndex].lastError << ")";
908 }
909
getPublicNode(unsigned apiIndex,string link,int timeout)910 void SdkTest::getPublicNode(unsigned apiIndex, string link, int timeout)
911 {
912 mApi[apiIndex].requestFlags[MegaRequest::TYPE_GET_PUBLIC_NODE] = false;
913 mApi[apiIndex].megaApi->getPublicNode(link.data());
914
915 ASSERT_TRUE(waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_GET_PUBLIC_NODE], timeout) )
916 << "Public link retrieval not finished after " << timeout << " seconds";
917 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Public link retrieval failed (error: " << mApi[apiIndex].lastError << ")";
918 }
919
removePublicLink(unsigned apiIndex,MegaNode * n,int timeout)920 void SdkTest::removePublicLink(unsigned apiIndex, MegaNode *n, int timeout)
921 {
922 mApi[apiIndex].requestFlags[MegaRequest::TYPE_EXPORT] = false;
923 mApi[apiIndex].megaApi->disableExport(n);
924
925 ASSERT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_EXPORT], timeout) )
926 << "Public link removal not finished after " << timeout << " seconds";
927 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Public link removal failed (error: " << mApi[apiIndex].lastError << ")";
928 }
929
getContactRequest(unsigned int apiIndex,bool outgoing,int expectedSize)930 void SdkTest::getContactRequest(unsigned int apiIndex, bool outgoing, int expectedSize)
931 {
932 MegaContactRequestList *crl;
933
934 if (outgoing)
935 {
936 crl = mApi[apiIndex].megaApi->getOutgoingContactRequests();
937 ASSERT_EQ(expectedSize, crl->size()) << "Too many outgoing contact requests in account " << apiIndex;
938 if (expectedSize)
939 mApi[apiIndex].cr.reset(crl->get(0)->copy());
940 }
941 else
942 {
943 crl = mApi[apiIndex].megaApi->getIncomingContactRequests();
944 ASSERT_EQ(expectedSize, crl->size()) << "Too many incoming contact requests in account " << apiIndex;
945 if (expectedSize)
946 mApi[apiIndex].cr.reset(crl->get(0)->copy());
947 }
948
949 delete crl;
950 }
951
createFolder(unsigned int apiIndex,const char * name,MegaNode * n,int timeout)952 void SdkTest::createFolder(unsigned int apiIndex, const char *name, MegaNode *n, int timeout)
953 {
954 mApi[apiIndex].requestFlags[MegaRequest::TYPE_CREATE_FOLDER] = false;
955 mApi[apiIndex].megaApi->createFolder(name, n);
956
957 ASSERT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_CREATE_FOLDER], timeout) )
958 << "Folder creation failed after " << timeout << " seconds";
959 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "Cannot create a folder (error: " << mApi[apiIndex].lastError << ")";
960 }
961
getRegisteredContacts(const std::map<std::string,std::string> & contacts)962 void SdkTest::getRegisteredContacts(const std::map<std::string, std::string>& contacts)
963 {
964 int apiIndex = 0;
965
966 auto contactsStringMap = std::unique_ptr<MegaStringMap>{MegaStringMap::createInstance()};
967 for (const auto& pair : contacts)
968 {
969 contactsStringMap->set(pair.first.c_str(), pair.second.c_str());
970 }
971
972 ASSERT_EQ(MegaError::API_OK, synchronousGetRegisteredContacts(apiIndex, contactsStringMap.get(), this)) << "Get registered contacts failed";
973 }
974
getCountryCallingCodes(const int timeout)975 void SdkTest::getCountryCallingCodes(const int timeout)
976 {
977 int apiIndex = 0;
978 ASSERT_EQ(MegaError::API_OK, synchronousGetCountryCallingCodes(apiIndex, this)) << "Get country calling codes failed";
979 }
980
setUserAttribute(int type,string value,int timeout)981 void SdkTest::setUserAttribute(int type, string value, int timeout)
982 {
983 int apiIndex = 0;
984 mApi[apiIndex].requestFlags[MegaRequest::TYPE_SET_ATTR_USER] = false;
985
986 if (type == MegaApi::USER_ATTR_AVATAR)
987 {
988 megaApi[apiIndex]->setAvatar(value.empty() ? NULL : value.c_str());
989 }
990 else
991 {
992 megaApi[apiIndex]->setUserAttribute(type, value.c_str());
993 }
994
995 ASSERT_TRUE( waitForResponse(&mApi[apiIndex].requestFlags[MegaRequest::TYPE_SET_ATTR_USER], timeout) )
996 << "User attribute setup not finished after " << timeout << " seconds";
997 ASSERT_EQ(MegaError::API_OK, mApi[apiIndex].lastError) << "User attribute setup failed (error: " << mApi[apiIndex].lastError << ")";
998 }
999
getUserAttribute(MegaUser * u,int type,int timeout,int apiIndex)1000 void SdkTest::getUserAttribute(MegaUser *u, int type, int timeout, int apiIndex)
1001 {
1002 mApi[apiIndex].requestFlags[MegaRequest::TYPE_GET_ATTR_USER] = false;
1003
1004 int err;
1005 if (type == MegaApi::USER_ATTR_AVATAR)
1006 {
1007 err = synchronousGetUserAvatar(apiIndex, u, AVATARDST.data());
1008 }
1009 else
1010 {
1011 err = synchronousGetUserAttribute(apiIndex, u, type);
1012 }
1013 bool result = (err == MegaError::API_OK) || (err == MegaError::API_ENOENT);
1014 ASSERT_TRUE(result) << "User attribute retrieval failed (error: " << err << ")";
1015 }
1016
1017 ///////////////////////////__ Tests using SdkTest __//////////////////////////////////
1018
1019 /**
1020 * @brief TEST_F SdkTestCreateAccount
1021 *
1022 * It tests the creation of a new account for a random user.
1023 * - Create account and send confirmation link
1024 * - Logout and resume the create-account process
1025 * - Send the confirmation link to a different email address
1026 * - Wait for confirmation of account by a different client
1027 */
TEST_F(SdkTest,DISABLED_SdkTestCreateAccount)1028 TEST_F(SdkTest, DISABLED_SdkTestCreateAccount)
1029 {
1030 string email1 = "user@domain.com";
1031 string pwd = "pwd";
1032 string email2 = "other-user@domain.com";
1033
1034 LOG_info << "___TEST Create account___";
1035
1036 // Create an ephemeral session internally and send a confirmation link to email
1037 ASSERT_TRUE(synchronousCreateAccount(0, email1.c_str(), pwd.c_str(), "MyFirstname", "MyLastname"))
1038 << "Account creation has failed after " << maxTimeout << " seconds";
1039 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Account creation failed (error: " << mApi[0].lastError << ")";
1040
1041 // Logout from ephemeral session and resume session
1042 ASSERT_NO_FATAL_FAILURE( locallogout() );
1043 ASSERT_TRUE(synchronousResumeCreateAccount(0, sid.c_str()))
1044 << "Account creation has failed after " << maxTimeout << " seconds";
1045 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Account creation failed (error: " << mApi[0].lastError << ")";
1046
1047 // Send the confirmation link to a different email address
1048 ASSERT_TRUE(synchronousSendSignupLink(0, email2.c_str(), "MyFirstname", pwd.c_str()))
1049 << "Send confirmation link to another email failed after " << maxTimeout << " seconds";
1050 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Send confirmation link to another email address failed (error: " << mApi[0].lastError << ")";
1051
1052 // Now, confirm the account by using a different client...
1053
1054 // ...and wait for the AP notifying the confirmation
1055 bool *flag = &mApi[0].accountUpdated; *flag = false;
1056 ASSERT_TRUE( waitForResponse(flag) )
1057 << "Account confirmation not received after " << maxTimeout << " seconds";
1058 }
1059
veryclose(double a,double b)1060 bool veryclose(double a, double b)
1061 {
1062 double diff = b - a;
1063 double denom = fabs(a) + fabs(b);
1064 if (denom == 0)
1065 {
1066 return diff == 0;
1067 }
1068 double ratio = fabs(diff / denom);
1069 return ratio * 1000000 < 1;
1070 }
1071
1072 /**
1073 * @brief TEST_F SdkTestNodeAttributes
1074 *
1075 *
1076 */
TEST_F(SdkTest,SdkTestNodeAttributes)1077 TEST_F(SdkTest, SdkTestNodeAttributes)
1078 {
1079 LOG_info << "___TEST Node attributes___";
1080
1081 std::unique_ptr<MegaNode> rootnode{megaApi[0]->getRootNode()};
1082
1083 string filename1 = UPFILE;
1084 createFile(filename1, false);
1085
1086 ASSERT_EQ(MegaError::API_OK, synchronousStartUpload(0, filename1.data(), rootnode.get())) << "Cannot upload a test file";
1087
1088 MegaNode *n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1089 bool null_pointer = (n1 == NULL);
1090 ASSERT_FALSE(null_pointer) << "Cannot initialize test scenario (error: " << mApi[0].lastError << ")";
1091
1092
1093 // ___ Set invalid duration of a node ___
1094
1095 gTestingInvalidArgs = true;
1096
1097 ASSERT_EQ(MegaError::API_EARGS, synchronousSetNodeDuration(0, n1, -14)) << "Unexpected error setting invalid node duration";
1098
1099 gTestingInvalidArgs = false;
1100
1101
1102 // ___ Set duration of a node ___
1103
1104 ASSERT_EQ(MegaError::API_OK, synchronousSetNodeDuration(0, n1, 929734)) << "Cannot set node duration";
1105
1106 delete n1;
1107 n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1108 ASSERT_EQ(929734, n1->getDuration()) << "Duration value does not match";
1109
1110
1111 // ___ Reset duration of a node ___
1112
1113 ASSERT_EQ(MegaError::API_OK, synchronousSetNodeDuration(0, n1, -1)) << "Cannot reset node duration";
1114
1115 delete n1;
1116 n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1117 ASSERT_EQ(-1, n1->getDuration()) << "Duration value does not match";
1118
1119
1120 // ___ Set invalid coordinates of a node (out of range) ___
1121
1122 gTestingInvalidArgs = true;
1123
1124 ASSERT_EQ(MegaError::API_EARGS, synchronousSetNodeCoordinates(0, n1, -1523421.8719987255814, +6349.54)) << "Unexpected error setting invalid node coordinates";
1125
1126
1127 // ___ Set invalid coordinates of a node (out of range) ___
1128
1129 ASSERT_EQ(MegaError::API_EARGS, synchronousSetNodeCoordinates(0, n1, -160.8719987255814, +49.54)) << "Unexpected error setting invalid node coordinates";
1130
1131
1132 // ___ Set invalid coordinates of a node (out of range) ___
1133
1134 ASSERT_EQ(MegaError::API_EARGS, synchronousSetNodeCoordinates(0, n1, MegaNode::INVALID_COORDINATE, +69.54)) << "Unexpected error trying to reset only one coordinate";
1135
1136 gTestingInvalidArgs = false;
1137
1138 // ___ Set coordinates of a node ___
1139
1140 double lat = -51.8719987255814;
1141 double lon = +179.54;
1142
1143 ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, lat, lon)) << "Cannot set node coordinates";
1144
1145 delete n1;
1146 n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1147
1148 // do same conversions to lose the same precision
1149 int buf = int(((lat + 90) / 180) * 0xFFFFFF);
1150 double res = -90 + 180 * (double) buf / 0xFFFFFF;
1151
1152 ASSERT_EQ(res, n1->getLatitude()) << "Latitude value does not match";
1153
1154 buf = int((lon == 180) ? 0 : (lon + 180) / 360 * 0x01000000);
1155 res = -180 + 360 * (double) buf / 0x01000000;
1156
1157 ASSERT_EQ(res, n1->getLongitude()) << "Longitude value does not match";
1158
1159
1160 // ___ Set coordinates of a node to origin (0,0) ___
1161
1162 lon = 0;
1163 lat = 0;
1164
1165 ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, 0, 0)) << "Cannot set node coordinates";
1166
1167 delete n1;
1168 n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1169
1170 // do same conversions to lose the same precision
1171 buf = int(((lat + 90) / 180) * 0xFFFFFF);
1172 res = -90 + 180 * (double) buf / 0xFFFFFF;
1173
1174 ASSERT_EQ(res, n1->getLatitude()) << "Latitude value does not match";
1175 ASSERT_EQ(lon, n1->getLongitude()) << "Longitude value does not match";
1176
1177
1178 // ___ Set coordinates of a node to border values (90,180) ___
1179
1180 lat = 90;
1181 lon = 180;
1182
1183 ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, lat, lon)) << "Cannot set node coordinates";
1184
1185 delete n1;
1186 n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1187
1188 ASSERT_EQ(lat, n1->getLatitude()) << "Latitude value does not match";
1189 bool value_ok = ((n1->getLongitude() == lon) || (n1->getLongitude() == -lon));
1190 ASSERT_TRUE(value_ok) << "Longitude value does not match";
1191
1192
1193 // ___ Set coordinates of a node to border values (-90,-180) ___
1194
1195 lat = -90;
1196 lon = -180;
1197
1198 ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, lat, lon)) << "Cannot set node coordinates";
1199
1200 delete n1;
1201 n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1202
1203 ASSERT_EQ(lat, n1->getLatitude()) << "Latitude value does not match";
1204 value_ok = ((n1->getLongitude() == lon) || (n1->getLongitude() == -lon));
1205 ASSERT_TRUE(value_ok) << "Longitude value does not match";
1206
1207
1208 // ___ Reset coordinates of a node ___
1209
1210 lat = lon = MegaNode::INVALID_COORDINATE;
1211
1212 synchronousSetNodeCoordinates(0, n1, lat, lon);
1213
1214 delete n1;
1215 n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1216 ASSERT_EQ(lat, n1->getLatitude()) << "Latitude value does not match";
1217 ASSERT_EQ(lon, n1->getLongitude()) << "Longitude value does not match";
1218
1219
1220 // ****************** also test shareable / unshareable versions:
1221
1222 // ___ set the coords (shareable)
1223 lat = -51.8719987255814;
1224 lon = +179.54;
1225 ASSERT_EQ(MegaError::API_OK, synchronousSetNodeCoordinates(0, n1, lat, lon)) << "Cannot set node coordinates";
1226
1227 // ___ get a link to the file node
1228 ASSERT_NO_FATAL_FAILURE(createPublicLink(0, n1));
1229 // The created link is stored in this->link at onRequestFinish()
1230 string nodelink = this->link;
1231
1232 // ___ log in to the other account
1233 ASSERT_NO_FATAL_FAILURE(getMegaApiAux()); // login + fetchnodes
1234
1235 // ___ import the link
1236 ASSERT_NO_FATAL_FAILURE(importPublicLink(1, nodelink, std::unique_ptr<MegaNode>{megaApi[1]->getRootNode()}.get()));
1237 std::unique_ptr<MegaNode> nimported{megaApi[1]->getNodeByHandle(mApi[1].h)};
1238
1239 ASSERT_TRUE(veryclose(lat, nimported->getLatitude())) << "Latitude " << n1->getLatitude() << " value does not match " << lat;
1240 ASSERT_TRUE(veryclose(lon, nimported->getLongitude())) << "Longitude " << n1->getLongitude() << " value does not match " << lon;
1241
1242 // ___ remove the imported node, for a clean next test
1243 mApi[1].requestFlags[MegaRequest::TYPE_REMOVE] = false;
1244 megaApi[1]->remove(nimported.get());
1245 ASSERT_TRUE(waitForResponse(&mApi[1].requestFlags[MegaRequest::TYPE_REMOVE]))
1246 << "Remove operation failed after " << maxTimeout << " seconds";
1247 ASSERT_EQ(MegaError::API_OK, mApi[1].lastError) << "Cannot remove a node (error: " << mApi[1].lastError << ")";
1248
1249
1250 // ___ again but unshareable this time - totally separate new node - set the coords (unshareable)
1251
1252 string filename2 = "a"+UPFILE;
1253 createFile(filename2, false);
1254 ASSERT_EQ(MegaError::API_OK, synchronousStartUpload(0, filename2.data(), rootnode.get())) << "Cannot upload a test file";
1255 MegaNode *n2 = megaApi[0]->getNodeByHandle(mApi[0].h);
1256 ASSERT_NE(n2, ((void*)NULL)) << "Cannot initialize second node for scenario (error: " << mApi[0].lastError << ")";
1257
1258 lat = -5 + -51.8719987255814;
1259 lon = -5 + +179.54;
1260 mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_NODE] = false;
1261 megaApi[0]->setUnshareableNodeCoordinates(n2, lat, lon);
1262 waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_NODE]);
1263 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot set unshareable node coordinates (error: " << mApi[0].lastError << ")";
1264
1265 // ___ confirm this user can read them
1266 MegaNode* selfread = megaApi[0]->getNodeByHandle(n2->getHandle());
1267 ASSERT_TRUE(veryclose(lat, selfread->getLatitude())) << "Latitude " << n2->getLatitude() << " value does not match " << lat;
1268 ASSERT_TRUE(veryclose(lon, selfread->getLongitude())) << "Longitude " << n2->getLongitude() << " value does not match " << lon;
1269
1270 // ___ get a link to the file node
1271 this->link.clear();
1272 ASSERT_NO_FATAL_FAILURE(createPublicLink(0, n2));
1273 // The created link is stored in this->link at onRequestFinish()
1274 string nodelink2 = this->link;
1275
1276 // ___ import the link
1277 ASSERT_NO_FATAL_FAILURE(importPublicLink(1, nodelink2, std::unique_ptr<MegaNode>{megaApi[1]->getRootNode()}.get()));
1278 nimported = std::unique_ptr<MegaNode>{megaApi[1]->getNodeByHandle(mApi[1].h)};
1279
1280 // ___ confirm other user cannot read them
1281 lat = nimported->getLatitude();
1282 lon = nimported->getLongitude();
1283 ASSERT_EQ(MegaNode::INVALID_COORDINATE, lat) << "Latitude value does not match";
1284 ASSERT_EQ(MegaNode::INVALID_COORDINATE, lon) << "Longitude value does not match";
1285 delete n1;
1286 }
1287
1288 /**
1289 * @brief TEST_F SdkTestResumeSession
1290 *
1291 * It creates a local cache, logs out of the current session and tries to resume it later.
1292 */
TEST_F(SdkTest,SdkTestResumeSession)1293 TEST_F(SdkTest, SdkTestResumeSession)
1294 {
1295 LOG_info << "___TEST Resume session___";
1296
1297 unique_ptr<char[]> session(dumpSession());
1298
1299 ASSERT_NO_FATAL_FAILURE( locallogout() );
1300 ASSERT_NO_FATAL_FAILURE( resumeSession(session.get()) );
1301 ASSERT_NO_FATAL_FAILURE( fetchnodes(0) );
1302 }
1303
1304 /**
1305 * @brief TEST_F SdkTestNodeOperations
1306 *
1307 * It performs different operations with nodes, assuming the Cloud folder is empty at the beginning.
1308 *
1309 * - Create a new folder
1310 * - Rename a node
1311 * - Copy a node
1312 * - Get child nodes of given node
1313 * - Get child node by name
1314 * - Get node by path
1315 * - Get node by name
1316 * - Move a node
1317 * - Get parent node
1318 * - Move a node to Rubbish bin
1319 * - Remove a node
1320 */
TEST_F(SdkTest,SdkTestNodeOperations)1321 TEST_F(SdkTest, SdkTestNodeOperations)
1322 {
1323 LOG_info << "___TEST Node operations___";
1324
1325 // --- Create a new folder ---
1326
1327 MegaNode *rootnode = megaApi[0]->getRootNode();
1328 char name1[64] = "New folder";
1329
1330 ASSERT_NO_FATAL_FAILURE( createFolder(0, name1, rootnode) );
1331
1332
1333 // --- Rename a node ---
1334
1335 MegaNode *n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1336 strcpy(name1, "Folder renamed");
1337
1338 mApi[0].requestFlags[MegaRequest::TYPE_RENAME] = false;
1339 megaApi[0]->renameNode(n1, name1);
1340 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_RENAME]) )
1341 << "Rename operation failed after " << maxTimeout << " seconds";
1342 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot rename a node (error: " << mApi[0].lastError << ")";
1343
1344
1345 // --- Copy a node ---
1346
1347 MegaNode *n2;
1348 char name2[64] = "Folder copy";
1349
1350 mApi[0].requestFlags[MegaRequest::TYPE_COPY] = false;
1351 megaApi[0]->copyNode(n1, rootnode, name2);
1352 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_COPY]) )
1353 << "Copy operation failed after " << maxTimeout << " seconds";
1354 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot create a copy of a node (error: " << mApi[0].lastError << ")";
1355 n2 = megaApi[0]->getNodeByHandle(mApi[0].h);
1356
1357
1358 // --- Get child nodes ---
1359
1360 MegaNodeList *children;
1361 children = megaApi[0]->getChildren(rootnode);
1362
1363 EXPECT_EQ(megaApi[0]->getNumChildren(rootnode), children->size()) << "Wrong number of child nodes";
1364 ASSERT_LE(2, children->size()) << "Wrong number of children nodes found";
1365 EXPECT_STREQ(name2, children->get(0)->getName()) << "Wrong name of child node"; // "Folder copy"
1366 EXPECT_STREQ(name1, children->get(1)->getName()) << "Wrong name of child node"; // "Folder rename"
1367
1368 delete children;
1369
1370
1371 // --- Get child node by name ---
1372
1373 MegaNode *n3;
1374 n3 = megaApi[0]->getChildNode(rootnode, name2);
1375
1376 bool null_pointer = (n3 == NULL);
1377 EXPECT_FALSE(null_pointer) << "Child node by name not found";
1378 // ASSERT_EQ(n2->getHandle(), n3->getHandle()); This test may fail due to multiple nodes with the same name
1379
1380
1381 // --- Get node by path ---
1382
1383 char path[128] = "/Folder copy";
1384 MegaNode *n4;
1385 n4 = megaApi[0]->getNodeByPath(path);
1386
1387 null_pointer = (n4 == NULL);
1388 EXPECT_FALSE(null_pointer) << "Node by path not found";
1389
1390
1391 // --- Search for a node ---
1392 MegaNodeList *nlist;
1393 nlist = megaApi[0]->search(rootnode, "copy");
1394
1395 ASSERT_EQ(1, nlist->size());
1396 EXPECT_EQ(n4->getHandle(), nlist->get(0)->getHandle()) << "Search node by pattern failed";
1397
1398 delete nlist;
1399
1400
1401 // --- Move a node ---
1402
1403 mApi[0].requestFlags[MegaRequest::TYPE_MOVE] = false;
1404 megaApi[0]->moveNode(n1, n2);
1405 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_MOVE]) )
1406 << "Move operation failed after " << maxTimeout << " seconds";
1407 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot move node (error: " << mApi[0].lastError << ")";
1408
1409
1410 // --- Get parent node ---
1411
1412 MegaNode *n5;
1413 n5 = megaApi[0]->getParentNode(n1);
1414
1415 ASSERT_EQ(n2->getHandle(), n5->getHandle()) << "Wrong parent node";
1416
1417
1418 // --- Send to Rubbish bin ---
1419
1420 mApi[0].requestFlags[MegaRequest::TYPE_MOVE] = false;
1421 megaApi[0]->moveNode(n2, megaApi[0]->getRubbishNode());
1422 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_MOVE]) )
1423 << "Move operation failed after " << maxTimeout << " seconds";
1424 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot move node to Rubbish bin (error: " << mApi[0].lastError << ")";
1425
1426
1427 // --- Remove a node ---
1428
1429 mApi[0].requestFlags[MegaRequest::TYPE_REMOVE] = false;
1430 megaApi[0]->remove(n2);
1431 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_REMOVE]) )
1432 << "Remove operation failed after " << maxTimeout << " seconds";
1433 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot remove a node (error: " << mApi[0].lastError << ")";
1434
1435 delete rootnode;
1436 delete n1;
1437 delete n2;
1438 delete n3;
1439 delete n4;
1440 delete n5;
1441 }
1442
1443 /**
1444 * @brief TEST_F SdkTestTransfers
1445 *
1446 * It performs different operations related to transfers in both directions: up and down.
1447 *
1448 * - Starts an upload transfer and cancel it
1449 * - Starts an upload transfer, pause it, resume it and complete it
1450 * - Get node by fingerprint
1451 * - Get size of a node
1452 * - Download a file
1453 */
TEST_F(SdkTest,SdkTestTransfers)1454 TEST_F(SdkTest, SdkTestTransfers)
1455 {
1456 LOG_info << "___TEST Transfers___";
1457
1458
1459 LOG_info << cwd();
1460
1461 MegaNode *rootnode = megaApi[0]->getRootNode();
1462 string filename1 = UPFILE;
1463 createFile(filename1);
1464
1465
1466 // --- Cancel a transfer ---
1467
1468 mApi[0].requestFlags[MegaRequest::TYPE_CANCEL_TRANSFERS] = false;
1469 megaApi[0]->startUpload(filename1.data(), rootnode);
1470 megaApi[0]->cancelTransfers(MegaTransfer::TYPE_UPLOAD);
1471 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CANCEL_TRANSFERS]) )
1472 << "Cancellation of transfers failed after " << maxTimeout << " seconds";
1473 EXPECT_EQ(MegaError::API_OK, mApi[0].lastError) << "Transfer cancellation failed (error: " << mApi[0].lastError << ")";
1474
1475
1476 // --- Upload a file (part 1) ---
1477
1478 mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD] = false;
1479 megaApi[0]->startUpload(filename1.data(), rootnode);
1480 // do not wait yet for completion
1481
1482
1483 // --- Pause a transfer ---
1484
1485 mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS] = false;
1486 megaApi[0]->pauseTransfers(true, MegaTransfer::TYPE_UPLOAD);
1487 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS]) )
1488 << "Pause of transfers failed after " << maxTimeout << " seconds";
1489 EXPECT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot pause transfer (error: " << mApi[0].lastError << ")";
1490 EXPECT_TRUE(megaApi[0]->areTransfersPaused(MegaTransfer::TYPE_UPLOAD)) << "Upload transfer not paused";
1491
1492
1493 // --- Resume a transfer ---
1494
1495 mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS] = false;
1496 megaApi[0]->pauseTransfers(false, MegaTransfer::TYPE_UPLOAD);
1497 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_PAUSE_TRANSFERS]) )
1498 << "Resumption of transfers after pause has failed after " << maxTimeout << " seconds";
1499 EXPECT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot resume transfer (error: " << mApi[0].lastError << ")";
1500 EXPECT_FALSE(megaApi[0]->areTransfersPaused(MegaTransfer::TYPE_UPLOAD)) << "Upload transfer not resumed";
1501
1502
1503 // --- Upload a file (part 2) ---
1504
1505
1506 ASSERT_TRUE( waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD], 600) )
1507 << "Upload transfer failed after " << 600 << " seconds";
1508 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot upload file (error: " << mApi[0].lastError << ")";
1509
1510 MegaNode *n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
1511 bool null_pointer = (n1 == NULL);
1512
1513 ASSERT_FALSE(null_pointer) << "Cannot upload file (error: " << mApi[0].lastError << ")";
1514 ASSERT_STREQ(filename1.data(), n1->getName()) << "Uploaded file with wrong name (error: " << mApi[0].lastError << ")";
1515
1516
1517 // --- Get node by fingerprint (needs to be a file, not a folder) ---
1518
1519 std::unique_ptr<char[]> fingerprint{megaApi[0]->getFingerprint(n1)};
1520 MegaNode *n2 = megaApi[0]->getNodeByFingerprint(fingerprint.get());
1521
1522 null_pointer = (n2 == NULL);
1523 EXPECT_FALSE(null_pointer) << "Node by fingerprint not found";
1524 // ASSERT_EQ(n2->getHandle(), n4->getHandle()); This test may fail due to multiple nodes with the same name
1525
1526 // --- Get the size of a file ---
1527
1528 int64_t filesize = getFilesize(filename1);
1529 int64_t nodesize = megaApi[0]->getSize(n2);
1530 EXPECT_EQ(filesize, nodesize) << "Wrong size of uploaded file";
1531
1532
1533 // --- Download a file ---
1534
1535 string filename2 = "./" + DOWNFILE;
1536
1537 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
1538 megaApi[0]->startDownload(n2, filename2.c_str());
1539 ASSERT_TRUE( waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600) )
1540 << "Download transfer failed after " << maxTimeout << " seconds";
1541 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")";
1542
1543 MegaNode *n3 = megaApi[0]->getNodeByHandle(mApi[0].h);
1544 null_pointer = (n3 == NULL);
1545
1546 ASSERT_FALSE(null_pointer) << "Cannot download node";
1547 ASSERT_EQ(n2->getHandle(), n3->getHandle()) << "Cannot download node (error: " << mApi[0].lastError << ")";
1548
1549
1550 // --- Upload a 0-bytes file ---
1551
1552 string filename3 = EMPTYFILE;
1553 FILE *fp = fopen(filename3.c_str(), "w");
1554 fclose(fp);
1555
1556 ASSERT_EQ(MegaError::API_OK, synchronousStartUpload(0, filename3.c_str(), rootnode)) << "Cannot upload a test file";
1557
1558 MegaNode *n4 = megaApi[0]->getNodeByHandle(mApi[0].h);
1559 null_pointer = (n4 == NULL);
1560
1561 ASSERT_FALSE(null_pointer) << "Cannot upload file (error: " << mApi[0].lastError << ")";
1562 ASSERT_STREQ(filename3.data(), n4->getName()) << "Uploaded file with wrong name (error: " << mApi[0].lastError << ")";
1563
1564
1565 // --- Download a 0-byte file ---
1566
1567 filename3 = "./" + EMPTYFILE;
1568 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
1569 megaApi[0]->startDownload(n4, filename3.c_str());
1570 ASSERT_TRUE( waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600) )
1571 << "Download 0-byte file failed after " << maxTimeout << " seconds";
1572 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")";
1573
1574 MegaNode *n5 = megaApi[0]->getNodeByHandle(mApi[0].h);
1575 null_pointer = (n5 == NULL);
1576
1577 ASSERT_FALSE(null_pointer) << "Cannot download node";
1578 ASSERT_EQ(n4->getHandle(), n5->getHandle()) << "Cannot download node (error: " << mApi[0].lastError << ")";
1579
1580
1581 delete rootnode;
1582 delete n1;
1583 delete n2;
1584 delete n3;
1585 delete n4;
1586 delete n5;
1587 }
1588
1589 /**
1590 * @brief TEST_F SdkTestContacts
1591 *
1592 * Creates an auxiliar 'MegaApi' object to interact with the main MEGA account.
1593 *
1594 * - Invite a contact
1595 * = Ignore the invitation
1596 * - Delete the invitation
1597 *
1598 * - Invite a contact
1599 * = Deny the invitation
1600 *
1601 * - Invite a contact
1602 * = Accept the invitation
1603 *
1604 * - Modify firstname
1605 * = Check firstname of a contact
1606 * = Set master key as exported
1607 * = Get preferred language
1608 * - Load avatar
1609 * = Check avatar of a contact
1610 * - Delete avatar
1611 * = Check non-existing avatar of a contact
1612 *
1613 * - Remove contact
1614 *
1615 * TODO:
1616 * - Invite a contact not registered in MEGA yet (requires validation of account)
1617 * - Remind an existing invitation (requires 2 weeks wait)
1618 */
TEST_F(SdkTest,SdkTestContacts)1619 TEST_F(SdkTest, SdkTestContacts)
1620 {
1621 LOG_info << "___TEST Contacts___";
1622
1623 ASSERT_NO_FATAL_FAILURE( getMegaApiAux() ); // login + fetchnodes
1624
1625
1626 // --- Check my email and the email of the contact ---
1627
1628 EXPECT_STREQ(mApi[0].email.data(), std::unique_ptr<char[]>{megaApi[0]->getMyEmail()}.get());
1629 EXPECT_STREQ(mApi[1].email.data(), std::unique_ptr<char[]>{megaApi[1]->getMyEmail()}.get());
1630
1631
1632 // --- Send a new contact request ---
1633
1634 string message = "Hi contact. This is a testing message";
1635
1636 mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false;
1637 ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) );
1638 // if there were too many invitations within a short period of time, the invitation can be rejected by
1639 // the API with `API_EOVERQUOTA = -17` as counter spamming meassure (+500 invites in the last 50 days)
1640
1641
1642 // --- Check the sent contact request ---
1643
1644 ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the source side (main account)
1645 << "Contact request update not received after " << maxTimeout << " seconds";
1646
1647 ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true) );
1648
1649 ASSERT_STREQ(message.data(), mApi[0].cr->getSourceMessage()) << "Message sent is corrupted";
1650 ASSERT_STREQ(mApi[0].email.data(), mApi[0].cr->getSourceEmail()) << "Wrong source email";
1651 ASSERT_STREQ(mApi[1].email.data(), mApi[0].cr->getTargetEmail()) << "Wrong target email";
1652 ASSERT_EQ(MegaContactRequest::STATUS_UNRESOLVED, mApi[0].cr->getStatus()) << "Wrong contact request status";
1653 ASSERT_TRUE(mApi[0].cr->isOutgoing()) << "Wrong direction of the contact request";
1654
1655 mApi[0].cr.reset();
1656
1657
1658 // --- Check received contact request ---
1659
1660 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
1661 << "Contact request update not received after " << maxTimeout << " seconds";
1662
1663 ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) );
1664
1665 // There isn't message when a user invites the same user too many times, to avoid spamming
1666 if (mApi[1].cr->getSourceMessage())
1667 {
1668 ASSERT_STREQ(message.data(), mApi[1].cr->getSourceMessage()) << "Message received is corrupted";
1669 }
1670 ASSERT_STREQ(mApi[0].email.data(), mApi[1].cr->getSourceEmail()) << "Wrong source email";
1671 ASSERT_STREQ(NULL, mApi[1].cr->getTargetEmail()) << "Wrong target email"; // NULL according to MegaApi documentation
1672 ASSERT_EQ(MegaContactRequest::STATUS_UNRESOLVED, mApi[1].cr->getStatus()) << "Wrong contact request status";
1673 ASSERT_FALSE(mApi[1].cr->isOutgoing()) << "Wrong direction of the contact request";
1674
1675 mApi[1].cr.reset();
1676
1677
1678 // --- Ignore received contact request ---
1679
1680 ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) );
1681
1682 mApi[1].contactRequestUpdated = false;
1683 ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_IGNORE) );
1684 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
1685 << "Contact request update not received after " << maxTimeout << " seconds";
1686
1687 // Ignoring a PCR does not generate actionpackets for the account sending the invitation
1688
1689 mApi[1].cr.reset();
1690
1691 ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false, 0) );
1692 mApi[1].cr.reset();
1693
1694
1695 // --- Cancel the invitation ---
1696
1697 message = "I don't wanna be your contact anymore";
1698
1699 mApi[0].contactRequestUpdated = false;
1700 ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_DELETE) );
1701 ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (auxiliar account), where the deletion is checked
1702 << "Contact request update not received after " << maxTimeout << " seconds";
1703
1704 ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true, 0) );
1705 mApi[0].cr.reset();
1706
1707
1708 // --- Remind a contact invitation (cannot until 2 weeks after invitation/last reminder) ---
1709
1710 // mApi[1].contactRequestUpdated = false;
1711 // megaApi->inviteContact(mApi[1].email.data(), message.data(), MegaContactRequest::INVITE_ACTION_REMIND);
1712 // waitForResponse(&mApi[1].contactRequestUpdated, 0); // only at auxiliar account, where the deletion is checked
1713
1714 // ASSERT_TRUE(mApi[1].contactRequestUpdated) << "Contact invitation reminder not received after " << timeout << " seconds";
1715
1716
1717 // --- Invite a new contact (again) ---
1718
1719 mApi[1].contactRequestUpdated = false;
1720 ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) );
1721 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
1722 << "Contact request creation not received after " << maxTimeout << " seconds";
1723
1724
1725 // --- Deny a contact invitation ---
1726
1727 ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) );
1728
1729 mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false;
1730 ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_DENY) );
1731 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
1732 << "Contact request creation not received after " << maxTimeout << " seconds";
1733 ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the source side (main account)
1734 << "Contact request creation not received after " << maxTimeout << " seconds";
1735
1736 mApi[1].cr.reset();
1737
1738 ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true, 0) );
1739 mApi[0].cr.reset();
1740
1741 ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false, 0) );
1742 mApi[1].cr.reset();
1743
1744
1745 // --- Invite a new contact (again) ---
1746
1747 mApi[1].contactRequestUpdated = false;
1748 ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) );
1749 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
1750 << "Contact request creation not received after " << maxTimeout << " seconds";
1751
1752
1753 // --- Accept a contact invitation ---
1754
1755 ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) );
1756
1757 mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false;
1758 ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT) );
1759 ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (main account)
1760 << "Contact request creation not received after " << maxTimeout << " seconds";
1761 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
1762 << "Contact request creation not received after " << maxTimeout << " seconds";
1763
1764 mApi[1].cr.reset();
1765
1766 ASSERT_NO_FATAL_FAILURE( getContactRequest(0, true, 0) );
1767 mApi[0].cr.reset();
1768
1769 ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false, 0) );
1770 mApi[1].cr.reset();
1771
1772
1773 // --- Modify firstname ---
1774
1775 string firstname = "My firstname";
1776
1777 mApi[1].userUpdated = false;
1778 ASSERT_NO_FATAL_FAILURE( setUserAttribute(MegaApi::USER_ATTR_FIRSTNAME, firstname));
1779 ASSERT_TRUE( waitForResponse(&mApi[1].userUpdated) ) // at the target side (auxiliar account)
1780 << "User attribute update not received after " << maxTimeout << " seconds";
1781
1782
1783 // --- Check firstname of a contact
1784
1785 MegaUser *u = megaApi[0]->getMyUser();
1786
1787 bool null_pointer = (u == NULL);
1788 ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[0].email;
1789
1790 ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_FIRSTNAME));
1791 ASSERT_EQ( firstname, attributeValue) << "Firstname is wrong";
1792
1793 delete u;
1794
1795
1796 // --- Set master key already as exported
1797
1798 u = megaApi[0]->getMyUser();
1799
1800 mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_USER] = false;
1801 megaApi[0]->masterKeyExported();
1802 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_SET_ATTR_USER]) );
1803
1804 ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_PWD_REMINDER, maxTimeout, 0));
1805 string pwdReminder = attributeValue;
1806 size_t offset = pwdReminder.find(':');
1807 offset += pwdReminder.find(':', offset+1);
1808 ASSERT_EQ( pwdReminder.at(offset), '1' ) << "Password reminder attribute not updated";
1809
1810 delete u;
1811
1812
1813 // --- Get language preference
1814
1815 u = megaApi[0]->getMyUser();
1816
1817 string langCode = "es";
1818 ASSERT_NO_FATAL_FAILURE( setUserAttribute(MegaApi::USER_ATTR_LANGUAGE, langCode));
1819 ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_LANGUAGE, maxTimeout, 0));
1820 string language = attributeValue;
1821 ASSERT_TRUE(!strcmp(langCode.c_str(), language.c_str())) << "Language code is wrong";
1822
1823 delete u;
1824
1825
1826 // --- Load avatar ---
1827
1828 ASSERT_TRUE(fileexists(AVATARSRC)) << "File " +AVATARSRC+ " is needed in folder " << cwd();
1829
1830 mApi[1].userUpdated = false;
1831 ASSERT_NO_FATAL_FAILURE( setUserAttribute(MegaApi::USER_ATTR_AVATAR, AVATARSRC));
1832 ASSERT_TRUE( waitForResponse(&mApi[1].userUpdated) ) // at the target side (auxiliar account)
1833 << "User attribute update not received after " << maxTimeout << " seconds";
1834
1835
1836 // --- Get avatar of a contact ---
1837
1838 u = megaApi[0]->getMyUser();
1839
1840 null_pointer = (u == NULL);
1841 ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[0].email;
1842
1843 attributeValue = "";
1844
1845 ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_AVATAR));
1846 ASSERT_STREQ( "Avatar changed", attributeValue.data()) << "Failed to change avatar";
1847
1848 int64_t filesizeSrc = getFilesize(AVATARSRC);
1849 int64_t filesizeDst = getFilesize(AVATARDST);
1850 ASSERT_EQ(filesizeDst, filesizeSrc) << "Received avatar differs from uploaded avatar";
1851
1852 delete u;
1853
1854
1855 // --- Delete avatar ---
1856
1857 mApi[1].userUpdated = false;
1858 ASSERT_NO_FATAL_FAILURE( setUserAttribute(MegaApi::USER_ATTR_AVATAR, ""));
1859 ASSERT_TRUE( waitForResponse(&mApi[1].userUpdated) ) // at the target side (auxiliar account)
1860 << "User attribute update not received after " << maxTimeout << " seconds";
1861
1862
1863 // --- Get non-existing avatar of a contact ---
1864
1865 u = megaApi[0]->getMyUser();
1866
1867 null_pointer = (u == NULL);
1868 ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[0].email;
1869
1870 attributeValue = "";
1871
1872 ASSERT_NO_FATAL_FAILURE( getUserAttribute(u, MegaApi::USER_ATTR_AVATAR));
1873 ASSERT_STREQ("Avatar not found", attributeValue.data()) << "Failed to remove avatar";
1874
1875 delete u;
1876
1877
1878 // --- Delete an existing contact ---
1879
1880 mApi[0].userUpdated = false;
1881 ASSERT_NO_FATAL_FAILURE( removeContact(mApi[1].email) );
1882 ASSERT_TRUE( waitForResponse(&mApi[0].userUpdated) ) // at the target side (main account)
1883 << "User attribute update not received after " << maxTimeout << " seconds";
1884
1885 u = megaApi[0]->getContact(mApi[1].email.data());
1886 null_pointer = (u == NULL);
1887
1888 ASSERT_FALSE(null_pointer) << "Cannot find the MegaUser for email: " << mApi[1].email;
1889 ASSERT_EQ(MegaUser::VISIBILITY_HIDDEN, u->getVisibility()) << "New contact is still visible";
1890
1891 delete u;
1892 }
1893
checkAlert(int apiIndex,const string & title,const string & path)1894 bool SdkTest::checkAlert(int apiIndex, const string& title, const string& path)
1895 {
1896 bool ok = false;
1897 for (int i = 0; !ok && i < 10; ++i)
1898 {
1899
1900 MegaUserAlertList* list = mApi[apiIndex].megaApi->getUserAlerts();
1901 if (list->size() > 0)
1902 {
1903 MegaUserAlert* a = list->get(list->size() - 1);
1904 ok = title == a->getTitle() && path == a->getPath() && !ISUNDEF(a->getNodeHandle());
1905
1906 if (!ok && i == 9)
1907 {
1908 EXPECT_STREQ(title.c_str(), a->getTitle());
1909 EXPECT_STREQ(path.c_str(), a->getPath());
1910 EXPECT_NE(a->getNodeHandle(), UNDEF);
1911 }
1912 }
1913 delete list;
1914
1915 if (!ok)
1916 {
1917 LOG_info << "Waiting some more for the alert";
1918 WaitMillisec(USERALERT_ARRIVAL_MILLISEC);
1919 }
1920 }
1921 return ok;
1922 }
1923
checkAlert(int apiIndex,const string & title,handle h,int n)1924 bool SdkTest::checkAlert(int apiIndex, const string& title, handle h, int n)
1925 {
1926 bool ok = false;
1927 for (int i = 0; !ok && i < 10; ++i)
1928 {
1929
1930 MegaUserAlertList* list = megaApi[apiIndex]->getUserAlerts();
1931 if (list->size() > 0)
1932 {
1933 MegaUserAlert* a = list->get(list->size() - 1);
1934 ok = title == a->getTitle() && a->getNodeHandle() == h && a->getNumber(0) == n;
1935
1936 if (!ok && i == 9)
1937 {
1938 EXPECT_STREQ(a->getTitle(), title.c_str());
1939 EXPECT_EQ(a->getNodeHandle(), h);
1940 EXPECT_EQ(a->getNumber(0), n); // 0 for number of folders
1941 }
1942 }
1943 delete list;
1944
1945 if (!ok)
1946 {
1947 LOG_info << "Waiting some more for the alert";
1948 WaitMillisec(USERALERT_ARRIVAL_MILLISEC);
1949 }
1950 }
1951 return ok;
1952 }
1953
1954 /**
1955 * @brief TEST_F SdkTestShares
1956 *
1957 * Initialize a test scenario by:
1958 *
1959 * - Creating/uploading some folders/files to share
1960 * - Creating a new contact to share to
1961 *
1962 * Performs different operations related to sharing:
1963 *
1964 * - Share a folder with an existing contact
1965 * - Check the correctness of the outgoing share
1966 * - Check the reception and correctness of the incoming share
1967 * - Modify the access level
1968 * - Revoke the access to the share
1969 * - Share a folder with a non registered email
1970 * - Check the correctness of the pending outgoing share
1971 * - Create a file public link
1972 * - Import a file public link
1973 * - Get a node from a file public link
1974 * - Remove a public link
1975 * - Create a folder public link
1976 */
TEST_F(SdkTest,SdkTestShares)1977 TEST_F(SdkTest, SdkTestShares)
1978 {
1979 LOG_info << "___TEST Shares___";
1980
1981 MegaShareList *sl;
1982 MegaShare *s;
1983 MegaNodeList *nl;
1984 MegaNode *n;
1985 MegaNode *n1;
1986
1987 ASSERT_NO_FATAL_FAILURE( getMegaApiAux() ); // login + fetchnodes
1988
1989
1990 // Initialize a test scenario : create some folders/files to share
1991
1992 // Create some nodes to share
1993 // |--Shared-folder
1994 // |--subfolder
1995 // |--file.txt
1996
1997 std::unique_ptr<MegaNode> rootnode{megaApi[0]->getRootNode()};
1998 char foldername1[64] = "Shared-folder";
1999 MegaHandle hfolder1;
2000
2001 ASSERT_NO_FATAL_FAILURE( createFolder(0, foldername1, rootnode.get()) );
2002
2003 hfolder1 = mApi[0].h; // 'h' is set in 'onRequestFinish()'
2004 n1 = megaApi[0]->getNodeByHandle(hfolder1);
2005
2006 char foldername2[64] = "subfolder";
2007 MegaHandle hfolder2;
2008
2009 ASSERT_NO_FATAL_FAILURE( createFolder(0, foldername2, std::unique_ptr<MegaNode>{megaApi[0]->getNodeByHandle(hfolder1)}.get()) );
2010
2011 hfolder2 = mApi[0].h;
2012
2013 MegaHandle hfile1;
2014 createFile(PUBLICFILE.data(), false); // not a large file since don't need to test transfers here
2015
2016 ASSERT_EQ(MegaError::API_OK, synchronousStartUpload(0, PUBLICFILE.data(), std::unique_ptr<MegaNode>{megaApi[0]->getNodeByHandle(hfolder1)}.get())) << "Cannot upload a test file";
2017
2018 hfile1 = mApi[0].h;
2019
2020
2021 // --- Download authorized node from another account ---
2022
2023 MegaNode *nNoAuth = megaApi[0]->getNodeByHandle(hfile1);
2024
2025 int transferError = synchronousStartDownload(1, nNoAuth, "unauthorized_node");
2026
2027 bool hasFailed = (transferError != API_OK);
2028 ASSERT_TRUE(hasFailed) << "Download of node without authorization successful! (it should fail)";
2029
2030 MegaNode *nAuth = megaApi[0]->authorizeNode(nNoAuth);
2031
2032 transferError = synchronousStartDownload(1, nAuth, "authorized_node");
2033 ASSERT_EQ(MegaError::API_OK, transferError) << "Cannot download authorized node (error: " << mApi[1].lastError << ")";
2034
2035 delete nNoAuth;
2036 delete nAuth;
2037
2038 // Initialize a test scenario: create a new contact to share to
2039
2040 string message = "Hi contact. Let's share some stuff";
2041
2042 mApi[1].contactRequestUpdated = false;
2043 ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) );
2044 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
2045 << "Contact request creation not received after " << maxTimeout << " seconds";
2046
2047
2048 ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) );
2049
2050 mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false;
2051 ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT) );
2052 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
2053 << "Contact request creation not received after " << maxTimeout << " seconds";
2054 ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the source side (main account)
2055 << "Contact request creation not received after " << maxTimeout << " seconds";
2056
2057 mApi[1].cr.reset();
2058
2059
2060 // --- Create a new outgoing share ---
2061
2062 mApi[0].nodeUpdated = mApi[1].nodeUpdated = false;
2063 ASSERT_NO_FATAL_FAILURE( shareFolder(n1, mApi[1].email.data(), MegaShare::ACCESS_READ) );
2064 ASSERT_TRUE( waitForResponse(&mApi[0].nodeUpdated) ) // at the target side (main account)
2065 << "Node update not received after " << maxTimeout << " seconds";
2066 ASSERT_TRUE( waitForResponse(&mApi[1].nodeUpdated) ) // at the target side (auxiliar account)
2067 << "Node update not received after " << maxTimeout << " seconds";
2068
2069
2070 // --- Check the outgoing share ---
2071
2072 sl = megaApi[0]->getOutShares();
2073 ASSERT_EQ(1, sl->size()) << "Outgoing share failed";
2074 s = sl->get(0);
2075
2076 n1 = megaApi[0]->getNodeByHandle(hfolder1); // get an updated version of the node
2077
2078 ASSERT_EQ(MegaShare::ACCESS_READ, s->getAccess()) << "Wrong access level of outgoing share";
2079 ASSERT_EQ(hfolder1, s->getNodeHandle()) << "Wrong node handle of outgoing share";
2080 ASSERT_STREQ(mApi[1].email.data(), s->getUser()) << "Wrong email address of outgoing share";
2081 ASSERT_TRUE(n1->isShared()) << "Wrong sharing information at outgoing share";
2082 ASSERT_TRUE(n1->isOutShare()) << "Wrong sharing information at outgoing share";
2083
2084 delete sl;
2085
2086
2087 // --- Check the incoming share ---
2088
2089 sl = megaApi[1]->getInSharesList();
2090 ASSERT_EQ(1, sl->size()) << "Incoming share not received in auxiliar account";
2091
2092 nl = megaApi[1]->getInShares(megaApi[1]->getContact(mApi[0].email.data()));
2093 ASSERT_EQ(1, nl->size()) << "Incoming share not received in auxiliar account";
2094 n = nl->get(0);
2095
2096 ASSERT_EQ(hfolder1, n->getHandle()) << "Wrong node handle of incoming share";
2097 ASSERT_STREQ(foldername1, n->getName()) << "Wrong folder name of incoming share";
2098 ASSERT_EQ(MegaError::API_OK, megaApi[1]->checkAccess(n, MegaShare::ACCESS_READ).getErrorCode()) << "Wrong access level of incoming share";
2099 ASSERT_TRUE(n->isInShare()) << "Wrong sharing information at incoming share";
2100 ASSERT_TRUE(n->isShared()) << "Wrong sharing information at incoming share";
2101
2102 delete nl;
2103
2104 // check the corresponding user alert
2105 ASSERT_TRUE(checkAlert(1, "New shared folder from " + mApi[0].email, mApi[0].email + ":Shared-folder"));
2106
2107 // add a folder under the share
2108 char foldernameA[64] = "dummyname1";
2109 char foldernameB[64] = "dummyname2";
2110 ASSERT_NO_FATAL_FAILURE(createFolder(0, foldernameA, std::unique_ptr<MegaNode>{megaApi[0]->getNodeByHandle(hfolder2)}.get()));
2111 ASSERT_NO_FATAL_FAILURE(createFolder(0, foldernameB, std::unique_ptr<MegaNode>{megaApi[0]->getNodeByHandle(hfolder2)}.get()));
2112
2113 // check the corresponding user alert
2114 ASSERT_TRUE(checkAlert(1, mApi[0].email + " added 2 folders", std::unique_ptr<MegaNode>{megaApi[0]->getNodeByHandle(hfolder2)}->getHandle(), 2));
2115
2116 // --- Modify the access level of an outgoing share ---
2117
2118 mApi[0].nodeUpdated = mApi[1].nodeUpdated = false;
2119 ASSERT_NO_FATAL_FAILURE( shareFolder(megaApi[0]->getNodeByHandle(hfolder1), mApi[1].email.data(), MegaShare::ACCESS_READWRITE) );
2120 ASSERT_TRUE( waitForResponse(&mApi[0].nodeUpdated) ) // at the target side (main account)
2121 << "Node update not received after " << maxTimeout << " seconds";
2122 ASSERT_TRUE( waitForResponse(&mApi[1].nodeUpdated) ) // at the target side (auxiliar account)
2123 << "Node update not received after " << maxTimeout << " seconds";
2124
2125 nl = megaApi[1]->getInShares(megaApi[1]->getContact(mApi[0].email.data()));
2126 ASSERT_EQ(1, nl->size()) << "Incoming share not received in auxiliar account";
2127 n = nl->get(0);
2128
2129 ASSERT_EQ(MegaError::API_OK, megaApi[1]->checkAccess(n, MegaShare::ACCESS_READWRITE).getErrorCode()) << "Wrong access level of incoming share";
2130
2131 delete nl;
2132
2133
2134 // --- Revoke access to an outgoing share ---
2135
2136 mApi[0].nodeUpdated = mApi[1].nodeUpdated = false;
2137 ASSERT_NO_FATAL_FAILURE( shareFolder(n1, mApi[1].email.data(), MegaShare::ACCESS_UNKNOWN) );
2138 ASSERT_TRUE( waitForResponse(&mApi[0].nodeUpdated) ) // at the target side (main account)
2139 << "Node update not received after " << maxTimeout << " seconds";
2140 ASSERT_TRUE( waitForResponse(&mApi[1].nodeUpdated) ) // at the target side (auxiliar account)
2141 << "Node update not received after " << maxTimeout << " seconds";
2142
2143 delete sl;
2144 sl = megaApi[0]->getOutShares();
2145 ASSERT_EQ(0, sl->size()) << "Outgoing share revocation failed";
2146 delete sl;
2147
2148 nl = megaApi[1]->getInShares(megaApi[1]->getContact(mApi[0].email.data()));
2149 ASSERT_EQ(0, nl->size()) << "Incoming share revocation failed";
2150 delete nl;
2151
2152 // check the corresponding user alert
2153 {
2154 MegaUserAlertList* list = megaApi[1]->getUserAlerts();
2155 ASSERT_TRUE(list->size() > 0);
2156 MegaUserAlert* a = list->get(list->size() - 1);
2157 ASSERT_STREQ(a->getTitle(), ("Access to folders shared by " + mApi[0].email + " was removed").c_str());
2158 ASSERT_STREQ(a->getPath(), (mApi[0].email + ":Shared-folder").c_str());
2159 ASSERT_NE(a->getNodeHandle(), UNDEF);
2160 delete list;
2161 }
2162
2163 // --- Get pending outgoing shares ---
2164
2165 char emailfake[64];
2166 srand(unsigned(time(NULL)));
2167 sprintf(emailfake, "%d@nonexistingdomain.com", rand()%1000000);
2168 // carefull, antispam rejects too many tries without response for the same address
2169
2170 n = megaApi[0]->getNodeByHandle(hfolder2);
2171
2172 mApi[0].contactRequestUpdated = false;
2173 mApi[0].nodeUpdated = false;
2174 ASSERT_NO_FATAL_FAILURE( shareFolder(n, emailfake, MegaShare::ACCESS_FULL) );
2175 ASSERT_TRUE( waitForResponse(&mApi[0].nodeUpdated) ) // at the target side (main account)
2176 << "Node update not received after " << maxTimeout << " seconds";
2177 ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (main account)
2178 << "Contact request update not received after " << maxTimeout << " seconds";
2179
2180 sl = megaApi[0]->getPendingOutShares(n); delete n;
2181 ASSERT_EQ(1, sl->size()) << "Pending outgoing share failed";
2182 s = sl->get(0);
2183 n = megaApi[0]->getNodeByHandle(s->getNodeHandle());
2184
2185 // ASSERT_STREQ(emailfake, s->getUser()) << "Wrong email address of outgoing share"; User is not created yet
2186 ASSERT_FALSE(n->isShared()) << "Node is already shared, must be pending";
2187 ASSERT_FALSE(n->isOutShare()) << "Node is already shared, must be pending";
2188 ASSERT_FALSE(n->isInShare()) << "Node is already shared, must be pending";
2189
2190 delete sl;
2191 delete n;
2192
2193
2194 // --- Create a file public link ---
2195
2196 std::unique_ptr<MegaNode> nfile1{megaApi[0]->getNodeByHandle(hfile1)};
2197
2198 ASSERT_NO_FATAL_FAILURE( createPublicLink(0, nfile1.get()) );
2199 // The created link is stored in this->link at onRequestFinish()
2200
2201 // Get a fresh snapshot of the node and check it's actually exported
2202 nfile1 = std::unique_ptr<MegaNode>{megaApi[0]->getNodeByHandle(hfile1)};
2203 ASSERT_TRUE(nfile1->isExported()) << "Node is not exported, must be exported";
2204 ASSERT_FALSE(nfile1->isTakenDown()) << "Public link is taken down, it mustn't";
2205
2206 // Regenerate the same link should not trigger a new request
2207 string oldLink = link;
2208 link = "";
2209 nfile1 = std::unique_ptr<MegaNode>{megaApi[0]->getNodeByHandle(hfile1)};
2210 ASSERT_NO_FATAL_FAILURE( createPublicLink(0, nfile1.get()) );
2211 ASSERT_STREQ(oldLink.c_str(), link.c_str()) << "Wrong public link after link update";
2212
2213
2214 // Try to update the expiration time of an existing link (only for PRO accounts are allowed, otherwise -11
2215 ASSERT_NO_FATAL_FAILURE( createPublicLink(0, nfile1.get(), 1577836800) ); // Wed, 01 Jan 2020 00:00:00 GMT
2216 nfile1 = std::unique_ptr<MegaNode>{megaApi[0]->getNodeByHandle(hfile1)};
2217 ASSERT_EQ(0, nfile1->getExpirationTime()) << "Expiration time successfully set, when it shouldn't";
2218 ASSERT_FALSE(nfile1->isExpired()) << "Public link is expired, it mustn't";
2219
2220
2221 // --- Import a file public link ---
2222
2223 ASSERT_NO_FATAL_FAILURE( importPublicLink(0, link, rootnode.get()) );
2224
2225 MegaNode *nimported = megaApi[0]->getNodeByHandle(mApi[0].h);
2226
2227 ASSERT_STREQ(nfile1->getName(), nimported->getName()) << "Imported file with wrong name";
2228 ASSERT_EQ(rootnode->getHandle(), nimported->getParentHandle()) << "Imported file in wrong path";
2229
2230
2231 // --- Get node from file public link ---
2232
2233 ASSERT_NO_FATAL_FAILURE( getPublicNode(1, link) );
2234
2235 ASSERT_TRUE(publicNode->isPublic()) << "Cannot get a node from public link";
2236
2237
2238 // --- Remove a public link ---
2239
2240 ASSERT_NO_FATAL_FAILURE( removePublicLink(0, nfile1.get()) );
2241
2242 nfile1 = std::unique_ptr<MegaNode>{megaApi[0]->getNodeByHandle(mApi[0].h)};
2243 ASSERT_FALSE(nfile1->isPublic()) << "Public link removal failed (still public)";
2244
2245 delete nimported;
2246
2247
2248 // --- Create a folder public link ---
2249
2250 MegaNode *nfolder1 = megaApi[0]->getNodeByHandle(hfolder1);
2251
2252 ASSERT_NO_FATAL_FAILURE( createPublicLink(0, nfolder1) );
2253 // The created link is stored in this->link at onRequestFinish()
2254
2255 delete nfolder1;
2256
2257 // Get a fresh snapshot of the node and check it's actually exported
2258 nfolder1 = megaApi[0]->getNodeByHandle(hfolder1);
2259 ASSERT_TRUE(nfolder1->isExported()) << "Node is not exported, must be exported";
2260 ASSERT_FALSE(nfolder1->isTakenDown()) << "Public link is taken down, it mustn't";
2261
2262 delete nfolder1;
2263
2264 oldLink = link;
2265 link = "";
2266 nfolder1 = megaApi[0]->getNodeByHandle(hfolder1);
2267 ASSERT_STREQ(oldLink.c_str(), nfolder1->getPublicLink()) << "Wrong public link from MegaNode";
2268
2269 // Regenerate the same link should not trigger a new request
2270 ASSERT_NO_FATAL_FAILURE( createPublicLink(0, nfolder1) );
2271 ASSERT_STREQ(oldLink.c_str(), link.c_str()) << "Wrong public link after link update";
2272
2273 delete nfolder1;
2274
2275 }
2276
2277
TEST_F(SdkTest,SdkTestShareKeys)2278 TEST_F(SdkTest, SdkTestShareKeys)
2279 {
2280 LOG_info << "___TEST ShareKeys___";
2281
2282 // Three user scenario, with nested shares and new nodes created that need keys to be shared to the other users.
2283 // User A creates folder and shares it with user B
2284 // User A creates folders / subfolder and shares it with user C
2285 // When user C adds files to subfolder, does B receive the keys ?
2286
2287 ASSERT_NO_FATAL_FAILURE(getMegaApiAux()); // login + fetchnodes
2288 ASSERT_NO_FATAL_FAILURE(getMegaApiAux(2)); // login + fetchnodes
2289
2290 unique_ptr<MegaNode> rootnodeA(megaApi[0]->getRootNode());
2291 unique_ptr<MegaNode> rootnodeB(megaApi[1]->getRootNode());
2292 unique_ptr<MegaNode> rootnodeC(megaApi[2]->getRootNode());
2293
2294 ASSERT_TRUE(rootnodeA &&rootnodeB &&rootnodeC);
2295
2296 ASSERT_NO_FATAL_FAILURE(createFolder(0, "share-folder-A", rootnodeA.get()));
2297 unique_ptr<MegaNode> shareFolderA(megaApi[0]->getNodeByHandle(mApi[0].h));
2298 ASSERT_TRUE(!!shareFolderA);
2299
2300 ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub-folder-A", shareFolderA.get()));
2301 unique_ptr<MegaNode> subFolderA(megaApi[0]->getNodeByHandle(mApi[0].h));
2302 ASSERT_TRUE(!!subFolderA);
2303
2304 // Initialize a test scenario: create a new contact to share to
2305
2306 ASSERT_EQ(MegaError::API_OK, synchronousInviteContact(0, mApi[1].email.c_str(), "SdkTestShareKeys contact request A to B", MegaContactRequest::INVITE_ACTION_ADD));
2307 ASSERT_EQ(MegaError::API_OK, synchronousInviteContact(0, mApi[2].email.c_str(), "SdkTestShareKeys contact request A to C", MegaContactRequest::INVITE_ACTION_ADD));
2308
2309 ASSERT_TRUE(WaitFor([this]() {return unique_ptr<MegaContactRequestList>(megaApi[1]->getIncomingContactRequests())->size() == 1
2310 && unique_ptr<MegaContactRequestList>(megaApi[2]->getIncomingContactRequests())->size() == 1;}, 60000));
2311 ASSERT_NO_FATAL_FAILURE(getContactRequest(1, false));
2312 ASSERT_NO_FATAL_FAILURE(getContactRequest(2, false));
2313
2314
2315 ASSERT_EQ(MegaError::API_OK, synchronousReplyContactRequest(1, mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT));
2316 ASSERT_EQ(MegaError::API_OK, synchronousReplyContactRequest(2, mApi[2].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT));
2317
2318 WaitMillisec(3000);
2319
2320 ASSERT_EQ(MegaError::API_OK, synchronousShare(0, shareFolderA.get(), mApi[1].email.c_str(), MegaShare::ACCESS_READ));
2321 ASSERT_EQ(MegaError::API_OK, synchronousShare(0, subFolderA.get(), mApi[2].email.c_str(), MegaShare::ACCESS_FULL));
2322
2323 ASSERT_TRUE(WaitFor([this]() { return unique_ptr<MegaShareList>(megaApi[1]->getInSharesList())->size() == 1
2324 && unique_ptr<MegaShareList>(megaApi[2]->getInSharesList())->size() == 1; }, 60000));
2325
2326 unique_ptr<MegaNodeList> nl1(megaApi[1]->getInShares(megaApi[1]->getContact(mApi[0].email.c_str())));
2327 unique_ptr<MegaNodeList> nl2(megaApi[2]->getInShares(megaApi[2]->getContact(mApi[0].email.c_str())));
2328
2329 ASSERT_EQ(1, nl1->size());
2330 ASSERT_EQ(1, nl2->size());
2331
2332 MegaNode* receivedShareNodeB = nl1->get(0);
2333 MegaNode* receivedShareNodeC = nl2->get(0);
2334
2335 ASSERT_NO_FATAL_FAILURE(createFolder(2, "folderByC1", receivedShareNodeC));
2336 ASSERT_NO_FATAL_FAILURE(createFolder(2, "folderByC2", receivedShareNodeC));
2337
2338 ASSERT_TRUE(WaitFor([this, &subFolderA]() { unique_ptr<MegaNodeList> aView(megaApi[0]->getChildren(subFolderA.get()));
2339 return aView->size() == 2; }, 60000));
2340
2341 WaitMillisec(10000); // make it shorter once we do actually get the keys (seems to need a bug fix)
2342
2343 // can A see the added folders?
2344
2345 unique_ptr<MegaNodeList> aView(megaApi[0]->getChildren(subFolderA.get()));
2346 ASSERT_EQ(2, aView->size());
2347 ASSERT_STREQ(aView->get(0)->getName(), "folderByC1");
2348 ASSERT_STREQ(aView->get(1)->getName(), "folderByC2");
2349
2350 // Can B see the added folders?
2351 unique_ptr<MegaNodeList> bView(megaApi[1]->getChildren(receivedShareNodeB));
2352 ASSERT_EQ(1, bView->size());
2353 ASSERT_STREQ(bView->get(0)->getName(), "sub-folder-A");
2354 unique_ptr<MegaNodeList> bView2(megaApi[1]->getChildren(bView->get(0)));
2355 ASSERT_EQ(2, bView2->size());
2356 ASSERT_STREQ(bView2->get(0)->getName(), "NO_KEY"); // TODO: This is technically not correct but a current side effect of avoiding going back to the servers frequently - to be fixed soon. For now choose the value that matches production
2357 ASSERT_STREQ(bView2->get(1)->getName(), "NO_KEY");
2358 }
2359
localpathToUtf8Leaf(const LocalPath & itemlocalname,FSACCESS_CLASS & fsa)2360 string localpathToUtf8Leaf(const LocalPath& itemlocalname, FSACCESS_CLASS& fsa)
2361 {
2362 size_t lastpart = itemlocalname.lastpartlocal(fsa);
2363 LocalPath name(itemlocalname.subpathFrom(lastpart));
2364 return name.toPath(fsa);
2365 }
2366
fspathToLocal(const fs::path & p,FSACCESS_CLASS & fsa)2367 LocalPath fspathToLocal(const fs::path& p, FSACCESS_CLASS& fsa)
2368 {
2369 string path(p.u8string());
2370 return LocalPath::fromPath(path, fsa);
2371 }
2372
2373
2374
TEST_F(SdkTest,SdkTestFolderIteration)2375 TEST_F(SdkTest, SdkTestFolderIteration)
2376 {
2377
2378 for (int testcombination = 0; testcombination < 2; testcombination++)
2379 {
2380 bool openWithNameOrUseFileAccess = testcombination == 0;
2381
2382 error_code ec;
2383 if (fs::exists("test_SdkTestFolderIteration"))
2384 {
2385 fs::remove_all("test_SdkTestFolderIteration", ec);
2386 ASSERT_TRUE(!ec) << "could not remove old test folder";
2387 }
2388
2389 fs::create_directory("test_SdkTestFolderIteration", ec);
2390 ASSERT_TRUE(!ec) << "could not create test folder";
2391
2392 fs::path iteratePath = fs::current_path() / "test_SdkTestFolderIteration";
2393
2394 // make a directory
2395 fs::create_directory(iteratePath / "folder");
2396
2397 // make a file
2398 {
2399 ofstream f( (iteratePath / "file.txt").u8string().c_str());
2400 f << "file content";
2401 }
2402
2403 // make some content to test the glob flag
2404 {
2405 fs::create_directory(iteratePath / "glob1folder");
2406 fs::create_directory(iteratePath / "glob2folder");
2407 ofstream f1( (iteratePath / "glob1file.txt").u8string().c_str());
2408 ofstream f2( (iteratePath / "glob2file.txt").u8string().c_str());
2409 f1 << "file content";
2410 f2 << "file content";
2411 }
2412 unsigned glob_entries = 4;
2413
2414 // make a symlink to a folder (not recoginised by our dnext() on windows currently)
2415 fs::create_directory_symlink(iteratePath / "folder", iteratePath / "folderlink", ec);
2416 ASSERT_TRUE(!ec) << "could not create folder symlink";
2417
2418 // make a symlinnk to a file
2419 fs::create_symlink(iteratePath / "file.txt", iteratePath / "filelink.txt", ec);
2420 ASSERT_TRUE(!ec) << "could not create folder symlink";
2421
2422 // note on windows: symlinks are excluded by skipAttributes for FILE_ATTRIBUTE_REPARSE_POINT (also see https://docs.microsoft.com/en-us/windows/win32/fileio/determining-whether-a-directory-is-a-volume-mount-point)
2423
2424 struct FileAccessFields
2425 {
2426 m_off_t size = -2;
2427 m_time_t mtime = 2;
2428 handle fsid = 3;
2429 bool fsidvalid = false;
2430 nodetype_t type = (nodetype_t)-9;
2431 bool mIsSymLink = false;
2432 bool retry = false;
2433 int errorcode = -998;
2434
2435 FileAccessFields() = default;
2436
2437 FileAccessFields(const FileAccess& f)
2438 {
2439 size = f.size;
2440 mtime = f.mtime;
2441 fsid = f.fsid;
2442 fsidvalid = f.fsidvalid;
2443 type = f.type;
2444 mIsSymLink = f.mIsSymLink;
2445 retry = f.retry;
2446 errorcode = f.errorcode;
2447 }
2448 bool operator == (const FileAccessFields& f) const
2449 {
2450 if (size != f.size) { EXPECT_EQ(size, f.size); return false; }
2451 if (mtime != f.mtime) { EXPECT_EQ(mtime, f.mtime); return false; }
2452
2453 if (!mIsSymLink)
2454 {
2455 // do we need fsid to be correct for symlink? Seems on mac plain vs iterated differ
2456 if (fsid != f.fsid) { EXPECT_EQ(fsid, f.fsid); return false; }
2457 }
2458
2459 if (fsidvalid != f.fsidvalid) { EXPECT_EQ(fsidvalid, f.fsidvalid); return false; }
2460 if (type != f.type) { EXPECT_EQ(type, f.type); return false; }
2461 if (mIsSymLink != f.mIsSymLink) { EXPECT_EQ(mIsSymLink, f.mIsSymLink); return false; }
2462 if (retry != f.retry) { EXPECT_EQ(retry, f.retry); return false; }
2463 if (errorcode != f.errorcode) { EXPECT_EQ(errorcode, f.errorcode); return false; }
2464 return true;
2465 }
2466 };
2467
2468 // capture results from the ways of gettnig the file info
2469 std::map<std::string, FileAccessFields > plain_fopen;
2470 std::map<std::string, FileAccessFields > iterate_fopen;
2471 std::map<std::string, FileAccessFields > plain_follow_fopen;
2472 std::map<std::string, FileAccessFields > iterate_follow_fopen;
2473
2474 FSACCESS_CLASS fsa;
2475 auto localdir = fspathToLocal(iteratePath, fsa);
2476
2477 std::unique_ptr<FileAccess> fopen_directory(fsa.newfileaccess(false)); // false = don't follow symlinks
2478 ASSERT_TRUE(fopen_directory->fopen(localdir, true, false));
2479
2480 // now open and iterate the directory, not following symlinks (either by name or fopen'd directory)
2481 std::unique_ptr<DirAccess> da(fsa.newdiraccess());
2482 if (da->dopen(openWithNameOrUseFileAccess ? &localdir : NULL, openWithNameOrUseFileAccess ? NULL : fopen_directory.get(), false))
2483 {
2484 nodetype_t type;
2485 LocalPath itemlocalname;
2486 while (da->dnext(localdir, itemlocalname, false, &type))
2487 {
2488 string leafNameUtf8 = localpathToUtf8Leaf(itemlocalname, fsa);
2489
2490 std::unique_ptr<FileAccess> plain_fopen_fa(fsa.newfileaccess(false));
2491 std::unique_ptr<FileAccess> iterate_fopen_fa(fsa.newfileaccess(false));
2492
2493 LocalPath localpath = localdir;
2494 localpath.appendWithSeparator(itemlocalname, true, fsa.localseparator);
2495
2496 ASSERT_TRUE(plain_fopen_fa->fopen(localpath, true, false));
2497 plain_fopen[leafNameUtf8] = *plain_fopen_fa;
2498
2499 ASSERT_TRUE(iterate_fopen_fa->fopen(localpath, true, false, da.get()));
2500 iterate_fopen[leafNameUtf8] = *iterate_fopen_fa;
2501 }
2502 }
2503
2504 std::unique_ptr<FileAccess> fopen_directory2(fsa.newfileaccess(true)); // true = follow symlinks
2505 ASSERT_TRUE(fopen_directory2->fopen(localdir, true, false));
2506
2507 // now open and iterate the directory, following symlinks (either by name or fopen'd directory)
2508 std::unique_ptr<DirAccess> da_follow(fsa.newdiraccess());
2509 if (da_follow->dopen(openWithNameOrUseFileAccess ? &localdir : NULL, openWithNameOrUseFileAccess ? NULL : fopen_directory2.get(), false))
2510 {
2511 nodetype_t type;
2512 LocalPath itemlocalname;
2513 while (da_follow->dnext(localdir, itemlocalname, true, &type))
2514 {
2515 string leafNameUtf8 = localpathToUtf8Leaf(itemlocalname, fsa);
2516
2517 std::unique_ptr<FileAccess> plain_follow_fopen_fa(fsa.newfileaccess(true));
2518 std::unique_ptr<FileAccess> iterate_follow_fopen_fa(fsa.newfileaccess(true));
2519
2520 LocalPath localpath = localdir;
2521 localpath.appendWithSeparator(itemlocalname, true, fsa.localseparator);
2522
2523 ASSERT_TRUE(plain_follow_fopen_fa->fopen(localpath, true, false));
2524 plain_follow_fopen[leafNameUtf8] = *plain_follow_fopen_fa;
2525
2526 ASSERT_TRUE(iterate_follow_fopen_fa->fopen(localpath, true, false, da_follow.get()));
2527 iterate_follow_fopen[leafNameUtf8] = *iterate_follow_fopen_fa;
2528 }
2529 }
2530
2531 #ifdef WIN32
2532 std::set<std::string> plain_names { "folder", "file.txt" }; // currently on windows, any type of symlink is ignored when iterating directories
2533 std::set<std::string> follow_names { "folder", "file.txt"};
2534 #else
2535 std::set<std::string> plain_names { "folder", "file.txt" };
2536 std::set<std::string> follow_names { "folder", "file.txt", "folderlink", "filelink.txt" };
2537 #endif
2538
2539 ASSERT_EQ(plain_fopen.size(), plain_names.size() + glob_entries);
2540 ASSERT_EQ(iterate_fopen.size(), plain_names.size() + glob_entries);
2541 ASSERT_EQ(plain_follow_fopen.size(), follow_names.size() + glob_entries);
2542 ASSERT_EQ(iterate_follow_fopen.size(), follow_names.size() + glob_entries);
2543
2544 for (auto& name : follow_names)
2545 {
2546 bool expected_non_follow = plain_names.find(name) != plain_names.end();
2547 bool issymlink = name.find("link") != string::npos;
2548
2549 if (expected_non_follow)
2550 {
2551 ASSERT_TRUE(plain_fopen.find(name) != plain_fopen.end()) << name;
2552 ASSERT_TRUE(iterate_fopen.find(name) != iterate_fopen.end()) << name;
2553
2554 auto& plain = plain_fopen[name];
2555 auto& iterate = iterate_fopen[name];
2556
2557 ASSERT_EQ(plain, iterate) << name;
2558 ASSERT_TRUE(plain.mIsSymLink == issymlink);
2559 }
2560
2561 ASSERT_TRUE(plain_follow_fopen.find(name) != plain_follow_fopen.end()) << name;
2562 ASSERT_TRUE(iterate_follow_fopen.find(name) != iterate_follow_fopen.end()) << name;
2563
2564 auto& plain_follow = plain_follow_fopen[name];
2565 auto& iterate_follow = iterate_follow_fopen[name];
2566
2567 ASSERT_EQ(plain_follow, iterate_follow) << name;
2568 ASSERT_TRUE(plain_follow.mIsSymLink == issymlink);
2569 }
2570
2571 //ASSERT_EQ(plain_fopen["folder"].size, 0); size field is not set for folders
2572 ASSERT_EQ(plain_fopen["folder"].type, FOLDERNODE);
2573 ASSERT_EQ(plain_fopen["folder"].fsidvalid, true);
2574 ASSERT_EQ(plain_fopen["folder"].mIsSymLink, false);
2575
2576 ASSERT_EQ(plain_fopen["file.txt"].size, 12);
2577 ASSERT_EQ(plain_fopen["file.txt"].fsidvalid, true);
2578 ASSERT_EQ(plain_fopen["file.txt"].type, FILENODE);
2579 ASSERT_EQ(plain_fopen["file.txt"].mIsSymLink, false);
2580
2581 // on windows and mac and linux, without the follow flag on, directory iteration does not report symlinks (currently)
2582 //
2583 // //ASSERT_EQ(plain_fopen["folder"].size, 0); size field is not set for folders
2584 // ASSERT_EQ(plain_fopen["folderlink"].type, FOLDERNODE);
2585 // ASSERT_EQ(plain_fopen["folderlink"].fsidvalid, true);
2586 // ASSERT_EQ(plain_fopen["folderlink"].mIsSymLink, true);
2587 //
2588 // ASSERT_EQ(plain_fopen["filelink.txt"].size, 12);
2589 // ASSERT_EQ(plain_fopen["filelink.txt"].fsidvalid, true);
2590 // ASSERT_EQ(plain_fopen["filelink.txt"].type, FILENODE);
2591 // ASSERT_EQ(plain_fopen["filelink.txt"].mIsSymLink, true);
2592 //
2593 ASSERT_TRUE(plain_fopen.find("folderlink") == plain_fopen.end());
2594 ASSERT_TRUE(plain_fopen.find("filelink.txt") == plain_fopen.end());
2595
2596 // check the glob flag
2597 auto localdirGlob = fspathToLocal(iteratePath / "glob1*", fsa);
2598 std::unique_ptr<DirAccess> da2(fsa.newdiraccess());
2599 if (da2->dopen(&localdirGlob, NULL, true))
2600 {
2601 nodetype_t type;
2602 LocalPath itemlocalname;
2603 set<string> remainingExpected { "glob1folder", "glob1file.txt" };
2604 while (da2->dnext(localdir, itemlocalname, true, &type))
2605 {
2606 string leafNameUtf8 = localpathToUtf8Leaf(itemlocalname, fsa);
2607 ASSERT_EQ(leafNameUtf8.substr(0, 5), string("glob1"));
2608 ASSERT_TRUE(remainingExpected.find(leafNameUtf8) != remainingExpected.end());
2609 remainingExpected.erase(leafNameUtf8);
2610 }
2611 ASSERT_EQ(remainingExpected.size(), 0u);
2612 }
2613
2614 }
2615 }
2616
2617
2618
2619 /**
2620 * @brief TEST_F SdkTestConsoleAutocomplete
2621 *
2622 * Run various tests confirming the console autocomplete will work as expected
2623 *
2624 */
2625 #ifdef _WIN32
2626
cmp(const autocomplete::CompletionState & c,std::vector<std::string> & s)2627 bool cmp(const autocomplete::CompletionState& c, std::vector<std::string>& s)
2628 {
2629 bool result = true;
2630 if (c.completions.size() != s.size())
2631 {
2632 result = false;
2633 }
2634 else
2635 {
2636 std::sort(s.begin(), s.end());
2637 for (size_t i = c.completions.size(); i--; )
2638 {
2639 if (c.completions[i].s != s[i])
2640 {
2641 result = false;
2642 break;
2643 }
2644 }
2645 }
2646 if (!result)
2647 {
2648 for (size_t i = 0; i < c.completions.size() || i < s.size(); ++i)
2649 {
2650 cout << (i < s.size() ? s[i] : "") << "/" << (i < c.completions.size() ? c.completions[i].s : "") << endl;
2651 }
2652 }
2653 return result;
2654 }
2655
TEST_F(SdkTest,SdkTestConsoleAutocomplete)2656 TEST_F(SdkTest, SdkTestConsoleAutocomplete)
2657 {
2658 using namespace autocomplete;
2659
2660 {
2661 std::unique_ptr<Either> p(new Either);
2662 p->Add(sequence(text("cd")));
2663 p->Add(sequence(text("lcd")));
2664 p->Add(sequence(text("ls"), opt(flag("-R"))));
2665 p->Add(sequence(text("lls"), opt(flag("-R")), param("folder")));
2666 ACN syntax(std::move(p));
2667
2668 {
2669 auto r = autoComplete("", 0, syntax, false);
2670 std::vector<std::string> e{ "cd", "lcd", "ls", "lls" };
2671 ASSERT_TRUE(cmp(r, e));
2672 }
2673
2674 {
2675 auto r = autoComplete("l", 1, syntax, false);
2676 std::vector<std::string> e{ "lcd", "ls", "lls" };
2677 ASSERT_TRUE(cmp(r, e));
2678 }
2679
2680 {
2681 auto r = autoComplete("ll", 2, syntax, false);
2682 std::vector<std::string> e{ "lls" };
2683 ASSERT_TRUE(cmp(r, e));
2684 }
2685
2686 {
2687 auto r = autoComplete("lls", 3, syntax, false);
2688 std::vector<std::string> e{ "lls" };
2689 ASSERT_TRUE(cmp(r, e));
2690 }
2691
2692 {
2693 auto r = autoComplete("lls ", 4, syntax, false);
2694 std::vector<std::string> e{ "<folder>" };
2695 ASSERT_TRUE(cmp(r, e));
2696 }
2697
2698 {
2699 auto r = autoComplete("lls -", 5, syntax, false);
2700 std::vector<std::string> e{ "-R" };
2701 ASSERT_TRUE(cmp(r, e));
2702 }
2703
2704 {
2705 auto r = autoComplete("x", 1, syntax, false);
2706 std::vector<std::string> e{};
2707 ASSERT_TRUE(cmp(r, e));
2708 }
2709
2710 {
2711 auto r = autoComplete("x ", 2, syntax, false);
2712 std::vector<std::string> e{};
2713 ASSERT_TRUE(cmp(r, e));
2714 }
2715 }
2716
2717 ::mega::handle megaCurDir = UNDEF;
2718
2719 MegaApiImpl* impl = *((MegaApiImpl**)(((char*)megaApi[0].get()) + sizeof(*megaApi[0].get())) - 1); //megaApi[0]->pImpl;
2720 MegaClient* client = impl->getMegaClient();
2721
2722
2723 std::unique_ptr<Either> p(new Either);
2724 p->Add(sequence(text("cd")));
2725 p->Add(sequence(text("lcd")));
2726 p->Add(sequence(text("ls"), opt(flag("-R")), opt(ACN(new MegaFS(true, true, client, &megaCurDir, "")))));
2727 p->Add(sequence(text("lls"), opt(flag("-R")), opt(ACN(new LocalFS(true, true, "")))));
2728 ACN syntax(std::move(p));
2729
2730 error_code e;
2731 fs::remove_all("test_autocomplete_files", e);
2732
2733 fs::create_directory("test_autocomplete_files");
2734 fs::path old_cwd = fs::current_path();
2735 fs::current_path("test_autocomplete_files");
2736
2737 fs::create_directory("dir1");
2738 fs::create_directory("dir1\\sub11");
2739 fs::create_directory("dir1\\sub12");
2740 fs::create_directory("dir2");
2741 fs::create_directory("dir2\\sub21");
2742 fs::create_directory("dir2\\sub22");
2743 fs::create_directory("dir2a");
2744 fs::create_directory("dir2a\\dir space");
2745 fs::create_directory("dir2a\\dir space\\next");
2746 fs::create_directory("dir2a\\dir space2");
2747 fs::create_directory("dir2a\\nospace");
2748
2749 {
2750 auto r = autoComplete("ls -R", 5, syntax, false);
2751 std::vector<std::string> e{"-R"};
2752 ASSERT_TRUE(cmp(r, e));
2753 }
2754
2755 // dos style file completion, local fs
2756 CompletionTextOut s;
2757
2758 {
2759 auto r = autoComplete("lls ", 4, syntax, false);
2760 std::vector<std::string> e{ "dir1", "dir2", "dir2a" };
2761 ASSERT_TRUE(cmp(r, e));
2762 applyCompletion(r, true, 100, s);
2763 ASSERT_EQ(r.line, "lls dir1");
2764 }
2765
2766 {
2767 auto r = autoComplete("lls di", 6, syntax, false);
2768 std::vector<std::string> e{ "dir1", "dir2", "dir2a" };
2769 ASSERT_TRUE(cmp(r, e));
2770 }
2771
2772 {
2773 auto r = autoComplete("lls dir2", 8, syntax, false);
2774 std::vector<std::string> e{ "dir2", "dir2a" };
2775 ASSERT_TRUE(cmp(r, e));
2776 }
2777
2778 {
2779 auto r = autoComplete("lls dir2a", 9, syntax, false);
2780 std::vector<std::string> e{ "dir2a" };
2781 ASSERT_TRUE(cmp(r, e));
2782 }
2783
2784 {
2785 auto r = autoComplete("lls dir2 something after", 8, syntax, false);
2786 std::vector<std::string> e{ "dir2", "dir2a" };
2787 ASSERT_TRUE(cmp(r, e));
2788 }
2789
2790 {
2791 auto r = autoComplete("lls dir2something immeditely after", 8, syntax, false);
2792 std::vector<std::string> e{ "dir2", "dir2a" };
2793 ASSERT_TRUE(cmp(r, e));
2794 }
2795
2796 {
2797 auto r = autoComplete("lls dir2\\", 9, syntax, false);
2798 std::vector<std::string> e{ "dir2\\sub21", "dir2\\sub22" };
2799 ASSERT_TRUE(cmp(r, e));
2800 }
2801
2802 {
2803 auto r = autoComplete("lls dir2\\.\\", 11, syntax, false);
2804 std::vector<std::string> e{ "dir2\\.\\sub21", "dir2\\.\\sub22" };
2805 ASSERT_TRUE(cmp(r, e));
2806 }
2807
2808 {
2809 auto r = autoComplete("lls dir2\\..", 11, syntax, false);
2810 std::vector<std::string> e{ "dir2\\.." };
2811 ASSERT_TRUE(cmp(r, e));
2812 }
2813
2814 {
2815 auto r = autoComplete("lls dir2\\..\\", 12, syntax, false);
2816 std::vector<std::string> e{ "dir2\\..\\dir1", "dir2\\..\\dir2", "dir2\\..\\dir2a" };
2817 ASSERT_TRUE(cmp(r, e));
2818 applyCompletion(r, true, 100, s);
2819 ASSERT_EQ(r.line, "lls dir2\\..\\dir1");
2820 applyCompletion(r, true, 100, s);
2821 ASSERT_EQ(r.line, "lls dir2\\..\\dir2");
2822 applyCompletion(r, true, 100, s);
2823 ASSERT_EQ(r.line, "lls dir2\\..\\dir2a");
2824 applyCompletion(r, true, 100, s);
2825 ASSERT_EQ(r.line, "lls dir2\\..\\dir1");
2826 applyCompletion(r, false, 100, s);
2827 ASSERT_EQ(r.line, "lls dir2\\..\\dir2a");
2828 applyCompletion(r, false, 100, s);
2829 ASSERT_EQ(r.line, "lls dir2\\..\\dir2");
2830 }
2831
2832 {
2833 auto r = autoComplete("lls dir2a\\", 10, syntax, false);
2834 applyCompletion(r, false, 100, s);
2835 ASSERT_EQ(r.line, "lls dir2a\\nospace");
2836 applyCompletion(r, false, 100, s);
2837 ASSERT_EQ(r.line, "lls \"dir2a\\dir space2\"");
2838 applyCompletion(r, false, 100, s);
2839 ASSERT_EQ(r.line, "lls \"dir2a\\dir space\"");
2840 applyCompletion(r, false, 100, s);
2841 ASSERT_EQ(r.line, "lls dir2a\\nospace");
2842 }
2843
2844 {
2845 auto r = autoComplete("lls \"dir\"1\\", 11, syntax, false);
2846 applyCompletion(r, true, 100, s);
2847 ASSERT_EQ(r.line, "lls \"dir1\\sub11\"");
2848 }
2849
2850 {
2851 auto r = autoComplete("lls dir1\\\"..\\dir2\\\"", std::string::npos, syntax, false);
2852 applyCompletion(r, true, 100, s);
2853 ASSERT_EQ(r.line, "lls \"dir1\\..\\dir2\\sub21\"");
2854 }
2855
2856 {
2857 auto r = autoComplete("lls c:\\prog", std::string::npos, syntax, false);
2858 applyCompletion(r, true, 100, s);
2859 ASSERT_EQ(r.line, "lls \"c:\\Program Files\"");
2860 applyCompletion(r, true, 100, s);
2861 ASSERT_EQ(r.line, "lls \"c:\\Program Files (x86)\"");
2862 }
2863
2864 {
2865 auto r = autoComplete("lls \"c:\\program files \"", std::string::npos, syntax, false);
2866 applyCompletion(r, true, 100, s);
2867 ASSERT_EQ(r.line, "lls \"c:\\Program Files (x86)\"");
2868 }
2869
2870 // unix style completions, local fs
2871
2872 {
2873 auto r = autoComplete("lls ", 4, syntax, true);
2874 std::vector<std::string> e{ "dir1\\", "dir2\\", "dir2a\\" };
2875 ASSERT_TRUE(cmp(r, e));
2876 applyCompletion(r, true, 100, s);
2877 ASSERT_EQ(r.line, "lls dir");
2878 }
2879
2880 {
2881 auto r = autoComplete("lls di", 6, syntax, true);
2882 std::vector<std::string> e{ "dir1\\", "dir2\\", "dir2a\\" };
2883 ASSERT_TRUE(cmp(r, e));
2884 applyCompletion(r, true, 100, s);
2885 ASSERT_EQ(r.line, "lls dir");
2886 }
2887
2888 {
2889 auto r = autoComplete("lls dir2", 8, syntax, true);
2890 std::vector<std::string> e{ "dir2\\", "dir2a\\" };
2891 ASSERT_TRUE(cmp(r, e));
2892 applyCompletion(r, true, 100, s);
2893 ASSERT_EQ(r.line, "lls dir2");
2894 }
2895
2896 {
2897 auto r = autoComplete("lls dir2a", 9, syntax, true);
2898 std::vector<std::string> e{ "dir2a\\" };
2899 ASSERT_TRUE(cmp(r, e));
2900 applyCompletion(r, true, 100, s);
2901 ASSERT_EQ(r.line, "lls dir2a\\");
2902 }
2903
2904 {
2905 auto r = autoComplete("lls dir2 something after", 8, syntax, true);
2906 std::vector<std::string> e{ "dir2\\", "dir2a\\" };
2907 ASSERT_TRUE(cmp(r, e));
2908 applyCompletion(r, true, 100, s);
2909 ASSERT_EQ(r.line, "lls dir2 something after");
2910 }
2911
2912 {
2913 auto r = autoComplete("lls dir2asomething immediately after", 9, syntax, true);
2914 std::vector<std::string> e{ "dir2a\\" };
2915 ASSERT_TRUE(cmp(r, e));
2916 applyCompletion(r, true, 100, s);
2917 ASSERT_EQ(r.line, "lls dir2a\\something immediately after");
2918 }
2919
2920 {
2921 auto r = autoComplete("lls dir2\\", 9, syntax, true);
2922 std::vector<std::string> e{ "dir2\\sub21\\", "dir2\\sub22\\" };
2923 ASSERT_TRUE(cmp(r, e));
2924 applyCompletion(r, true, 100, s);
2925 ASSERT_EQ(r.line, "lls dir2\\sub2");
2926 auto rr = autoComplete("lls dir2\\sub22", 14, syntax, true);
2927 applyCompletion(rr, true, 100, s);
2928 ASSERT_EQ(rr.line, "lls dir2\\sub22\\");
2929 }
2930
2931 {
2932 auto r = autoComplete("lls dir2\\.\\", 11, syntax, true);
2933 std::vector<std::string> e{ "dir2\\.\\sub21\\", "dir2\\.\\sub22\\" };
2934 ASSERT_TRUE(cmp(r, e));
2935 applyCompletion(r, true, 100, s);
2936 ASSERT_EQ(r.line, "lls dir2\\.\\sub2");
2937 }
2938
2939 {
2940 auto r = autoComplete("lls dir2\\..", 11, syntax, true);
2941 std::vector<std::string> e{ "dir2\\..\\" };
2942 ASSERT_TRUE(cmp(r, e));
2943 applyCompletion(r, true, 100, s);
2944 ASSERT_EQ(r.line, "lls dir2\\..\\");
2945 }
2946
2947 {
2948 auto r = autoComplete("lls dir2\\..\\", 12, syntax, true);
2949 std::vector<std::string> e{ "dir2\\..\\dir1\\", "dir2\\..\\dir2\\", "dir2\\..\\dir2a\\" };
2950 ASSERT_TRUE(cmp(r, e));
2951 applyCompletion(r, true, 100, s);
2952 ASSERT_EQ(r.line, "lls dir2\\..\\dir");
2953 }
2954
2955 {
2956 auto r = autoComplete("lls dir2\\..\\", 12, syntax, true);
2957 std::vector<std::string> e{ "dir2\\..\\dir1\\", "dir2\\..\\dir2\\", "dir2\\..\\dir2a\\" };
2958 ASSERT_TRUE(cmp(r, e));
2959 applyCompletion(r, true, 100, s);
2960 ASSERT_EQ(r.line, "lls dir2\\..\\dir");
2961 }
2962
2963 {
2964 auto r = autoComplete("lls dir2a\\d", 11, syntax, true);
2965 applyCompletion(r, true, 100, s);
2966 ASSERT_EQ(r.line, "lls \"dir2a\\dir space\"");
2967 auto rr = autoComplete("lls \"dir2a\\dir space\"\\", std::string::npos, syntax, false);
2968 applyCompletion(rr, true, 100, s);
2969 ASSERT_EQ(rr.line, "lls \"dir2a\\dir space\\next\"");
2970 }
2971
2972 {
2973 auto r = autoComplete("lls \"dir\"1\\", std::string::npos, syntax, true);
2974 applyCompletion(r, true, 100, s);
2975 ASSERT_EQ(r.line, "lls \"dir1\\sub1\"");
2976 }
2977
2978 {
2979 auto r = autoComplete("lls dir1\\\"..\\dir2\\\"", std::string::npos, syntax, true);
2980 applyCompletion(r, true, 100, s);
2981 ASSERT_EQ(r.line, "lls \"dir1\\..\\dir2\\sub2\"");
2982 }
2983
2984 {
2985 auto r = autoComplete("lls c:\\prog", std::string::npos, syntax, true);
2986 applyCompletion(r, true, 100, s);
2987 ASSERT_EQ(r.line, "lls c:\\program");
2988 }
2989
2990 {
2991 auto r = autoComplete("lls \"c:\\program files \"", std::string::npos, syntax, true);
2992 applyCompletion(r, true, 100, s);
2993 ASSERT_EQ(r.line, "lls \"c:\\program files (x86)\\\"");
2994 }
2995
2996 {
2997 auto r = autoComplete("lls 'c:\\program files '", std::string::npos, syntax, true);
2998 applyCompletion(r, true, 100, s);
2999 ASSERT_EQ(r.line, "lls 'c:\\program files (x86)\\'");
3000 }
3001
3002 // mega dir setup
3003
3004 MegaNode *rootnode = megaApi[0]->getRootNode();
3005 ASSERT_NO_FATAL_FAILURE(createFolder(0, "test_autocomplete_megafs", rootnode));
3006 MegaNode *n0 = megaApi[0]->getNodeByHandle(mApi[0].h);
3007
3008 megaCurDir = mApi[0].h;
3009
3010 ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir1", n0));
3011 MegaNode *n1 = megaApi[0]->getNodeByHandle(mApi[0].h);
3012 ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub11", n1));
3013 ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub12", n1));
3014
3015 ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir2", n0));
3016 MegaNode *n2 = megaApi[0]->getNodeByHandle(mApi[0].h);
3017 ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub21", n2));
3018 ASSERT_NO_FATAL_FAILURE(createFolder(0, "sub22", n2));
3019
3020 ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir2a", n0));
3021 MegaNode *n3 = megaApi[0]->getNodeByHandle(mApi[0].h);
3022 ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir space", n3));
3023 MegaNode *n31 = megaApi[0]->getNodeByHandle(mApi[0].h);
3024 ASSERT_NO_FATAL_FAILURE(createFolder(0, "dir space2", n3));
3025 ASSERT_NO_FATAL_FAILURE(createFolder(0, "nospace", n3));
3026 ASSERT_NO_FATAL_FAILURE(createFolder(0, "next", n31));
3027
3028
3029 // dos style mega FS completions
3030
3031 {
3032 auto r = autoComplete("ls ", std::string::npos, syntax, false);
3033 std::vector<std::string> e{ "dir1", "dir2", "dir2a" };
3034 ASSERT_TRUE(cmp(r, e));
3035 applyCompletion(r, true, 100, s);
3036 ASSERT_EQ(r.line, "ls dir1");
3037 }
3038
3039 {
3040 auto r = autoComplete("ls di", std::string::npos, syntax, false);
3041 std::vector<std::string> e{ "dir1", "dir2", "dir2a" };
3042 ASSERT_TRUE(cmp(r, e));
3043 }
3044
3045 {
3046 auto r = autoComplete("ls dir2", std::string::npos, syntax, false);
3047 std::vector<std::string> e{ "dir2", "dir2a" };
3048 ASSERT_TRUE(cmp(r, e));
3049 }
3050
3051 {
3052 auto r = autoComplete("ls dir2a", std::string::npos, syntax, false);
3053 std::vector<std::string> e{ "dir2a" };
3054 ASSERT_TRUE(cmp(r, e));
3055 }
3056
3057 {
3058 auto r = autoComplete("ls dir2 something after", 7, syntax, false);
3059 std::vector<std::string> e{ "dir2", "dir2a" };
3060 ASSERT_TRUE(cmp(r, e));
3061 }
3062
3063 {
3064 auto r = autoComplete("ls dir2something immeditely after", 7, syntax, false);
3065 std::vector<std::string> e{ "dir2", "dir2a" };
3066 ASSERT_TRUE(cmp(r, e));
3067 }
3068
3069 {
3070 auto r = autoComplete("ls dir2/", std::string::npos, syntax, false);
3071 std::vector<std::string> e{ "dir2/sub21", "dir2/sub22" };
3072 ASSERT_TRUE(cmp(r, e));
3073 }
3074
3075 {
3076 auto r = autoComplete("ls dir2/./", std::string::npos, syntax, false);
3077 std::vector<std::string> e{ "dir2/./sub21", "dir2/./sub22" };
3078 ASSERT_TRUE(cmp(r, e));
3079 }
3080
3081 {
3082 auto r = autoComplete("ls dir2/..", std::string::npos, syntax, false);
3083 std::vector<std::string> e{ "dir2/.." };
3084 ASSERT_TRUE(cmp(r, e));
3085 }
3086
3087 {
3088 auto r = autoComplete("ls dir2/../", std::string::npos, syntax, false);
3089 std::vector<std::string> e{ "dir2/../dir1", "dir2/../dir2", "dir2/../dir2a" };
3090 ASSERT_TRUE(cmp(r, e));
3091 applyCompletion(r, true, 100, s);
3092 ASSERT_EQ(r.line, "ls dir2/../dir1");
3093 applyCompletion(r, true, 100, s);
3094 ASSERT_EQ(r.line, "ls dir2/../dir2");
3095 applyCompletion(r, true, 100, s);
3096 ASSERT_EQ(r.line, "ls dir2/../dir2a");
3097 applyCompletion(r, true, 100, s);
3098 ASSERT_EQ(r.line, "ls dir2/../dir1");
3099 applyCompletion(r, false, 100, s);
3100 ASSERT_EQ(r.line, "ls dir2/../dir2a");
3101 applyCompletion(r, false, 100, s);
3102 ASSERT_EQ(r.line, "ls dir2/../dir2");
3103 }
3104
3105 {
3106 auto r = autoComplete("ls dir2a/", std::string::npos, syntax, false);
3107 applyCompletion(r, false, 100, s);
3108 ASSERT_EQ(r.line, "ls dir2a/nospace");
3109 applyCompletion(r, false, 100, s);
3110 ASSERT_EQ(r.line, "ls \"dir2a/dir space2\"");
3111 applyCompletion(r, false, 100, s);
3112 ASSERT_EQ(r.line, "ls \"dir2a/dir space\"");
3113 applyCompletion(r, false, 100, s);
3114 ASSERT_EQ(r.line, "ls dir2a/nospace");
3115 }
3116
3117 {
3118 auto r = autoComplete("ls \"dir\"1/", std::string::npos, syntax, false);
3119 applyCompletion(r, true, 100, s);
3120 ASSERT_EQ(r.line, "ls \"dir1/sub11\"");
3121 }
3122
3123 {
3124 auto r = autoComplete("ls dir1/\"../dir2/\"", std::string::npos, syntax, false);
3125 applyCompletion(r, true, 100, s);
3126 ASSERT_EQ(r.line, "ls \"dir1/../dir2/sub21\"");
3127 }
3128
3129 {
3130 auto r = autoComplete("ls /test_autocomplete_meg", std::string::npos, syntax, false);
3131 applyCompletion(r, true, 100, s);
3132 ASSERT_EQ(r.line, "ls /test_autocomplete_megafs");
3133 }
3134
3135 // unix style mega FS completions
3136
3137 {
3138 auto r = autoComplete("ls ", std::string::npos, syntax, true);
3139 std::vector<std::string> e{ "dir1/", "dir2/", "dir2a/" };
3140 ASSERT_TRUE(cmp(r, e));
3141 applyCompletion(r, true, 100, s);
3142 ASSERT_EQ(r.line, "ls dir");
3143 }
3144
3145 {
3146 auto r = autoComplete("ls di", std::string::npos, syntax, true);
3147 std::vector<std::string> e{ "dir1/", "dir2/", "dir2a/" };
3148 ASSERT_TRUE(cmp(r, e));
3149 applyCompletion(r, true, 100, s);
3150 ASSERT_EQ(r.line, "ls dir");
3151 }
3152
3153 {
3154 auto r = autoComplete("ls dir2", std::string::npos, syntax, true);
3155 std::vector<std::string> e{ "dir2/", "dir2a/" };
3156 ASSERT_TRUE(cmp(r, e));
3157 applyCompletion(r, true, 100, s);
3158 ASSERT_EQ(r.line, "ls dir2");
3159 }
3160
3161 {
3162 auto r = autoComplete("ls dir2a", std::string::npos, syntax, true);
3163 std::vector<std::string> e{ "dir2a/" };
3164 ASSERT_TRUE(cmp(r, e));
3165 applyCompletion(r, true, 100, s);
3166 ASSERT_EQ(r.line, "ls dir2a/");
3167 }
3168
3169 {
3170 auto r = autoComplete("ls dir2 something after", 7, syntax, true);
3171 std::vector<std::string> e{ "dir2/", "dir2a/" };
3172 ASSERT_TRUE(cmp(r, e));
3173 applyCompletion(r, true, 100, s);
3174 ASSERT_EQ(r.line, "ls dir2 something after");
3175 }
3176
3177 {
3178 auto r = autoComplete("ls dir2asomething immediately after", 8, syntax, true);
3179 std::vector<std::string> e{ "dir2a/" };
3180 ASSERT_TRUE(cmp(r, e));
3181 applyCompletion(r, true, 100, s);
3182 ASSERT_EQ(r.line, "ls dir2a/something immediately after");
3183 }
3184
3185 {
3186 auto r = autoComplete("ls dir2/", std::string::npos, syntax, true);
3187 std::vector<std::string> e{ "dir2/sub21/", "dir2/sub22/" };
3188 ASSERT_TRUE(cmp(r, e));
3189 applyCompletion(r, true, 100, s);
3190 ASSERT_EQ(r.line, "ls dir2/sub2");
3191 auto rr = autoComplete("ls dir2/sub22", std::string::npos, syntax, true);
3192 applyCompletion(rr, true, 100, s);
3193 ASSERT_EQ(rr.line, "ls dir2/sub22/");
3194 }
3195
3196 {
3197 auto r = autoComplete("ls dir2/./", std::string::npos, syntax, true);
3198 std::vector<std::string> e{ "dir2/./sub21/", "dir2/./sub22/" };
3199 ASSERT_TRUE(cmp(r, e));
3200 applyCompletion(r, true, 100, s);
3201 ASSERT_EQ(r.line, "ls dir2/./sub2");
3202 }
3203
3204 {
3205 auto r = autoComplete("ls dir2/..", std::string::npos, syntax, true);
3206 std::vector<std::string> e{ "dir2/../" };
3207 ASSERT_TRUE(cmp(r, e));
3208 applyCompletion(r, true, 100, s);
3209 ASSERT_EQ(r.line, "ls dir2/../");
3210 }
3211
3212 {
3213 auto r = autoComplete("ls dir2/../", std::string::npos, syntax, true);
3214 std::vector<std::string> e{ "dir2/../dir1/", "dir2/../dir2/", "dir2/../dir2a/" };
3215 ASSERT_TRUE(cmp(r, e));
3216 applyCompletion(r, true, 100, s);
3217 ASSERT_EQ(r.line, "ls dir2/../dir");
3218 }
3219
3220 {
3221 auto r = autoComplete("ls dir2/../", std::string::npos, syntax, true);
3222 std::vector<std::string> e{ "dir2/../dir1/", "dir2/../dir2/", "dir2/../dir2a/" };
3223 ASSERT_TRUE(cmp(r, e));
3224 applyCompletion(r, true, 100, s);
3225 ASSERT_EQ(r.line, "ls dir2/../dir");
3226 }
3227
3228 {
3229 auto r = autoComplete("ls dir2a/d", std::string::npos, syntax, true);
3230 applyCompletion(r, true, 100, s);
3231 ASSERT_EQ(r.line, "ls \"dir2a/dir space\"");
3232 auto rr = autoComplete("ls \"dir2a/dir space\"/", std::string::npos, syntax, false);
3233 applyCompletion(rr, true, 100, s);
3234 ASSERT_EQ(rr.line, "ls \"dir2a/dir space/next\"");
3235 }
3236
3237 {
3238 auto r = autoComplete("ls \"dir\"1/", std::string::npos, syntax, true);
3239 applyCompletion(r, true, 100, s);
3240 ASSERT_EQ(r.line, "ls \"dir1/sub1\"");
3241 }
3242
3243 {
3244 auto r = autoComplete("ls dir1/\"../dir2/\"", std::string::npos, syntax, true);
3245 applyCompletion(r, true, 100, s);
3246 ASSERT_EQ(r.line, "ls \"dir1/../dir2/sub2\"");
3247 }
3248
3249 {
3250 auto r = autoComplete("ls /test_autocomplete_meg", std::string::npos, syntax, true);
3251 applyCompletion(r, true, 100, s);
3252 ASSERT_EQ(r.line, "ls /test_autocomplete_megafs/");
3253 r = autoComplete(r.line + "dir2a", std::string::npos, syntax, true);
3254 applyCompletion(r, true, 100, s);
3255 ASSERT_EQ(r.line, "ls /test_autocomplete_megafs/dir2a/");
3256 r = autoComplete(r.line + "d", std::string::npos, syntax, true);
3257 applyCompletion(r, true, 100, s);
3258 ASSERT_EQ(r.line, "ls \"/test_autocomplete_megafs/dir2a/dir space\"");
3259 }
3260
3261 fs::current_path(old_cwd);
3262
3263 }
3264 #endif
3265
3266 #ifdef ENABLE_CHAT
3267
3268 /**
3269 * @brief TEST_F SdkTestChat
3270 *
3271 * Initialize a test scenario by:
3272 *
3273 * - Setting a new contact to chat with
3274 *
3275 * Performs different operations related to chats:
3276 *
3277 * - Fetch the list of available chats
3278 * - Create a group chat
3279 * - Remove a peer from the chat
3280 * - Invite a contact to a chat
3281 * - Get the user-specific URL for the chat
3282 * - Update permissions of an existing peer in a chat
3283 */
TEST_F(SdkTest,SdkTestChat)3284 TEST_F(SdkTest, SdkTestChat)
3285 {
3286 LOG_info << "___TEST Chat___";
3287
3288 ASSERT_NO_FATAL_FAILURE( getMegaApiAux() ); // login + fetchnodes
3289
3290 // --- Send a new contact request ---
3291
3292 string message = "Hi contact. This is a testing message";
3293
3294 mApi[1].contactRequestUpdated = false;
3295 ASSERT_NO_FATAL_FAILURE( inviteContact(mApi[1].email, message, MegaContactRequest::INVITE_ACTION_ADD) );
3296 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
3297 << "Contact request update not received after " << maxTimeout << " seconds";
3298 // if there were too many invitations within a short period of time, the invitation can be rejected by
3299 // the API with `API_EOVERQUOTA = -17` as counter spamming meassure (+500 invites in the last 50 days)
3300
3301 // --- Accept a contact invitation ---
3302
3303 ASSERT_NO_FATAL_FAILURE( getContactRequest(1, false) );
3304
3305 mApi[0].contactRequestUpdated = mApi[1].contactRequestUpdated = false;
3306 ASSERT_NO_FATAL_FAILURE( replyContact(mApi[1].cr.get(), MegaContactRequest::REPLY_ACTION_ACCEPT) );
3307 ASSERT_TRUE( waitForResponse(&mApi[1].contactRequestUpdated) ) // at the target side (auxiliar account)
3308 << "Contact request update not received after " << maxTimeout << " seconds";
3309 ASSERT_TRUE( waitForResponse(&mApi[0].contactRequestUpdated) ) // at the target side (main account)
3310 << "Contact request update not received after " << maxTimeout << " seconds";
3311
3312 mApi[1].cr.reset();
3313
3314
3315 // --- Check list of available chats --- (fetch is done at SetUp())
3316
3317 size_t numChats = mApi[0].chats.size(); // permanent chats cannot be deleted, so they're kept forever
3318
3319
3320 // --- Create a group chat ---
3321
3322 MegaTextChatPeerList *peers;
3323 handle h;
3324 bool group;
3325
3326 h = megaApi[1]->getMyUser()->getHandle();
3327 peers = MegaTextChatPeerList::createInstance();//new MegaTextChatPeerListPrivate();
3328 peers->addPeer(h, PRIV_STANDARD);
3329 group = true;
3330
3331 mApi[1].chatUpdated = false;
3332 mApi[0].requestFlags[MegaRequest::TYPE_CHAT_CREATE] = false;
3333 ASSERT_NO_FATAL_FAILURE( createChat(group, peers) );
3334 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_CREATE]) )
3335 << "Cannot create a new chat";
3336 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Chat creation failed (error: " << mApi[0].lastError << ")";
3337 ASSERT_TRUE( waitForResponse(&mApi[1].chatUpdated )) // at the target side (auxiliar account)
3338 << "Chat update not received after " << maxTimeout << " seconds";
3339
3340 MegaHandle chatid = mApi[0].chatid; // set at onRequestFinish() of chat creation request
3341
3342 delete peers;
3343
3344 // check the new chat information
3345 ASSERT_EQ(mApi[0].chats.size(), ++numChats) << "Unexpected received number of chats";
3346 ASSERT_TRUE(mApi[1].chatUpdated) << "The peer didn't receive notification of the chat creation";
3347
3348
3349 // --- Remove a peer from the chat ---
3350
3351 mApi[1].chatUpdated = false;
3352 mApi[0].requestFlags[MegaRequest::TYPE_CHAT_REMOVE] = false;
3353 megaApi[0]->removeFromChat(chatid, h);
3354 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_REMOVE]) )
3355 << "Chat remove failed after " << maxTimeout << " seconds";
3356 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Removal of chat peer failed (error: " << mApi[0].lastError << ")";
3357 int numpeers = mApi[0].chats[chatid]->getPeerList() ? mApi[0].chats[chatid]->getPeerList()->size() : 0;
3358 ASSERT_EQ(numpeers, 0) << "Wrong number of peers in the list of peers";
3359 ASSERT_TRUE( waitForResponse(&mApi[1].chatUpdated) ) // at the target side (auxiliar account)
3360 << "Didn't receive notification of the peer removal after " << maxTimeout << " seconds";
3361
3362
3363 // --- Invite a contact to a chat ---
3364
3365 mApi[1].chatUpdated = false;
3366 mApi[0].requestFlags[MegaRequest::TYPE_CHAT_INVITE] = false;
3367 megaApi[0]->inviteToChat(chatid, h, PRIV_STANDARD);
3368 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_INVITE]) )
3369 << "Chat invitation failed after " << maxTimeout << " seconds";
3370 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Invitation of chat peer failed (error: " << mApi[0].lastError << ")";
3371 numpeers = mApi[0].chats[chatid]->getPeerList() ? mApi[0].chats[chatid]->getPeerList()->size() : 0;
3372 ASSERT_EQ(numpeers, 1) << "Wrong number of peers in the list of peers";
3373 ASSERT_TRUE( waitForResponse(&mApi[1].chatUpdated) ) // at the target side (auxiliar account)
3374 << "The peer didn't receive notification of the invitation after " << maxTimeout << " seconds";
3375
3376
3377 // --- Get the user-specific URL for the chat ---
3378
3379 mApi[0].requestFlags[MegaRequest::TYPE_CHAT_URL] = false;
3380 megaApi[0]->getUrlChat(chatid);
3381 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_URL]) )
3382 << "Retrieval of chat URL failed after " << maxTimeout << " seconds";
3383 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Retrieval of chat URL failed (error: " << mApi[0].lastError << ")";
3384
3385
3386 // --- Update Permissions of an existing peer in the chat
3387
3388 mApi[1].chatUpdated = false;
3389 mApi[0].requestFlags[MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS] = false;
3390 megaApi[0]->updateChatPermissions(chatid, h, PRIV_RO);
3391 ASSERT_TRUE( waitForResponse(&mApi[0].requestFlags[MegaRequest::TYPE_CHAT_UPDATE_PERMISSIONS]) )
3392 << "Update chat permissions failed after " << maxTimeout << " seconds";
3393 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Update of chat permissions failed (error: " << mApi[0].lastError << ")";
3394 ASSERT_TRUE( waitForResponse(&mApi[1].chatUpdated) ) // at the target side (auxiliar account)
3395 << "The peer didn't receive notification of the invitation after " << maxTimeout << " seconds";
3396
3397 }
3398 #endif
3399
3400 class myMIS : public MegaInputStream
3401 {
3402 public:
3403 int64_t size;
3404 ifstream ifs;
3405
myMIS(const char * filename)3406 myMIS(const char* filename)
3407 : ifs(filename, ios::binary)
3408 {
3409 ifs.seekg(0, ios::end);
3410 size = ifs.tellg();
3411 ifs.seekg(0, ios::beg);
3412 }
getSize()3413 virtual int64_t getSize() { return size; }
3414
read(char * buffer,size_t size)3415 virtual bool read(char *buffer, size_t size) {
3416 if (buffer)
3417 {
3418 ifs.read(buffer, size);
3419 }
3420 else
3421 {
3422 ifs.seekg(size, ios::cur);
3423 }
3424 return !ifs.fail();
3425 }
3426 };
3427
3428
TEST_F(SdkTest,SdkTestFingerprint)3429 TEST_F(SdkTest, SdkTestFingerprint)
3430 {
3431 LOG_info << "___TEST fingerprint stream/file___";
3432
3433 int filesizes[] = { 10, 100, 1000, 10000, 100000, 10000000 };
3434 string expected[] = {
3435 "DAQoBAMCAQQDAgEEAwAAAAAAAAQAypo7",
3436 "DAWQjMO2LBXoNwH_agtF8CX73QQAypo7",
3437 "EAugDFlhW_VTCMboWWFb9VMIxugQAypo7",
3438 "EAhAnWCqOGBx0gGOWe7N6wznWRAQAypo7",
3439 "GA6CGAQFLOwb40BGchttx22PvhZ5gQAypo7",
3440 "GA4CWmAdW1TwQ-bddEIKTmSDv0b2QQAypo7",
3441 };
3442
3443 FSACCESS_CLASS fsa;
3444 string name = "testfile";
3445 LocalPath localname = LocalPath::fromPath(name, fsa);
3446
3447 int value = 0x01020304;
3448 for (int i = sizeof filesizes / sizeof filesizes[0]; i--; )
3449 {
3450
3451 {
3452 ofstream ofs(name.c_str(), ios::binary);
3453 char s[8192];
3454 ofs.rdbuf()->pubsetbuf(s, sizeof s);
3455 for (int j = filesizes[i] / sizeof(value); j-- ; ) ofs.write((char*)&value, sizeof(value));
3456 ofs.write((char*)&value, filesizes[i] % sizeof(value));
3457 }
3458
3459 fsa.setmtimelocal(localname, 1000000000);
3460
3461 string streamfp, filefp;
3462 {
3463 m_time_t mtime = 0;
3464 {
3465 auto nfa = fsa.newfileaccess();
3466 nfa->fopen(localname);
3467 mtime = nfa->mtime;
3468 }
3469
3470 myMIS mis(name.c_str());
3471 streamfp.assign(megaApi[0]->getFingerprint(&mis, mtime));
3472 }
3473
3474 filefp = megaApi[0]->getFingerprint(name.c_str());
3475
3476 ASSERT_EQ(streamfp, filefp);
3477 ASSERT_EQ(streamfp, expected[i]);
3478 }
3479 }
3480
3481
3482
incrementFilename(string & s)3483 static void incrementFilename(string& s)
3484 {
3485 if (s.size() > 2)
3486 {
3487 if (isdigit(s[s.size() - 2]) | !isdigit(s[s.size() - 1]))
3488 {
3489 s += "00";
3490 }
3491 else
3492 {
3493 s[s.size() - 1] += 1;
3494 if (s[s.size() - 1] > '9')
3495 {
3496 s[s.size() - 1] -= 1;
3497 s[s.size() - 2] += 1;
3498 }
3499 }
3500 }
3501 }
3502
3503 struct second_timer
3504 {
3505 m_time_t t;
3506 m_time_t pause_t;
second_timersecond_timer3507 second_timer() { t = m_time(); }
resetsecond_timer3508 void reset () { t = m_time(); }
pausesecond_timer3509 void pause() { pause_t = m_time(); }
resumesecond_timer3510 void resume() { t += m_time() - pause_t; }
elapsedsecond_timer3511 size_t elapsed() { return size_t(m_time() - t); }
3512 };
3513
3514 namespace mega
3515 {
3516 class DebugTestHook
3517 {
3518 public:
3519 static int countdownToOverquota;
3520 static int countdownTo404;
3521 static int countdownTo403;
3522 static int countdownToTimeout;
3523 static bool isRaid;
3524 static bool isRaidKnown;
3525
onSetIsRaid_morechunks(::mega::RaidBufferManager * tbm)3526 static void onSetIsRaid_morechunks(::mega::RaidBufferManager* tbm)
3527 {
3528
3529 unsigned oldvalue = tbm->raidLinesPerChunk;
3530 tbm->raidLinesPerChunk /= 4;
3531 LOG_info << "adjusted raidlinesPerChunk from " << oldvalue << " to " << tbm->raidLinesPerChunk;
3532 }
3533
onHttpReqPost509(HttpReq * req)3534 static bool onHttpReqPost509(HttpReq* req)
3535 {
3536 if (req->type == REQ_BINARY)
3537 {
3538 if (countdownToOverquota-- == 0) {
3539 req->httpstatus = 509;
3540 req->timeleft = 30; // in seconds
3541 req->status = REQ_FAILURE;
3542
3543 LOG_info << "SIMULATING HTTP GET 509 OVERQUOTA";
3544 return true;
3545 }
3546 }
3547 return false;
3548 }
3549
onHttpReqPost404Or403(HttpReq * req)3550 static bool onHttpReqPost404Or403(HttpReq* req)
3551 {
3552 if (req->type == REQ_BINARY)
3553 {
3554 if (countdownTo404-- == 0) {
3555 req->httpstatus = 404;
3556 req->status = REQ_FAILURE;
3557
3558 LOG_info << "SIMULATING HTTP GET 404";
3559 return true;
3560 }
3561 if (countdownTo403-- == 0) {
3562 req->httpstatus = 403;
3563 req->status = REQ_FAILURE;
3564
3565 LOG_info << "SIMULATING HTTP GET 403";
3566 return true;
3567 }
3568 }
3569 return false;
3570 }
3571
3572
onHttpReqPostTimeout(HttpReq * req)3573 static bool onHttpReqPostTimeout(HttpReq* req)
3574 {
3575 if (req->type == REQ_BINARY)
3576 {
3577 if (countdownToTimeout-- == 0) {
3578 req->lastdata = Waiter::ds;
3579 req->status = REQ_INFLIGHT;
3580
3581 LOG_info << "SIMULATING HTTP TIMEOUT (timeout period begins now)";
3582 return true;
3583 }
3584 }
3585 return false;
3586 }
3587
onSetIsRaid(::mega::RaidBufferManager * tbm)3588 static void onSetIsRaid(::mega::RaidBufferManager* tbm)
3589 {
3590 isRaid = tbm->isRaid();
3591 isRaidKnown = true;
3592 }
3593
resetForTests()3594 static bool resetForTests()
3595 {
3596 #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED
3597 globalMegaTestHooks = MegaTestHooks(); // remove any callbacks set in other tests
3598 countdownToOverquota = 3;
3599 countdownTo404 = 5;
3600 countdownTo403 = 10;
3601 countdownToTimeout = 15;
3602 isRaid = false;
3603 isRaidKnown = false;
3604 return true;
3605 #else
3606 return false;
3607 #endif
3608 }
3609
onSetIsRaid_smallchunks10(::mega::RaidBufferManager * tbm)3610 static void onSetIsRaid_smallchunks10(::mega::RaidBufferManager* tbm)
3611 {
3612 tbm->raidLinesPerChunk = 10;
3613 }
3614
3615 };
3616
3617 int DebugTestHook::countdownToOverquota = 3;
3618 bool DebugTestHook::isRaid = false;
3619 bool DebugTestHook::isRaidKnown = false;
3620 int DebugTestHook::countdownTo404 = 5;
3621 int DebugTestHook::countdownTo403 = 10;
3622 int DebugTestHook::countdownToTimeout = 15;
3623
3624 }
3625
3626
3627 /**
3628 * @brief TEST_F SdkTestCloudraidTransfers
3629 *
3630 * - Download our well-known cloudraid file with standard settings
3631 * - Download our well-known cloudraid file, but this time with small chunk sizes and periodically pausing and unpausing
3632 * - Download our well-known cloudraid file, but this time with small chunk sizes and periodically destrying the megaApi object, then recreating and Resuming (with session token)
3633 *
3634 */
3635
3636 #ifdef DEBUG
TEST_F(SdkTest,SdkTestCloudraidTransfers)3637 TEST_F(SdkTest, SdkTestCloudraidTransfers)
3638 {
3639 LOG_info << "___TEST Cloudraid transfers___";
3640
3641 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
3642
3643 MegaNode *rootnode = megaApi[0]->getRootNode();
3644
3645 ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", rootnode));
3646 MegaHandle imported_file_handle = mApi[0].h;
3647
3648 MegaNode *nimported = megaApi[0]->getNodeByHandle(imported_file_handle);
3649
3650
3651 string filename = "./cloudraid_downloaded_file.sdktest";
3652 deleteFile(filename.c_str());
3653
3654 // plain cloudraid download
3655 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
3656 megaApi[0]->startDownload(nimported, filename.c_str());
3657 ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600))
3658 << "Download cloudraid transfer failed after " << maxTimeout << " seconds";
3659 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")";
3660
3661
3662 // cloudraid download with periodic pause and resume
3663
3664 incrementFilename(filename);
3665 deleteFile(filename.c_str());
3666
3667 // smaller chunk sizes so we can get plenty of pauses
3668 #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED
3669 globalMegaTestHooks.onSetIsRaid = ::mega::DebugTestHook::onSetIsRaid_morechunks;
3670 #endif
3671
3672 // plain cloudraid download
3673 {
3674 onTransferUpdate_progress = 0;
3675 onTransferUpdate_filesize = 0;
3676 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
3677 megaApi[0]->startDownload(nimported, filename.c_str());
3678
3679 m_off_t lastprogress = 0, pausecount = 0;
3680 second_timer t;
3681 while (t.elapsed() < 60 && (onTransferUpdate_filesize == 0 || onTransferUpdate_progress < onTransferUpdate_filesize))
3682 {
3683 if (onTransferUpdate_progress > lastprogress)
3684 {
3685 megaApi[0]->pauseTransfers(true);
3686 pausecount += 1;
3687 WaitMillisec(100);
3688 megaApi[0]->pauseTransfers(false);
3689 lastprogress = onTransferUpdate_progress;
3690 }
3691 WaitMillisec(100);
3692 }
3693 ASSERT_LT(t.elapsed(), 60u) << "timed out downloading cloudraid file";
3694 ASSERT_GE(onTransferUpdate_filesize, 0u);
3695 ASSERT_TRUE(onTransferUpdate_progress == onTransferUpdate_filesize);
3696 ASSERT_GE(pausecount, 3);
3697 ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 30))<< "Download cloudraid transfer with pauses failed";
3698 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")";
3699 }
3700
3701
3702 incrementFilename(filename);
3703 deleteFile(filename.c_str());
3704
3705 // cloudraid download with periodic full exit and resume from session ID
3706 // plain cloudraid download
3707 {
3708 megaApi[0]->setMaxDownloadSpeed(32 * 1024 * 1024 * 8 / 30); // should take 30 seconds, not counting exit/resume session
3709 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
3710 megaApi[0]->startDownload(nimported, filename.c_str());
3711
3712 std::string sessionId = megaApi[0]->dumpSession();
3713
3714 onTransferUpdate_progress = 0;// updated in callbacks
3715 onTransferUpdate_filesize = 0;
3716 m_off_t lastprogress = 0;
3717 unsigned exitresumecount = 0;
3718 second_timer t;
3719 auto initialOnTranferFinishedCount = onTranferFinishedCount;
3720 auto lastOnTranferFinishedCount = onTranferFinishedCount;
3721 while (t.elapsed() < 180 && onTranferFinishedCount < initialOnTranferFinishedCount + 2)
3722 {
3723 if (onTransferUpdate_progress > lastprogress + onTransferUpdate_filesize/6)
3724 {
3725 megaApi[0].reset();
3726 exitresumecount += 1;
3727 WaitMillisec(100);
3728
3729 megaApi[0].reset(new MegaApi(APP_KEY.c_str(), megaApiCacheFolder(0).c_str(), USER_AGENT.c_str(), THREADS_PER_MEGACLIENT));
3730 mApi[0].megaApi = megaApi[0].get();
3731 megaApi[0]->setLogLevel(MegaApi::LOG_LEVEL_DEBUG);
3732 megaApi[0]->addListener(this);
3733 megaApi[0]->setMaxDownloadSpeed(32 * 1024 * 1024 * 8 / 30); // should take 30 seconds, not counting exit/resume session
3734
3735 t.pause();
3736 ASSERT_NO_FATAL_FAILURE(resumeSession(sessionId.c_str()));
3737 ASSERT_NO_FATAL_FAILURE(fetchnodes(0));
3738 t.resume();
3739
3740 lastprogress = onTransferUpdate_progress;
3741 }
3742 else if (onTranferFinishedCount > lastOnTranferFinishedCount)
3743 {
3744 t.reset();
3745 lastOnTranferFinishedCount = onTranferFinishedCount;
3746 deleteFile(filename.c_str());
3747 onTransferUpdate_progress = 0;
3748 onTransferUpdate_filesize = 0;
3749 lastprogress = 0;
3750 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
3751 megaApi[0]->startDownload(nimported, filename.c_str());
3752 }
3753 WaitMillisec(1);
3754 }
3755 ASSERT_EQ(onTransferUpdate_progress, onTransferUpdate_filesize);
3756 ASSERT_EQ(initialOnTranferFinishedCount + 2, onTranferFinishedCount);
3757 ASSERT_GE(exitresumecount, 6u);
3758 ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 1)) << "Download cloudraid transfer with pauses failed";
3759 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")";
3760 }
3761
3762 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
3763 }
3764 #endif
3765
3766
3767 /**
3768 * @brief TEST_F SdkTestCloudraidTransferWithConnectionFailures
3769 *
3770 * Download a cloudraid file but with a connection failing with http errors 404 and 403. The download should recover from the problems in 5 channel mode
3771 *
3772 */
3773
3774 #ifdef DEBUG
TEST_F(SdkTest,SdkTestCloudraidTransferWithConnectionFailures)3775 TEST_F(SdkTest, SdkTestCloudraidTransferWithConnectionFailures)
3776 {
3777 LOG_info << "___TEST Cloudraid transfers___";
3778
3779 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
3780
3781 std::unique_ptr<MegaNode> rootnode{megaApi[0]->getRootNode()};
3782
3783 ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", rootnode.get()));
3784 MegaHandle imported_file_handle = mApi[0].h;
3785 std::unique_ptr<MegaNode> nimported{megaApi[0]->getNodeByHandle(imported_file_handle)};
3786
3787
3788 string filename = "./cloudraid_downloaded_file.sdktest";
3789 deleteFile(filename.c_str());
3790
3791 // set up for 404 and 403 errors
3792 // smaller chunk sizes so we can get plenty of pauses
3793 DebugTestHook::countdownTo404 = 5;
3794 DebugTestHook::countdownTo403 = 12;
3795 #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED
3796 globalMegaTestHooks.onHttpReqPost = DebugTestHook::onHttpReqPost404Or403;
3797 globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid_morechunks;
3798 #endif
3799
3800 // plain cloudraid download
3801 {
3802 onTransferUpdate_progress = 0;
3803 onTransferUpdate_filesize = 0;
3804 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
3805 megaApi[0]->startDownload(nimported.get(), filename.c_str());
3806
3807 ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 180)) << "Cloudraid download with 404 and 403 errors time out (180 seconds)";
3808 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")";
3809 ASSERT_GE(onTransferUpdate_filesize, 0u);
3810 ASSERT_TRUE(onTransferUpdate_progress == onTransferUpdate_filesize);
3811 ASSERT_LT(DebugTestHook::countdownTo404, 0);
3812 ASSERT_LT(DebugTestHook::countdownTo403, 0);
3813 }
3814
3815
3816 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
3817 }
3818 #endif
3819
3820
3821 /**
3822 * @brief TEST_F SdkTestCloudraidTransferWithConnectionFailures
3823 *
3824 * Download a cloudraid file but with a connection failing with http errors 404 and 403. The download should recover from the problems in 5 channel mode
3825 *
3826 */
3827
3828 #ifdef DEBUG
TEST_F(SdkTest,SdkTestCloudraidTransferWithSingleChannelTimeouts)3829 TEST_F(SdkTest, SdkTestCloudraidTransferWithSingleChannelTimeouts)
3830 {
3831 LOG_info << "___TEST Cloudraid transfers___";
3832
3833 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
3834
3835 std::unique_ptr<MegaNode> rootnode{megaApi[0]->getRootNode()};
3836
3837 ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", rootnode.get()));
3838 MegaHandle imported_file_handle = mApi[0].h;
3839 std::unique_ptr<MegaNode> nimported{megaApi[0]->getNodeByHandle(imported_file_handle)};
3840
3841
3842 string filename = "./cloudraid_downloaded_file.sdktest";
3843 deleteFile(filename.c_str());
3844
3845 // set up for 404 and 403 errors
3846 // smaller chunk sizes so we can get plenty of pauses
3847 DebugTestHook::countdownToTimeout = 15;
3848 #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED
3849 globalMegaTestHooks.onHttpReqPost = DebugTestHook::onHttpReqPostTimeout;
3850 globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid_morechunks;
3851 #endif
3852
3853 // plain cloudraid download
3854 {
3855 onTransferUpdate_progress = 0;
3856 onTransferUpdate_filesize = 0;
3857 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
3858 megaApi[0]->startDownload(nimported.get(), filename.c_str());
3859
3860 ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 180)) << "Cloudraid download with timeout errors timed out (180 seconds)";
3861 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the cloudraid file (error: " << mApi[0].lastError << ")";
3862 ASSERT_GE(onTransferUpdate_filesize, 0u);
3863 ASSERT_EQ(onTransferUpdate_progress, onTransferUpdate_filesize);
3864 ASSERT_LT(DebugTestHook::countdownToTimeout, 0);
3865 }
3866 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
3867 }
3868 #endif
3869
3870
3871
3872 /**
3873 * @brief TEST_F SdkTestOverquotaNonCloudraid
3874 *
3875 * Induces a simulated overquota error during a conventional download. Confirms the download stops, pauses, and resumes.
3876 *
3877 */
3878
3879 #ifdef DEBUG
TEST_F(SdkTest,SdkTestOverquotaNonCloudraid)3880 TEST_F(SdkTest, SdkTestOverquotaNonCloudraid)
3881 {
3882 LOG_info << "___TEST SdkTestOverquotaNonCloudraid";
3883
3884 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
3885
3886 // make a file to download, and upload so we can pull it down
3887 std::unique_ptr<MegaNode> rootnode{megaApi[0]->getRootNode()};
3888 deleteFile(UPFILE);
3889 createFile(UPFILE, true);
3890 mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD] = false;
3891 megaApi[0]->startUpload(UPFILE.c_str(), rootnode.get());
3892 ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD], 600))
3893 << "Upload transfer failed after " << 600 << " seconds";
3894 std::unique_ptr<MegaNode> n1{megaApi[0]->getNodeByHandle(mApi[0].h)};
3895 ASSERT_NE(n1.get(), ((::mega::MegaNode *)NULL));
3896
3897 // set up to simulate 509 error
3898 DebugTestHook::isRaid = false;
3899 DebugTestHook::isRaidKnown = false;
3900 DebugTestHook::countdownToOverquota = 3;
3901 #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED
3902 globalMegaTestHooks.onHttpReqPost = DebugTestHook::onHttpReqPost509;
3903 globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid;
3904 #endif
3905
3906 // download - we should see a 30 second pause for 509 processing in the middle
3907 string filename2 = "./" + DOWNFILE;
3908 deleteFile(filename2);
3909 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
3910 megaApi[0]->startDownload(n1.get(), filename2.c_str());
3911
3912 // get to 30 sec pause point
3913 second_timer t;
3914 while (t.elapsed() < 30 && DebugTestHook::countdownToOverquota >= 0)
3915 {
3916 WaitMillisec(1000);
3917 }
3918 ASSERT_TRUE(DebugTestHook::isRaidKnown);
3919 ASSERT_FALSE(DebugTestHook::isRaid);
3920
3921 // ok so now we should see no more http requests sent for 30 seconds. Test 20 for reliable testing
3922 int originalcount = DebugTestHook::countdownToOverquota;
3923 second_timer t2;
3924 while (t2.elapsed() < 20)
3925 {
3926 WaitMillisec(1000);
3927 }
3928 ASSERT_TRUE(DebugTestHook::countdownToOverquota == originalcount);
3929
3930 // Now wait for the file to finish
3931
3932 ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600))
3933 << "Download transfer failed after " << maxTimeout << " seconds";
3934 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")";
3935
3936 ASSERT_LT(DebugTestHook::countdownToOverquota, 0);
3937 ASSERT_LT(DebugTestHook::countdownToOverquota, originalcount); // there should have been more http activity after the wait
3938
3939 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
3940 }
3941 #endif
3942
3943
3944 /**
3945 * @brief TEST_F SdkTestOverquotaNonCloudraid
3946 *
3947 * use the hooks to simulate an overquota condition while running a raid download transfer, and check the handling
3948 *
3949 */
3950
3951 #ifdef DEBUG
TEST_F(SdkTest,SdkTestOverquotaCloudraid)3952 TEST_F(SdkTest, SdkTestOverquotaCloudraid)
3953 {
3954 LOG_info << "___TEST SdkTestOverquotaCloudraid";
3955
3956 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
3957
3958 ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", megaApi[0]->getRootNode()));
3959 MegaHandle imported_file_handle = mApi[0].h;
3960 MegaNode *nimported = megaApi[0]->getNodeByHandle(imported_file_handle);
3961
3962 // set up to simulate 509 error
3963 DebugTestHook::isRaid = false;
3964 DebugTestHook::isRaidKnown = false;
3965 DebugTestHook::countdownToOverquota = 8;
3966 #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED
3967 globalMegaTestHooks.onHttpReqPost = DebugTestHook::onHttpReqPost509;
3968 globalMegaTestHooks.onSetIsRaid = DebugTestHook::onSetIsRaid;
3969 #endif
3970
3971 // download - we should see a 30 second pause for 509 processing in the middle
3972 string filename2 = "./" + DOWNFILE;
3973 deleteFile(filename2);
3974 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
3975 megaApi[0]->startDownload(nimported, filename2.c_str());
3976
3977 // get to 30 sec pause point
3978 second_timer t;
3979 while (t.elapsed() < 30 && DebugTestHook::countdownToOverquota >= 0)
3980 {
3981 WaitMillisec(1000);
3982 }
3983 ASSERT_TRUE(DebugTestHook::isRaidKnown);
3984 ASSERT_TRUE(DebugTestHook::isRaid);
3985
3986 // ok so now we should see no more http requests sent for 30 seconds. Test 20 for reliablilty
3987 int originalcount = DebugTestHook::countdownToOverquota;
3988 second_timer t2;
3989 while (t2.elapsed() < 20)
3990 {
3991 WaitMillisec(1000);
3992 }
3993 ASSERT_EQ(DebugTestHook::countdownToOverquota, originalcount);
3994
3995 // Now wait for the file to finish
3996
3997 ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD], 600))
3998 << "Download transfer failed after " << maxTimeout << " seconds";
3999 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the file (error: " << mApi[0].lastError << ")";
4000
4001 ASSERT_LT(DebugTestHook::countdownToOverquota, 0);
4002 ASSERT_LT(DebugTestHook::countdownToOverquota, originalcount); // there should have been more http activity after the wait
4003
4004 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
4005 }
4006 #endif
4007
4008
4009 struct CheckStreamedFile_MegaTransferListener : public MegaTransferListener
4010 {
4011 typedef ::mega::byte byte;
4012
4013 size_t reserved;
4014 size_t receiveBufPos;
4015 size_t file_start_offset;
4016 byte* receiveBuf;
4017 bool completedSuccessfully;
4018 bool completedUnsuccessfully;
4019 MegaError* completedUnsuccessfullyError;
4020 byte* compareDecryptedData;
4021 bool comparedEqual;
4022
4023
CheckStreamedFile_MegaTransferListenerCheckStreamedFile_MegaTransferListener4024 CheckStreamedFile_MegaTransferListener(size_t receiveStartPoint, size_t receiveSizeExpected, byte* fileCompareData)
4025 : reserved(0)
4026 , receiveBufPos(0)
4027 , file_start_offset(0)
4028 , receiveBuf(NULL)
4029 , completedSuccessfully(false)
4030 , completedUnsuccessfully(false)
4031 , completedUnsuccessfullyError(NULL)
4032 , compareDecryptedData(fileCompareData)
4033 , comparedEqual(true)
4034 {
4035 file_start_offset = receiveStartPoint;
4036 reserved = receiveSizeExpected;
4037 receiveBuf = new byte[reserved];
4038 compareDecryptedData = fileCompareData;
4039 }
4040
~CheckStreamedFile_MegaTransferListenerCheckStreamedFile_MegaTransferListener4041 ~CheckStreamedFile_MegaTransferListener()
4042 {
4043 delete[] receiveBuf;
4044 }
4045
onTransferStartCheckStreamedFile_MegaTransferListener4046 void onTransferStart(MegaApi *api, MegaTransfer *transfer) override
4047 {
4048 }
onTransferFinishCheckStreamedFile_MegaTransferListener4049 void onTransferFinish(MegaApi* api, MegaTransfer *transfer, MegaError* error) override
4050 {
4051 if (error && error->getErrorCode() != API_OK)
4052 {
4053 ((error->getErrorCode() == API_EARGS && reserved == 0) ? completedSuccessfully : completedUnsuccessfully) = true;
4054 completedUnsuccessfullyError = error->copy();
4055 }
4056 else
4057 {
4058 if (0 != memcmp(receiveBuf, compareDecryptedData + file_start_offset, receiveBufPos))
4059 comparedEqual = false;
4060 completedSuccessfully = true;
4061 }
4062 }
onTransferUpdateCheckStreamedFile_MegaTransferListener4063 void onTransferUpdate(MegaApi *api, MegaTransfer *transfer) override
4064 {
4065 }
onTransferTemporaryErrorCheckStreamedFile_MegaTransferListener4066 void onTransferTemporaryError(MegaApi *api, MegaTransfer * /*transfer*/, MegaError* error) override
4067 {
4068 ostringstream msg;
4069 msg << "onTransferTemporaryError: " << (error ? error->getErrorString() : "NULL") << endl;
4070 api->log(MegaApi::LOG_LEVEL_WARNING, msg.str().c_str());
4071 }
onTransferDataCheckStreamedFile_MegaTransferListener4072 bool onTransferData(MegaApi *api, MegaTransfer *transfer, char *buffer, size_t size) override
4073 {
4074 assert(receiveBufPos + size <= reserved);
4075 memcpy(receiveBuf + receiveBufPos, buffer, size);
4076 receiveBufPos += size;
4077
4078 if (0 != memcmp(receiveBuf, compareDecryptedData + file_start_offset, receiveBufPos))
4079 comparedEqual = false;
4080
4081 return true;
4082 }
4083 };
4084
4085
StreamRaidFilePart(MegaApi * megaApi,m_off_t start,m_off_t end,bool raid,bool smallpieces,MegaNode * raidFileNode,MegaNode * nonRaidFileNode,::mega::byte * filecomparedata)4086 CheckStreamedFile_MegaTransferListener* StreamRaidFilePart(MegaApi* megaApi, m_off_t start, m_off_t end, bool raid, bool smallpieces, MegaNode* raidFileNode, MegaNode*nonRaidFileNode, ::mega::byte* filecomparedata)
4087 {
4088 assert(raidFileNode && nonRaidFileNode);
4089 LOG_info << "stream test ---------------------------------------------------" << start << " to " << end << "(len " << end - start << ") " << (raid ? " RAID " : " non-raid ") << (raid ? (smallpieces ? " smallpieces " : "normalpieces") : "");
4090
4091 #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED
4092 globalMegaTestHooks.onSetIsRaid = smallpieces ? &DebugTestHook::onSetIsRaid_smallchunks10 : NULL;
4093 #endif
4094
4095 CheckStreamedFile_MegaTransferListener* p = new CheckStreamedFile_MegaTransferListener(size_t(start), size_t(end - start), filecomparedata);
4096 megaApi->setStreamingMinimumRate(0);
4097 megaApi->startStreaming(raid ? raidFileNode : nonRaidFileNode, start, end - start, p);
4098 return p;
4099 }
4100
4101
4102
4103 /**
4104 * @brief TEST_F SdkCloudraidStreamingSoakTest
4105 *
4106 * Stream random portions of the well-known file for 10 minutes, while randomly varying
4107 * raid / non-raid
4108 * front/end/middle (especial attention to first and last raidlines, and varying start/end within a raidline)
4109 * large piece / small piece
4110 * small raid chunk sizes (so small pieces of file don't just load in one request per connection) / normal sizes
4111 *
4112 */
4113
4114
TEST_F(SdkTest,SdkCloudraidStreamingSoakTest)4115 TEST_F(SdkTest, SdkCloudraidStreamingSoakTest)
4116 {
4117 LOG_info << "___TEST SdkCloudraidStreamingSoakTest";
4118
4119 #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED
4120 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
4121 #endif
4122
4123 // ensure we have our standard raid test file
4124 ASSERT_NO_FATAL_FAILURE(importPublicLink(0, "https://mega.nz/#!zAJnUTYD!8YE5dXrnIEJ47NdDfFEvqtOefhuDMphyae0KY5zrhns", std::unique_ptr<MegaNode>{megaApi[0]->getRootNode()}.get()));
4125 MegaHandle imported_file_handle = mApi[0].h;
4126 MegaNode *nimported = megaApi[0]->getNodeByHandle(imported_file_handle);
4127
4128 MegaNode *rootnode = megaApi[0]->getRootNode();
4129
4130 // get the file, and upload as non-raid
4131 string filename2 = "./" + DOWNFILE;
4132 deleteFile(filename2);
4133
4134 mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD] = false;
4135 megaApi[0]->startDownload(nimported, filename2.c_str());
4136 ASSERT_TRUE(waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_DOWNLOAD])) << "Setup transfer failed after " << maxTimeout << " seconds";
4137 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot download the initial file (error: " << mApi[0].lastError << ")";
4138
4139 char raidchar = 0;
4140 char nonraidchar = 'M';
4141
4142 string filename3 = filename2;
4143 incrementFilename(filename3);
4144 filename3 += ".neverseenbefore";
4145 deleteFile(filename3);
4146 copyFile(filename2, filename3);
4147 {
4148 fstream fs(filename3.c_str(), ios::in | ios::out | ios::binary);
4149 raidchar = (char)fs.get();
4150 fs.seekg(0);
4151 fs.put('M'); // we have to edit the file before upload, as Mega is too clever and will skip actual upload otherwise
4152 fs.flush();
4153 }
4154
4155 // actual upload
4156 mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD] = false;
4157 megaApi[0]->startUpload(filename3.data(), rootnode);
4158 waitForResponse(&mApi[0].transferFlags[MegaTransfer::TYPE_UPLOAD]);
4159
4160 ASSERT_EQ(MegaError::API_OK, mApi[0].lastError) << "Cannot upload a test file (error: " << mApi[0].lastError << ")";
4161
4162 MegaNode *nonRaidNode = megaApi[0]->getNodeByHandle(mApi[0].h);
4163
4164 int64_t filesize = getFilesize(filename2);
4165 std::ifstream compareDecryptedFile(filename2.c_str(), ios::binary);
4166 std::vector<::mega::byte> compareDecryptedData(static_cast<size_t>(filesize));
4167 compareDecryptedFile.read((char*)compareDecryptedData.data(), filesize);
4168
4169 m_time_t starttime = m_time();
4170 int seconds_to_test_for = gRunningInCI ? 60 : 60 * 10;
4171
4172 // ok loop for 10 minutes (one munite under jenkins)
4173 srand(unsigned(starttime));
4174 int randomRunsDone = 0;
4175 m_off_t randomRunsBytes = 0;
4176 for (; m_time() - starttime < seconds_to_test_for; ++randomRunsDone)
4177 {
4178
4179 int testtype = rand() % 10;
4180 int smallpieces = rand() % 2;
4181 int nonraid = rand() % 4 == 1;
4182
4183 compareDecryptedData[0] = ::mega::byte(nonraid ? nonraidchar : raidchar);
4184
4185 m_off_t start = 0, end = 0;
4186
4187 if (testtype < 3) // front of file
4188 {
4189 start = std::max<int>(0, rand() % 5 * 10240 - 1024);
4190 end = start + rand() % 5 * 10240;
4191 }
4192 else if (testtype == 3) // within 1, 2, or 3 raidlines
4193 {
4194 start = std::max<int>(0, rand() % 5 * 10240 - 1024);
4195 end = start + rand() % (3 * RAIDLINE);
4196 }
4197 else if (testtype < 8) // end of file
4198 {
4199 end = std::min<m_off_t>(32620740, 32620740 + RAIDLINE - rand() % (2 * RAIDLINE));
4200 start = end - rand() % 5 * 10240;
4201 }
4202 else if (testtype == 8) // 0 size [seems this is not allowed at intermediate layer now - EARGS]
4203 {
4204 start = rand() % 32620740;
4205 end = start;
4206 }
4207 else // decent piece of the file
4208 {
4209 int pieceSize = gRunningInCI ? 50000 : 5000000;
4210 start = rand() % pieceSize;
4211 int n = pieceSize / (smallpieces ? 100 : 1);
4212 end = start + n + rand() % n;
4213 }
4214
4215 // seems 0 size not allowed now - make sure we get at least 1 byte
4216 if (start == end)
4217 {
4218 if (start > 0) start -= 1;
4219 else end += 1;
4220 }
4221 randomRunsBytes += end - start;
4222
4223 LOG_info << "beginning stream test, " << start << " to " << end << "(len " << end - start << ") " << (nonraid ? " non-raid " : " RAID ") << (!nonraid ? (smallpieces ? " smallpieces " : "normalpieces") : "");
4224
4225 CheckStreamedFile_MegaTransferListener* p = StreamRaidFilePart(megaApi[0].get(), start, end, !nonraid, smallpieces, nimported, nonRaidNode, compareDecryptedData.data());
4226
4227 for (unsigned i = 0; p->comparedEqual; ++i)
4228 {
4229 WaitMillisec(100);
4230 if (p->completedUnsuccessfully)
4231 {
4232 ASSERT_FALSE(p->completedUnsuccessfully) << " on random run " << randomRunsDone << ", download failed: " << start << " to " << end << ", "
4233 << (nonraid?"nonraid":"raid") << ", " << (smallpieces?"small pieces":"normal size pieces")
4234 << ", reported error: " << (p->completedUnsuccessfullyError ? p->completedUnsuccessfullyError->getErrorCode() : 0)
4235 << " " << (p->completedUnsuccessfullyError ? p->completedUnsuccessfullyError->getErrorString() : "NULL");
4236 break;
4237 }
4238 else if (p->completedSuccessfully)
4239 {
4240 break;
4241 }
4242 else if (i > maxTimeout * 10)
4243 {
4244 ASSERT_TRUE(i <= maxTimeout * 10) << "download took too long, more than " << maxTimeout << " seconds. Is the free transfer quota exhausted?";
4245 break;
4246 }
4247 }
4248 ASSERT_TRUE(p->comparedEqual);
4249
4250 delete p;
4251
4252 }
4253
4254 ASSERT_GT(randomRunsDone, (gRunningInCI ? 10 : 100));
4255
4256 ostringstream msg;
4257 msg << "Streaming test downloaded " << randomRunsDone << " samples of the file from random places and sizes, " << randomRunsBytes << " bytes total" << endl;
4258 megaApi[0]->log(MegaApi::LOG_LEVEL_DEBUG, msg.str().c_str());
4259
4260 delete nimported;
4261 delete nonRaidNode;
4262 delete rootnode;
4263
4264 #ifdef MEGASDK_DEBUG_TEST_HOOKS_ENABLED
4265 ASSERT_TRUE(DebugTestHook::resetForTests()) << "SDK test hooks are not enabled in release mode";
4266 #endif
4267 }
4268
TEST_F(SdkTest,SdkRecentsTest)4269 TEST_F(SdkTest, SdkRecentsTest)
4270 {
4271 LOG_info << "___TEST SdkRecentsTest___";
4272
4273 MegaNode *rootnode = megaApi[0]->getRootNode();
4274
4275 deleteFile(UPFILE);
4276 deleteFile(DOWNFILE);
4277
4278 string filename1 = UPFILE;
4279 createFile(filename1, false);
4280 auto err = synchronousStartUpload(0, filename1.c_str(), rootnode);
4281 ASSERT_EQ(MegaError::API_OK, err) << "Cannot upload a test file (error: " << err << ")";
4282
4283 ofstream f(filename1);
4284 f << "update";
4285 f.close();
4286
4287 err = synchronousStartUpload(0, filename1.c_str(), rootnode);
4288 ASSERT_EQ(MegaError::API_OK, err) << "Cannot upload an updated test file (error: " << err << ")";
4289
4290 synchronousCatchup(0);
4291
4292 string filename2 = DOWNFILE;
4293 createFile(filename2, false);
4294
4295 err = synchronousStartUpload(0, filename2.c_str(), rootnode);
4296 ASSERT_EQ(MegaError::API_OK, err) << "Cannot upload a test file2 (error: " << err << ")";
4297
4298 ofstream f2(filename2);
4299 f2 << "update";
4300 f2.close();
4301
4302 err = synchronousStartUpload(0, filename2.c_str(), rootnode);
4303 ASSERT_EQ(MegaError::API_OK, err) << "Cannot upload an updated test file2 (error: " << err << ")";
4304
4305 synchronousCatchup(0);
4306
4307
4308 std::unique_ptr<MegaRecentActionBucketList> buckets{megaApi[0]->getRecentActions(1, 10)};
4309
4310 ostringstream logMsg;
4311 for (int i = 0; i < buckets->size(); ++i)
4312 {
4313 logMsg << "bucket " << to_string(i) << endl;
4314 megaApi[0]->log(MegaApi::LOG_LEVEL_INFO, logMsg.str().c_str());
4315 auto bucket = buckets->get(i);
4316 for (int j = 0; j < buckets->get(i)->getNodes()->size(); ++j)
4317 {
4318 auto node = bucket->getNodes()->get(j);
4319 logMsg << node->getName() << " " << node->getCreationTime() << " " << bucket->getTimestamp() << " " << bucket->getParentHandle() << " " << bucket->isUpdate() << " " << bucket->isMedia() << endl;
4320 megaApi[0]->log(MegaApi::LOG_LEVEL_DEBUG, logMsg.str().c_str());
4321 }
4322 }
4323
4324 ASSERT_TRUE(buckets != nullptr);
4325 ASSERT_TRUE(buckets->size() > 0);
4326 ASSERT_TRUE(buckets->get(0)->getNodes()->size() > 1);
4327 ASSERT_EQ(DOWNFILE, string(buckets->get(0)->getNodes()->get(0)->getName()));
4328 ASSERT_EQ(UPFILE, string(buckets->get(0)->getNodes()->get(1)->getName()));
4329 }
4330
TEST_F(SdkTest,SdkGetCountryCallingCodes)4331 TEST_F(SdkTest, SdkGetCountryCallingCodes)
4332 {
4333 LOG_info << "___TEST SdkGetCountryCallingCodes___";
4334
4335 getCountryCallingCodes();
4336 ASSERT_NE(nullptr, stringListMap);
4337 ASSERT_GT(stringListMap->size(), 0);
4338 // sanity check a few country codes
4339 const MegaStringList* const nz = stringListMap->get("NZ");
4340 ASSERT_NE(nullptr, nz);
4341 ASSERT_EQ(1, nz->size());
4342 ASSERT_EQ(0, strcmp("64", nz->get(0)));
4343 const MegaStringList* const de = stringListMap->get("DE");
4344 ASSERT_NE(nullptr, de);
4345 ASSERT_EQ(1, de->size());
4346 ASSERT_EQ(0, strcmp("49", de->get(0)));
4347 }
4348
TEST_F(SdkTest,SdkGetRegisteredContacts)4349 TEST_F(SdkTest, SdkGetRegisteredContacts)
4350 {
4351 LOG_info << "___TEST SdkGetRegisteredContacts___";
4352
4353 const std::string js1 = "+0000000010";
4354 const std::string js2 = "+0000000011";
4355 const std::map<std::string, std::string> contacts{
4356 {js1, "John Smith"}, // sms verified
4357 {js2, "John Smith"}, // sms verified
4358 {"+640", "John Smith"}, // not sms verified
4359 };
4360 getRegisteredContacts(contacts);
4361 ASSERT_NE(nullptr, stringTable);
4362 ASSERT_EQ(2, stringTable->size());
4363
4364 // repacking and sorting result
4365 using row_t = std::tuple<std::string, std::string, std::string>;
4366 std::vector<row_t> table;
4367 for (int i = 0; i < stringTable->size(); ++i)
4368 {
4369 const MegaStringList* const stringList = stringTable->get(i);
4370 ASSERT_EQ(3, stringList->size());
4371 table.emplace_back(stringList->get(0), stringList->get(1), stringList->get(2));
4372 }
4373
4374 std::sort(table.begin(), table.end(), [](const row_t& lhs, const row_t& rhs)
4375 {
4376 return std::get<0>(lhs) < std::get<0>(rhs);
4377 });
4378
4379 // Check johnsmith1
4380 ASSERT_EQ(js1, std::get<0>(table[0])); // eud
4381 ASSERT_GT(std::get<1>(table[0]).size(), 0u); // id
4382 ASSERT_EQ(js1, std::get<2>(table[0])); // ud
4383
4384 // Check johnsmith2
4385 ASSERT_EQ(js2, std::get<0>(table[1])); // eud
4386 ASSERT_GT(std::get<1>(table[1]).size(), 0u); // id
4387 ASSERT_EQ(js2, std::get<2>(table[1])); // ud
4388 }
4389
TEST_F(SdkTest,DISABLED_invalidFileNames)4390 TEST_F(SdkTest, DISABLED_invalidFileNames)
4391 {
4392 LOG_info << "___TEST invalidFileNames___";
4393
4394 FSACCESS_CLASS fsa;
4395 auto aux = LocalPath::fromPath(fs::current_path().u8string(), fsa);
4396
4397 #if defined (__linux__) || defined (__ANDROID__)
4398 if (fileSystemAccess.getlocalfstype(aux) == FS_EXT)
4399 {
4400 // Escape set of characters and check if it's the expected one
4401 const char *name = megaApi[0]->escapeFsIncompatible("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", fs::current_path().c_str());
4402 ASSERT_TRUE (!strcmp(name, "!\"#$%&'()*+,-.%2f:;<=>?@[\\]^_`{|}~"));
4403 delete [] name;
4404
4405 // Unescape set of characters and check if it's the expected one
4406 name = megaApi[0]->unescapeFsIncompatible("%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d"
4407 "%2e%2f%30%31%32%33%34%35%36%37"
4408 "%38%39%3a%3b%3c%3d%3e%3f%40%5b"
4409 "%5c%5d%5e%5f%60%7b%7c%7d%7e",
4410 fs::current_path().c_str());
4411
4412 ASSERT_TRUE(!strcmp(name, "%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e"
4413 "/%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e"
4414 "%3f%40%5b%5c%5d%5e%5f%60%7b%7c%7d%7e"));
4415 delete [] name;
4416 }
4417 #elif defined (__APPLE__) || defined (USE_IOS)
4418 if (fileSystemAccess.getlocalfstype(aux) == FS_APFS
4419 || fileSystemAccess.getlocalfstype(aux) == FS_HFS)
4420 {
4421 // Escape set of characters and check if it's the expected one
4422 const char *name = megaApi[0]->escapeFsIncompatible("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", fs::current_path().c_str());
4423 ASSERT_TRUE (!strcmp(name, "!\"#$%&'()*+,-./%3a;<=>?@[\\]^_`{|}~"));
4424 delete [] name;
4425
4426 // Unescape set of characters and check if it's the expected one
4427 name = megaApi[0]->unescapeFsIncompatible("%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d"
4428 "%2e%2f%30%31%32%33%34%35%36%37"
4429 "%38%39%3a%3b%3c%3d%3e%3f%40%5b"
4430 "%5c%5d%5e%5f%60%7b%7c%7d%7e",
4431 fs::current_path().c_str());
4432
4433 ASSERT_TRUE(!strcmp(name, "%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e"
4434 "%2f%30%31%32%33%34%35%36%37%38%39:%3b%3c%3d%3e"
4435 "%3f%40%5b%5c%5d%5e%5f%60%7b%7c%7d%7e"));
4436 delete [] name;
4437 }
4438 #elif defined(_WIN32) || defined(_WIN64) || defined(WINDOWS_PHONE)
4439 if (fileSystemAccess.getlocalfstype(aux) == FS_NTFS)
4440 {
4441 // Escape set of characters and check if it's the expected one
4442 const char *name = megaApi[0]->escapeFsIncompatible("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~", fs::current_path().u8string().c_str());
4443 ASSERT_TRUE (!strcmp(name, "!%22#$%&'()%2a+,-.%2f%3a;%3c=%3e%3f@[%5c]^_`{%7c}~"));
4444 delete [] name;
4445
4446 // Unescape set of characters and check if it's the expected one
4447 name = megaApi[0]->unescapeFsIncompatible("%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d"
4448 "%2e%2f%30%31%32%33%34%35%36%37"
4449 "%38%39%3a%3b%3c%3d%3e%3f%40%5b"
4450 "%5c%5d%5e%5f%60%7b%7c%7d%7e",
4451 fs::current_path().u8string().c_str());
4452
4453 ASSERT_TRUE(!strcmp(name, "%21\"%23%24%25%26%27%28%29*%2b%2c%2d"
4454 "%2e/%30%31%32%33%34%35%36%37"
4455 "%38%39:%3b<%3d>?%40%5b"
4456 "\\%5d%5e%5f%60%7b|%7d%7e"));
4457
4458 delete [] name;
4459 }
4460 #endif
4461
4462 // Maps filename unescaped (original) to filename escaped (expected result): f%2ff => f/f
4463 std::unique_ptr<MegaStringMap> fileNamesStringMap = std::unique_ptr<MegaStringMap>{MegaStringMap::createInstance()};
4464 fs::path uploadPath = fs::current_path() / "upload_invalid_filenames";
4465 if (fs::exists(uploadPath))
4466 {
4467 fs::remove_all(uploadPath);
4468 }
4469 fs::create_directories(uploadPath);
4470
4471 for (int i = 0x01; i <= 0xA0; i++)
4472 {
4473 // skip [0-9] [A-Z] [a-z]
4474 if ((i >= 0x30 && i <= 0x39)
4475 || (i >= 0x41 && i <= 0x5A)
4476 || (i >= 0x61 && i <= 0x7A))
4477 {
4478 continue;
4479 }
4480
4481 // Create file with unescaped character ex: f%5cf
4482 char unescapedName[6];
4483 sprintf(unescapedName, "f%%%02xf", i);
4484 if (createLocalFile(uploadPath, unescapedName))
4485 {
4486 const char *unescapedFileName = megaApi[0]->unescapeFsIncompatible(unescapedName, uploadPath.u8string().c_str());
4487 fileNamesStringMap->set(unescapedName, unescapedFileName);
4488 delete [] unescapedFileName;
4489 }
4490
4491 // Create another file with the original character if supported f\f
4492 if ((i >= 0x01 && i <= 0x20)
4493 || (i >= 0x7F && i <= 0xA0))
4494 {
4495 // Skip control characters
4496 continue;
4497 }
4498
4499 char escapedName[4];
4500 sprintf(escapedName, "f%cf", i);
4501 const char *escapedFileName = megaApi[0]->escapeFsIncompatible(escapedName, uploadPath.u8string().c_str());
4502 if (escapedFileName && !strcmp(escapedName, escapedFileName))
4503 {
4504 // Only create those files with supported characters, those ones that need unescaping
4505 // has been created above
4506 if (createLocalFile(uploadPath, escapedName))
4507 {
4508 const char * unescapedFileName = megaApi[0]->unescapeFsIncompatible(escapedName, uploadPath.u8string().c_str());
4509 fileNamesStringMap->set(escapedName, unescapedFileName);
4510 delete [] unescapedFileName;
4511 }
4512 }
4513 delete [] escapedFileName;
4514 }
4515
4516 TransferTracker uploadListener(megaApi[0].get());
4517 megaApi[0]->startUpload(uploadPath.u8string().c_str(), std::unique_ptr<MegaNode>{megaApi[0]->getRootNode()}.get(), &uploadListener);
4518 ASSERT_EQ(API_OK, uploadListener.waitForResult());
4519
4520 ::mega::unique_ptr <MegaNode> n(megaApi[0]->getNodeByPath("/upload_invalid_filenames"));
4521 ASSERT_TRUE(n.get());
4522 ::mega::unique_ptr <MegaNode> authNode(megaApi[0]->authorizeNode(n.get()));
4523 ASSERT_TRUE(authNode.get());
4524 MegaNodeList *children(authNode->getChildren());
4525 ASSERT_TRUE(children && children->size());
4526
4527 for (int i = 0; i < children->size(); i++)
4528 {
4529 MegaNode *child = children->get(i);
4530 const char *uploadedName = child->getName();
4531 const char *uploadedNameEscaped = megaApi[0]->escapeFsIncompatible(child->getName(), uploadPath.u8string().c_str());
4532 const char *expectedName = fileNamesStringMap->get(uploadedNameEscaped);
4533 delete [] uploadedNameEscaped;
4534
4535 // Conditions to check if uploaded fileName is correct:
4536 // 1) Escaped uploaded filename must be found in fileNamesStringMap (original filename found)
4537 // 2) Uploaded filename must be equal than the expected value (original filename unescaped)
4538 ASSERT_TRUE (uploadedName && expectedName && !strcmp(uploadedName, expectedName));
4539 }
4540
4541 // Download files
4542 fs::path downloadPath = fs::current_path() / "download_invalid_filenames";
4543 if (fs::exists(downloadPath))
4544 {
4545 fs::remove_all(downloadPath);
4546 }
4547 fs::create_directories(downloadPath);
4548 TransferTracker downloadListener(megaApi[0].get());
4549 megaApi[0]->startDownload(authNode.get(), downloadPath.u8string().c_str(), &downloadListener);
4550 ASSERT_EQ(API_OK, downloadListener.waitForResult());
4551
4552 for (fs::directory_iterator itpath (downloadPath); itpath != fs::directory_iterator(); ++itpath)
4553 {
4554 std::string downloadedName = itpath->path().filename().u8string();
4555 if (!downloadedName.compare(".") || !downloadedName.compare(".."))
4556 {
4557 continue;
4558 }
4559
4560 // Conditions to check if downloaded fileName is correct:
4561 // download filename must be found in fileNamesStringMap (original filename found)
4562 ASSERT_TRUE(fileNamesStringMap->get(downloadedName.c_str()));
4563 }
4564
4565 #ifdef WIN32
4566 // double check a few well known paths
4567 ASSERT_EQ(fileSystemAccess.getlocalfstype(LocalPath::fromPath("c:", fsa)), FS_NTFS);
4568 ASSERT_EQ(fileSystemAccess.getlocalfstype(LocalPath::fromPath("c:\\", fsa)), FS_NTFS);
4569 ASSERT_EQ(fileSystemAccess.getlocalfstype(LocalPath::fromPath("C:\\", fsa)), FS_NTFS);
4570 ASSERT_EQ(fileSystemAccess.getlocalfstype(LocalPath::fromPath("C:\\Program Files", fsa)), FS_NTFS);
4571 ASSERT_EQ(fileSystemAccess.getlocalfstype(LocalPath::fromPath("c:\\Program Files\\Windows NT", fsa)), FS_NTFS);
4572 #endif
4573
4574 }
4575
TEST_F(SdkTest,RecursiveUploadWithLogout)4576 TEST_F(SdkTest, RecursiveUploadWithLogout)
4577 {
4578 LOG_info << "___TEST RecursiveUploadWithLogout___";
4579
4580 // this one used to cause a double-delete
4581
4582 // make new folders (and files) in the local filesystem - approx 90
4583 fs::path p = fs::current_path() / "uploadme_mega_auto_test_sdk";
4584 if (fs::exists(p))
4585 {
4586 fs::remove_all(p);
4587 }
4588 fs::create_directories(p);
4589 ASSERT_TRUE(buildLocalFolders(p.u8string().c_str(), "newkid", 3, 2, 10));
4590
4591 // start uploading
4592 TransferTracker uploadListener(megaApi[0].get());
4593 megaApi[0]->startUpload(p.u8string().c_str(), std::unique_ptr<MegaNode>{megaApi[0]->getRootNode()}.get(), &uploadListener);
4594 WaitMillisec(500);
4595
4596 // logout while the upload (which consists of many transfers) is ongoing
4597 ASSERT_EQ(API_OK, doRequestLogout(0));
4598 int result = uploadListener.waitForResult();
4599 ASSERT_TRUE(result == API_EACCESS || result == API_EINCOMPLETE);
4600 }
4601
TEST_F(SdkTest,DISABLED_RecursiveDownloadWithLogout)4602 TEST_F(SdkTest, DISABLED_RecursiveDownloadWithLogout)
4603 {
4604 LOG_info << "___TEST RecursiveDownloadWithLogout";
4605
4606 // this one used to cause a double-delete
4607
4608 // make new folders (and files) in the local filesystem - approx 130 - we must upload in order to have something to download
4609 fs::path uploadpath = fs::current_path() / "uploadme_mega_auto_test_sdk";
4610 fs::path downloadpath = fs::current_path() / "downloadme_mega_auto_test_sdk";
4611
4612 std::error_code ec;
4613 fs::remove_all(uploadpath, ec);
4614 fs::remove_all(downloadpath, ec);
4615 ASSERT_TRUE(!fs::exists(uploadpath));
4616 ASSERT_TRUE(!fs::exists(downloadpath));
4617 fs::create_directories(uploadpath);
4618 fs::create_directories(downloadpath);
4619
4620 ASSERT_TRUE(buildLocalFolders(uploadpath.u8string().c_str(), "newkid", 3, 2, 10));
4621
4622 // upload all of those
4623 TransferTracker uploadListener(megaApi[0].get());
4624 megaApi[0]->startUpload(uploadpath.u8string().c_str(), std::unique_ptr<MegaNode>{megaApi[0]->getRootNode()}.get(), &uploadListener);
4625 ASSERT_EQ(API_OK, uploadListener.waitForResult());
4626
4627 // ok now try the download
4628 TransferTracker downloadListener(megaApi[0].get());
4629 megaApi[0]->startDownload(megaApi[0]->getNodeByPath("/uploadme_mega_auto_test_sdk"), downloadpath.u8string().c_str(), &downloadListener);
4630 WaitMillisec(1000);
4631 ASSERT_TRUE(downloadListener.started);
4632 ASSERT_TRUE(!downloadListener.finished);
4633
4634 // logout while the download (which consists of many transfers) is ongoing
4635
4636 ASSERT_EQ(API_OK, doRequestLogout(0));
4637
4638 int result = downloadListener.waitForResult();
4639 ASSERT_TRUE(result == API_EACCESS || result == API_EINCOMPLETE);
4640 fs::remove_all(uploadpath, ec);
4641 fs::remove_all(downloadpath, ec);
4642 }
4643
4644 #ifdef ENABLE_SYNC
TEST_F(SdkTest,SyncResumptionAfterFetchNodes)4645 TEST_F(SdkTest, SyncResumptionAfterFetchNodes)
4646 {
4647 LOG_info << "___TEST SyncResumptionAfterFetchNodes___";
4648
4649 // This test has several issues:
4650 // 1. Remote nodes may not be committed to the sctable database in time for fetchnodes which
4651 // then fails adding syncs because the remotes are missing. For this reason we wait until
4652 // we receive the EVENT_COMMIT_DB event after transferring the nodes.
4653 // 2. Syncs are deleted some time later leading to error messages (like local fingerprint mismatch)
4654 // if we don't wait for long enough after we get called back. A sync only gets flagged but
4655 // is deleted later.
4656
4657 const std::string session = dumpSession();
4658
4659 const fs::path basePath = "SyncResumptionAfterFetchNodes";
4660 const auto sync1Path = fs::current_path() / basePath / "sync1"; // stays active
4661 const auto sync2Path = fs::current_path() / basePath / "sync2"; // will be made inactive
4662 const auto sync3Path = fs::current_path() / basePath / "sync3"; // will be deleted
4663 const auto sync4Path = fs::current_path() / basePath / "sync4"; // stays active
4664
4665 auto cleanUp = [this, &basePath]()
4666 {
4667 std::error_code ignoredEc;
4668 fs::remove_all(basePath, ignoredEc);
4669
4670 std::unique_ptr<MegaNode> baseNode{megaApi[0]->getNodeByPath(("/" + basePath.u8string()).c_str())};
4671 if (baseNode)
4672 {
4673 RequestTracker removeTracker(megaApi[0].get());
4674 megaApi[0]->remove(baseNode.get(), &removeTracker);
4675 ASSERT_EQ(API_OK, removeTracker.waitForResult());
4676 }
4677 };
4678
4679 cleanUp();
4680
4681 fs::create_directories(sync1Path);
4682 fs::create_directories(sync2Path);
4683 fs::create_directories(sync3Path);
4684 fs::create_directories(sync4Path);
4685
4686 {
4687 std::lock_guard<std::mutex> lock{lastEventMutex};
4688 lastEvent.reset();
4689 // we're assuming we're not getting any unrelated db commits while the transfer is running
4690 }
4691
4692 // transfer the folder and its subfolders
4693 TransferTracker uploadListener(megaApi[0].get());
4694 megaApi[0]->startUpload(basePath.u8string().c_str(), megaApi[0]->getRootNode(), &uploadListener);
4695 ASSERT_EQ(API_OK, uploadListener.waitForResult());
4696
4697 // loop until we get a commit to the sctable to ensure we cached the new remote nodes
4698 for (;;)
4699 {
4700 {
4701 std::lock_guard<std::mutex> lock{lastEventMutex};
4702 if (lastEvent && lastEvent->getType() == MegaEvent::EVENT_COMMIT_DB)
4703 {
4704 // we're assuming this event is the event for the whole batch of nodes
4705 break;
4706 }
4707 }
4708 std::this_thread::sleep_for(std::chrono::milliseconds{100});
4709 }
4710
4711 auto megaNode = [this, &basePath](const std::string& p)
4712 {
4713 const auto path = "/" + basePath.u8string() + "/" + p;
4714 return std::unique_ptr<MegaNode>{megaApi[0]->getNodeByPath(path.c_str())};
4715 };
4716
4717 auto localFp = [this, &megaNode](const fs::path& p)
4718 {
4719 auto node = megaNode(p.filename().u8string());
4720 auto sync = std::unique_ptr<MegaSync>{megaApi[0]->getSyncByNode(node.get())};
4721 return sync->getLocalFingerprint();
4722 };
4723
4724 auto syncFolder = [this, &megaNode](const fs::path& p)
4725 {
4726 RequestTracker syncTracker(megaApi[0].get());
4727 auto node = megaNode(p.filename().u8string());
4728 megaApi[0]->syncFolder(p.u8string().c_str(), node.get(), &syncTracker);
4729 ASSERT_EQ(API_OK, syncTracker.waitForResult());
4730 };
4731
4732 auto disableSync = [this, &megaNode](const fs::path& p)
4733 {
4734 RequestTracker syncTracker(megaApi[0].get());
4735 auto node = megaNode(p.filename().u8string());
4736 megaApi[0]->disableSync(node.get(), &syncTracker);
4737 ASSERT_EQ(API_OK, syncTracker.waitForResult());
4738 };
4739
4740 auto resumeSync = [this, &megaNode](const fs::path& p, const long long localfp)
4741 {
4742 RequestTracker syncTracker(megaApi[0].get());
4743 auto node = megaNode(p.filename().u8string());
4744 megaApi[0]->resumeSync(p.u8string().c_str(), node.get(), localfp, &syncTracker);
4745 ASSERT_EQ(API_OK, syncTracker.waitForResult());
4746 };
4747
4748 auto removeSync = [this, &megaNode](const fs::path& p)
4749 {
4750 RequestTracker syncTracker(megaApi[0].get());
4751 auto node = megaNode(p.filename().u8string());
4752 megaApi[0]->removeSync(node.get(), &syncTracker);
4753 ASSERT_EQ(API_OK, syncTracker.waitForResult());
4754 };
4755
4756 auto checkSyncOK = [this, &megaNode](const fs::path& p)
4757 {
4758 auto node = megaNode(p.filename().u8string());
4759 return std::unique_ptr<MegaSync>{megaApi[0]->getSyncByNode(node.get())} != nullptr;
4760 };
4761
4762 auto reloginViaSession = [this, &session]()
4763 {
4764 locallogout();
4765 loginBySessionId(0, session);
4766 };
4767
4768 syncFolder(sync1Path);
4769 syncFolder(sync2Path);
4770 syncFolder(sync3Path);
4771 syncFolder(sync4Path);
4772
4773 ASSERT_TRUE(checkSyncOK(sync1Path));
4774 ASSERT_TRUE(checkSyncOK(sync2Path));
4775 ASSERT_TRUE(checkSyncOK(sync3Path));
4776 ASSERT_TRUE(checkSyncOK(sync4Path));
4777 const auto sync2LocalFp = localFp(sync2Path); // need this for manual resume
4778
4779 disableSync(sync2Path);
4780 removeSync(sync3Path);
4781
4782 // wait for the sync removals to actually take place
4783 std::this_thread::sleep_for(std::chrono::seconds{20});
4784
4785 ASSERT_TRUE(checkSyncOK(sync1Path));
4786 ASSERT_FALSE(checkSyncOK(sync2Path));
4787 ASSERT_FALSE(checkSyncOK(sync3Path));
4788 ASSERT_TRUE(checkSyncOK(sync4Path));
4789
4790 reloginViaSession();
4791
4792 ASSERT_FALSE(checkSyncOK(sync1Path));
4793 ASSERT_FALSE(checkSyncOK(sync2Path));
4794 ASSERT_FALSE(checkSyncOK(sync3Path));
4795 ASSERT_FALSE(checkSyncOK(sync4Path));
4796
4797 fetchnodes(0, maxTimeout, true); // auto-resumes two active syncs
4798
4799 ASSERT_TRUE(checkSyncOK(sync1Path));
4800 ASSERT_FALSE(checkSyncOK(sync2Path));
4801 ASSERT_FALSE(checkSyncOK(sync3Path));
4802 ASSERT_TRUE(checkSyncOK(sync4Path));
4803
4804 // check if we can still resume manually
4805 resumeSync(sync2Path, sync2LocalFp);
4806
4807 ASSERT_TRUE(checkSyncOK(sync1Path));
4808 ASSERT_TRUE(checkSyncOK(sync2Path));
4809 ASSERT_FALSE(checkSyncOK(sync3Path));
4810 ASSERT_TRUE(checkSyncOK(sync4Path));
4811
4812 // check if resumeSync re-activated the sync
4813 reloginViaSession();
4814
4815 ASSERT_FALSE(checkSyncOK(sync1Path));
4816 ASSERT_FALSE(checkSyncOK(sync2Path));
4817 ASSERT_FALSE(checkSyncOK(sync3Path));
4818 ASSERT_FALSE(checkSyncOK(sync4Path));
4819
4820 fetchnodes(0, maxTimeout, true); // auto-resumes three active syncs
4821
4822 ASSERT_TRUE(checkSyncOK(sync1Path));
4823 ASSERT_TRUE(checkSyncOK(sync2Path));
4824 ASSERT_FALSE(checkSyncOK(sync3Path));
4825 ASSERT_TRUE(checkSyncOK(sync4Path));
4826
4827 removeSync(sync1Path);
4828 removeSync(sync2Path);
4829 removeSync(sync4Path);
4830
4831 // wait for the sync removals to actually take place
4832 std::this_thread::sleep_for(std::chrono::seconds{20});
4833
4834 cleanUp();
4835 }
4836 #endif
4837