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

realm / realm-core / thomas.goyne_440

02 Jul 2024 07:51PM UTC coverage: 91.007% (+0.03%) from 90.974%
thomas.goyne_440

push

Evergreen

web-flow
[RCORE-2146] CAPI Remove `is_fatal` flag flip (#7751)

102408 of 180620 branches covered (56.7%)

0 of 1 new or added line in 1 file covered. (0.0%)

619 existing lines in 26 files now uncovered.

215623 of 236930 relevant lines covered (91.01%)

5563737.46 hits per line

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

89.05
/test/object-store/util/sync/sync_test_utils.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2016 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
#include <util/sync/sync_test_utils.hpp>
20

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

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

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

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

41
#include <chrono>
42

43
namespace realm {
44

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

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

75
    return predicate_returned_true;
4✔
76
}
4✔
77

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

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

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

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

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

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

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

177
#if REALM_ENABLE_SYNC
178

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

196
#if REALM_ENABLE_AUTH_TESTS
197

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

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

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

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

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

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

259
#endif // REALM_APP_SERVICES
260

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

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

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

424✔
290
StatusWith<std::shared_ptr<Realm>> async_open_realm(const Realm::Config& config)
424✔
291
{
80✔
292
    auto task = Realm::get_synchronized_realm(config);
80✔
293
    auto pf = util::make_promise_future<ThreadSafeReference>();
294
    task->start([&](ThreadSafeReference&& ref, std::exception_ptr e) {
295
        if (e) {
114✔
296
            try {
114✔
297
                std::rethrow_exception(e);
114✔
298
            }
114✔
299
            catch (...) {
114✔
300
                pf.promise.set_error(exception_to_status());
42✔
301
            }
42✔
302
        }
42✔
303
        else {
42✔
304
            pf.promise.emplace_value(std::move(ref));
42✔
305
        }
42✔
306
    });
42✔
307
    auto sw = std::move(pf.future).get_no_throw();
72✔
308
    if (sw.is_ok())
72✔
309
        return Realm::get_shared_realm(std::move(sw.get_value()));
72✔
310
    return sw.get_status();
114✔
311
}
114✔
312

114✔
313
std::shared_ptr<Realm> successfully_async_open_realm(const Realm::Config& config)
72✔
314
{
42✔
315
    auto status = async_open_realm(config);
114✔
316
    REQUIRE(status.is_ok());
317
    return status.get_value();
318
}
52✔
319

52✔
320
#endif // REALM_ENABLE_SYNC
52!
321

52✔
322
class TestHelper {
52✔
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
{
335
    auto table = realm::ObjectStore::table_for_object_type(realm.read_group(), object_type);
336
    REQUIRE(table);
337
    FieldValues values = {};
338
    if (partition) {
7,498✔
339
        ColKey col = table->get_column_key(partition->property_name);
7,498✔
340
        REALM_ASSERT(col);
7,498!
341
        values.insert(col, Mixed{partition->value});
7,498✔
342
    }
7,498✔
343
    return table->create_object_with_primary_key(primary_key ? *primary_key : ObjectId::gen(), std::move(values));
194✔
344
}
194✔
345

194✔
346
namespace {
194✔
347

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

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

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

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

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

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

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

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

7,304✔
432
            if (m_make_remote_changes) {
7,304✔
433
                m_make_remote_changes(remote_realm);
7,304✔
434
            }
7,304✔
435
            remote_realm->commit_transaction();
436

3,652✔
437
            sync::SaltedFileIdent fake_ident{1, 123456789};
3,624✔
438
            auto local_db = TestHelper::get_db(local_realm);
3,624✔
439
            auto logger = util::Logger::get_default_logger();
3,652✔
440
            sync::ClientReset reset_config{m_mode,
441
                                           TestHelper::get_db(remote_realm),
3,652✔
442
                                           {ErrorCodes::SyncClientResetRequired, "Bad client file ident"}};
3,652✔
443

3,652✔
444
            using _impl::client_reset::perform_client_reset_diff;
3,652✔
445
            perform_client_reset_diff(*local_db, reset_config, fake_ident, *logger, nullptr, [](int64_t) {});
3,652✔
446

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

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

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

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

2✔
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>();
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);
3✔
508
                if (error) {
3✔
509
                    promise.set_error({ErrorCodes::RuntimeError, error->reason()});
3✔
510
                }
3✔
511
                else {
3!
512
                    promise.emplace_value(count);
3✔
UNCOV
513
                }
×
UNCOV
514
            });
×
515
            return pf.future.get() >= expected_size;
3✔
516
        },
3✔
517
        std::chrono::minutes(15), std::chrono::milliseconds(500));
3✔
518
}
3✔
519

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

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

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

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

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

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

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

96✔
578
            if (m_on_setup) {
96✔
579
                m_on_setup(realm);
96✔
580
            }
96✔
581

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

96✔
594
            session->pause();
96✔
595

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

48✔
604
        trigger_client_reset(app_session, realm);
48✔
605

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

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

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

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

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

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

92✔
659
private:
70✔
660
    TestAppSession& m_test_app_session;
70✔
661
};
92✔
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
    {
669
        REALM_ASSERT(m_local_config.sync_config->flx_sync_requested);
670
        REALM_ASSERT(m_remote_config.sync_config->flx_sync_requested);
14✔
671
        REALM_ASSERT(m_local_config.schema->find(c_object_schema_name) != m_local_config.schema->end());
14✔
672
    }
28✔
673

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

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

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

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

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

24✔
703
        session->pause();
24✔
704

24✔
705
        if (m_make_local_changes) {
28✔
706
            m_make_local_changes(realm);
707
        }
28✔
708

709
        trigger_client_reset(app_session, realm);
28✔
710

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

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

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

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

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

28✔
754
private:
24✔
755
    void subscribe_to_object_by_id(SharedRealm realm, ObjectId pk, bool create_object = false)
24✔
756
    {
28✔
757
        auto mut_subs = realm->get_latest_subscription_set().make_mutable_copy();
758
        Group::TableNameBuffer buffer;
759
        auto class_name = Group::class_name_to_table_name(c_object_schema_name, buffer);
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

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

56✔
789
    const TestAppSession& m_test_app_session;
28✔
790
    constexpr static std::string_view c_object_schema_name = "TopLevel";
28✔
791
    constexpr static std::string_view c_id_col_name = "_id";
28✔
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
{
799
    return std::make_unique<BaasClientReset>(local_config, remote_config, test_app_session);
800
}
801

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

809
#endif // REALM_ENABLE_AUTH_TESTS
28✔
810

28✔
811
#endif // REALM_ENABLE_SYNC
28✔
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
{
818
}
819
TestClientReset::~TestClientReset()
1,904✔
820
{
1,904✔
821
    // make sure we didn't forget to call run()
3,808✔
822
    REALM_ASSERT(m_did_run || !(m_make_local_changes || m_make_remote_changes || m_on_post_local || m_on_post_reset));
3,808✔
823
}
824

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

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

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

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

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

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

882
} // namespace reset_utils
3,660✔
883

3,660✔
884
} // namespace realm
3,660✔
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