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×tamp=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