chromium/services/device/geolocation/win/location_provider_winrt_unittest.cc

// Copyright 2019 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/win/location_provider_winrt.h"

#include <memory>
#include <optional>
#include <utility>

#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/win/scoped_winrt_initializer.h"
#include "services/device/geolocation/win/fake_geocoordinate_winrt.h"
#include "services/device/geolocation/win/fake_geolocator_winrt.h"
#include "services/device/public/cpp/geolocation/geoposition.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace device {
namespace {
using ABI::Windows::Devices::Geolocation::IGeolocator;
using ABI::Windows::Devices::Geolocation::PositionStatus;
using ABI::Windows::Devices::Geolocation::AltitudeReferenceSystem::
    AltitudeReferenceSystem_Terrain;

class MockLocationObserver {
 public:
  explicit MockLocationObserver(base::OnceClosure update_called)
      : update_called_(std::move(update_called)) {}
  ~MockLocationObserver() = default;

  void InvalidateLastPosition() { last_result_.reset(); }

  void OnLocationUpdate(const LocationProvider* provider,
                        mojom::GeopositionResultPtr result) {
    last_result_ = std::move(result);
    on_location_update_called_ = true;
    std::move(update_called_).Run();
  }

  const mojom::GeopositionResult* GetLastResult() { return last_result_.get(); }

  bool on_location_update_called() { return on_location_update_called_; }

 private:
  base::OnceClosure update_called_;
  mojom::GeopositionResultPtr last_result_;
  bool on_location_update_called_ = false;
};

}  // namespace

class TestingLocationProviderWinrt : public LocationProviderWinrt {
 public:
  TestingLocationProviderWinrt(
      std::unique_ptr<FakeGeocoordinateData> position_data,
      PositionStatus position_status)
      : position_data_(std::move(position_data)),
        position_status_(position_status) {}

  bool HasPermissionBeenGrantedForTest() { return permission_granted_; }

  bool IsHighAccuracyEnabled() { return enable_high_accuracy_; }

  std::optional<EventRegistrationToken> GetStatusChangedToken() {
    return status_changed_token_;
  }

  std::optional<EventRegistrationToken> GetPositionChangedToken() {
    return position_changed_token_;
  }

  HRESULT GetGeolocator(IGeolocator** geo_locator) override {
    *geo_locator = Microsoft::WRL::Make<FakeGeolocatorWinrt>(
                       std::move(position_data_), position_status_)
                       .Detach();
    return S_OK;
  }

 private:
  std::unique_ptr<FakeGeocoordinateData> position_data_;
  const PositionStatus position_status_;
};

class LocationProviderWinrtTest : public testing::Test {
 protected:
  LocationProviderWinrtTest()
      : observer_(
            std::make_unique<MockLocationObserver>(run_loop_.QuitClosure())),
        callback_(base::BindRepeating(&MockLocationObserver::OnLocationUpdate,
                                      base::Unretained(observer_.get()))) {}

  void SetUp() override {
    winrt_initializer_.emplace();
    ASSERT_TRUE(winrt_initializer_->Succeeded());
  }

  void InitializeProvider(
      PositionStatus position_status = PositionStatus::PositionStatus_Ready) {
    auto test_data = FakeGeocoordinateData();
    test_data.longitude = 0;
    test_data.latitude = 0;
    test_data.accuracy = 0;
    InitializeProvider(test_data, position_status);
  }

  void InitializeProvider(
      FakeGeocoordinateData position_data,
      PositionStatus position_status = PositionStatus::PositionStatus_Ready) {
    provider_ = std::make_unique<TestingLocationProviderWinrt>(
        std::make_unique<FakeGeocoordinateData>(position_data),
        position_status);
    provider_->SetUpdateCallback(callback_);
  }

  mojom::GeolocationDiagnostics::ProviderState GetProviderState() {
    mojom::GeolocationDiagnostics diagnostics;
    provider_->FillDiagnostics(diagnostics);
    return diagnostics.provider_state;
  }

  base::test::TaskEnvironment task_environment_;
  base::RunLoop run_loop_;
  std::optional<base::win::ScopedWinrtInitializer> winrt_initializer_;
  const std::unique_ptr<MockLocationObserver> observer_;
  const LocationProvider::LocationProviderUpdateCallback callback_;
  std::unique_ptr<TestingLocationProviderWinrt> provider_;
};

TEST_F(LocationProviderWinrtTest, CreateDestroy) {
  InitializeProvider();
  EXPECT_TRUE(provider_);
  EXPECT_EQ(GetProviderState(),
            mojom::GeolocationDiagnostics::ProviderState::kStopped);
  provider_.reset();
}

TEST_F(LocationProviderWinrtTest, OnPermissionGranted) {
  InitializeProvider();
  EXPECT_FALSE(provider_->HasPermissionBeenGrantedForTest());
  provider_->OnPermissionGranted();
  EXPECT_TRUE(provider_->HasPermissionBeenGrantedForTest());
}

TEST_F(LocationProviderWinrtTest, SetAccuracyOptions) {
  InitializeProvider();
  provider_->StartProvider(/*enable_high_accuracy=*/false);
  EXPECT_EQ(false, provider_->IsHighAccuracyEnabled());
  provider_->StartProvider(/*enable_high_accuracy=*/true);
  EXPECT_EQ(true, provider_->IsHighAccuracyEnabled());
}

// Tests when OnPermissionGranted() called location update is provided.
TEST_F(LocationProviderWinrtTest, HasPermissions) {
  auto test_data = FakeGeocoordinateData();
  test_data.longitude = 1;
  test_data.latitude = 2;
  test_data.accuracy = 3;
  test_data.speed = 4;

  InitializeProvider(test_data);
  provider_->OnPermissionGranted();
  provider_->StartProvider(/*enable_high_accuracy=*/false);

  EXPECT_FALSE(observer_->on_location_update_called());
  EXPECT_FALSE(observer_->GetLastResult());

  EXPECT_EQ(GetProviderState(),
            mojom::GeolocationDiagnostics::ProviderState::kLowAccuracy);
  EXPECT_TRUE(provider_->GetStatusChangedToken().has_value());
  EXPECT_TRUE(provider_->GetPositionChangedToken().has_value());

  run_loop_.Run();

  EXPECT_TRUE(observer_->on_location_update_called());
  auto* last_result = observer_->GetLastResult();
  ASSERT_TRUE(last_result && last_result->is_position());
  auto& position = *last_result->get_position();
  EXPECT_TRUE(ValidateGeoposition(position));
  EXPECT_EQ(position.latitude, test_data.latitude);
  EXPECT_EQ(position.longitude, test_data.longitude);
  EXPECT_EQ(position.accuracy, test_data.accuracy);
  EXPECT_EQ(position.altitude, device::mojom::kBadAltitude);
  EXPECT_EQ(position.altitude_accuracy, device::mojom::kBadAccuracy);
  EXPECT_EQ(position.speed, test_data.speed.value());
  EXPECT_EQ(position.heading, device::mojom::kBadHeading);
}

// Tests when OnPermissionGranted() called location update is provided with all
// possible values populated.
TEST_F(LocationProviderWinrtTest, HasPermissionsAllValues) {
  auto test_data = FakeGeocoordinateData();
  test_data.longitude = 1;
  test_data.latitude = 2;
  test_data.accuracy = 3;
  test_data.altitude = 4;
  test_data.altitude_accuracy = 5;
  test_data.heading = 6;
  test_data.speed = 7;
  test_data.altitude_reference_system = ABI::Windows::Devices::Geolocation::
      AltitudeReferenceSystem::AltitudeReferenceSystem_Ellipsoid;

  InitializeProvider(test_data);
  provider_->OnPermissionGranted();
  provider_->StartProvider(/*enable_high_accuracy=*/false);

  EXPECT_FALSE(observer_->on_location_update_called());
  EXPECT_FALSE(observer_->GetLastResult());

  EXPECT_EQ(GetProviderState(),
            mojom::GeolocationDiagnostics::ProviderState::kLowAccuracy);
  EXPECT_TRUE(provider_->GetStatusChangedToken().has_value());
  EXPECT_TRUE(provider_->GetPositionChangedToken().has_value());

  run_loop_.Run();

  EXPECT_TRUE(observer_->on_location_update_called());
  auto* last_result = observer_->GetLastResult();
  ASSERT_TRUE(last_result && last_result->is_position());
  auto& position = *last_result->get_position();
  EXPECT_TRUE(ValidateGeoposition(position));
  EXPECT_EQ(position.latitude, test_data.latitude);
  EXPECT_EQ(position.longitude, test_data.longitude);
  EXPECT_EQ(position.accuracy, test_data.accuracy);
  EXPECT_EQ(position.altitude, test_data.altitude.value());
  EXPECT_EQ(position.altitude_accuracy, test_data.altitude_accuracy.value());
  EXPECT_EQ(position.speed, test_data.speed.value());
  EXPECT_EQ(position.heading, test_data.heading.value());
}

// Tests when provider is stopped and started quickly access errors
// do not occur and location update is not called.
TEST_F(LocationProviderWinrtTest, StartStopProviderRunTasks) {
  InitializeProvider();
  provider_->OnPermissionGranted();
  provider_->StartProvider(/*enable_high_accuracy=*/false);
  EXPECT_EQ(GetProviderState(),
            mojom::GeolocationDiagnostics::ProviderState::kLowAccuracy);
  provider_->StopProvider();
  EXPECT_EQ(GetProviderState(),
            mojom::GeolocationDiagnostics::ProviderState::kStopped);

  EXPECT_FALSE(observer_->on_location_update_called());
  EXPECT_FALSE(observer_->GetLastResult());

  run_loop_.RunUntilIdle();

  EXPECT_FALSE(observer_->on_location_update_called());
  EXPECT_FALSE(provider_->GetStatusChangedToken().has_value());
  EXPECT_FALSE(provider_->GetPositionChangedToken().has_value());
}

// Tests when OnPermissionGranted() has not been called location update
// is not provided.
TEST_F(LocationProviderWinrtTest, NoPermissions) {
  InitializeProvider();
  provider_->StartProvider(/*enable_high_accuracy=*/false);

  EXPECT_EQ(
      GetProviderState(),
      mojom::GeolocationDiagnostics::ProviderState::kBlockedBySystemPermission);
  EXPECT_FALSE(observer_->on_location_update_called());
  EXPECT_FALSE(observer_->GetLastResult());

  run_loop_.RunUntilIdle();

  EXPECT_FALSE(observer_->on_location_update_called());
  EXPECT_FALSE(provider_->GetStatusChangedToken().has_value());
  EXPECT_FALSE(provider_->GetPositionChangedToken().has_value());
}

// Tests when a PositionStatus_Disabled is returned from the OS indicating
// access to location on the OS is disabled, a permission denied is returned.
TEST_F(LocationProviderWinrtTest, PositionStatusDisabledOsPermissions) {
  InitializeProvider(PositionStatus::PositionStatus_Disabled);
  provider_->OnPermissionGranted();
  provider_->StartProvider(/*enable_high_accuracy=*/false);

  EXPECT_EQ(GetProviderState(),
            mojom::GeolocationDiagnostics::ProviderState::kLowAccuracy);
  EXPECT_FALSE(observer_->on_location_update_called());
  EXPECT_FALSE(observer_->GetLastResult());

  EXPECT_TRUE(provider_->GetStatusChangedToken().has_value());
  EXPECT_TRUE(provider_->GetPositionChangedToken().has_value());

  run_loop_.Run();

  EXPECT_TRUE(observer_->on_location_update_called());
  auto* last_result = observer_->GetLastResult();
  ASSERT_TRUE(last_result && last_result->is_error());
  EXPECT_EQ(last_result->get_error()->error_code,
            mojom::GeopositionErrorCode::kPermissionDenied);
}

// Tests when the altitude reference system is not Ellipsoid.
// In this case, the altitude should be set to device::mojom::kBadAltitude.
TEST_F(LocationProviderWinrtTest, NonEllipsoidAltitudeReferenceSystem) {
  auto test_data = FakeGeocoordinateData();
  test_data.longitude = 1;
  test_data.latitude = 2;
  test_data.accuracy = 3;
  test_data.altitude = 4;
  test_data.altitude_accuracy = 5;
  test_data.altitude_reference_system = AltitudeReferenceSystem_Terrain;

  InitializeProvider(test_data);
  provider_->OnPermissionGranted();
  provider_->StartProvider(/*enable_high_accuracy=*/false);

  EXPECT_FALSE(observer_->on_location_update_called());
  EXPECT_FALSE(observer_->GetLastResult());

  EXPECT_EQ(GetProviderState(),
            mojom::GeolocationDiagnostics::ProviderState::kLowAccuracy);
  EXPECT_TRUE(provider_->GetStatusChangedToken().has_value());
  EXPECT_TRUE(provider_->GetPositionChangedToken().has_value());

  run_loop_.Run();

  EXPECT_TRUE(observer_->on_location_update_called());
  auto* last_result = observer_->GetLastResult();
  ASSERT_TRUE(last_result && last_result->is_position());
  auto& position = *last_result->get_position();
  EXPECT_TRUE(ValidateGeoposition(position));
  EXPECT_EQ(position.latitude, test_data.latitude);
  EXPECT_EQ(position.longitude, test_data.longitude);
  EXPECT_EQ(position.accuracy, test_data.accuracy);
  EXPECT_EQ(position.altitude, device::mojom::kBadAltitude);
}

}  // namespace device