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