• 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

93.9
/test/object-store/util/sync/sync_test_utils.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/sync/sync_test_utils.hpp>
20

21
#include <util/sync/baas_admin_api.hpp>
22

23
#include <realm/object-store/binding_context.hpp>
24
#include <realm/object-store/object_store.hpp>
25
#include <realm/object-store/impl/object_accessor_impl.hpp>
26
#include <realm/object-store/sync/async_open_task.hpp>
27
#include <realm/object-store/sync/mongo_client.hpp>
28
#include <realm/object-store/sync/mongo_collection.hpp>
29
#include <realm/object-store/sync/mongo_database.hpp>
30

31
#include <realm/sync/noinst/client_history_impl.hpp>
32
#include <realm/sync/noinst/client_reset.hpp>
33

34
#include <realm/util/base64.hpp>
35
#include <realm/util/hex_dump.hpp>
36
#include <realm/util/sha_crypto.hpp>
37

38
#include <chrono>
39

40
namespace realm {
41

42
std::ostream& operator<<(std::ostream& os, util::Optional<app::AppError> error)
43
{
×
44
    if (!error) {
×
45
        os << "(none)";
×
46
    }
×
47
    else {
×
48
        os << "AppError(error_code=" << error->code() << ", server_error=" << error->server_error
×
49
           << ", http_status_code=" << error->additional_status_code.value_or(0) << ", message=\"" << error->reason()
×
50
           << "\", link_to_server_logs=\"" << error->link_to_server_logs << "\")";
×
51
    }
×
52
    return os;
×
53
}
×
54

55
bool results_contains_user(SyncUserMetadataResults& results, const std::string& identity)
56
{
18✔
57
    for (size_t i = 0; i < results.size(); i++) {
26✔
58
        auto this_result = results.get(i);
24✔
59
        if (this_result.identity() == identity) {
24✔
60
            return true;
16✔
61
        }
16✔
62
    }
24✔
63
    return false;
10✔
64
}
18✔
65

66
bool results_contains_original_name(SyncFileActionMetadataResults& results, const std::string& original_name)
67
{
6✔
68
    for (size_t i = 0; i < results.size(); i++) {
12✔
69
        if (results.get(i).original_name() == original_name) {
12✔
70
            return true;
6✔
71
        }
6✔
72
    }
12✔
73
    return false;
3✔
74
}
6✔
75

76
bool ReturnsTrueWithinTimeLimit::match(util::FunctionRef<bool()> condition) const
77
{
4✔
78
    const auto wait_start = std::chrono::steady_clock::now();
4✔
79
    bool predicate_returned_true = false;
4✔
80
    util::EventLoop::main().run_until([&] {
8✔
81
        if (std::chrono::steady_clock::now() - wait_start > m_max_ms) {
8✔
82
            return true;
×
83
        }
×
84
        auto ret = condition();
8✔
85
        if (ret) {
8✔
86
            predicate_returned_true = true;
6✔
87
        }
6✔
88
        return ret;
8✔
89
    });
8✔
90

2✔
91
    return predicate_returned_true;
4✔
92
}
4✔
93

94
void timed_wait_for(util::FunctionRef<bool()> condition, std::chrono::milliseconds max_ms)
95
{
88✔
96
    const auto wait_start = std::chrono::steady_clock::now();
88✔
97
    util::EventLoop::main().run_until([&] {
24,289,349✔
98
        if (std::chrono::steady_clock::now() - wait_start > max_ms) {
24,289,349✔
99
            throw std::runtime_error(util::format("timed_wait_for exceeded %1 ms", max_ms.count()));
×
100
        }
×
101
        return condition();
24,289,349✔
102
    });
24,289,349✔
103
}
88✔
104

105
void timed_sleeping_wait_for(util::FunctionRef<bool()> condition, std::chrono::milliseconds max_ms,
106
                             std::chrono::milliseconds sleep_ms)
107
{
364✔
108
    const auto wait_start = std::chrono::steady_clock::now();
364✔
109
    while (!condition()) {
17,456✔
110
        if (std::chrono::steady_clock::now() - wait_start > max_ms) {
17,092✔
111
            throw std::runtime_error(util::format("timed_sleeping_wait_for exceeded %1 ms", max_ms.count()));
×
112
        }
×
113
        std::this_thread::sleep_for(sleep_ms);
17,092✔
114
    }
17,092✔
115
}
364✔
116

117
auto do_hash = [](const std::string& name) -> std::string {
38✔
118
    std::array<unsigned char, 32> hash;
38✔
119
    util::sha256(name.data(), name.size(), hash.data());
38✔
120
    return util::hex_dump(hash.data(), hash.size(), "");
38✔
121
};
38✔
122

123
ExpectedRealmPaths::ExpectedRealmPaths(const std::string& base_path, const std::string& app_id,
124
                                       const std::string& identity, const std::vector<std::string>& legacy_identities,
125
                                       const std::string& partition)
126
{
38✔
127
    // This is copied from SyncManager.cpp string_from_partition() in order to prevent
19✔
128
    // us changing that function and therefore breaking user's existing paths unknowingly.
19✔
129
    std::string cleaned_partition;
38✔
130
    bson::Bson partition_value = bson::parse(partition);
38✔
131
    switch (partition_value.type()) {
38✔
132
        case bson::Bson::Type::Int32:
✔
133
            cleaned_partition = util::format("i_%1", static_cast<int32_t>(partition_value));
×
134
            break;
×
135
        case bson::Bson::Type::Int64:
✔
136
            cleaned_partition = util::format("l_%1", static_cast<int64_t>(partition_value));
×
137
            break;
×
138
        case bson::Bson::Type::String:
38✔
139
            cleaned_partition = util::format("s_%1", static_cast<std::string>(partition_value));
38✔
140
            break;
38✔
141
        case bson::Bson::Type::ObjectId:
✔
142
            cleaned_partition = util::format("o_%1", static_cast<ObjectId>(partition_value).to_string());
×
143
            break;
×
144
        case bson::Bson::Type::Uuid:
✔
145
            cleaned_partition = util::format("u_%1", static_cast<UUID>(partition_value).to_string());
×
146
            break;
×
147
        case bson::Bson::Type::Null:
✔
148
            cleaned_partition = "null";
×
149
            break;
×
150
        default:
✔
151
            REALM_ASSERT(false);
×
152
    }
38✔
153

19✔
154
    std::string clean_name = cleaned_partition;
38✔
155
    std::string cleaned_app_id = util::make_percent_encoded_string(app_id);
38✔
156
    const auto manager_path = fs::path{base_path}.make_preferred() / "mongodb-realm" / cleaned_app_id;
38✔
157
    const auto preferred_name = manager_path / identity / clean_name;
38✔
158
    current_preferred_path = preferred_name.string() + ".realm";
38✔
159
    fallback_hashed_path = (manager_path / do_hash(preferred_name.string())).string() + ".realm";
38✔
160

19✔
161
    if (legacy_identities.size() < 1)
38✔
NEW
162
        return;
×
163
    auto& local_identity = legacy_identities[0];
38✔
164
    legacy_sync_directories_to_make.push_back((manager_path / local_identity).string());
38✔
165
    std::string encoded_partition = util::make_percent_encoded_string(partition);
38✔
166
    legacy_local_id_path = (manager_path / local_identity / encoded_partition).concat(".realm").string();
38✔
167
    auto dir_builder = manager_path / "realm-object-server";
38✔
168
    legacy_sync_directories_to_make.push_back(dir_builder.string());
38✔
169
    dir_builder /= local_identity;
38✔
170
    legacy_sync_directories_to_make.push_back(dir_builder.string());
38✔
171
    legacy_sync_path = (dir_builder / cleaned_partition).string();
38✔
172
}
38✔
173

174
#if REALM_ENABLE_SYNC
175

176
#if REALM_ENABLE_AUTH_TESTS
177

178
#ifdef REALM_MONGODB_ENDPOINT
179
std::string get_base_url()
180
{
471✔
181
    // allows configuration with or without quotes
228✔
182
    std::string base_url = REALM_QUOTE(REALM_MONGODB_ENDPOINT);
471✔
183
    if (base_url.size() > 0 && base_url[0] == '"') {
471✔
184
        base_url.erase(0, 1);
471✔
185
    }
471✔
186
    if (base_url.size() > 0 && base_url[base_url.size() - 1] == '"') {
471✔
187
        base_url.erase(base_url.size() - 1);
471✔
188
    }
471✔
189
    return base_url;
471✔
190
}
471✔
191
#endif // REALM_MONGODB_ENDPOINT
192

193
AutoVerifiedEmailCredentials::AutoVerifiedEmailCredentials()
194
{
986✔
195
    // emails with this prefix will pass through the baas app due to the register function
482✔
196
    email = util::format("realm_tests_do_autoverify%1@%2.com", random_string(10), random_string(10));
986✔
197
    password = random_string(10);
986✔
198
    static_cast<AppCredentials&>(*this) = AppCredentials::username_password(email, password);
986✔
199
}
986✔
200

201
AutoVerifiedEmailCredentials create_user_and_log_in(app::SharedApp app)
202
{
884✔
203
    REQUIRE(app);
884!
204
    AutoVerifiedEmailCredentials creds;
884✔
205
    app->provider_client<app::App::UsernamePasswordProviderClient>().register_email(
884✔
206
        creds.email, creds.password, [&](util::Optional<app::AppError> error) {
884✔
207
            REQUIRE(!error);
884!
208
        });
884✔
209
    app->log_in_with_credentials(realm::app::AppCredentials::username_password(creds.email, creds.password),
884✔
210
                                 [&](std::shared_ptr<realm::SyncUser> user, util::Optional<app::AppError> error) {
884✔
211
                                     REQUIRE(user);
884!
212
                                     REQUIRE(!error);
884!
213
                                 });
884✔
214
    return creds;
884✔
215
}
884✔
216

217
void wait_for_advance(Realm& realm)
218
{
48✔
219
    struct Context : BindingContext {
48✔
220
        Realm& realm;
48✔
221
        DB::version_type target_version;
48✔
222
        bool& done;
48✔
223
        Context(Realm& realm, bool& done)
48✔
224
            : realm(realm)
48✔
225
            , target_version(*realm.latest_snapshot_version())
48✔
226
            , done(done)
48✔
227
        {
48✔
228
        }
48✔
229

24✔
230
        void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
48✔
231
        {
48✔
232
            if (realm.read_transaction_version().version >= target_version) {
48✔
233
                done = true;
48✔
234
            }
48✔
235
        }
48✔
236
    };
48✔
237

24✔
238
    bool done = false;
48✔
239
    realm.m_binding_context = std::make_unique<Context>(realm, done);
48✔
240
    timed_wait_for([&] {
264✔
241
        return done;
264✔
242
    });
264✔
243
    realm.m_binding_context = nullptr;
48✔
244
}
48✔
245

246
void async_open_realm(const Realm::Config& config,
247
                      util::UniqueFunction<void(ThreadSafeReference&& ref, std::exception_ptr e)> finish)
248
{
20✔
249
    std::mutex mutex;
20✔
250
    bool did_finish = false;
20✔
251
    auto task = Realm::get_synchronized_realm(config);
20✔
252
    task->start([&, callback = std::move(finish)](ThreadSafeReference&& ref, std::exception_ptr e) {
20✔
253
        callback(std::move(ref), e);
20✔
254
        std::lock_guard lock(mutex);
20✔
255
        did_finish = true;
20✔
256
    });
20✔
257
    util::EventLoop::main().run_until([&] {
4,352,387✔
258
        std::lock_guard lock(mutex);
4,352,387✔
259
        return did_finish;
4,352,387✔
260
    });
4,352,387✔
261
    task->cancel(); // don't run the above notifier again on this session
20✔
262
}
20✔
263

264
#endif // REALM_ENABLE_AUTH_TESTS
265
#endif // REALM_ENABLE_SYNC
266

267
class TestHelper {
268
public:
269
    static DBRef& get_db(SharedRealm const& shared_realm)
270
    {
271
        return Realm::Internal::get_db(*shared_realm);
272
    }
273
};
274

275
namespace reset_utils {
276

277
Obj create_object(Realm& realm, StringData object_type, util::Optional<ObjectId> primary_key,
278
                  util::Optional<Partition> partition)
279
{
6,622✔
280
    auto table = realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
6,622✔
281
    REQUIRE(table);
6,622!
282
    FieldValues values = {};
6,622✔
283
    if (partition) {
6,622✔
284
        ColKey col = table->get_column_key(partition->property_name);
174✔
285
        REALM_ASSERT(col);
174✔
286
        values.insert(col, Mixed{partition->value});
174✔
287
    }
174✔
288
    return table->create_object_with_primary_key(primary_key ? *primary_key : ObjectId::gen(), std::move(values));
6,621✔
289
}
6,622✔
290

291
namespace {
292

293
TableRef get_table(Realm& realm, StringData object_type)
294
{
6,668✔
295
    return realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
6,668✔
296
}
6,668✔
297

298
// Run through the client reset steps manually without involving a sync server.
299
// Useful for speed and when integration testing is not available on a platform.
300
struct FakeLocalClientReset : public TestClientReset {
301
    FakeLocalClientReset(const Realm::Config& local_config, const Realm::Config& remote_config)
302
        : TestClientReset(local_config, remote_config)
303
    {
3,232✔
304
        REALM_ASSERT(m_local_config.sync_config);
3,232✔
305
        m_mode = m_local_config.sync_config->client_resync_mode;
3,232✔
306
        REALM_ASSERT(m_mode == ClientResyncMode::DiscardLocal || m_mode == ClientResyncMode::Recover);
3,232✔
307
        // Turn off real sync. But we still need a SyncClientHistory for recovery mode so fake it.
1,616✔
308
        m_local_config.sync_config = {};
3,232✔
309
        m_remote_config.sync_config = {};
3,232✔
310
        m_local_config.force_sync_history = true;
3,232✔
311
        m_remote_config.force_sync_history = true;
3,232✔
312
        m_local_config.in_memory = true;
3,232✔
313
        m_local_config.encryption_key = std::vector<char>();
3,232✔
314
        m_remote_config.in_memory = true;
3,232✔
315
        m_remote_config.encryption_key = std::vector<char>();
3,232✔
316
    }
3,232✔
317

318
    void run() override
319
    {
3,224✔
320
        m_did_run = true;
3,224✔
321
        auto local_realm = Realm::get_shared_realm(m_local_config);
3,224✔
322
        if (m_on_setup) {
3,224✔
323
            local_realm->begin_transaction();
3,216✔
324
            m_on_setup(local_realm);
3,216✔
325
            local_realm->commit_transaction();
3,216✔
326

1,608✔
327
            // Update the sync history to mark this initial setup state as if it
1,608✔
328
            // has been uploaded so that it doesn't replay during recovery.
1,608✔
329
            auto history_local =
3,216✔
330
                dynamic_cast<sync::ClientHistory*>(local_realm->read_group().get_replication()->_get_history_write());
3,216✔
331
            REALM_ASSERT(history_local);
3,216✔
332
            sync::version_type current_version;
3,216✔
333
            sync::SaltedFileIdent file_ident;
3,216✔
334
            sync::SyncProgress progress;
3,216✔
335
            history_local->get_status(current_version, file_ident, progress);
3,216✔
336
            progress.upload.client_version = current_version;
3,216✔
337
            progress.upload.last_integrated_server_version = current_version;
3,216✔
338
            sync::VersionInfo info_out;
3,216✔
339
            history_local->set_sync_progress(progress, nullptr, info_out);
3,216✔
340
        }
3,216✔
341
        {
3,224✔
342
            local_realm->begin_transaction();
3,224✔
343
            auto obj = create_object(*local_realm, "object", m_pk_driving_reset);
3,224✔
344
            auto col = obj.get_table()->get_column_key("value");
3,224✔
345
            obj.set(col, 1);
3,224✔
346
            obj.set(col, 2);
3,224✔
347
            obj.set(col, 3);
3,224✔
348
            local_realm->commit_transaction();
3,224✔
349

1,612✔
350
            local_realm->begin_transaction();
3,224✔
351
            obj.set(col, 4);
3,224✔
352
            if (m_make_local_changes) {
3,224✔
353
                m_make_local_changes(local_realm);
3,220✔
354
            }
3,220✔
355
            local_realm->commit_transaction();
3,224✔
356
            if (m_on_post_local) {
3,224✔
357
                m_on_post_local(local_realm);
2,996✔
358
            }
2,996✔
359
        }
3,224✔
360

1,612✔
361
        {
3,224✔
362
            auto remote_realm = Realm::get_shared_realm(m_remote_config);
3,224✔
363
            remote_realm->begin_transaction();
3,224✔
364
            if (m_on_setup) {
3,224✔
365
                m_on_setup(remote_realm);
3,216✔
366
            }
3,216✔
367

1,612✔
368
            // fake a sync by creating an object with the same pk
1,612✔
369
            create_object(*remote_realm, "object", m_pk_driving_reset);
3,224✔
370

1,612✔
371
            for (int i = 0; i < 2; ++i) {
9,672✔
372
                auto table = get_table(*remote_realm, "object");
6,448✔
373
                auto col = table->get_column_key("value");
6,448✔
374
                table->begin()->set(col, i + 5);
6,448✔
375
            }
6,448✔
376

1,612✔
377
            if (m_make_remote_changes) {
3,224✔
378
                m_make_remote_changes(remote_realm);
3,216✔
379
            }
3,216✔
380
            remote_realm->commit_transaction();
3,224✔
381

1,612✔
382
            sync::SaltedFileIdent fake_ident{1, 123456789};
3,224✔
383
            auto local_db = TestHelper::get_db(local_realm);
3,224✔
384
            auto remote_db = TestHelper::get_db(remote_realm);
3,224✔
385
            util::StderrLogger logger(realm::util::Logger::Level::TEST_LOGGING_LEVEL);
3,224✔
386
            using _impl::client_reset::perform_client_reset_diff;
3,224✔
387
            constexpr bool recovery_is_allowed = true;
3,224✔
388
            perform_client_reset_diff(local_db, remote_db, fake_ident, logger, m_mode, recovery_is_allowed, nullptr,
3,224✔
389
                                      nullptr, nullptr);
3,224✔
390

1,612✔
391
            remote_realm->close();
3,224✔
392
            if (m_on_post_reset) {
3,224✔
393
                m_on_post_reset(local_realm);
3,222✔
394
            }
3,222✔
395
        }
3,224✔
396
    }
3,224✔
397

398
private:
399
    ClientResyncMode m_mode;
400
};
401
} // anonymous namespace
402

403
#if REALM_ENABLE_SYNC
404

405
#if REALM_ENABLE_AUTH_TESTS
406

407
void wait_for_object_to_persist_to_atlas(std::shared_ptr<SyncUser> user, const AppSession& app_session,
408
                                         const std::string& schema_name, const bson::BsonDocument& filter_bson)
409
{
4✔
410
    // While at this point the object has been sync'd successfully, we must also
2✔
411
    // wait for it to appear in the backing database before terminating sync
2✔
412
    // otherwise the translator may be terminated before it has a chance to
2✔
413
    // integrate it into the backing database. If the server were to change
2✔
414
    // the meaning of "upload complete" to include writing to atlas then this would
2✔
415
    // not be necessary.
2✔
416
    app::MongoClient remote_client = user->mongo_client("BackingDB");
4✔
417
    app::MongoDatabase db = remote_client.db(app_session.config.mongo_dbname);
4✔
418
    app::MongoCollection object_coll = db[schema_name];
4✔
419

2✔
420
    timed_sleeping_wait_for(
4✔
421
        [&]() -> bool {
13✔
422
            auto pf = util::make_promise_future<uint64_t>();
13✔
423
            object_coll.count(filter_bson, [promise = std::move(pf.promise)](
13✔
424
                                               uint64_t count, util::Optional<app::AppError> error) mutable {
13✔
425
                REQUIRE(!error);
13!
426
                if (error) {
13✔
427
                    promise.set_error({ErrorCodes::RuntimeError, error->reason()});
×
428
                }
×
429
                else {
13✔
430
                    promise.emplace_value(count);
13✔
431
                }
13✔
432
            });
13✔
433
            return pf.future.get() > 0;
13✔
434
        },
13✔
435
        std::chrono::minutes(15), std::chrono::milliseconds(500));
4✔
436
}
4✔
437

438
void wait_for_num_objects_in_atlas(std::shared_ptr<SyncUser> user, const AppSession& app_session,
439
                                   const std::string& schema_name, size_t expected_size)
440
{
2✔
441
    app::MongoClient remote_client = user->mongo_client("BackingDB");
2✔
442
    app::MongoDatabase db = remote_client.db(app_session.config.mongo_dbname);
2✔
443
    app::MongoCollection object_coll = db[schema_name];
2✔
444

1✔
445
    const bson::BsonDocument& filter_bson{};
2✔
446
    timed_sleeping_wait_for(
2✔
447
        [&]() -> bool {
4✔
448
            auto pf = util::make_promise_future<uint64_t>();
4✔
449
            object_coll.count(filter_bson, [promise = std::move(pf.promise)](
4✔
450
                                               uint64_t count, util::Optional<app::AppError> error) mutable {
4✔
451
                REQUIRE(!error);
4!
452
                if (error) {
4✔
453
                    promise.set_error({ErrorCodes::RuntimeError, error->reason()});
×
454
                }
×
455
                else {
4✔
456
                    promise.emplace_value(count);
4✔
457
                }
4✔
458
            });
4✔
459
            return pf.future.get() >= expected_size;
4✔
460
        },
4✔
461
        std::chrono::minutes(15), std::chrono::milliseconds(500));
2✔
462
}
2✔
463

464
void trigger_client_reset(const AppSession& app_session)
465
{
4✔
466
    // cause a client reset by restarting the sync service
2✔
467
    // this causes the server's sync history to be resynthesized
2✔
468
    auto baas_sync_service = app_session.admin_api.get_sync_service(app_session.server_app_id);
4✔
469
    auto baas_sync_config = app_session.admin_api.get_config(app_session.server_app_id, baas_sync_service);
4✔
470

2✔
471
    REQUIRE(app_session.admin_api.is_sync_enabled(app_session.server_app_id));
4!
472
    app_session.admin_api.disable_sync(app_session.server_app_id, baas_sync_service.id, baas_sync_config);
4✔
473
    timed_sleeping_wait_for([&] {
38✔
474
        return app_session.admin_api.is_sync_terminated(app_session.server_app_id);
38✔
475
    });
38✔
476
    app_session.admin_api.enable_sync(app_session.server_app_id, baas_sync_service.id, baas_sync_config);
4✔
477
    REQUIRE(app_session.admin_api.is_sync_enabled(app_session.server_app_id));
4!
478
    if (app_session.config.dev_mode_enabled) { // dev mode is not sticky across a reset
4✔
479
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, true);
2✔
480
    }
2✔
481

2✔
482
    // In FLX sync, the server won't let you connect until the initial sync is complete. With PBS tho, we need
2✔
483
    // to make sure we've actually copied all the data from atlas into the realm history before we do any of
2✔
484
    // our remote changes.
2✔
485
    if (!app_session.config.flx_sync_config) {
4✔
486
        timed_sleeping_wait_for([&] {
514✔
487
            return app_session.admin_api.is_initial_sync_complete(app_session.server_app_id);
514✔
488
        });
514✔
489
    }
2✔
490
}
4✔
491

492
void trigger_client_reset(const AppSession& app_session, const SharedRealm& realm)
493
{
134✔
494
    auto file_ident = SyncSession::OnlyForTesting::get_file_ident(*realm->sync_session());
134✔
495
    REQUIRE(file_ident.ident != 0);
134!
496
    app_session.admin_api.trigger_client_reset(app_session.server_app_id, file_ident.ident);
134✔
497
}
134✔
498

499
struct BaasClientReset : public TestClientReset {
500
    BaasClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
501
                    TestAppSession& test_app_session)
502
        : TestClientReset(local_config, remote_config)
503
        , m_test_app_session(test_app_session)
504
    {
110✔
505
    }
110✔
506

507
    void run() override
508
    {
86✔
509
        m_did_run = true;
86✔
510
        const AppSession& app_session = m_test_app_session.app_session();
86✔
511
        auto sync_manager = m_test_app_session.app()->sync_manager();
86✔
512
        std::string partition_value = m_local_config.sync_config->partition_value;
86✔
513
        REALM_ASSERT(partition_value.size() > 2 && *partition_value.begin() == '"' &&
86✔
514
                     *(partition_value.end() - 1) == '"');
86✔
515
        partition_value = partition_value.substr(1, partition_value.size() - 2);
86✔
516
        Partition partition = {app_session.config.partition_key.name, partition_value};
86✔
517

43✔
518
        // There is a race in PBS where if initial sync is still in-progress while you're creating the initial
43✔
519
        // object below, you may end up creating it in your local realm, uploading it, have the translator process
43✔
520
        // the upload, then initial sync the processed object, and then send it back to you as an erase/create
43✔
521
        // object instruction.
43✔
522
        //
43✔
523
        // So just don't try to do anything until initial sync is done and we're sure the server is in a stable
43✔
524
        // state.
43✔
525
        timed_sleeping_wait_for([&] {
10,436✔
526
            return app_session.admin_api.is_initial_sync_complete(app_session.server_app_id);
10,436✔
527
        });
10,436✔
528

43✔
529
        auto realm = Realm::get_shared_realm(m_local_config);
86✔
530
        auto session = sync_manager->get_existing_session(realm->config().path);
86✔
531
        const std::string object_schema_name = "object";
86✔
532
        {
86✔
533
            wait_for_download(*realm);
86✔
534
            realm->begin_transaction();
86✔
535

43✔
536
            if (m_on_setup) {
86✔
537
                m_on_setup(realm);
16✔
538
            }
16✔
539

43✔
540
            auto obj = create_object(*realm, object_schema_name, {m_pk_driving_reset}, {partition});
86✔
541
            auto table = obj.get_table();
86✔
542
            auto col = table->get_column_key("value");
86✔
543
            std::string pk_col_name = table->get_column_name(table->get_primary_key_column());
86✔
544
            obj.set(col, 1);
86✔
545
            obj.set(col, 2);
86✔
546
            constexpr int64_t last_synced_value = 3;
86✔
547
            obj.set(col, last_synced_value);
86✔
548
            realm->commit_transaction();
86✔
549
            wait_for_upload(*realm);
86✔
550
            wait_for_download(*realm);
86✔
551

43✔
552
            session->pause();
86✔
553

43✔
554
            realm->begin_transaction();
86✔
555
            obj.set(col, 4);
86✔
556
            if (m_make_local_changes) {
86✔
557
                m_make_local_changes(realm);
42✔
558
            }
42✔
559
            realm->commit_transaction();
86✔
560
        }
86✔
561

43✔
562
        trigger_client_reset(app_session, realm);
86✔
563

43✔
564
        {
86✔
565
            auto realm2 = Realm::get_shared_realm(m_remote_config);
86✔
566
            wait_for_download(*realm2);
86✔
567

43✔
568
            timed_sleeping_wait_for(
86✔
569
                [&]() -> bool {
86✔
570
                    realm2->begin_transaction();
86✔
571
                    auto table = get_table(*realm2, object_schema_name);
86✔
572
                    auto objkey = table->find_primary_key({m_pk_driving_reset});
86✔
573
                    realm2->cancel_transaction();
86✔
574
                    return bool(objkey);
86✔
575
                },
86✔
576
                std::chrono::seconds(60));
86✔
577

43✔
578
            // expect the last sync'd object to be in place
43✔
579
            realm2->begin_transaction();
86✔
580
            auto table = get_table(*realm2, object_schema_name);
86✔
581
            REQUIRE(table->size() >= 1);
86!
582
            auto obj = table->get_object_with_primary_key({m_pk_driving_reset});
86✔
583
            REQUIRE(obj.is_valid());
86!
584
            auto col = table->get_column_key("value");
86✔
585
            REQUIRE(obj.get_any(col) == Mixed{3});
86!
586

43✔
587
            // make a change
43✔
588
            table->begin()->set(col, 6);
86✔
589
            realm2->commit_transaction();
86✔
590
            wait_for_upload(*realm2);
86✔
591
            wait_for_download(*realm2);
86✔
592

43✔
593
            realm2->begin_transaction();
86✔
594
            if (m_make_remote_changes) {
86✔
595
                m_make_remote_changes(realm2);
22✔
596
            }
22✔
597
            realm2->commit_transaction();
86✔
598
            wait_for_upload(*realm2);
86✔
599
            wait_for_download(*realm2);
86✔
600
            realm2->close();
86✔
601
        }
86✔
602

43✔
603
        // Resuming sync on the first realm should now result in a client reset
43✔
604
        session->resume();
86✔
605
        if (m_on_post_local) {
86✔
606
            m_on_post_local(realm);
32✔
607
        }
32✔
608
        if (!m_wait_for_reset_completion) {
86✔
609
            return;
4✔
610
        }
4✔
611
        wait_for_upload(*realm);
82✔
612
        if (m_on_post_reset) {
82✔
613
            m_on_post_reset(realm);
62✔
614
        }
62✔
615
    }
82✔
616

617
private:
618
    TestAppSession& m_test_app_session;
619
};
620

621
struct BaasFLXClientReset : public TestClientReset {
622
    BaasFLXClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
623
                       const TestAppSession& test_app_session)
624
        : TestClientReset(local_config, remote_config)
625
        , m_test_app_session(test_app_session)
626
    {
24✔
627
        REALM_ASSERT(m_local_config.sync_config->flx_sync_requested);
24✔
628
        REALM_ASSERT(m_remote_config.sync_config->flx_sync_requested);
24✔
629
        REALM_ASSERT(m_local_config.schema->find(c_object_schema_name) != m_local_config.schema->end());
24✔
630
    }
24✔
631

632
    void run() override
633
    {
24✔
634
        m_did_run = true;
24✔
635
        const AppSession& app_session = m_test_app_session.app_session();
24✔
636

12✔
637
        auto realm = Realm::get_shared_realm(m_local_config);
24✔
638
        auto session = realm->sync_session();
24✔
639
        if (m_on_setup) {
24✔
640
            m_on_setup(realm);
2✔
641
        }
2✔
642

12✔
643
        ObjectId pk_of_added_object = [&] {
24✔
644
            if (m_populate_initial_object) {
24✔
645
                return m_populate_initial_object(realm);
2✔
646
            }
2✔
647

11✔
648
            auto ret = ObjectId::gen();
22✔
649
            constexpr bool create_object = true;
22✔
650
            subscribe_to_object_by_id(realm, ret, create_object);
22✔
651
            return ret;
22✔
652
        }();
22✔
653

12✔
654
        session->pause();
24✔
655

12✔
656
        if (m_make_local_changes) {
24✔
657
            m_make_local_changes(realm);
20✔
658
        }
20✔
659

12✔
660
        trigger_client_reset(app_session, realm);
24✔
661

12✔
662
        {
24✔
663
            auto realm2 = Realm::get_shared_realm(m_remote_config);
24✔
664
            wait_for_download(*realm2);
24✔
665
            load_initial_data(realm2);
24✔
666

12✔
667
            timed_sleeping_wait_for(
24✔
668
                [&]() -> bool {
24✔
669
                    realm2->begin_transaction();
24✔
670
                    auto table = get_table(*realm2, c_object_schema_name);
24✔
671
                    auto objkey = table->find_primary_key({pk_of_added_object});
24✔
672
                    realm2->cancel_transaction();
24✔
673
                    return bool(objkey);
24✔
674
                },
24✔
675
                std::chrono::seconds(60));
24✔
676

12✔
677
            // expect the last sync'd object to be in place
12✔
678
            realm2->begin_transaction();
24✔
679
            auto table = get_table(*realm2, c_object_schema_name);
24✔
680
            REQUIRE(table->size() >= 1);
24!
681
            auto obj = table->get_object_with_primary_key({pk_of_added_object});
24✔
682
            REQUIRE(obj.is_valid());
24!
683
            realm2->commit_transaction();
24✔
684

12✔
685
            if (m_make_remote_changes) {
24✔
686
                m_make_remote_changes(realm2);
12✔
687
            }
12✔
688
            wait_for_upload(*realm2);
24✔
689
            auto subs = realm2->get_latest_subscription_set();
24✔
690
            subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
24✔
691
            realm2->close();
24✔
692
        }
24✔
693

12✔
694
        // Resuming sync on the first realm should now result in a client reset
12✔
695
        session->resume();
24✔
696
        if (m_on_post_local) {
24✔
697
            m_on_post_local(realm);
2✔
698
        }
2✔
699
        wait_for_upload(*realm);
24✔
700
        if (m_on_post_reset) {
24✔
701
            m_on_post_reset(realm);
20✔
702
        }
20✔
703
    }
24✔
704

705
private:
706
    void subscribe_to_object_by_id(SharedRealm realm, ObjectId pk, bool create_object = false)
707
    {
22✔
708
        auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
22✔
709
        Group::TableNameBuffer buffer;
22✔
710
        auto class_name = Group::class_name_to_table_name(c_object_schema_name, buffer);
22✔
711
        TableRef table = realm->read_group().get_table(class_name);
22✔
712
        REALM_ASSERT(table);
22✔
713
        ColKey id_col = table->get_column_key(c_id_col_name);
22✔
714
        REALM_ASSERT(id_col);
22✔
715
        ColKey str_col = table->get_column_key(c_str_col_name);
22✔
716
        REALM_ASSERT(str_col);
22✔
717
        Query query_for_added_object = table->where().equal(id_col, pk);
22✔
718
        mut_subs.insert_or_assign(query_for_added_object);
22✔
719
        auto subs = std::move(mut_subs).commit();
22✔
720
        subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
22✔
721
        if (create_object) {
22✔
722
            realm->begin_transaction();
22✔
723
            table->create_object_with_primary_key(pk, {{str_col, "initial value"}});
22✔
724
            realm->commit_transaction();
22✔
725
        }
22✔
726
        wait_for_upload(*realm);
22✔
727
    }
22✔
728

729
    void load_initial_data(SharedRealm realm)
730
    {
24✔
731
        auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
24✔
732
        for (const auto& table : realm->schema()) {
48✔
733
            Query query_for_table(realm->read_group().get_table(table.table_key));
48✔
734
            mut_subs.insert_or_assign(query_for_table);
48✔
735
        }
48✔
736
        auto subs = std::move(mut_subs).commit();
24✔
737
        subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
24✔
738
    }
24✔
739

740
    const TestAppSession& m_test_app_session;
741
    constexpr static std::string_view c_object_schema_name = "TopLevel";
742
    constexpr static std::string_view c_id_col_name = "_id";
743
    constexpr static std::string_view c_str_col_name = "queryable_str_field";
744
};
745

746
std::unique_ptr<TestClientReset> make_baas_client_reset(const Realm::Config& local_config,
747
                                                        const Realm::Config& remote_config,
748
                                                        TestAppSession& test_app_session)
749
{
110✔
750
    return std::make_unique<BaasClientReset>(local_config, remote_config, test_app_session);
110✔
751
}
110✔
752

753
std::unique_ptr<TestClientReset> make_baas_flx_client_reset(const Realm::Config& local_config,
754
                                                            const Realm::Config& remote_config,
755
                                                            const TestAppSession& session)
756
{
24✔
757
    return std::make_unique<BaasFLXClientReset>(local_config, remote_config, session);
24✔
758
}
24✔
759

760
#endif // REALM_ENABLE_AUTH_TESTS
761

762
#endif // REALM_ENABLE_SYNC
763

764

765
TestClientReset::TestClientReset(const Realm::Config& local_config, const Realm::Config& remote_config)
766
    : m_local_config(local_config)
767
    , m_remote_config(remote_config)
768
{
3,366✔
769
}
3,366✔
770
TestClientReset::~TestClientReset()
771
{
3,366✔
772
    // make sure we didn't forget to call run()
1,683✔
773
    REALM_ASSERT(m_did_run || !(m_make_local_changes || m_make_remote_changes || m_on_post_local || m_on_post_reset));
3,366✔
774
}
3,366✔
775

776
TestClientReset* TestClientReset::setup(Callback&& on_setup)
777
{
3,378✔
778
    m_on_setup = std::move(on_setup);
3,378✔
779
    return this;
3,378✔
780
}
3,378✔
781
TestClientReset* TestClientReset::make_local_changes(Callback&& changes_local)
782
{
3,282✔
783
    m_make_local_changes = std::move(changes_local);
3,282✔
784
    return this;
3,282✔
785
}
3,282✔
786
TestClientReset* TestClientReset::populate_initial_object(InitialObjectCallback&& callback)
787
{
2✔
788
    m_populate_initial_object = std::move(callback);
2✔
789
    return this;
2✔
790
}
2✔
791

792
TestClientReset* TestClientReset::make_remote_changes(Callback&& changes_remote)
793
{
3,250✔
794
    m_make_remote_changes = std::move(changes_remote);
3,250✔
795
    return this;
3,250✔
796
}
3,250✔
797
TestClientReset* TestClientReset::on_post_local_changes(Callback&& post_local)
798
{
3,030✔
799
    m_on_post_local = std::move(post_local);
3,030✔
800
    return this;
3,030✔
801
}
3,030✔
802
TestClientReset* TestClientReset::on_post_reset(Callback&& post_reset)
803
{
3,308✔
804
    m_on_post_reset = std::move(post_reset);
3,308✔
805
    return this;
3,308✔
806
}
3,308✔
807

808
void TestClientReset::set_pk_of_object_driving_reset(const ObjectId& pk)
809
{
2✔
810
    m_pk_driving_reset = pk;
2✔
811
}
2✔
812

813
ObjectId TestClientReset::get_pk_of_object_driving_reset() const
814
{
2✔
815
    return m_pk_driving_reset;
2✔
816
}
2✔
817

818
void TestClientReset::disable_wait_for_reset_completion()
819
{
4✔
820
    m_wait_for_reset_completion = false;
4✔
821
}
4✔
822

823
std::unique_ptr<TestClientReset> make_fake_local_client_reset(const Realm::Config& local_config,
824
                                                              const Realm::Config& remote_config)
825
{
3,232✔
826
    return std::make_unique<FakeLocalClientReset>(local_config, remote_config);
3,232✔
827
}
3,232✔
828

829
} // namespace reset_utils
830

831
} // namespace realm
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