File: SdkTestDeviceCenter_test.cpp

package info (click to toggle)
megacmd 2.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 32,592 kB
  • sloc: cpp: 326,437; ansic: 34,524; python: 4,630; java: 3,965; sh: 2,869; objc: 2,459; makefile: 197; xml: 113
file content (291 lines) | stat: -rw-r--r-- 9,867 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
/**
 * @file SdkTestDeviceCenter_test.cpp
 * @brief Test Device Center operations on full-account syncs
 */

#ifdef ENABLE_SYNC

#include "integration_test_utils.h"
#include "mock_listeners.h"
#include "sdk_test_utils.h"
#include "SdkTest_test.h"

using namespace sdk_test;
using namespace testing;

/**
 * @brief Test fixture which initializates two sessions of the same account
 *
 * It offers functionality to perform operations from the Device Center.
 *
 * It initializes 2 MegaApi instance, the first (index 0) plays the role of the main device while
 * the second (index 1) is used as the remote Device Center.
 *
 */
class SdkTestDeviceCenter: public SdkTest
{
public:
    static constexpr auto MAX_TIMEOUT = 3min;
    handle mBackupID{UNDEF};

    void SetUp() override
    {
        SdkTest::SetUp();

        ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1));
        ASSERT_NO_FATAL_FAILURE(ensureAccountDeviceNamesAttrExists(megaApi[0].get()));

        // Initialize a second session with the same credentials
        ASSERT_NO_FATAL_FAILURE(initializeSecondSession());
    }

    bool resumeFromDeviceCenter(error expectedError = API_OK)
    {
        return doChangeFromDeviceCenter(DeviceCenterOperations::RESUME, expectedError);
    }

    bool pauseFromDeviceCenter(error expectedError = API_OK)
    {
        return doChangeFromDeviceCenter(DeviceCenterOperations::PAUSE, expectedError);
    }

    bool deleteFromDeviceCenter(error expectedError = API_OK,
                                MegaHandle destination = INVALID_HANDLE)
    {
        return doChangeFromDeviceCenter(DeviceCenterOperations::REMOVE, expectedError, destination);
    }

    const fs::path& getLocalFolder()
    {
        return localFolder.getPath();
    }

    bool waitForSyncStateFromMain(const MegaSync::SyncRunningState runState)
    {
        return waitForSyncState(megaApi[0].get(), mBackupID, runState, MegaSync::NO_SYNC_ERROR) !=
               nullptr;
    }

private:
    std::string localFolderName = getFilePrefix() + "dir";
    LocalTempDir localFolder{fs::current_path() / localFolderName};

    void initializeSecondSession()
    {
        megaApi.resize(megaApi.size() + 1);
        mApi.resize(mApi.size() + 1);
        configureTestInstance(1, mApi[0].email, mApi[0].pwd);

        NiceMock<MockRequestListener> loginTracker(megaApi[1].get());
        megaApi[1]->login(mApi[1].email.c_str(), mApi[1].pwd.c_str(), &loginTracker);
        ASSERT_TRUE(loginTracker.waitForFinishOrTimeout(MAX_TIMEOUT))
            << "Second session login failed";

        NiceMock<MockRequestListener> fetchNodesTracker(megaApi[1].get());
        megaApi[1]->fetchNodes(&fetchNodesTracker);
        ASSERT_TRUE(fetchNodesTracker.waitForFinishOrTimeout(MAX_TIMEOUT))
            << "Second session fetch nodes failed";
    }

    // Internal values to define the operations in the Device Center
    enum class DeviceCenterOperations
    {
        PAUSE,
        RESUME,
        REMOVE
    };

    bool doChangeFromDeviceCenter(const DeviceCenterOperations operation,
                                  error expectedError = API_OK,
                                  MegaHandle destination = INVALID_HANDLE)
    {
        NiceMock<MockRequestListener> reqTracker{megaApi[1].get()};
        reqTracker.setErrorExpectations(expectedError);

        switch (operation)
        {
            case DeviceCenterOperations::PAUSE:
                megaApi[1]->pauseFromBC(mBackupID, &reqTracker);
                break;
            case DeviceCenterOperations::RESUME:
                megaApi[1]->resumeFromBC(mBackupID, &reqTracker);
                break;
            case DeviceCenterOperations::REMOVE:
                megaApi[1]->removeFromBC(mBackupID, destination, &reqTracker);
                break;
        }
        return reqTracker.waitForFinishOrTimeout(MAX_TIMEOUT);
    }
};

class SdkTestDeviceCenterFullSync: public SdkTestDeviceCenter
{
public:
    void SetUp() override
    {
        SdkTestDeviceCenter::SetUp();

        ASSERT_NO_FATAL_FAILURE(setupFullSync());
    }

    void TearDown() override
    {
        if (const std::unique_ptr<MegaSync> sync{megaApi[0]->getSyncByBackupId(mBackupID)}; sync)
        {
            removeSync(megaApi[0].get(), mBackupID);
        }
        SdkTestDeviceCenter::TearDown();
    }

private:
    void setupFullSync()
    {
        LOG_debug << "Creating a full account sync";
        const unique_ptr<MegaNode> rootnode{megaApi[0]->getRootNode()};
        mBackupID =
            syncFolder(megaApi[0].get(), path_u8string(getLocalFolder()), rootnode->getHandle());
        ASSERT_NE(mBackupID, INVALID_HANDLE) << "Invalid full-sync ID";
    }
};

/**
 * @brief Exercises the pause, resume and remove Device Center operations from a second session
 */
TEST_F(SdkTestDeviceCenterFullSync, FullSyncOperations)
{
    static const auto logPre{getLogPrefix()};

    // Pause the sync from the second session
    LOG_debug << logPre << "Pause full-sync from the Device Center";
    ASSERT_TRUE(pauseFromDeviceCenter()) << "Failed to pause full-sync from the second session";

    ASSERT_TRUE(waitForSyncStateFromMain(MegaSync::RUNSTATE_SUSPENDED))
        << "Full-sync not paused after 30 seconds";

    // Wait a while (for the *!sds user attr to be updated and propagated in response).
    std::this_thread::sleep_for(std::chrono::seconds{5});

    // Resume the sync from the second session
    LOG_debug << logPre << "Resume full-sync from the Device Center";
    ASSERT_TRUE(resumeFromDeviceCenter()) << "Failed to resume full-sync from the second session";

    ASSERT_TRUE(waitForSyncStateFromMain(MegaSync::RUNSTATE_RUNNING))
        << "Full-sync not resumed after 30 seconds";

    // Wait a while (for the *!sds user attr to be updated and propagated in response).
    std::this_thread::sleep_for(std::chrono::seconds{5});

    // Delete the sync from the second session
    LOG_debug << logPre << "Remove full-sync from the Device Center";

    NiceMock<MockSyncListener> listener;
    const auto hasExpectedId = Pointee(Property(&MegaSync::getBackupId, mBackupID));
    std::promise<void> removed;
    EXPECT_CALL(listener, onSyncDeleted(_, hasExpectedId))
        .WillOnce(
            [&removed]
            {
                removed.set_value();
            });
    megaApi[0]->addListener(&listener);

    ASSERT_TRUE(deleteFromDeviceCenter()) << "Failed to delete full-sync from the second session";
    ASSERT_EQ(removed.get_future().wait_for(MAX_TIMEOUT), std::future_status::ready)
        << "Full-sync still exists after 3 minutes";

    megaApi[0]->removeListener(&listener);
}

/**
 * @brief Test fixture to test Backups from the Device Center.
 *
 * It configures a backup from the first account and a folder to potentially stotre backups once
 * removed.
 *
 * It inherits functionality to perform operations from the Device Center using a secondary account.
 */
class SdkTestDeviceCenterBackup: public SdkTestDeviceCenter
{
public:
    void SetUp() override
    {
        SdkTestDeviceCenter::SetUp();

        ASSERT_NO_FATAL_FAILURE(setupBackup());
        ASSERT_NO_FATAL_FAILURE(setupDestinationDirectory());
    }

    void TearDown() override
    {
        if (const std::unique_ptr<MegaSync> sync{megaApi[0]->getSyncByBackupId(mBackupID)}; sync)
        {
            removeSync(megaApi[0].get(), mBackupID);
        }
        SdkTestDeviceCenter::TearDown();
    }

    void duplicateDestinationBackupFolder()
    {
        // Get parent folder
        const unique_ptr<MegaNode> parentFolder{
            megaApi[0]->getNodeByHandle(mDestinationFolderHandle)};
        ASSERT_TRUE(parentFolder);

        // Create a folder in the destination with the same name as the backup
        const MegaHandle newFolder = createFolder(0, mBackupName.c_str(), parentFolder.get());
        ASSERT_NE(newFolder, INVALID_HANDLE) << "Invalid destination folder handle";

        // Ensure that the second client can see the new folder
        unique_ptr<MegaNode> newFolderNode;
        waitFor(
            [&api = megaApi[1], newFolder, &newFolderNode]()
            {
                newFolderNode.reset(api->getNodeByHandle(newFolder));
                return newFolderNode != nullptr;
            },
            120s);
        ASSERT_TRUE(newFolderNode) << "Second account can't see the new folder.";
    }

    bool deleteFromDeviceCenterAndArchive(error expectedError)
    {
        return deleteFromDeviceCenter(expectedError, mDestinationFolderHandle);
    }

private:
    const fs::path mDestinationFolderName{"BackupArchive"};
    const std::string mBackupName{"myBackup"};
    MegaHandle mDestinationFolderHandle{INVALID_HANDLE};

    void setupBackup()
    {
        LOG_debug << "Creating a backup";
        mBackupID = backupFolder(megaApi[0].get(), path_u8string(getLocalFolder()), mBackupName);
        ASSERT_NE(mBackupID, INVALID_HANDLE) << "Invalid Backup ID";
    }

    void setupDestinationDirectory()
    {
        const unique_ptr<MegaNode> rootnode{megaApi[0]->getRootNode()};
        ASSERT_TRUE(rootnode) << "Account root node not available.";
        mDestinationFolderHandle =
            createFolder(0, path_u8string(mDestinationFolderName).c_str(), rootnode.get());
        ASSERT_NE(mDestinationFolderHandle, INVALID_HANDLE) << "Invalid destination folder handle";
    }
};

TEST_F(SdkTestDeviceCenterBackup, RemoveDestinationClash)
{
    static const auto logPre{getLogPrefix()};

    LOG_debug << logPre << "Duplicate destination folder to cause a clash.";
    duplicateDestinationBackupFolder();

    LOG_debug
        << logPre
        << "Try to remove backup from the second session and move the data to the destination.";
    ASSERT_TRUE(deleteFromDeviceCenterAndArchive(API_EEXIST))
        << "Backups should not have been removed.";
}

#endif // ENABLE_SYNC