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

realm / realm-core / 2214

10 Apr 2024 11:21PM UTC coverage: 91.813% (-0.8%) from 92.623%
2214

push

Evergreen

web-flow
Add missing availability checks for SecCopyErrorMessageString (#7577)

This requires iOS 11.3 and we currently target iOS 11.

94848 of 175770 branches covered (53.96%)

7 of 22 new or added lines in 2 files covered. (31.82%)

1815 existing lines in 77 files now uncovered.

242945 of 264608 relevant lines covered (91.81%)

6136478.37 hits per line

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

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

2✔
72
    return predicate_returned_true;
4✔
73
}
4✔
74

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

87
void timed_sleeping_wait_for(util::FunctionRef<bool()> condition, std::chrono::milliseconds max_ms,
88
                             std::chrono::milliseconds sleep_ms)
89
{
775✔
90
    const auto wait_start = std::chrono::steady_clock::now();
775✔
91
    const auto delay = TEST_TIMEOUT_EXTRA > 0 ? max_ms + std::chrono::seconds(TEST_TIMEOUT_EXTRA) : max_ms;
775✔
92
    while (!condition()) {
11,328✔
93
        if (std::chrono::steady_clock::now() - wait_start > delay) {
10,553✔
UNCOV
94
            throw std::runtime_error(util::format("timed_sleeping_wait_for exceeded %1 ms", delay.count()));
×
UNCOV
95
        }
×
96
        std::this_thread::sleep_for(sleep_ms);
10,553✔
97
    }
10,553✔
98
}
775✔
99

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

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

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

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

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

174
#if REALM_ENABLE_SYNC
175

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

193
#if REALM_ENABLE_AUTH_TESTS
194

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

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

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

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

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

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

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

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

27✔
277
    bool done = false;
54✔
278
    realm.m_binding_context = std::make_unique<Context>(realm, done);
54✔
279
    timed_wait_for([&] {
297✔
280
        return done;
297✔
281
    });
297✔
282
    realm.m_binding_context = nullptr;
54✔
283
}
54✔
284

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

307
class TestHelper {
308
public:
309
    static DBRef& get_db(SharedRealm const& shared_realm)
310
    {
311
        return Realm::Internal::get_db(*shared_realm);
312
    }
313
};
314

315
namespace reset_utils {
316

317
Obj create_object(Realm& realm, StringData object_type, util::Optional<ObjectId> primary_key,
318
                  util::Optional<Partition> partition)
319
{
7,302✔
320
    auto table = realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
7,302✔
321
    REQUIRE(table);
7,302!
322
    FieldValues values = {};
7,302✔
323
    if (partition) {
7,302✔
324
        ColKey col = table->get_column_key(partition->property_name);
190✔
325
        REALM_ASSERT(col);
190✔
326
        values.insert(col, Mixed{partition->value});
190✔
327
    }
190✔
328
    return table->create_object_with_primary_key(primary_key ? *primary_key : ObjectId::gen(), std::move(values));
7,301✔
329
}
7,302✔
330

331
namespace {
332

333
TableRef get_table(Realm& realm, StringData object_type)
334
{
7,352✔
335
    return realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
7,352✔
336
}
7,352✔
337

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

358
    void run() override
359
    {
3,556✔
360
        m_did_run = true;
3,556✔
361
        auto local_realm = Realm::get_shared_realm(m_local_config);
3,556✔
362
        if (m_on_setup) {
3,556✔
363
            local_realm->begin_transaction();
3,516✔
364
            m_on_setup(local_realm);
3,516✔
365
            local_realm->commit_transaction();
3,516✔
366

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

1,778✔
390
            local_realm->begin_transaction();
3,556✔
391
            obj.set(col, 4);
3,556✔
392
            if (m_make_local_changes) {
3,556✔
393
                m_make_local_changes(local_realm);
3,544✔
394
            }
3,544✔
395
            local_realm->commit_transaction();
3,556✔
396
            if (m_on_post_local) {
3,556✔
397
                m_on_post_local(local_realm);
3,260✔
398
            }
3,260✔
399
        }
3,556✔
400

1,778✔
401
        {
3,556✔
402
            auto remote_realm = Realm::get_shared_realm(m_remote_config);
3,556✔
403
            remote_realm->begin_transaction();
3,556✔
404
            if (m_on_setup) {
3,556✔
405
                m_on_setup(remote_realm);
3,516✔
406
            }
3,516✔
407

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

1,778✔
411
            for (int i = 0; i < 2; ++i) {
10,668✔
412
                auto table = get_table(*remote_realm, "object");
7,112✔
413
                auto col = table->get_column_key("value");
7,112✔
414
                table->begin()->set(col, i + 5);
7,112✔
415
            }
7,112✔
416

1,778✔
417
            if (m_make_remote_changes) {
3,556✔
418
                m_make_remote_changes(remote_realm);
3,544✔
419
            }
3,544✔
420
            remote_realm->commit_transaction();
3,556✔
421

1,778✔
422
            sync::SaltedFileIdent fake_ident{1, 123456789};
3,556✔
423
            auto local_db = TestHelper::get_db(local_realm);
3,556✔
424
            auto remote_db = TestHelper::get_db(remote_realm);
3,556✔
425
            auto logger = util::Logger::get_default_logger();
3,556✔
426

1,778✔
427
            using _impl::client_reset::perform_client_reset_diff;
3,556✔
428
            constexpr bool recovery_is_allowed = true;
3,556✔
429
            perform_client_reset_diff(*local_db, *remote_db, fake_ident, *logger, m_mode, recovery_is_allowed,
3,556✔
430
                                      nullptr, [](int64_t) {});
3,554✔
431

1,778✔
432
            remote_realm->close();
3,556✔
433
            if (m_on_post_reset) {
3,556✔
434
                m_on_post_reset(local_realm);
3,552✔
435
            }
3,552✔
436
        }
3,556✔
437
    }
3,556✔
438

439
private:
440
    ClientResyncMode m_mode;
441
};
442
} // anonymous namespace
443

444
#if REALM_ENABLE_SYNC
445

446
#if REALM_ENABLE_AUTH_TESTS
447

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

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

479
void wait_for_num_objects_in_atlas(std::shared_ptr<app::User> user, const AppSession& app_session,
480
                                   const std::string& schema_name, size_t expected_size)
481
{
2✔
482
    app::MongoClient remote_client = user->mongo_client("BackingDB");
2✔
483
    app::MongoDatabase db = remote_client.db(app_session.config.mongo_dbname);
2✔
484
    app::MongoCollection object_coll = db[schema_name];
2✔
485

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

505
void trigger_client_reset(const AppSession& app_session, const SyncSession& sync_session)
506
{
154✔
507
    auto file_ident = sync_session.get_file_ident();
154✔
508
    REQUIRE(file_ident.ident != 0);
154!
509
    app_session.admin_api.trigger_client_reset(app_session.server_app_id, file_ident.ident);
154✔
510
}
154✔
511

512
void trigger_client_reset(const AppSession& app_session, const SharedRealm& realm)
513
{
146✔
514
    trigger_client_reset(app_session, *realm->sync_session());
146✔
515
}
146✔
516

517
struct BaasClientReset : public TestClientReset {
518
    BaasClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
519
                    TestAppSession& test_app_session)
520
        : TestClientReset(local_config, remote_config)
521
        , m_test_app_session(test_app_session)
522
    {
118✔
523
    }
118✔
524

525
    TestClientReset* set_development_mode(bool enable) override
526
    {
14✔
527
        const AppSession& app_session = m_test_app_session.app_session();
14✔
528
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, enable);
14✔
529
        return this;
14✔
530
    }
14✔
531

532
    void run() override
533
    {
94✔
534
        m_did_run = true;
94✔
535
        const AppSession& app_session = m_test_app_session.app_session();
94✔
536
        auto sync_manager = m_test_app_session.sync_manager();
94✔
537
        std::string partition_value = m_local_config.sync_config->partition_value;
94✔
538
        REALM_ASSERT(partition_value.size() > 2 && *partition_value.begin() == '"' &&
94✔
539
                     *(partition_value.end() - 1) == '"');
94✔
540
        partition_value = partition_value.substr(1, partition_value.size() - 2);
94✔
541
        Partition partition = {app_session.config.partition_key.name, partition_value};
94✔
542

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

47✔
556
        auto realm = Realm::get_shared_realm(m_local_config);
94✔
557
        auto session = sync_manager->get_existing_session(realm->config().path);
94✔
558
        const std::string object_schema_name = "object";
94✔
559
        {
94✔
560
            wait_for_download(*realm);
94✔
561
            realm->begin_transaction();
94✔
562

47✔
563
            if (m_on_setup) {
94✔
564
                m_on_setup(realm);
18✔
565
            }
18✔
566

47✔
567
            auto obj = create_object(*realm, object_schema_name, {m_pk_driving_reset}, {partition});
94✔
568
            auto table = obj.get_table();
94✔
569
            auto col = table->get_column_key("value");
94✔
570
            std::string pk_col_name = table->get_column_name(table->get_primary_key_column());
94✔
571
            obj.set(col, 1);
94✔
572
            obj.set(col, 2);
94✔
573
            constexpr int64_t last_synced_value = 3;
94✔
574
            obj.set(col, last_synced_value);
94✔
575
            realm->commit_transaction();
94✔
576
            wait_for_upload(*realm);
94✔
577
            wait_for_download(*realm);
94✔
578

47✔
579
            session->pause();
94✔
580

47✔
581
            realm->begin_transaction();
94✔
582
            obj.set(col, 4);
94✔
583
            if (m_make_local_changes) {
94✔
584
                m_make_local_changes(realm);
46✔
585
            }
46✔
586
            realm->commit_transaction();
94✔
587
        }
94✔
588

47✔
589
        trigger_client_reset(app_session, realm);
94✔
590

47✔
591
        {
94✔
592
            auto realm2 = Realm::get_shared_realm(m_remote_config);
94✔
593
            wait_for_download(*realm2);
94✔
594

47✔
595
            timed_sleeping_wait_for(
94✔
596
                [&]() -> bool {
94✔
597
                    realm2->begin_transaction();
94✔
598
                    auto table = get_table(*realm2, object_schema_name);
94✔
599
                    auto objkey = table->find_primary_key({m_pk_driving_reset});
94✔
600
                    realm2->cancel_transaction();
94✔
601
                    return bool(objkey);
94✔
602
                },
94✔
603
                std::chrono::seconds(60));
94✔
604

47✔
605
            // expect the last sync'd object to be in place
47✔
606
            realm2->begin_transaction();
94✔
607
            auto table = get_table(*realm2, object_schema_name);
94✔
608
            REQUIRE(table->size() >= 1);
94!
609
            auto obj = table->get_object_with_primary_key({m_pk_driving_reset});
94✔
610
            REQUIRE(obj.is_valid());
94!
611
            auto col = table->get_column_key("value");
94✔
612
            REQUIRE(obj.get_any(col) == Mixed{3});
94!
613

47✔
614
            // make a change
47✔
615
            table->begin()->set(col, 6);
94✔
616
            realm2->commit_transaction();
94✔
617
            wait_for_upload(*realm2);
94✔
618
            wait_for_download(*realm2);
94✔
619

47✔
620
            realm2->begin_transaction();
94✔
621
            if (m_make_remote_changes) {
94✔
622
                m_make_remote_changes(realm2);
24✔
623
            }
24✔
624
            realm2->commit_transaction();
94✔
625
            wait_for_upload(*realm2);
94✔
626
            wait_for_download(*realm2);
94✔
627
            realm2->close();
94✔
628
        }
94✔
629

47✔
630
        // Resuming sync on the first realm should now result in a client reset
47✔
631
        session->resume();
94✔
632
        if (m_on_post_local) {
94✔
633
            m_on_post_local(realm);
32✔
634
        }
32✔
635
        if (!m_wait_for_reset_completion) {
94✔
636
            return;
4✔
637
        }
4✔
638
        wait_for_upload(*realm);
90✔
639
        if (m_on_post_reset) {
90✔
640
            m_on_post_reset(realm);
68✔
641
        }
68✔
642
    }
90✔
643

644
private:
645
    TestAppSession& m_test_app_session;
646
};
647

648
struct BaasFLXClientReset : public TestClientReset {
649
    BaasFLXClientReset(const Realm::Config& local_config, const Realm::Config& remote_config,
650
                       const TestAppSession& test_app_session)
651
        : TestClientReset(local_config, remote_config)
652
        , m_test_app_session(test_app_session)
653
    {
26✔
654
        REALM_ASSERT(m_local_config.sync_config->flx_sync_requested);
26✔
655
        REALM_ASSERT(m_remote_config.sync_config->flx_sync_requested);
26✔
656
        REALM_ASSERT(m_local_config.schema->find(c_object_schema_name) != m_local_config.schema->end());
26✔
657
    }
26✔
658

659
    TestClientReset* set_development_mode(bool enable) override
UNCOV
660
    {
×
UNCOV
661
        const AppSession& app_session = m_test_app_session.app_session();
×
UNCOV
662
        app_session.admin_api.set_development_mode_to(app_session.server_app_id, enable);
×
UNCOV
663
        return this;
×
UNCOV
664
    }
×
665

666
    void run() override
667
    {
26✔
668
        m_did_run = true;
26✔
669
        const AppSession& app_session = m_test_app_session.app_session();
26✔
670

13✔
671
        auto realm = Realm::get_shared_realm(m_local_config);
26✔
672
        auto session = realm->sync_session();
26✔
673
        if (m_on_setup) {
26✔
674
            m_on_setup(realm);
2✔
675
        }
2✔
676

13✔
677
        ObjectId pk_of_added_object = [&] {
26✔
678
            if (m_populate_initial_object) {
26✔
679
                return m_populate_initial_object(realm);
2✔
680
            }
2✔
681

12✔
682
            auto ret = ObjectId::gen();
24✔
683
            constexpr bool create_object = true;
24✔
684
            subscribe_to_object_by_id(realm, ret, create_object);
24✔
685
            return ret;
24✔
686
        }();
24✔
687

13✔
688
        session->pause();
26✔
689

13✔
690
        if (m_make_local_changes) {
26✔
691
            m_make_local_changes(realm);
22✔
692
        }
22✔
693

13✔
694
        trigger_client_reset(app_session, realm);
26✔
695

13✔
696
        {
26✔
697
            auto realm2 = Realm::get_shared_realm(m_remote_config);
26✔
698
            wait_for_download(*realm2);
26✔
699
            load_initial_data(realm2);
26✔
700

13✔
701
            timed_sleeping_wait_for(
26✔
702
                [&]() -> bool {
26✔
703
                    realm2->begin_transaction();
26✔
704
                    auto table = get_table(*realm2, c_object_schema_name);
26✔
705
                    auto objkey = table->find_primary_key({pk_of_added_object});
26✔
706
                    realm2->cancel_transaction();
26✔
707
                    return bool(objkey);
26✔
708
                },
26✔
709
                std::chrono::seconds(60));
26✔
710

13✔
711
            // expect the last sync'd object to be in place
13✔
712
            realm2->begin_transaction();
26✔
713
            auto table = get_table(*realm2, c_object_schema_name);
26✔
714
            REQUIRE(table->size() >= 1);
26!
715
            auto obj = table->get_object_with_primary_key({pk_of_added_object});
26✔
716
            REQUIRE(obj.is_valid());
26!
717
            realm2->commit_transaction();
26✔
718

13✔
719
            if (m_make_remote_changes) {
26✔
720
                m_make_remote_changes(realm2);
14✔
721
            }
14✔
722
            wait_for_upload(*realm2);
26✔
723
            auto subs = realm2->get_latest_subscription_set();
26✔
724
            subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
26✔
725
            realm2->close();
26✔
726
        }
26✔
727

13✔
728
        // Resuming sync on the first realm should now result in a client reset
13✔
729
        session->resume();
26✔
730
        if (m_on_post_local) {
26✔
731
            m_on_post_local(realm);
6✔
732
        }
6✔
733
        wait_for_upload(*realm);
26✔
734
        if (m_on_post_reset) {
26✔
735
            m_on_post_reset(realm);
22✔
736
        }
22✔
737
    }
26✔
738

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

763
    void load_initial_data(SharedRealm realm)
764
    {
26✔
765
        auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
26✔
766
        for (const auto& table : realm->schema()) {
52✔
767
            Query query_for_table(realm->read_group().get_table(table.table_key));
52✔
768
            mut_subs.insert_or_assign(query_for_table);
52✔
769
        }
52✔
770
        auto subs = std::move(mut_subs).commit();
26✔
771
        subs.get_state_change_notification(sync::SubscriptionSet::State::Complete).get();
26✔
772
    }
26✔
773

774
    const TestAppSession& m_test_app_session;
775
    constexpr static std::string_view c_object_schema_name = "TopLevel";
776
    constexpr static std::string_view c_id_col_name = "_id";
777
    constexpr static std::string_view c_str_col_name = "queryable_str_field";
778
};
779

780
std::unique_ptr<TestClientReset> make_baas_client_reset(const Realm::Config& local_config,
781
                                                        const Realm::Config& remote_config,
782
                                                        TestAppSession& test_app_session)
783
{
118✔
784
    return std::make_unique<BaasClientReset>(local_config, remote_config, test_app_session);
118✔
785
}
118✔
786

787
std::unique_ptr<TestClientReset> make_baas_flx_client_reset(const Realm::Config& local_config,
788
                                                            const Realm::Config& remote_config,
789
                                                            const TestAppSession& session)
790
{
26✔
791
    return std::make_unique<BaasFLXClientReset>(local_config, remote_config, session);
26✔
792
}
26✔
793

794
#endif // REALM_ENABLE_AUTH_TESTS
795

796
#endif // REALM_ENABLE_SYNC
797

798

799
TestClientReset::TestClientReset(const Realm::Config& local_config, const Realm::Config& remote_config)
800
    : m_local_config(local_config)
801
    , m_remote_config(remote_config)
802
{
3,708✔
803
}
3,708✔
804
TestClientReset::~TestClientReset()
805
{
3,708✔
806
    // make sure we didn't forget to call run()
1,854✔
807
    REALM_ASSERT(m_did_run || !(m_make_local_changes || m_make_remote_changes || m_on_post_local || m_on_post_reset));
3,708✔
808
}
3,708✔
809

810
TestClientReset* TestClientReset::setup(Callback&& on_setup)
811
{
3,828✔
812
    m_on_setup = std::move(on_setup);
3,828✔
813
    return this;
3,828✔
814
}
3,828✔
815
TestClientReset* TestClientReset::make_local_changes(Callback&& changes_local)
816
{
3,612✔
817
    m_make_local_changes = std::move(changes_local);
3,612✔
818
    return this;
3,612✔
819
}
3,612✔
820
TestClientReset* TestClientReset::populate_initial_object(InitialObjectCallback&& callback)
821
{
2✔
822
    m_populate_initial_object = std::move(callback);
2✔
823
    return this;
2✔
824
}
2✔
825

826
TestClientReset* TestClientReset::make_remote_changes(Callback&& changes_remote)
827
{
3,582✔
828
    m_make_remote_changes = std::move(changes_remote);
3,582✔
829
    return this;
3,582✔
830
}
3,582✔
831
TestClientReset* TestClientReset::on_post_local_changes(Callback&& post_local)
832
{
3,298✔
833
    m_on_post_local = std::move(post_local);
3,298✔
834
    return this;
3,298✔
835
}
3,298✔
836
TestClientReset* TestClientReset::on_post_reset(Callback&& post_reset)
837
{
3,646✔
838
    m_on_post_reset = std::move(post_reset);
3,646✔
839
    return this;
3,646✔
840
}
3,646✔
841
TestClientReset* TestClientReset::set_development_mode(bool)
UNCOV
842
{
×
UNCOV
843
    return this;
×
UNCOV
844
}
×
845

846
void TestClientReset::set_pk_of_object_driving_reset(const ObjectId& pk)
847
{
2✔
848
    m_pk_driving_reset = pk;
2✔
849
}
2✔
850

851
ObjectId TestClientReset::get_pk_of_object_driving_reset() const
852
{
2✔
853
    return m_pk_driving_reset;
2✔
854
}
2✔
855

856
void TestClientReset::disable_wait_for_reset_completion()
857
{
4✔
858
    m_wait_for_reset_completion = false;
4✔
859
}
4✔
860

861
std::unique_ptr<TestClientReset> make_fake_local_client_reset(const Realm::Config& local_config,
862
                                                              const Realm::Config& remote_config)
863
{
3,564✔
864
    return std::make_unique<FakeLocalClientReset>(local_config, remote_config);
3,564✔
865
}
3,564✔
866

867
} // namespace reset_utils
868

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