From 280ce605d19d104e675cd51569929bd5a9bf214d Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 14 Feb 2024 15:19:46 -0800 Subject: [PATCH] Separate TestSyncManager and OfflineAppSession Co-authored-by: James Stone --- CHANGELOG.md | 2 +- src/realm/object-store/audit.mm | 3 +- src/realm/object-store/sync/app.cpp | 3 +- src/realm/object-store/sync/sync_manager.cpp | 82 ++- src/realm/object-store/sync/sync_manager.hpp | 44 +- test/object-store/audit.cpp | 30 +- test/object-store/benchmarks/client_reset.cpp | 5 +- test/object-store/benchmarks/object.cpp | 1 - test/object-store/c_api/c_api.cpp | 33 +- test/object-store/dictionary.cpp | 5 - test/object-store/frozen_objects.cpp | 3 - test/object-store/list.cpp | 5 +- test/object-store/migrations.cpp | 1 - test/object-store/object.cpp | 4 +- test/object-store/primitive_list.cpp | 2 - test/object-store/realm.cpp | 64 +-- test/object-store/results.cpp | 13 +- test/object-store/set.cpp | 1 - test/object-store/sync/app.cpp | 509 +++++++----------- test/object-store/sync/client_reset.cpp | 25 +- test/object-store/sync/flx_sync.cpp | 4 - test/object-store/sync/session/session.cpp | 2 - .../sync/session/wait_for_completion.cpp | 1 - test/object-store/sync/sync_manager.cpp | 61 +-- test/object-store/sync/user.cpp | 6 +- test/object-store/thread_safe_reference.cpp | 4 +- .../util/sync/sync_test_utils.hpp | 8 - test/object-store/util/test_file.cpp | 94 +++- test/object-store/util/test_file.hpp | 167 +++--- test/object-store/util/test_utils.hpp | 19 +- .../object-store/util/unit_test_transport.hpp | 5 + 31 files changed, 540 insertions(+), 666 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e16bc182ee..0a2eea95227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ * Fix a minor race condition when backing up Realm files before a client reset which could have lead to overwriting an existing file. ([PR #7341](https://github.com/realm/realm-core/pull/7341)). ### Breaking changes -* None. +* SyncManager no longer supports reconfiguring after calling reset_for_testing(). SyncManager::configure() has been folded into the constructor, and reset_for_testing() has been renamed to tear_down_for_testing(). ([PR #7351](https://github.com/realm/realm-core/pull/7351)) ### Compatibility * Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5. diff --git a/src/realm/object-store/audit.mm b/src/realm/object-store/audit.mm index f1828a33437..d7c6921ee5b 100644 --- a/src/realm/object-store/audit.mm +++ b/src/realm/object-store/audit.mm @@ -683,7 +683,7 @@ explicit AuditRealmPool(Private, std::shared_ptr user, std::string con }), s_pools.end()); - auto app_id = user->sync_manager()->app().lock()->config().app_id; + auto app_id = user->sync_manager()->app_id(); auto it = std::find_if(s_pools.begin(), s_pools.end(), [&](auto& pool) { return pool.user_identity == user->identity() && pool.partition_prefix == partition_prefix && pool.app_id == app_id; @@ -889,7 +889,6 @@ explicit AuditRealmPool(Private, std::shared_ptr user, std::string con Realm::Config config; config.automatic_change_notifications = false; - config.cache = false; config.path = util::format("%1%2.realm", m_path_root, partition); config.scheduler = util::Scheduler::make_dummy(); config.schema = Schema{schema}; diff --git a/src/realm/object-store/sync/app.cpp b/src/realm/object-store/sync/app.cpp index 60a8745f023..6878f0f94d2 100644 --- a/src/realm/object-store/sync/app.cpp +++ b/src/realm/object-store/sync/app.cpp @@ -313,8 +313,7 @@ void App::configure(const SyncClientConfig& sync_client_config) // Start with an empty sync route in the sync manager. It will ensure the // location has been updated at least once when the first sync session is // started by requesting a new access token. - m_sync_manager = std::make_shared(); - m_sync_manager->configure(shared_from_this(), util::none, sync_client_config); + m_sync_manager = SyncManager::create(shared_from_this(), {}, sync_client_config, config().app_id); } bool App::init_logger() diff --git a/src/realm/object-store/sync/sync_manager.cpp b/src/realm/object-store/sync/sync_manager.cpp index e53eb4888a9..24713d85c42 100644 --- a/src/realm/object-store/sync/sync_manager.cpp +++ b/src/realm/object-store/sync/sync_manager.cpp @@ -43,58 +43,37 @@ SyncClientTimeouts::SyncClientTimeouts() { } -SyncManager::SyncManager() = default; - -void SyncManager::configure(std::shared_ptr app, std::optional sync_route, - const SyncClientConfig& config) +std::shared_ptr SyncManager::create(std::shared_ptr app, std::optional sync_route, + const SyncClientConfig& config, const std::string& app_id) { - std::vector> users_to_add; - { - // Locking the mutex here ensures that it is released before locking m_user_mutex - util::CheckedLockGuard lock(m_mutex); - m_app = app; - m_sync_route = sync_route; - m_config = std::move(config); - if (m_sync_client) - return; - - // create a new logger - if the logger_factory is updated later, a new - // logger will be created at that time. - do_make_logger(); - - { - util::CheckedLockGuard lock(m_file_system_mutex); + return std::make_shared(Private(), std::move(app), std::move(sync_route), config, app_id); +} - // Set up the file manager. - if (m_file_manager) { - // Changing the base path for tests requires calling reset_for_testing() - // first, and otherwise isn't supported - REALM_ASSERT(m_file_manager->base_path() == m_config.base_file_path); - } - else { - m_file_manager = std::make_unique(m_config.base_file_path, app->config().app_id); - } +SyncManager::SyncManager(Private, std::shared_ptr app, std::optional sync_route, + const SyncClientConfig& config, const std::string& app_id) + : m_config(config) + , m_file_manager(std::make_unique(m_config.base_file_path, app_id)) + , m_sync_route(sync_route) + , m_app(app) + , m_app_id(app_id) +{ + // create the initial logger - if the logger_factory is updated later, a new + // logger will be created at that time. + do_make_logger(); - // Set up the metadata manager, and perform initial loading/purging work. - if (m_metadata_manager || m_config.metadata_mode == MetadataMode::NoMetadata) { - return; - } + if (m_config.metadata_mode == MetadataMode::NoMetadata) { + return; + } - bool encrypt = m_config.metadata_mode == MetadataMode::Encryption; - m_metadata_manager = std::make_unique(m_file_manager->metadata_path(), encrypt, - m_config.custom_encryption_key); + bool encrypt = m_config.metadata_mode == MetadataMode::Encryption; + m_metadata_manager = std::make_unique(m_file_manager->metadata_path(), encrypt, + m_config.custom_encryption_key); - m_metadata_manager->perform_launch_actions(*m_file_manager); + m_metadata_manager->perform_launch_actions(*m_file_manager); - // Load persisted users into the users map. - for (auto user : m_metadata_manager->all_logged_in_users()) { - users_to_add.push_back(std::make_shared(SyncUser::Private(), user, this)); - } - } - } - { - util::CheckedLockGuard lock(m_user_mutex); - m_users.insert(m_users.end(), users_to_add.begin(), users_to_add.end()); + // Load persisted users into the users map. + for (auto user : m_metadata_manager->all_logged_in_users()) { + m_users.push_back(std::make_shared(SyncUser::Private(), user, this)); } } @@ -107,8 +86,10 @@ bool SyncManager::immediately_run_file_actions(const std::string& realm_path) return false; } -void SyncManager::reset_for_testing() +void SyncManager::tear_down_for_testing() { + close_all_sessions(); + { util::CheckedLockGuard lock(m_file_system_mutex); m_metadata_manager = nullptr; @@ -146,8 +127,8 @@ void SyncManager::reset_for_testing() std::this_thread::sleep_for(std::chrono::milliseconds(10)); lock.lock(); } - // Callers of `SyncManager::reset_for_testing` should ensure there are no existing sessions - // prior to calling `reset_for_testing`. + // Callers of `SyncManager::tear_down_for_testing` should ensure there are no existing sessions + // prior to calling `tear_down_for_testing`. if (!no_sessions) { util::CheckedLockGuard lock(m_mutex); for (auto session : m_sessions) { @@ -168,9 +149,6 @@ void SyncManager::reset_for_testing() util::CheckedLockGuard lock(m_mutex); // Destroy the client now that we have no remaining sessions. m_sync_client = nullptr; - - // Reset even more state. - m_config = {}; m_logger_ptr.reset(); m_sync_route.reset(); } diff --git a/src/realm/object-store/sync/sync_manager.hpp b/src/realm/object-store/sync/sync_manager.hpp index 3a32d88f9c9..12e345a93f6 100644 --- a/src/realm/object-store/sync/sync_manager.hpp +++ b/src/realm/object-store/sync/sync_manager.hpp @@ -105,9 +105,7 @@ struct SyncClientConfig { }; class SyncManager : public std::enable_shared_from_this { - friend class SyncSession; - friend class ::TestSyncManager; - friend class ::TestAppSession; + struct Private {}; public: using MetadataMode = SyncClientConfig::MetadataMode; @@ -215,11 +213,11 @@ class SyncManager : public std::enable_shared_from_this { std::string recovery_directory_path(util::Optional const& custom_dir_name = none) const REQUIRES(!m_file_system_mutex); - // Reset the singleton state for testing purposes. DO NOT CALL OUTSIDE OF TESTING CODE. - // Precondition: any synced Realms or `SyncSession`s must be closed or rendered inactive prior to - // calling this method. - void reset_for_testing() REQUIRES(!m_mutex, !m_file_system_mutex, !m_user_mutex, !m_session_mutex); - + // DO NOT CALL OUTSIDE OF TESTING CODE. + // Forcibly close all remaining sync sessions, stop the sync client, and + // discard all state. The SyncManager must never be used again after this + // function has been called. + void tear_down_for_testing() REQUIRES(!m_mutex, !m_file_system_mutex, !m_user_mutex, !m_session_mutex); // Immediately closes any open sync sessions for this sync manager void close_all_sessions() REQUIRES(!m_mutex, !m_session_mutex); @@ -249,6 +247,11 @@ class SyncManager : public std::enable_shared_from_this { return m_app; } + const std::string& app_id() const + { + return m_app_id; + } + SyncClientConfig config() const REQUIRES(!m_mutex) { util::CheckedLockGuard lock(m_mutex); @@ -258,29 +261,23 @@ class SyncManager : public std::enable_shared_from_this { // Return the cached logger const std::shared_ptr& get_logger() const REQUIRES(!m_mutex); - SyncManager(); - SyncManager(const SyncManager&) = delete; - SyncManager& operator=(const SyncManager&) = delete; - struct OnlyForTesting { friend class TestHelper; static void voluntary_disconnect_all_connections(SyncManager&); }; -protected: - friend class SyncUser; - friend class SyncSesson; - - using std::enable_shared_from_this::shared_from_this; - using std::enable_shared_from_this::weak_from_this; + static std::shared_ptr create(std::shared_ptr app, std::optional sync_route, + const SyncClientConfig& config, const std::string& app_id); + SyncManager(Private, std::shared_ptr app, std::optional sync_route, + const SyncClientConfig& config, const std::string& app_id); private: friend class app::App; - - void configure(std::shared_ptr app, std::optional sync_route, - const SyncClientConfig& config) - REQUIRES(!m_mutex, !m_file_system_mutex, !m_user_mutex, !m_session_mutex); + friend class SyncSession; + friend class SyncUser; + friend class ::TestSyncManager; + friend class ::TestAppSession; // Stop tracking the session for the given path if it is inactive. // No-op if the session is either still active or in the active sessions list @@ -332,10 +329,11 @@ class SyncManager : public std::enable_shared_from_this { bool do_has_existing_sessions() REQUIRES(m_session_mutex); // The sync route URL to connect to the server. This can be initially empty, but should not - // be cleared once it has been set to a value, except by `reset_for_testing()`. + // be cleared once it has been set to a value, except by `tear_down_for_testing()`. std::optional m_sync_route GUARDED_BY(m_mutex); std::weak_ptr m_app GUARDED_BY(m_mutex); + const std::string m_app_id; }; } // namespace realm diff --git a/test/object-store/audit.cpp b/test/object-store/audit.cpp index 7d04d0c4690..a7b6547fa5e 100644 --- a/test/object-store/audit.cpp +++ b/test/object-store/audit.cpp @@ -265,7 +265,7 @@ struct TestClock { TEST_CASE("audit object serialization", "[sync][pbs][audit]") { TestSyncManager test_session; - SyncTestFile config(test_session.app(), "parent"); + SyncTestFile config(test_session, "parent"); config.automatic_change_notifications = false; config.schema_version = 1; config.schema = Schema{ @@ -1066,7 +1066,7 @@ TEST_CASE("audit management", "[sync][pbs][audit]") { TestClock clock; TestSyncManager test_session; - SyncTestFile config(test_session.app(), "parent"); + SyncTestFile config(test_session, "parent"); config.automatic_change_notifications = false; config.schema_version = 1; config.schema = Schema{ @@ -1087,7 +1087,7 @@ TEST_CASE("audit management", "[sync][pbs][audit]") { realm->sync_session()->close(); SECTION("config validation") { - SyncTestFile config(test_session.app(), "parent2"); + SyncTestFile config(test_session, "parent2"); config.automatic_change_notifications = false; config.audit_config = std::make_shared(); SECTION("invalid prefix") { @@ -1495,7 +1495,7 @@ TEST_CASE("audit realm sharding", "[sync][pbs][audit]") { // a lot of local unuploaded data. TestSyncManager test_session{{}, {.start_immediately = false}}; - SyncTestFile config(test_session.app(), "parent"); + SyncTestFile config(test_session, "parent"); config.automatic_change_notifications = false; config.schema_version = 1; config.schema = Schema{ @@ -1604,7 +1604,7 @@ TEST_CASE("audit realm sharding", "[sync][pbs][audit]") { test_session.sync_server().start(); // Open a different Realm with the same user and audit prefix - SyncTestFile config(test_session.app(), "other"); + SyncTestFile config(test_session, "other"); config.audit_config = std::make_shared(); config.audit_config->logger = audit_logger; auto realm = Realm::get_shared_realm(config); @@ -1631,7 +1631,7 @@ TEST_CASE("audit realm sharding", "[sync][pbs][audit]") { test_session.sync_server().start(); // Open the same Realm with a different audit prefix - SyncTestFile config(test_session.app(), "parent"); + SyncTestFile config(test_session, "parent"); config.audit_config = std::make_shared(); config.audit_config->logger = audit_logger; config.audit_config->partition_value_prefix = "other"; @@ -1719,7 +1719,7 @@ TEST_CASE("audit integration tests", "[sync][pbs][audit][baas]") { realm->sync_session()->close(); generate_event(realm); - auto events = get_audit_events_from_baas(session, *config.sync_config->user, 1); + auto events = get_audit_events_from_baas(session, *session.app()->current_user(), 1); REQUIRE(events.size() == 1); REQUIRE(events[0].activity == "scope"); REQUIRE(events[0].event == "read"); @@ -1728,18 +1728,20 @@ TEST_CASE("audit integration tests", "[sync][pbs][audit][baas]") { } SECTION("different user from parent Realm") { + auto sync_user = session.app()->current_user(); create_user_and_log_in(session.app()); - config.audit_config->audit_user = session.app()->current_user(); + auto audit_user = session.app()->current_user(); + config.audit_config->audit_user = audit_user; auto realm = Realm::get_shared_realm(config); // If audit uses the sync user this'll make it fail as that user is logged out - config.sync_config->user->log_out(); + sync_user->log_out(); generate_event(realm); - REQUIRE(get_audit_events_from_baas(session, *config.audit_config->audit_user, 1).size() == 1); + REQUIRE(get_audit_events_from_baas(session, *audit_user, 1).size() == 1); } SECTION("different app from parent Realm") { - auto audit_user = config.sync_config->user; + auto audit_user = session.app()->current_user(); // Create an app which does not include AuditEvent in the schema so that // things will break if audit tries to use it @@ -1766,7 +1768,7 @@ TEST_CASE("audit integration tests", "[sync][pbs][audit][baas]") { generate_event(realm, 3); using Metadata = std::map; - auto events = get_audit_events_from_baas(session, *config.sync_config->user, 4); + auto events = get_audit_events_from_baas(session, *session.app()->current_user(), 4); REQUIRE(events[0].metadata.empty()); REQUIRE(events[1].metadata == Metadata({{"metadata 1", "value 1"}})); REQUIRE(events[2].metadata == Metadata({{"metadata 2", "value 2"}})); @@ -1814,7 +1816,7 @@ TEST_CASE("audit integration tests", "[sync][pbs][audit][baas]") { } SECTION("incoming changesets are discarded") { - app::MongoClient remote_client = config.sync_config->user->mongo_client("BackingDB"); + app::MongoClient remote_client = session.app()->current_user()->mongo_client("BackingDB"); app::MongoDatabase db = remote_client.db(session.app_session().config.mongo_dbname); app::MongoCollection collection = db["AuditEvent"]; @@ -1905,7 +1907,7 @@ TEST_CASE("audit integration tests", "[sync][pbs][audit][baas]") { realm->sync_session()->force_close(); generate_event(realm, 0); - get_audit_events_from_baas(session, *config.audit_config->audit_user, 1); + get_audit_events_from_baas(session, *session.app()->current_user(), 1); } } diff --git a/test/object-store/benchmarks/client_reset.cpp b/test/object-store/benchmarks/client_reset.cpp index 93a83fe99fb..a2bf75571b8 100644 --- a/test/object-store/benchmarks/client_reset.cpp +++ b/test/object-store/benchmarks/client_reset.cpp @@ -192,13 +192,12 @@ TEST_CASE("client reset", "[sync][pbs][benchmark][client reset]") { }; TestSyncManager init_sync_manager; - SyncTestFile config(init_sync_manager.app(), "default"); - config.cache = false; + SyncTestFile config(init_sync_manager, "default"); config.automatic_change_notifications = false; config.schema = schema; ClientResyncMode reset_mode = GENERATE(ClientResyncMode::DiscardLocal, ClientResyncMode::Recover); config.sync_config->client_resync_mode = reset_mode; - SyncTestFile config2(init_sync_manager.app(), "default"); + SyncTestFile config2(init_sync_manager, "default"); auto populate_objects = [&](SharedRealm realm, size_t num_objects) { TableRef table = get_table(*realm, "object"); diff --git a/test/object-store/benchmarks/object.cpp b/test/object-store/benchmarks/object.cpp index 47413d90077..4f4a3001b2b 100644 --- a/test/object-store/benchmarks/object.cpp +++ b/test/object-store/benchmarks/object.cpp @@ -781,7 +781,6 @@ TEST_CASE("Benchmark object notification delivery", "[benchmark][notifications]" InMemoryTestFile config; config.automatic_change_notifications = false; config.schema = Schema{{"object", {{"value", PropertyType::Int}}}}; - config.cache = false; auto r = Realm::get_shared_realm(config); r->begin_transaction(); diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 2d3f98557a6..d976a05b220 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -619,14 +619,14 @@ TEST_CASE("C API (non-database)", "[c_api]") { auto token = realm_sync_user_on_state_change_register_callback(sync_user, user_state, nullptr, user_data_free); - auto check_base_url = [&](std::string expected) { + auto check_base_url = [&](const std::string& expected) { CHECK(transport->get_location_called()); auto app_base_url = realm_app_get_base_url(test_app.get()); CHECK(app_base_url == expected); realm_free(app_base_url); }; - auto update_and_check_base_url = [&](const char* new_base_url, std::string expected) { + auto update_and_check_base_url = [&](const char* new_base_url, const std::string& expected) { transport->set_base_url(expected); realm_app_update_base_url( test_app.get(), new_base_url, @@ -5248,8 +5248,7 @@ static void sync_error_handler(void* p, realm_sync_session_t*, const realm_sync_ TEST_CASE("C API - async_open", "[sync][pbs][c_api]") { TestSyncManager init_sync_manager; - SyncTestFile test_config(init_sync_manager.app(), "default"); - test_config.cache = false; + SyncTestFile test_config(init_sync_manager, "default"); ObjectSchema object_schema = {"object", { {"_id", PropertyType::Int, Property::IsPrimary{true}}, @@ -5260,7 +5259,7 @@ TEST_CASE("C API - async_open", "[sync][pbs][c_api]") { SECTION("can open synced Realms that don't already exist") { realm_config_t* config = realm_config_new(); config->schema = Schema{object_schema}; - realm_user user(init_sync_manager.app()->current_user()); + realm_user user(test_config.sync_config->user); realm_sync_config_t* sync_config = realm_sync_config_new(&user, "default"); realm_sync_config_set_initial_subscription_handler(sync_config, task_init_subscription, false, nullptr, nullptr); @@ -5293,14 +5292,31 @@ TEST_CASE("C API - async_open", "[sync][pbs][c_api]") { SECTION("cancels download and reports an error on auth error") { auto expired_token = encode_fake_jwt("", 123, 456); + struct Transport : UnitTestTransport { + void send_request_to_server( + const realm::app::Request& req, + realm::util::UniqueFunction&& completion) override + { + if (req.url.find("/auth/session") != std::string::npos) { + completion(app::Response{403}); + } + else { + UnitTestTransport::send_request_to_server(req, std::move(completion)); + } + } + }; + OfflineAppSession::Config oas_config; + oas_config.transport = std::make_shared(); + OfflineAppSession oas(oas_config); + SyncTestFile test_config(oas, "realm"); + test_config.sync_config->user->log_in(expired_token, expired_token); + realm_config_t* config = realm_config_new(); config->schema = Schema{object_schema}; - realm_user user(init_sync_manager.app()->current_user()); + realm_user user(test_config.sync_config->user); realm_sync_config_t* sync_config = realm_sync_config_new(&user, "realm"); realm_sync_config_set_initial_subscription_handler(sync_config, task_init_subscription, false, nullptr, nullptr); - sync_config->user->log_in(expired_token, expired_token); - realm_config_set_path(config, test_config.path.c_str()); realm_config_set_schema_version(config, 1); Userdata userdata; @@ -5310,7 +5326,6 @@ TEST_CASE("C API - async_open", "[sync][pbs][c_api]") { realm_async_open_task_t* task = realm_open_synchronized(config); REQUIRE(task); realm_async_open_task_start(task, task_completion_func, &userdata, nullptr); - init_sync_manager.network_callback(app::Response{403}); util::EventLoop::main().run_until([&] { return userdata.called.load(); }); diff --git a/test/object-store/dictionary.cpp b/test/object-store/dictionary.cpp index 52ffc325873..adcfef9d024 100644 --- a/test/object-store/dictionary.cpp +++ b/test/object-store/dictionary.cpp @@ -73,7 +73,6 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf using W = typename TestType::Wrapped; InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", @@ -883,7 +882,6 @@ TEMPLATE_TEST_CASE("dictionary types", "[dictionary]", cf::MixedVal, cf::Int, cf TEST_CASE("embedded dictionary", "[dictionary]") { InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"origin", {{"links", PropertyType::Dictionary | PropertyType::Object | PropertyType::Nullable, "target"}}}, @@ -940,7 +938,6 @@ TEMPLATE_TEST_CASE("dictionary of objects", "[dictionary][links]", cf::MixedVal, using T = typename TestType::Type; using W = typename TestType::Wrapped; InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", {{"links", PropertyType::Dictionary | PropertyType::Object | PropertyType::Nullable, "target"}}}, @@ -1050,7 +1047,6 @@ TEMPLATE_TEST_CASE("dictionary of objects", "[dictionary][links]", cf::MixedVal, TEST_CASE("dictionary with mixed links", "[dictionary]") { InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", {{"value", PropertyType::Dictionary | PropertyType::Mixed | PropertyType::Nullable}}}, @@ -1586,7 +1582,6 @@ TEST_CASE("dictionary sort by keyPath value", "[dictionary]") { TEST_CASE("dictionary sort by linked object value", "[dictionary]") { InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", diff --git a/test/object-store/frozen_objects.cpp b/test/object-store/frozen_objects.cpp index 7b3db1bb188..afabaef1b58 100644 --- a/test/object-store/frozen_objects.cpp +++ b/test/object-store/frozen_objects.cpp @@ -500,10 +500,7 @@ TEST_CASE("Reclaim Frozen", "[frozen]") { std::vector refs; refs.resize(num_pending_transactions); TestFile config; - config.schema_version = 1; - config.automatic_change_notifications = true; - config.cache = false; config.schema = Schema{ {"table", {{"value", PropertyType::Int}, {"link", PropertyType::Object | PropertyType::Nullable, "table"}}}}; auto realm = Realm::get_shared_realm(config); diff --git a/test/object-store/list.cpp b/test/object-store/list.cpp index 19f1ef596b5..7fc56b9c9f0 100644 --- a/test/object-store/list.cpp +++ b/test/object-store/list.cpp @@ -44,7 +44,6 @@ using util::any_cast; TEST_CASE("list", "[list]") { InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; auto r = Realm::get_shared_realm(config); r->update_schema({ @@ -1842,7 +1841,7 @@ TEST_CASE("list with unresolved links", "[list]") { TestSyncManager init_sync_manager({}, {false}); auto& server = init_sync_manager.sync_server(); - SyncTestFile config1(init_sync_manager.app(), "shared"); + SyncTestFile config1(init_sync_manager, "shared"); config1.schema = Schema{ {"origin", {{"_id", PropertyType::Int, Property::IsPrimary(true)}, @@ -1850,7 +1849,7 @@ TEST_CASE("list with unresolved links", "[list]") { {"target", {{"_id", PropertyType::Int, Property::IsPrimary(true)}, {"value", PropertyType::Int}}}, }; - SyncTestFile config2(init_sync_manager.app(), "shared"); + SyncTestFile config2(init_sync_manager, "shared"); auto r1 = Realm::get_shared_realm(config1); auto r2 = Realm::get_shared_realm(config2); diff --git a/test/object-store/migrations.cpp b/test/object-store/migrations.cpp index b9118724905..f254aa4952f 100644 --- a/test/object-store/migrations.cpp +++ b/test/object-store/migrations.cpp @@ -2159,7 +2159,6 @@ TEST_CASE("migration: Additive", "[migration]") { }; TestFile config; - config.cache = false; config.schema = schema; config.schema_mode = GENERATE(SchemaMode::AdditiveDiscovered, SchemaMode::AdditiveExplicit); auto realm = Realm::get_shared_realm(config); diff --git a/test/object-store/object.cpp b/test/object-store/object.cpp index b8feac9e910..b5c9a45b11c 100644 --- a/test/object-store/object.cpp +++ b/test/object-store/object.cpp @@ -1982,9 +1982,9 @@ TEST_CASE("object") { SECTION("defaults do not override values explicitly passed to create()") { TestSyncManager init_sync_manager({}, {false}); auto& server = init_sync_manager.sync_server(); - SyncTestFile config1(init_sync_manager.app(), "shared"); + SyncTestFile config1(init_sync_manager, "shared"); config1.schema = config.schema; - SyncTestFile config2(init_sync_manager.app(), "shared"); + SyncTestFile config2(init_sync_manager, "shared"); config2.schema = config.schema; AnyDict v1{ diff --git a/test/object-store/primitive_list.cpp b/test/object-store/primitive_list.cpp index 731fbce9698..a947bd43a4d 100644 --- a/test/object-store/primitive_list.cpp +++ b/test/object-store/primitive_list.cpp @@ -118,7 +118,6 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: using Boxed = typename TestType::Boxed; InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", {{"value", PropertyType::Array | TestType::property_type}}}, @@ -848,7 +847,6 @@ TEMPLATE_TEST_CASE("primitive list", "[primitives]", cf::MixedVal, cf::Int, cf:: TEST_CASE("list of mixed links", "[primitives]") { InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", {{"value", PropertyType::Array | PropertyType::Mixed | PropertyType::Nullable}}}, diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index b0582db0832..dd32c6b3fb1 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -387,11 +387,7 @@ TEST_CASE("SharedRealm: get_shared_realm()") { } SECTION("should sensibly handle opening an uninitialized file without a schema specified") { - SECTION("cached") { - } - SECTION("uncached") { - config.cache = false; - } + config.cache = GENERATE(false, true); // create an empty file util::File(config.path, util::File::mode_Write); @@ -926,16 +922,15 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { if (!util::EventLoop::has_implementation()) return; - TestSyncManager init_sync_manager; - SyncTestFile config(init_sync_manager.app(), "default"); - config.cache = false; + TestSyncManager tsm; + SyncTestFile config(tsm, "default"); ObjectSchema object_schema = {"object", { {"_id", PropertyType::Int, Property::IsPrimary{true}}, {"value", PropertyType::Int}, }}; config.schema = Schema{object_schema}; - SyncTestFile config2(init_sync_manager.app(), "default"); + SyncTestFile config2(tsm, "default"); config2.schema = config.schema; std::mutex mutex; @@ -965,7 +960,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { SECTION("can write a realm file without client file id") { ThreadSafeReference realm_ref; - SyncTestFile config3(init_sync_manager.app(), "default"); + SyncTestFile config3(tsm, "default"); config3.schema = config.schema; uint64_t client_file_id; @@ -1116,10 +1111,10 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } SECTION("can download multiple Realms at a time") { - SyncTestFile config1(init_sync_manager.app(), "realm1"); - SyncTestFile config2(init_sync_manager.app(), "realm2"); - SyncTestFile config3(init_sync_manager.app(), "realm3"); - SyncTestFile config4(init_sync_manager.app(), "realm4"); + SyncTestFile config1(tsm, "realm1"); + SyncTestFile config2(tsm, "realm2"); + SyncTestFile config3(tsm, "realm3"); + SyncTestFile config4(tsm, "realm4"); std::vector> tasks = { Realm::get_synchronized_realm(config1), @@ -1142,9 +1137,10 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { auto expired_token = encode_fake_jwt("", 123, 456); SECTION("can async open while waiting for a token refresh") { - SyncTestFile config(init_sync_manager.app(), "realm"); - auto valid_token = config.sync_config->user->access_token(); - config.sync_config->user->update_access_token(std::move(expired_token)); + SyncTestFile config(tsm, "realm"); + auto user = config.sync_config->user; + auto valid_token = user->access_token(); + user->update_access_token(std::move(expired_token)); std::atomic called{false}; auto task = Realm::get_synchronized_realm(config); @@ -1154,9 +1150,11 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { REQUIRE(!error); called = true; }); + auto session = tsm.sync_manager()->get_existing_session(config.path); + REQUIRE(session); + CHECK(session->state() == SyncSession::State::WaitingForAccessToken); - auto body = nlohmann::json({{"access_token", valid_token}}).dump(); - init_sync_manager.network_callback(app::Response{200, 0, {}, body}); + session->update_access_token(valid_token); util::EventLoop::main().run_until([&] { return called.load(); }); @@ -1165,19 +1163,24 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } SECTION("cancels download and reports an error on auth error") { - struct Transport : realm::app::GenericNetworkTransport { + struct Transport : UnitTestTransport { void send_request_to_server( - const realm::app::Request&, + const realm::app::Request& req, realm::util::UniqueFunction&& completion) override { - completion(app::Response{403}); + if (req.url.find("/auth/session") != std::string::npos) { + completion(app::Response{403}); + } + else { + UnitTestTransport::send_request_to_server(req, std::move(completion)); + } } }; - TestSyncManager::Config tsm_config; - tsm_config.transport = std::make_shared(); - TestSyncManager tsm(tsm_config); + OfflineAppSession::Config oas_config; + oas_config.transport = std::make_shared(); + OfflineAppSession oas(oas_config); - SyncTestFile config(tsm.app(), "realm"); + SyncTestFile config(oas, "realm"); config.sync_config->user->log_in(expired_token, expired_token); bool got_error = false; @@ -1345,7 +1348,7 @@ TEST_CASE("SharedRealm: convert", "[sync][pbs][convert]") { }}; Schema schema{object_schema}; - SyncTestFile sync_config1(tsm.app(), "default"); + SyncTestFile sync_config1(tsm, "default"); sync_config1.schema = schema; TestFile local_config1; local_config1.schema = schema; @@ -1360,7 +1363,7 @@ TEST_CASE("SharedRealm: convert", "[sync][pbs][convert]") { wait_for_download(*sync_realm1); // Copy to a new sync config - SyncTestFile sync_config2(tsm.app(), "default"); + SyncTestFile sync_config2(tsm, "default"); sync_config2.schema = schema; sync_realm1->convert(sync_config2); @@ -1447,7 +1450,7 @@ TEST_CASE("SharedRealm: convert - embedded objects", "[sync][pbs][convert][embed }}; Schema schema{object_schema, embedded_schema}; - SyncTestFile sync_config1(tsm.app(), "default"); + SyncTestFile sync_config1(tsm, "default"); sync_config1.schema = schema; TestFile local_config1; local_config1.schema = schema; @@ -1472,7 +1475,7 @@ TEST_CASE("SharedRealm: convert - embedded objects", "[sync][pbs][convert][embed wait_for_download(*sync_realm1); // Copy to a new sync config - SyncTestFile sync_config2(tsm.app(), "default"); + SyncTestFile sync_config2(tsm, "default"); sync_config2.schema = schema; sync_realm1->convert(sync_config2); @@ -2614,7 +2617,6 @@ TEST_CASE("SharedRealm: async_writes_2") { return; TestFile config; - config.cache = false; config.schema_version = 0; config.schema = Schema{ {"object", {{"value", PropertyType::Int}}}, diff --git a/test/object-store/results.cpp b/test/object-store/results.cpp index 6bf262e98d3..5815d258e84 100644 --- a/test/object-store/results.cpp +++ b/test/object-store/results.cpp @@ -901,7 +901,6 @@ TEST_CASE("notifications: async delivery", "[notifications]") { TEST_CASE("notifications: skip", "[notifications]") { _impl::RealmCoordinator::assert_no_open_realms(); InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; auto r = Realm::get_shared_realm(config); @@ -1430,8 +1429,7 @@ TEST_CASE("notifications: sync", "[sync][pbs][notifications]") { TestSyncManager init_sync_manager({}, {false}); auto& server = init_sync_manager.sync_server(); - SyncTestFile config(init_sync_manager.app(), "test"); - config.cache = false; + SyncTestFile config(init_sync_manager, "test"); config.schema = Schema{ {"object", { @@ -1476,7 +1474,6 @@ TEST_CASE("notifications: sync", "[sync][pbs][notifications]") { TEST_CASE("notifications: results", "[notifications][results]") { _impl::RealmCoordinator::assert_no_open_realms(); InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; auto r = Realm::get_shared_realm(config); @@ -3277,7 +3274,6 @@ TEST_CASE("results: notifications after move", "[notifications][results]") { TEST_CASE("results: notifier with no callbacks", "[notifications][results]") { _impl::RealmCoordinator::assert_no_open_realms(); InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; auto coordinator = _impl::RealmCoordinator::get_coordinator(config.path); @@ -3353,7 +3349,6 @@ TEST_CASE("results: notifier with no callbacks", "[notifications][results]") { TEST_CASE("results: snapshots", "[results]") { InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", @@ -3674,7 +3669,6 @@ TEST_CASE("results: snapshots", "[results]") { TEST_CASE("results: distinct", "[results]") { const int N = 10; InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; auto r = Realm::get_shared_realm(config); @@ -3885,7 +3879,6 @@ TEST_CASE("results: distinct", "[results]") { TEST_CASE("results: sort", "[results]") { InMemoryTestFile config; - config.cache = false; config.schema = Schema{ {"object", { @@ -4792,7 +4785,6 @@ TEST_CASE("results: nullable list of primitives", "[results]") { TEST_CASE("results: limit", "[results][limit]") { InMemoryTestFile config; - // config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", @@ -4918,7 +4910,6 @@ TEST_CASE("results: limit", "[results][limit]") { TEST_CASE("results: filter", "[results]") { InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; auto r = Realm::get_shared_realm(config); @@ -4974,7 +4965,6 @@ TEST_CASE("results: filter", "[results]") { TEST_CASE("results: public name declared", "[results]") { InMemoryTestFile config; - // config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", @@ -5021,7 +5011,6 @@ TEST_CASE("results: public name declared", "[results]") { TEST_CASE("notifications: objects with PK recreated", "[results]") { _impl::RealmCoordinator::assert_no_open_realms(); InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; auto r = Realm::get_shared_realm(config); diff --git a/test/object-store/set.cpp b/test/object-store/set.cpp index cb90a588da0..5327b98e31b 100644 --- a/test/object-store/set.cpp +++ b/test/object-store/set.cpp @@ -1390,7 +1390,6 @@ TEMPLATE_TEST_CASE("set", "[set]", CreateNewSet, ReuseSet) TEST_CASE("set with mixed links", "[set]") { InMemoryTestFile config; - config.cache = false; config.automatic_change_notifications = false; config.schema = Schema{ {"object", {{"value", PropertyType::Set | PropertyType::Mixed | PropertyType::Nullable}}}, diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 441351f82c2..098cf23cf67 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -19,6 +19,7 @@ #include "collection_fixtures.hpp" #include "util/sync/baas_admin_api.hpp" #include "util/sync/sync_test_utils.hpp" +#include "util/test_path.hpp" #include "util/unit_test_transport.hpp" #include @@ -47,7 +48,6 @@ #include #include -#include #include #include #include @@ -735,26 +735,24 @@ TEST_CASE("app: login_with_credentials integration", "[sync][app][user][baas]") app->log_out([](auto) {}); int subscribe_processed = 0; - auto token = app->subscribe([&subscribe_processed](auto& app) { - if (!subscribe_processed) { - REQUIRE(app.current_user()); - } - else { - REQUIRE_FALSE(app.current_user()); - } + + auto token = app->subscribe([&subscribe_processed](auto&) { subscribe_processed++; }); + REQUIRE_FALSE(app->current_user()); auto user = log_in(app); CHECK(!user->device_id().empty()); CHECK(user->has_device_id()); + REQUIRE(app->current_user()); + CHECK(subscribe_processed == 1); bool processed = false; app->log_out([&](auto error) { REQUIRE_FALSE(error); processed = true; }); - + REQUIRE_FALSE(app->current_user()); CHECK(processed); CHECK(subscribe_processed == 2); @@ -909,8 +907,8 @@ TEST_CASE("app: UsernamePasswordProviderClient integration", "[sync][app][user][ SECTION("log in, remove, log in") { app->remove_user(app->current_user(), [](auto) {}); - CHECK(app->sync_manager()->all_users().size() == 0); - CHECK(app->sync_manager()->get_current_user() == nullptr); + CHECK(app->all_users().size() == 0); + CHECK(app->current_user() == nullptr); auto user = log_in(app, AppCredentials::username_password(email, password)); CHECK(user->user_profile().email() == email); @@ -930,7 +928,7 @@ TEST_CASE("app: UsernamePasswordProviderClient integration", "[sync][app][user][ app->remove_user(user, [&](Optional error) { REQUIRE(!error); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); processed = true; }); @@ -1276,7 +1274,7 @@ TEST_CASE("app: delete anonymous user integration", "[sync][app][user][baas]") { auto app = session.app(); SECTION("delete user expect success") { - CHECK(app->sync_manager()->all_users().size() == 1); + CHECK(app->all_users().size() == 1); // Log in user 1 auto user_a = app->current_user(); @@ -1286,26 +1284,26 @@ TEST_CASE("app: delete anonymous user integration", "[sync][app][user][baas]") { // a logged out anon user will be marked as Removed, not LoggedOut CHECK(user_a->state() == SyncUser::State::Removed); }); - CHECK(app->sync_manager()->all_users().empty()); - CHECK(app->sync_manager()->get_current_user() == nullptr); + CHECK(app->all_users().empty()); + CHECK(app->current_user() == nullptr); app->delete_user(user_a, [&](Optional error) { CHECK(error->reason() == "User must be logged in to be deleted."); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); }); // Log in user 2 auto user_b = log_in(app); - CHECK(app->sync_manager()->get_current_user() == user_b); + CHECK(app->current_user() == user_b); CHECK(user_b->state() == SyncUser::State::LoggedIn); - CHECK(app->sync_manager()->all_users().size() == 1); + CHECK(app->all_users().size() == 1); app->delete_user(user_b, [&](Optional error) { REQUIRE_FALSE(error); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); }); - CHECK(app->sync_manager()->get_current_user() == nullptr); + CHECK(app->current_user() == nullptr); // check both handles are no longer valid CHECK(user_a->state() == SyncUser::State::Removed); @@ -1319,35 +1317,35 @@ TEST_CASE("app: delete user with credentials integration", "[sync][app][user][ba app->remove_user(app->current_user(), [](auto) {}); SECTION("log in and delete") { - CHECK(app->sync_manager()->all_users().size() == 0); - CHECK(app->sync_manager()->get_current_user() == nullptr); + CHECK(app->all_users().size() == 0); + CHECK(app->current_user() == nullptr); auto credentials = create_user_and_log_in(app); auto user = app->current_user(); - CHECK(app->sync_manager()->get_current_user() == user); + CHECK(app->current_user() == user); CHECK(user->state() == SyncUser::State::LoggedIn); app->delete_user(user, [&](Optional error) { REQUIRE_FALSE(error); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); }); CHECK(user->state() == SyncUser::State::Removed); - CHECK(app->sync_manager()->get_current_user() == nullptr); + CHECK(app->current_user() == nullptr); app->log_in_with_credentials(credentials, [](std::shared_ptr user, util::Optional error) { CHECK(!user); REQUIRE(error); REQUIRE(error->code() == ErrorCodes::InvalidPassword); }); - CHECK(app->sync_manager()->get_current_user() == nullptr); + CHECK(app->current_user() == nullptr); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); app->delete_user(user, [](Optional err) { CHECK(err->code() > 0); }); - CHECK(app->sync_manager()->get_current_user() == nullptr); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->current_user() == nullptr); + CHECK(app->all_users().size() == 0); CHECK(user->state() == SyncUser::State::Removed); } } @@ -1365,7 +1363,7 @@ TEST_CASE("app: call function", "[sync][app][function][baas]") { CHECK(*sum == 15); }; app->call_function("sumFunc", toSum, checkFn); - app->call_function(app->sync_manager()->get_current_user(), "sumFunc", toSum, checkFn); + app->call_function(app->current_user(), "sumFunc", toSum, checkFn); } // MARK: - Remote Mongo Client Tests @@ -2183,7 +2181,7 @@ TEST_CASE("app: mixed lists with object links", "[sync][pbs][app][links][baas]") }; { TestAppSession test_session(app_session, nullptr, DeleteApp{false}); - SyncTestFile config(test_session.app(), partition, schema); + SyncTestFile config(test_session.app()->current_user(), partition, schema); auto realm = Realm::get_shared_realm(config); CppContext c(realm); @@ -2785,16 +2783,14 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { { std::unique_ptr app_session; - std::string base_file_path = util::make_temp_dir() + random_string(10); auto redir_transport = std::make_shared(); AutoVerifiedEmailCredentials creds; auto app_config = get_config(redir_transport, session.app_session()); set_app_config_defaults(app_config, redir_transport); - util::try_make_dir(base_file_path); SyncClientConfig sc_config; - sc_config.base_file_path = base_file_path; + sc_config.base_file_path = util::make_temp_dir(); sc_config.metadata_mode = realm::SyncManager::MetadataMode::NoEncryption; // initialize app and sync client @@ -2955,16 +2951,14 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { } SECTION("Test app redirect with no metadata") { std::unique_ptr app_session; - std::string base_file_path = util::make_temp_dir() + random_string(10); auto redir_transport = std::make_shared(); AutoVerifiedEmailCredentials creds, creds2; auto app_config = get_config(redir_transport, session.app_session()); set_app_config_defaults(app_config, redir_transport); - util::try_make_dir(base_file_path); SyncClientConfig sc_config; - sc_config.base_file_path = base_file_path; + sc_config.base_file_path = util::make_temp_dir(); sc_config.metadata_mode = realm::SyncManager::MetadataMode::NoMetadata; // initialize app and sync client @@ -3495,7 +3489,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { { std::atomic called{false}; session->wait_for_upload_completion([&](Status stat) { - std::lock_guard lock(mtx); + std::lock_guard lock(mtx); called.store(true); REQUIRE(stat.code() == ErrorCodes::InvalidSession); }); @@ -3503,7 +3497,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { timed_wait_for([&] { return called.load(); }); - std::lock_guard lock(mtx); + std::lock_guard lock(mtx); REQUIRE(called); } @@ -3513,7 +3507,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { Catch::Matchers::StartsWith("Unable to refresh the user access token")); // the failed refresh logs out the user - std::lock_guard lock(mtx); + std::lock_guard lock(mtx); REQUIRE(!user->is_logged_in()); }; @@ -3926,7 +3920,6 @@ TEST_CASE("app: base_url", "[sync][app][base_url]") { }; std::unique_ptr app_session; - std::string base_file_path = util::make_temp_dir() + random_string(10); auto redir_transport = std::make_shared(); AutoVerifiedEmailCredentials creds; util::Logger::set_default_level_threshold(realm::util::Logger::Level::TEST_LOGGING_LEVEL); @@ -3935,9 +3928,8 @@ TEST_CASE("app: base_url", "[sync][app][base_url]") { App::Config app_config = {"fake-app-id"}; set_app_config_defaults(app_config, redir_transport); - util::try_make_dir(base_file_path); SyncClientConfig sc_config; - sc_config.base_file_path = base_file_path; + sc_config.base_file_path = util::make_temp_dir(); sc_config.metadata_mode = realm::SyncManager::MetadataMode::NoEncryption; sc_config.logger_factory = [](util::Logger::Level) { return util::Logger::get_default_logger(); @@ -3953,7 +3945,6 @@ TEST_CASE("app: base_url", "[sync][app][base_url]") { }; SECTION("Test app config baseurl") { - // Metadata mode doesn't matter, since App isn't using it anymore { redir_transport->reset("https://realm.mongodb.com"); @@ -4664,10 +4655,10 @@ TEST_CASE("app: custom error handling", "[sync][app][custom errors]") { }; SECTION("custom code and message is sent back") { - TestSyncManager::Config config; + OfflineAppSession::Config config; config.transport = std::make_shared(1001, "Boom!"); - TestSyncManager tsm(config); - auto error = failed_log_in(tsm.app()); + OfflineAppSession oas(config); + auto error = failed_log_in(oas.app()); CHECK(error.is_custom_error()); CHECK(*error.additional_status_code == 1001); CHECK(error.reason() == "Boom!"); @@ -4676,11 +4667,6 @@ TEST_CASE("app: custom error handling", "[sync][app][custom errors]") { // MARK: - Unit Tests -static TestSyncManager::Config get_config() -{ - return get_config(instance_of); -} - static const std::string bad_access_token = "lolwut"; static const std::string dummy_device_id = "123400000000000000000000"; @@ -4754,17 +4740,17 @@ TEST_CASE("subscribable unit tests", "[sync][app]") { } TEST_CASE("app: login_with_credentials unit_tests", "[sync][app][user]") { - auto config = get_config(); + OfflineAppSession::Config config{std::make_shared()}; static_cast(config.transport.get())->set_profile(profile_0); SECTION("login_anonymous good") { UnitTestTransport::access_token = good_access_token; - config.base_path = util::make_temp_dir(); - config.should_teardown_test_directory = false; + config.delete_storage = false; config.metadata_mode = SyncManager::MetadataMode::NoEncryption; + config.storage_path = util::make_temp_dir(); { - TestSyncManager tsm(config); - auto app = tsm.app(); + OfflineAppSession oas(config); + auto app = oas.app(); auto user = log_in(app); @@ -4785,8 +4771,9 @@ TEST_CASE("app: login_with_credentials unit_tests", "[sync][app][user]") { App::clear_cached_apps(); // assert everything is stored properly between runs { - TestSyncManager tsm(config); - auto app = tsm.app(); + config.delete_storage = true; // clean up after this session + OfflineAppSession oas(config); + auto app = oas.app(); REQUIRE(app->all_users().size() == 1); auto user = app->all_users()[0]; REQUIRE(user->identities().size() == 1); @@ -4820,8 +4807,8 @@ TEST_CASE("app: login_with_credentials unit_tests", "[sync][app][user]") { }; config.transport = instance_of; - TestSyncManager tsm(config); - auto error = failed_log_in(tsm.app()); + OfflineAppSession oas(config); + auto error = failed_log_in(oas.app()); CHECK(error.reason() == std::string("malformed JWT")); CHECK(error.code_string() == "BadToken"); CHECK(error.is_json_error()); @@ -4830,10 +4817,8 @@ TEST_CASE("app: login_with_credentials unit_tests", "[sync][app][user]") { SECTION("login_anonynous multiple users") { UnitTestTransport::access_token = good_access_token; - config.base_path = util::make_temp_dir(); - config.should_teardown_test_directory = false; - TestSyncManager tsm(config); - auto app = tsm.app(); + OfflineAppSession oas(config); + auto app = oas.app(); auto user1 = log_in(app); auto user2 = log_in(app, AppCredentials::anonymous(false)); @@ -4842,11 +4827,10 @@ TEST_CASE("app: login_with_credentials unit_tests", "[sync][app][user]") { } TEST_CASE("app: UserAPIKeyProviderClient unit_tests", "[sync][app][user][api key]") { - TestSyncManager sync_manager(get_config(), {}); - auto app = sync_manager.app(); - auto client = app->provider_client(); + OfflineAppSession oas({std::make_shared()}); + auto client = oas.app()->provider_client(); - auto logged_in_user = sync_manager.fake_user(); + auto logged_in_user = oas.make_user(); bool processed = false; ObjectId obj_id(UnitTestTransport::api_key_id.c_str()); @@ -4888,8 +4872,8 @@ TEST_CASE("app: UserAPIKeyProviderClient unit_tests", "[sync][app][user][api key TEST_CASE("app: user_semantics", "[sync][app][user]") { - TestSyncManager tsm(get_config(), {}); - auto app = tsm.app(); + OfflineAppSession oas(instance_of); + auto app = oas.app(); const auto login_user_email_pass = [=] { return log_in(app, AppCredentials::username_password("bob", "thompson")); @@ -5018,6 +5002,7 @@ TEST_CASE("app: user_semantics", "[sync][app][user]") { } } +namespace { struct ErrorCheckingTransport : public GenericNetworkTransport { ErrorCheckingTransport(Response* r) : m_response(r) @@ -5042,6 +5027,7 @@ struct ErrorCheckingTransport : public GenericNetworkTransport { private: Response* m_response; }; +} // namespace TEST_CASE("app: response error handling", "[sync][app]") { std::string response_body = nlohmann::json({{"access_token", good_access_token}, @@ -5052,8 +5038,8 @@ TEST_CASE("app: response error handling", "[sync][app]") { Response response{200, 0, {{"Content-Type", "text/plain"}}, response_body}; - TestSyncManager tsm(get_config(std::make_shared(&response))); - auto app = tsm.app(); + OfflineAppSession oas({std::make_shared(&response)}); + auto app = oas.app(); SECTION("http 404") { response.http_status_code = 404; @@ -5127,67 +5113,64 @@ TEST_CASE("app: response error handling", "[sync][app]") { } TEST_CASE("app: switch user", "[sync][app][user]") { - TestSyncManager tsm(get_config(), {}); - auto app = tsm.app(); + OfflineAppSession oas; + auto app = oas.app(); bool processed = false; SECTION("switch user expect success") { - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); // Log in user 1 auto user_a = log_in(app, AppCredentials::username_password("test@10gen.com", "password")); - CHECK(app->sync_manager()->get_current_user() == user_a); + CHECK(app->current_user() == user_a); // Log in user 2 auto user_b = log_in(app, AppCredentials::username_password("test2@10gen.com", "password")); - CHECK(app->sync_manager()->get_current_user() == user_b); + CHECK(app->current_user() == user_b); - CHECK(app->sync_manager()->all_users().size() == 2); + CHECK(app->all_users().size() == 2); - auto user1 = app->switch_user(user_a); - CHECK(user1 == user_a); + app->switch_user(user_a); + CHECK(app->current_user() == user_a); - CHECK(app->sync_manager()->get_current_user() == user_a); + app->switch_user(user_b); - auto user2 = app->switch_user(user_b); - CHECK(user2 == user_b); - - CHECK(app->sync_manager()->get_current_user() == user_b); + CHECK(app->current_user() == user_b); processed = true; CHECK(processed); } - SECTION("cannot switch to a logged out but not removed user") { - CHECK(app->sync_manager()->all_users().size() == 0); + SECTION("cannot switch to a logged out user") { + CHECK(app->all_users().size() == 0); // Log in user 1 auto user_a = log_in(app, AppCredentials::username_password("test@10gen.com", "password")); - CHECK(app->sync_manager()->get_current_user() == user_a); + CHECK(app->current_user() == user_a); app->log_out([&](Optional error) { REQUIRE_FALSE(error); }); - CHECK(app->sync_manager()->get_current_user() == nullptr); + CHECK(app->current_user() == nullptr); CHECK(user_a->state() == SyncUser::State::LoggedOut); // Log in user 2 auto user_b = log_in(app, AppCredentials::username_password("test2@10gen.com", "password")); - CHECK(app->sync_manager()->get_current_user() == user_b); - CHECK(app->sync_manager()->all_users().size() == 2); + CHECK(app->current_user() == user_b); + CHECK(app->all_users().size() == 2); REQUIRE_THROWS_AS(app->switch_user(user_a), AppError); - CHECK(app->sync_manager()->get_current_user() == user_b); + CHECK(app->current_user() == user_b); } } -TEST_CASE("app: remove anonymous user", "[sync][app][user]") { - TestSyncManager tsm(get_config(), {}); - auto app = tsm.app(); +TEST_CASE("app: remove user", "[sync][app][user]") { + OfflineAppSession oas; + auto app = oas.app(); - SECTION("remove user expect success") { - CHECK(app->sync_manager()->all_users().size() == 0); + SECTION("remove anonymous user") { + CHECK(app->all_users().size() == 0); // Log in user 1 auto user_a = log_in(app); @@ -5198,39 +5181,34 @@ TEST_CASE("app: remove anonymous user", "[sync][app][user]") { // a logged out anon user will be marked as Removed, not LoggedOut CHECK(user_a->state() == SyncUser::State::Removed); }); - CHECK(app->sync_manager()->all_users().empty()); + CHECK(app->all_users().empty()); app->remove_user(user_a, [&](Optional error) { CHECK(error->reason() == "User has already been removed"); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); }); // Log in user 2 auto user_b = log_in(app); - CHECK(app->sync_manager()->get_current_user() == user_b); + CHECK(app->current_user() == user_b); CHECK(user_b->state() == SyncUser::State::LoggedIn); - CHECK(app->sync_manager()->all_users().size() == 1); + CHECK(app->all_users().size() == 1); app->remove_user(user_b, [&](Optional error) { REQUIRE_FALSE(error); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); }); - CHECK(app->sync_manager()->get_current_user() == nullptr); + CHECK(app->current_user() == nullptr); // check both handles are no longer valid CHECK(user_a->state() == SyncUser::State::Removed); CHECK(user_b->state() == SyncUser::State::Removed); } -} - -TEST_CASE("app: remove user with credentials", "[sync][app][user]") { - TestSyncManager tsm(get_config(), {}); - auto app = tsm.app(); - SECTION("log in, log out and remove") { - CHECK(app->sync_manager()->all_users().size() == 0); - CHECK(app->sync_manager()->get_current_user() == nullptr); + SECTION("remove user with credentials") { + CHECK(app->all_users().size() == 0); + CHECK(app->current_user() == nullptr); auto user = log_in(app, AppCredentials::username_password("email", "pass")); @@ -5245,21 +5223,21 @@ TEST_CASE("app: remove user with credentials", "[sync][app][user]") { app->remove_user(user, [&](Optional error) { REQUIRE_FALSE(error); }); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); Optional error; app->remove_user(user, [&](Optional err) { error = err; }); CHECK(error->code() > 0); - CHECK(app->sync_manager()->all_users().size() == 0); + CHECK(app->all_users().size() == 0); CHECK(user->state() == SyncUser::State::Removed); } } TEST_CASE("app: link_user", "[sync][app][user]") { - TestSyncManager tsm(get_config(), {}); - auto app = tsm.app(); + OfflineAppSession oas; + auto app = oas.app(); auto email = util::format("realm_tests_do_autoverify%1@%2.com", random_string(10), random_string(10)); auto password = random_string(10); @@ -5395,13 +5373,12 @@ TEST_CASE("app: refresh access token unit tests", "[sync][app][user][token]") { } } }; - - TestSyncManager sync_manager(get_config(instance_of)); - auto app = sync_manager.app(); - auto user = sync_manager.fake_user(); + OfflineAppSession oas(OfflineAppSession::Config{instance_of}); + auto app = oas.app(); + oas.make_user(); bool processed = false; - app->refresh_custom_data(user, [&](const Optional& error) { + app->refresh_custom_data(app->current_user(), [&](const Optional& error) { REQUIRE_FALSE(error); CHECK(session_route_hit); processed = true; @@ -5427,12 +5404,12 @@ TEST_CASE("app: refresh access token unit tests", "[sync][app][user][token]") { } }; - TestSyncManager sync_manager(get_config(instance_of)); - auto app = sync_manager.app(); - auto user = sync_manager.fake_user(); + OfflineAppSession oas(OfflineAppSession::Config{instance_of}); + auto app = oas.app(); + oas.make_user(); bool processed = false; - app->refresh_custom_data(user, [&](const Optional& error) { + app->refresh_custom_data(app->current_user(), [&](const Optional& error) { CHECK(error->reason() == "malformed JWT"); CHECK(error->code() == ErrorCodes::BadToken); CHECK(session_route_hit); @@ -5449,53 +5426,42 @@ TEST_CASE("app: refresh access token unit tests", "[sync][app][user][token]") { Refresh token - get a new token for the user Get profile - get the profile with the new token */ - struct transport : GenericNetworkTransport { - bool login_hit = false; - bool get_profile_1_hit = false; - bool get_profile_2_hit = false; - bool refresh_hit = false; - + enum class TestState { unknown, location, login, profile_1, refresh, profile_2 }; + TestingStateMachine state{TestState::unknown}; void send_request_to_server(const Request& request, util::UniqueFunction&& completion) override { if (request.url.find("/login") != std::string::npos) { - login_hit = true; + CHECK(state.get() == TestState::location); + state.transition_to(TestState::login); completion({200, 0, {}, user_json(good_access_token).dump()}); } else if (request.url.find("/profile") != std::string::npos) { - CHECK(login_hit); - auto item = AppUtils::find_header("Authorization", request.headers); CHECK(item); auto access_token = item->second; // simulated bad token request if (access_token.find(good_access_token2) != std::string::npos) { - CHECK(login_hit); - CHECK(get_profile_1_hit); - CHECK(refresh_hit); - - get_profile_2_hit = true; - + CHECK(state.get() == TestState::refresh); + state.transition_to(TestState::profile_2); completion({200, 0, {}, user_profile_json().dump()}); } else if (access_token.find(good_access_token) != std::string::npos) { - CHECK(!get_profile_2_hit); - get_profile_1_hit = true; - + CHECK(state.get() == TestState::login); + state.transition_to(TestState::profile_1); completion({401, 0, {}}); } } else if (request.url.find("/session") != std::string::npos && request.method == HttpMethod::post) { - CHECK(login_hit); - CHECK(get_profile_1_hit); - CHECK(!get_profile_2_hit); - refresh_hit = true; - + CHECK(state.get() == TestState::profile_1); + state.transition_to(TestState::refresh); nlohmann::json json{{"access_token", good_access_token2}}; completion({200, 0, {}, json.dump()}); } else if (request.url.find("/location") != std::string::npos) { + CHECK(state.get() == TestState::unknown); + state.transition_to(TestState::location); CHECK(request.method == HttpMethod::get); completion({200, 0, @@ -5503,11 +5469,15 @@ TEST_CASE("app: refresh access token unit tests", "[sync][app][user][token]") { "{\"deployment_model\":\"GLOBAL\",\"location\":\"US-VA\",\"hostname\":" "\"http://localhost:9090\",\"ws_hostname\":\"ws://localhost:9090\"}"}); } + else { + FAIL("Unexpected request in test code" + request.url); + } } }; - TestSyncManager sync_manager(get_config(instance_of)); - REQUIRE(log_in(sync_manager.app())); + OfflineAppSession oas(OfflineAppSession::Config{instance_of}); + auto app = oas.app(); + REQUIRE(log_in(app)); } } @@ -5519,39 +5489,42 @@ class AsyncMockNetworkTransport { { } - void add_work_item(Response&& response, util::UniqueFunction&& completion) + ~AsyncMockNetworkTransport() { - std::lock_guard lk(transport_work_mutex); - transport_work.push_front(ResponseWorkItem{std::move(response), std::move(completion)}); + { + std::lock_guard lk(transport_work_mutex); + test_complete = true; + } transport_work_cond.notify_one(); + transport_thread.join(); + REALM_ASSERT(transport_work.empty()); } - void add_work_item(util::UniqueFunction cb) + void add_work_item(Response&& response, util::UniqueFunction&& completion) { - std::lock_guard lk(transport_work_mutex); - transport_work.push_front(std::move(cb)); + { + std::lock_guard lk(transport_work_mutex); + transport_work.push_front([response = std::move(response), completion = std::move(completion)] { + completion(response); + }); + } transport_work_cond.notify_one(); } - void mark_complete() + void add_work_item(util::UniqueFunction cb) { - std::unique_lock lk(transport_work_mutex); - test_complete = true; + { + std::lock_guard lk(transport_work_mutex); + transport_work.push_front(std::move(cb)); + } transport_work_cond.notify_one(); - lk.unlock(); - transport_thread.join(); } private: - struct ResponseWorkItem { - Response response; - util::UniqueFunction completion; - }; - void worker_routine() { - std::unique_lock lk(transport_work_mutex); for (;;) { + std::unique_lock lk(transport_work_mutex); transport_work_cond.wait(lk, [&] { return test_complete || !transport_work.empty(); }); @@ -5561,15 +5534,7 @@ class AsyncMockNetworkTransport { transport_work.pop_back(); lk.unlock(); - mpark::visit(util::overload{[](ResponseWorkItem& work_item) { - work_item.completion(std::move(work_item.response)); - }, - [](util::UniqueFunction& cb) { - cb(); - }}, - work_item); - - lk.lock(); + work_item(); continue; } @@ -5582,7 +5547,7 @@ class AsyncMockNetworkTransport { std::mutex transport_work_mutex; std::condition_variable transport_work_cond; bool test_complete = false; - std::list>> transport_work; + std::list> transport_work; JoiningThread transport_thread; }; @@ -5657,8 +5622,8 @@ TEST_CASE("app: app destroyed during token refresh", "[sync][app][user][token]") AsyncMockNetworkTransport& mock_transport_worker; TestingStateMachine& state; }; - TestSyncManager sync_manager(get_config(std::make_shared(mock_transport_worker, state))); - auto app = sync_manager.app(); + OfflineAppSession oas({std::make_shared(mock_transport_worker, state)}); + auto app = oas.app(); { auto [cur_user_promise, cur_user_future] = util::make_promise_future>(); @@ -5710,102 +5675,15 @@ TEST_CASE("app: app destroyed during token refresh", "[sync][app][user][token]") timed_wait_for([&] { return !app->sync_manager()->has_existing_sessions(); }); - - mock_transport_worker.mark_complete(); -} - -TEST_CASE("app: metadata is persisted between sessions", "[sync][app][metadata]") { - const auto orig_hostname = "proto://host:1234"; - const auto orig_ws_hostname = "wsproto://host:1234"; - - struct LocalTransport : UnitTestTransport { - void send_request_to_server(const Request& request, - util::UniqueFunction&& completion) override - { - if (request.url.find("/location") != std::string::npos) { - CHECK(request.method == HttpMethod::get); - completion({200, - 0, - {}, - nlohmann::json({{"deployment_model", "LOCAL"}, - {"location", "IE"}, - {"hostname", test_hostname}, - {"ws_hostname", test_ws_hostname}}) - .dump()}); - } - else if (request.url.find("functions/call") != std::string::npos) { - REQUIRE(request.url.rfind(test_hostname, 0) != std::string::npos); - } - else { - UnitTestTransport::send_request_to_server(request, std::move(completion)); - } - } - - void set_hostname(std::string hostname, std::string ws_hostname) - { - test_hostname = hostname; - test_ws_hostname = ws_hostname; - } - std::string test_hostname; - std::string test_ws_hostname; - }; - - auto transport = std::make_shared(); - transport->set_hostname(orig_hostname, orig_ws_hostname); - - TestSyncManager::Config config = get_config(transport); - config.base_path = util::make_temp_dir(); - config.should_teardown_test_directory = false; - config.metadata_mode = SyncManager::MetadataMode::NoEncryption; - - { - TestSyncManager sync_manager(config, {}); - auto app = sync_manager.app(); - - // This is single threaded - app->log_in_with_credentials(AppCredentials::anonymous(), [](auto, auto error) { - REQUIRE_FALSE(error); - }); - // Sync route is updated during first request - REQUIRE(app->sync_manager()->sync_route()); - REQUIRE(app->sync_manager()->sync_route()->rfind(orig_ws_hostname, 0) != std::string::npos); - } - - config.override_sync_route = false; - config.should_teardown_test_directory = true; - { - TestSyncManager sync_manager(config); - auto app = sync_manager.app(); - - std::string base_url = sync_manager.sync_server().base_url(); - std::string ws_url = base_url; - size_t uri_scheme_start = ws_url.find("http"); - if (uri_scheme_start == 0) - ws_url.replace(uri_scheme_start, 4, "ws"); - - transport->set_hostname(base_url, ws_url); - - REQUIRE(!app->sync_manager()->sync_route()); // sync route is null to force location update on sync startup - - app->call_function("function", {}, [](auto error, auto) { - REQUIRE_FALSE(error); - }); - - REQUIRE(app->sync_manager()->sync_route()); // sync route is updated after operation - REQUIRE(app->sync_manager()->sync_route()->rfind(ws_url, 0) != std::string::npos); - } } TEST_CASE("app: make_streaming_request", "[sync][app][streaming]") { UnitTestTransport::access_token = good_access_token; + constexpr uint64_t timeout_ms = 60000; // this is the default + OfflineAppSession oas({std::make_shared(timeout_ms)}); + auto app = oas.app(); - constexpr uint64_t timeout_ms = 60000; - auto config = get_config(); - config.app_config.default_request_timeout_ms = timeout_ms; - TestSyncManager tsm(config); - auto app = tsm.app(); - - std::shared_ptr user = log_in(app); + auto user = log_in(app); using Headers = decltype(Request().headers); @@ -5907,10 +5785,9 @@ TEST_CASE("app: sync_user_profile unit tests", "[sync][app][user]") { } } -#if 0 TEST_CASE("app: app cannot get deallocated during log in", "[sync][app]") { AsyncMockNetworkTransport mock_transport_worker; - enum class TestState { unknown, location, login, app_deallocated, profile }; + enum class TestState { unknown, app_released }; TestingStateMachine state(TestState::unknown); struct transport : public GenericNetworkTransport { transport(AsyncMockNetworkTransport& worker, TestingStateMachine& state) @@ -5922,20 +5799,17 @@ TEST_CASE("app: app cannot get deallocated during log in", "[sync][app]") { void send_request_to_server(const Request& request, util::UniqueFunction&& completion) override { if (request.url.find("/login") != std::string::npos) { - state.transition_to(TestState::login); - state.wait_for(TestState::app_deallocated); + state.wait_for(TestState::app_released); mock_transport_worker.add_work_item( Response{200, 0, {}, user_json(encode_fake_jwt("access token")).dump()}, std::move(completion)); } else if (request.url.find("/profile") != std::string::npos) { - state.transition_to(TestState::profile); mock_transport_worker.add_work_item(Response{200, 0, {}, user_profile_json().dump()}, std::move(completion)); } else if (request.url.find("/location") != std::string::npos) { CHECK(request.method == HttpMethod::get); - state.transition_to(TestState::location); mock_transport_worker.add_work_item( Response{200, 0, @@ -5954,38 +5828,37 @@ TEST_CASE("app: app cannot get deallocated during log in", "[sync][app]") { auto transporter = std::make_shared(mock_transport_worker, state); { - TestSyncManager sync_manager(get_config(transporter)); - auto app = sync_manager.app(); - - app->log_in_with_credentials(AppCredentials::anonymous(), - [promise = std::move(cur_user_promise)](std::shared_ptr user, - util::Optional error) mutable { - REQUIRE_FALSE(error); - promise.emplace_value(std::move(user)); - }); + OfflineAppSession oas({transporter}); + oas.app()->log_in_with_credentials( + AppCredentials::anonymous(), [promise = std::move(cur_user_promise)]( + std::shared_ptr user, util::Optional error) mutable { + REQUIRE_FALSE(error); + promise.emplace_value(std::move(user)); + }); } - // At this point the test does not hold any reference to `app`. - state.transition_to(TestState::app_deallocated); + // At this point the test does not hold any reference to `app`, but the + // app is keeping itself alive + state.transition_to(TestState::app_released); auto cur_user = std::move(cur_user_future).get(); CHECK(cur_user); - - mock_transport_worker.mark_complete(); } -#endif TEST_CASE("app: user logs out while profile is fetched", "[sync][app][user]") { AsyncMockNetworkTransport mock_transport_worker; - enum class TestState { unknown, location, login, profile }; + enum class TestState { unknown, location, login, logout, create, profile }; TestingStateMachine state(TestState::unknown); struct transport : public GenericNetworkTransport { - transport(AsyncMockNetworkTransport& worker, TestingStateMachine& state, - std::shared_ptr& logged_in_user) + transport(AsyncMockNetworkTransport& worker, TestingStateMachine& state) : mock_transport_worker(worker) , state(state) - , logged_in_user(logged_in_user) { } + void set_app(App* app) + { + REQUIRE(state.get() == TestState::unknown); + m_app = app; + } void send_request_to_server(const Request& request, util::UniqueFunction&& completion) override @@ -5996,7 +5869,10 @@ TEST_CASE("app: user logs out while profile is fetched", "[sync][app][user]") { Response{200, 0, {}, user_json(encode_fake_jwt("access token")).dump()}, std::move(completion)); } else if (request.url.find("/profile") != std::string::npos) { - logged_in_user->log_out(); + REQUIRE(m_app); + auto user = m_app->current_user(); + REQUIRE(user); + user->log_out(); state.transition_to(TestState::profile); mock_transport_worker.add_work_item(Response{200, 0, {}, user_profile_json().dump()}, std::move(completion)); @@ -6012,46 +5888,53 @@ TEST_CASE("app: user logs out while profile is fetched", "[sync][app][user]") { "\"http://localhost:9090\",\"ws_hostname\":\"ws://localhost:9090\"}"}, std::move(completion)); } + else if (request.url.find("/session") != std::string::npos) { + CHECK(request.method == HttpMethod::del); + state.transition_to(TestState::logout); + mock_transport_worker.add_work_item(Response{200, 0, {}, ""}, std::move(completion)); + } + else { + FAIL("Unexpected request in test transport " + request.url); + } } AsyncMockNetworkTransport& mock_transport_worker; TestingStateMachine& state; - std::shared_ptr& logged_in_user; + App* m_app; }; - std::shared_ptr logged_in_user; - auto transporter = std::make_shared(mock_transport_worker, state, logged_in_user); - - TestSyncManager sync_manager(get_config(transporter)); - auto app = sync_manager.app(); + auto transporter = std::make_shared(mock_transport_worker, state); + OfflineAppSession oas({transporter}); + auto app = oas.app(); + transporter->set_app(app.get()); - logged_in_user = app->sync_manager()->get_user("userid", good_access_token, good_access_token, dummy_device_id); auto custom_credentials = AppCredentials::facebook("a_token"); auto [cur_user_promise, cur_user_future] = util::make_promise_future>(); - app->link_user(logged_in_user, custom_credentials, - [promise = std::move(cur_user_promise)](std::shared_ptr user, - util::Optional error) mutable { - REQUIRE_FALSE(error); - promise.emplace_value(std::move(user)); - }); + app->log_in_with_credentials(custom_credentials, + [promise = std::move(cur_user_promise)](std::shared_ptr user, + util::Optional error) mutable { + REQUIRE_FALSE(error); + promise.emplace_value(std::move(user)); + }); auto cur_user = std::move(cur_user_future).get(); CHECK(state.get() == TestState::profile); CHECK(cur_user); - CHECK(cur_user == logged_in_user); - - mock_transport_worker.mark_complete(); + CHECK(cur_user->state() == SyncUser::State::LoggedOut); + REQUIRE(app->all_users().size() == 1); + CHECK(app->all_users()[0] == cur_user); } TEST_CASE("app: shared instances", "[sync][app]") { + test_util::TestDirGuard test_dir(util::make_temp_dir(), false); + App::Config base_config; set_app_config_defaults(base_config, instance_of); SyncClientConfig sync_config; sync_config.metadata_mode = SyncClientConfig::MetadataMode::NoMetadata; - sync_config.base_file_path = util::make_temp_dir() + random_string(10); - util::try_make_dir(sync_config.base_file_path); + sync_config.base_file_path = test_dir; auto config1 = base_config; config1.app_id = "app1"; diff --git a/test/object-store/sync/client_reset.cpp b/test/object-store/sync/client_reset.cpp index a36c91059d1..f45a3a95707 100644 --- a/test/object-store/sync/client_reset.cpp +++ b/test/object-store/sync/client_reset.cpp @@ -1946,9 +1946,8 @@ TEMPLATE_TEST_CASE("client reset types", "[sync][pbs][client reset]", cf::MixedV if (!util::EventLoop::has_implementation()) return; - TestSyncManager init_sync_manager; - SyncTestFile config(init_sync_manager.app(), "default"); - config.cache = false; + OfflineAppSession oas; + SyncTestFile config(oas, "default"); config.automatic_change_notifications = false; ClientResyncMode test_mode = GENERATE(ClientResyncMode::DiscardLocal, ClientResyncMode::Recover); CAPTURE(test_mode); @@ -1967,7 +1966,7 @@ TEMPLATE_TEST_CASE("client reset types", "[sync][pbs][client reset]", cf::MixedV {"set", PropertyType::Set | TestType::property_type}}}, }; - SyncTestFile config2(init_sync_manager.app(), "default"); + SyncTestFile config2(oas.app()->current_user(), "default"); config2.schema = config.schema; Results results; @@ -2623,16 +2622,15 @@ TEMPLATE_TEST_CASE("client reset collections of links", "[sync][pbs][client rese }}, }; - TestSyncManager init_sync_manager; - SyncTestFile config(init_sync_manager.app(), "default"); - config.cache = false; + OfflineAppSession oas; + SyncTestFile config(oas, "default"); config.automatic_change_notifications = false; config.schema = schema; ClientResyncMode test_mode = GENERATE(ClientResyncMode::DiscardLocal, ClientResyncMode::Recover); CAPTURE(test_mode); config.sync_config->client_resync_mode = test_mode; - SyncTestFile config2(init_sync_manager.app(), "default"); + SyncTestFile config2(oas.app()->current_user(), "default"); config2.schema = schema; std::unique_ptr test_reset = @@ -3140,9 +3138,8 @@ TEST_CASE("client reset with embedded object", "[sync][pbs][client reset][embedd if (!util::EventLoop::has_implementation()) return; - TestSyncManager init_sync_manager; - SyncTestFile config(init_sync_manager.app(), "default"); - config.cache = false; + OfflineAppSession oas; + SyncTestFile config(oas, "default"); config.automatic_change_notifications = false; ClientResyncMode test_mode = GENERATE(ClientResyncMode::DiscardLocal, ClientResyncMode::Recover); CAPTURE(test_mode); @@ -3488,7 +3485,7 @@ TEST_CASE("client reset with embedded object", "[sync][pbs][client reset][embedd } }; - SyncTestFile config2(init_sync_manager.app(), "default"); + SyncTestFile config2(oas.app()->current_user(), "default"); config2.schema = config.schema; std::unique_ptr test_reset = @@ -4280,7 +4277,7 @@ TEST_CASE("client reset with embedded object", "[sync][pbs][client reset][embedd reset_embedded_object({local}, {remote}, expected_recovered); } SECTION("server adds embedded object classes") { - SyncTestFile config2(init_sync_manager.app(), "default"); + SyncTestFile config2(oas.app()->current_user(), "default"); config2.schema = config.schema; config.schema = Schema{shared_class}; test_reset = reset_utils::make_fake_local_client_reset(config, config2); @@ -4305,7 +4302,7 @@ TEST_CASE("client reset with embedded object", "[sync][pbs][client reset][embedd ->run(); } SECTION("client adds embedded object classes") { - SyncTestFile config2(init_sync_manager.app(), "default"); + SyncTestFile config2(oas.app()->current_user(), "default"); config2.schema = Schema{shared_class}; test_reset = reset_utils::make_fake_local_client_reset(config, config2); TopLevelContent local_content; diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index c0050fe7b6f..d41a0f87f09 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -2218,7 +2218,6 @@ TEST_CASE("flx: interrupted bootstrap restarts/recovers on reconnect", "[sync][f std::vector obj_ids_at_end = fill_large_array_schema(harness); SyncTestFile interrupted_realm_config(harness.app()->current_user(), harness.schema(), SyncConfig::FLXSyncEnabled{}); - interrupted_realm_config.cache = false; { auto [interrupted_promise, interrupted] = util::make_promise_future(); @@ -2742,7 +2741,6 @@ TEST_CASE("flx: bootstrap batching prevents orphan documents", "[sync][flx][boot std::vector obj_ids_at_end = fill_large_array_schema(harness); SyncTestFile interrupted_realm_config(harness.app()->current_user(), harness.schema(), SyncConfig::FLXSyncEnabled{}); - interrupted_realm_config.cache = false; auto check_interrupted_state = [&](const DBRef& realm) { auto tr = realm->start_read(); @@ -4053,7 +4051,6 @@ TEST_CASE("flx: compensating write errors get re-sent across sessions", "[sync][ create_user_and_log_in(harness.app()); SyncTestFile config(harness.app()->current_user(), harness.schema(), SyncConfig::FLXSyncEnabled{}); - config.cache = false; { auto error_received_pf = util::make_promise_future(); @@ -4630,7 +4627,6 @@ TEST_CASE("flx sync: resend pending subscriptions when reconnecting", "[sync][fl std::vector obj_ids_at_end = fill_large_array_schema(harness); SyncTestFile interrupted_realm_config(harness.app()->current_user(), harness.schema(), SyncConfig::FLXSyncEnabled{}); - interrupted_realm_config.cache = false; { auto pf = util::make_promise_future(); diff --git a/test/object-store/sync/session/session.cpp b/test/object-store/sync/session/session.cpp index 3d2107ebb18..ad2a3e6221c 100644 --- a/test/object-store/sync/session/session.cpp +++ b/test/object-store/sync/session/session.cpp @@ -458,8 +458,6 @@ TEST_CASE("sync: error handling", "[sync][session]") { CHECK(idx != std::string::npos); idx = recovery_path.find(tsm.sync_manager()->recovery_directory_path()); CHECK(idx != std::string::npos); - idx = recovery_path.find(tsm.app()->config().app_id); - CHECK(idx != std::string::npos); if (just_before.tm_year == just_after.tm_year) { idx = recovery_path.find(util::format_local_time(just_after_raw, "%Y")); CHECK(idx != std::string::npos); diff --git a/test/object-store/sync/session/wait_for_completion.cpp b/test/object-store/sync/session/wait_for_completion.cpp index c4fbb576b4e..ae2923b5eae 100644 --- a/test/object-store/sync/session/wait_for_completion.cpp +++ b/test/object-store/sync/session/wait_for_completion.cpp @@ -137,7 +137,6 @@ TEST_CASE("SyncSession: wait_for_upload_completion() API", "[sync][pbs][session] SECTION("works properly when called on a logged-out session") { server.start(); - const auto user_id = "user-async-wait-upload-3"; auto user = tsm.fake_user(); auto session = sync_session(user, "/user-async-wait-upload-3", [](auto, auto) {}); EventLoop::main().run_until([&] { diff --git a/test/object-store/sync/sync_manager.cpp b/test/object-store/sync/sync_manager.cpp index ea07c3403ac..57bb908cbc1 100644 --- a/test/object-store/sync/sync_manager.cpp +++ b/test/object-store/sync/sync_manager.cpp @@ -35,7 +35,8 @@ using namespace realm; using namespace realm::util; using File = realm::util::File; -static const auto base_path = fs::path{util::make_temp_dir()}.make_preferred() / "realm_objectstore_sync_manager.test-dir"; +static const auto base_path = + fs::path{util::make_temp_dir()}.make_preferred() / "realm_objectstore_sync_manager.test-dir"; static const std::string dummy_device_id = "123400000000000000000000"; namespace { @@ -54,37 +55,22 @@ bool validate_user_in_vector(std::vector> vector, cons } // anonymous namespace TEST_CASE("sync_manager: basic properties and APIs", "[sync][sync manager]") { - TestSyncManager init_sync_manager; - auto app = init_sync_manager.app(); + TestSyncManager tsm; SECTION("should not crash on 'reconnect()'") { - app->sync_manager()->reconnect(); + tsm.sync_manager()->reconnect(); } } TEST_CASE("sync_manager: `path_for_realm` API", "[sync][sync manager]") { const std::string raw_url = "realms://realm.example.org/a/b/~/123456/xyz"; - SECTION("should work properly without metadata") { + SECTION("should work properly") { TestSyncManager tsm; auto user = tsm.fake_user(); - auto base_path = fs::path{tsm.base_file_path()}.make_preferred() / "mongodb-realm" / "app_id" / user->identity(); - const auto expected = base_path / "realms%3A%2F%2Frealm.example.org%2Fa%2Fb%2F%7E%2F123456%2Fxyz.realm"; - SyncConfig config(user, bson::Bson{}); - REQUIRE(tsm.sync_manager()->path_for_realm(config, raw_url) == expected); - // This API should also generate the directory if it doesn't already exist. - REQUIRE_DIR_PATH_EXISTS(base_path); - } - - SECTION("should work properly with metadata") { - TestSyncManager tsm(SyncManager::MetadataMode::NoEncryption); - auto sync_manager = tsm.sync_manager(); - const std::string identity = random_string(10); - auto base_path = fs::path{tsm.base_file_path()}.make_preferred() / "mongodb-realm" / "app_id" / identity; + auto base_path = + fs::path{tsm.base_file_path()}.make_preferred() / "mongodb-realm" / "app_id" / user->identity(); const auto expected = base_path / "realms%3A%2F%2Frealm.example.org%2Fa%2Fb%2F%7E%2F123456%2Fxyz.realm"; - auto user = tsm.sync_manager()->get_user(identity, ENCODE_FAKE_JWT("dummy_token"), - ENCODE_FAKE_JWT("not_a_real_token"), dummy_device_id); - REQUIRE(user->identity() == identity); SyncConfig config(user, bson::Bson{}); REQUIRE(tsm.sync_manager()->path_for_realm(config, raw_url) == expected); // This API should also generate the directory if it doesn't already exist. @@ -95,7 +81,8 @@ TEST_CASE("sync_manager: `path_for_realm` API", "[sync][sync manager]") { TestSyncManager tsm; auto sync_manager = tsm.sync_manager(); auto user = tsm.fake_user(); - auto base_path = fs::path{tsm.base_file_path()}.make_preferred() / "mongodb-realm" / "app_id" / user->identity(); + auto base_path = + fs::path{tsm.base_file_path()}.make_preferred() / "mongodb-realm" / "app_id" / user->identity(); // Directory should not be created until we get the path REQUIRE_DIR_PATH_DOES_NOT_EXIST(base_path); @@ -194,7 +181,7 @@ TEST_CASE("sync_manager: `path_for_realm` API", "[sync][sync manager]") { } TEST_CASE("sync_manager: user state management", "[sync][sync manager]") { - TestSyncManager init_sync_manager(SyncManager::MetadataMode::NoEncryption); + TestSyncManager init_sync_manager; auto sync_manager = init_sync_manager.sync_manager(); const std::string r_token_1 = ENCODE_FAKE_JWT("foo_token"); @@ -278,12 +265,11 @@ TEST_CASE("sync_manager: user state management", "[sync][sync manager]") { TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager]") { TestSyncManager::Config config; - auto app_id = config.app_config.app_id = "app_id-" + random_string(10); config.metadata_mode = SyncManager::MetadataMode::NoEncryption; TestSyncManager tsm(config); config.base_path = tsm.base_file_path(); config.should_teardown_test_directory = false; - auto file_manager = SyncFileManager(tsm.base_file_path(), app_id); + auto file_manager = SyncFileManager(tsm.base_file_path(), "app_id"); // Open the metadata separately, so we can investigate it ourselves. SyncMetadataManager manager(file_manager.metadata_path(), false); @@ -316,9 +302,8 @@ TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager REQUIRE(manager.all_unmarked_users().size() == 4); SECTION("they should be added to the active users list when metadata is enabled") { - config.metadata_mode = SyncManager::MetadataMode::NoEncryption; - TestSyncManager tsm(config); - auto users = tsm.sync_manager()->all_users(); + TestSyncManager tsm2(config); + auto users = tsm2.sync_manager()->all_users(); REQUIRE(users.size() == 3); REQUIRE(validate_user_in_vector(users, identity_1, r_token_1, a_token_1, dummy_device_id)); REQUIRE(validate_user_in_vector(users, identity_2, r_token_2, a_token_2, dummy_device_id)); @@ -327,8 +312,8 @@ TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager SECTION("they should not be added to the active users list when metadata is disabled") { config.metadata_mode = SyncManager::MetadataMode::NoMetadata; - TestSyncManager tsm(config); - auto users = tsm.sync_manager()->all_users(); + TestSyncManager tsm2(config); + auto users = tsm2.sync_manager()->all_users(); REQUIRE(users.size() == 0); } } @@ -358,7 +343,7 @@ TEST_CASE("sync_manager: persistent user state management", "[sync][sync manager { auto expected_u1_path = [&](const bson::Bson& partition) { - return ExpectedRealmPaths(tsm.base_file_path(), app_id, u1->identity(), u1->legacy_identities(), + return ExpectedRealmPaths(tsm.base_file_path(), "app_id", u1->identity(), u1->legacy_identities(), partition.to_string()); }; bson::Bson partition = "partition1"; @@ -460,12 +445,11 @@ TEST_CASE("sync_manager: file actions", "[sync][sync manager]") { using Action = SyncFileActionMetadata::Action; - auto file_manager = SyncFileManager(base_path.string(), "bar_app_id"); + auto file_manager = SyncFileManager(base_path.string(), "app_id"); // Open the metadata separately, so we can investigate it ourselves. SyncMetadataManager manager(file_manager.metadata_path(), false); TestSyncManager::Config config; - config.app_config.app_id = "bar_app_id"; config.base_path = base_path.string(); config.metadata_mode = SyncManager::MetadataMode::NoEncryption; config.should_teardown_test_directory = false; @@ -719,15 +703,13 @@ TEST_CASE("sync_manager: file actions", "[sync][sync manager]") { TEST_CASE("sync_manager: set_session_multiplexing", "[sync][sync manager]") { TestSyncManager::Config tsm_config; tsm_config.start_sync_client = false; - TestSyncManager tsm(std::move(tsm_config)); + TestSyncManager tsm(tsm_config); bool sync_multiplexing_allowed = GENERATE(true, false); auto sync_manager = tsm.sync_manager(); sync_manager->set_session_multiplexing(sync_multiplexing_allowed); - auto user_1 = sync_manager->get_user("user-name-1", ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("samesies"), dummy_device_id); - auto user_2 = sync_manager->get_user("user-name-2", ENCODE_FAKE_JWT("not_a_real_token"), - ENCODE_FAKE_JWT("samesies"), dummy_device_id); + auto user_1 = tsm.fake_user("user 1"); + auto user_2 = tsm.fake_user("user 2"); SyncTestFile file_1(user_1, "partition1", util::none); SyncTestFile file_2(user_1, "partition2", util::none); @@ -770,8 +752,7 @@ TEST_CASE("sync_manager: has_existing_sessions", "[sync][sync manager][active se std::atomic error_handler_invoked(false); Realm::Config config; - auto user = sync_manager->get_user("user-name", ENCODE_FAKE_JWT("not_a_real_token"), ENCODE_FAKE_JWT("samesies"), - dummy_device_id); + auto user = init_sync_manager.fake_user(); auto create_session = [&](SyncSessionStopPolicy stop_policy) { std::shared_ptr session = sync_session( user, "/test-dying-state", diff --git a/test/object-store/sync/user.cpp b/test/object-store/sync/user.cpp index 058b0914b34..57c0189d258 100644 --- a/test/object-store/sync/user.cpp +++ b/test/object-store/sync/user.cpp @@ -180,9 +180,11 @@ TEST_CASE("sync_user: logout", "[sync][user]") { } TEST_CASE("sync_user: user persistence", "[sync][user]") { - TestSyncManager tsm(SyncManager::MetadataMode::NoEncryption); + TestSyncManager::Config tsm_config; + tsm_config.metadata_mode = SyncManager::MetadataMode::NoEncryption; + TestSyncManager tsm(tsm_config); auto sync_manager = tsm.sync_manager(); - auto file_manager = SyncFileManager(tsm.base_file_path(), tsm.app()->config().app_id); + auto file_manager = SyncFileManager(tsm.base_file_path(), "app_id"); // Open the metadata separately, so we can investigate it ourselves. SyncMetadataManager manager(file_manager.metadata_path(), false); diff --git a/test/object-store/thread_safe_reference.cpp b/test/object-store/thread_safe_reference.cpp index 883154152bc..89532d69d7b 100644 --- a/test/object-store/thread_safe_reference.cpp +++ b/test/object-store/thread_safe_reference.cpp @@ -70,10 +70,8 @@ TEST_CASE("thread safe reference") { TestFile config; config.automatic_change_notifications = false; - config.cache = false; - config.in_memory = true; - config.encryption_key = std::vector(); config.schema = schema; + config.in_memory = true; auto r = Realm::get_shared_realm(config); const auto int_obj_col = r->schema().find("int object")->persisted_properties[0].column_key; diff --git a/test/object-store/util/sync/sync_test_utils.hpp b/test/object-store/util/sync/sync_test_utils.hpp index 4eff1d1dc2d..7474e8c47b2 100644 --- a/test/object-store/util/sync/sync_test_utils.hpp +++ b/test/object-store/util/sync/sync_test_utils.hpp @@ -135,14 +135,6 @@ const std::shared_ptr instance_of = std::make_shar std::ostream& operator<<(std::ostream& os, util::Optional error); -template -TestSyncManager::Config get_config(Transport&& transport) -{ - TestSyncManager::Config config; - config.transport = transport; - return config; -} - void subscribe_to_all_and_bootstrap(Realm& realm); #if REALM_ENABLE_AUTH_TESTS diff --git a/test/object-store/util/test_file.cpp b/test/object-store/util/test_file.cpp index 54544c0ee52..3402204ee72 100644 --- a/test/object-store/util/test_file.cpp +++ b/test/object-store/util/test_file.cpp @@ -132,13 +132,18 @@ static const std::string fake_refresh_token = ENCODE_FAKE_JWT("not_a_real_token" static const std::string fake_access_token = ENCODE_FAKE_JWT("also_not_real"); static const std::string fake_device_id = "123400000000000000000000"; -static std::shared_ptr get_fake_user(app::App& app, const std::string& user_name) +static std::shared_ptr get_fake_user(SyncManager& sync_manager, const std::string& user_name) { - return app.sync_manager()->get_user(user_name, fake_refresh_token, fake_access_token, fake_device_id); + return sync_manager.get_user(user_name, fake_refresh_token, fake_access_token, fake_device_id); } -SyncTestFile::SyncTestFile(std::shared_ptr app, std::string name, std::string user_name) - : SyncTestFile(get_fake_user(*app, user_name), bson::Bson(name)) +SyncTestFile::SyncTestFile(TestSyncManager& tsm, std::string name, std::string user_name) + : SyncTestFile(tsm.fake_user(user_name), bson::Bson(name)) +{ +} + +SyncTestFile::SyncTestFile(OfflineAppSession& oas, std::string name) + : SyncTestFile(oas.make_user(), bson::Bson(name)) { } @@ -365,7 +370,7 @@ TestAppSession::~TestAppSession() { if (util::File::exists(m_base_file_path)) { try { - m_app->sync_manager()->reset_for_testing(); + m_app->sync_manager()->tear_down_for_testing(); util::try_remove_dir_recursive(m_base_file_path); } catch (const std::exception& ex) { @@ -424,28 +429,19 @@ std::vector TestAppSession::get_documents(SyncUser& user, co // MARK: - TestSyncManager TestSyncManager::TestSyncManager(const Config& config, const SyncServer::Config& sync_server_config) - : transport(config.transport ? config.transport : std::make_shared(network_callback)) - , m_sync_server(sync_server_config) + : m_sync_server(sync_server_config) + , m_base_file_path(config.base_path.empty() ? util::make_temp_dir() : config.base_path) , m_should_teardown_test_directory(config.should_teardown_test_directory) { - app::App::Config app_config = config.app_config; - set_app_config_defaults(app_config, transport); + util::try_make_dir(m_base_file_path); util::Logger::set_default_level_threshold(config.log_level); - SyncClientConfig sc_config; - m_base_file_path = config.base_path.empty() ? util::make_temp_dir() + random_string(10) : config.base_path; - util::try_make_dir(m_base_file_path); sc_config.base_file_path = m_base_file_path; sc_config.metadata_mode = config.metadata_mode; - - m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config, sc_config); - if (config.override_sync_route) { - m_app->sync_manager()->set_sync_route(m_sync_server.base_url() + "/realm-sync"); - } + m_sync_manager = SyncManager::create(nullptr, m_sync_server.base_url() + "/realm-sync", sc_config, "app_id"); if (config.start_sync_client) { - // initialize sync client - m_app->sync_manager()->get_sync_client(); + m_sync_manager->get_sync_client(); } } @@ -454,7 +450,7 @@ TestSyncManager::~TestSyncManager() if (m_should_teardown_test_directory) { if (!m_base_file_path.empty() && util::File::exists(m_base_file_path)) { try { - m_app->sync_manager()->reset_for_testing(); + m_sync_manager->tear_down_for_testing(); util::try_remove_dir_recursive(m_base_file_path); } catch (const std::exception& ex) { @@ -467,7 +463,63 @@ TestSyncManager::~TestSyncManager() std::shared_ptr TestSyncManager::fake_user(const std::string& name) { - return get_fake_user(*m_app, name); + return get_fake_user(*m_sync_manager, name); +} + +OfflineAppSession::Config::Config(std::shared_ptr t) + : transport(t) +{ +} + +OfflineAppSession::OfflineAppSession(OfflineAppSession::Config config) + : m_transport(std::move(config.transport)) + , m_delete_storage(config.delete_storage) +{ + REALM_ASSERT(m_transport); + if (config.storage_path) { + m_base_file_path = *config.storage_path; + util::try_make_dir(m_base_file_path); + } + else { + m_base_file_path = util::make_temp_dir(); + } + + app::App::Config app_config; + set_app_config_defaults(app_config, m_transport); + if (config.base_url) { + app_config.base_url = *config.base_url; + } + if (config.app_id) { + app_config.app_id = *config.app_id; + } + + SyncClientConfig sc_config; + sc_config.base_file_path = m_base_file_path; + sc_config.metadata_mode = config.metadata_mode; + sc_config.socket_provider = config.socket_provider; + + util::Logger::set_default_level_threshold(realm::util::Logger::Level::TEST_LOGGING_LEVEL); + + m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config, sc_config); +} + +OfflineAppSession::~OfflineAppSession() +{ + if (util::File::exists(m_base_file_path) && m_delete_storage) { + try { + m_app->sync_manager()->tear_down_for_testing(); + util::try_remove_dir_recursive(m_base_file_path); + } + catch (const std::exception& ex) { + std::cerr << ex.what() << "\n"; + } + app::App::clear_cached_apps(); + } +} + +std::shared_ptr OfflineAppSession::make_user() const +{ + return get_fake_user(*m_app->sync_manager(), "test user"); } #endif // REALM_ENABLE_SYNC diff --git a/test/object-store/util/test_file.hpp b/test/object-store/util/test_file.hpp index a657802f27c..1c7870559cf 100644 --- a/test/object-store/util/test_file.hpp +++ b/test/object-store/util/test_file.hpp @@ -20,24 +20,22 @@ #define REALM_TEST_UTIL_TEST_FILE_HPP #include -#include - #include -#include - -#include +#include #if REALM_ENABLE_SYNC -#include -#include -#include #include "test_utils.hpp" +#include "unit_test_transport.hpp" +#include +#include #include +#include #include - #endif // REALM_ENABLE_SYNC +#include + #ifndef TEST_TIMEOUT_EXTRA #define TEST_TIMEOUT_EXTRA 0 #endif @@ -162,6 +160,7 @@ class SyncServer : private realm::sync::Clock { } }; +class OfflineAppSession; struct SyncTestFile : TestFile { template SyncTestFile(const realm::SyncConfig& sync_config, realm::SyncSessionStopPolicy stop_policy, @@ -173,8 +172,8 @@ struct SyncTestFile : TestFile { schema_mode = realm::SchemaMode::AdditiveExplicit; } - SyncTestFile(std::shared_ptr app = nullptr, std::string name = "", - std::string user_name = "test"); + SyncTestFile(TestSyncManager&, std::string name = "", std::string user_name = "test"); + SyncTestFile(OfflineAppSession&, std::string name = ""); SyncTestFile(std::shared_ptr user, realm::bson::Bson partition, realm::util::Optional schema = realm::util::none); SyncTestFile(std::shared_ptr user, realm::bson::Bson partition, @@ -184,118 +183,120 @@ struct SyncTestFile : TestFile { SyncTestFile(std::shared_ptr user, realm::Schema schema, realm::SyncConfig::FLXSyncEnabled); }; -#if REALM_ENABLE_AUTH_TESTS -using DeleteApp = realm::util::TaggedBool; -class TestAppSession { +class TestSyncManager { public: - TestAppSession(); - TestAppSession(realm::AppSession, std::shared_ptr = nullptr, - DeleteApp = true, realm::ReconnectMode reconnect_mode = realm::ReconnectMode::normal, - std::shared_ptr custom_socket_provider = nullptr); - ~TestAppSession(); + struct Config { + Config() {} + std::string base_path; + realm::SyncManager::MetadataMode metadata_mode = realm::SyncManager::MetadataMode::NoMetadata; + bool should_teardown_test_directory = true; + realm::util::Logger::Level log_level = realm::util::Logger::Level::TEST_LOGGING_LEVEL; + bool start_sync_client = true; + }; - std::shared_ptr app() const noexcept - { - return m_app; - } - const realm::AppSession& app_session() const noexcept + TestSyncManager(const Config& = Config(), const SyncServer::Config& = {}); + ~TestSyncManager(); + + std::string base_file_path() const { - return *m_app_session; + return m_base_file_path; } - realm::app::GenericNetworkTransport* transport() + SyncServer& sync_server() { - return m_transport.get(); + return m_sync_server; } - const std::shared_ptr& sync_manager() const + const std::shared_ptr& sync_manager() { - return m_app->sync_manager(); + return m_sync_manager; } - std::vector get_documents(realm::SyncUser& user, const std::string& object_type, - size_t expected_count) const; + std::shared_ptr fake_user(const std::string& name = "test"); private: - std::shared_ptr m_app; - std::unique_ptr m_app_session; + std::shared_ptr m_sync_manager; + SyncServer m_sync_server; std::string m_base_file_path; - bool m_delete_app = true; - std::shared_ptr m_transport; + bool m_should_teardown_test_directory = true; }; -#endif -class TestSyncManager { +class OfflineAppSession { public: struct Config { - Config() {} - realm::app::App::Config app_config; - std::string base_path; - realm::SyncManager::MetadataMode metadata_mode = realm::SyncManager::MetadataMode::NoMetadata; - bool should_teardown_test_directory = true; - realm::util::Logger::Level log_level = realm::util::Logger::Level::TEST_LOGGING_LEVEL; - bool override_sync_route = true; + Config(std::shared_ptr = std::make_shared()); std::shared_ptr transport; - bool start_sync_client = true; + bool delete_storage = true; + std::optional storage_path; + realm::SyncManager::MetadataMode metadata_mode = realm::SyncManager::MetadataMode::NoMetadata; + std::optional base_url; + std::shared_ptr socket_provider; + std::optional app_id; }; - - TestSyncManager(realm::SyncManager::MetadataMode mode); - TestSyncManager(const Config& = Config(), const SyncServer::Config& = {}); - ~TestSyncManager(); + OfflineAppSession(Config = {}); + ~OfflineAppSession(); std::shared_ptr app() const noexcept { return m_app; } - std::string base_file_path() const + std::shared_ptr make_user() const; + realm::app::GenericNetworkTransport* transport() { - return m_base_file_path; + return m_transport.get(); } - SyncServer& sync_server() + std::string base_file_path() const { - return m_sync_server; + return m_base_file_path; } const std::shared_ptr& sync_manager() { return m_app->sync_manager(); } - std::shared_ptr fake_user(const std::string& name = "test"); - - // Capture the token refresh callback so that we can invoke it later with - // the desired result - realm::util::UniqueFunction network_callback; +private: + std::shared_ptr m_app; + std::string m_base_file_path; + std::shared_ptr m_transport; + bool m_delete_storage = true; +}; - struct Transport : realm::app::GenericNetworkTransport { - Transport(realm::util::UniqueFunction& network_callback) - : network_callback(network_callback) - { - } +#if REALM_ENABLE_AUTH_TESTS +using DeleteApp = realm::util::TaggedBool; +class TestAppSession { +public: + TestAppSession(); + TestAppSession(realm::AppSession, std::shared_ptr = nullptr, + DeleteApp = true, realm::ReconnectMode reconnect_mode = realm::ReconnectMode::normal, + std::shared_ptr custom_socket_provider = nullptr); + ~TestAppSession(); - void - send_request_to_server(const realm::app::Request&, - realm::util::UniqueFunction&& completion) override - { - network_callback = std::move(completion); - } + std::shared_ptr app() const noexcept + { + return m_app; + } + const realm::AppSession& app_session() const noexcept + { + return *m_app_session; + } + realm::app::GenericNetworkTransport* transport() + { + return m_transport.get(); + } + const std::shared_ptr& sync_manager() const + { + return m_app->sync_manager(); + } - realm::util::UniqueFunction& network_callback; - }; - const std::shared_ptr transport; + std::vector get_documents(realm::SyncUser& user, const std::string& object_type, + size_t expected_count) const; private: std::shared_ptr m_app; - SyncServer m_sync_server; + std::unique_ptr m_app_session; std::string m_base_file_path; - bool m_should_teardown_test_directory = true; + bool m_delete_app = true; + std::shared_ptr m_transport; }; - -inline TestSyncManager::TestSyncManager(realm::SyncManager::MetadataMode mode) - : TestSyncManager([=] { - Config config; - config.metadata_mode = mode; - return config; - }()) -{ -} +#endif bool wait_for_upload(realm::Realm& realm, std::chrono::seconds timeout = std::chrono::seconds(60)); bool wait_for_download(realm::Realm& realm, std::chrono::seconds timeout = std::chrono::seconds(60)); diff --git a/test/object-store/util/test_utils.hpp b/test/object-store/util/test_utils.hpp index 86a548a6b74..89fc92e1b83 100644 --- a/test/object-store/util/test_utils.hpp +++ b/test/object-store/util/test_utils.hpp @@ -47,21 +47,24 @@ class TestingStateMachine { void transition_to(E new_state) { - std::lock_guard lock{m_mutex}; - m_cur_state = new_state; + { + std::lock_guard lock{m_mutex}; + m_cur_state = new_state; + } m_cv.notify_one(); } template void transition_with(Func&& func) { - std::lock_guard lock{m_mutex}; - std::optional new_state = func(m_cur_state); - if (!new_state) { - return; + { + std::lock_guard lock{m_mutex}; + std::optional new_state = func(m_cur_state); + if (!new_state) { + return; + } + m_cur_state = *new_state; } - - m_cur_state = *new_state; m_cv.notify_one(); } diff --git a/test/object-store/util/unit_test_transport.hpp b/test/object-store/util/unit_test_transport.hpp index 0211b784fee..509e4187275 100644 --- a/test/object-store/util/unit_test_transport.hpp +++ b/test/object-store/util/unit_test_transport.hpp @@ -16,6 +16,9 @@ // //////////////////////////////////////////////////////////////////////////// +#ifndef REALM_TEST_UTIL_TRANSPORT_HPP +#define REALM_TEST_UTIL_TRANSPORT_HPP + #include #include @@ -94,3 +97,5 @@ class UnitTestTransport : public realm::app::GenericNetworkTransport { void handle_token_refresh(const realm::app::Request& request, realm::util::UniqueFunction&& completion); }; + +#endif // REALM_TEST_UTIL_TRANSPORT_HPP