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