|  | // Copyright 2017 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "components/language/content/browser/geo_language_provider.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/macros.h" | 
|  | #include "base/test/test_mock_time_task_runner.h" | 
|  | #include "base/timer/timer.h" | 
|  | #include "mojo/public/cpp/bindings/binding.h" | 
|  | #include "services/device/public/mojom/constants.mojom.h" | 
|  | #include "services/device/public/mojom/geolocation.mojom.h" | 
|  | #include "services/device/public/mojom/public_ip_address_geolocation_provider.mojom.h" | 
|  | #include "services/service_manager/public/cpp/connector.h" | 
|  | #include "services/service_manager/public/mojom/connector.mojom.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Mock impl of mojom::Geolocation that allows tests to control the returned | 
|  | // location. | 
|  | class MockGeoLocation : public device::mojom::Geolocation { | 
|  | public: | 
|  | MockGeoLocation() : binding_(this) {} | 
|  | // device::mojom::Geolocation implementation: | 
|  | void SetHighAccuracy(bool high_accuracy) override {} | 
|  | void QueryNextPosition(QueryNextPositionCallback callback) override { | 
|  | ++query_next_position_called_times_; | 
|  | std::move(callback).Run(position_.Clone()); | 
|  | } | 
|  |  | 
|  | void BindGeoLocation(device::mojom::GeolocationRequest request) { | 
|  | binding_.Bind(std::move(request)); | 
|  | } | 
|  |  | 
|  | void MoveToLocation(float latitude, float longitude) { | 
|  | position_.latitude = latitude; | 
|  | position_.longitude = longitude; | 
|  | } | 
|  |  | 
|  | int query_next_position_called_times() const { | 
|  | return query_next_position_called_times_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | int query_next_position_called_times_ = 0; | 
|  | device::mojom::Geoposition position_; | 
|  | mojo::Binding<device::mojom::Geolocation> binding_; | 
|  | }; | 
|  |  | 
|  | // Mock impl of mojom::PublicIpAddressGeolocationProvider that binds Geolocation | 
|  | // to testing impl. | 
|  | class MockIpGeoLocationProvider | 
|  | : public device::mojom::PublicIpAddressGeolocationProvider { | 
|  | public: | 
|  | explicit MockIpGeoLocationProvider(MockGeoLocation* mock_geo_location) | 
|  | : mock_geo_location_(mock_geo_location), binding_(this) {} | 
|  |  | 
|  | void Bind(mojo::ScopedMessagePipeHandle handle) { | 
|  | binding_.Bind(device::mojom::PublicIpAddressGeolocationProviderRequest( | 
|  | std::move(handle))); | 
|  | } | 
|  |  | 
|  | void CreateGeolocation( | 
|  | const net::MutablePartialNetworkTrafficAnnotationTag& /* unused */, | 
|  | device::mojom::GeolocationRequest request) override { | 
|  | mock_geo_location_->BindGeoLocation(std::move(request)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | MockGeoLocation* mock_geo_location_; | 
|  |  | 
|  | mojo::Binding<device::mojom::PublicIpAddressGeolocationProvider> binding_; | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | namespace language { | 
|  |  | 
|  | class GeoLanguageProviderTest : public testing::Test { | 
|  | public: | 
|  | GeoLanguageProviderTest() | 
|  | : task_runner_(base::MakeRefCounted<base::TestMockTimeTaskRunner>( | 
|  | base::TestMockTimeTaskRunner::Type::kBoundToThread)), | 
|  | scoped_context_(task_runner_.get()), | 
|  | geo_language_provider_(task_runner_), | 
|  | mock_ip_geo_location_provider_(&mock_geo_location_) { | 
|  | service_manager::mojom::ConnectorRequest request; | 
|  | connector_ = service_manager::Connector::Create(&request); | 
|  | service_manager::Connector::TestApi test_api(connector_.get()); | 
|  | test_api.OverrideBinderForTesting( | 
|  | service_manager::Identity(device::mojom::kServiceName), | 
|  | device::mojom::PublicIpAddressGeolocationProvider::Name_, | 
|  | base::BindRepeating(&MockIpGeoLocationProvider::Bind, | 
|  | base::Unretained(&mock_ip_geo_location_provider_))); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | std::vector<std::string> GetCurrentGeoLanguages() { | 
|  | return geo_language_provider_.CurrentGeoLanguages(); | 
|  | } | 
|  |  | 
|  | void StartGeoLanguageProvider() { | 
|  | geo_language_provider_.StartUp(std::move(connector_)); | 
|  | } | 
|  |  | 
|  | void MoveToLocation(float latitude, float longitude) { | 
|  | mock_geo_location_.MoveToLocation(latitude, longitude); | 
|  | } | 
|  |  | 
|  | const scoped_refptr<base::TestMockTimeTaskRunner>& GetTaskRunner() { | 
|  | return task_runner_; | 
|  | } | 
|  |  | 
|  | int GetQueryNextPositionCalledTimes() { | 
|  | return mock_geo_location_.query_next_position_called_times(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | scoped_refptr<base::TestMockTimeTaskRunner> task_runner_; | 
|  | const base::TestMockTimeTaskRunner::ScopedContext scoped_context_; | 
|  |  | 
|  | // Object under test. | 
|  | GeoLanguageProvider geo_language_provider_; | 
|  | MockGeoLocation mock_geo_location_; | 
|  | MockIpGeoLocationProvider mock_ip_geo_location_provider_; | 
|  | std::unique_ptr<service_manager::Connector> connector_; | 
|  | }; | 
|  |  | 
|  | TEST_F(GeoLanguageProviderTest, GetCurrentGeoLanguages) { | 
|  | // Setup a random place in Madhya Pradesh, India. | 
|  | MoveToLocation(23.0, 80.0); | 
|  | StartGeoLanguageProvider(); | 
|  | const auto task_runner = GetTaskRunner(); | 
|  | task_runner->RunUntilIdle(); | 
|  |  | 
|  | const std::vector<std::string>& result = GetCurrentGeoLanguages(); | 
|  | std::vector<std::string> expected_langs = {"hi", "mr", "ur"}; | 
|  | EXPECT_EQ(expected_langs, result); | 
|  | EXPECT_EQ(1, GetQueryNextPositionCalledTimes()); | 
|  | } | 
|  |  | 
|  | TEST_F(GeoLanguageProviderTest, NoFrequentCalls) { | 
|  | // Setup a random place in Madhya Pradesh, India. | 
|  | MoveToLocation(23.0, 80.0); | 
|  | StartGeoLanguageProvider(); | 
|  | const auto task_runner = GetTaskRunner(); | 
|  | task_runner->RunUntilIdle(); | 
|  |  | 
|  | const std::vector<std::string>& result = GetCurrentGeoLanguages(); | 
|  | std::vector<std::string> expected_langs = {"hi", "mr", "ur"}; | 
|  | EXPECT_EQ(expected_langs, result); | 
|  |  | 
|  | task_runner->FastForwardBy(base::TimeDelta::FromHours(12)); | 
|  | EXPECT_EQ(1, GetQueryNextPositionCalledTimes()); | 
|  | } | 
|  |  | 
|  | TEST_F(GeoLanguageProviderTest, ButDoCallInTheNextDay) { | 
|  | // Setup a random place in Madhya Pradesh, India. | 
|  | MoveToLocation(23.0, 80.0); | 
|  | StartGeoLanguageProvider(); | 
|  | const auto task_runner = GetTaskRunner(); | 
|  | task_runner->RunUntilIdle(); | 
|  |  | 
|  | std::vector<std::string> result = GetCurrentGeoLanguages(); | 
|  | std::vector<std::string> expected_langs = {"hi", "mr", "ur"}; | 
|  | EXPECT_EQ(expected_langs, result); | 
|  |  | 
|  | // Move to another random place in Karnataka, India. | 
|  | MoveToLocation(15.0, 75.0); | 
|  | task_runner->FastForwardBy(base::TimeDelta::FromHours(25)); | 
|  | EXPECT_EQ(2, GetQueryNextPositionCalledTimes()); | 
|  |  | 
|  | result = GetCurrentGeoLanguages(); | 
|  | std::vector<std::string> expected_langs_2 = {"kn", "ur", "te", "mr", "ta"}; | 
|  | EXPECT_EQ(expected_langs_2, result); | 
|  | } | 
|  |  | 
|  | }  // namespace language |