#include "services/device/geolocation/geolocation_impl.h"
#include <memory>
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/network_change_notifier.h"
#include "services/device/geolocation/geolocation_context.h"
#include "services/device/geolocation/geolocation_provider.h"
#include "services/device/public/mojom/geolocation_client_id.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
using ::base::test::TestFuture;
class FakeGeolocationProvider : public GeolocationProvider {
public:
FakeGeolocationProvider() = default;
FakeGeolocationProvider(const FakeGeolocationProvider&) = delete;
FakeGeolocationProvider& operator=(const FakeGeolocationProvider&) = delete;
~FakeGeolocationProvider() override = default;
base::CallbackListSubscription AddLocationUpdateCallback(
const LocationUpdateCallback& callback,
bool enable_high_accuracy) override {
location_update_callback_ = callback;
last_set_accuracy_ = enable_high_accuracy;
add_callback_count_++;
return {};
}
void OverrideLocationForTesting(mojom::GeopositionResultPtr result) override {
}
void SimulateLocationUpdate(const mojom::GeopositionResult& result) {
if (location_update_callback_) {
location_update_callback_.Run(result);
}
}
bool GetLastSetAccuracy() const { return last_set_accuracy_; }
int GetAddCallbackCount() const { return add_callback_count_; }
private:
LocationUpdateCallback location_update_callback_;
bool last_set_accuracy_;
int add_callback_count_ = 0;
};
}
class GeolocationImplTest : public testing::Test {
public:
GeolocationImplTest()
: network_change_notifier_(
net::NetworkChangeNotifier::CreateMockIfNeeded()) {}
GeolocationImplTest(const GeolocationImplTest&) = delete;
GeolocationImplTest& operator=(const GeolocationImplTest&) = delete;
~GeolocationImplTest() override = default;
void SetUp() override {
GeolocationProvider::SetInstanceForTesting(&geolocation_provider_);
BindGeolocation(true);
}
void TearDown() override {
GeolocationProvider::SetInstanceForTesting(nullptr);
}
void BindGeolocation(bool has_precise_permission) {
geolocation_.reset();
geolocation_context_.BindGeolocation(
geolocation_.BindNewPipeAndPassReceiver(), GURL("https://test.com"),
mojom::GeolocationClientId::kForTesting,
true);
}
void OnPermissionUpdated(mojom::GeolocationPermissionLevel permission_level) {
geolocation_context_.OnPermissionUpdated(
url::Origin::Create(GURL("https://test.com")), permission_level);
}
bool GetLastSetAccuracy() {
return geolocation_provider_.GetLastSetAccuracy();
}
int GetAddCallbackCount() {
return geolocation_provider_.GetAddCallbackCount();
}
void SimulateLocationUpdate(const mojom::GeopositionResult& result) {
geolocation_provider_.SimulateLocationUpdate(result);
}
void SetOverride(const mojom::GeopositionResult& result) {
geolocation_context_.SetOverride(result.Clone());
}
void ClearOverride() { geolocation_context_.ClearOverride(); }
mojom::GeopositionResultPtr MakeGeoposition(double latitude,
double longitude) {
auto position = mojom::Geoposition::New();
position->latitude = latitude;
position->longitude = longitude;
position->accuracy = 100;
position->timestamp = base::Time::Now();
return mojom::GeopositionResult::NewPosition(std::move(position));
}
mojom::GeopositionResultPtr MakeGeopositionError(
mojom::GeopositionErrorCode error_code) {
return mojom::GeopositionResult::NewError(mojom::GeopositionError::New(
error_code, "", ""));
}
const mojo::Remote<mojom::Geolocation>& geolocation() { return geolocation_; }
private:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_;
FakeGeolocationProvider geolocation_provider_;
GeolocationContext geolocation_context_;
mojo::Remote<mojom::Geolocation> geolocation_;
};
TEST_F(GeolocationImplTest, QueryNextPosition) {
TestFuture<mojom::GeopositionResultPtr> future;
geolocation()->QueryNextPosition(future.GetCallback());
base::RunLoop().RunUntilIdle();
auto position = MakeGeoposition(37, -122);
SimulateLocationUpdate(*position);
EXPECT_EQ(future.Get(), position);
}
TEST_F(GeolocationImplTest, QueryNextPositionError) {
TestFuture<mojom::GeopositionResultPtr> future;
geolocation()->QueryNextPosition(future.GetCallback());
base::RunLoop().RunUntilIdle();
auto error =
MakeGeopositionError(mojom::GeopositionErrorCode::kPositionUnavailable);
SimulateLocationUpdate(*error);
EXPECT_EQ(future.Get(), error);
}
TEST_F(GeolocationImplTest, QueryNextPositionWithoutUpdate) {
TestFuture<mojom::GeopositionResultPtr> future;
geolocation()->QueryNextPosition(future.GetCallback());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(future.IsReady());
}
TEST_F(GeolocationImplTest, SetAndClearOverride) {
auto initial_position = MakeGeoposition(37, -122);
SimulateLocationUpdate(*initial_position);
base::RunLoop().RunUntilIdle();
auto override_position = MakeGeoposition(41, 74);
TestFuture<mojom::GeopositionResultPtr> override_future;
geolocation()->QueryNextPosition(override_future.GetCallback());
SetOverride(*override_position);
EXPECT_EQ(override_future.Get(), override_position);
TestFuture<mojom::GeopositionResultPtr> clear_future;
geolocation()->QueryNextPosition(clear_future.GetCallback());
ClearOverride();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(clear_future.IsReady());
}
TEST_F(GeolocationImplTest, SetAndClearOverrideWithoutUpdate) {
TestFuture<mojom::GeopositionResultPtr> error_future;
geolocation()->QueryNextPosition(error_future.GetCallback());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(error_future.IsReady());
auto override_position = MakeGeoposition(41, 74);
SetOverride(*override_position);
ASSERT_TRUE(error_future.Get()->is_error());
EXPECT_EQ(error_future.Get()->get_error()->error_code,
mojom::GeopositionErrorCode::kPositionUnavailable);
TestFuture<mojom::GeopositionResultPtr> override_future;
geolocation()->QueryNextPosition(override_future.GetCallback());
EXPECT_EQ(override_future.Get(), override_position);
TestFuture<mojom::GeopositionResultPtr> clear_future;
geolocation()->QueryNextPosition(clear_future.GetCallback());
ClearOverride();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(clear_future.IsReady());
}
TEST_F(GeolocationImplTest, PermissionDenied) {
TestFuture<mojom::GeopositionResultPtr> future;
geolocation()->QueryNextPosition(future.GetCallback());
base::RunLoop().RunUntilIdle();
OnPermissionUpdated(mojom::GeolocationPermissionLevel::kDenied);
auto result = future.Take();
ASSERT_TRUE(result->is_error());
EXPECT_EQ(result->get_error()->error_code,
mojom::GeopositionErrorCode::kPermissionDenied);
}
TEST_F(GeolocationImplTest, OnPermissionUpdated) {
geolocation()->SetHighAccuracyHint(true);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetLastSetAccuracy());
OnPermissionUpdated(mojom::GeolocationPermissionLevel::kApproximate);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetLastSetAccuracy());
geolocation()->SetHighAccuracyHint(true);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetLastSetAccuracy());
OnPermissionUpdated(mojom::GeolocationPermissionLevel::kPrecise);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetLastSetAccuracy());
geolocation()->SetHighAccuracyHint(false);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetLastSetAccuracy());
}
TEST_F(GeolocationImplTest, EffectiveHighAccuracy) {
EXPECT_EQ(1, GetAddCallbackCount());
geolocation()->SetHighAccuracyHint(false);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetLastSetAccuracy());
EXPECT_EQ(1, GetAddCallbackCount());
geolocation()->SetHighAccuracyHint(true);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetLastSetAccuracy());
EXPECT_EQ(2, GetAddCallbackCount());
geolocation()->SetHighAccuracyHint(true);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetLastSetAccuracy());
EXPECT_EQ(2, GetAddCallbackCount());
OnPermissionUpdated(mojom::GeolocationPermissionLevel::kApproximate);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetLastSetAccuracy());
EXPECT_EQ(3, GetAddCallbackCount());
OnPermissionUpdated(mojom::GeolocationPermissionLevel::kApproximate);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetLastSetAccuracy());
EXPECT_EQ(3, GetAddCallbackCount());
OnPermissionUpdated(mojom::GeolocationPermissionLevel::kPrecise);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetLastSetAccuracy());
EXPECT_EQ(4, GetAddCallbackCount());
geolocation()->SetHighAccuracyHint(false);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetLastSetAccuracy());
EXPECT_EQ(5, GetAddCallbackCount());
geolocation()->SetHighAccuracyHint(false);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetLastSetAccuracy());
EXPECT_EQ(5, GetAddCallbackCount());
}
}