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 "components/search_provider_logos/logo_tracker.h" 6 7 #include <vector> 8 9 #include "base/base64.h" 10 #include "base/bind.h" 11 #include "base/callback.h" 12 #include "base/files/file_path.h" 13 #include "base/json/json_writer.h" 14 #include "base/memory/ref_counted.h" 15 #include "base/memory/scoped_vector.h" 16 #include "base/run_loop.h" 17 #include "base/strings/string_piece.h" 18 #include "base/strings/stringprintf.h" 19 #include "base/test/simple_test_clock.h" 20 #include "base/time/time.h" 21 #include "base/values.h" 22 #include "components/search_provider_logos/google_logo_api.h" 23 #include "net/base/url_util.h" 24 #include "net/http/http_response_headers.h" 25 #include "net/http/http_status_code.h" 26 #include "net/url_request/test_url_fetcher_factory.h" 27 #include "net/url_request/url_request_status.h" 28 #include "net/url_request/url_request_test_util.h" 29 #include "testing/gmock/include/gmock/gmock.h" 30 #include "testing/gtest/include/gtest/gtest.h" 31 #include "ui/gfx/image/image.h" 32 33 using ::testing::_; 34 using ::testing::AnyNumber; 35 using ::testing::AtMost; 36 using ::testing::InSequence; 37 using ::testing::Invoke; 38 using ::testing::Mock; 39 using ::testing::NiceMock; 40 using ::testing::Return; 41 42 namespace search_provider_logos { 43 44 namespace { 45 46 bool AreImagesSameSize(const SkBitmap& bitmap1, const SkBitmap& bitmap2) { 47 return bitmap1.width() == bitmap2.width() && 48 bitmap1.height() == bitmap2.height(); 49 } 50 51 scoped_refptr<base::RefCountedString> EncodeBitmapAsPNG( 52 const SkBitmap& bitmap) { 53 scoped_refptr<base::RefCountedMemory> png_bytes = 54 gfx::Image::CreateFrom1xBitmap(bitmap).As1xPNGBytes(); 55 scoped_refptr<base::RefCountedString> str = new base::RefCountedString(); 56 str->data().assign(png_bytes->front_as<char>(), png_bytes->size()); 57 return str; 58 } 59 60 std::string EncodeBitmapAsPNGBase64(const SkBitmap& bitmap) { 61 scoped_refptr<base::RefCountedString> png_bytes = EncodeBitmapAsPNG(bitmap); 62 std::string encoded_image_base64; 63 base::Base64Encode(png_bytes->data(), &encoded_image_base64); 64 return encoded_image_base64; 65 } 66 67 SkBitmap MakeBitmap(int width, int height) { 68 SkBitmap bitmap; 69 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); 70 bitmap.allocPixels(); 71 bitmap.eraseColor(SK_ColorBLUE); 72 return bitmap; 73 } 74 75 EncodedLogo EncodeLogo(const Logo& logo) { 76 EncodedLogo encoded_logo; 77 encoded_logo.encoded_image = EncodeBitmapAsPNG(logo.image); 78 encoded_logo.metadata = logo.metadata; 79 return encoded_logo; 80 } 81 82 Logo DecodeLogo(const EncodedLogo& encoded_logo) { 83 Logo logo; 84 logo.image = gfx::Image::CreateFrom1xPNGBytes( 85 encoded_logo.encoded_image->front(), 86 encoded_logo.encoded_image->size()).AsBitmap(); 87 logo.metadata = encoded_logo.metadata; 88 return logo; 89 } 90 91 Logo GetSampleLogo(const GURL& logo_url, base::Time response_time) { 92 Logo logo; 93 logo.image = MakeBitmap(2, 5); 94 logo.metadata.can_show_after_expiration = false; 95 logo.metadata.expiration_time = 96 response_time + base::TimeDelta::FromHours(19); 97 logo.metadata.fingerprint = "8bc33a80"; 98 logo.metadata.source_url = logo_url.spec(); 99 logo.metadata.on_click_url = "http://www.google.com/search?q=potato"; 100 logo.metadata.alt_text = "A logo about potatoes"; 101 logo.metadata.mime_type = "image/png"; 102 return logo; 103 } 104 105 Logo GetSampleLogo2(const GURL& logo_url, base::Time response_time) { 106 Logo logo; 107 logo.image = MakeBitmap(4, 3); 108 logo.metadata.can_show_after_expiration = true; 109 logo.metadata.expiration_time = base::Time(); 110 logo.metadata.fingerprint = "71082741021409127"; 111 logo.metadata.source_url = logo_url.spec(); 112 logo.metadata.on_click_url = "http://example.com/page25"; 113 logo.metadata.alt_text = "The logo for example.com"; 114 logo.metadata.mime_type = "image/png"; 115 return logo; 116 } 117 118 std::string MakeServerResponse( 119 const SkBitmap& image, 120 const std::string& on_click_url, 121 const std::string& alt_text, 122 const std::string& mime_type, 123 const std::string& fingerprint, 124 base::TimeDelta time_to_live) { 125 base::DictionaryValue dict; 126 if (!image.isNull()) { 127 dict.SetString("update.logo.data", EncodeBitmapAsPNGBase64(image)); 128 } 129 130 dict.SetString("update.logo.target", on_click_url); 131 dict.SetString("update.logo.alt", alt_text); 132 dict.SetString("update.logo.mime_type", mime_type); 133 dict.SetString("update.logo.fingerprint", fingerprint); 134 if (time_to_live.ToInternalValue() != 0) 135 dict.SetInteger("update.logo.time_to_live", 136 static_cast<int>(time_to_live.InMilliseconds())); 137 138 std::string output; 139 base::JSONWriter::Write(&dict, &output); 140 return output; 141 } 142 143 std::string MakeServerResponse(const Logo& logo, base::TimeDelta time_to_live) { 144 return MakeServerResponse(logo.image, 145 logo.metadata.on_click_url, 146 logo.metadata.alt_text, 147 logo.metadata.mime_type, 148 logo.metadata.fingerprint, 149 time_to_live); 150 } 151 152 void ExpectLogosEqual(const Logo* expected_logo, 153 const Logo* actual_logo) { 154 if (!expected_logo) { 155 ASSERT_FALSE(actual_logo); 156 return; 157 } 158 ASSERT_TRUE(actual_logo); 159 EXPECT_TRUE(AreImagesSameSize(expected_logo->image, actual_logo->image)); 160 EXPECT_EQ(expected_logo->metadata.on_click_url, 161 actual_logo->metadata.on_click_url); 162 EXPECT_EQ(expected_logo->metadata.source_url, 163 actual_logo->metadata.source_url); 164 EXPECT_EQ(expected_logo->metadata.fingerprint, 165 actual_logo->metadata.fingerprint); 166 EXPECT_EQ(expected_logo->metadata.can_show_after_expiration, 167 actual_logo->metadata.can_show_after_expiration); 168 } 169 170 void ExpectLogosEqual(const Logo* expected_logo, 171 const EncodedLogo* actual_encoded_logo) { 172 Logo actual_logo; 173 if (actual_encoded_logo) 174 actual_logo = DecodeLogo(*actual_encoded_logo); 175 ExpectLogosEqual(expected_logo, actual_encoded_logo ? &actual_logo : NULL); 176 } 177 178 ACTION_P(ExpectLogosEqualAction, expected_logo) { 179 ExpectLogosEqual(expected_logo, arg0); 180 } 181 182 class MockLogoCache : public LogoCache { 183 public: 184 MockLogoCache() : LogoCache(base::FilePath()) { 185 // Delegate actions to the *Internal() methods by default. 186 ON_CALL(*this, UpdateCachedLogoMetadata(_)).WillByDefault( 187 Invoke(this, &MockLogoCache::UpdateCachedLogoMetadataInternal)); 188 ON_CALL(*this, GetCachedLogoMetadata()).WillByDefault( 189 Invoke(this, &MockLogoCache::GetCachedLogoMetadataInternal)); 190 ON_CALL(*this, SetCachedLogo(_)) 191 .WillByDefault(Invoke(this, &MockLogoCache::SetCachedLogoInternal)); 192 } 193 194 MOCK_METHOD1(UpdateCachedLogoMetadata, void(const LogoMetadata& metadata)); 195 MOCK_METHOD0(GetCachedLogoMetadata, const LogoMetadata*()); 196 MOCK_METHOD1(SetCachedLogo, void(const EncodedLogo* logo)); 197 // GetCachedLogo() can't be mocked since it returns a scoped_ptr, which is 198 // non-copyable. Instead create a method that's pinged when GetCachedLogo() is 199 // called. 200 MOCK_METHOD0(OnGetCachedLogo, void()); 201 202 void EncodeAndSetCachedLogo(const Logo& logo) { 203 EncodedLogo encoded_logo = EncodeLogo(logo); 204 SetCachedLogo(&encoded_logo); 205 } 206 207 void ExpectSetCachedLogo(const Logo* expected_logo) { 208 Mock::VerifyAndClearExpectations(this); 209 EXPECT_CALL(*this, SetCachedLogo(_)) 210 .WillOnce(ExpectLogosEqualAction(expected_logo)); 211 } 212 213 void UpdateCachedLogoMetadataInternal(const LogoMetadata& metadata) { 214 metadata_.reset(new LogoMetadata(metadata)); 215 } 216 217 virtual const LogoMetadata* GetCachedLogoMetadataInternal() { 218 return metadata_.get(); 219 } 220 221 virtual void SetCachedLogoInternal(const EncodedLogo* logo) { 222 logo_.reset(logo ? new EncodedLogo(*logo) : NULL); 223 metadata_.reset(logo ? new LogoMetadata(logo->metadata) : NULL); 224 } 225 226 virtual scoped_ptr<EncodedLogo> GetCachedLogo() OVERRIDE { 227 OnGetCachedLogo(); 228 return make_scoped_ptr(logo_ ? new EncodedLogo(*logo_) : NULL); 229 } 230 231 private: 232 scoped_ptr<LogoMetadata> metadata_; 233 scoped_ptr<EncodedLogo> logo_; 234 }; 235 236 class MockLogoObserver : public LogoObserver { 237 public: 238 virtual ~MockLogoObserver() {} 239 240 void ExpectNoLogo() { 241 Mock::VerifyAndClearExpectations(this); 242 EXPECT_CALL(*this, OnLogoAvailable(_, _)).Times(0); 243 EXPECT_CALL(*this, OnObserverRemoved()).Times(1); 244 } 245 246 void ExpectCachedLogo(const Logo* expected_cached_logo) { 247 Mock::VerifyAndClearExpectations(this); 248 EXPECT_CALL(*this, OnLogoAvailable(_, true)) 249 .WillOnce(ExpectLogosEqualAction(expected_cached_logo)); 250 EXPECT_CALL(*this, OnLogoAvailable(_, false)).Times(0); 251 EXPECT_CALL(*this, OnObserverRemoved()).Times(1); 252 } 253 254 void ExpectFreshLogo(const Logo* expected_fresh_logo) { 255 Mock::VerifyAndClearExpectations(this); 256 EXPECT_CALL(*this, OnLogoAvailable(_, true)).Times(0); 257 EXPECT_CALL(*this, OnLogoAvailable(NULL, true)); 258 EXPECT_CALL(*this, OnLogoAvailable(_, false)) 259 .WillOnce(ExpectLogosEqualAction(expected_fresh_logo)); 260 EXPECT_CALL(*this, OnObserverRemoved()).Times(1); 261 } 262 263 void ExpectCachedAndFreshLogos(const Logo* expected_cached_logo, 264 const Logo* expected_fresh_logo) { 265 Mock::VerifyAndClearExpectations(this); 266 InSequence dummy; 267 EXPECT_CALL(*this, OnLogoAvailable(_, true)) 268 .WillOnce(ExpectLogosEqualAction(expected_cached_logo)); 269 EXPECT_CALL(*this, OnLogoAvailable(_, false)) 270 .WillOnce(ExpectLogosEqualAction(expected_fresh_logo)); 271 EXPECT_CALL(*this, OnObserverRemoved()).Times(1); 272 } 273 274 MOCK_METHOD2(OnLogoAvailable, void(const Logo*, bool)); 275 MOCK_METHOD0(OnObserverRemoved, void()); 276 }; 277 278 class TestLogoDelegate : public LogoDelegate { 279 public: 280 TestLogoDelegate() {} 281 virtual ~TestLogoDelegate() {} 282 283 virtual void DecodeUntrustedImage( 284 const scoped_refptr<base::RefCountedString>& encoded_image, 285 base::Callback<void(const SkBitmap&)> image_decoded_callback) OVERRIDE { 286 SkBitmap bitmap = 287 gfx::Image::CreateFrom1xPNGBytes(encoded_image->front(), 288 encoded_image->size()).AsBitmap(); 289 base::MessageLoopProxy::current()->PostTask( 290 FROM_HERE, base::Bind(image_decoded_callback, bitmap)); 291 } 292 }; 293 294 class LogoTrackerTest : public ::testing::Test { 295 protected: 296 LogoTrackerTest() 297 : message_loop_(new base::MessageLoop()), 298 logo_url_("https://google.com/doodleoftheday?size=hp"), 299 test_clock_(new base::SimpleTestClock()), 300 logo_cache_(new NiceMock<MockLogoCache>()), 301 fake_url_fetcher_factory_(NULL) { 302 test_clock_->SetNow(base::Time::FromJsTime(GG_INT64_C(1388686828000))); 303 logo_tracker_ = new LogoTracker( 304 base::FilePath(), 305 base::MessageLoopProxy::current(), 306 base::MessageLoopProxy::current(), 307 new net::TestURLRequestContextGetter(base::MessageLoopProxy::current()), 308 scoped_ptr<LogoDelegate>(new TestLogoDelegate())); 309 logo_tracker_->SetServerAPI(logo_url_, 310 base::Bind(&GoogleParseLogoResponse), 311 base::Bind(&GoogleAppendFingerprintToLogoURL)); 312 logo_tracker_->SetClockForTests(scoped_ptr<base::Clock>(test_clock_)); 313 logo_tracker_->SetLogoCacheForTests(scoped_ptr<LogoCache>(logo_cache_)); 314 } 315 316 virtual void TearDown() { 317 // logo_tracker_ owns logo_cache_, which gets destructed on the file thread 318 // after logo_tracker_'s destruction. Ensure that logo_cache_ is actually 319 // destructed before the test ends to make gmock happy. 320 delete logo_tracker_; 321 message_loop_->RunUntilIdle(); 322 } 323 324 // Returns the response that the server would send for the given logo. 325 std::string ServerResponse(const Logo& logo) const; 326 327 // Sets the response to be returned when the LogoTracker fetches the logo. 328 void SetServerResponse(const std::string& response, 329 net::URLRequestStatus::Status request_status = 330 net::URLRequestStatus::SUCCESS, 331 net::HttpStatusCode response_code = net::HTTP_OK); 332 333 // Sets the response to be returned when the LogoTracker fetches the logo and 334 // provides the given fingerprint. 335 void SetServerResponseWhenFingerprint( 336 const std::string& fingerprint, 337 const std::string& response_when_fingerprint, 338 net::URLRequestStatus::Status request_status = 339 net::URLRequestStatus::SUCCESS, 340 net::HttpStatusCode response_code = net::HTTP_OK); 341 342 // Calls logo_tracker_->GetLogo() with listener_ and waits for the 343 // asynchronous response(s). 344 void GetLogo(); 345 346 scoped_ptr<base::MessageLoop> message_loop_; 347 GURL logo_url_; 348 base::SimpleTestClock* test_clock_; 349 NiceMock<MockLogoCache>* logo_cache_; 350 net::FakeURLFetcherFactory fake_url_fetcher_factory_; 351 LogoTracker* logo_tracker_; 352 NiceMock<MockLogoObserver> observer_; 353 }; 354 355 std::string LogoTrackerTest::ServerResponse(const Logo& logo) const { 356 base::TimeDelta time_to_live; 357 if (!logo.metadata.expiration_time.is_null()) 358 time_to_live = logo.metadata.expiration_time - test_clock_->Now(); 359 return MakeServerResponse(logo, time_to_live); 360 } 361 362 void LogoTrackerTest::SetServerResponse( 363 const std::string& response, 364 net::URLRequestStatus::Status request_status, 365 net::HttpStatusCode response_code) { 366 fake_url_fetcher_factory_.SetFakeResponse( 367 logo_url_, response, response_code, request_status); 368 } 369 370 void LogoTrackerTest::SetServerResponseWhenFingerprint( 371 const std::string& fingerprint, 372 const std::string& response_when_fingerprint, 373 net::URLRequestStatus::Status request_status, 374 net::HttpStatusCode response_code) { 375 GURL url_with_fp = 376 net::AppendQueryParameter(logo_url_, "async", "es_dfp:" + fingerprint); 377 fake_url_fetcher_factory_.SetFakeResponse( 378 url_with_fp, response_when_fingerprint, response_code, request_status); 379 } 380 381 void LogoTrackerTest::GetLogo() { 382 logo_tracker_->GetLogo(&observer_); 383 base::RunLoop().RunUntilIdle(); 384 } 385 386 // Tests ----------------------------------------------------------------------- 387 388 TEST_F(LogoTrackerTest, DownloadAndCacheLogo) { 389 Logo logo = GetSampleLogo(logo_url_, test_clock_->Now()); 390 SetServerResponse(ServerResponse(logo)); 391 logo_cache_->ExpectSetCachedLogo(&logo); 392 observer_.ExpectFreshLogo(&logo); 393 GetLogo(); 394 } 395 396 TEST_F(LogoTrackerTest, EmptyCacheAndFailedDownload) { 397 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(0); 398 EXPECT_CALL(*logo_cache_, SetCachedLogo(_)).Times(0); 399 EXPECT_CALL(*logo_cache_, SetCachedLogo(NULL)).Times(AnyNumber()); 400 401 SetServerResponse("server is borked"); 402 observer_.ExpectCachedLogo(NULL); 403 GetLogo(); 404 405 SetServerResponse("", net::URLRequestStatus::FAILED, net::HTTP_OK); 406 observer_.ExpectCachedLogo(NULL); 407 GetLogo(); 408 409 SetServerResponse("", net::URLRequestStatus::SUCCESS, net::HTTP_BAD_GATEWAY); 410 observer_.ExpectCachedLogo(NULL); 411 GetLogo(); 412 } 413 414 TEST_F(LogoTrackerTest, AcceptMinimalLogoResponse) { 415 Logo logo; 416 logo.image = MakeBitmap(1, 2); 417 logo.metadata.source_url = logo_url_.spec(); 418 logo.metadata.can_show_after_expiration = true; 419 420 std::string response = ")]}' {\"update\":{\"logo\":{\"data\":\"" + 421 EncodeBitmapAsPNGBase64(logo.image) + 422 "\",\"mime_type\":\"image/png\"}}}"; 423 424 SetServerResponse(response); 425 observer_.ExpectFreshLogo(&logo); 426 GetLogo(); 427 } 428 429 TEST_F(LogoTrackerTest, ReturnCachedLogo) { 430 Logo cached_logo = GetSampleLogo(logo_url_, test_clock_->Now()); 431 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 432 SetServerResponseWhenFingerprint(cached_logo.metadata.fingerprint, 433 "", 434 net::URLRequestStatus::FAILED, 435 net::HTTP_OK); 436 437 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(0); 438 EXPECT_CALL(*logo_cache_, SetCachedLogo(_)).Times(0); 439 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(1)); 440 observer_.ExpectCachedLogo(&cached_logo); 441 GetLogo(); 442 } 443 444 TEST_F(LogoTrackerTest, ValidateCachedLogoFingerprint) { 445 Logo cached_logo = GetSampleLogo(logo_url_, test_clock_->Now()); 446 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 447 448 Logo fresh_logo = cached_logo; 449 fresh_logo.image.reset(); 450 fresh_logo.metadata.expiration_time = 451 test_clock_->Now() + base::TimeDelta::FromDays(8); 452 SetServerResponseWhenFingerprint(fresh_logo.metadata.fingerprint, 453 ServerResponse(fresh_logo)); 454 455 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(1); 456 EXPECT_CALL(*logo_cache_, SetCachedLogo(_)).Times(0); 457 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(1)); 458 observer_.ExpectCachedLogo(&cached_logo); 459 460 GetLogo(); 461 462 EXPECT_TRUE(logo_cache_->GetCachedLogoMetadata() != NULL); 463 EXPECT_EQ(logo_cache_->GetCachedLogoMetadata()->expiration_time, 464 fresh_logo.metadata.expiration_time); 465 } 466 467 TEST_F(LogoTrackerTest, UpdateCachedLogo) { 468 Logo cached_logo = GetSampleLogo(logo_url_, test_clock_->Now()); 469 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 470 471 Logo fresh_logo = GetSampleLogo2(logo_url_, test_clock_->Now()); 472 SetServerResponseWhenFingerprint(cached_logo.metadata.fingerprint, 473 ServerResponse(fresh_logo)); 474 475 logo_cache_->ExpectSetCachedLogo(&fresh_logo); 476 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(0); 477 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(1)); 478 observer_.ExpectCachedAndFreshLogos(&cached_logo, &fresh_logo); 479 480 GetLogo(); 481 } 482 483 TEST_F(LogoTrackerTest, InvalidateCachedLogo) { 484 Logo cached_logo = GetSampleLogo(logo_url_, test_clock_->Now()); 485 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 486 487 // This response means there's no current logo. 488 SetServerResponseWhenFingerprint(cached_logo.metadata.fingerprint, 489 ")]}' {\"update\":{}}"); 490 491 logo_cache_->ExpectSetCachedLogo(NULL); 492 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(0); 493 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(1)); 494 observer_.ExpectCachedAndFreshLogos(&cached_logo, NULL); 495 496 GetLogo(); 497 } 498 499 TEST_F(LogoTrackerTest, DeleteCachedLogoFromOldUrl) { 500 SetServerResponse("", net::URLRequestStatus::FAILED, net::HTTP_OK); 501 Logo cached_logo = 502 GetSampleLogo(GURL("http://oldsearchprovider.com"), test_clock_->Now()); 503 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 504 505 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(0); 506 EXPECT_CALL(*logo_cache_, SetCachedLogo(_)).Times(0); 507 EXPECT_CALL(*logo_cache_, SetCachedLogo(NULL)).Times(AnyNumber()); 508 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(1)); 509 observer_.ExpectCachedLogo(NULL); 510 GetLogo(); 511 } 512 513 TEST_F(LogoTrackerTest, LogoWithTTLCannotBeShownAfterExpiration) { 514 Logo logo = GetSampleLogo(logo_url_, test_clock_->Now()); 515 base::TimeDelta time_to_live = base::TimeDelta::FromDays(3); 516 logo.metadata.expiration_time = test_clock_->Now() + time_to_live; 517 SetServerResponse(ServerResponse(logo)); 518 GetLogo(); 519 520 const LogoMetadata* cached_metadata = 521 logo_cache_->GetCachedLogoMetadata(); 522 EXPECT_TRUE(cached_metadata != NULL); 523 EXPECT_FALSE(cached_metadata->can_show_after_expiration); 524 EXPECT_EQ(test_clock_->Now() + time_to_live, 525 cached_metadata->expiration_time); 526 } 527 528 TEST_F(LogoTrackerTest, LogoWithoutTTLCanBeShownAfterExpiration) { 529 Logo logo = GetSampleLogo(logo_url_, test_clock_->Now()); 530 base::TimeDelta time_to_live = base::TimeDelta(); 531 SetServerResponse(MakeServerResponse(logo, time_to_live)); 532 GetLogo(); 533 534 const LogoMetadata* cached_metadata = 535 logo_cache_->GetCachedLogoMetadata(); 536 EXPECT_TRUE(cached_metadata != NULL); 537 EXPECT_TRUE(cached_metadata->can_show_after_expiration); 538 EXPECT_EQ(test_clock_->Now() + base::TimeDelta::FromDays(30), 539 cached_metadata->expiration_time); 540 } 541 542 TEST_F(LogoTrackerTest, UseSoftExpiredCachedLogo) { 543 SetServerResponse("", net::URLRequestStatus::FAILED, net::HTTP_OK); 544 Logo cached_logo = GetSampleLogo(logo_url_, test_clock_->Now()); 545 cached_logo.metadata.expiration_time = 546 test_clock_->Now() - base::TimeDelta::FromSeconds(1); 547 cached_logo.metadata.can_show_after_expiration = true; 548 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 549 550 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(0); 551 EXPECT_CALL(*logo_cache_, SetCachedLogo(_)).Times(0); 552 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(1)); 553 observer_.ExpectCachedLogo(&cached_logo); 554 GetLogo(); 555 } 556 557 TEST_F(LogoTrackerTest, RerequestSoftExpiredCachedLogo) { 558 Logo cached_logo = GetSampleLogo(logo_url_, test_clock_->Now()); 559 cached_logo.metadata.expiration_time = 560 test_clock_->Now() - base::TimeDelta::FromDays(5); 561 cached_logo.metadata.can_show_after_expiration = true; 562 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 563 564 Logo fresh_logo = GetSampleLogo2(logo_url_, test_clock_->Now()); 565 SetServerResponse(ServerResponse(fresh_logo)); 566 567 logo_cache_->ExpectSetCachedLogo(&fresh_logo); 568 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(0); 569 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(1)); 570 observer_.ExpectCachedAndFreshLogos(&cached_logo, &fresh_logo); 571 572 GetLogo(); 573 } 574 575 TEST_F(LogoTrackerTest, DeleteAncientCachedLogo) { 576 SetServerResponse("", net::URLRequestStatus::FAILED, net::HTTP_OK); 577 Logo cached_logo = GetSampleLogo(logo_url_, test_clock_->Now()); 578 cached_logo.metadata.expiration_time = 579 test_clock_->Now() - base::TimeDelta::FromDays(200); 580 cached_logo.metadata.can_show_after_expiration = true; 581 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 582 583 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(0); 584 EXPECT_CALL(*logo_cache_, SetCachedLogo(_)).Times(0); 585 EXPECT_CALL(*logo_cache_, SetCachedLogo(NULL)).Times(AnyNumber()); 586 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(1)); 587 observer_.ExpectCachedLogo(NULL); 588 GetLogo(); 589 } 590 591 TEST_F(LogoTrackerTest, DeleteExpiredCachedLogo) { 592 SetServerResponse("", net::URLRequestStatus::FAILED, net::HTTP_OK); 593 Logo cached_logo = GetSampleLogo(logo_url_, test_clock_->Now()); 594 cached_logo.metadata.expiration_time = 595 test_clock_->Now() - base::TimeDelta::FromSeconds(1); 596 cached_logo.metadata.can_show_after_expiration = false; 597 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 598 599 EXPECT_CALL(*logo_cache_, UpdateCachedLogoMetadata(_)).Times(0); 600 EXPECT_CALL(*logo_cache_, SetCachedLogo(_)).Times(0); 601 EXPECT_CALL(*logo_cache_, SetCachedLogo(NULL)).Times(AnyNumber()); 602 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(1)); 603 observer_.ExpectCachedLogo(NULL); 604 GetLogo(); 605 } 606 607 // Tests that deal with multiple listeners. 608 609 void EnqueueObservers(LogoTracker* logo_tracker, 610 const ScopedVector<MockLogoObserver>& observers, 611 size_t start_index) { 612 if (start_index >= observers.size()) 613 return; 614 615 logo_tracker->GetLogo(observers[start_index]); 616 base::MessageLoop::current()->PostTask(FROM_HERE, 617 base::Bind(&EnqueueObservers, 618 logo_tracker, 619 base::ConstRef(observers), 620 start_index + 1)); 621 } 622 623 TEST_F(LogoTrackerTest, SupportOverlappingLogoRequests) { 624 Logo cached_logo = GetSampleLogo(logo_url_, test_clock_->Now()); 625 logo_cache_->EncodeAndSetCachedLogo(cached_logo); 626 ON_CALL(*logo_cache_, SetCachedLogo(_)).WillByDefault(Return()); 627 628 Logo fresh_logo = GetSampleLogo2(logo_url_, test_clock_->Now()); 629 std::string response = ServerResponse(fresh_logo); 630 SetServerResponse(response); 631 SetServerResponseWhenFingerprint(cached_logo.metadata.fingerprint, response); 632 633 const int kNumListeners = 10; 634 ScopedVector<MockLogoObserver> listeners; 635 for (int i = 0; i < kNumListeners; ++i) { 636 MockLogoObserver* listener = new MockLogoObserver(); 637 listener->ExpectCachedAndFreshLogos(&cached_logo, &fresh_logo); 638 listeners.push_back(listener); 639 } 640 EnqueueObservers(logo_tracker_, listeners, 0); 641 642 EXPECT_CALL(*logo_cache_, SetCachedLogo(_)).Times(AtMost(3)); 643 EXPECT_CALL(*logo_cache_, OnGetCachedLogo()).Times(AtMost(3)); 644 645 base::RunLoop().RunUntilIdle(); 646 } 647 648 TEST_F(LogoTrackerTest, DeleteObserversWhenLogoURLChanged) { 649 MockLogoObserver listener1; 650 listener1.ExpectNoLogo(); 651 logo_tracker_->GetLogo(&listener1); 652 653 logo_url_ = GURL("http://example.com/new-logo-url"); 654 logo_tracker_->SetServerAPI(logo_url_, 655 base::Bind(&GoogleParseLogoResponse), 656 base::Bind(&GoogleAppendFingerprintToLogoURL)); 657 Logo logo = GetSampleLogo(logo_url_, test_clock_->Now()); 658 SetServerResponse(ServerResponse(logo)); 659 660 MockLogoObserver listener2; 661 listener2.ExpectFreshLogo(&logo); 662 logo_tracker_->GetLogo(&listener2); 663 664 base::RunLoop().RunUntilIdle(); 665 } 666 667 } // namespace 668 669 } // namespace search_provider_logos 670