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

realm / realm-core / 2123

12 Mar 2024 11:18PM UTC coverage: 91.833% (+0.05%) from 91.787%
2123

push

Evergreen

web-flow
RCORE-1928 Use baasaas for baas integration tests in CI (#7423)

94732 of 174812 branches covered (54.19%)

201 of 288 new or added lines in 4 files covered. (69.79%)

62 existing lines in 11 files now uncovered.

242847 of 264443 relevant lines covered (91.83%)

5989046.65 hits per line

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

90.06
/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([&] {
8✔
82
        if (std::chrono::steady_clock::now() - wait_start > delay) {
8✔
83
            util::format("ReturnsTrueWithinTimeLimit exceeded %1 ms", delay.count());
×
84
            return true;
×
85
        }
×
86
        auto ret = condition();
8✔
87
        if (ret) {
8✔
88
            predicate_returned_true = true;
6✔
89
        }
6✔
90
        return ret;
8✔
91
    });
8✔
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
{
110✔
98
    const auto wait_start = std::chrono::steady_clock::now();
110✔
99
    const auto delay = TEST_TIMEOUT_EXTRA > 0 ? max_ms + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : max_ms;
110✔
100
    util::EventLoop::main().run_until([&] {
26,127,021✔
101
        if (std::chrono::steady_clock::now() - wait_start > delay) {
26,127,021✔
102
            throw std::runtime_error(util::format("timed_wait_for exceeded %1 ms", delay.count()));
×
103
        }
×
104
        return condition();
26,127,021✔
105
    });
26,127,021✔
106
}
110✔
107

108
void timed_sleeping_wait_for(util::FunctionRef<bool()> condition, std::chrono::milliseconds max_ms,
109
                             std::chrono::milliseconds sleep_ms)
110
{
763✔
111
    const auto wait_start = std::chrono::steady_clock::now();
763✔
112
    const auto delay = TEST_TIMEOUT_EXTRA > 0 ? max_ms + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : max_ms;
763✔
113
    while (!condition()) {
11,300✔
114
        if (std::chrono::steady_clock::now() - wait_start > delay) {
10,537✔
115
            throw std::runtime_error(util::format("timed_sleeping_wait_for exceeded %1 ms", delay.count()));
×
116
        }
×
117
        std::this_thread::sleep_for(sleep_ms);
10,537✔
118
    }
10,537✔
119
}
763✔
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
std::string unquote_string(std::string_view possibly_quoted_string)
NEW
179
{
×
NEW
180
    if (possibly_quoted_string.size() > 0) {
×
NEW
181
        auto check_char = possibly_quoted_string.front();
×
NEW
182
        if (check_char == '"' || check_char == '\'') {
×
NEW
183
            possibly_quoted_string.remove_prefix(1);
×
NEW
184
        }
×
NEW
185
    }
×
NEW
186
    if (possibly_quoted_string.size() > 0) {
×
NEW
187
        auto check_char = possibly_quoted_string.back();
×
NEW
188
        if (check_char == '"' || check_char == '\'') {
×
NEW
189
            possibly_quoted_string.remove_suffix(1);
×
NEW
190
        }
×
NEW
191
    }
×
NEW
192
    return std::string{possibly_quoted_string};
×
NEW
193
}
×
194

195
#if REALM_ENABLE_SYNC
196

197
void subscribe_to_all_and_bootstrap(Realm& realm)
198
{
70✔
199
    auto mut_subs = realm.get_latest_subscription_set().make_mutable_copy();
70✔
200
    auto& group = realm.read_group();
70✔
201
    for (auto key : group.get_table_keys()) {
600✔
202
        if (group.table_is_public(key)) {
600✔
203
            auto table = group.get_table(key);
110✔
204
            if (table->get_table_type() == Table::Type::TopLevel) {
110✔
205
                mut_subs.insert_or_assign(table->where());
106✔
206
            }
106✔
207
        }
110✔
208
    }
600✔
209
    auto subs = std::move(mut_subs).commit();
70✔
210
    subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
70✔
211
    wait_for_download(realm);
70✔
212
}
70✔
213

214
#if REALM_ENABLE_AUTH_TESTS
215

216
void wait_for_sessions_to_close(const TestAppSession& test_app_session)
217
{
4✔
218
    timed_sleeping_wait_for(
4✔
219
        [&]() -> bool {
4✔
220
            return !test_app_session.sync_manager()->has_existing_sessions();
4✔
221
        },
4✔
222
        std::chrono::minutes(5), std::chrono::milliseconds(100));
4✔
223
}
4✔
224

225
std::string get_compile_time_base_url()
UNCOV
226
{
×
227
#ifdef REALM_MONGODB_ENDPOINT
228
    // allows configuration with or without quotes
229
    return unquote_string(REALM_QUOTE(REALM_MONGODB_ENDPOINT));
230
#else
NEW
231
    return {};
×
NEW
232
#endif
×
UNCOV
233
}
×
234

235
std::string get_compile_time_admin_url()
236
{
355✔
237
#ifdef REALM_ADMIN_ENDPOINT
238
    // allows configuration with or without quotes
239
    return unquote_string(REALM_QUOTE(REALM_ADMIN_ENDPOINT));
240
#else
241
    return {};
355✔
242
#endif
355✔
243
}
355✔
244
#endif // REALM_MONGODB_ENDPOINT
245
#endif // REALM_ENABLE_AUTH_TESTS
246

247
AutoVerifiedEmailCredentials::AutoVerifiedEmailCredentials()
248
{
1,108✔
249
    // emails with this prefix will pass through the baas app due to the register function
543✔
250
    email = util::format("realm_tests_do_autoverify%1@%2.com", random_string(10), random_string(10));
1,108✔
251
    password = random_string(10);
1,108✔
252
    static_cast<AppCredentials&>(*this) = AppCredentials::username_password(email, password);
1,108✔
253
}
1,108✔
254

255
AutoVerifiedEmailCredentials create_user_and_log_in(app::SharedApp app)
256
{
988✔
257
    REQUIRE(app);
988!
258
    AutoVerifiedEmailCredentials creds;
988✔
259
    app->provider_client<app::App::UsernamePasswordProviderClient>().register_email(
988✔
260
        creds.email, creds.password, [&](util::Optional<app::AppError> error) {
988✔
261
            REQUIRE(!error);
988!
262
        });
988✔
263
    app->log_in_with_credentials(realm::app::AppCredentials::username_password(creds.email, creds.password),
988✔
264
                                 [&](std::shared_ptr<realm::SyncUser> user, util::Optional<app::AppError> error) {
988✔
265
                                     REQUIRE(user);
988!
266
                                     REQUIRE(!error);
988!
267
                                 });
988✔
268
    return creds;
988✔
269
}
988✔
270

271
void wait_for_advance(Realm& realm)
272
{
54✔
273
    struct Context : BindingContext {
54✔
274
        Realm& realm;
54✔
275
        DB::version_type target_version;
54✔
276
        bool& done;
54✔
277
        Context(Realm& realm, bool& done)
54✔
278
            : realm(realm)
54✔
279
            , target_version(*realm.latest_snapshot_version())
54✔
280
            , done(done)
54✔
281
        {
54✔
282
        }
54✔
283

27✔
284
        void did_change(std::vector<ObserverState> const&, std::vector<void*> const&, bool) override
54✔
285
        {
54✔
286
            if (realm.read_transaction_version().version >= target_version) {
54✔
287
                done = true;
54✔
288
            }
54✔
289
        }
54✔
290
    };
54✔
291

27✔
292
    bool done = false;
54✔
293
    realm.m_binding_context = std::make_unique<Context>(realm, done);
54✔
294
    timed_wait_for([&] {
297✔
295
        return done;
297✔
296
    });
297✔
297
    realm.m_binding_context = nullptr;
54✔
298
}
54✔
299

300
void async_open_realm(const Realm::Config& config,
301
                      util::UniqueFunction<void(ThreadSafeReference&& ref, std::exception_ptr e)> finish)
302
{
22✔
303
    std::mutex mutex;
22✔
304
    bool did_finish = false;
22✔
305
    auto task = Realm::get_synchronized_realm(config);
22✔
306
    ThreadSafeReference tsr;
22✔
307
    std::exception_ptr err = nullptr;
22✔
308
    task->start([&](ThreadSafeReference&& ref, std::exception_ptr e) {
22✔
309
        std::lock_guard lock(mutex);
22✔
310
        did_finish = true;
22✔
311
        tsr = std::move(ref);
22✔
312
        err = e;
22✔
313
    });
22✔
314
    util::EventLoop::main().run_until([&] {
7,820,098✔
315
        std::lock_guard lock(mutex);
7,820,098✔
316
        return did_finish;
7,820,098✔
317
    });
7,820,098✔
318
    task->cancel(); // don't run the above notifier again on this session
22✔
319
    finish(std::move(tsr), err);
22✔
320
}
22✔
321

322
class TestHelper {
323
public:
324
    static DBRef& get_db(SharedRealm const& shared_realm)
325
    {
326
        return Realm::Internal::get_db(*shared_realm);
327
    }
328
};
329

330
namespace reset_utils {
331

332
Obj create_object(Realm& realm, StringData object_type, util::Optional<ObjectId> primary_key,
333
                  util::Optional<Partition> partition)
334
{
7,302✔
335
    auto table = realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
7,302✔
336
    REQUIRE(table);
7,302!
337
    FieldValues values = {};
7,302✔
338
    if (partition) {
7,302✔
339
        ColKey col = table->get_column_key(partition->property_name);
190✔
340
        REALM_ASSERT(col);
190✔
341
        values.insert(col, Mixed{partition->value});
190✔
342
    }
190✔
343
    return table->create_object_with_primary_key(primary_key ? *primary_key : ObjectId::gen(), std::move(values));
7,301✔
344
}
7,302✔
345

346
namespace {
347

348
TableRef get_table(Realm& realm, StringData object_type)
349
{
7,352✔
350
    return realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
7,352✔
351
}
7,352✔
352

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

373
    void run() override
374
    {
3,556✔
375
        m_did_run = true;
3,556✔
376
        auto local_realm = Realm::get_shared_realm(m_local_config);
3,556✔
377
        if (m_on_setup) {
3,556✔
378
            local_realm->begin_transaction();
3,516✔
379
            m_on_setup(local_realm);
3,516✔
380
            local_realm->commit_transaction();
3,516✔
381

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

1,778✔
405
            local_realm->begin_transaction();
3,556✔
406
            obj.set(col, 4);
3,556✔
407
            if (m_make_local_changes) {
3,556✔
408
                m_make_local_changes(local_realm);
3,544✔
409
            }
3,544✔
410
            local_realm->commit_transaction();
3,556✔
411
            if (m_on_post_local) {
3,556✔
412
                m_on_post_local(local_realm);
3,260✔
413
            }
3,260✔
414
        }
3,556✔
415

1,778✔
416
        {
3,556✔
417
            auto remote_realm = Realm::get_shared_realm(m_remote_config);
3,556✔
418
            remote_realm->begin_transaction();
3,556✔
419
            if (m_on_setup) {
3,556✔
420
                m_on_setup(remote_realm);
3,516✔
421
            }
3,516✔
422

1,778✔
423
            // fake a sync by creating an object with the same pk
1,778✔
424
            create_object(*remote_realm, "object", m_pk_driving_reset);
3,556✔
425

1,778✔
426
            for (int i = 0; i < 2; ++i) {
10,668✔
427
                auto table = get_table(*remote_realm, "object");
7,112✔
428
                auto col = table->get_column_key("value");
7,112✔
429
                table->begin()->set(col, i + 5);
7,112✔
430
            }
7,112✔
431

1,778✔
432
            if (m_make_remote_changes) {
3,556✔
433
                m_make_remote_changes(remote_realm);
3,544✔
434
            }
3,544✔
435
            remote_realm->commit_transaction();
3,556✔
436

1,778✔
437
            sync::SaltedFileIdent fake_ident{1, 123456789};
3,556✔
438
            auto local_db = TestHelper::get_db(local_realm);
3,556✔
439
            auto remote_db = TestHelper::get_db(remote_realm);
3,556✔
440
            auto logger = util::Logger::get_default_logger();
3,556✔
441

1,778✔
442
            using _impl::client_reset::perform_client_reset_diff;
3,556✔
443
            constexpr bool recovery_is_allowed = true;
3,556✔
444
            perform_client_reset_diff(*local_db, *remote_db, fake_ident, *logger, m_mode, recovery_is_allowed,
3,556✔
445
                                      nullptr, [](int64_t) {});
3,554✔
446

1,778✔
447
            remote_realm->close();
3,556✔
448
            if (m_on_post_reset) {
3,556✔
449
                m_on_post_reset(local_realm);
3,552✔
450
            }
3,552✔
451
        }
3,556✔
452
    }
3,556✔
453

454
private:
455
    ClientResyncMode m_mode;
456
};
457
} // anonymous namespace
458

459
#if REALM_ENABLE_SYNC
460

461
#if REALM_ENABLE_AUTH_TESTS
462

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

2✔
476
    timed_sleeping_wait_for(
4✔
477
        [&]() -> bool {
5✔
478
            auto pf = util::make_promise_future<uint64_t>();
5✔
479
            object_coll.count(filter_bson, [promise = std::move(pf.promise)](
5✔
480
                                               uint64_t count, util::Optional<app::AppError> error) mutable {
5✔
481
                REQUIRE(!error);
5!
482
                if (error) {
5✔
483
                    promise.set_error({ErrorCodes::RuntimeError, error->reason()});
×
484
                }
×
485
                else {
5✔
486
                    promise.emplace_value(count);
5✔
487
                }
5✔
488
            });
5✔
489
            return pf.future.get() > 0;
5✔
490
        },
5✔
491
        std::chrono::minutes(15), std::chrono::milliseconds(500));
4✔
492
}
4✔
493

494
void wait_for_num_objects_in_atlas(std::shared_ptr<SyncUser> user, const AppSession& app_session,
495
                                   const std::string& schema_name, size_t expected_size)
496
{
2✔
497
    app::MongoClient remote_client = user->mongo_client("BackingDB");
2✔
498
    app::MongoDatabase db = remote_client.db(app_session.config.mongo_dbname);
2✔
499
    app::MongoCollection object_coll = db[schema_name];
2✔
500

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

520
void trigger_client_reset(const AppSession& app_session, const SyncSession& sync_session)
521
{
152✔
522
    auto file_ident = sync_session.get_file_ident();
152✔
523
    REQUIRE(file_ident.ident != 0);
152!
524
    app_session.admin_api.trigger_client_reset(app_session.server_app_id, file_ident.ident);
152✔
525
}
152✔
526

527
void trigger_client_reset(const AppSession& app_session, const SharedRealm& realm)
528
{
146✔
529
    trigger_client_reset(app_session, *realm->sync_session());
146✔
530
}
146✔
531

532
struct BaasClientReset : public TestClientReset {
533
    BaasClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
534
                    TestAppSession& test_app_session)
535
        : TestClientReset(local_config, remote_config)
536
        , m_test_app_session(test_app_session)
537
    {
118✔
538
    }
118✔
539

540
    TestClientReset* set_development_mode(bool enable) override
541
    {
14✔
542
        const AppSession& app_session = m_test_app_session.app_session();
14✔
543
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, enable);
14✔
544
        return this;
14✔
545
    }
14✔
546

547
    void run() override
548
    {
94✔
549
        m_did_run = true;
94✔
550
        const AppSession& app_session = m_test_app_session.app_session();
94✔
551
        auto sync_manager = m_test_app_session.sync_manager();
94✔
552
        std::string partition_value = m_local_config.sync_config->partition_value;
94✔
553
        REALM_ASSERT(partition_value.size() > 2 && *partition_value.begin() == '"' &&
94✔
554
                     *(partition_value.end() - 1) == '"');
94✔
555
        partition_value = partition_value.substr(1, partition_value.size() - 2);
94✔
556
        Partition partition = {app_session.config.partition_key.name, partition_value};
94✔
557

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

47✔
571
        auto realm = Realm::get_shared_realm(m_local_config);
94✔
572
        auto session = sync_manager->get_existing_session(realm->config().path);
94✔
573
        const std::string object_schema_name = "object";
94✔
574
        {
94✔
575
            wait_for_download(*realm);
94✔
576
            realm->begin_transaction();
94✔
577

47✔
578
            if (m_on_setup) {
94✔
579
                m_on_setup(realm);
18✔
580
            }
18✔
581

47✔
582
            auto obj = create_object(*realm, object_schema_name, {m_pk_driving_reset}, {partition});
94✔
583
            auto table = obj.get_table();
94✔
584
            auto col = table->get_column_key("value");
94✔
585
            std::string pk_col_name = table->get_column_name(table->get_primary_key_column());
94✔
586
            obj.set(col, 1);
94✔
587
            obj.set(col, 2);
94✔
588
            constexpr int64_t last_synced_value = 3;
94✔
589
            obj.set(col, last_synced_value);
94✔
590
            realm->commit_transaction();
94✔
591
            wait_for_upload(*realm);
94✔
592
            wait_for_download(*realm);
94✔
593

47✔
594
            session->pause();
94✔
595

47✔
596
            realm->begin_transaction();
94✔
597
            obj.set(col, 4);
94✔
598
            if (m_make_local_changes) {
94✔
599
                m_make_local_changes(realm);
46✔
600
            }
46✔
601
            realm->commit_transaction();
94✔
602
        }
94✔
603

47✔
604
        trigger_client_reset(app_session, realm);
94✔
605

47✔
606
        {
94✔
607
            auto realm2 = Realm::get_shared_realm(m_remote_config);
94✔
608
            wait_for_download(*realm2);
94✔
609

47✔
610
            timed_sleeping_wait_for(
94✔
611
                [&]() -> bool {
94✔
612
                    realm2->begin_transaction();
94✔
613
                    auto table = get_table(*realm2, object_schema_name);
94✔
614
                    auto objkey = table->find_primary_key({m_pk_driving_reset});
94✔
615
                    realm2->cancel_transaction();
94✔
616
                    return bool(objkey);
94✔
617
                },
94✔
618
                std::chrono::seconds(60));
94✔
619

47✔
620
            // expect the last sync'd object to be in place
47✔
621
            realm2->begin_transaction();
94✔
622
            auto table = get_table(*realm2, object_schema_name);
94✔
623
            REQUIRE(table->size() >= 1);
94!
624
            auto obj = table->get_object_with_primary_key({m_pk_driving_reset});
94✔
625
            REQUIRE(obj.is_valid());
94!
626
            auto col = table->get_column_key("value");
94✔
627
            REQUIRE(obj.get_any(col) == Mixed{3});
94!
628

47✔
629
            // make a change
47✔
630
            table->begin()->set(col, 6);
94✔
631
            realm2->commit_transaction();
94✔
632
            wait_for_upload(*realm2);
94✔
633
            wait_for_download(*realm2);
94✔
634

47✔
635
            realm2->begin_transaction();
94✔
636
            if (m_make_remote_changes) {
94✔
637
                m_make_remote_changes(realm2);
24✔
638
            }
24✔
639
            realm2->commit_transaction();
94✔
640
            wait_for_upload(*realm2);
94✔
641
            wait_for_download(*realm2);
94✔
642
            realm2->close();
94✔
643
        }
94✔
644

47✔
645
        // Resuming sync on the first realm should now result in a client reset
47✔
646
        session->resume();
94✔
647
        if (m_on_post_local) {
94✔
648
            m_on_post_local(realm);
32✔
649
        }
32✔
650
        if (!m_wait_for_reset_completion) {
94✔
651
            return;
4✔
652
        }
4✔
653
        wait_for_upload(*realm);
90✔
654
        if (m_on_post_reset) {
90✔
655
            m_on_post_reset(realm);
68✔
656
        }
68✔
657
    }
90✔
658

659
private:
660
    TestAppSession& m_test_app_session;
661
};
662

663
struct BaasFLXClientReset : public TestClientReset {
664
    BaasFLXClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
665
                       const TestAppSession& test_app_session)
666
        : TestClientReset(local_config, remote_config)
667
        , m_test_app_session(test_app_session)
668
    {
26✔
669
        REALM_ASSERT(m_local_config.sync_config->flx_sync_requested);
26✔
670
        REALM_ASSERT(m_remote_config.sync_config->flx_sync_requested);
26✔
671
        REALM_ASSERT(m_local_config.schema->find(c_object_schema_name) != m_local_config.schema->end());
26✔
672
    }
26✔
673

674
    TestClientReset* set_development_mode(bool enable) override
675
    {
×
676
        const AppSession& app_session = m_test_app_session.app_session();
×
677
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, enable);
×
678
        return this;
×
679
    }
×
680

681
    void run() override
682
    {
26✔
683
        m_did_run = true;
26✔
684
        const AppSession& app_session = m_test_app_session.app_session();
26✔
685

13✔
686
        auto realm = Realm::get_shared_realm(m_local_config);
26✔
687
        auto session = realm->sync_session();
26✔
688
        if (m_on_setup) {
26✔
689
            m_on_setup(realm);
2✔
690
        }
2✔
691

13✔
692
        ObjectId pk_of_added_object = [&] {
26✔
693
            if (m_populate_initial_object) {
26✔
694
                return m_populate_initial_object(realm);
2✔
695
            }
2✔
696

12✔
697
            auto ret = ObjectId::gen();
24✔
698
            constexpr bool create_object = true;
24✔
699
            subscribe_to_object_by_id(realm, ret, create_object);
24✔
700
            return ret;
24✔
701
        }();
24✔
702

13✔
703
        session->pause();
26✔
704

13✔
705
        if (m_make_local_changes) {
26✔
706
            m_make_local_changes(realm);
22✔
707
        }
22✔
708

13✔
709
        trigger_client_reset(app_session, realm);
26✔
710

13✔
711
        {
26✔
712
            auto realm2 = Realm::get_shared_realm(m_remote_config);
26✔
713
            wait_for_download(*realm2);
26✔
714
            load_initial_data(realm2);
26✔
715

13✔
716
            timed_sleeping_wait_for(
26✔
717
                [&]() -> bool {
26✔
718
                    realm2->begin_transaction();
26✔
719
                    auto table = get_table(*realm2, c_object_schema_name);
26✔
720
                    auto objkey = table->find_primary_key({pk_of_added_object});
26✔
721
                    realm2->cancel_transaction();
26✔
722
                    return bool(objkey);
26✔
723
                },
26✔
724
                std::chrono::seconds(60));
26✔
725

13✔
726
            // expect the last sync'd object to be in place
13✔
727
            realm2->begin_transaction();
26✔
728
            auto table = get_table(*realm2, c_object_schema_name);
26✔
729
            REQUIRE(table->size() >= 1);
26!
730
            auto obj = table->get_object_with_primary_key({pk_of_added_object});
26✔
731
            REQUIRE(obj.is_valid());
26!
732
            realm2->commit_transaction();
26✔
733

13✔
734
            if (m_make_remote_changes) {
26✔
735
                m_make_remote_changes(realm2);
14✔
736
            }
14✔
737
            wait_for_upload(*realm2);
26✔
738
            auto subs = realm2->get_latest_subscription_set();
26✔
739
            subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
26✔
740
            realm2->close();
26✔
741
        }
26✔
742

13✔
743
        // Resuming sync on the first realm should now result in a client reset
13✔
744
        session->resume();
26✔
745
        if (m_on_post_local) {
26✔
746
            m_on_post_local(realm);
6✔
747
        }
6✔
748
        wait_for_upload(*realm);
26✔
749
        if (m_on_post_reset) {
26✔
750
            m_on_post_reset(realm);
22✔
751
        }
22✔
752
    }
26✔
753

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

778
    void load_initial_data(SharedRealm realm)
779
    {
26✔
780
        auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
26✔
781
        for (const auto& table : realm->schema()) {
52✔
782
            Query query_for_table(realm->read_group().get_table(table.table_key));
52✔
783
            mut_subs.insert_or_assign(query_for_table);
52✔
784
        }
52✔
785
        auto subs = std::move(mut_subs).commit();
26✔
786
        subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
26✔
787
    }
26✔
788

789
    const TestAppSession& m_test_app_session;
790
    constexpr static std::string_view c_object_schema_name = "TopLevel";
791
    constexpr static std::string_view c_id_col_name = "_id";
792
    constexpr static std::string_view c_str_col_name = "queryable_str_field";
793
};
794

795
std::unique_ptr<TestClientReset> make_baas_client_reset(const Realm::Config& local_config,
796
                                                        const Realm::Config& remote_config,
797
                                                        TestAppSession& test_app_session)
798
{
118✔
799
    return std::make_unique<BaasClientReset>(local_config, remote_config, test_app_session);
118✔
800
}
118✔
801

802
std::unique_ptr<TestClientReset> make_baas_flx_client_reset(const Realm::Config& local_config,
803
                                                            const Realm::Config& remote_config,
804
                                                            const TestAppSession& session)
805
{
26✔
806
    return std::make_unique<BaasFLXClientReset>(local_config, remote_config, session);
26✔
807
}
26✔
808

809
#endif // REALM_ENABLE_AUTH_TESTS
810

811
#endif // REALM_ENABLE_SYNC
812

813

814
TestClientReset::TestClientReset(const Realm::Config& local_config, const Realm::Config& remote_config)
815
    : m_local_config(local_config)
816
    , m_remote_config(remote_config)
817
{
3,708✔
818
}
3,708✔
819
TestClientReset::~TestClientReset()
820
{
3,708✔
821
    // make sure we didn't forget to call run()
1,854✔
822
    REALM_ASSERT(m_did_run || !(m_make_local_changes || m_make_remote_changes || m_on_post_local || m_on_post_reset));
3,708✔
823
}
3,708✔
824

825
TestClientReset* TestClientReset::setup(Callback&& on_setup)
826
{
3,828✔
827
    m_on_setup = std::move(on_setup);
3,828✔
828
    return this;
3,828✔
829
}
3,828✔
830
TestClientReset* TestClientReset::make_local_changes(Callback&& changes_local)
831
{
3,612✔
832
    m_make_local_changes = std::move(changes_local);
3,612✔
833
    return this;
3,612✔
834
}
3,612✔
835
TestClientReset* TestClientReset::populate_initial_object(InitialObjectCallback&& callback)
836
{
2✔
837
    m_populate_initial_object = std::move(callback);
2✔
838
    return this;
2✔
839
}
2✔
840

841
TestClientReset* TestClientReset::make_remote_changes(Callback&& changes_remote)
842
{
3,582✔
843
    m_make_remote_changes = std::move(changes_remote);
3,582✔
844
    return this;
3,582✔
845
}
3,582✔
846
TestClientReset* TestClientReset::on_post_local_changes(Callback&& post_local)
847
{
3,298✔
848
    m_on_post_local = std::move(post_local);
3,298✔
849
    return this;
3,298✔
850
}
3,298✔
851
TestClientReset* TestClientReset::on_post_reset(Callback&& post_reset)
852
{
3,646✔
853
    m_on_post_reset = std::move(post_reset);
3,646✔
854
    return this;
3,646✔
855
}
3,646✔
856
TestClientReset* TestClientReset::set_development_mode(bool)
857
{
×
858
    return this;
×
859
}
×
860

861
void TestClientReset::set_pk_of_object_driving_reset(const ObjectId& pk)
862
{
2✔
863
    m_pk_driving_reset = pk;
2✔
864
}
2✔
865

866
ObjectId TestClientReset::get_pk_of_object_driving_reset() const
867
{
2✔
868
    return m_pk_driving_reset;
2✔
869
}
2✔
870

871
void TestClientReset::disable_wait_for_reset_completion()
872
{
4✔
873
    m_wait_for_reset_completion = false;
4✔
874
}
4✔
875

876
std::unique_ptr<TestClientReset> make_fake_local_client_reset(const Realm::Config& local_config,
877
                                                              const Realm::Config& remote_config)
878
{
3,564✔
879
    return std::make_unique<FakeLocalClientReset>(local_config, remote_config);
3,564✔
880
}
3,564✔
881

882
} // namespace reset_utils
883

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