blob: 7a1ed04f786e49c8552465d91a61f9f779714cec [file]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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 "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
using ::base::test::TestFuture;
// Fake implementation of GeolocationProvider that can simulate location
// updates.
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;
return {};
}
bool HighAccuracyLocationInUse() override { return false; }
void OverrideLocationForTesting(mojom::GeopositionResultPtr result) override {
}
void SimulateLocationUpdate(const mojom::GeopositionResult& result) {
if (location_update_callback_) {
location_update_callback_.Run(result);
}
}
private:
LocationUpdateCallback location_update_callback_;
};
} // namespace
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_);
geolocation_context_.BindGeolocation(
geolocation_.BindNewPipeAndPassReceiver(), GURL::EmptyGURL());
}
void TearDown() override {
GeolocationProvider::SetInstanceForTesting(nullptr);
}
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, /*error_message=*/"", /*error_technical=*/""));
}
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) {
// Simulate a location update. The estimate returned from QueryNextPosition
// should match the update.
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) {
// Simulate a location update, but the update is an error. The callback is
// called with the error.
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) {
// Query the position before the first location update. The callback is not
// called.
TestFuture<mojom::GeopositionResultPtr> future;
geolocation()->QueryNextPosition(future.GetCallback());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(future.IsReady());
}
TEST_F(GeolocationImplTest, SetAndClearOverride) {
// Simulate a location update.
auto initial_position = MakeGeoposition(37, -122);
SimulateLocationUpdate(*initial_position);
base::RunLoop().RunUntilIdle();
auto override_position = MakeGeoposition(41, 74);
// Set the position override. The callback is called with the overridden
// position.
TestFuture<mojom::GeopositionResultPtr> override_future;
geolocation()->QueryNextPosition(override_future.GetCallback());
SetOverride(*override_position);
EXPECT_EQ(override_future.Get(), override_position);
// Clear the override. The callback is not called.
TestFuture<mojom::GeopositionResultPtr> clear_future;
geolocation()->QueryNextPosition(clear_future.GetCallback());
ClearOverride();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(clear_future.IsReady());
}
TEST_F(GeolocationImplTest, SetAndClearOverrideWithoutUpdate) {
// Query the position before the first location update. The callback is not
// called.
TestFuture<mojom::GeopositionResultPtr> error_future;
geolocation()->QueryNextPosition(error_future.GetCallback());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(error_future.IsReady());
// Set the position override. The callback is called with a GeopositionError.
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);
// Query the position again. The callback is called with the overridden
// position.
TestFuture<mojom::GeopositionResultPtr> override_future;
geolocation()->QueryNextPosition(override_future.GetCallback());
EXPECT_EQ(override_future.Get(), override_position);
// Clear the override. The callback is not called.
TestFuture<mojom::GeopositionResultPtr> clear_future;
geolocation()->QueryNextPosition(clear_future.GetCallback());
ClearOverride();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(clear_future.IsReady());
}
} // namespace device