• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / 1691

20 Sep 2023 01:57AM UTC coverage: 91.217% (+0.05%) from 91.168%
1691

push

Evergreen

web-flow
Merge pull request #6837 from realm/tg/user-provider

Fix handling of users with multiple identities

95990 of 175908 branches covered (0.0%)

799 of 830 new or added lines in 24 files covered. (96.27%)

44 existing lines in 15 files now uncovered.

233732 of 256237 relevant lines covered (91.22%)

6741306.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.32
/test/object-store/sync/metadata.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2016 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
#include <util/test_path.hpp>
20
#include <util/test_utils.hpp>
21
#include <util/sync/sync_test_utils.hpp>
22

23
#include <realm/object-store/object_schema.hpp>
24
#include <realm/object-store/property.hpp>
25
#include <realm/object-store/schema.hpp>
26
#include <realm/object-store/impl/apple/keychain_helper.hpp>
27
#include <realm/object-store/impl/object_accessor_impl.hpp>
28
#include <realm/object-store/impl/realm_coordinator.hpp>
29

30
#include <realm/util/file.hpp>
31
#include <realm/util/scope_exit.hpp>
32

33
#include <iostream>
34

35
using namespace realm;
36
using namespace realm::util;
37
using File = realm::util::File;
38
using SyncAction = SyncFileActionMetadata::Action;
39

40
static const std::string base_path = util::make_temp_dir() + "realm_objectstore_sync_metadata";
41
static const std::string metadata_path = base_path + "/metadata.realm";
42

43
TEST_CASE("sync_metadata: user metadata", "[sync][metadata]") {
16✔
44
    util::try_make_dir(base_path);
16✔
45
    auto close = util::make_scope_exit([=]() noexcept {
16✔
46
        util::try_remove_dir_recursive(base_path);
16✔
47
    });
16✔
48

8✔
49
    SyncMetadataManager manager(metadata_path, false);
16✔
50

8✔
51
    SECTION("can be properly constructed") {
16✔
52
        const auto identity = "testcase1a";
2✔
53
        auto user_metadata = manager.get_or_make_user_metadata(identity);
2✔
54
        REQUIRE(user_metadata->identity() == identity);
2!
55
        REQUIRE(user_metadata->access_token().empty());
2!
56
    }
2✔
57

8✔
58
    SECTION("properly reflects updating state") {
16✔
59
        const auto identity = "testcase1b";
2✔
60
        const std::string sample_token = "this_is_a_user_token";
2✔
61
        auto user_metadata = manager.get_or_make_user_metadata(identity);
2✔
62
        user_metadata->set_access_token(sample_token);
2✔
63
        REQUIRE(user_metadata->identity() == identity);
2!
64
        REQUIRE(user_metadata->access_token() == sample_token);
2!
65
    }
2✔
66

8✔
67
    SECTION("can be properly re-retrieved from the same manager") {
16✔
68
        const auto identity = "testcase1c";
2✔
69
        const std::string sample_token = "this_is_a_user_token";
2✔
70
        auto first = manager.get_or_make_user_metadata(identity);
2✔
71
        first->set_access_token(sample_token);
2✔
72
        // Get a second instance of the user metadata for the same identity.
1✔
73
        auto second = manager.get_or_make_user_metadata(identity, false);
2✔
74
        REQUIRE(second->identity() == identity);
2!
75
        REQUIRE(second->access_token() == sample_token);
2!
76
    }
2✔
77

8✔
78
    SECTION("properly reflects changes across different instances") {
16✔
79
        const auto identity = "testcase1d";
2✔
80
        const std::string sample_token_1 = "this_is_a_user_token";
2✔
81
        auto first = manager.get_or_make_user_metadata(identity);
2✔
82
        auto second = manager.get_or_make_user_metadata(identity);
2✔
83
        first->set_access_token(sample_token_1);
2✔
84
        REQUIRE(first->identity() == identity);
2!
85
        REQUIRE(first->access_token() == sample_token_1);
2!
86
        REQUIRE(second->identity() == identity);
2!
87
        REQUIRE(second->access_token() == sample_token_1);
2!
88
        // Set the state again.
1✔
89
        const std::string sample_token_2 = "this_is_another_user_token";
2✔
90
        second->set_access_token(sample_token_2);
2✔
91
        REQUIRE(first->identity() == identity);
2!
92
        REQUIRE(first->access_token() == sample_token_2);
2!
93
        REQUIRE(second->identity() == identity);
2!
94
        REQUIRE(second->access_token() == sample_token_2);
2!
95
    }
2✔
96

8✔
97
    SECTION("can be removed") {
16✔
98
        const auto identity = "testcase1e";
2✔
99
        auto user_metadata = manager.get_or_make_user_metadata(identity);
2✔
100
        REQUIRE(user_metadata->is_valid());
2!
101
        user_metadata->remove();
2✔
102
        REQUIRE(!user_metadata->is_valid());
2!
103
    }
2✔
104

8✔
105
    SECTION("respects make_if_absent flag set to false in constructor") {
16✔
106
        const std::string sample_token = "this_is_a_user_token";
6✔
107

3✔
108
        SECTION("with no prior metadata for the identifier") {
6✔
109
            const auto identity = "testcase1g1";
2✔
110
            auto user_metadata = manager.get_or_make_user_metadata(identity, false);
2✔
111
            REQUIRE(!user_metadata);
2!
112
        }
2✔
113
        SECTION("with valid prior metadata for the identifier") {
6✔
114
            const auto identity = "testcase1g2";
2✔
115
            auto first = manager.get_or_make_user_metadata(identity);
2✔
116
            first->set_access_token(sample_token);
2✔
117
            auto second = manager.get_or_make_user_metadata(identity, false);
2✔
118
            REQUIRE(second->is_valid());
2!
119
            REQUIRE(second->identity() == identity);
2!
120
            REQUIRE(second->access_token() == sample_token);
2!
121
        }
2✔
122
        SECTION("with invalid prior metadata for the identifier") {
6✔
123
            const auto identity = "testcase1g3";
2✔
124
            auto first = manager.get_or_make_user_metadata(identity);
2✔
125
            first->set_access_token(sample_token);
2✔
126
            first->set_state(SyncUser::State::Removed);
2✔
127
            auto second = manager.get_or_make_user_metadata(identity, false);
2✔
128
            REQUIRE(!second);
2!
129
        }
2✔
130
    }
6✔
131
}
16✔
132

133
TEST_CASE("sync_metadata: user metadata APIs", "[sync][metadata]") {
2✔
134
    util::try_make_dir(base_path);
2✔
135
    auto close = util::make_scope_exit([=]() noexcept {
2✔
136
        util::try_remove_dir_recursive(base_path);
2✔
137
    });
2✔
138

1✔
139
    SyncMetadataManager manager(metadata_path, false);
2✔
140
    const std::string provider_type = "https://realm.example.org";
2✔
141

1✔
142
    SECTION("properly list all marked and unmarked users") {
2✔
143
        const auto identity1 = "testcase2a1";
2✔
144
        const auto identity2 = "testcase2a3";
2✔
145
        auto first = manager.get_or_make_user_metadata(identity1);
2✔
146
        auto second = manager.get_or_make_user_metadata(identity1);
2✔
147
        auto third = manager.get_or_make_user_metadata(identity2);
2✔
148
        auto unmarked_users = manager.all_unmarked_users();
2✔
149
        REQUIRE(unmarked_users.size() == 2);
2!
150
        REQUIRE(results_contains_user(unmarked_users, identity1));
2!
151
        REQUIRE(results_contains_user(unmarked_users, identity2));
2!
152
        auto marked_users = manager.all_users_marked_for_removal();
2✔
153
        REQUIRE(marked_users.size() == 0);
2!
154
        // Now, mark a few users for removal.
1✔
155
        first->set_state(SyncUser::State::Removed);
2✔
156
        unmarked_users = manager.all_unmarked_users();
2✔
157
        REQUIRE(unmarked_users.size() == 1);
2!
158
        REQUIRE(results_contains_user(unmarked_users, identity2));
2!
159
        marked_users = manager.all_users_marked_for_removal();
2✔
160
        REQUIRE(marked_users.size() == 1);
2!
161
        REQUIRE(results_contains_user(marked_users, identity1));
2!
162
    }
2✔
163
}
2✔
164

165
TEST_CASE("sync_metadata: file action metadata", "[sync][metadata]") {
4✔
166
    util::try_make_dir(base_path);
4✔
167
    auto close = util::make_scope_exit([=]() noexcept {
4✔
168
        util::try_remove_dir_recursive(base_path);
4✔
169
    });
4✔
170

2✔
171
    SyncMetadataManager manager(metadata_path, false);
4✔
172

2✔
173
    const std::string local_uuid_1 = "asdfg";
4✔
174
    const std::string local_uuid_2 = "qwerty";
4✔
175
    const std::string url_1 = "realm://realm.example.com/1";
4✔
176
    const std::string url_2 = "realm://realm.example.com/2";
4✔
177

2✔
178
    SECTION("can be properly constructed") {
4✔
179
        const auto original_name = util::make_temp_dir() + "foobar/test1";
2✔
180
        manager.make_file_action_metadata(original_name, url_1, local_uuid_1, SyncAction::BackUpThenDeleteRealm);
2✔
181
        auto metadata = *manager.get_file_action_metadata(original_name);
2✔
182
        REQUIRE(metadata.original_name() == original_name);
2!
183
        REQUIRE(metadata.new_name() == none);
2!
184
        REQUIRE(metadata.action() == SyncAction::BackUpThenDeleteRealm);
2!
185
        REQUIRE(metadata.partition() == url_1);
2!
186
        REQUIRE(metadata.user_local_uuid() == local_uuid_1);
2!
187
    }
2✔
188

2✔
189
    SECTION("properly reflects updating state, across multiple instances") {
4✔
190
        const auto original_name = util::make_temp_dir() + "foobar/test2a";
2✔
191
        const std::string new_name_1 = util::make_temp_dir() + "foobar/test2b";
2✔
192
        const std::string new_name_2 = util::make_temp_dir() + "foobar/test2c";
2✔
193

1✔
194
        manager.make_file_action_metadata(original_name, url_1, local_uuid_1, SyncAction::BackUpThenDeleteRealm,
2✔
195
                                          new_name_1);
2✔
196
        auto metadata_1 = *manager.get_file_action_metadata(original_name);
2✔
197
        REQUIRE(metadata_1.original_name() == original_name);
2!
198
        REQUIRE(metadata_1.new_name() == new_name_1);
2!
199
        REQUIRE(metadata_1.action() == SyncAction::BackUpThenDeleteRealm);
2!
200
        REQUIRE(metadata_1.partition() == url_1);
2!
201
        REQUIRE(metadata_1.user_local_uuid() == local_uuid_1);
2!
202

1✔
203
        manager.make_file_action_metadata(original_name, url_2, local_uuid_2, SyncAction::DeleteRealm, new_name_2);
2✔
204
        auto metadata_2 = *manager.get_file_action_metadata(original_name);
2✔
205
        REQUIRE(metadata_1.original_name() == original_name);
2!
206
        REQUIRE(metadata_1.new_name() == new_name_2);
2!
207
        REQUIRE(metadata_1.action() == SyncAction::DeleteRealm);
2!
208
        REQUIRE(metadata_2.original_name() == original_name);
2!
209
        REQUIRE(metadata_2.new_name() == new_name_2);
2!
210
        REQUIRE(metadata_2.action() == SyncAction::DeleteRealm);
2!
211
        REQUIRE(metadata_1.partition() == url_2);
2!
212
        REQUIRE(metadata_1.user_local_uuid() == local_uuid_2);
2!
213
    }
2✔
214
}
4✔
215

216
TEST_CASE("sync_metadata: file action metadata APIs", "[sync][metadata]") {
2✔
217
    util::try_make_dir(base_path);
2✔
218
    auto close = util::make_scope_exit([=]() noexcept {
2✔
219
        util::try_remove_dir_recursive(base_path);
2✔
220
    });
2✔
221

1✔
222
    SyncMetadataManager manager(metadata_path, false);
2✔
223
    SECTION("properly list all pending actions, reflecting their deletion") {
2✔
224
        const auto filename1 = util::make_temp_dir() + "foobar/file1";
2✔
225
        const auto filename2 = util::make_temp_dir() + "foobar/file2";
2✔
226
        const auto filename3 = util::make_temp_dir() + "foobar/file3";
2✔
227
        manager.make_file_action_metadata(filename1, "asdf", "realm://realm.example.com/1",
2✔
228
                                          SyncAction::BackUpThenDeleteRealm);
2✔
229
        manager.make_file_action_metadata(filename2, "asdf", "realm://realm.example.com/2",
2✔
230
                                          SyncAction::BackUpThenDeleteRealm);
2✔
231
        manager.make_file_action_metadata(filename3, "asdf", "realm://realm.example.com/3",
2✔
232
                                          SyncAction::BackUpThenDeleteRealm);
2✔
233
        auto actions = manager.all_pending_actions();
2✔
234
        REQUIRE(actions.size() == 3);
2!
235
        REQUIRE(results_contains_original_name(actions, filename1));
2!
236
        REQUIRE(results_contains_original_name(actions, filename2));
2!
237
        REQUIRE(results_contains_original_name(actions, filename3));
2!
238
        manager.get_file_action_metadata(filename1)->remove();
2✔
239
        manager.get_file_action_metadata(filename2)->remove();
2✔
240
        manager.get_file_action_metadata(filename3)->remove();
2✔
241
        REQUIRE(actions.size() == 0);
2!
242
    }
2✔
243
}
2✔
244

245
TEST_CASE("sync_metadata: results", "[sync][metadata]") {
4✔
246
    util::try_make_dir(base_path);
4✔
247
    auto close = util::make_scope_exit([=]() noexcept {
4✔
248
        util::try_remove_dir_recursive(base_path);
4✔
249
    });
4✔
250

2✔
251
    SyncMetadataManager manager(metadata_path, false);
4✔
252
    const auto identity1 = "testcase3a1";
4✔
253
    const auto identity2 = "testcase3a3";
4✔
254

2✔
255
    SECTION("properly update as underlying items are added") {
4✔
256
        auto results = manager.all_unmarked_users();
2✔
257
        REQUIRE(results.size() == 0);
2!
258
        // Add users, one at a time.
1✔
259
        auto first = manager.get_or_make_user_metadata(identity1);
2✔
260
        REQUIRE(results.size() == 1);
2!
261
        REQUIRE(results_contains_user(results, identity1));
2!
262
        auto second = manager.get_or_make_user_metadata(identity2);
2✔
263
        REQUIRE(results.size() == 2);
2!
264
        REQUIRE(results_contains_user(results, identity2));
2!
265
    }
2✔
266

2✔
267
    SECTION("properly update as underlying items are removed") {
4✔
268
        auto results = manager.all_unmarked_users();
2✔
269
        auto first = manager.get_or_make_user_metadata(identity1);
2✔
270
        auto second = manager.get_or_make_user_metadata(identity2);
2✔
271
        REQUIRE(results.size() == 2);
2!
272
        REQUIRE(results_contains_user(results, identity1));
2!
273
        REQUIRE(results_contains_user(results, identity2));
2!
274
        // Remove users, one at a time.
1✔
275
        first->remove();
2✔
276
        REQUIRE(results.size() == 1);
2!
277
        REQUIRE(!results_contains_user(results, identity1));
2!
278
        second->remove();
2✔
279
        REQUIRE(results.size() == 0);
2!
280
    }
2✔
281
}
4✔
282

283
TEST_CASE("sync_metadata: persistence across metadata manager instances", "[sync][metadata]") {
2✔
284
    util::try_make_dir(base_path);
2✔
285
    auto close = util::make_scope_exit([=]() noexcept {
2✔
286
        util::try_remove_dir_recursive(base_path);
2✔
287
    });
2✔
288

1✔
289
    SECTION("works for the basic case") {
2✔
290
        const auto identity = "testcase4a";
2✔
291
        const std::string provider_type = "any-type";
2✔
292
        const std::string sample_token = "this_is_a_user_token";
2✔
293
        SyncMetadataManager first_manager(metadata_path, false);
2✔
294
        auto first = first_manager.get_or_make_user_metadata(identity);
2✔
295
        first->set_access_token(sample_token);
2✔
296
        REQUIRE(first->identity() == identity);
2!
297
        REQUIRE(first->access_token() == sample_token);
2!
298
        REQUIRE(first->state() == SyncUser::State::LoggedIn);
2!
299
        first->set_state(SyncUser::State::LoggedOut);
2✔
300

1✔
301
        SyncMetadataManager second_manager(metadata_path, false);
2✔
302
        auto second = second_manager.get_or_make_user_metadata(identity, false);
2✔
303
        REQUIRE(second->identity() == identity);
2!
304
        REQUIRE(second->access_token() == sample_token);
2!
305
        REQUIRE(second->state() == SyncUser::State::LoggedOut);
2!
306
    }
2✔
307
}
2✔
308

309
TEST_CASE("sync_metadata: encryption", "[sync][metadata]") {
8✔
310
    util::try_make_dir(base_path);
8✔
311
    auto close = util::make_scope_exit([=]() noexcept {
8✔
312
        util::try_remove_dir_recursive(base_path);
8✔
313
    });
8✔
314

4✔
315
    const auto identity0 = "identity0";
8✔
316
    SECTION("prohibits opening the metadata Realm with different keys") {
8✔
317
        SECTION("different keys") {
4✔
318
            {
2✔
319
                // Open metadata realm, make metadata
1✔
320
                std::vector<char> key0 = make_test_encryption_key(10);
2✔
321
                SyncMetadataManager manager0(metadata_path, true, key0);
2✔
322

1✔
323
                auto user_metadata0 = manager0.get_or_make_user_metadata(identity0);
2✔
324
                REQUIRE(bool(user_metadata0));
2!
325
                CHECK(user_metadata0->identity() == identity0);
2!
326
                CHECK(user_metadata0->access_token().empty());
2!
327
                CHECK(user_metadata0->is_valid());
2!
328
            }
2✔
329
            // Metadata realm is closed because only reference to the realm (user_metadata) is now out of scope
1✔
330
            // Open new metadata realm at path with different key
1✔
331
            std::vector<char> key1 = make_test_encryption_key(11);
2✔
332
            SyncMetadataManager manager1(metadata_path, true, key1);
2✔
333

1✔
334
            auto user_metadata1 = manager1.get_or_make_user_metadata(identity0, false);
2✔
335
            // Expect previous metadata to have been deleted
1✔
336
            CHECK_FALSE(bool(user_metadata1));
2!
337

1✔
338
            // But new metadata can still be created
1✔
339
            const auto identity1 = "identity1";
2✔
340
            auto user_metadata2 = manager1.get_or_make_user_metadata(identity1);
2✔
341
            CHECK(user_metadata2->identity() == identity1);
2!
342
            CHECK(user_metadata2->access_token().empty());
2!
343
            CHECK(user_metadata2->is_valid());
2!
344
        }
2✔
345
        SECTION("different encryption settings") {
4✔
346
            {
2✔
347
                // Encrypt metadata realm at path, make metadata
1✔
348
                SyncMetadataManager manager0(metadata_path, true, make_test_encryption_key(10));
2✔
349

1✔
350
                auto user_metadata0 = manager0.get_or_make_user_metadata(identity0);
2✔
351
                REQUIRE(bool(user_metadata0));
2!
352
                CHECK(user_metadata0->identity() == identity0);
2!
353
                CHECK(user_metadata0->access_token().empty());
2!
354
                CHECK(user_metadata0->is_valid());
2!
355
            }
2✔
356
            // Metadata realm is closed because only reference to the realm (user_metadata) is now out of scope
1✔
357
            // Open new metadata realm at path with different encryption configuration
1✔
358
            SyncMetadataManager manager1(metadata_path, false);
2✔
359
            auto user_metadata1 = manager1.get_or_make_user_metadata(identity0, false);
2✔
360
            // Expect previous metadata to have been deleted
1✔
361
            CHECK_FALSE(bool(user_metadata1));
2!
362

1✔
363
            // But new metadata can still be created
1✔
364
            const auto identity1 = "identity1";
2✔
365
            auto user_metadata2 = manager1.get_or_make_user_metadata(identity1);
2✔
366
            CHECK(user_metadata2->identity() == identity1);
2!
367
            CHECK(user_metadata2->access_token().empty());
2!
368
            CHECK(user_metadata2->is_valid());
2!
369
        }
2✔
370
    }
4✔
371

4✔
372
    SECTION("works when enabled") {
8✔
373
        std::vector<char> key = make_test_encryption_key(10);
2✔
374
        const auto identity = "testcase5a";
2✔
375
        SyncMetadataManager manager(metadata_path, true, key);
2✔
376
        auto user_metadata = manager.get_or_make_user_metadata(identity);
2✔
377
        REQUIRE(bool(user_metadata));
2!
378
        CHECK(user_metadata->identity() == identity);
2!
379
        CHECK(user_metadata->access_token().empty());
2!
380
        CHECK(user_metadata->is_valid());
2!
381
        // Reopen the metadata file with the same key.
1✔
382
        SyncMetadataManager manager_2(metadata_path, true, key);
2✔
383
        auto user_metadata_2 = manager_2.get_or_make_user_metadata(identity, false);
2✔
384
        REQUIRE(bool(user_metadata_2));
2!
385
        CHECK(user_metadata_2->identity() == identity);
2!
386
        CHECK(user_metadata_2->is_valid());
2!
387
    }
2✔
388

4✔
389
    SECTION("enabled without custom encryption key") {
8✔
390
#if REALM_PLATFORM_APPLE
1✔
391
        static bool can_access_keychain = [] {
1✔
392
            bool can_access = keychain::create_new_metadata_realm_key() != none;
1✔
393
            if (!can_access) {
1✔
394
                std::cout << "Skipping keychain tests as the keychain is not accessible\n";
1✔
395
            }
1✔
396
            return can_access;
1✔
397
        }();
1✔
398
        if (!can_access_keychain) {
1✔
399
            return;
1✔
400
        }
1✔
401
        auto delete_key = util::make_scope_exit([=]() noexcept {
402
            keychain::delete_metadata_realm_encryption_key();
403
        });
404

405
        SECTION("automatically generates an encryption key for new files") {
406
            {
407
                SyncMetadataManager manager(metadata_path, true, none);
408
                manager.set_current_user_identity(identity0);
409
            }
410

411
            // Should be able to reopen and read data
412
            {
413
                SyncMetadataManager manager(metadata_path, true, none);
414
                REQUIRE(manager.get_current_user_identity() == identity0);
×
415
            }
416

417
            // Verify that the file is actually encrypted
418
            REQUIRE_EXCEPTION(Group(metadata_path), InvalidDatabase,
×
419
                              Catch::Matchers::ContainsSubstring("invalid mnemonic"));
420
        }
421

422
        SECTION("leaves existing unencrypted files unencrypted") {
423
            {
424
                SyncMetadataManager manager(metadata_path, false, none);
425
                manager.set_current_user_identity(identity0);
426
            }
427
            {
428
                SyncMetadataManager manager(metadata_path, true, none);
429
                REQUIRE(manager.get_current_user_identity() == identity0);
×
430
            }
431
            REQUIRE_NOTHROW(Group(metadata_path));
432
        }
433

434
        SECTION("recreates the file if the old encryption key was lost") {
435
            {
436
                SyncMetadataManager manager(metadata_path, true, none);
437
                manager.set_current_user_identity(identity0);
438
            }
439

440
            keychain::delete_metadata_realm_encryption_key();
441

442
            {
443
                // File should now be missing the data
444
                SyncMetadataManager manager(metadata_path, true, none);
445
                REQUIRE(manager.get_current_user_identity() == none);
×
446
            }
447
            // New file should be encrypted
448
            REQUIRE_EXCEPTION(Group(metadata_path), InvalidDatabase,
×
449
                              Catch::Matchers::ContainsSubstring("invalid mnemonic"));
450
        }
451
#else
452
        REQUIRE_EXCEPTION(SyncMetadataManager(metadata_path, true, none), InvalidArgument,
1✔
453
                          "Metadata Realm encryption was specified, but no encryption key was provided.");
1✔
454
#endif
1✔
455
    }
1✔
456
}
8✔
457

458
#ifndef SWIFT_PACKAGE // The SPM build currently doesn't copy resource files
459
TEST_CASE("sync metadata: can open old metadata realms", "[sync][metadata]") {
6✔
460
    util::try_make_dir(base_path);
6✔
461
    auto close = util::make_scope_exit([=]() noexcept {
6✔
462
        util::try_remove_dir_recursive(base_path);
6✔
463
    });
6✔
464

3✔
465
    const std::string provider_type = "https://realm.example.org";
6✔
466
    const auto identity = "metadata migration test";
6✔
467
    const std::string sample_token = "metadata migration token";
6✔
468

3✔
469
    const auto access_token_1 = encode_fake_jwt("access token 1", 456, 123);
6✔
470
    const auto access_token_2 = encode_fake_jwt("access token 2", 456, 124);
6✔
471
    const auto refresh_token_1 = encode_fake_jwt("refresh token 1", 456, 123);
6✔
472
    const auto refresh_token_2 = encode_fake_jwt("refresh token 2", 456, 124);
6✔
473

3✔
474
    // change to true to create a test file for the current schema version
3✔
475
    // this will only work on unix-like systems
3✔
476
    if ((false)) {
6✔
477
#if false   // The code to generate the v4 and v5 Realms
478
        { // Create a metadata Realm with a test user
479
            SyncMetadataManager manager(metadata_path, false);
480
            auto user_metadata = manager.get_or_make_user_metadata(identity, provider_type);
481
            user_metadata->set_access_token(sample_token);
482
        }
483
#elif false // The code to generate the v6 Realm
484
        // Code to generate the v6 metadata Realm used to test the 6 -> 7 migration
485
        {
486
            using State = SyncUser::State;
487
            SyncMetadataManager manager(metadata_path, false);
488

489
            auto user = manager.get_or_make_user_metadata("removed user", "");
490
            user->set_state(State::Removed);
491

492
            auto make_user_pair = [&](const char* name, State state1, State state2, const std::string& token_1,
493
                                      const std::string& token_2) {
494
                auto user = manager.get_or_make_user_metadata(name, "a");
495
                user->set_state_and_tokens(state1, token_1, refresh_token_1);
496
                user->set_identities({{"identity 1", "a"}, {"shared identity", "shared"}});
497
                user->add_realm_file_path("file 1");
498
                user->add_realm_file_path("file 2");
499

500
                user = manager.get_or_make_user_metadata(name, "b");
501
                user->set_state_and_tokens(state2, token_2, refresh_token_2);
502
                user->set_identities({{"identity 2", "b"}, {"shared identity", "shared"}});
503
                user->add_realm_file_path("file 2");
504
                user->add_realm_file_path("file 3");
505
            };
506

507
            make_user_pair("first logged in, second logged out", State::LoggedIn, State::LoggedOut, access_token_1,
508
                           access_token_2);
509
            make_user_pair("first logged in, second removed", State::LoggedIn, State::Removed, access_token_1,
510
                           access_token_2);
511
            make_user_pair("second logged in, first logged out", State::LoggedOut, State::LoggedIn, access_token_1,
512
                           access_token_2);
513
            make_user_pair("second logged in, first removed", State::Removed, State::LoggedIn, access_token_1,
514
                           access_token_2);
515
            make_user_pair("both logged in, first newer", State::LoggedIn, State::LoggedIn, access_token_2,
516
                           access_token_1);
517
            make_user_pair("both logged in, second newer", State::LoggedIn, State::LoggedIn, access_token_1,
518
                           access_token_2);
519
        }
520

521
        // Replace the randomly generated UUIDs with deterministic values
522
        {
523
            Realm::Config config;
524
            config.path = metadata_path;
525
            auto realm = Realm::get_shared_realm(config);
526
            realm->begin_transaction();
527
            auto& group = realm->read_group();
528
            auto table = group.get_table("class_UserMetadata");
529
            auto col = table->get_column_key("local_uuid");
530
            size_t i = 0;
531
            for (auto& obj : *table) {
532
                obj.set(col, util::to_string(i++));
533
            }
534
            realm->commit_transaction();
535
        }
536
#else
NEW
537
        { // Create a metadata Realm with a test user
×
NEW
538
            SyncMetadataManager manager(metadata_path, false);
×
NEW
539
            auto user_metadata = manager.get_or_make_user_metadata(identity);
×
NEW
540
            user_metadata->set_access_token(sample_token);
×
NEW
541
        }
×
NEW
542
#endif
×
543

544
        // Open the metadata Realm directly and grab the schema version from it
545
        Realm::Config config;
×
546
        config.path = metadata_path;
×
547
        auto realm = Realm::get_shared_realm(config);
×
548
        realm->read_group();
×
549
        auto schema_version = realm->schema_version();
×
550

551
        // Take the path of this file, remove everything after the "test" directory,
552
        // then append the output filename
553
        std::string out_path = __FILE__;
×
554
        auto suffix = out_path.find("sync/metadata.cpp");
×
555
        REQUIRE(suffix != out_path.npos);
×
556
        out_path.resize(suffix);
×
557
        out_path.append(util::format("sync-metadata-v%1.realm", schema_version));
×
558

559
        // Write a compacted copy of the metadata realm to the test directory
560
        Realm::Config out_config;
×
561
        out_config.path = out_path;
×
562
        realm->convert(out_config);
×
563

564
        std::cout << "Wrote metadata realm to: " << out_path << "\n";
×
565
        return;
×
566
    }
6✔
567

3✔
568
    SECTION("open schema version 4") {
6✔
569
        File::copy(test_util::get_test_resource_path() + "sync-metadata-v4.realm", metadata_path);
2✔
570
        SyncMetadataManager manager(metadata_path, false);
2✔
571
        auto user_metadata = manager.get_or_make_user_metadata(identity);
2✔
572
        REQUIRE(user_metadata->identity() == identity);
2!
573
        REQUIRE(user_metadata->access_token() == sample_token);
2!
574
    }
2✔
575

3✔
576
    SECTION("open schema version 5") {
6✔
577
        File::copy(test_util::get_test_resource_path() + "sync-metadata-v5.realm", metadata_path);
2✔
578
        SyncMetadataManager manager(metadata_path, false);
2✔
579
        auto user_metadata = manager.get_or_make_user_metadata(identity);
2✔
580
        REQUIRE(user_metadata->identity() == identity);
2!
581
        REQUIRE(user_metadata->access_token() == sample_token);
2!
582
    }
2✔
583

3✔
584
    SECTION("open schema version 6") {
6✔
585
        using State = SyncUser::State;
2✔
586
        File::copy(test_util::get_test_resource_path() + "sync-metadata-v6.realm", metadata_path);
2✔
587
        SyncMetadataManager manager(metadata_path, false);
2✔
588

1✔
589
        SyncUserIdentity id_1{"identity 1", "a"};
2✔
590
        SyncUserIdentity id_2{"identity 2", "b"};
2✔
591
        SyncUserIdentity id_shared{"shared identity", "shared"};
2✔
592
        const std::vector<SyncUserIdentity> all_ids = {id_1, id_shared, id_2};
2✔
593
        const std::vector<std::string> realm_files = {"file 1", "file 2", "file 3"};
2✔
594

1✔
595
        auto check_user = [&](const char* user_id, State state, const std::string& access_token,
2✔
596
                              const std::string& refresh_token, const std::vector<std::string>& uuids) {
12✔
597
            auto user = manager.get_or_make_user_metadata(user_id, false);
12✔
598
            CAPTURE(user_id);
12✔
599
            REQUIRE(user);
12!
600
            CHECK(user->state() == state);
12!
601
            CHECK(user->access_token() == access_token);
12!
602
            CHECK(user->refresh_token() == refresh_token);
12!
603
            CHECK(user->legacy_identities() == uuids);
12!
604
            CHECK(user->identities() == all_ids);
12!
605
            CHECK(user->realm_file_paths() == realm_files);
12!
606
        };
12✔
607

1✔
608
        REQUIRE_FALSE(manager.get_or_make_user_metadata("removed user", false));
2!
609
        check_user("first logged in, second logged out", State::LoggedIn, access_token_1, refresh_token_1,
2✔
610
                   {"1", "2"});
2✔
611
        check_user("first logged in, second removed", State::LoggedIn, access_token_1, refresh_token_1, {"3", "4"});
2✔
612
        check_user("second logged in, first logged out", State::LoggedIn, access_token_2, refresh_token_2,
2✔
613
                   {"5", "6"});
2✔
614
        check_user("second logged in, first removed", State::LoggedIn, access_token_2, refresh_token_2, {"7", "8"});
2✔
615
        check_user("both logged in, first newer", State::LoggedIn, access_token_2, refresh_token_1, {"9", "10"});
2✔
616
        check_user("both logged in, second newer", State::LoggedIn, access_token_2, refresh_token_2, {"11", "12"});
2✔
617
    }
2✔
618
}
6✔
619
#endif // SWIFT_PACKAGE
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc