blob: 5eb58916a93853009c5ecfb2f5da214aeadfc6a4 [file] [log] [blame]
// Copyright (c) 2012 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 "device/geolocation/network_location_provider.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "device/geolocation/fake_access_token_store.h"
#include "device/geolocation/location_arbitrator.h"
#include "device/geolocation/wifi_data_provider.h"
#include "net/base/net_errors.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_status.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
// Constants used in multiple tests.
const char kTestServerUrl[] = "https://www.geolocation.test/service";
const char kAccessTokenString[] = "accessToken";
// Using #define so we can easily paste this into various other strings.
#define REFERENCE_ACCESS_TOKEN "2:k7j3G6LaL6u_lafw:4iXOeOpTh1glSXe"
// Stops the specified (nested) message loop when the listener is called back.
class MessageLoopQuitListener {
public:
MessageLoopQuitListener()
: client_message_loop_(base::MessageLoop::current()),
updated_provider_(nullptr) {
CHECK(client_message_loop_);
}
void OnLocationUpdate(const LocationProvider* provider,
const Geoposition& position) {
EXPECT_EQ(client_message_loop_, base::MessageLoop::current());
updated_provider_ = provider;
}
base::MessageLoop* client_message_loop_;
const LocationProvider* updated_provider_;
};
// A mock implementation of WifiDataProvider for testing. Adapted from
// http://gears.googlecode.com/svn/trunk/gears/geolocation/geolocation_test.cc
class MockWifiDataProvider : public WifiDataProvider {
public:
// Factory method for use with WifiDataProvider::SetFactoryForTesting.
static WifiDataProvider* GetInstance() {
CHECK(instance_);
return instance_;
}
static MockWifiDataProvider* CreateInstance() {
CHECK(!instance_);
instance_ = new MockWifiDataProvider;
return instance_;
}
MockWifiDataProvider() : start_calls_(0), stop_calls_(0), got_data_(true) {}
// WifiDataProvider implementation.
void StartDataProvider() override { ++start_calls_; }
void StopDataProvider() override { ++stop_calls_; }
bool GetData(WifiData* data_out) override {
CHECK(data_out);
*data_out = data_;
return got_data_;
}
void SetData(const WifiData& new_data) {
got_data_ = true;
const bool differs = data_.DiffersSignificantly(new_data);
data_ = new_data;
if (differs)
this->RunCallbacks();
}
void set_got_data(bool got_data) { got_data_ = got_data; }
int start_calls_;
int stop_calls_;
private:
~MockWifiDataProvider() override {
CHECK(this == instance_);
instance_ = nullptr;
}
static MockWifiDataProvider* instance_;
WifiData data_;
bool got_data_;
DISALLOW_COPY_AND_ASSIGN(MockWifiDataProvider);
};
MockWifiDataProvider* MockWifiDataProvider::instance_ = nullptr;
// Main test fixture
class GeolocationNetworkProviderTest : public testing::Test {
public:
void TearDown() override {
WifiDataProviderManager::ResetFactoryForTesting();
}
LocationProvider* CreateProvider(bool set_permission_granted) {
LocationProvider* provider = NewNetworkLocationProvider(
access_token_store_,
nullptr, // No URLContextGetter needed, using test urlfecther factory.
test_server_url_,
access_token_store_->access_token_map_[test_server_url_]);
if (set_permission_granted)
provider->OnPermissionGranted();
return provider;
}
protected:
GeolocationNetworkProviderTest()
: test_server_url_(kTestServerUrl),
access_token_store_(new FakeAccessTokenStore),
wifi_data_provider_(MockWifiDataProvider::CreateInstance()) {
// TODO(joth): Really these should be in SetUp, not here, but they take no
// effect on Mac OS Release builds if done there. I kid not. Figure out why.
WifiDataProviderManager::SetFactoryForTesting(
MockWifiDataProvider::GetInstance);
}
// Returns the current url fetcher (if any) and advances the id ready for the
// next test step.
net::TestURLFetcher* get_url_fetcher_and_advance_id() {
net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(
NetworkLocationRequest::url_fetcher_id_for_tests);
if (fetcher)
++NetworkLocationRequest::url_fetcher_id_for_tests;
return fetcher;
}
static int IndexToChannel(int index) { return index + 4; }
// Creates wifi data containing the specified number of access points, with
// some differentiating charactistics in each.
static WifiData CreateReferenceWifiScanData(int ap_count) {
WifiData data;
for (int i = 0; i < ap_count; ++i) {
AccessPointData ap;
ap.mac_address =
base::ASCIIToUTF16(base::StringPrintf("%02d-34-56-78-54-32", i));
ap.radio_signal_strength = ap_count - i;
ap.channel = IndexToChannel(i);
ap.signal_to_noise = i + 42;
ap.ssid = base::ASCIIToUTF16("Some nice+network|name\\");
data.access_point_data.insert(ap);
}
return data;
}
static void CreateReferenceWifiScanDataJson(
int ap_count,
int start_index,
base::ListValue* wifi_access_point_list) {
std::vector<std::string> wifi_data;
for (int i = 0; i < ap_count; ++i) {
std::unique_ptr<base::DictionaryValue> ap(new base::DictionaryValue());
ap->SetString("macAddress", base::StringPrintf("%02d-34-56-78-54-32", i));
ap->SetInteger("signalStrength", start_index + ap_count - i);
ap->SetInteger("age", 0);
ap->SetInteger("channel", IndexToChannel(i));
ap->SetInteger("signalToNoiseRatio", i + 42);
wifi_access_point_list->Append(std::move(ap));
}
}
static Geoposition CreateReferencePosition(int id) {
Geoposition pos;
pos.latitude = id;
pos.longitude = -(id + 1);
pos.altitude = 2 * id;
pos.timestamp = base::Time::Now();
return pos;
}
static std::string PrettyJson(const base::Value& value) {
std::string pretty;
base::JSONWriter::WriteWithOptions(
value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &pretty);
return pretty;
}
static testing::AssertionResult JsonGetList(
const std::string& field,
const base::DictionaryValue& dict,
const base::ListValue** output_list) {
if (!dict.GetList(field, output_list))
return testing::AssertionFailure() << "Dictionary " << PrettyJson(dict)
<< " is missing list field " << field;
return testing::AssertionSuccess();
}
static testing::AssertionResult JsonFieldEquals(
const std::string& field,
const base::DictionaryValue& expected,
const base::DictionaryValue& actual) {
const base::Value* expected_value;
const base::Value* actual_value;
if (!expected.Get(field, &expected_value))
return testing::AssertionFailure() << "Expected dictionary "
<< PrettyJson(expected)
<< " is missing field " << field;
if (!expected.Get(field, &actual_value))
return testing::AssertionFailure() << "Actual dictionary "
<< PrettyJson(actual)
<< " is missing field " << field;
if (!expected_value->Equals(actual_value))
return testing::AssertionFailure()
<< "Field " << field
<< " mismatch: " << PrettyJson(*expected_value)
<< " != " << PrettyJson(*actual_value);
return testing::AssertionSuccess();
}
static GURL UrlWithoutQuery(const GURL& url) {
url::Replacements<char> replacements;
replacements.ClearQuery();
return url.ReplaceComponents(replacements);
}
testing::AssertionResult IsTestServerUrl(const GURL& request_url) {
const GURL a(UrlWithoutQuery(test_server_url_));
const GURL b(UrlWithoutQuery(request_url));
if (a == b)
return testing::AssertionSuccess();
return testing::AssertionFailure() << a << " != " << b;
}
void CheckRequestIsValid(const net::TestURLFetcher& request,
int expected_routers,
int expected_wifi_aps,
int wifi_start_index,
const std::string& expected_access_token) {
const GURL& request_url = request.GetOriginalURL();
EXPECT_TRUE(IsTestServerUrl(request_url));
// Check to see that the api key is being appended for the default
// network provider url.
bool is_default_url =
UrlWithoutQuery(request_url) ==
UrlWithoutQuery(LocationArbitrator::DefaultNetworkProviderURL());
EXPECT_EQ(is_default_url, !request_url.query().empty());
const std::string& upload_data = request.upload_data();
ASSERT_FALSE(upload_data.empty());
std::string json_parse_error_msg;
std::unique_ptr<base::Value> parsed_json =
base::JSONReader::ReadAndReturnError(upload_data, base::JSON_PARSE_RFC,
nullptr, &json_parse_error_msg);
EXPECT_TRUE(json_parse_error_msg.empty());
ASSERT_TRUE(parsed_json);
const base::DictionaryValue* request_json;
ASSERT_TRUE(parsed_json->GetAsDictionary(&request_json));
if (!is_default_url) {
if (expected_access_token.empty()) {
ASSERT_FALSE(request_json->HasKey(kAccessTokenString));
} else {
std::string access_token;
EXPECT_TRUE(request_json->GetString(kAccessTokenString, &access_token));
EXPECT_EQ(expected_access_token, access_token);
}
}
if (expected_wifi_aps) {
base::ListValue expected_wifi_aps_json;
CreateReferenceWifiScanDataJson(expected_wifi_aps, wifi_start_index,
&expected_wifi_aps_json);
EXPECT_EQ(size_t(expected_wifi_aps), expected_wifi_aps_json.GetSize());
const base::ListValue* wifi_aps_json;
ASSERT_TRUE(
JsonGetList("wifiAccessPoints", *request_json, &wifi_aps_json));
for (size_t i = 0; i < expected_wifi_aps_json.GetSize(); ++i) {
const base::DictionaryValue* expected_json;
ASSERT_TRUE(expected_wifi_aps_json.GetDictionary(i, &expected_json));
const base::DictionaryValue* actual_json;
ASSERT_TRUE(wifi_aps_json->GetDictionary(i, &actual_json));
ASSERT_TRUE(
JsonFieldEquals("macAddress", *expected_json, *actual_json));
ASSERT_TRUE(
JsonFieldEquals("signalStrength", *expected_json, *actual_json));
ASSERT_TRUE(JsonFieldEquals("channel", *expected_json, *actual_json));
ASSERT_TRUE(JsonFieldEquals("signalToNoiseRatio", *expected_json,
*actual_json));
}
} else {
ASSERT_FALSE(request_json->HasKey("wifiAccessPoints"));
}
EXPECT_TRUE(request_url.is_valid());
}
GURL test_server_url_;
const base::MessageLoop main_message_loop_;
const scoped_refptr<FakeAccessTokenStore> access_token_store_;
const net::TestURLFetcherFactory url_fetcher_factory_;
const scoped_refptr<MockWifiDataProvider> wifi_data_provider_;
};
TEST_F(GeolocationNetworkProviderTest, CreateDestroy) {
// Test fixture members were SetUp correctly.
EXPECT_EQ(&main_message_loop_, base::MessageLoop::current());
std::unique_ptr<LocationProvider> provider(CreateProvider(true));
EXPECT_TRUE(provider);
provider.reset();
SUCCEED();
}
TEST_F(GeolocationNetworkProviderTest, StartProvider) {
std::unique_ptr<LocationProvider> provider(CreateProvider(true));
EXPECT_TRUE(provider->StartProvider(false));
net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
ASSERT_TRUE(fetcher);
CheckRequestIsValid(*fetcher, 0, 0, 0, std::string());
}
TEST_F(GeolocationNetworkProviderTest, StartProviderDefaultUrl) {
test_server_url_ = LocationArbitrator::DefaultNetworkProviderURL();
std::unique_ptr<LocationProvider> provider(CreateProvider(true));
EXPECT_TRUE(provider->StartProvider(false));
net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
ASSERT_TRUE(fetcher);
CheckRequestIsValid(*fetcher, 0, 0, 0, std::string());
}
TEST_F(GeolocationNetworkProviderTest, StartProviderLongRequest) {
std::unique_ptr<LocationProvider> provider(CreateProvider(true));
EXPECT_TRUE(provider->StartProvider(false));
const int kFirstScanAps = 20;
wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps));
base::RunLoop().RunUntilIdle();
net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
ASSERT_TRUE(fetcher);
// The request url should have been shortened to less than 2048 characters
// in length by not including access points with the lowest signal strength
// in the request.
EXPECT_LT(fetcher->GetOriginalURL().spec().size(), size_t(2048));
CheckRequestIsValid(*fetcher, 0, 16, 4, std::string());
}
TEST_F(GeolocationNetworkProviderTest, MultipleWifiScansComplete) {
std::unique_ptr<LocationProvider> provider(CreateProvider(true));
EXPECT_TRUE(provider->StartProvider(false));
net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
ASSERT_TRUE(fetcher);
EXPECT_TRUE(IsTestServerUrl(fetcher->GetOriginalURL()));
// Complete the network request with bad position fix.
const char* kNoFixNetworkResponse =
"{"
" \"status\": \"ZERO_RESULTS\""
"}";
fetcher->set_url(test_server_url_);
fetcher->set_status(net::URLRequestStatus());
fetcher->set_response_code(200); // OK
fetcher->SetResponseString(kNoFixNetworkResponse);
fetcher->delegate()->OnURLFetchComplete(fetcher);
Geoposition position = provider->GetPosition();
EXPECT_FALSE(position.Validate());
// Now wifi data arrives -- SetData will notify listeners.
const int kFirstScanAps = 6;
wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps));
base::RunLoop().RunUntilIdle();
fetcher = get_url_fetcher_and_advance_id();
ASSERT_TRUE(fetcher);
// The request should have the wifi data.
CheckRequestIsValid(*fetcher, 0, kFirstScanAps, 0, std::string());
// Send a reply with good position fix.
const char* kReferenceNetworkResponse =
"{"
" \"accessToken\": \"" REFERENCE_ACCESS_TOKEN
"\","
" \"accuracy\": 1200.4,"
" \"location\": {"
" \"lat\": 51.0,"
" \"lng\": -0.1"
" }"
"}";
fetcher->set_url(test_server_url_);
fetcher->set_status(net::URLRequestStatus());
fetcher->set_response_code(200); // OK
fetcher->SetResponseString(kReferenceNetworkResponse);
fetcher->delegate()->OnURLFetchComplete(fetcher);
position = provider->GetPosition();
EXPECT_EQ(51.0, position.latitude);
EXPECT_EQ(-0.1, position.longitude);
EXPECT_EQ(1200.4, position.accuracy);
EXPECT_FALSE(position.timestamp.is_null());
EXPECT_TRUE(position.Validate());
// Token should be in the store.
EXPECT_EQ(base::UTF8ToUTF16(REFERENCE_ACCESS_TOKEN),
access_token_store_->access_token_map_[test_server_url_]);
// Wifi updated again, with one less AP. This is 'close enough' to the
// previous scan, so no new request made.
const int kSecondScanAps = kFirstScanAps - 1;
wifi_data_provider_->SetData(CreateReferenceWifiScanData(kSecondScanAps));
base::RunLoop().RunUntilIdle();
fetcher = get_url_fetcher_and_advance_id();
EXPECT_FALSE(fetcher);
position = provider->GetPosition();
EXPECT_EQ(51.0, position.latitude);
EXPECT_EQ(-0.1, position.longitude);
EXPECT_TRUE(position.Validate());
// Now a third scan with more than twice the original amount -> new request.
const int kThirdScanAps = kFirstScanAps * 2 + 1;
wifi_data_provider_->SetData(CreateReferenceWifiScanData(kThirdScanAps));
base::RunLoop().RunUntilIdle();
fetcher = get_url_fetcher_and_advance_id();
EXPECT_TRUE(fetcher);
CheckRequestIsValid(*fetcher, 0, kThirdScanAps, 0, REFERENCE_ACCESS_TOKEN);
// ...reply with a network error.
fetcher->set_url(test_server_url_);
fetcher->set_status(net::URLRequestStatus::FromError(net::ERR_FAILED));
fetcher->set_response_code(200); // should be ignored
fetcher->SetResponseString(std::string());
fetcher->delegate()->OnURLFetchComplete(fetcher);
// Error means we now no longer have a fix.
position = provider->GetPosition();
EXPECT_FALSE(position.Validate());
// Wifi scan returns to original set: should be serviced from cache.
wifi_data_provider_->SetData(CreateReferenceWifiScanData(kFirstScanAps));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(get_url_fetcher_and_advance_id()); // No new request created.
position = provider->GetPosition();
EXPECT_EQ(51.0, position.latitude);
EXPECT_EQ(-0.1, position.longitude);
EXPECT_TRUE(position.Validate());
}
TEST_F(GeolocationNetworkProviderTest, NoRequestOnStartupUntilWifiData) {
MessageLoopQuitListener listener;
wifi_data_provider_->set_got_data(false);
std::unique_ptr<LocationProvider> provider(CreateProvider(true));
EXPECT_TRUE(provider->StartProvider(false));
provider->SetUpdateCallback(base::Bind(
&MessageLoopQuitListener::OnLocationUpdate, base::Unretained(&listener)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(get_url_fetcher_and_advance_id())
<< "Network request should not be created right away on startup when "
"wifi data has not yet arrived";
wifi_data_provider_->SetData(CreateReferenceWifiScanData(1));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(get_url_fetcher_and_advance_id());
}
TEST_F(GeolocationNetworkProviderTest, NewDataReplacesExistingNetworkRequest) {
// Send initial request with empty data
std::unique_ptr<LocationProvider> provider(CreateProvider(true));
EXPECT_TRUE(provider->StartProvider(false));
net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
EXPECT_TRUE(fetcher);
// Now wifi data arrives; new request should be sent.
wifi_data_provider_->SetData(CreateReferenceWifiScanData(4));
base::RunLoop().RunUntilIdle();
fetcher = get_url_fetcher_and_advance_id();
EXPECT_TRUE(fetcher);
}
TEST_F(GeolocationNetworkProviderTest, NetworkRequestDeferredForPermission) {
std::unique_ptr<LocationProvider> provider(CreateProvider(false));
EXPECT_TRUE(provider->StartProvider(false));
net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
EXPECT_FALSE(fetcher);
provider->OnPermissionGranted();
fetcher = get_url_fetcher_and_advance_id();
ASSERT_TRUE(fetcher);
EXPECT_TRUE(IsTestServerUrl(fetcher->GetOriginalURL()));
}
TEST_F(GeolocationNetworkProviderTest,
NetworkRequestWithWifiDataDeferredForPermission) {
access_token_store_->access_token_map_[test_server_url_] =
base::UTF8ToUTF16(REFERENCE_ACCESS_TOKEN);
std::unique_ptr<LocationProvider> provider(CreateProvider(false));
EXPECT_TRUE(provider->StartProvider(false));
net::TestURLFetcher* fetcher = get_url_fetcher_and_advance_id();
EXPECT_FALSE(fetcher);
static const int kScanCount = 4;
wifi_data_provider_->SetData(CreateReferenceWifiScanData(kScanCount));
base::RunLoop().RunUntilIdle();
fetcher = get_url_fetcher_and_advance_id();
EXPECT_FALSE(fetcher);
provider->OnPermissionGranted();
fetcher = get_url_fetcher_and_advance_id();
ASSERT_TRUE(fetcher);
CheckRequestIsValid(*fetcher, 0, kScanCount, 0, REFERENCE_ACCESS_TOKEN);
}
TEST_F(GeolocationNetworkProviderTest, NetworkPositionCache) {
NetworkLocationProvider::PositionCache cache;
const int kCacheSize = NetworkLocationProvider::PositionCache::kMaximumSize;
for (int i = 1; i < kCacheSize * 2 + 1; ++i) {
Geoposition pos = CreateReferencePosition(i);
bool ret = cache.CachePosition(CreateReferenceWifiScanData(i), pos);
EXPECT_TRUE(ret) << i;
const Geoposition* item =
cache.FindPosition(CreateReferenceWifiScanData(i));
ASSERT_TRUE(item) << i;
EXPECT_EQ(pos.latitude, item->latitude) << i;
EXPECT_EQ(pos.longitude, item->longitude) << i;
if (i <= kCacheSize) {
// Nothing should have spilled yet; check oldest item is still there.
EXPECT_TRUE(cache.FindPosition(CreateReferenceWifiScanData(1)));
} else {
const int evicted = i - kCacheSize;
EXPECT_FALSE(cache.FindPosition(CreateReferenceWifiScanData(evicted)));
EXPECT_TRUE(cache.FindPosition(CreateReferenceWifiScanData(evicted + 1)));
}
}
}
} // namespace device