• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AreImagesSameSize(const SkBitmap & bitmap1,const SkBitmap & bitmap2)46 bool AreImagesSameSize(const SkBitmap& bitmap1, const SkBitmap& bitmap2) {
47   return bitmap1.width() == bitmap2.width() &&
48          bitmap1.height() == bitmap2.height();
49 }
50 
EncodeBitmapAsPNG(const SkBitmap & bitmap)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 
EncodeBitmapAsPNGBase64(const SkBitmap & bitmap)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 
MakeBitmap(int width,int height)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 
EncodeLogo(const Logo & logo)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 
DecodeLogo(const EncodedLogo & encoded_logo)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 
GetSampleLogo(const GURL & logo_url,base::Time response_time)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 
GetSampleLogo2(const GURL & logo_url,base::Time response_time)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 
MakeServerResponse(const SkBitmap & image,const std::string & on_click_url,const std::string & alt_text,const std::string & mime_type,const std::string & fingerprint,base::TimeDelta time_to_live)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 
MakeServerResponse(const Logo & logo,base::TimeDelta time_to_live)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 
ExpectLogosEqual(const Logo * expected_logo,const Logo * actual_logo)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 
ExpectLogosEqual(const Logo * expected_logo,const EncodedLogo * actual_encoded_logo)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 
ACTION_P(ExpectLogosEqualAction,expected_logo)178 ACTION_P(ExpectLogosEqualAction, expected_logo) {
179   ExpectLogosEqual(expected_logo, arg0);
180 }
181 
182 class MockLogoCache : public LogoCache {
183  public:
MockLogoCache()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 
EncodeAndSetCachedLogo(const Logo & logo)202   void EncodeAndSetCachedLogo(const Logo& logo) {
203     EncodedLogo encoded_logo = EncodeLogo(logo);
204     SetCachedLogo(&encoded_logo);
205   }
206 
ExpectSetCachedLogo(const Logo * expected_logo)207   void ExpectSetCachedLogo(const Logo* expected_logo) {
208     Mock::VerifyAndClearExpectations(this);
209     EXPECT_CALL(*this, SetCachedLogo(_))
210         .WillOnce(ExpectLogosEqualAction(expected_logo));
211   }
212 
UpdateCachedLogoMetadataInternal(const LogoMetadata & metadata)213   void UpdateCachedLogoMetadataInternal(const LogoMetadata& metadata) {
214     metadata_.reset(new LogoMetadata(metadata));
215   }
216 
GetCachedLogoMetadataInternal()217   virtual const LogoMetadata* GetCachedLogoMetadataInternal() {
218     return metadata_.get();
219   }
220 
SetCachedLogoInternal(const EncodedLogo * logo)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 
GetCachedLogo()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:
~MockLogoObserver()238   virtual ~MockLogoObserver() {}
239 
ExpectNoLogo()240   void ExpectNoLogo() {
241     Mock::VerifyAndClearExpectations(this);
242     EXPECT_CALL(*this, OnLogoAvailable(_, _)).Times(0);
243     EXPECT_CALL(*this, OnObserverRemoved()).Times(1);
244   }
245 
ExpectCachedLogo(const Logo * expected_cached_logo)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 
ExpectFreshLogo(const Logo * expected_fresh_logo)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 
ExpectCachedAndFreshLogos(const Logo * expected_cached_logo,const Logo * expected_fresh_logo)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:
TestLogoDelegate()280   TestLogoDelegate() {}
~TestLogoDelegate()281   virtual ~TestLogoDelegate() {}
282 
DecodeUntrustedImage(const scoped_refptr<base::RefCountedString> & encoded_image,base::Callback<void (const SkBitmap &)> image_decoded_callback)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:
LogoTrackerTest()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 
TearDown()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 
ServerResponse(const Logo & logo) const355 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 
SetServerResponse(const std::string & response,net::URLRequestStatus::Status request_status,net::HttpStatusCode response_code)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 
SetServerResponseWhenFingerprint(const std::string & fingerprint,const std::string & response_when_fingerprint,net::URLRequestStatus::Status request_status,net::HttpStatusCode response_code)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 
GetLogo()381 void LogoTrackerTest::GetLogo() {
382   logo_tracker_->GetLogo(&observer_);
383   base::RunLoop().RunUntilIdle();
384 }
385 
386 // Tests -----------------------------------------------------------------------
387 
TEST_F(LogoTrackerTest,DownloadAndCacheLogo)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 
TEST_F(LogoTrackerTest,EmptyCacheAndFailedDownload)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 
TEST_F(LogoTrackerTest,AcceptMinimalLogoResponse)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 
TEST_F(LogoTrackerTest,ReturnCachedLogo)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 
TEST_F(LogoTrackerTest,ValidateCachedLogoFingerprint)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 
TEST_F(LogoTrackerTest,UpdateCachedLogo)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 
TEST_F(LogoTrackerTest,InvalidateCachedLogo)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 
TEST_F(LogoTrackerTest,DeleteCachedLogoFromOldUrl)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 
TEST_F(LogoTrackerTest,LogoWithTTLCannotBeShownAfterExpiration)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 
TEST_F(LogoTrackerTest,LogoWithoutTTLCanBeShownAfterExpiration)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 
TEST_F(LogoTrackerTest,UseSoftExpiredCachedLogo)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 
TEST_F(LogoTrackerTest,RerequestSoftExpiredCachedLogo)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 
TEST_F(LogoTrackerTest,DeleteAncientCachedLogo)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 
TEST_F(LogoTrackerTest,DeleteExpiredCachedLogo)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 
EnqueueObservers(LogoTracker * logo_tracker,const ScopedVector<MockLogoObserver> & observers,size_t start_index)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 
TEST_F(LogoTrackerTest,SupportOverlappingLogoRequests)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 
TEST_F(LogoTrackerTest,DeleteObserversWhenLogoURLChanged)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