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