-
Notifications
You must be signed in to change notification settings - Fork 169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RCORE-2060 Enabling 'cancel_waits_on_nonfatal_error' does not cancel waits during location update while offline #7528
Changes from 9 commits
2990263
b5d2dc5
c060daf
91a54f1
5650107
ea76f65
0776dd8
7298787
5ac829b
b3bb528
3bad95e
3c158a9
6e5f0a4
3f29c30
a6d55e9
1ef3639
52a1d54
669d09c
b83a346
b2e7406
5830b46
c1a25c0
dce8409
a34d5f6
2e0a68f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,6 +45,7 @@ | |
|
||
#if REALM_ENABLE_SYNC | ||
#include <util/sync/flx_sync_harness.hpp> | ||
#include <util/sync/sync_test_utils.hpp> | ||
|
||
#include <realm/object-store/sync/async_open_task.hpp> | ||
#include <realm/object-store/sync/impl/sync_metadata.hpp> | ||
|
@@ -1206,6 +1207,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<HookedTransport<UnitTestTransport>>(); | ||
auto socket_provider = std::make_shared<HookedSocketProvider>(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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we add a test (maybe already have one?) when this is false? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's needed... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, what I meant is to test that behavior and expectations too (in a separate test not as a section). But I think you added a test in the previous PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the tests added in the previous PR should test the normal cases; although I could add an additional test specifically for async open if you think that is necessary. |
||
config.sync_config->error_handler = [&logger](std::shared_ptr<SyncSession> 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 (error.status.code() == ErrorCodes::AuthError) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. looking at the websocket replies, these are the only two errors allowed. you could just:
|
||
REQUIRE(error.is_fatal); | ||
} | ||
}; | ||
|
||
auto valid_token = user->access_token(); | ||
// User should be logged in at this point | ||
michael-wb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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") { | ||
michael-wb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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") { | ||
michael-wb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
logger->trace(">>> access token expired by websocket - mode: %1", mode); | ||
not_authorized = true; | ||
} | ||
SECTION("access token expired when websocket connects") { | ||
logger->trace(">>> websocket returns connection failed - mode: %1", mode); | ||
} | ||
|
||
transport->request_hook = [&](const app::Request& req) -> std::optional<app::Response> { | ||
std::lock_guard<std::mutex> lock(mutex); | ||
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"}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the 28 have some special significance? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really - it just corresponded with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a define for the value to try to explain why the value is being used. |
||
} | ||
} | ||
return std::nullopt; | ||
}; | ||
|
||
socket_provider->websocket_connect_func = [&]() -> std::optional<SocketProviderError> { | ||
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<std::exception_ptr>(); | ||
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<std::mutex> lock(mutex); | ||
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); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't appear to be used anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was previously using this to get the exception thrown in the Async open start callback, but not anymore. Removed this unused code.