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