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

realm / realm-core / michael.wilkersonbarker_1128

31 May 2024 12:31AM CUT coverage: 90.825% (-0.02%) from 90.846%
michael.wilkersonbarker_1128

Pull #7649

Evergreen

michael-wb
Merge branch 'master' of github.com:realm/realm-core into mwb/add-extra-client-reset-fields
Pull Request #7649: RCORE-1386 Track client reset reason in table that detects client reset cycles

101648 of 179968 branches covered (56.48%)

598 of 636 new or added lines in 17 files covered. (94.03%)

120 existing lines in 17 files now uncovered.

214684 of 236371 relevant lines covered (90.83%)

5828091.73 hits per line

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

88.87
/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/test_file.hpp>
22
#include <util/sync/baas_admin_api.hpp>
23

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

32
#include <realm/sync/client_base.hpp>
33
#include <realm/sync/protocol.hpp>
34
#include <realm/sync/noinst/client_history_impl.hpp>
35
#include <realm/sync/noinst/client_reset.hpp>
36

37
#include <realm/util/base64.hpp>
38
#include <realm/util/hex_dump.hpp>
39
#include <realm/util/sha_crypto.hpp>
40

41
#include <chrono>
42

43
namespace realm {
44

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

58
bool ReturnsTrueWithinTimeLimit::match(util::FunctionRef<bool()> condition) const
59
{
4✔
60
    const auto wait_start = std::chrono::steady_clock::now();
4✔
61
    const auto delay = TEST_TIMEOUT_EXTRA > 0 ? m_max_ms + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : m_max_ms;
4✔
62
    bool predicate_returned_true = false;
4✔
63
    util::EventLoop::main().run_until([&] {
6✔
64
        if (std::chrono::steady_clock::now() - wait_start > delay) {
6✔
65
            util::format("ReturnsTrueWithinTimeLimit exceeded %1 ms", delay.count());
×
66
            return true;
×
67
        }
×
68
        auto ret = condition();
6✔
69
        if (ret) {
6✔
70
            predicate_returned_true = true;
6✔
71
        }
6✔
72
        return ret;
6✔
73
    });
6✔
74

75
    return predicate_returned_true;
4✔
76
}
4✔
77

78
void timed_wait_for(util::FunctionRef<bool()> condition, std::chrono::milliseconds max_ms)
79
{
118✔
80
    const auto wait_start = std::chrono::steady_clock::now();
118✔
81
    const auto delay = TEST_TIMEOUT_EXTRA > 0 ? max_ms + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : max_ms;
118✔
82
    util::EventLoop::main().run_until([&] {
56,388,903✔
83
        if (std::chrono::steady_clock::now() - wait_start > delay) {
56,388,903✔
84
            throw std::runtime_error(util::format("timed_wait_for exceeded %1 ms", delay.count()));
×
85
        }
×
86
        return condition();
56,388,903✔
87
    });
56,388,903✔
88
}
118✔
89

90
void timed_sleeping_wait_for(util::FunctionRef<bool()> condition, std::chrono::milliseconds max_ms,
91
                             std::chrono::milliseconds sleep_ms)
92
{
793✔
93
    const auto wait_start = std::chrono::steady_clock::now();
793✔
94
    const auto delay = TEST_TIMEOUT_EXTRA > 0 ? max_ms + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : max_ms;
793✔
95
    while (!condition()) {
9,362✔
96
        if (std::chrono::steady_clock::now() - wait_start > delay) {
8,569✔
97
            throw std::runtime_error(util::format("timed_sleeping_wait_for exceeded %1 ms", delay.count()));
×
98
        }
×
99
        std::this_thread::sleep_for(sleep_ms);
8,569✔
100
    }
8,569✔
101
}
793✔
102

103
auto do_hash = [](const std::string& name) -> std::string {
18✔
104
    std::array<unsigned char, 32> hash;
18✔
105
    util::sha256(name.data(), name.size(), hash.data());
18✔
106
    return util::hex_dump(hash.data(), hash.size(), "");
18✔
107
};
18✔
108

109
ExpectedRealmPaths::ExpectedRealmPaths(const std::string& base_path, const std::string& app_id,
110
                                       const std::string& identity, const std::vector<std::string>& legacy_identities,
111
                                       const std::string& partition)
112
{
18✔
113
    // This is copied from SyncManager.cpp string_from_partition() in order to prevent
114
    // us changing that function and therefore breaking user's existing paths unknowingly.
115
    std::string cleaned_partition;
18✔
116
    bson::Bson partition_value = bson::parse(partition);
18✔
117
    switch (partition_value.type()) {
18✔
118
        case bson::Bson::Type::Int32:
✔
119
            cleaned_partition = util::format("i_%1", static_cast<int32_t>(partition_value));
×
120
            break;
×
121
        case bson::Bson::Type::Int64:
✔
122
            cleaned_partition = util::format("l_%1", static_cast<int64_t>(partition_value));
×
123
            break;
×
124
        case bson::Bson::Type::String:
18✔
125
            cleaned_partition = util::format("s_%1", static_cast<std::string>(partition_value));
18✔
126
            break;
18✔
127
        case bson::Bson::Type::ObjectId:
✔
128
            cleaned_partition = util::format("o_%1", static_cast<ObjectId>(partition_value).to_string());
×
129
            break;
×
130
        case bson::Bson::Type::Uuid:
✔
131
            cleaned_partition = util::format("u_%1", static_cast<UUID>(partition_value).to_string());
×
132
            break;
×
133
        case bson::Bson::Type::Null:
✔
134
            cleaned_partition = "null";
×
135
            break;
×
136
        default:
✔
137
            REALM_ASSERT(false);
×
138
    }
18✔
139

140
    std::string clean_name = cleaned_partition;
18✔
141
    std::string cleaned_app_id = util::make_percent_encoded_string(app_id);
18✔
142
    const auto manager_path = fs::path{base_path}.make_preferred() / "mongodb-realm" / cleaned_app_id;
18✔
143
    const auto preferred_name = manager_path / identity / clean_name;
18✔
144
    current_preferred_path = preferred_name.string() + ".realm";
18✔
145
    fallback_hashed_path = (manager_path / do_hash(preferred_name.string())).string() + ".realm";
18✔
146

147
    if (legacy_identities.size() < 1)
18✔
148
        return;
×
149
    auto& local_identity = legacy_identities[0];
18✔
150
    legacy_sync_directories_to_make.push_back((manager_path / local_identity).string());
18✔
151
    std::string encoded_partition = util::make_percent_encoded_string(partition);
18✔
152
    legacy_local_id_path = (manager_path / local_identity / encoded_partition).concat(".realm").string();
18✔
153
    auto dir_builder = manager_path / "realm-object-server";
18✔
154
    legacy_sync_directories_to_make.push_back(dir_builder.string());
18✔
155
    dir_builder /= local_identity;
18✔
156
    legacy_sync_directories_to_make.push_back(dir_builder.string());
18✔
157
    legacy_sync_path = (dir_builder / cleaned_partition).string();
18✔
158
}
18✔
159

160
std::string unquote_string(std::string_view possibly_quoted_string)
161
{
×
162
    if (possibly_quoted_string.size() > 0) {
×
163
        auto check_char = possibly_quoted_string.front();
×
164
        if (check_char == '"' || check_char == '\'') {
×
165
            possibly_quoted_string.remove_prefix(1);
×
166
        }
×
167
    }
×
168
    if (possibly_quoted_string.size() > 0) {
×
169
        auto check_char = possibly_quoted_string.back();
×
170
        if (check_char == '"' || check_char == '\'') {
×
171
            possibly_quoted_string.remove_suffix(1);
×
172
        }
×
173
    }
×
174
    return std::string{possibly_quoted_string};
×
175
}
×
176

177
#if REALM_ENABLE_SYNC
178

179
void subscribe_to_all_and_bootstrap(Realm& realm)
180
{
84✔
181
    auto mut_subs = realm.get_latest_subscription_set().make_mutable_copy();
84✔
182
    auto& group = realm.read_group();
84✔
183
    for (auto key : group.get_table_keys()) {
732✔
184
        if (group.table_is_public(key)) {
732✔
185
            auto table = group.get_table(key);
144✔
186
            if (table->get_table_type() == Table::Type::TopLevel) {
144✔
187
                mut_subs.insert_or_assign(table->where());
122✔
188
            }
122✔
189
        }
144✔
190
    }
732✔
191
    auto subs = std::move(mut_subs).commit();
84✔
192
    subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
84✔
193
    wait_for_download(realm);
84✔
194
}
84✔
195

196
#if REALM_ENABLE_AUTH_TESTS
197

198
void wait_for_sessions_to_close(const TestAppSession& test_app_session)
199
{
4✔
200
    timed_sleeping_wait_for(
4✔
201
        [&]() -> bool {
4✔
202
            return !test_app_session.sync_manager()->has_existing_sessions();
4✔
203
        },
4✔
204
        std::chrono::minutes(5), std::chrono::milliseconds(100));
4✔
205
}
4✔
206

207
std::string get_compile_time_base_url()
208
{
×
209
#ifdef REALM_MONGODB_ENDPOINT
210
    // allows configuration with or without quotes
211
    return unquote_string(REALM_QUOTE(REALM_MONGODB_ENDPOINT));
212
#else
213
    return {};
×
NEW
214
#endif // REALM_MONGODB_ENDPOINT
×
215
}
×
216

217
std::string get_compile_time_admin_url()
218
{
373✔
219
#ifdef REALM_ADMIN_ENDPOINT
220
    // allows configuration with or without quotes
221
    return unquote_string(REALM_QUOTE(REALM_ADMIN_ENDPOINT));
222
#else
223
    return {};
373✔
224
#endif // REALM_ADMIN_ENDPOINT
373✔
225
}
373✔
226
#endif // REALM_ENABLE_AUTH_TESTS
227

228
#if REALM_APP_SERVICES
229
AutoVerifiedEmailCredentials::AutoVerifiedEmailCredentials()
230
{
4,808✔
231
    // emails with this prefix will pass through the baas app due to the register function
232
    email = util::format("realm_tests_do_autoverify%1@%2.com", random_string(10), random_string(10));
4,808✔
233
    password = random_string(10);
4,808✔
234
    static_cast<AppCredentials&>(*this) = AppCredentials::username_password(email, password);
4,808✔
235
}
4,808✔
236

237
AutoVerifiedEmailCredentials create_user_and_log_in(app::SharedApp app)
238
{
4,750✔
239
    REQUIRE(app);
4,750!
240
    AutoVerifiedEmailCredentials creds;
4,750✔
241
    app->provider_client<app::App::UsernamePasswordProviderClient>().register_email(
4,750✔
242
        creds.email, creds.password, [&](util::Optional<app::AppError> error) {
4,750✔
243
            REQUIRE(!error);
4,750!
244
        });
4,750✔
245
    log_in_user(app, creds);
4,750✔
246
    return creds;
4,750✔
247
}
4,750✔
248

249
void log_in_user(app::SharedApp app, app::AppCredentials creds)
250
{
4,750✔
251
    REQUIRE(app);
4,750!
252
    app->log_in_with_credentials(creds,
4,750✔
253
                                 [&](std::shared_ptr<realm::SyncUser> user, util::Optional<app::AppError> error) {
4,750✔
254
                                     REQUIRE(user);
4,750!
255
                                     REQUIRE(!error);
4,750!
256
                                 });
4,750✔
257
}
4,750✔
258

259
#endif // REALM_APP_SERVICES
260

261
void wait_for_advance(Realm& realm)
262
{
60✔
263
    struct Context : BindingContext {
60✔
264
        Realm& realm;
60✔
265
        DB::version_type target_version;
60✔
266
        bool& done;
60✔
267
        Context(Realm& realm, bool& done)
60✔
268
            : realm(realm)
60✔
269
            , target_version(*realm.latest_snapshot_version())
60✔
270
            , done(done)
60✔
271
        {
60✔
272
        }
60✔
273

274
        void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
60✔
275
        {
60✔
276
            if (realm.read_transaction_version().version >= target_version) {
60✔
277
                done = true;
60✔
278
            }
60✔
279
        }
60✔
280
    };
60✔
281

282
    bool done = false;
60✔
283
    realm.m_binding_context = std::make_unique<Context>(realm, done);
60✔
284
    timed_wait_for([&] {
330✔
285
        return done;
330✔
286
    });
330✔
287
    realm.m_binding_context = nullptr;
60✔
288
}
60✔
289

290
void async_open_realm(const Realm::Config& config,
291
                      util::UniqueFunction<void(ThreadSafeReference&& ref, std::exception_ptr e)> finish)
292
{
22✔
293
    std::mutex mutex;
22✔
294
    bool did_finish = false;
22✔
295
    auto task = Realm::get_synchronized_realm(config);
22✔
296
    ThreadSafeReference tsr;
22✔
297
    std::exception_ptr err = nullptr;
22✔
298
    task->start([&](ThreadSafeReference&& ref, std::exception_ptr e) {
22✔
299
        std::lock_guard lock(mutex);
22✔
300
        did_finish = true;
22✔
301
        tsr = std::move(ref);
22✔
302
        err = e;
22✔
303
    });
22✔
304
    util::EventLoop::main().run_until([&] {
18,294,142✔
305
        std::lock_guard lock(mutex);
18,294,142✔
306
        return did_finish;
18,294,142✔
307
    });
18,294,142✔
308
    task->cancel(); // don't run the above notifier again on this session
22✔
309
    finish(std::move(tsr), err);
22✔
310
}
22✔
311

312
#endif // REALM_ENABLE_SYNC
313

314
class TestHelper {
315
public:
316
    static DBRef& get_db(SharedRealm const& shared_realm)
317
    {
318
        return Realm::Internal::get_db(*shared_realm);
319
    }
320
};
321

322
namespace reset_utils {
323

324
Obj create_object(Realm& realm, StringData object_type, util::Optional<ObjectId> primary_key,
325
                  util::Optional<Partition> partition)
326
{
7,498✔
327
    auto table = realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
7,498✔
328
    REQUIRE(table);
7,498!
329
    FieldValues values = {};
7,498✔
330
    if (partition) {
7,498✔
331
        ColKey col = table->get_column_key(partition->property_name);
194✔
332
        REALM_ASSERT(col);
194✔
333
        values.insert(col, Mixed{partition->value});
194✔
334
    }
194✔
335
    return table->create_object_with_primary_key(primary_key ? *primary_key : ObjectId::gen(), std::move(values));
7,498✔
336
}
7,498✔
337

338
namespace {
339

340
TableRef get_table(Realm& realm, StringData object_type)
341
{
7,552✔
342
    return realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
7,552✔
343
}
7,552✔
344

345
// Run through the client reset steps manually without involving a sync server.
346
// Useful for speed and when integration testing is not available on a platform.
347
struct FakeLocalClientReset : public TestClientReset {
348
    FakeLocalClientReset(const Realm::Config& local_config, const Realm::Config& remote_config)
349
        : TestClientReset(local_config, remote_config)
1,830✔
350
    {
3,660✔
351
        REALM_ASSERT(m_local_config.sync_config);
3,660✔
352
        m_mode = m_local_config.sync_config->client_resync_mode;
3,660✔
353
        REALM_ASSERT(m_mode == ClientResyncMode::DiscardLocal || m_mode == ClientResyncMode::Recover);
3,660✔
354
        // Turn off real sync. But we still need a SyncClientHistory for recovery mode so fake it.
355
        m_local_config.sync_config = {};
3,660✔
356
        m_remote_config.sync_config = {};
3,660✔
357
        m_local_config.force_sync_history = true;
3,660✔
358
        m_remote_config.force_sync_history = true;
3,660✔
359
        m_local_config.in_memory = true;
3,660✔
360
        m_local_config.encryption_key = std::vector<char>();
3,660✔
361
        m_remote_config.in_memory = true;
3,660✔
362
        m_remote_config.encryption_key = std::vector<char>();
3,660✔
363
    }
3,660✔
364

365
    void run() override
366
    {
3,652✔
367
        m_did_run = true;
3,652✔
368
        auto local_realm = Realm::get_shared_realm(m_local_config);
3,652✔
369
        if (m_on_setup) {
3,652✔
370
            local_realm->begin_transaction();
3,612✔
371
            m_on_setup(local_realm);
3,612✔
372
            local_realm->commit_transaction();
3,612✔
373

374
            // Update the sync history to mark this initial setup state as if it
375
            // has been uploaded so that it doesn't replay during recovery.
376
            auto history_local =
3,612✔
377
                dynamic_cast<sync::ClientHistory*>(local_realm->read_group().get_replication()->_get_history_write());
3,612✔
378
            REALM_ASSERT(history_local);
3,612✔
379
            sync::version_type current_version;
3,612✔
380
            sync::SaltedFileIdent file_ident;
3,612✔
381
            sync::SyncProgress progress;
3,612✔
382
            history_local->get_status(current_version, file_ident, progress);
3,612✔
383
            progress.upload.client_version = current_version;
3,612✔
384
            progress.upload.last_integrated_server_version = current_version;
3,612✔
385
            sync::VersionInfo info_out;
3,612✔
386
            history_local->set_sync_progress(progress, nullptr, info_out);
3,612✔
387
        }
3,612✔
388
        {
3,652✔
389
            local_realm->begin_transaction();
3,652✔
390
            auto obj = create_object(*local_realm, "object", m_pk_driving_reset);
3,652✔
391
            auto col = obj.get_table()->get_column_key("value");
3,652✔
392
            obj.set(col, 1);
3,652✔
393
            obj.set(col, 2);
3,652✔
394
            obj.set(col, 3);
3,652✔
395
            local_realm->commit_transaction();
3,652✔
396

397
            local_realm->begin_transaction();
3,652✔
398
            obj.set(col, 4);
3,652✔
399
            if (m_make_local_changes) {
3,652✔
400
                m_make_local_changes(local_realm);
3,640✔
401
            }
3,640✔
402
            local_realm->commit_transaction();
3,652✔
403
            if (m_on_post_local) {
3,652✔
404
                m_on_post_local(local_realm);
3,272✔
405
            }
3,272✔
406
        }
3,652✔
407

408
        {
3,652✔
409
            auto remote_realm = Realm::get_shared_realm(m_remote_config);
3,652✔
410
            remote_realm->begin_transaction();
3,652✔
411
            if (m_on_setup) {
3,652✔
412
                m_on_setup(remote_realm);
3,612✔
413
            }
3,612✔
414

415
            // fake a sync by creating an object with the same pk
416
            create_object(*remote_realm, "object", m_pk_driving_reset);
3,652✔
417

418
            for (int i = 0; i < 2; ++i) {
10,956✔
419
                auto table = get_table(*remote_realm, "object");
7,304✔
420
                auto col = table->get_column_key("value");
7,304✔
421
                table->begin()->set(col, i + 5);
7,304✔
422
            }
7,304✔
423

424
            if (m_make_remote_changes) {
3,652✔
425
                m_make_remote_changes(remote_realm);
3,624✔
426
            }
3,624✔
427
            remote_realm->commit_transaction();
3,652✔
428

429
            sync::SaltedFileIdent fake_ident{1, 123456789};
3,652✔
430
            auto local_db = TestHelper::get_db(local_realm);
3,652✔
431
            auto logger = util::Logger::get_default_logger();
3,652✔
432
            sync::ClientReset reset_config{m_mode,
3,652✔
433
                                           TestHelper::get_db(remote_realm),
3,652✔
434
                                           {ErrorCodes::SyncClientResetRequired, "Bad client file ident"}};
3,652✔
435

436
            using _impl::client_reset::perform_client_reset_diff;
3,652✔
437
            perform_client_reset_diff(*local_db, reset_config, fake_ident, *logger, nullptr, [](int64_t) {});
3,652✔
438

439
            remote_realm->close();
3,652✔
440
            if (m_on_post_reset) {
3,652✔
441
                m_on_post_reset(local_realm);
3,648✔
442
            }
3,648✔
443
        }
3,652✔
444
    }
3,652✔
445

446
private:
447
    ClientResyncMode m_mode;
448
};
449
} // anonymous namespace
450

451
#if REALM_ENABLE_SYNC
452

453
#if REALM_ENABLE_AUTH_TESTS
454

455
void wait_for_object_to_persist_to_atlas(std::shared_ptr<app::User> user, const AppSession& app_session,
456
                                         const std::string& schema_name, const bson::BsonDocument& filter_bson)
457
{
4✔
458
    // While at this point the object has been sync'd successfully, we must also
459
    // wait for it to appear in the backing database before terminating sync
460
    // otherwise the translator may be terminated before it has a chance to
461
    // integrate it into the backing database. If the server were to change
462
    // the meaning of "upload complete" to include writing to atlas then this would
463
    // not be necessary.
464
    app::MongoClient remote_client = user->mongo_client("BackingDB");
4✔
465
    app::MongoDatabase db = remote_client.db(app_session.config.mongo_dbname);
4✔
466
    app::MongoCollection object_coll = db[schema_name];
4✔
467

468
    timed_sleeping_wait_for(
4✔
469
        [&]() -> bool {
7✔
470
            auto pf = util::make_promise_future<uint64_t>();
7✔
471
            object_coll.count(filter_bson, [promise = std::move(pf.promise)](
7✔
472
                                               uint64_t count, util::Optional<app::AppError> error) mutable {
7✔
473
                REQUIRE(!error);
7!
474
                if (error) {
7✔
475
                    promise.set_error({ErrorCodes::RuntimeError, error->reason()});
×
476
                }
×
477
                else {
7✔
478
                    promise.emplace_value(count);
7✔
479
                }
7✔
480
            });
7✔
481
            return pf.future.get() > 0;
7✔
482
        },
7✔
483
        std::chrono::minutes(15), std::chrono::milliseconds(500));
4✔
484
}
4✔
485

486
void wait_for_num_objects_in_atlas(std::shared_ptr<app::User> user, const AppSession& app_session,
487
                                   const std::string& schema_name, size_t expected_size)
488
{
2✔
489
    app::MongoClient remote_client = user->mongo_client("BackingDB");
2✔
490
    app::MongoDatabase db = remote_client.db(app_session.config.mongo_dbname);
2✔
491
    app::MongoCollection object_coll = db[schema_name];
2✔
492

493
    const bson::BsonDocument& filter_bson{};
2✔
494
    timed_sleeping_wait_for(
2✔
495
        [&]() -> bool {
4✔
496
            auto pf = util::make_promise_future<uint64_t>();
4✔
497
            object_coll.count(filter_bson, [promise = std::move(pf.promise)](
4✔
498
                                               uint64_t count, util::Optional<app::AppError> error) mutable {
4✔
499
                REQUIRE(!error);
4!
500
                if (error) {
4✔
501
                    promise.set_error({ErrorCodes::RuntimeError, error->reason()});
×
502
                }
×
503
                else {
4✔
504
                    promise.emplace_value(count);
4✔
505
                }
4✔
506
            });
4✔
507
            return pf.future.get() >= expected_size;
4✔
508
        },
4✔
509
        std::chrono::minutes(15), std::chrono::milliseconds(500));
2✔
510
}
2✔
511

512
void trigger_client_reset(const AppSession& app_session, const SyncSession& sync_session)
513
{
158✔
514
    auto file_ident = sync_session.get_file_ident();
158✔
515
    REQUIRE(file_ident.ident != 0);
158!
516
    app_session.admin_api.trigger_client_reset(app_session.server_app_id, file_ident.ident);
158✔
517
}
158✔
518

519
void trigger_client_reset(const AppSession& app_session, const SharedRealm& realm)
520
{
150✔
521
    trigger_client_reset(app_session, *realm->sync_session());
150✔
522
}
150✔
523

524
struct BaasClientReset : public TestClientReset {
525
    BaasClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
526
                    TestAppSession& test_app_session)
527
        : TestClientReset(local_config, remote_config)
60✔
528
        , m_test_app_session(test_app_session)
60✔
529
    {
120✔
530
    }
120✔
531

532
    TestClientReset* set_development_mode(bool enable) override
533
    {
14✔
534
        const AppSession& app_session = m_test_app_session.app_session();
14✔
535
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, enable);
14✔
536
        return this;
14✔
537
    }
14✔
538

539
    void run() override
540
    {
96✔
541
        m_did_run = true;
96✔
542
        const AppSession& app_session = m_test_app_session.app_session();
96✔
543
        auto sync_manager = m_test_app_session.sync_manager();
96✔
544
        std::string partition_value = m_local_config.sync_config->partition_value;
96✔
545
        REALM_ASSERT(partition_value.size() > 2 && *partition_value.begin() == '"' &&
96✔
546
                     *(partition_value.end() - 1) == '"');
96✔
547
        partition_value = partition_value.substr(1, partition_value.size() - 2);
96✔
548
        Partition partition = {app_session.config.partition_key.name, partition_value};
96✔
549

550
        // There is a race in PBS where if initial sync is still in-progress while you're creating the initial
551
        // object below, you may end up creating it in your local realm, uploading it, have the translator process
552
        // the upload, then initial sync the processed object, and then send it back to you as an erase/create
553
        // object instruction.
554
        //
555
        // So just don't try to do anything until initial sync is done and we're sure the server is in a stable
556
        // state.
557
        timed_sleeping_wait_for(
96✔
558
            [&] {
96✔
559
                return app_session.admin_api.is_initial_sync_complete(app_session.server_app_id);
96✔
560
            },
96✔
561
            std::chrono::seconds(30), std::chrono::seconds(1));
96✔
562

563
        auto realm = Realm::get_shared_realm(m_local_config);
96✔
564
        auto session = sync_manager->get_existing_session(realm->config().path);
96✔
565
        const std::string object_schema_name = "object";
96✔
566
        {
96✔
567
            wait_for_download(*realm);
96✔
568
            realm->begin_transaction();
96✔
569

570
            if (m_on_setup) {
96✔
571
                m_on_setup(realm);
20✔
572
            }
20✔
573

574
            auto obj = create_object(*realm, object_schema_name, {m_pk_driving_reset}, {partition});
96✔
575
            auto table = obj.get_table();
96✔
576
            auto col = table->get_column_key("value");
96✔
577
            std::string pk_col_name = table->get_column_name(table->get_primary_key_column());
96✔
578
            obj.set(col, 1);
96✔
579
            obj.set(col, 2);
96✔
580
            constexpr int64_t last_synced_value = 3;
96✔
581
            obj.set(col, last_synced_value);
96✔
582
            realm->commit_transaction();
96✔
583
            wait_for_upload(*realm);
96✔
584
            wait_for_download(*realm);
96✔
585

586
            session->pause();
96✔
587

588
            realm->begin_transaction();
96✔
589
            obj.set(col, 4);
96✔
590
            if (m_make_local_changes) {
96✔
591
                m_make_local_changes(realm);
48✔
592
            }
48✔
593
            realm->commit_transaction();
96✔
594
        }
96✔
595

596
        trigger_client_reset(app_session, realm);
96✔
597

598
        {
96✔
599
            auto realm2 = Realm::get_shared_realm(m_remote_config);
96✔
600
            wait_for_download(*realm2);
96✔
601

602
            timed_sleeping_wait_for(
96✔
603
                [&]() -> bool {
96✔
604
                    realm2->begin_transaction();
96✔
605
                    auto table = get_table(*realm2, object_schema_name);
96✔
606
                    auto objkey = table->find_primary_key({m_pk_driving_reset});
96✔
607
                    realm2->cancel_transaction();
96✔
608
                    return bool(objkey);
96✔
609
                },
96✔
610
                std::chrono::seconds(60));
96✔
611

612
            // expect the last sync'd object to be in place
613
            realm2->begin_transaction();
96✔
614
            auto table = get_table(*realm2, object_schema_name);
96✔
615
            REQUIRE(table->size() >= 1);
96!
616
            auto obj = table->get_object_with_primary_key({m_pk_driving_reset});
96✔
617
            REQUIRE(obj.is_valid());
96!
618
            auto col = table->get_column_key("value");
96✔
619
            REQUIRE(obj.get_any(col) == Mixed{3});
96!
620

621
            // make a change
622
            table->begin()->set(col, 6);
96✔
623
            realm2->commit_transaction();
96✔
624
            wait_for_upload(*realm2);
96✔
625
            wait_for_download(*realm2);
96✔
626

627
            realm2->begin_transaction();
96✔
628
            if (m_make_remote_changes) {
96✔
629
                m_make_remote_changes(realm2);
26✔
630
            }
26✔
631
            realm2->commit_transaction();
96✔
632
            wait_for_upload(*realm2);
96✔
633
            wait_for_download(*realm2);
96✔
634
            realm2->close();
96✔
635
        }
96✔
636

637
        // Resuming sync on the first realm should now result in a client reset
638
        session->resume();
96✔
639
        if (m_on_post_local) {
96✔
640
            m_on_post_local(realm);
32✔
641
        }
32✔
642
        if (!m_wait_for_reset_completion) {
96✔
643
            return;
4✔
644
        }
4✔
645
        wait_for_upload(*realm);
92✔
646
        if (m_on_post_reset) {
92✔
647
            m_on_post_reset(realm);
70✔
648
        }
70✔
649
    }
92✔
650

651
private:
652
    TestAppSession& m_test_app_session;
653
};
654

655
struct BaasFLXClientReset : public TestClientReset {
656
    BaasFLXClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
657
                       const TestAppSession& test_app_session)
658
        : TestClientReset(local_config, remote_config)
14✔
659
        , m_test_app_session(test_app_session)
14✔
660
    {
28✔
661
        REALM_ASSERT(m_local_config.sync_config->flx_sync_requested);
28✔
662
        REALM_ASSERT(m_remote_config.sync_config->flx_sync_requested);
28✔
663
        REALM_ASSERT(m_local_config.schema->find(c_object_schema_name) != m_local_config.schema->end());
28✔
664
    }
28✔
665

666
    TestClientReset* set_development_mode(bool enable) override
667
    {
×
668
        const AppSession& app_session = m_test_app_session.app_session();
×
669
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, enable);
×
670
        return this;
×
671
    }
×
672

673
    void run() override
674
    {
28✔
675
        m_did_run = true;
28✔
676
        const AppSession& app_session = m_test_app_session.app_session();
28✔
677

678
        auto realm = Realm::get_shared_realm(m_local_config);
28✔
679
        auto session = realm->sync_session();
28✔
680
        if (m_on_setup) {
28✔
681
            m_on_setup(realm);
2✔
682
        }
2✔
683

684
        ObjectId pk_of_added_object = [&] {
28✔
685
            if (m_populate_initial_object) {
28✔
686
                return m_populate_initial_object(realm);
4✔
687
            }
4✔
688

689
            auto ret = ObjectId::gen();
24✔
690
            constexpr bool create_object = true;
24✔
691
            subscribe_to_object_by_id(realm, ret, create_object);
24✔
692
            return ret;
24✔
693
        }();
28✔
694

695
        session->pause();
28✔
696

697
        if (m_make_local_changes) {
28✔
698
            m_make_local_changes(realm);
24✔
699
        }
24✔
700

701
        trigger_client_reset(app_session, realm);
28✔
702

703
        {
28✔
704
            auto realm2 = Realm::get_shared_realm(m_remote_config);
28✔
705
            wait_for_download(*realm2);
28✔
706
            load_initial_data(realm2);
28✔
707

708
            timed_sleeping_wait_for(
28✔
709
                [&]() -> bool {
28✔
710
                    realm2->begin_transaction();
28✔
711
                    auto table = get_table(*realm2, c_object_schema_name);
28✔
712
                    auto objkey = table->find_primary_key({pk_of_added_object});
28✔
713
                    realm2->cancel_transaction();
28✔
714
                    return bool(objkey);
28✔
715
                },
28✔
716
                std::chrono::seconds(60));
28✔
717

718
            // expect the last sync'd object to be in place
719
            realm2->begin_transaction();
28✔
720
            auto table = get_table(*realm2, c_object_schema_name);
28✔
721
            REQUIRE(table->size() >= 1);
28!
722
            auto obj = table->get_object_with_primary_key({pk_of_added_object});
28✔
723
            REQUIRE(obj.is_valid());
28!
724
            realm2->commit_transaction();
28✔
725

726
            if (m_make_remote_changes) {
28✔
727
                m_make_remote_changes(realm2);
16✔
728
            }
16✔
729
            wait_for_upload(*realm2);
28✔
730
            auto subs = realm2->get_latest_subscription_set();
28✔
731
            subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
28✔
732
            realm2->close();
28✔
733
        }
28✔
734

735
        // Resuming sync on the first realm should now result in a client reset
736
        session->resume();
28✔
737
        if (m_on_post_local) {
28✔
738
            m_on_post_local(realm);
6✔
739
        }
6✔
740
        wait_for_upload(*realm);
28✔
741
        if (m_on_post_reset) {
28✔
742
            m_on_post_reset(realm);
24✔
743
        }
24✔
744
    }
28✔
745

746
private:
747
    void subscribe_to_object_by_id(SharedRealm realm, ObjectId pk, bool create_object = false)
748
    {
24✔
749
        auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
24✔
750
        Group::TableNameBuffer buffer;
24✔
751
        auto class_name = Group::class_name_to_table_name(c_object_schema_name, buffer);
24✔
752
        TableRef table = realm->read_group().get_table(class_name);
24✔
753
        REALM_ASSERT(table);
24✔
754
        ColKey id_col = table->get_column_key(c_id_col_name);
24✔
755
        REALM_ASSERT(id_col);
24✔
756
        ColKey str_col = table->get_column_key(c_str_col_name);
24✔
757
        REALM_ASSERT(str_col);
24✔
758
        Query query_for_added_object = table->where().equal(id_col, pk);
24✔
759
        mut_subs.insert_or_assign(query_for_added_object);
24✔
760
        auto subs = std::move(mut_subs).commit();
24✔
761
        subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
24✔
762
        if (create_object) {
24✔
763
            realm->begin_transaction();
24✔
764
            table->create_object_with_primary_key(pk, {{str_col, "initial value"}});
24✔
765
            realm->commit_transaction();
24✔
766
        }
24✔
767
        wait_for_upload(*realm);
24✔
768
    }
24✔
769

770
    void load_initial_data(SharedRealm realm)
771
    {
28✔
772
        auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
28✔
773
        for (const auto& table : realm->schema()) {
56✔
774
            Query query_for_table(realm->read_group().get_table(table.table_key));
56✔
775
            mut_subs.insert_or_assign(query_for_table);
56✔
776
        }
56✔
777
        auto subs = std::move(mut_subs).commit();
28✔
778
        subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
28✔
779
    }
28✔
780

781
    const TestAppSession& m_test_app_session;
782
    constexpr static std::string_view c_object_schema_name = "TopLevel";
783
    constexpr static std::string_view c_id_col_name = "_id";
784
    constexpr static std::string_view c_str_col_name = "queryable_str_field";
785
};
786

787
std::unique_ptr<TestClientReset> make_baas_client_reset(const Realm::Config& local_config,
788
                                                        const Realm::Config& remote_config,
789
                                                        TestAppSession& test_app_session)
790
{
120✔
791
    return std::make_unique<BaasClientReset>(local_config, remote_config, test_app_session);
120✔
792
}
120✔
793

794
std::unique_ptr<TestClientReset> make_baas_flx_client_reset(const Realm::Config& local_config,
795
                                                            const Realm::Config& remote_config,
796
                                                            const TestAppSession& session)
797
{
28✔
798
    return std::make_unique<BaasFLXClientReset>(local_config, remote_config, session);
28✔
799
}
28✔
800

801
#endif // REALM_ENABLE_AUTH_TESTS
802

803
#endif // REALM_ENABLE_SYNC
804

805

806
TestClientReset::TestClientReset(const Realm::Config& local_config, const Realm::Config& remote_config)
807
    : m_local_config(local_config)
1,904✔
808
    , m_remote_config(remote_config)
1,904✔
809
{
3,808✔
810
}
3,808✔
811
TestClientReset::~TestClientReset()
812
{
3,808✔
813
    // make sure we didn't forget to call run()
814
    REALM_ASSERT(m_did_run || !(m_make_local_changes || m_make_remote_changes || m_on_post_local || m_on_post_reset));
3,808✔
815
}
3,808✔
816

817
TestClientReset* TestClientReset::setup(Callback&& on_setup)
818
{
3,926✔
819
    m_on_setup = std::move(on_setup);
3,926✔
820
    return this;
3,926✔
821
}
3,926✔
822
TestClientReset* TestClientReset::make_local_changes(Callback&& changes_local)
823
{
3,712✔
824
    m_make_local_changes = std::move(changes_local);
3,712✔
825
    return this;
3,712✔
826
}
3,712✔
827
TestClientReset* TestClientReset::populate_initial_object(InitialObjectCallback&& callback)
828
{
4✔
829
    m_populate_initial_object = std::move(callback);
4✔
830
    return this;
4✔
831
}
4✔
832

833
TestClientReset* TestClientReset::make_remote_changes(Callback&& changes_remote)
834
{
3,666✔
835
    m_make_remote_changes = std::move(changes_remote);
3,666✔
836
    return this;
3,666✔
837
}
3,666✔
838
TestClientReset* TestClientReset::on_post_local_changes(Callback&& post_local)
839
{
3,310✔
840
    m_on_post_local = std::move(post_local);
3,310✔
841
    return this;
3,310✔
842
}
3,310✔
843
TestClientReset* TestClientReset::on_post_reset(Callback&& post_reset)
844
{
3,746✔
845
    m_on_post_reset = std::move(post_reset);
3,746✔
846
    return this;
3,746✔
847
}
3,746✔
848
TestClientReset* TestClientReset::set_development_mode(bool)
849
{
×
850
    return this;
×
851
}
×
852

853
void TestClientReset::set_pk_of_object_driving_reset(const ObjectId& pk)
854
{
2✔
855
    m_pk_driving_reset = pk;
2✔
856
}
2✔
857

858
ObjectId TestClientReset::get_pk_of_object_driving_reset() const
859
{
2✔
860
    return m_pk_driving_reset;
2✔
861
}
2✔
862

863
void TestClientReset::disable_wait_for_reset_completion()
864
{
4✔
865
    m_wait_for_reset_completion = false;
4✔
866
}
4✔
867

868
std::unique_ptr<TestClientReset> make_fake_local_client_reset(const Realm::Config& local_config,
869
                                                              const Realm::Config& remote_config)
870
{
3,660✔
871
    return std::make_unique<FakeLocalClientReset>(local_config, remote_config);
3,660✔
872
}
3,660✔
873

874
} // namespace reset_utils
875

876
} // 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