#include "services/device/geolocation/public_ip_address_location_notifier.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/task_environment.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/device/device_service_test_base.h"
#include "services/device/public/cpp/geolocation/geoposition.h"
#include "services/device/public/mojom/geoposition.mojom.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
class PublicIpAddressLocationNotifierTest : public testing::Test {
protected:
class TestPositionQuery {
public:
PublicIpAddressLocationNotifier::QueryNextPositionCallback MakeCallback() {
return base::BindOnce(&TestPositionQuery::OnQueryNextPositionResponse,
base::Unretained(this));
}
void Wait() { loop_.Run(); }
const mojom::GeopositionResult* result() const { return result_.get(); }
private:
void OnQueryNextPositionResponse(mojom::GeopositionResultPtr result) {
result_ = std::move(result);
loop_.Quit();
}
base::RunLoop loop_;
mojom::GeopositionResultPtr result_;
};
PublicIpAddressLocationNotifierTest()
: network_connection_tracker_(
network::TestNetworkConnectionTracker::CreateInstance()),
notifier_(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_),
network::TestNetworkConnectionTracker::GetInstance(),
kTestGeolocationApiKey) {}
~PublicIpAddressLocationNotifierTest() override {}
void RespondToFetchWithLatitude(const float latitude) {
ASSERT_EQ(1, test_url_loader_factory_.NumPending());
const std::string& request_url =
test_url_loader_factory_.pending_requests()->back().request.url.spec();
std::string expected_url =
"https://www.googleapis.com/geolocation/v1/"
"geolocate?key=";
expected_url.append(kTestGeolocationApiKey);
EXPECT_EQ(expected_url, request_url);
const char kNetworkResponseFormatString[] =
R"({
"accuracy": 100.0,
"location": {
"lat": %f,
"lng": 90.0
}
})";
std::string body =
base::StringPrintf(kNetworkResponseFormatString, latitude);
test_url_loader_factory_.AddResponse(request_url, body, net::HTTP_OK);
task_environment_.RunUntilIdle();
test_url_loader_factory_.ClearResponses();
}
void RespondToFetchWithServerError() {
ASSERT_EQ(1, test_url_loader_factory_.NumPending());
const std::string& request_url =
test_url_loader_factory_.pending_requests()->back().request.url.spec();
std::string expected_url =
"https://www.googleapis.com/geolocation/v1/"
"geolocate?key=";
expected_url.append(kTestGeolocationApiKey);
EXPECT_EQ(expected_url, request_url);
test_url_loader_factory_.AddResponse(request_url, std::string(),
net::HTTP_INTERNAL_SERVER_ERROR);
task_environment_.RunUntilIdle();
test_url_loader_factory_.ClearResponses();
}
void ExpectValidPosition(const mojom::GeopositionResult* result,
const float latitude) {
ASSERT_TRUE(result && result->is_position());
EXPECT_TRUE(ValidateGeoposition(*result->get_position()));
EXPECT_FLOAT_EQ(result->get_position()->latitude, latitude);
}
void ExpectError(const mojom::GeopositionResult* result) {
ASSERT_TRUE(result && result->is_error());
EXPECT_THAT(result->get_error()->error_code,
mojom::GeopositionErrorCode::kPositionUnavailable);
}
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<network::TestNetworkConnectionTracker>
network_connection_tracker_;
network::TestURLLoaderFactory test_url_loader_factory_;
PublicIpAddressLocationNotifier notifier_;
};
TEST_F(PublicIpAddressLocationNotifierTest, SingleQueryReturns) {
TestPositionQuery query;
notifier_.QueryNextPosition(base::Time::Now(),
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query.MakeCallback());
RespondToFetchWithLatitude(1.0f);
ExpectValidPosition(query.result(), 1.0f);
}
TEST_F(PublicIpAddressLocationNotifierTest, OlderQueryReturnsCached) {
const auto time = base::Time::Now();
TestPositionQuery query_1;
notifier_.QueryNextPosition(time, PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query_1.MakeCallback());
RespondToFetchWithLatitude(1.0f);
ExpectValidPosition(query_1.result(), 1.0f);
TestPositionQuery query_2;
notifier_.QueryNextPosition(time - base::Minutes(5),
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query_2.MakeCallback());
EXPECT_EQ(0, test_url_loader_factory_.NumPending());
ExpectValidPosition(query_2.result(), 1.0f);
}
TEST_F(PublicIpAddressLocationNotifierTest,
SubsequentQueryWaitsForNetworkChange) {
TestPositionQuery query_1;
notifier_.QueryNextPosition(base::Time::Now(),
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query_1.MakeCallback());
RespondToFetchWithLatitude(1.0f);
ASSERT_TRUE(query_1.result()->is_position());
ExpectValidPosition(query_1.result(), 1.0f);
TestPositionQuery query_2;
notifier_.QueryNextPosition(query_1.result()->get_position()->timestamp,
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query_2.MakeCallback());
EXPECT_EQ(0, test_url_loader_factory_.NumPending());
EXPECT_FALSE(query_2.result());
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
task_environment_.FastForwardUntilNoTasksRemain();
RespondToFetchWithLatitude(2.0f);
ExpectValidPosition(query_2.result(), 2.0f);
}
TEST_F(PublicIpAddressLocationNotifierTest,
ConsecutiveNetworkChangesRequestsOnlyOnce) {
TestPositionQuery query_1;
notifier_.QueryNextPosition(base::Time::Now(),
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query_1.MakeCallback());
RespondToFetchWithLatitude(1.0f);
ASSERT_TRUE(query_1.result()->is_position());
ExpectValidPosition(query_1.result(), 1.0f);
TestPositionQuery query_2;
notifier_.QueryNextPosition(query_1.result()->get_position()->timestamp,
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query_2.MakeCallback());
EXPECT_EQ(0, test_url_loader_factory_.NumPending());
EXPECT_FALSE(query_2.result());
for (int i = 0; i < 10; ++i) {
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
task_environment_.FastForwardBy(base::Seconds(5));
}
EXPECT_EQ(0, test_url_loader_factory_.NumPending());
EXPECT_FALSE(query_2.result());
task_environment_.FastForwardUntilNoTasksRemain();
RespondToFetchWithLatitude(2.0f);
ExpectValidPosition(query_2.result(), 2.0f);
}
TEST_F(PublicIpAddressLocationNotifierTest, MutipleWaitingQueries) {
TestPositionQuery query_1;
notifier_.QueryNextPosition(base::Time::Now(),
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query_1.MakeCallback());
RespondToFetchWithLatitude(1.0f);
ASSERT_TRUE(query_1.result()->is_position());
ExpectValidPosition(query_1.result(), 1.0f);
TestPositionQuery query_2;
notifier_.QueryNextPosition(query_1.result()->get_position()->timestamp,
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query_2.MakeCallback());
TestPositionQuery query_3;
notifier_.QueryNextPosition(query_1.result()->get_position()->timestamp,
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query_3.MakeCallback());
EXPECT_EQ(0, test_url_loader_factory_.NumPending());
EXPECT_FALSE(query_2.result());
EXPECT_FALSE(query_3.result());
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_UNKNOWN);
task_environment_.FastForwardUntilNoTasksRemain();
RespondToFetchWithLatitude(2.0f);
ExpectValidPosition(query_2.result(), 2.0f);
ExpectValidPosition(query_3.result(), 2.0f);
}
TEST_F(PublicIpAddressLocationNotifierTest, ServerError) {
TestPositionQuery query;
notifier_.QueryNextPosition(base::Time::Now(),
PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS,
query.MakeCallback());
RespondToFetchWithServerError();
ExpectError(query.result());
}
}