From 299026351e2f0f5ad19b747b539bae87ca86f433 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Tue, 26 Mar 2024 21:58:22 -0400 Subject: [PATCH 01/17] added tests using cancel_waits_on_nonfatal_error and fix operation during location update --- src/realm/object-store/sync/app.cpp | 9 +- src/realm/object-store/sync/sync_session.cpp | 6 ++ test/object-store/realm.cpp | 95 +++++++++++++++++++ test/object-store/sync/app.cpp | 12 +-- test/object-store/sync/flx_sync.cpp | 2 +- .../object-store/util/sync/baas_admin_api.cpp | 5 +- .../util/sync/sync_test_utils.hpp | 5 +- 7 files changed, 121 insertions(+), 13 deletions(-) diff --git a/src/realm/object-store/sync/app.cpp b/src/realm/object-store/sync/app.cpp index f89a8e79ddd..9ff2ffbaed2 100644 --- a/src/realm/object-store/sync/app.cpp +++ b/src/realm/object-store/sync/app.cpp @@ -823,7 +823,7 @@ void App::log_out(const std::shared_ptr& user, UniqueFunctionuser_profile().name()); + log_debug("App: log_out(%1)", user->identity()); auto refresh_token = user->refresh_token(); user->log_out(); @@ -1255,15 +1255,18 @@ void App::refresh_access_token(const std::shared_ptr& sync_user, bool return; } - log_debug("App: refresh_access_token: email: %1 %2", sync_user->user_profile().email(), + log_debug("App: refresh_access_token: email: %1 %2", sync_user->identity(), update_location ? "(updating location)" : ""); // If update_location is set, force the location info to be updated before sending the request do_request( {HttpMethod::post, url_for_path("/auth/session"), m_request_timeout_ms, get_request_headers(sync_user, RequestTokenType::RefreshToken)}, - [completion = std::move(completion), sync_user](const Response& response) { + [self = shared_from_this(), completion = std::move(completion), sync_user](const Response& response) { if (auto error = AppUtils::check_for_errors(response)) { + self->log_error("App: refresh_access_token: %1 -> %2 ERROR: %3", sync_user->identity(), + response.http_status_code, error->what()); + return completion(std::move(error)); } diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index a85c13472fc..08f41f0da58 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -343,9 +343,15 @@ SyncSession::handle_refresh(const std::shared_ptr& session, bool re // internal backoff timer which will happen automatically so nothing needs to // happen here. util::CheckedUniqueLock lock(session->m_state_mutex); + // If updating access token while opening realm, just become active at this point + // and try to use the current access token. if (session->m_state == State::WaitingForAccessToken) { session->become_active(); } + // If `cancel_waits_on_nonfatal_error` is true, then cancel the waiters and pass along the error + else if (session->config(&SyncConfig::cancel_waits_on_nonfatal_error)) { + session->cancel_pending_waits(std::move(lock), error->to_status()); // unlocks the mutex + } } } else { diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index a9a2a0ad43e..d29a8599702 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1206,6 +1206,101 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { REQUIRE(got_error); } + SECTION("waiters are cancelled if cancel_waits_on_nonfatal_error") { + auto logger = util::Logger::get_default_logger(); + auto transport = std::make_shared>(); + auto socket_provider = std::make_shared(logger, "some user agent"); + enum TestMode { location_fails, token_fails, token_not_authorized }; + + OfflineAppSession::Config oas_config; + oas_config.transport = transport; + oas_config.socket_provider = socket_provider; + OfflineAppSession oas(oas_config); + + SyncTestFile config(oas, "realm"); + auto user = config.sync_config->user; + config.sync_config->cancel_waits_on_nonfatal_error = true; + config.sync_config->error_handler = [&logger](std::shared_ptr session, SyncError error) { + logger->debug("The error handler caught an unexpected sync error: '%1' for '%2'", error.status, + session->path()); + // Ignore connection failed non-fatal errors and check for access token refresh unauthorized fatal errors + if (error.status.code() == ErrorCodes::SyncConnectFailed) { + REQUIRE_FALSE(error.is_fatal); + return; + } + if (error.status.code() == ErrorCodes::AuthError) { + REQUIRE(error.is_fatal); + } + }; + + auto valid_token = user->access_token(); + // User should be logged in at this point + bool not_authorized = false; + bool token_refresh_called = false; + bool location_refresh_called = false; + + TestMode mode = GENERATE(location_fails, token_fails, token_not_authorized); + + SECTION("access token expired when realm is opened") { + logger->info(">>> access token expired at start - mode: %1", mode); + user->update_access_token(std::move(expired_token)); + } + SECTION("access token expired when websocket connects") { + logger->info(">>> access token expired by websocket - mode: %1", mode); + not_authorized = true; + } + SECTION("access token expired when websocket connects") { + logger->info(">>> websocket returns connection failed - mode: %1", mode); + } + + transport->request_hook = [&](const app::Request& req) -> std::optional { + if (req.url.find("/auth/session") != std::string::npos) { + token_refresh_called = true; + if (mode == token_not_authorized) { + return app::Response{403, 0, {}, "403 not authorized"}; + } + if (mode == token_fails) { + return app::Response{0, 28, {}, "Operation timed out"}; + } + } + else if (req.url.find("/location") != std::string::npos) { + location_refresh_called = true; + if (mode == location_fails) { + // Fake "offline/request timed out" custom error response + return app::Response{0, 28, {}, "Operation timed out"}; + } + } + return std::nullopt; + }; + + socket_provider->websocket_connect_func = [&]() -> std::optional { + if (not_authorized) { + not_authorized = false; // one shot + return SocketProviderError(sync::websocket::WebSocketError::websocket_unauthorized, + "403 not authorized"); + } + return SocketProviderError(sync::websocket::WebSocketError::websocket_connection_failed, + "Operation timed out"); + }; + + std::atomic called{false}; + + auto task = Realm::get_synchronized_realm(config); + task->start([&](auto ref, auto error) { + REQUIRE(!ref); + REQUIRE(error); + called = true; + }); + util::EventLoop::main().run_until([&] { + return called.load(); + }); + REQUIRE(called); + REQUIRE(location_refresh_called); + if (mode != TestMode::location_fails) { + REQUIRE(token_refresh_called); + } + } + SECTION("read-only mode sets the schema version") { { SharedRealm realm = Realm::get_shared_realm(config); diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 3ee45661600..25b25d231a5 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -2685,7 +2685,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { } } { - auto redir_transport = std::make_shared(); + auto redir_transport = std::make_shared>(); AutoVerifiedEmailCredentials creds; auto app_config = get_config(redir_transport, session.app_session()); @@ -2852,7 +2852,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { } } SECTION("Test app redirect with no metadata") { - auto redir_transport = std::make_shared(); + auto redir_transport = std::make_shared>(); AutoVerifiedEmailCredentials creds, creds2; auto app_config = get_config(redir_transport, session.app_session()); @@ -2943,7 +2943,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { const std::string redirect_host = "fakerealm.example.com:9090"; const std::string redirect_url = "http://fakerealm.example.com:9090"; - auto redir_transport = std::make_shared(); + auto redir_transport = std::make_shared>(); auto redir_provider = std::make_shared(logger, ""); redir_provider->websocket_endpoint_resolver = [&](sync::WebSocketEndpoint&& ep) { ep.address = original_address; @@ -3164,7 +3164,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { original_address = original_host.substr(0, port_pos); } - auto redir_transport = std::make_shared(); + auto redir_transport = std::make_shared>(); auto redir_provider = std::make_shared(logger, ""); redir_provider->websocket_endpoint_resolver = [&](sync::WebSocketEndpoint&& ep) { ep.address = original_address; @@ -3262,7 +3262,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { REQUIRE(get_dogs(r).size() == 1); } - auto transport = std::make_shared(); + auto transport = std::make_shared>(); TestAppSession hooked_session(session.app_session(), transport, DeleteApp{false}); auto app = hooked_session.app(); std::shared_ptr user = app->current_user(); @@ -3321,7 +3321,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { REQUIRE(token.expired(now)); } - auto transport = std::make_shared(); + auto transport = std::make_shared>(); TestAppSession hooked_session(session.app_session(), transport, DeleteApp{false}); auto app = hooked_session.app(); std::shared_ptr user = app->current_user(); diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index 9ba6e1688ef..27a4ee599f1 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -2678,7 +2678,7 @@ TEST_CASE("flx: connect to PBS as FLX returns an error", "[sync][flx][protocol][ } TEST_CASE("flx: commit subscription while refreshing the access token", "[sync][flx][token][baas]") { - auto transport = std::make_shared(); + auto transport = std::make_shared>(); FLXSyncTestHarness harness("flx_wait_access_token2", FLXSyncTestHarness::default_server_schema(), transport); auto app = harness.app(); std::shared_ptr user = app->current_user(); diff --git a/test/object-store/util/sync/baas_admin_api.cpp b/test/object-store/util/sync/baas_admin_api.cpp index 41dd4337f8d..cfe78697ff9 100644 --- a/test/object-store/util/sync/baas_admin_api.cpp +++ b/test/object-store/util/sync/baas_admin_api.cpp @@ -346,8 +346,11 @@ app::Response do_http_request(const app::Request& request) auto logger = util::Logger::get_default_logger(); if (response_code != CURLE_OK) { + std::string message = curl_easy_strerror(response_code); logger->error("curl_easy_perform() failed when sending request to '%1' with body '%2': %3", request.url, - request.body, curl_easy_strerror(response_code)); + request.body, message); + // Return a failing response with the CURL error as the custom code + return {0, response_code, {}, message}; } if (logger->would_log(util::Logger::Level::trace)) { std::string coid = [&] { diff --git a/test/object-store/util/sync/sync_test_utils.hpp b/test/object-store/util/sync/sync_test_utils.hpp index 89046211345..eb20cabb5eb 100644 --- a/test/object-store/util/sync/sync_test_utils.hpp +++ b/test/object-store/util/sync/sync_test_utils.hpp @@ -194,7 +194,8 @@ class SynchronousTestTransport : public app::GenericNetworkTransport { }; -class HookedTransport : public SynchronousTestTransport { +template ::value>> +class HookedTransport : public Parent { public: void send_request_to_server(const app::Request& request, util::UniqueFunction&& completion) override @@ -204,7 +205,7 @@ class HookedTransport : public SynchronousTestTransport { return completion(*simulated_response); } } - SynchronousTestTransport::send_request_to_server(request, [&](const app::Response& response) mutable { + Parent::send_request_to_server(request, [&](const app::Response& response) mutable { if (response_hook) { response_hook(request, response); } From c060daf3bfbe7516899eebe7cd231deb8004264d Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Tue, 26 Mar 2024 23:08:41 -0400 Subject: [PATCH 02/17] Updated changelog and updated comments/debug statements --- CHANGELOG.md | 2 +- test/object-store/realm.cpp | 9 ++++----- test/object-store/util/sync/sync_test_utils.hpp | 2 ++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eba307cbea..23a0ffbaf6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Fixed * ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?) -* None. +* Enabling 'cancel_waits_on_nonfatal_error' does not cancel waits during location update while offline ([#7527](https://github.com/realm/realm-core/issues/7527), since v13.26.0) ### Breaking changes * None. diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index d29a8599702..733bcc4d042 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1221,8 +1221,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { auto user = config.sync_config->user; config.sync_config->cancel_waits_on_nonfatal_error = true; config.sync_config->error_handler = [&logger](std::shared_ptr session, SyncError error) { - logger->debug("The error handler caught an unexpected sync error: '%1' for '%2'", error.status, - session->path()); + logger->debug("The error handler caught a sync error: '%1' for '%2'", error.status, session->path()); // Ignore connection failed non-fatal errors and check for access token refresh unauthorized fatal errors if (error.status.code() == ErrorCodes::SyncConnectFailed) { REQUIRE_FALSE(error.is_fatal); @@ -1242,15 +1241,15 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { TestMode mode = GENERATE(location_fails, token_fails, token_not_authorized); SECTION("access token expired when realm is opened") { - logger->info(">>> access token expired at start - mode: %1", mode); + logger->trace(">>> access token expired at start - mode: %1", mode); user->update_access_token(std::move(expired_token)); } SECTION("access token expired when websocket connects") { - logger->info(">>> access token expired by websocket - mode: %1", mode); + logger->trace(">>> access token expired by websocket - mode: %1", mode); not_authorized = true; } SECTION("access token expired when websocket connects") { - logger->info(">>> websocket returns connection failed - mode: %1", mode); + logger->trace(">>> websocket returns connection failed - mode: %1", mode); } transport->request_hook = [&](const app::Request& req) -> std::optional { diff --git a/test/object-store/util/sync/sync_test_utils.hpp b/test/object-store/util/sync/sync_test_utils.hpp index eb20cabb5eb..cecead0ddba 100644 --- a/test/object-store/util/sync/sync_test_utils.hpp +++ b/test/object-store/util/sync/sync_test_utils.hpp @@ -194,6 +194,8 @@ class SynchronousTestTransport : public app::GenericNetworkTransport { }; +// Converted to a templated class to allow creating against the UnitTestTransport or +// SynchronousTestTransport (or other custom) GenericNetworkTransport base class. template ::value>> class HookedTransport : public Parent { public: From 91a54f14c91845274d4255d4a29672cf1e4e6eea Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Tue, 26 Mar 2024 23:24:27 -0400 Subject: [PATCH 03/17] fix swift build and test and tsan errors --- test/object-store/realm.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 733bcc4d042..de4cb2b0d99 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -45,6 +45,7 @@ #if REALM_ENABLE_SYNC #include +#include #include #include @@ -1283,9 +1284,9 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { }; std::atomic called{false}; - auto task = Realm::get_synchronized_realm(config); task->start([&](auto ref, auto error) { + std::lock_guard lock(mutex); REQUIRE(!ref); REQUIRE(error); called = true; @@ -1293,6 +1294,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { util::EventLoop::main().run_until([&] { return called.load(); }); + std::lock_guard lock(mutex); REQUIRE(called); REQUIRE(location_refresh_called); if (mode != TestMode::location_fails) { From 565010704251c6d06a5e4790bf4b600b6d456be0 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Tue, 26 Mar 2024 23:52:05 -0400 Subject: [PATCH 04/17] Update from review --- test/object-store/util/sync/sync_test_utils.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/object-store/util/sync/sync_test_utils.hpp b/test/object-store/util/sync/sync_test_utils.hpp index cecead0ddba..2a00b2bb72e 100644 --- a/test/object-store/util/sync/sync_test_utils.hpp +++ b/test/object-store/util/sync/sync_test_utils.hpp @@ -196,9 +196,12 @@ class SynchronousTestTransport : public app::GenericNetworkTransport { // Converted to a templated class to allow creating against the UnitTestTransport or // SynchronousTestTransport (or other custom) GenericNetworkTransport base class. -template ::value>> +template class HookedTransport : public Parent { public: + static_assert(std::is_base_of::value, + "HookedTransport must be derived from a class whose parent is app::GenericNetworkTransport"); + void send_request_to_server(const app::Request& request, util::UniqueFunction&& completion) override { From ea76f6542d1f02a91b3aa11295654f703a845758 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Wed, 27 Mar 2024 01:12:10 -0400 Subject: [PATCH 05/17] Fixed TSAN failures --- src/realm/exceptions.cpp | 5 ++++- src/realm/exceptions.hpp | 3 ++- test/object-store/realm.cpp | 25 ++++++++++++++----------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/realm/exceptions.cpp b/src/realm/exceptions.cpp index 966572fe3f5..91eb0cb670e 100644 --- a/src/realm/exceptions.cpp +++ b/src/realm/exceptions.cpp @@ -64,9 +64,12 @@ Exception::Exception(Status status) { } -Status exception_to_status() noexcept +Status exception_to_status(std::exception_ptr exc_ptr) noexcept { try { + if (exc_ptr) { + std::rethrow_exception(exc_ptr); + } throw; } catch (const Exception& e) { diff --git a/src/realm/exceptions.hpp b/src/realm/exceptions.hpp index 2c4804922aa..64f69b08e41 100644 --- a/src/realm/exceptions.hpp +++ b/src/realm/exceptions.hpp @@ -21,6 +21,7 @@ #include +#include #include #include @@ -49,7 +50,7 @@ class Exception : public std::exception { * * Currently this works for exceptions that derive from std::exception or Exception only. */ -Status exception_to_status() noexcept; +Status exception_to_status(std::exception_ptr exc_ptr = nullptr) noexcept; /// The UnsupportedFileFormatVersion exception is thrown by DB::open() diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index de4cb2b0d99..83280511358 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1254,6 +1254,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } transport->request_hook = [&](const app::Request& req) -> std::optional { + std::lock_guard lock(mutex); if (req.url.find("/auth/session") != std::string::npos) { token_refresh_called = true; if (mode == token_not_authorized) { @@ -1283,19 +1284,21 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { "Operation timed out"); }; - std::atomic called{false}; auto task = Realm::get_synchronized_realm(config); - task->start([&](auto ref, auto error) { - std::lock_guard lock(mutex); - REQUIRE(!ref); - REQUIRE(error); - called = true; - }); - util::EventLoop::main().run_until([&] { - return called.load(); - }); + auto [promise, async_future] = util::make_promise_future(); + task->start( + [async_promise = util::CopyablePromiseHolder(std::move(promise))](auto ref, auto error) mutable { + REQUIRE(!ref); + REQUIRE(error); + if (error) + async_promise.get_promise().set_error(exception_to_status(error)); + else + async_promise.get_promise().emplace_value(); + }); + + auto result = async_future.get_no_throw(); + REQUIRE_FALSE(result.is_ok()); std::lock_guard lock(mutex); - REQUIRE(called); REQUIRE(location_refresh_called); if (mode != TestMode::location_fails) { REQUIRE(token_refresh_called); From 7298787988fdefbb9d908b20ca24ba6a7a5947f4 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Wed, 27 Mar 2024 08:39:21 -0400 Subject: [PATCH 06/17] Updates from review --- test/object-store/realm.cpp | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 83280511358..ed393fc5313 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1222,7 +1222,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { auto user = config.sync_config->user; config.sync_config->cancel_waits_on_nonfatal_error = true; config.sync_config->error_handler = [&logger](std::shared_ptr session, SyncError error) { - logger->debug("The error handler caught a sync error: '%1' for '%2'", error.status, session->path()); + logger->debug("The sync error handler caught an error: '%1' for '%2'", error.status, session->path()); // Ignore connection failed non-fatal errors and check for access token refresh unauthorized fatal errors if (error.status.code() == ErrorCodes::SyncConnectFailed) { REQUIRE_FALSE(error.is_fatal); @@ -1285,19 +1285,16 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { }; auto task = Realm::get_synchronized_realm(config); - auto [promise, async_future] = util::make_promise_future(); - task->start( - [async_promise = util::CopyablePromiseHolder(std::move(promise))](auto ref, auto error) mutable { - REQUIRE(!ref); - REQUIRE(error); - if (error) - async_promise.get_promise().set_error(exception_to_status(error)); - else - async_promise.get_promise().emplace_value(); - }); + auto pf = util::make_promise_future(); + task->start([&pf](auto ref, auto error) mutable { + REQUIRE(!ref); + REQUIRE(error); + pf.promise.emplace_value(error); + }); - auto result = async_future.get_no_throw(); - REQUIRE_FALSE(result.is_ok()); + auto result = pf.future.get_no_throw(); + REQUIRE(result.is_ok()); + REQUIRE(result.get_value()); std::lock_guard lock(mutex); REQUIRE(location_refresh_called); if (mode != TestMode::location_fails) { From 5ac829b2b1f2b54896d7106042470c721754dc75 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Wed, 27 Mar 2024 08:42:03 -0400 Subject: [PATCH 07/17] Fixed lint failure --- test/object-store/util/sync/sync_test_utils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/object-store/util/sync/sync_test_utils.hpp b/test/object-store/util/sync/sync_test_utils.hpp index 2a00b2bb72e..926e5c32711 100644 --- a/test/object-store/util/sync/sync_test_utils.hpp +++ b/test/object-store/util/sync/sync_test_utils.hpp @@ -200,7 +200,7 @@ template class HookedTransport : public Parent { public: static_assert(std::is_base_of::value, - "HookedTransport must be derived from a class whose parent is app::GenericNetworkTransport"); + "HookedTransport must be derived from a class whose parent is app::GenericNetworkTransport"); void send_request_to_server(const app::Request& request, util::UniqueFunction&& completion) override From b3bb528917f5a03edae9c4ec7cf40d03730418ac Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Wed, 27 Mar 2024 10:05:25 -0400 Subject: [PATCH 08/17] Updates from review --- test/object-store/realm.cpp | 7 ++++--- test/object-store/sync/app.cpp | 12 ++++++------ test/object-store/sync/flx_sync.cpp | 2 +- test/object-store/util/sync/sync_test_utils.hpp | 3 +++ 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index ed393fc5313..040be839687 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1209,7 +1209,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { SECTION("waiters are cancelled if cancel_waits_on_nonfatal_error") { auto logger = util::Logger::get_default_logger(); - auto transport = std::make_shared>(); + auto transport = std::make_shared(); auto socket_provider = std::make_shared(logger, "some user agent"); enum TestMode { location_fails, token_fails, token_not_authorized }; @@ -1254,6 +1254,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } transport->request_hook = [&](const app::Request& req) -> std::optional { + static constexpr int CURLE_OPERATION_TIMEDOUT = 28; std::lock_guard lock(mutex); if (req.url.find("/auth/session") != std::string::npos) { token_refresh_called = true; @@ -1261,14 +1262,14 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { return app::Response{403, 0, {}, "403 not authorized"}; } if (mode == token_fails) { - return app::Response{0, 28, {}, "Operation timed out"}; + return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; } } else if (req.url.find("/location") != std::string::npos) { location_refresh_called = true; if (mode == location_fails) { // Fake "offline/request timed out" custom error response - return app::Response{0, 28, {}, "Operation timed out"}; + return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; } } return std::nullopt; diff --git a/test/object-store/sync/app.cpp b/test/object-store/sync/app.cpp index 25b25d231a5..aa8aaa687e9 100644 --- a/test/object-store/sync/app.cpp +++ b/test/object-store/sync/app.cpp @@ -2685,7 +2685,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { } } { - auto redir_transport = std::make_shared>(); + auto redir_transport = std::make_shared(); AutoVerifiedEmailCredentials creds; auto app_config = get_config(redir_transport, session.app_session()); @@ -2852,7 +2852,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { } } SECTION("Test app redirect with no metadata") { - auto redir_transport = std::make_shared>(); + auto redir_transport = std::make_shared(); AutoVerifiedEmailCredentials creds, creds2; auto app_config = get_config(redir_transport, session.app_session()); @@ -2943,7 +2943,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { const std::string redirect_host = "fakerealm.example.com:9090"; const std::string redirect_url = "http://fakerealm.example.com:9090"; - auto redir_transport = std::make_shared>(); + auto redir_transport = std::make_shared(); auto redir_provider = std::make_shared(logger, ""); redir_provider->websocket_endpoint_resolver = [&](sync::WebSocketEndpoint&& ep) { ep.address = original_address; @@ -3164,7 +3164,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { original_address = original_host.substr(0, port_pos); } - auto redir_transport = std::make_shared>(); + auto redir_transport = std::make_shared(); auto redir_provider = std::make_shared(logger, ""); redir_provider->websocket_endpoint_resolver = [&](sync::WebSocketEndpoint&& ep) { ep.address = original_address; @@ -3262,7 +3262,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { REQUIRE(get_dogs(r).size() == 1); } - auto transport = std::make_shared>(); + auto transport = std::make_shared(); TestAppSession hooked_session(session.app_session(), transport, DeleteApp{false}); auto app = hooked_session.app(); std::shared_ptr user = app->current_user(); @@ -3321,7 +3321,7 @@ TEST_CASE("app: sync integration", "[sync][pbs][app][baas]") { REQUIRE(token.expired(now)); } - auto transport = std::make_shared>(); + auto transport = std::make_shared(); TestAppSession hooked_session(session.app_session(), transport, DeleteApp{false}); auto app = hooked_session.app(); std::shared_ptr user = app->current_user(); diff --git a/test/object-store/sync/flx_sync.cpp b/test/object-store/sync/flx_sync.cpp index 27a4ee599f1..afe657ffd61 100644 --- a/test/object-store/sync/flx_sync.cpp +++ b/test/object-store/sync/flx_sync.cpp @@ -2678,7 +2678,7 @@ TEST_CASE("flx: connect to PBS as FLX returns an error", "[sync][flx][protocol][ } TEST_CASE("flx: commit subscription while refreshing the access token", "[sync][flx][token][baas]") { - auto transport = std::make_shared>(); + auto transport = std::make_shared(); FLXSyncTestHarness harness("flx_wait_access_token2", FLXSyncTestHarness::default_server_schema(), transport); auto app = harness.app(); std::shared_ptr user = app->current_user(); diff --git a/test/object-store/util/sync/sync_test_utils.hpp b/test/object-store/util/sync/sync_test_utils.hpp index 926e5c32711..d8bb4fc4ea3 100644 --- a/test/object-store/util/sync/sync_test_utils.hpp +++ b/test/object-store/util/sync/sync_test_utils.hpp @@ -224,6 +224,9 @@ class HookedTransport : public Parent { std::function(const app::Request&)> request_hook; }; +using HookedSynchronousTransport = HookedTransport; +using HookedUnitTestTransport = HookedTransport; + struct SocketProviderError { SocketProviderError(sync::HTTPStatus code, std::string message = "") From 3c158a9ecbf4a685fd03304acf04ac58b4f33da5 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Wed, 27 Mar 2024 11:21:22 -0400 Subject: [PATCH 09/17] More updates from review --- test/object-store/realm.cpp | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 040be839687..f864dfd5a81 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1212,6 +1212,18 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { auto transport = std::make_shared(); auto socket_provider = std::make_shared(logger, "some user agent"); enum TestMode { location_fails, token_fails, token_not_authorized }; + auto txt_test_mode = [](TestMode mode) { + switch (mode) { + case TestMode::location_fails: + return "location_fails"; + case TestMode::token_fails: + return "token_fails"; + case TestMode::token_not_authorized: + return "token_not_authorized"; + default: + return "Unknown TestMode"; + } + }; OfflineAppSession::Config oas_config; oas_config.transport = transport; @@ -1233,24 +1245,27 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } }; - auto valid_token = user->access_token(); // User should be logged in at this point + REQUIRE(user->is_logged_in()); bool not_authorized = false; bool token_refresh_called = false; bool location_refresh_called = false; TestMode mode = GENERATE(location_fails, token_fails, token_not_authorized); - SECTION("access token expired when realm is opened") { - logger->trace(">>> access token expired at start - mode: %1", mode); + SECTION(util::format("access token expired when realm is opened - mode: %1", txt_test_mode(mode))) { + logger->trace(">>> access token expired when realm is opened - mode: %1", txt_test_mode(mode)); + // invalidate the user's cached access token user->update_access_token(std::move(expired_token)); } - SECTION("access token expired when websocket connects") { - logger->trace(">>> access token expired by websocket - mode: %1", mode); + SECTION(util::format("access token expired by websocket - mode: %1", txt_test_mode(mode))) { + logger->trace(">>> access token expired by websocket - mode: %1", txt_test_mode(mode)); + // tell websocket to return not authorized to refresh access token not_authorized = true; } - SECTION("access token expired when websocket connects") { - logger->trace(">>> websocket returns connection failed - mode: %1", mode); + SECTION(util::format("websocket returns connection failed - mode: %1", txt_test_mode(mode))) { + logger->trace(">>> websocket returns connection failed - mode: %1", txt_test_mode(mode)); + // default case } transport->request_hook = [&](const app::Request& req) -> std::optional { From 6e5f0a4938ce554ab6aefeaeea4afa09fa72f468 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Wed, 27 Mar 2024 14:51:04 -0400 Subject: [PATCH 10/17] Additional updates from review --- test/object-store/realm.cpp | 66 +++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index f864dfd5a81..dc01200ab77 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1211,19 +1211,32 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { auto logger = util::Logger::get_default_logger(); auto transport = std::make_shared(); auto socket_provider = std::make_shared(logger, "some user agent"); - enum TestMode { location_fails, token_fails, token_not_authorized }; + enum TestMode { expired_at_start, expired_by_websocket, websocket_fails }; + enum FailureMode { location_fails, token_fails, token_not_authorized }; auto txt_test_mode = [](TestMode mode) { switch (mode) { - case TestMode::location_fails: - return "location_fails"; - case TestMode::token_fails: - return "token_fails"; - case TestMode::token_not_authorized: - return "token_not_authorized"; + case TestMode::expired_at_start: + return "access token expired when realm is opened"; + case TestMode::expired_by_websocket: + return "access token expired by websocket"; + case TestMode::websocket_fails: + return "websocket returns connection failed"; default: return "Unknown TestMode"; } }; + auto txt_failure_mode = [](FailureMode mode) { + switch (mode) { + case FailureMode::location_fails: + return "location update fails"; + case FailureMode::token_fails: + return "access token refresh fails"; + case FailureMode::token_not_authorized: + return "websocket connect not authorized"; + default: + return "Unknown FailureMode"; + } + }; OfflineAppSession::Config oas_config; oas_config.transport = transport; @@ -1240,9 +1253,9 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { REQUIRE_FALSE(error.is_fatal); return; } - if (error.status.code() == ErrorCodes::AuthError) { - REQUIRE(error.is_fatal); - } + // If it's not SyncConnectFailed, then it should be AuthError + REQUIRE(error.status.code() == ErrorCodes::AuthError); + REQUIRE(error.is_fatal); }; // User should be logged in at this point @@ -1251,21 +1264,18 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { bool token_refresh_called = false; bool location_refresh_called = false; - TestMode mode = GENERATE(location_fails, token_fails, token_not_authorized); + TestMode test_mode = GENERATE(expired_at_start, expired_by_websocket, websocket_fails); + FailureMode failure = GENERATE(location_fails, token_fails, token_not_authorized); - SECTION(util::format("access token expired when realm is opened - mode: %1", txt_test_mode(mode))) { - logger->trace(">>> access token expired when realm is opened - mode: %1", txt_test_mode(mode)); - // invalidate the user's cached access token - user->update_access_token(std::move(expired_token)); - } - SECTION(util::format("access token expired by websocket - mode: %1", txt_test_mode(mode))) { - logger->trace(">>> access token expired by websocket - mode: %1", txt_test_mode(mode)); - // tell websocket to return not authorized to refresh access token - not_authorized = true; - } - SECTION(util::format("websocket returns connection failed - mode: %1", txt_test_mode(mode))) { - logger->trace(">>> websocket returns connection failed - mode: %1", txt_test_mode(mode)); - // default case + DYNAMIC_SECTION(txt_test_mode(test_mode) << " - " << txt_failure_mode(failure)) { + if (test_mode == TestMode::expired_at_start) { + // invalidate the user's cached access token + user->update_access_token(std::move(expired_token)); + } + else if (test_mode == TestMode::expired_by_websocket) { + // tell websocket to return not authorized to refresh access token + not_authorized = true; + } } transport->request_hook = [&](const app::Request& req) -> std::optional { @@ -1273,16 +1283,16 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { std::lock_guard lock(mutex); if (req.url.find("/auth/session") != std::string::npos) { token_refresh_called = true; - if (mode == token_not_authorized) { + if (failure == FailureMode::token_not_authorized) { return app::Response{403, 0, {}, "403 not authorized"}; } - if (mode == token_fails) { + if (failure == FailureMode::token_fails) { return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; } } else if (req.url.find("/location") != std::string::npos) { location_refresh_called = true; - if (mode == location_fails) { + if (failure == FailureMode::location_fails) { // Fake "offline/request timed out" custom error response return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; } @@ -1313,7 +1323,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { REQUIRE(result.get_value()); std::lock_guard lock(mutex); REQUIRE(location_refresh_called); - if (mode != TestMode::location_fails) { + if (failure != FailureMode::location_fails) { REQUIRE(token_refresh_called); } } From 3f29c30046758ecff83244c65898fb6b52b5dac6 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Wed, 3 Apr 2024 00:41:23 -0400 Subject: [PATCH 11/17] Added test to replicate swift autoopen feature --- test/object-store/realm.cpp | 99 ++++++++++++++++++++++++++++ test/object-store/util/test_file.cpp | 4 +- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index dc01200ab77..556e1772961 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1461,6 +1461,105 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } } + +TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { + const auto partition = random_string(100); + auto schema = get_default_schema(); + enum TestMode { expired_at_start, expired_by_websocket, websocket_fails }; + enum FailureMode { location_fails, token_fails, token_not_authorized }; + + auto logger = util::Logger::get_default_logger(); + auto transport = std::make_shared(); + auto socket_provider = std::make_shared(logger, "some user agent"); + std::mutex mutex; + + // Create the app session and get the logged in user identity + auto server_app_config = minimal_app_config("autoopen-realm", schema); + TestAppSession session(create_app(server_app_config), transport, DeleteApp{true}, realm::ReconnectMode::normal, + socket_provider); + auto user = session.app()->current_user(); + std::string identity = user->identity(); + REQUIRE(user->is_logged_in()); + REQUIRE(!identity.empty()); + // Reopen the App instance and retrieve the cached user + session.reopen(false); + user = session.sync_manager()->get_existing_logged_in_user(identity); + + SyncTestFile config(user, partition, schema); + config.sync_config->cancel_waits_on_nonfatal_error = true; + config.sync_config->error_handler = [&logger](std::shared_ptr session, SyncError error) { + logger->debug("The sync error handler caught an error: '%1' for '%2'", error.status, session->path()); + // Ignore connection failed non-fatal errors and check for access token refresh unauthorized fatal errors + if (error.status.code() == ErrorCodes::SyncConnectFailed) { + REQUIRE_FALSE(error.is_fatal); + return; + } + // If it's not SyncConnectFailed, then it should be AuthError + REQUIRE(error.status.code() == ErrorCodes::AuthError); + REQUIRE(error.is_fatal); + }; + + bool not_authorized = false; + bool token_refresh_called = false; + bool location_refresh_called = false; + + FailureMode failure = FailureMode::location_fails; + + transport->request_hook = [&](const app::Request& req) -> std::optional { + static constexpr int CURLE_OPERATION_TIMEDOUT = 28; + std::lock_guard lock(mutex); + if (req.url.find("/auth/session") != std::string::npos) { + token_refresh_called = true; + if (failure == FailureMode::token_not_authorized) { + return app::Response{403, 0, {}, "403 not authorized"}; + } + if (failure == FailureMode::token_fails) { + return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; + } + } + else if (req.url.find("/location") != std::string::npos) { + location_refresh_called = true; + if (failure == FailureMode::location_fails) { + // Fake "offline/request timed out" custom error response + return app::Response{0, CURLE_OPERATION_TIMEDOUT, {}, "Operation timed out"}; + } + } + return std::nullopt; + }; + + socket_provider->websocket_connect_func = [&]() -> std::optional { + if (not_authorized) { + not_authorized = false; // one shot + return SocketProviderError(sync::websocket::WebSocketError::websocket_unauthorized, "403 not authorized"); + } + return SocketProviderError(sync::websocket::WebSocketError::websocket_connection_failed, + "Operation timed out"); + }; + + auto task = Realm::get_synchronized_realm(config); + auto pf = util::make_promise_future(); + task->start([&pf](auto ref, auto error) mutable { + REQUIRE(!ref); + REQUIRE(error); + pf.promise.emplace_value(error); + }); + + auto result = pf.future.get_no_throw(); + REQUIRE(result.is_ok()); + REQUIRE(result.get_value()); + std::lock_guard lock(mutex); + REQUIRE(location_refresh_called); + if (failure != FailureMode::location_fails) { + REQUIRE(token_refresh_called); + } + + // transport->request_hook = nullptr; + // socket_provider->websocket_connect_func = nullptr; + auto r = Realm::get_shared_realm(config); + wait_for_download(*r); +} + + TEST_CASE("SharedRealm: convert", "[sync][pbs][convert]") { TestSyncManager tsm; ObjectSchema object_schema = {"object", diff --git a/test/object-store/util/test_file.cpp b/test/object-store/util/test_file.cpp index e4365668f07..78cb16d044f 100644 --- a/test/object-store/util/test_file.cpp +++ b/test/object-store/util/test_file.cpp @@ -400,7 +400,9 @@ void TestAppSession::reopen(bool log_in) { // These are REALM_ASSERTs so the test crashes if this object is in a bad state REALM_ASSERT(!m_base_file_path.empty()); - REALM_ASSERT(!m_app); + if (m_app) { + close(false); + } m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config, sc_config); // initialize sync client From 1ef36398fc309b2d72baf4672d60d76d814f0324 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Wed, 3 Apr 2024 00:51:32 -0400 Subject: [PATCH 12/17] Updated test --- test/object-store/realm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 556e1772961..624d74bca49 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1553,8 +1553,8 @@ TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { REQUIRE(token_refresh_called); } - // transport->request_hook = nullptr; - // socket_provider->websocket_connect_func = nullptr; + transport->request_hook = nullptr; + socket_provider->websocket_connect_func = nullptr; auto r = Realm::get_shared_realm(config); wait_for_download(*r); } From 669d09c1739f5492e6fb28d7d6bed022c9970717 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Thu, 25 Apr 2024 14:26:44 -0400 Subject: [PATCH 13/17] Fixed swift build issue --- src/realm/object-store/sync/app.cpp | 4 ++-- test/object-store/realm.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/realm/object-store/sync/app.cpp b/src/realm/object-store/sync/app.cpp index c3bccbe17b7..644ae476722 100644 --- a/src/realm/object-store/sync/app.cpp +++ b/src/realm/object-store/sync/app.cpp @@ -1285,8 +1285,8 @@ void App::refresh_access_token(const std::shared_ptr& user, bool update_lo return; } - log_debug("App: refresh_access_token: email: %1 %2", user->user_id(), - update_location ? "(updating location)" : ""); + log_debug("App: refresh_access_token: user-id: %1%2", user->user_id(), + update_location ? " (updating location)" : ""); // If update_location is set, force the location info to be updated before sending the request do_request( diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index 3c1d73958e4..dc08a956fe9 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -46,6 +46,9 @@ #if REALM_ENABLE_SYNC #include #include +#ifdef REALM_ENABLE_AUTH_TESTS +#include +#endif // REALM_ENABLE_AUTH_TESTS #include #include @@ -53,7 +56,7 @@ #include #include -#endif +#endif // REALM_ENABLE_SYNC #include #include @@ -63,7 +66,7 @@ #include #if REALM_HAVE_UV #include -#endif +#endif // REALM_HAVE_UV namespace realm { class TestHelper { @@ -1473,6 +1476,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } } +#if REALM_ENABLE_AUTH_TESTS TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { const auto partition = random_string(100); @@ -1571,6 +1575,7 @@ TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { wait_for_download(*r); } +#endif // REALM_ENABLE_AUTH_TESTS TEST_CASE("SharedRealm: convert", "[sync][pbs][convert]") { TestSyncManager tsm; From b2e74060cfd9639ad85116c7ab62de453d198391 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Thu, 25 Apr 2024 22:01:09 -0400 Subject: [PATCH 14/17] Fixed test failures --- test/object-store/realm.cpp | 71 ++++++++++++++++++---------- test/object-store/util/test_file.cpp | 21 ++++---- test/object-store/util/test_file.hpp | 3 ++ 3 files changed, 63 insertions(+), 32 deletions(-) diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index dc08a956fe9..e8867d76107 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -46,6 +46,7 @@ #if REALM_ENABLE_SYNC #include #include +#include #ifdef REALM_ENABLE_AUTH_TESTS #include #endif // REALM_ENABLE_AUTH_TESTS @@ -1219,6 +1220,8 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { REQUIRE(got_error); } +#if REALM_APP_SERVICES + SECTION("waiters are cancelled if cancel_waits_on_nonfatal_error") { auto logger = util::Logger::get_default_logger(); auto transport = std::make_shared>(); @@ -1250,28 +1253,18 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } }; - OfflineAppSession::Config oas_config; - oas_config.transport = transport; - oas_config.socket_provider = socket_provider; - OfflineAppSession oas(oas_config); - - SyncTestFile config(oas, "realm"); - auto user = config.sync_config->user; - config.sync_config->cancel_waits_on_nonfatal_error = true; - config.sync_config->error_handler = [&logger](std::shared_ptr session, SyncError error) { - logger->debug("The sync error handler caught an error: '%1' for '%2'", error.status, session->path()); - // Ignore connection failed non-fatal errors and check for access token refresh unauthorized fatal errors - if (error.status.code() == ErrorCodes::SyncConnectFailed) { - REQUIRE_FALSE(error.is_fatal); - return; - } - // If it's not SyncConnectFailed, then it should be AuthError - REQUIRE(error.status.code() == ErrorCodes::AuthError); - REQUIRE(error.is_fatal); - }; + app::AppConfig app_config; + set_app_config_defaults(app_config, transport); + app_config.sync_client_config.socket_provider = socket_provider; + app_config.base_file_path = util::make_temp_dir(); + app_config.metadata_mode = app::AppConfig::MetadataMode::NoEncryption; + auto the_app = app::App::get_app(app::App::CacheMode::Disabled, app_config); + create_user_and_log_in(the_app); + auto user = the_app->current_user(); // User should be logged in at this point REQUIRE(user->is_logged_in()); + bool not_authorized = false; bool token_refresh_called = false; bool location_refresh_called = false; @@ -1280,9 +1273,10 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { FailureMode failure = GENERATE(location_fails, token_fails, token_not_authorized); DYNAMIC_SECTION(txt_test_mode(test_mode) << " - " << txt_failure_mode(failure)) { + logger->info("TEST: %1 - %2", txt_test_mode(test_mode), txt_failure_mode(failure)); if (test_mode == TestMode::expired_at_start) { // invalidate the user's cached access token - auto app_user = oas.app()->current_user(); + auto app_user = the_app->current_user(); app_user->update_data_for_testing([&](app::UserData& data) { data.access_token = RealmJWT(expired_token); }); @@ -1293,6 +1287,21 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } } + the_app.reset(); + + auto err_handler = [](std::shared_ptr session, SyncError error) { + auto logger = util::Logger::get_default_logger(); + logger->debug("The sync error handler caught an error: '%1' for '%2'", error.status, session->path()); + // Ignore connection failed non-fatal errors and check for access token refresh unauthorized fatal errors + if (error.status.code() == ErrorCodes::SyncConnectFailed) { + REQUIRE_FALSE(error.is_fatal); + return; + } + // If it's not SyncConnectFailed, then it should be AuthError + REQUIRE(error.status.code() == ErrorCodes::AuthError); + REQUIRE(error.is_fatal); + }; + transport->request_hook = [&](const app::Request& req) -> std::optional { static constexpr int CURLE_OPERATION_TIMEDOUT = 28; std::lock_guard lock(mutex); @@ -1325,6 +1334,14 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { "Operation timed out"); }; + the_app = app::App::get_app(app::App::CacheMode::Disabled, app_config); + SyncTestFile config(the_app->current_user(), "realm"); + config.sync_config->cancel_waits_on_nonfatal_error = true; + config.sync_config->error_handler = err_handler; + + // User should be logged in at this point + REQUIRE(config.sync_config->user->is_logged_in()); + auto task = Realm::get_synchronized_realm(config); auto pf = util::make_promise_future(); task->start([&pf](auto ref, auto error) mutable { @@ -1343,6 +1360,8 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } } +#endif // REALM_APP_SERVICES + SECTION("read-only mode sets the schema version") { { SharedRealm realm = Realm::get_shared_realm(config); @@ -1477,6 +1496,7 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } #if REALM_ENABLE_AUTH_TESTS +#if REALM_APP_SERVICES TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { const auto partition = random_string(100); @@ -1563,10 +1583,12 @@ TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { auto result = pf.future.get_no_throw(); REQUIRE(result.is_ok()); REQUIRE(result.get_value()); - std::lock_guard lock(mutex); - REQUIRE(location_refresh_called); - if (failure != FailureMode::location_fails) { - REQUIRE(token_refresh_called); + { + std::lock_guard lock(mutex); + REQUIRE(location_refresh_called); + if (failure != FailureMode::location_fails) { + REQUIRE(token_refresh_called); + } } transport->request_hook = nullptr; @@ -1575,6 +1597,7 @@ TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { wait_for_download(*r); } +#endif // REALM_APP_SERVICES #endif // REALM_ENABLE_AUTH_TESTS TEST_CASE("SharedRealm: convert", "[sync][pbs][convert]") { diff --git a/test/object-store/util/test_file.cpp b/test/object-store/util/test_file.cpp index d9c52510eed..0e93f56e8e6 100644 --- a/test/object-store/util/test_file.cpp +++ b/test/object-store/util/test_file.cpp @@ -524,8 +524,7 @@ OfflineAppSession::OfflineAppSession(OfflineAppSession::Config config) , m_delete_storage(config.delete_storage) { REALM_ASSERT(m_transport); - app::AppConfig app_config; - set_app_config_defaults(app_config, m_transport); + set_app_config_defaults(m_app_config, m_transport); if (config.storage_path) { m_base_file_path = *config.storage_path; @@ -535,16 +534,16 @@ OfflineAppSession::OfflineAppSession(OfflineAppSession::Config config) m_base_file_path = util::make_temp_dir(); } - app_config.base_file_path = m_base_file_path; - app_config.metadata_mode = config.metadata_mode; + m_app_config.base_file_path = m_base_file_path; + m_app_config.metadata_mode = config.metadata_mode; if (config.base_url) { - app_config.base_url = *config.base_url; + m_app_config.base_url = *config.base_url; } if (config.app_id) { - app_config.app_id = *config.app_id; + m_app_config.app_id = *config.app_id; } - app_config.sync_client_config.socket_provider = config.socket_provider; - m_app = app::App::get_app(app::App::CacheMode::Disabled, app_config); + m_app_config.sync_client_config.socket_provider = config.socket_provider; + m_app = app::App::get_app(app::App::CacheMode::Disabled, m_app_config); } OfflineAppSession::~OfflineAppSession() @@ -567,6 +566,12 @@ std::shared_ptr OfflineAppSession::make_user() const return app()->current_user(); } +void OfflineAppSession::restart_app() +{ + m_app.reset(); + m_app = app::App::get_app(app::App::CacheMode::Disabled, m_app_config); +} + #endif // REALM_APP_SERVICES #endif // REALM_ENABLE_SYNC diff --git a/test/object-store/util/test_file.hpp b/test/object-store/util/test_file.hpp index 776c944a65a..c0fa2d52173 100644 --- a/test/object-store/util/test_file.hpp +++ b/test/object-store/util/test_file.hpp @@ -312,7 +312,10 @@ class OfflineAppSession { return m_app->sync_manager(); } + void restart_app(); + private: + realm::app::AppConfig m_app_config; std::shared_ptr m_app; std::string m_base_file_path; std::shared_ptr m_transport; From c1a25c0fc5f7e1db990f265781b227a62d592c36 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Thu, 25 Apr 2024 22:48:48 -0400 Subject: [PATCH 15/17] removed an unused function --- test/object-store/util/test_file.cpp | 6 ------ test/object-store/util/test_file.hpp | 2 -- 2 files changed, 8 deletions(-) diff --git a/test/object-store/util/test_file.cpp b/test/object-store/util/test_file.cpp index 0e93f56e8e6..50e58d8af5c 100644 --- a/test/object-store/util/test_file.cpp +++ b/test/object-store/util/test_file.cpp @@ -566,12 +566,6 @@ std::shared_ptr OfflineAppSession::make_user() const return app()->current_user(); } -void OfflineAppSession::restart_app() -{ - m_app.reset(); - m_app = app::App::get_app(app::App::CacheMode::Disabled, m_app_config); -} - #endif // REALM_APP_SERVICES #endif // REALM_ENABLE_SYNC diff --git a/test/object-store/util/test_file.hpp b/test/object-store/util/test_file.hpp index c0fa2d52173..73853a0fc64 100644 --- a/test/object-store/util/test_file.hpp +++ b/test/object-store/util/test_file.hpp @@ -312,8 +312,6 @@ class OfflineAppSession { return m_app->sync_manager(); } - void restart_app(); - private: realm::app::AppConfig m_app_config; std::shared_ptr m_app; From a34d5f676216ee6d9a1796e72074bde4d951dce1 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Fri, 26 Apr 2024 11:36:58 -0400 Subject: [PATCH 16/17] Fixed debug message --- src/realm/object-store/sync/app.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/realm/object-store/sync/app.cpp b/src/realm/object-store/sync/app.cpp index 564be6aeef8..73f0d07cc4f 100644 --- a/src/realm/object-store/sync/app.cpp +++ b/src/realm/object-store/sync/app.cpp @@ -1285,7 +1285,7 @@ void App::refresh_access_token(const std::shared_ptr& user, bool update_lo return; } - log_debug("App: refresh_access_token: user-id: %1%2", user->user_id(), + log_debug("App: refresh_access_token: user_id: %1%2", user->user_id(), update_location ? " (updating location)" : ""); // If update_location is set, force the location info to be updated before sending the request From 2e0a68fc400a789a4a62d817f730213cb4e9e7a0 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson-Barker Date: Fri, 26 Apr 2024 11:58:12 -0400 Subject: [PATCH 17/17] Updates from review --- src/realm/exceptions.cpp | 5 +---- src/realm/exceptions.hpp | 3 +-- test/object-store/realm.cpp | 2 -- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/realm/exceptions.cpp b/src/realm/exceptions.cpp index 91eb0cb670e..966572fe3f5 100644 --- a/src/realm/exceptions.cpp +++ b/src/realm/exceptions.cpp @@ -64,12 +64,9 @@ Exception::Exception(Status status) { } -Status exception_to_status(std::exception_ptr exc_ptr) noexcept +Status exception_to_status() noexcept { try { - if (exc_ptr) { - std::rethrow_exception(exc_ptr); - } throw; } catch (const Exception& e) { diff --git a/src/realm/exceptions.hpp b/src/realm/exceptions.hpp index 64f69b08e41..2c4804922aa 100644 --- a/src/realm/exceptions.hpp +++ b/src/realm/exceptions.hpp @@ -21,7 +21,6 @@ #include -#include #include #include @@ -50,7 +49,7 @@ class Exception : public std::exception { * * Currently this works for exceptions that derive from std::exception or Exception only. */ -Status exception_to_status(std::exception_ptr exc_ptr = nullptr) noexcept; +Status exception_to_status() noexcept; /// The UnsupportedFileFormatVersion exception is thrown by DB::open() diff --git a/test/object-store/realm.cpp b/test/object-store/realm.cpp index e8867d76107..0ee72b3759d 100644 --- a/test/object-store/realm.cpp +++ b/test/object-store/realm.cpp @@ -1496,7 +1496,6 @@ TEST_CASE("Get Realm using Async Open", "[sync][pbs][async open]") { } #if REALM_ENABLE_AUTH_TESTS -#if REALM_APP_SERVICES TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { const auto partition = random_string(100); @@ -1597,7 +1596,6 @@ TEST_CASE("Syhcnronized realm: AutoOpen", "[sync][baas][pbs][async open]") { wait_for_download(*r); } -#endif // REALM_APP_SERVICES #endif // REALM_ENABLE_AUTH_TESTS TEST_CASE("SharedRealm: convert", "[sync][pbs][convert]") {