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

realm / realm-core / 1833

14 Nov 2023 03:52AM UTC coverage: 91.644% (-0.05%) from 91.69%
1833

push

Evergreen

web-flow
Merge feature/network-faultiness branch into master (#7120)

* Add baas-network-tests nightly task for testing sync client operation with non-ideal network conditions (#6852)

* Added support for starting baas proxy
* Fixed some issues when running the scripts
* minor updates to install_baas.sh
* Updates to scripts to run on evergreen spawn host
* Added total time output to object store tests
* Increased initial ssh connect attempts; renamed proxy to 'baas_proxy'
* Minor updates to help output
* Added baas network test to run bass with the proxy
* Added support for separate baas admin api url value
* Minor port check adjustments
* Removed 'compile' task from network_tests
* Disable development mode by default
* Added baas remote host and network tests instructions doc
* Minor updates to the instructions
* Minor updates to documentation

* Add non-ideal transfer and network fault tests with non-ideal network conditions (#7063)

* Added non-ideal transfer and network fault tests
* Added baas-network-tests to be allowed for PR runs
* Updated changelog
* Updated curl command to be silent
* Updated changelog
* Reverted some changes and added descriptions to config.yml

* Fixed test extra delay

* Added some evergreen script updates from another branch

* Disabled baas network faults tests

* Update flx migration test to enable dev mode

* Fix baas branch for network tests

* add more time to 'too large sync message error handling' test

* Added network faults test and pulled nonideal out of nightly tests until fixes are added

92116 of 168852 branches covered (0.0%)

179 of 192 new or added lines in 14 files covered. (93.23%)

140 existing lines in 19 files now uncovered.

231053 of 252119 relevant lines covered (91.64%)

6637899.19 hits per line

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

92.73
/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
    const auto delay = TEST_TIMEOUT_EXTRA > 0 ? m_max_ms + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : m_max_ms;
4✔
80
    bool predicate_returned_true = false;
4✔
81
    util::EventLoop::main().run_until([&] {
16✔
82
        if (std::chrono::steady_clock::now() - wait_start > delay) {
16✔
NEW
83
            util::format("ReturnsTrueWithinTimeLimit exceeded %1 ms", delay.count());
×
84
            return true;
×
85
        }
×
86
        auto ret = condition();
16✔
87
        if (ret) {
16✔
88
            predicate_returned_true = true;
6✔
89
        }
6✔
90
        return ret;
16✔
91
    });
16✔
92

2✔
93
    return predicate_returned_true;
4✔
94
}
4✔
95

96
void timed_wait_for(util::FunctionRef<bool()> condition, std::chrono::milliseconds max_ms)
97
{
114✔
98
    const auto wait_start = std::chrono::steady_clock::now();
114✔
99
    const auto delay = TEST_TIMEOUT_EXTRA > 0 ? max_ms + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : max_ms;
114✔
100
    util::EventLoop::main().run_until([&] {
26,151,680✔
101
        if (std::chrono::steady_clock::now() - wait_start > delay) {
26,151,680✔
NEW
102
            throw std::runtime_error(util::format("timed_wait_for exceeded %1 ms", delay.count()));
×
UNCOV
103
        }
×
104
        return condition();
26,151,680✔
105
    });
26,151,680✔
106
}
114✔
107

108
void timed_sleeping_wait_for(util::FunctionRef<bool()> condition, std::chrono::milliseconds max_ms,
109
                             std::chrono::milliseconds sleep_ms)
110
{
364✔
111
    const auto wait_start = std::chrono::steady_clock::now();
364✔
112
    const auto delay = TEST_TIMEOUT_EXTRA > 0 ? max_ms + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : max_ms;
364✔
113
    while (!condition()) {
17,233✔
114
        if (std::chrono::steady_clock::now() - wait_start > delay) {
16,869✔
NEW
115
            throw std::runtime_error(util::format("timed_sleeping_wait_for exceeded %1 ms", delay.count()));
×
UNCOV
116
        }
×
117
        std::this_thread::sleep_for(sleep_ms);
16,869✔
118
    }
16,869✔
119
}
364✔
120

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

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

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

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

178
#if REALM_ENABLE_SYNC
179

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

197
#if REALM_ENABLE_AUTH_TESTS
198

199
static std::string unquote_string(std::string_view possibly_quoted_string)
200
{
648✔
201
    if (possibly_quoted_string.size() > 0) {
648✔
202
        auto check_char = possibly_quoted_string.front();
648✔
203
        if (check_char == '"' || check_char == '\'') {
648!
204
            possibly_quoted_string.remove_prefix(1);
648✔
205
        }
648✔
206
    }
648✔
207
    if (possibly_quoted_string.size() > 0) {
648✔
208
        auto check_char = possibly_quoted_string.back();
648✔
209
        if (check_char == '"' || check_char == '\'') {
648!
210
            possibly_quoted_string.remove_suffix(1);
648✔
211
        }
648✔
212
    }
648✔
213
    return std::string{possibly_quoted_string};
648✔
214
}
648✔
215

216
#ifdef REALM_MONGODB_ENDPOINT
217
std::string get_base_url()
218
{
648✔
219
    // allows configuration with or without quotes
309✔
220
    return unquote_string(REALM_QUOTE(REALM_MONGODB_ENDPOINT));
648✔
221
}
648✔
222

223
std::string get_admin_url()
224
{
311✔
225
#ifdef REALM_ADMIN_ENDPOINT
226
    // allows configuration with or without quotes
227
    return unquote_string(REALM_QUOTE(REALM_ADMIN_ENDPOINT));
228
#else
229
    return get_base_url();
311✔
230
#endif
311✔
231
}
311✔
232
#endif // REALM_MONGODB_ENDPOINT
233

234
AutoVerifiedEmailCredentials::AutoVerifiedEmailCredentials()
235
{
1,008✔
236
    // emails with this prefix will pass through the baas app due to the register function
493✔
237
    email = util::format("realm_tests_do_autoverify%1@%2.com", random_string(10), random_string(10));
1,008✔
238
    password = random_string(10);
1,008✔
239
    static_cast<AppCredentials&>(*this) = AppCredentials::username_password(email, password);
1,008✔
240
}
1,008✔
241

242
AutoVerifiedEmailCredentials create_user_and_log_in(app::SharedApp app)
243
{
906✔
244
    REQUIRE(app);
906!
245
    AutoVerifiedEmailCredentials creds;
906✔
246
    app->provider_client<app::App::UsernamePasswordProviderClient>().register_email(
906✔
247
        creds.email, creds.password, [&](util::Optional<app::AppError> error) {
906✔
248
            REQUIRE(!error);
906!
249
        });
906✔
250
    app->log_in_with_credentials(realm::app::AppCredentials::username_password(creds.email, creds.password),
906✔
251
                                 [&](std::shared_ptr<realm::SyncUser> user, util::Optional<app::AppError> error) {
906✔
252
                                     REQUIRE(user);
906!
253
                                     REQUIRE(!error);
906!
254
                                 });
906✔
255
    return creds;
906✔
256
}
906✔
257

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

26✔
271
        void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
52✔
272
        {
52✔
273
            if (realm.read_transaction_version().version >= target_version) {
52✔
274
                done = true;
52✔
275
            }
52✔
276
        }
52✔
277
    };
52✔
278

26✔
279
    bool done = false;
52✔
280
    realm.m_binding_context = std::make_unique<Context>(realm, done);
52✔
281
    timed_wait_for([&] {
286✔
282
        return done;
286✔
283
    });
286✔
284
    realm.m_binding_context = nullptr;
52✔
285
}
52✔
286

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

309
#endif // REALM_ENABLE_AUTH_TESTS
310
#endif // REALM_ENABLE_SYNC
311

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

320
namespace reset_utils {
321

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

336
namespace {
337

338
TableRef get_table(Realm& realm, StringData object_type)
339
{
6,668✔
340
    return realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
6,668✔
341
}
6,668✔
342

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

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

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

1,612✔
395
            local_realm->begin_transaction();
3,224✔
396
            obj.set(col, 4);
3,224✔
397
            if (m_make_local_changes) {
3,224✔
398
                m_make_local_changes(local_realm);
3,220✔
399
            }
3,220✔
400
            local_realm->commit_transaction();
3,224✔
401
            if (m_on_post_local) {
3,224✔
402
                m_on_post_local(local_realm);
2,996✔
403
            }
2,996✔
404
        }
3,224✔
405

1,612✔
406
        {
3,224✔
407
            auto remote_realm = Realm::get_shared_realm(m_remote_config);
3,224✔
408
            remote_realm->begin_transaction();
3,224✔
409
            if (m_on_setup) {
3,224✔
410
                m_on_setup(remote_realm);
3,216✔
411
            }
3,216✔
412

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

1,612✔
416
            for (int i = 0; i < 2; ++i) {
9,672✔
417
                auto table = get_table(*remote_realm, "object");
6,448✔
418
                auto col = table->get_column_key("value");
6,448✔
419
                table->begin()->set(col, i + 5);
6,448✔
420
            }
6,448✔
421

1,612✔
422
            if (m_make_remote_changes) {
3,224✔
423
                m_make_remote_changes(remote_realm);
3,216✔
424
            }
3,216✔
425
            remote_realm->commit_transaction();
3,224✔
426

1,612✔
427
            sync::SaltedFileIdent fake_ident{1, 123456789};
3,224✔
428
            auto local_db = TestHelper::get_db(local_realm);
3,224✔
429
            auto remote_db = TestHelper::get_db(remote_realm);
3,224✔
430
            auto logger = util::Logger::get_default_logger();
3,224✔
431

1,612✔
432
            using _impl::client_reset::perform_client_reset_diff;
3,224✔
433
            constexpr bool recovery_is_allowed = true;
3,224✔
434
            perform_client_reset_diff(*local_db, *remote_db, fake_ident, *logger, m_mode, recovery_is_allowed,
3,224✔
435
                                      nullptr, nullptr, [](int64_t) {});
2,405✔
436

1,612✔
437
            remote_realm->close();
3,224✔
438
            if (m_on_post_reset) {
3,224✔
439
                m_on_post_reset(local_realm);
3,222✔
440
            }
3,222✔
441
        }
3,224✔
442
    }
3,224✔
443

444
private:
445
    ClientResyncMode m_mode;
446
};
447
} // anonymous namespace
448

449
#if REALM_ENABLE_SYNC
450

451
#if REALM_ENABLE_AUTH_TESTS
452

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

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

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

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

510
void trigger_client_reset(const AppSession& app_session)
511
{
4✔
512
    // cause a client reset by restarting the sync service
2✔
513
    // this causes the server's sync history to be resynthesized
2✔
514
    auto baas_sync_service = app_session.admin_api.get_sync_service(app_session.server_app_id);
4✔
515
    auto baas_sync_config = app_session.admin_api.get_config(app_session.server_app_id, baas_sync_service);
4✔
516

2✔
517
    REQUIRE(app_session.admin_api.is_sync_enabled(app_session.server_app_id));
4!
518
    app_session.admin_api.disable_sync(app_session.server_app_id, baas_sync_service.id, baas_sync_config);
4✔
519
    timed_sleeping_wait_for([&] {
154✔
520
        return app_session.admin_api.is_sync_terminated(app_session.server_app_id);
154✔
521
    });
154✔
522
    app_session.admin_api.enable_sync(app_session.server_app_id, baas_sync_service.id, baas_sync_config);
4✔
523
    REQUIRE(app_session.admin_api.is_sync_enabled(app_session.server_app_id));
4!
524
    if (app_session.config.dev_mode_enabled) { // dev mode is not sticky across a reset
4✔
UNCOV
525
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, true);
×
UNCOV
526
    }
×
527

2✔
528
    // In FLX sync, the server won't let you connect until the initial sync is complete. With PBS tho, we need
2✔
529
    // to make sure we've actually copied all the data from atlas into the realm history before we do any of
2✔
530
    // our remote changes.
2✔
531
    if (!app_session.config.flx_sync_config) {
4✔
532
        timed_sleeping_wait_for([&] {
517✔
533
            return app_session.admin_api.is_initial_sync_complete(app_session.server_app_id);
517✔
534
        });
517✔
535
    }
2✔
536
}
4✔
537

538
void trigger_client_reset(const AppSession& app_session, const SharedRealm& realm)
539
{
134✔
540
    auto file_ident = SyncSession::OnlyForTesting::get_file_ident(*realm->sync_session());
134✔
541
    REQUIRE(file_ident.ident != 0);
134!
542
    app_session.admin_api.trigger_client_reset(app_session.server_app_id, file_ident.ident);
134✔
543
}
134✔
544

545
struct BaasClientReset : public TestClientReset {
546
    BaasClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
547
                    TestAppSession& test_app_session)
548
        : TestClientReset(local_config, remote_config)
549
        , m_test_app_session(test_app_session)
550
    {
110✔
551
    }
110✔
552

553
    TestClientReset* set_development_mode(bool enable) override
554
    {
14✔
555
        const AppSession& app_session = m_test_app_session.app_session();
14✔
556
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, enable);
14✔
557
        return this;
14✔
558
    }
14✔
559

560
    void run() override
561
    {
86✔
562
        m_did_run = true;
86✔
563
        const AppSession& app_session = m_test_app_session.app_session();
86✔
564
        auto sync_manager = m_test_app_session.app()->sync_manager();
86✔
565
        std::string partition_value = m_local_config.sync_config->partition_value;
86✔
566
        REALM_ASSERT(partition_value.size() > 2 && *partition_value.begin() == '"' &&
86✔
567
                     *(partition_value.end() - 1) == '"');
86✔
568
        partition_value = partition_value.substr(1, partition_value.size() - 2);
86✔
569
        Partition partition = {app_session.config.partition_key.name, partition_value};
86✔
570

43✔
571
        // There is a race in PBS where if initial sync is still in-progress while you're creating the initial
43✔
572
        // object below, you may end up creating it in your local realm, uploading it, have the translator process
43✔
573
        // the upload, then initial sync the processed object, and then send it back to you as an erase/create
43✔
574
        // object instruction.
43✔
575
        //
43✔
576
        // So just don't try to do anything until initial sync is done and we're sure the server is in a stable
43✔
577
        // state.
43✔
578
        timed_sleeping_wait_for([&] {
10,424✔
579
            return app_session.admin_api.is_initial_sync_complete(app_session.server_app_id);
10,424✔
580
        });
10,424✔
581

43✔
582
        auto realm = Realm::get_shared_realm(m_local_config);
86✔
583
        auto session = sync_manager->get_existing_session(realm->config().path);
86✔
584
        const std::string object_schema_name = "object";
86✔
585
        {
86✔
586
            wait_for_download(*realm);
86✔
587
            realm->begin_transaction();
86✔
588

43✔
589
            if (m_on_setup) {
86✔
590
                m_on_setup(realm);
16✔
591
            }
16✔
592

43✔
593
            auto obj = create_object(*realm, object_schema_name, {m_pk_driving_reset}, {partition});
86✔
594
            auto table = obj.get_table();
86✔
595
            auto col = table->get_column_key("value");
86✔
596
            std::string pk_col_name = table->get_column_name(table->get_primary_key_column());
86✔
597
            obj.set(col, 1);
86✔
598
            obj.set(col, 2);
86✔
599
            constexpr int64_t last_synced_value = 3;
86✔
600
            obj.set(col, last_synced_value);
86✔
601
            realm->commit_transaction();
86✔
602
            wait_for_upload(*realm);
86✔
603
            wait_for_download(*realm);
86✔
604

43✔
605
            session->pause();
86✔
606

43✔
607
            realm->begin_transaction();
86✔
608
            obj.set(col, 4);
86✔
609
            if (m_make_local_changes) {
86✔
610
                m_make_local_changes(realm);
42✔
611
            }
42✔
612
            realm->commit_transaction();
86✔
613
        }
86✔
614

43✔
615
        trigger_client_reset(app_session, realm);
86✔
616

43✔
617
        {
86✔
618
            auto realm2 = Realm::get_shared_realm(m_remote_config);
86✔
619
            wait_for_download(*realm2);
86✔
620

43✔
621
            timed_sleeping_wait_for(
86✔
622
                [&]() -> bool {
86✔
623
                    realm2->begin_transaction();
86✔
624
                    auto table = get_table(*realm2, object_schema_name);
86✔
625
                    auto objkey = table->find_primary_key({m_pk_driving_reset});
86✔
626
                    realm2->cancel_transaction();
86✔
627
                    return bool(objkey);
86✔
628
                },
86✔
629
                std::chrono::seconds(60));
86✔
630

43✔
631
            // expect the last sync'd object to be in place
43✔
632
            realm2->begin_transaction();
86✔
633
            auto table = get_table(*realm2, object_schema_name);
86✔
634
            REQUIRE(table->size() >= 1);
86!
635
            auto obj = table->get_object_with_primary_key({m_pk_driving_reset});
86✔
636
            REQUIRE(obj.is_valid());
86!
637
            auto col = table->get_column_key("value");
86✔
638
            REQUIRE(obj.get_any(col) == Mixed{3});
86!
639

43✔
640
            // make a change
43✔
641
            table->begin()->set(col, 6);
86✔
642
            realm2->commit_transaction();
86✔
643
            wait_for_upload(*realm2);
86✔
644
            wait_for_download(*realm2);
86✔
645

43✔
646
            realm2->begin_transaction();
86✔
647
            if (m_make_remote_changes) {
86✔
648
                m_make_remote_changes(realm2);
22✔
649
            }
22✔
650
            realm2->commit_transaction();
86✔
651
            wait_for_upload(*realm2);
86✔
652
            wait_for_download(*realm2);
86✔
653
            realm2->close();
86✔
654
        }
86✔
655

43✔
656
        // Resuming sync on the first realm should now result in a client reset
43✔
657
        session->resume();
86✔
658
        if (m_on_post_local) {
86✔
659
            m_on_post_local(realm);
32✔
660
        }
32✔
661
        if (!m_wait_for_reset_completion) {
86✔
662
            return;
4✔
663
        }
4✔
664
        wait_for_upload(*realm);
82✔
665
        if (m_on_post_reset) {
82✔
666
            m_on_post_reset(realm);
62✔
667
        }
62✔
668
    }
82✔
669

670
private:
671
    TestAppSession& m_test_app_session;
672
};
673

674
struct BaasFLXClientReset : public TestClientReset {
675
    BaasFLXClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
676
                       const TestAppSession& test_app_session)
677
        : TestClientReset(local_config, remote_config)
678
        , m_test_app_session(test_app_session)
679
    {
24✔
680
        REALM_ASSERT(m_local_config.sync_config->flx_sync_requested);
24✔
681
        REALM_ASSERT(m_remote_config.sync_config->flx_sync_requested);
24✔
682
        REALM_ASSERT(m_local_config.schema->find(c_object_schema_name) != m_local_config.schema->end());
24✔
683
    }
24✔
684

685
    TestClientReset* set_development_mode(bool enable) override
NEW
686
    {
×
NEW
687
        const AppSession& app_session = m_test_app_session.app_session();
×
NEW
688
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, enable);
×
NEW
689
        return this;
×
NEW
690
    }
×
691

692
    void run() override
693
    {
24✔
694
        m_did_run = true;
24✔
695
        const AppSession& app_session = m_test_app_session.app_session();
24✔
696

12✔
697
        auto realm = Realm::get_shared_realm(m_local_config);
24✔
698
        auto session = realm->sync_session();
24✔
699
        if (m_on_setup) {
24✔
700
            m_on_setup(realm);
2✔
701
        }
2✔
702

12✔
703
        ObjectId pk_of_added_object = [&] {
24✔
704
            if (m_populate_initial_object) {
24✔
705
                return m_populate_initial_object(realm);
2✔
706
            }
2✔
707

11✔
708
            auto ret = ObjectId::gen();
22✔
709
            constexpr bool create_object = true;
22✔
710
            subscribe_to_object_by_id(realm, ret, create_object);
22✔
711
            return ret;
22✔
712
        }();
22✔
713

12✔
714
        session->pause();
24✔
715

12✔
716
        if (m_make_local_changes) {
24✔
717
            m_make_local_changes(realm);
20✔
718
        }
20✔
719

12✔
720
        trigger_client_reset(app_session, realm);
24✔
721

12✔
722
        {
24✔
723
            auto realm2 = Realm::get_shared_realm(m_remote_config);
24✔
724
            wait_for_download(*realm2);
24✔
725
            load_initial_data(realm2);
24✔
726

12✔
727
            timed_sleeping_wait_for(
24✔
728
                [&]() -> bool {
24✔
729
                    realm2->begin_transaction();
24✔
730
                    auto table = get_table(*realm2, c_object_schema_name);
24✔
731
                    auto objkey = table->find_primary_key({pk_of_added_object});
24✔
732
                    realm2->cancel_transaction();
24✔
733
                    return bool(objkey);
24✔
734
                },
24✔
735
                std::chrono::seconds(60));
24✔
736

12✔
737
            // expect the last sync'd object to be in place
12✔
738
            realm2->begin_transaction();
24✔
739
            auto table = get_table(*realm2, c_object_schema_name);
24✔
740
            REQUIRE(table->size() >= 1);
24!
741
            auto obj = table->get_object_with_primary_key({pk_of_added_object});
24✔
742
            REQUIRE(obj.is_valid());
24!
743
            realm2->commit_transaction();
24✔
744

12✔
745
            if (m_make_remote_changes) {
24✔
746
                m_make_remote_changes(realm2);
12✔
747
            }
12✔
748
            wait_for_upload(*realm2);
24✔
749
            auto subs = realm2->get_latest_subscription_set();
24✔
750
            subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
24✔
751
            realm2->close();
24✔
752
        }
24✔
753

12✔
754
        // Resuming sync on the first realm should now result in a client reset
12✔
755
        session->resume();
24✔
756
        if (m_on_post_local) {
24✔
757
            m_on_post_local(realm);
2✔
758
        }
2✔
759
        wait_for_upload(*realm);
24✔
760
        if (m_on_post_reset) {
24✔
761
            m_on_post_reset(realm);
20✔
762
        }
20✔
763
    }
24✔
764

765
private:
766
    void subscribe_to_object_by_id(SharedRealm realm, ObjectId pk, bool create_object = false)
767
    {
22✔
768
        auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
22✔
769
        Group::TableNameBuffer buffer;
22✔
770
        auto class_name = Group::class_name_to_table_name(c_object_schema_name, buffer);
22✔
771
        TableRef table = realm->read_group().get_table(class_name);
22✔
772
        REALM_ASSERT(table);
22✔
773
        ColKey id_col = table->get_column_key(c_id_col_name);
22✔
774
        REALM_ASSERT(id_col);
22✔
775
        ColKey str_col = table->get_column_key(c_str_col_name);
22✔
776
        REALM_ASSERT(str_col);
22✔
777
        Query query_for_added_object = table->where().equal(id_col, pk);
22✔
778
        mut_subs.insert_or_assign(query_for_added_object);
22✔
779
        auto subs = std::move(mut_subs).commit();
22✔
780
        subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
22✔
781
        if (create_object) {
22✔
782
            realm->begin_transaction();
22✔
783
            table->create_object_with_primary_key(pk, {{str_col, "initial value"}});
22✔
784
            realm->commit_transaction();
22✔
785
        }
22✔
786
        wait_for_upload(*realm);
22✔
787
    }
22✔
788

789
    void load_initial_data(SharedRealm realm)
790
    {
24✔
791
        auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
24✔
792
        for (const auto& table : realm->schema()) {
48✔
793
            Query query_for_table(realm->read_group().get_table(table.table_key));
48✔
794
            mut_subs.insert_or_assign(query_for_table);
48✔
795
        }
48✔
796
        auto subs = std::move(mut_subs).commit();
24✔
797
        subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
24✔
798
    }
24✔
799

800
    const TestAppSession& m_test_app_session;
801
    constexpr static std::string_view c_object_schema_name = "TopLevel";
802
    constexpr static std::string_view c_id_col_name = "_id";
803
    constexpr static std::string_view c_str_col_name = "queryable_str_field";
804
};
805

806
std::unique_ptr<TestClientReset> make_baas_client_reset(const Realm::Config& local_config,
807
                                                        const Realm::Config& remote_config,
808
                                                        TestAppSession& test_app_session)
809
{
110✔
810
    return std::make_unique<BaasClientReset>(local_config, remote_config, test_app_session);
110✔
811
}
110✔
812

813
std::unique_ptr<TestClientReset> make_baas_flx_client_reset(const Realm::Config& local_config,
814
                                                            const Realm::Config& remote_config,
815
                                                            const TestAppSession& session)
816
{
24✔
817
    return std::make_unique<BaasFLXClientReset>(local_config, remote_config, session);
24✔
818
}
24✔
819

820
#endif // REALM_ENABLE_AUTH_TESTS
821

822
#endif // REALM_ENABLE_SYNC
823

824

825
TestClientReset::TestClientReset(const Realm::Config& local_config, const Realm::Config& remote_config)
826
    : m_local_config(local_config)
827
    , m_remote_config(remote_config)
828
{
3,366✔
829
}
3,366✔
830
TestClientReset::~TestClientReset()
831
{
3,366✔
832
    // make sure we didn't forget to call run()
1,683✔
833
    REALM_ASSERT(m_did_run || !(m_make_local_changes || m_make_remote_changes || m_on_post_local || m_on_post_reset));
3,366✔
834
}
3,366✔
835

836
TestClientReset* TestClientReset::setup(Callback&& on_setup)
837
{
3,378✔
838
    m_on_setup = std::move(on_setup);
3,378✔
839
    return this;
3,378✔
840
}
3,378✔
841
TestClientReset* TestClientReset::make_local_changes(Callback&& changes_local)
842
{
3,282✔
843
    m_make_local_changes = std::move(changes_local);
3,282✔
844
    return this;
3,282✔
845
}
3,282✔
846
TestClientReset* TestClientReset::populate_initial_object(InitialObjectCallback&& callback)
847
{
2✔
848
    m_populate_initial_object = std::move(callback);
2✔
849
    return this;
2✔
850
}
2✔
851

852
TestClientReset* TestClientReset::make_remote_changes(Callback&& changes_remote)
853
{
3,250✔
854
    m_make_remote_changes = std::move(changes_remote);
3,250✔
855
    return this;
3,250✔
856
}
3,250✔
857
TestClientReset* TestClientReset::on_post_local_changes(Callback&& post_local)
858
{
3,030✔
859
    m_on_post_local = std::move(post_local);
3,030✔
860
    return this;
3,030✔
861
}
3,030✔
862
TestClientReset* TestClientReset::on_post_reset(Callback&& post_reset)
863
{
3,308✔
864
    m_on_post_reset = std::move(post_reset);
3,308✔
865
    return this;
3,308✔
866
}
3,308✔
867
TestClientReset* TestClientReset::set_development_mode(bool)
NEW
868
{
×
NEW
869
    return this;
×
NEW
870
}
×
871

872
void TestClientReset::set_pk_of_object_driving_reset(const ObjectId& pk)
873
{
2✔
874
    m_pk_driving_reset = pk;
2✔
875
}
2✔
876

877
ObjectId TestClientReset::get_pk_of_object_driving_reset() const
878
{
2✔
879
    return m_pk_driving_reset;
2✔
880
}
2✔
881

882
void TestClientReset::disable_wait_for_reset_completion()
883
{
4✔
884
    m_wait_for_reset_completion = false;
4✔
885
}
4✔
886

887
std::unique_ptr<TestClientReset> make_fake_local_client_reset(const Realm::Config& local_config,
888
                                                              const Realm::Config& remote_config)
889
{
3,232✔
890
    return std::make_unique<FakeLocalClientReset>(local_config, remote_config);
3,232✔
891
}
3,232✔
892

893
} // namespace reset_utils
894

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

© 2026 Coveralls, Inc