diff --git a/editor/osm_auth_tests/CMakeLists.txt b/editor/osm_auth_tests/CMakeLists.txt index 0e0a082349..b06924db60 100644 --- a/editor/osm_auth_tests/CMakeLists.txt +++ b/editor/osm_auth_tests/CMakeLists.txt @@ -2,6 +2,7 @@ project(osm_auth_tests) set(SRC osm_auth_tests.cpp + run_on_network_thread.hpp server_api_test.cpp ) diff --git a/editor/osm_auth_tests/osm_auth_tests.cpp b/editor/osm_auth_tests/osm_auth_tests.cpp index 3e3bd257c1..e3d66d110c 100644 --- a/editor/osm_auth_tests/osm_auth_tests.cpp +++ b/editor/osm_auth_tests/osm_auth_tests.cpp @@ -1,38 +1,46 @@ #include "testing/testing.hpp" +#include "run_on_network_thread.hpp" + +#include "platform/platform.hpp" #include "editor/osm_auth.hpp" #include +namespace osm_auth +{ using osm::OsmOAuth; char const * kValidOsmUser = "OrganicMapsTestUser"; char const * kValidOsmPassword = "12345678"; -namespace -{ -constexpr char const * kInvalidOsmPassword = "123"; -constexpr char const * kForgotPasswordEmail = "osmtest1@organicmaps.app"; -} // namespace +static constexpr char const * kInvalidOsmPassword = "123"; +static constexpr char const * kForgotPasswordEmail = "osmtest1@organicmaps.app"; UNIT_TEST(OSM_Auth_InvalidLogin) { - OsmOAuth auth = OsmOAuth::DevServerAuth(); - bool result; - TEST_NO_THROW(result = auth.AuthorizePassword(kValidOsmUser, kInvalidOsmPassword), ()); - TEST_EQUAL(result, false, ("invalid password")); - TEST(!auth.IsAuthorized(), ("Should not be authorized.")); + testing::RunOnNetworkThread([] + { + OsmOAuth auth = OsmOAuth::DevServerAuth(); + bool result; + TEST_NO_THROW(result = auth.AuthorizePassword(kValidOsmUser, kInvalidOsmPassword), ()); + TEST_EQUAL(result, false, ("invalid password")); + TEST(!auth.IsAuthorized(), ("Should not be authorized.")); + }); } UNIT_TEST(OSM_Auth_Login) { - OsmOAuth auth = OsmOAuth::DevServerAuth(); - bool result; - TEST_NO_THROW(result = auth.AuthorizePassword(kValidOsmUser, kValidOsmPassword), ()); - TEST_EQUAL(result, true, ("login to test server")); - TEST(auth.IsAuthorized(), ("Should be authorized.")); - OsmOAuth::Response const perm = auth.Request("/permissions"); - TEST_EQUAL(perm.first, OsmOAuth::HTTP::OK, ("permission request ok")); - TEST_NOT_EQUAL(perm.second.find("write_api"), std::string::npos, ("can write to api")); + testing::RunOnNetworkThread([] + { + OsmOAuth auth = OsmOAuth::DevServerAuth(); + bool result; + TEST_NO_THROW(result = auth.AuthorizePassword(kValidOsmUser, kValidOsmPassword), ()); + TEST_EQUAL(result, true, ("login to test server")); + TEST(auth.IsAuthorized(), ("Should be authorized.")); + OsmOAuth::Response const perm = auth.Request("/permissions"); + TEST_EQUAL(perm.first, OsmOAuth::HTTP::OK, ("permission request ok")); + TEST_NOT_EQUAL(perm.second.find("write_api"), std::string::npos, ("can write to api")); + }); } /* @@ -46,3 +54,4 @@ UNIT_TEST(OSM_Auth_ForgotPassword) TEST_EQUAL(result, false, ("Incorrect email")); } */ +} // namespace osm_auth \ No newline at end of file diff --git a/editor/osm_auth_tests/run_on_network_thread.hpp b/editor/osm_auth_tests/run_on_network_thread.hpp new file mode 100644 index 0000000000..7735475938 --- /dev/null +++ b/editor/osm_auth_tests/run_on_network_thread.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "platform/platform.hpp" + +#include + +namespace testing +{ +inline void RunOnNetworkThread(std::function && f) +{ +#if defined(__APPLE__) + // ThreadRunner's destructor waits until the thread's task is finished. + Platform::ThreadRunner threadRunner; + // Current HttpRequest implementation on Mac requires that it is run on the non-main thread, + // see `dispatch_group_wait(group, DISPATCH_TIME_FOREVER);` + // TODO(AB): Refactor HTTP networking to support async callbacks. + GetPlatform().RunTask(Platform::Thread::Network, [f = std::move(f)] + { + f(); + }); +#else + f(); +#endif +} +} // namespace testing \ No newline at end of file diff --git a/editor/osm_auth_tests/server_api_test.cpp b/editor/osm_auth_tests/server_api_test.cpp index 5615ff0e7c..2aa5f7357b 100644 --- a/editor/osm_auth_tests/server_api_test.cpp +++ b/editor/osm_auth_tests/server_api_test.cpp @@ -1,5 +1,7 @@ #include "testing/testing.hpp" +#include "run_on_network_thread.hpp" + #include "editor/server_api.hpp" #include "geometry/mercator.hpp" @@ -13,6 +15,8 @@ #include +namespace osm_auth +{ using osm::ServerApi06; using osm::OsmOAuth; using namespace pugi; @@ -73,69 +77,76 @@ void DeleteOSMNodeIfExists(ServerApi06 const & api, uint64_t changeSetId, ms::La UNIT_TEST(OSM_ServerAPI_ChangesetAndNode) { - ms::LatLon const kOriginalLocation = RandomCoordinate(); - ms::LatLon const kModifiedLocation = RandomCoordinate(); - - using editor::XMLFeature; - XMLFeature node(XMLFeature::Type::Node); - - ServerApi06 const api = CreateAPI(); - uint64_t changeSetId = api.CreateChangeSet({{"created_by", "OMaps Unit Test"}, - {"comment", "For test purposes only."}}); - auto const changesetCloser = [&]() { api.CloseChangeSet(changeSetId); }; - + testing::RunOnNetworkThread([] { + ms::LatLon const kOriginalLocation = RandomCoordinate(); + ms::LatLon const kModifiedLocation = RandomCoordinate(); + + using editor::XMLFeature; + XMLFeature node(XMLFeature::Type::Node); + + ServerApi06 const api = CreateAPI(); + uint64_t changeSetId = api.CreateChangeSet({{"created_by", "OMaps Unit Test"}, + {"comment", "For test purposes only."}}); + auto const changesetCloser = [&]() { api.CloseChangeSet(changeSetId); }; + + { + SCOPE_GUARD(guard, changesetCloser); + + // Sometimes network can unexpectedly fail (or test exception can be raised), so do some cleanup before unit tests. + DeleteOSMNodeIfExists(api, changeSetId, kOriginalLocation); + DeleteOSMNodeIfExists(api, changeSetId, kModifiedLocation); + + node.SetCenter(kOriginalLocation); + node.SetAttribute("changeset", strings::to_string(changeSetId)); + node.SetAttribute("version", "1"); + node.SetTagValue("testkey", "firstnode"); + + // Pushes node to OSM server and automatically sets node id. + api.CreateElementAndSetAttributes(node); + TEST(!node.GetAttribute("id").empty(), ()); + + // Change node's coordinates and tags. + node.SetCenter(kModifiedLocation); + node.SetTagValue("testkey", "secondnode"); + api.ModifyElementAndSetVersion(node); + // After modification, node version increases in ModifyElement. + TEST_EQUAL(node.GetAttribute("version"), "2", ()); + + // All tags must be specified, because there is no merging of old and new tags. + api.UpdateChangeSet(changeSetId, {{"created_by", "OMaps Unit Test"}, + {"comment", "For test purposes only (updated)."}}); + + // To retrieve created node, changeset should be closed first. + // It is done here via Scope Guard. + } + + auto const response = api.GetXmlFeaturesAtLatLon(kModifiedLocation, 1.0); + TEST_EQUAL(response.first, OsmOAuth::HTTP::OK, ()); + auto const features = XMLFeature::FromOSM(response.second); + TEST_EQUAL(1, features.size(), ()); + TEST_EQUAL(node.GetAttribute("id"), features[0].GetAttribute("id"), ()); + + // Cleanup - delete unit test node from the server. + changeSetId = api.CreateChangeSet({{"created_by", "OMaps Unit Test"}, + {"comment", "For test purposes only."}}); SCOPE_GUARD(guard, changesetCloser); - - // Sometimes network can unexpectedly fail (or test exception can be raised), so do some cleanup before unit tests. - DeleteOSMNodeIfExists(api, changeSetId, kOriginalLocation); - DeleteOSMNodeIfExists(api, changeSetId, kModifiedLocation); - - node.SetCenter(kOriginalLocation); + // New changeset has new id. node.SetAttribute("changeset", strings::to_string(changeSetId)); - node.SetAttribute("version", "1"); - node.SetTagValue("testkey", "firstnode"); - - // Pushes node to OSM server and automatically sets node id. - api.CreateElementAndSetAttributes(node); - TEST(!node.GetAttribute("id").empty(), ()); - - // Change node's coordinates and tags. - node.SetCenter(kModifiedLocation); - node.SetTagValue("testkey", "secondnode"); - api.ModifyElementAndSetVersion(node); - // After modification, node version increases in ModifyElement. - TEST_EQUAL(node.GetAttribute("version"), "2", ()); - - // All tags must be specified, because there is no merging of old and new tags. - api.UpdateChangeSet(changeSetId, {{"created_by", "OMaps Unit Test"}, - {"comment", "For test purposes only (updated)."}}); - - // To retrieve created node, changeset should be closed first. - // It is done here via Scope Guard. - } - - auto const response = api.GetXmlFeaturesAtLatLon(kModifiedLocation, 1.0); - TEST_EQUAL(response.first, OsmOAuth::HTTP::OK, ()); - auto const features = XMLFeature::FromOSM(response.second); - TEST_EQUAL(1, features.size(), ()); - TEST_EQUAL(node.GetAttribute("id"), features[0].GetAttribute("id"), ()); - - // Cleanup - delete unit test node from the server. - changeSetId = api.CreateChangeSet({{"created_by", "OMaps Unit Test"}, - {"comment", "For test purposes only."}}); - SCOPE_GUARD(guard, changesetCloser); - // New changeset has new id. - node.SetAttribute("changeset", strings::to_string(changeSetId)); - TEST_NO_THROW(api.DeleteElement(node), ()); + TEST_NO_THROW(api.DeleteElement(node), ()); + }); } UNIT_TEST(OSM_ServerAPI_Notes) { - ms::LatLon const pos = RandomCoordinate(); - ServerApi06 const api = CreateAPI(); - uint64_t id; - TEST_NO_THROW(id = api.CreateNote(pos, "A test note"), ("Creating a note")); - TEST_GREATER(id, 0, ("Note id should be a positive integer")); - TEST_NO_THROW(api.CloseNote(id), ("Closing a note")); + testing::RunOnNetworkThread([] + { + ms::LatLon const pos = RandomCoordinate(); + ServerApi06 const api = CreateAPI(); + uint64_t id; + TEST_NO_THROW(id = api.CreateNote(pos, "A test note"), ("Creating a note")); + TEST_GREATER(id, 0, ("Note id should be a positive integer")); + TEST_NO_THROW(api.CloseNote(id), ("Closing a note")); + }); } +} // namespace osm_auth diff --git a/platform/http_client_apple.mm b/platform/http_client_apple.mm index dc78cdd1f6..86530e8001 100644 --- a/platform/http_client_apple.mm +++ b/platform/http_client_apple.mm @@ -103,7 +103,7 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); - + RedirectDelegate * delegate = [[RedirectDelegate alloc] init: handleRedirects]; [[[HttpSessionManager sharedManager]