Home | History | Annotate | Download | only in timezone
      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/geoposition.h"
      7 #include "chrome/browser/chromeos/timezone/timezone_provider.h"
      8 #include "content/public/test/test_browser_thread_bundle.h"
      9 #include "net/http/http_response_headers.h"
     10 #include "net/http/http_status_code.h"
     11 #include "net/url_request/test_url_fetcher_factory.h"
     12 #include "net/url_request/url_fetcher_impl.h"
     13 #include "net/url_request/url_request_status.h"
     14 #include "testing/gtest/include/gtest/gtest.h"
     15 
     16 namespace {
     17 
     18 const int kRequestRetryIntervalMilliSeconds = 200;
     19 
     20 // This should be different from default to prevent TimeZoneRequest
     21 // from modifying it.
     22 const char kTestTimeZoneProviderUrl[] =
     23     "https://localhost/maps/api/timezone/json?";
     24 
     25 const char kSimpleResponseBody[] =
     26     "{\n"
     27     "    \"dstOffset\" : 0.0,\n"
     28     "    \"rawOffset\" : -28800.0,\n"
     29     "    \"status\" : \"OK\",\n"
     30     "    \"timeZoneId\" : \"America/Los_Angeles\",\n"
     31     "    \"timeZoneName\" : \"Pacific Standard Time\"\n"
     32     "}";
     33 
     34 struct SimpleRequest {
     35   SimpleRequest()
     36       : url("https://localhost/maps/api/timezone/"
     37             "json?location=39.603481,-119.682251&timestamp=1331161200&sensor="
     38             "false"),
     39         http_response(kSimpleResponseBody) {
     40     position.latitude = 39.6034810;
     41     position.longitude = -119.6822510;
     42     position.accuracy = 1;
     43     position.error_code = 0;
     44     position.timestamp = base::Time::FromTimeT(1331161200);
     45     position.status = chromeos::Geoposition::STATUS_NONE;
     46     EXPECT_EQ(
     47         "latitude=39.603481, longitude=-119.682251, accuracy=1.000000, "
     48         "error_code=0, error_message='', status=0 (NONE)",
     49         position.ToString());
     50 
     51     timezone.dstOffset = 0;
     52     timezone.rawOffset = -28800;
     53     timezone.timeZoneId = "America/Los_Angeles";
     54     timezone.timeZoneName = "Pacific Standard Time";
     55     timezone.error_message.erase();
     56     timezone.status = chromeos::TimeZoneResponseData::OK;
     57     EXPECT_EQ(
     58         "dstOffset=0.000000, rawOffset=-28800.000000, "
     59         "timeZoneId='America/Los_Angeles', timeZoneName='Pacific Standard "
     60         "Time', error_message='', status=0 (OK)",
     61         timezone.ToStringForDebug());
     62   }
     63 
     64   GURL url;
     65   chromeos::Geoposition position;
     66   std::string http_response;
     67   chromeos::TimeZoneResponseData timezone;
     68 };
     69 
     70 }  // anonymous namespace
     71 
     72 namespace chromeos {
     73 
     74 // This is helper class for net::FakeURLFetcherFactory.
     75 class TestTimeZoneAPIURLFetcherCallback {
     76  public:
     77   TestTimeZoneAPIURLFetcherCallback(const GURL& url,
     78                                     const size_t require_retries,
     79                                     const std::string& response,
     80                                     TimeZoneProvider* provider)
     81       : url_(url),
     82         require_retries_(require_retries),
     83         response_(response),
     84         factory_(NULL),
     85         attempts_(0),
     86         provider_(provider) {}
     87 
     88   scoped_ptr<net::FakeURLFetcher> CreateURLFetcher(
     89       const GURL& url,
     90       net::URLFetcherDelegate* delegate,
     91       const std::string& response_data,
     92       net::HttpStatusCode response_code,
     93       net::URLRequestStatus::Status status) {
     94     EXPECT_EQ(provider_->requests_.size(), 1U);
     95 
     96     TimeZoneRequest* timezone_request = provider_->requests_[0];
     97 
     98     const base::TimeDelta base_retry_interval =
     99         base::TimeDelta::FromMilliseconds(kRequestRetryIntervalMilliSeconds);
    100     timezone_request->set_retry_sleep_on_server_error_for_testing(
    101         base_retry_interval);
    102     timezone_request->set_retry_sleep_on_bad_response_for_testing(
    103         base_retry_interval);
    104 
    105     ++attempts_;
    106     if (attempts_ > require_retries_) {
    107       response_code = net::HTTP_OK;
    108       status = net::URLRequestStatus::SUCCESS;
    109       factory_->SetFakeResponse(url, response_, response_code, status);
    110     }
    111     scoped_ptr<net::FakeURLFetcher> fetcher(new net::FakeURLFetcher(
    112         url, delegate, response_, response_code, status));
    113     scoped_refptr<net::HttpResponseHeaders> download_headers =
    114         new net::HttpResponseHeaders(std::string());
    115     download_headers->AddHeader("Content-Type: application/json");
    116     fetcher->set_response_headers(download_headers);
    117     return fetcher.Pass();
    118   }
    119 
    120   void Initialize(net::FakeURLFetcherFactory* factory) {
    121     factory_ = factory;
    122     factory_->SetFakeResponse(url_,
    123                               std::string(),
    124                               net::HTTP_INTERNAL_SERVER_ERROR,
    125                               net::URLRequestStatus::FAILED);
    126   }
    127 
    128   size_t attempts() const { return attempts_; }
    129 
    130  private:
    131   const GURL url_;
    132   // Respond with OK on required retry attempt.
    133   const size_t require_retries_;
    134   std::string response_;
    135   net::FakeURLFetcherFactory* factory_;
    136   size_t attempts_;
    137   TimeZoneProvider* provider_;
    138 
    139   DISALLOW_COPY_AND_ASSIGN(TestTimeZoneAPIURLFetcherCallback);
    140 };
    141 
    142 // This implements fake TimeZone API remote endpoint.
    143 // Response data is served to TimeZoneProvider via
    144 // net::FakeURLFetcher.
    145 class TimeZoneAPIFetcherFactory {
    146  public:
    147   TimeZoneAPIFetcherFactory(const GURL& url,
    148                             const std::string& response,
    149                             const size_t require_retries,
    150                             TimeZoneProvider* provider) {
    151     url_callback_.reset(new TestTimeZoneAPIURLFetcherCallback(
    152         url, require_retries, response, provider));
    153     net::URLFetcherImpl::set_factory(NULL);
    154     fetcher_factory_.reset(new net::FakeURLFetcherFactory(
    155         NULL,
    156         base::Bind(&TestTimeZoneAPIURLFetcherCallback::CreateURLFetcher,
    157                    base::Unretained(url_callback_.get()))));
    158     url_callback_->Initialize(fetcher_factory_.get());
    159   }
    160 
    161   size_t attempts() const { return url_callback_->attempts(); }
    162 
    163  private:
    164   scoped_ptr<TestTimeZoneAPIURLFetcherCallback> url_callback_;
    165   scoped_ptr<net::FakeURLFetcherFactory> fetcher_factory_;
    166 
    167   DISALLOW_COPY_AND_ASSIGN(TimeZoneAPIFetcherFactory);
    168 };
    169 
    170 class TimeZoneReceiver {
    171  public:
    172   TimeZoneReceiver() : server_error_(false) {}
    173 
    174   void OnRequestDone(scoped_ptr<TimeZoneResponseData> timezone,
    175                      bool server_error) {
    176     timezone_ = timezone.Pass();
    177     server_error_ = server_error;
    178 
    179     message_loop_runner_->Quit();
    180   }
    181 
    182   void WaitUntilRequestDone() {
    183     message_loop_runner_.reset(new base::RunLoop);
    184     message_loop_runner_->Run();
    185   }
    186 
    187   const TimeZoneResponseData* timezone() const { return timezone_.get(); }
    188   bool server_error() const { return server_error_; }
    189 
    190  private:
    191   scoped_ptr<TimeZoneResponseData> timezone_;
    192   bool server_error_;
    193   scoped_ptr<base::RunLoop> message_loop_runner_;
    194 };
    195 
    196 class TimeZoneTest : public testing::Test {
    197  private:
    198   content::TestBrowserThreadBundle thread_bundle_;
    199 };
    200 
    201 TEST_F(TimeZoneTest, ResponseOK) {
    202   TimeZoneProvider provider(NULL, GURL(kTestTimeZoneProviderUrl));
    203   const SimpleRequest simple_request;
    204 
    205   TimeZoneAPIFetcherFactory url_factory(simple_request.url,
    206                                         simple_request.http_response,
    207                                         0 /* require_retries */,
    208                                         &provider);
    209 
    210   TimeZoneReceiver receiver;
    211 
    212   provider.RequestTimezone(simple_request.position,
    213                            false,
    214                            base::TimeDelta::FromSeconds(1),
    215                            base::Bind(&TimeZoneReceiver::OnRequestDone,
    216                                       base::Unretained(&receiver)));
    217   receiver.WaitUntilRequestDone();
    218 
    219   EXPECT_EQ(simple_request.timezone.ToStringForDebug(),
    220             receiver.timezone()->ToStringForDebug());
    221   EXPECT_FALSE(receiver.server_error());
    222   EXPECT_EQ(1U, url_factory.attempts());
    223 }
    224 
    225 TEST_F(TimeZoneTest, ResponseOKWithRetries) {
    226   TimeZoneProvider provider(NULL, GURL(kTestTimeZoneProviderUrl));
    227   const SimpleRequest simple_request;
    228 
    229   TimeZoneAPIFetcherFactory url_factory(simple_request.url,
    230                                         simple_request.http_response,
    231                                         3 /* require_retries */,
    232                                         &provider);
    233 
    234   TimeZoneReceiver receiver;
    235 
    236   provider.RequestTimezone(simple_request.position,
    237                            false,
    238                            base::TimeDelta::FromSeconds(1),
    239                            base::Bind(&TimeZoneReceiver::OnRequestDone,
    240                                       base::Unretained(&receiver)));
    241   receiver.WaitUntilRequestDone();
    242   EXPECT_EQ(simple_request.timezone.ToStringForDebug(),
    243             receiver.timezone()->ToStringForDebug());
    244   EXPECT_FALSE(receiver.server_error());
    245   EXPECT_EQ(4U, url_factory.attempts());
    246 }
    247 
    248 TEST_F(TimeZoneTest, InvalidResponse) {
    249   TimeZoneProvider provider(NULL, GURL(kTestTimeZoneProviderUrl));
    250   const SimpleRequest simple_request;
    251 
    252   TimeZoneAPIFetcherFactory url_factory(simple_request.url,
    253                                         "invalid JSON string",
    254                                         0 /* require_retries */,
    255                                         &provider);
    256 
    257   TimeZoneReceiver receiver;
    258 
    259   const int timeout_seconds = 1;
    260   size_t expected_retries = static_cast<size_t>(
    261       timeout_seconds * 1000 / kRequestRetryIntervalMilliSeconds);
    262   ASSERT_GE(expected_retries, 2U);
    263 
    264   provider.RequestTimezone(simple_request.position,
    265                            false,
    266                            base::TimeDelta::FromSeconds(timeout_seconds),
    267                            base::Bind(&TimeZoneReceiver::OnRequestDone,
    268                                       base::Unretained(&receiver)));
    269   receiver.WaitUntilRequestDone();
    270   EXPECT_EQ(
    271       "dstOffset=0.000000, rawOffset=0.000000, timeZoneId='', timeZoneName='', "
    272       "error_message='TimeZone provider at 'https://localhost/' : JSONReader "
    273       "failed: Line: 1, column: 1, Unexpected token..', status=6 "
    274       "(REQUEST_ERROR)",
    275       receiver.timezone()->ToStringForDebug());
    276   EXPECT_FALSE(receiver.server_error());
    277   EXPECT_GE(url_factory.attempts(), 2U);
    278   if (url_factory.attempts() > expected_retries + 1) {
    279     LOG(WARNING) << "TimeZoneTest::InvalidResponse: Too many attempts ("
    280                  << url_factory.attempts() << "), no more then "
    281                  << expected_retries + 1 << " expected.";
    282   }
    283   if (url_factory.attempts() < expected_retries - 1) {
    284     LOG(WARNING) << "TimeZoneTest::InvalidResponse: Too less attempts ("
    285                  << url_factory.attempts() << "), greater then "
    286                  << expected_retries - 1 << " expected.";
    287   }
    288 }
    289 
    290 }  // namespace chromeos
    291