1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/run_loop.h"
6#include "chrome/browser/chromeos/geolocation/simple_geolocation_provider.h"
7#include "content/public/test/test_browser_thread_bundle.h"
8#include "net/http/http_response_headers.h"
9#include "net/http/http_status_code.h"
10#include "net/url_request/test_url_fetcher_factory.h"
11#include "net/url_request/url_fetcher_impl.h"
12#include "net/url_request/url_request_status.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15namespace {
16
17const int kRequestRetryIntervalMilliSeconds = 200;
18
19// This should be different from default to prevent SimpleGeolocationRequest
20// from modifying it.
21const char kTestGeolocationProviderUrl[] =
22    "https://localhost/geolocation/v1/geolocate?";
23
24const char kSimpleResponseBody[] =
25    "{\n"
26    "  \"location\": {\n"
27    "    \"lat\": 51.0,\n"
28    "    \"lng\": -0.1\n"
29    "  },\n"
30    "  \"accuracy\": 1200.4\n"
31    "}";
32}  // anonymous namespace
33
34namespace chromeos {
35
36// This is helper class for net::FakeURLFetcherFactory.
37class TestGeolocationAPIURLFetcherCallback {
38 public:
39  TestGeolocationAPIURLFetcherCallback(const GURL& url,
40                                       const size_t require_retries,
41                                       const std::string& response,
42                                       SimpleGeolocationProvider* provider)
43      : url_(url),
44        require_retries_(require_retries),
45        response_(response),
46        factory_(NULL),
47        attempts_(0),
48        provider_(provider) {}
49
50  scoped_ptr<net::FakeURLFetcher> CreateURLFetcher(
51      const GURL& url,
52      net::URLFetcherDelegate* delegate,
53      const std::string& response_data,
54      net::HttpStatusCode response_code,
55      net::URLRequestStatus::Status status) {
56    EXPECT_EQ(provider_->requests_.size(), 1U);
57
58    SimpleGeolocationRequest* geolocation_request = provider_->requests_[0];
59
60    const base::TimeDelta base_retry_interval =
61        base::TimeDelta::FromMilliseconds(kRequestRetryIntervalMilliSeconds);
62    geolocation_request->set_retry_sleep_on_server_error_for_testing(
63        base_retry_interval);
64    geolocation_request->set_retry_sleep_on_bad_response_for_testing(
65        base_retry_interval);
66
67    ++attempts_;
68    if (attempts_ > require_retries_) {
69      response_code = net::HTTP_OK;
70      status = net::URLRequestStatus::SUCCESS;
71      factory_->SetFakeResponse(url, response_, response_code, status);
72    }
73    scoped_ptr<net::FakeURLFetcher> fetcher(new net::FakeURLFetcher(
74        url, delegate, response_, response_code, status));
75    scoped_refptr<net::HttpResponseHeaders> download_headers =
76        new net::HttpResponseHeaders(std::string());
77    download_headers->AddHeader("Content-Type: application/json");
78    fetcher->set_response_headers(download_headers);
79    return fetcher.Pass();
80  }
81
82  void Initialize(net::FakeURLFetcherFactory* factory) {
83    factory_ = factory;
84    factory_->SetFakeResponse(url_,
85                              std::string(),
86                              net::HTTP_INTERNAL_SERVER_ERROR,
87                              net::URLRequestStatus::FAILED);
88  }
89
90  size_t attempts() const { return attempts_; }
91
92 private:
93  const GURL url_;
94  // Respond with OK on required retry attempt.
95  const size_t require_retries_;
96  std::string response_;
97  net::FakeURLFetcherFactory* factory_;
98  size_t attempts_;
99  SimpleGeolocationProvider* provider_;
100
101  DISALLOW_COPY_AND_ASSIGN(TestGeolocationAPIURLFetcherCallback);
102};
103
104// This implements fake Google MAPS Geolocation API remote endpoint.
105// Response data is served to SimpleGeolocationProvider via
106// net::FakeURLFetcher.
107class GeolocationAPIFetcherFactory {
108 public:
109  GeolocationAPIFetcherFactory(const GURL& url,
110                               const std::string& response,
111                               const size_t require_retries,
112                               SimpleGeolocationProvider* provider) {
113    url_callback_.reset(new TestGeolocationAPIURLFetcherCallback(
114        url, require_retries, response, provider));
115    net::URLFetcherImpl::set_factory(NULL);
116    fetcher_factory_.reset(new net::FakeURLFetcherFactory(
117        NULL,
118        base::Bind(&TestGeolocationAPIURLFetcherCallback::CreateURLFetcher,
119                   base::Unretained(url_callback_.get()))));
120    url_callback_->Initialize(fetcher_factory_.get());
121  }
122
123  size_t attempts() const { return url_callback_->attempts(); }
124
125 private:
126  scoped_ptr<TestGeolocationAPIURLFetcherCallback> url_callback_;
127  scoped_ptr<net::FakeURLFetcherFactory> fetcher_factory_;
128
129  DISALLOW_COPY_AND_ASSIGN(GeolocationAPIFetcherFactory);
130};
131
132class GeolocationReceiver {
133 public:
134  GeolocationReceiver() : server_error_(false) {}
135
136  void OnRequestDone(const Geoposition& position,
137                     bool server_error,
138                     const base::TimeDelta elapsed) {
139    position_ = position;
140    server_error_ = server_error;
141    elapsed_ = elapsed;
142
143    message_loop_runner_->Quit();
144  }
145
146  void WaitUntilRequestDone() {
147    message_loop_runner_.reset(new base::RunLoop);
148    message_loop_runner_->Run();
149  }
150
151  const Geoposition& position() const { return position_; }
152  bool server_error() const { return server_error_; }
153  base::TimeDelta elapsed() const { return elapsed_; }
154
155 private:
156  Geoposition position_;
157  bool server_error_;
158  base::TimeDelta elapsed_;
159  scoped_ptr<base::RunLoop> message_loop_runner_;
160};
161
162class SimpleGeolocationTest : public testing::Test {
163 private:
164  content::TestBrowserThreadBundle thread_bundle_;
165};
166
167TEST_F(SimpleGeolocationTest, ResponseOK) {
168  SimpleGeolocationProvider provider(NULL, GURL(kTestGeolocationProviderUrl));
169
170  GeolocationAPIFetcherFactory url_factory(GURL(kTestGeolocationProviderUrl),
171                                           std::string(kSimpleResponseBody),
172                                           0 /* require_retries */,
173                                           &provider);
174
175  GeolocationReceiver receiver;
176  provider.RequestGeolocation(base::TimeDelta::FromSeconds(1),
177                              base::Bind(&GeolocationReceiver::OnRequestDone,
178                                         base::Unretained(&receiver)));
179  receiver.WaitUntilRequestDone();
180
181  EXPECT_EQ(
182      "latitude=51.000000, longitude=-0.100000, accuracy=1200.400000, "
183      "error_code=0, error_message='', status=1 (OK)",
184      receiver.position().ToString());
185  EXPECT_FALSE(receiver.server_error());
186  EXPECT_EQ(1U, url_factory.attempts());
187}
188
189TEST_F(SimpleGeolocationTest, ResponseOKWithRetries) {
190  SimpleGeolocationProvider provider(NULL, GURL(kTestGeolocationProviderUrl));
191
192  GeolocationAPIFetcherFactory url_factory(GURL(kTestGeolocationProviderUrl),
193                                           std::string(kSimpleResponseBody),
194                                           3 /* require_retries */,
195                                           &provider);
196
197  GeolocationReceiver receiver;
198  provider.RequestGeolocation(base::TimeDelta::FromSeconds(1),
199                              base::Bind(&GeolocationReceiver::OnRequestDone,
200                                         base::Unretained(&receiver)));
201  receiver.WaitUntilRequestDone();
202  EXPECT_EQ(
203      "latitude=51.000000, longitude=-0.100000, accuracy=1200.400000, "
204      "error_code=0, error_message='', status=1 (OK)",
205      receiver.position().ToString());
206  EXPECT_FALSE(receiver.server_error());
207  EXPECT_EQ(4U, url_factory.attempts());
208}
209
210TEST_F(SimpleGeolocationTest, InvalidResponse) {
211  SimpleGeolocationProvider provider(NULL, GURL(kTestGeolocationProviderUrl));
212
213  GeolocationAPIFetcherFactory url_factory(GURL(kTestGeolocationProviderUrl),
214                                           "invalid JSON string",
215                                           0 /* require_retries */,
216                                           &provider);
217
218  GeolocationReceiver receiver;
219
220  const int timeout_seconds = 1;
221  size_t expected_retries = static_cast<size_t>(
222      timeout_seconds * 1000 / kRequestRetryIntervalMilliSeconds);
223  ASSERT_GE(expected_retries, 2U);
224
225  provider.RequestGeolocation(base::TimeDelta::FromSeconds(timeout_seconds),
226                              base::Bind(&GeolocationReceiver::OnRequestDone,
227                                         base::Unretained(&receiver)));
228  receiver.WaitUntilRequestDone();
229
230  EXPECT_EQ(
231      "latitude=200.000000, longitude=200.000000, accuracy=-1.000000, "
232      "error_code=0, error_message='SimpleGeolocation provider at "
233      "'https://localhost/' : JSONReader failed: Line: 1, column: 1, "
234      "Unexpected token..', status=4 (TIMEOUT)",
235      receiver.position().ToString());
236  EXPECT_TRUE(receiver.server_error());
237  EXPECT_GE(url_factory.attempts(), 2U);
238  if (url_factory.attempts() > expected_retries + 1) {
239    LOG(WARNING)
240        << "SimpleGeolocationTest::InvalidResponse: Too many attempts ("
241        << url_factory.attempts() << "), no more then " << expected_retries + 1
242        << " expected.";
243  }
244  if (url_factory.attempts() < expected_retries - 1) {
245    LOG(WARNING)
246        << "SimpleGeolocationTest::InvalidResponse: Too little attempts ("
247        << url_factory.attempts() << "), greater then " << expected_retries - 1
248        << " expected.";
249  }
250}
251
252}  // namespace chromeos
253