Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(269)

Side by Side Diff: components/doodle/doodle_fetcher_unittest.cc

Issue 2660883002: Introduce a Doodle Fetcher for NTP (Closed)
Patch Set: Tests for URL and TTL. Stubs/Fakes instead nullptr. Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « components/doodle/doodle_fetcher.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2016 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/doodle/doodle_fetcher.h"
6
7 #include <string>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/json/json_reader.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/test/test_mock_time_task_runner.h"
15 #include "base/threading/sequenced_task_runner_handle.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "base/values.h"
18 #include "components/google/core/browser/google_switches.h"
19 #include "components/google/core/browser/google_url_tracker.h"
20 #include "net/http/http_status_code.h"
21 #include "net/url_request/test_url_fetcher_factory.h"
22 #include "net/url_request/url_request_status.h"
23 #include "net/url_request/url_request_test_util.h"
24 #include "testing/gmock/include/gmock/gmock.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26
27 using testing::Eq;
28 using testing::IsEmpty;
29
30 namespace doodle {
31
32 namespace {
33
34 const char kDoodleConfigPath[] = "/async/ddljson";
35
36 const char kSampleResponse[] = R"json()]}'{
37 "ddljson": {
38 "alt_text":"Mouseover Text",
39 "doodle_type":"SIMPLE",
40 "id":0,
41 "interactive_html":"\u003cscript\u003e\u003c\/script\u003e",
Marc Treib 2017/02/06 17:26:36 Are the Unicode escape sequences required?!
fhorschig 2017/02/08 09:10:24 No. But they are part of the response and have to
42 "search_url":"/search?q\u003dtest",
43 "share_text":"Share Text #GoogleDoodle\nhttps://g.co/doodle/8hfqzq",
44 "short_link":"//g.co/doodle/8hfqzq",
45 "show_now_header_search_affordance":false,
46 "show_now_header_share_button":true,
47 "target_url":"/search?q\u003dtest\u0026sa\u003dX\u0026ved\u003d0ahUKEwjm",
48 "time_to_live_ms":55000,
49 "large_image": {
50 "background_color":"#ffffff",
51 "height":225,
52 "image_id":0,
53 "is_animated_gif":true,
54 "is_cta":false,
55 "slot":1,
56 "url":"/logos/doodles/2015/new-years-eve-2015-5985438795825152-hp.gif",
57 "width":489
58 },
59 "large_cta_image": {
60 "background_color":"#ffffff",
61 "height":225,
62 "image_id":0,
63 "is_animated_gif":true,
64 "is_cta":true,
65 "slot":8,
66 "url":"/logos/doodles/2015/new-years-eve-2015-5985438795825152-cta.gif",
67 "width":489
68 },
69 "transparent_large_image": {
70 "background_color":"",
71 "height":225,
72 "image_id":0,
73 "is_animated_gif":false,
74 "is_cta":false,
75 "slot":2,
76 "url":"/logos/doodles/2015/new-years-eve-2015-5985438795825152-thp.png",
77 "width":510
78 }
79 }})json";
80
81 const char kMinimalAvailableDoodleResponse[] = R"json({"ddljson": {
82 "large_image": {
83 "url":"/logos/doodles/2015/new-years-eve-2015-5985438795825152-hp.gif"
84 }}})json";
85
86 const char kMalformedImageUrlResponse[] = R"json({"ddljson": {
87 "time_to_live_ms":55000,
88 "large_image": {
89 "height":225,
90 "width":489
91 }}})json";
92
93 // Default Factory for FakeURLFetcherFactory that always fails.
94 class FailingURLFetcherFactory : public net::URLFetcherFactory {
95 public:
96 std::unique_ptr<net::URLFetcher> CreateURLFetcher(
97 int id,
98 const GURL& url,
99 net::URLFetcher::RequestType request_type,
100 net::URLFetcherDelegate* d) override {
101 return base::MakeUnique<net::FakeURLFetcher>(
102 url, d, /*response_data=*/std::string(), net::HTTP_NOT_FOUND,
103 net::URLRequestStatus::FAILED);
104 }
105 };
106
107 // Required to instantiate a BaseUrlTracker in GoogleURLTracker::UNIT_TEST_MODE.
Marc Treib 2017/02/06 17:26:36 s/BaseUrlTracker/GoogleURLTracker/ ? UNIT_TEST_MO
fhorschig 2017/02/08 09:10:24 Okay, removed.
108 class GoogleURLTrackerClientStub : public GoogleURLTrackerClient {
109 public:
110 GoogleURLTrackerClientStub() {}
111 ~GoogleURLTrackerClientStub() override {}
112
113 bool IsBackgroundNetworkingEnabled() override { return true; }
114
115 PrefService* GetPrefs() override { return nullptr; }
116
117 net::URLRequestContextGetter* GetRequestContext() override { return nullptr; }
118
119 private:
120 DISALLOW_COPY_AND_ASSIGN(GoogleURLTrackerClientStub);
121 };
122
123 std::string Resolve(const std::string& relative_url) {
124 return GURL(GoogleURLTracker::kDefaultGoogleHomepage)
125 .Resolve(relative_url)
126 .spec();
127 }
128
129 void ParseJson(
130 const std::string& json,
131 const base::Callback<void(std::unique_ptr<base::Value> json)>& success,
132 const base::Callback<void(const std::string&)>& error) {
133 base::JSONReader json_reader;
134 std::unique_ptr<base::Value> value = json_reader.ReadToValue(json);
135 if (value) {
136 success.Run(std::move(value));
137 } else {
138 error.Run(json_reader.GetErrorMessage());
139 }
140 }
141
142 } // namespace
143
144 class DoodleFetcherTest : public ::testing::Test {
145 public:
146 DoodleFetcherTest()
147 : url_(GURL(GoogleURLTracker::kDefaultGoogleHomepage)),
148 // Constructor of FakeURLFetcherFactory uses FailingURLFetcherFactory
149 // as fallback for instantiating fetchers for undefined paths.
150 url_fetcher_factory_(
151 /*default_implementation=*/&failing_fetcher_factory_),
152 mock_task_runner_(new base::TestMockTimeTaskRunner()),
153 mock_task_runner_handle_(mock_task_runner_),
154 context_getter(
155 new net::TestURLRequestContextGetter(mock_task_runner_.get())),
156 google_url_tracker_(base::MakeUnique<GoogleURLTrackerClientStub>(),
157 GoogleURLTracker::UNIT_TEST_MODE) {
158 // Random difference to 0 ensures that expiry_dates are really relative.
159 mock_task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(80082));
Marc Treib 2017/02/06 17:26:35 I just now understood this particular number. Migh
fhorschig 2017/02/08 09:10:24 Changed. Sorry, couldn't resist...
160 }
161
162 void SetResponse(const std::string& data) { SetResponseForUrl(url_, data); }
163
164 void SetResponseForUrl(const GURL& url, const std::string& data) {
165 url_fetcher_factory_.ClearFakeResponses();
166 url_fetcher_factory_.SetFakeResponse(url.Resolve(kDoodleConfigPath), data,
167 net::HTTP_OK,
168 net::URLRequestStatus::SUCCESS);
169 }
170
171 void SetResponseToError() {
172 url_fetcher_factory_.ClearFakeResponses();
173 url_fetcher_factory_.SetFakeResponse(url_, "", net::HTTP_NOT_FOUND,
174 net::URLRequestStatus::FAILED);
175 }
176
177 DoodleState FetchDoodle(base::Optional<DoodleConfig>* config) {
178 DoodleState state;
179 std::unique_ptr<DoodleFetcher> fetcher = CreateDoodleFetcher();
180 TriggerFetch(fetcher.get(), &state, config);
181 WaitForCallbacksToReturn();
182 return state;
183 }
184
185 std::unique_ptr<DoodleFetcher> CreateDoodleFetcher() {
186 return CreateDoodleFetcherWithTracker(&google_url_tracker_);
187 }
188
189 std::unique_ptr<DoodleFetcher> CreateDoodleFetcherWithTracker(
190 GoogleURLTracker* google_url_tracker) {
191 std::unique_ptr<DoodleFetcher> fetcher = base::MakeUnique<DoodleFetcher>(
192 context_getter.get(), google_url_tracker, base::Bind(ParseJson));
193 fetcher->SetClockForTesting(mock_task_runner_->GetMockClock());
194 return fetcher;
195 }
196
197 void TriggerFetch(DoodleFetcher* fetcher,
198 DoodleState* state,
199 base::Optional<DoodleConfig>* config) {
200 fetcher->FetchDoodle(base::BindOnce(
201 [](DoodleState* state_out, base::Optional<DoodleConfig>* config_out,
202 DoodleState state, const base::Optional<DoodleConfig>& config) {
203 *state_out = std::move(state);
204 *config_out = config;
205 },
206 state, config));
207 }
208
209 base::Time TimeFromNow(uint64_t milliseconds) {
210 return mock_task_runner_->GetMockClock()->Now() +
211 base::TimeDelta::FromMilliseconds(milliseconds);
212 }
213
214 void WaitForCallbacksToReturn() {
215 mock_task_runner_->FastForwardUntilNoTasksRemain();
216 }
217
218 size_t NumberOfPendingTasks() {
219 return mock_task_runner_->GetPendingTaskCount();
220 }
221
222 private:
223 GURL url_;
224 net::FakeURLFetcherFactory url_fetcher_factory_;
225 FailingURLFetcherFactory failing_fetcher_factory_;
226 scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_;
227 base::ThreadTaskRunnerHandle mock_task_runner_handle_;
228 scoped_refptr<net::TestURLRequestContextGetter> context_getter;
229 GoogleURLTracker google_url_tracker_;
230 };
231
232 TEST_F(DoodleFetcherTest, ReturnsFromFetchWithoutError) {
233 SetResponse(kSampleResponse);
234
235 base::Optional<DoodleConfig> response;
236 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::AVAILABLE));
237 EXPECT_TRUE(response.has_value());
238 }
239
240 TEST_F(DoodleFetcherTest, ReturnsFrom404FetchWithError) {
241 SetResponseToError();
242
243 base::Optional<DoodleConfig> response;
244 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::DOWNLOAD_ERROR));
245 EXPECT_FALSE(response.has_value());
246 }
247
248 TEST_F(DoodleFetcherTest, ReturnsErrorForInvalidJson) {
249 SetResponse("{}");
250
251 base::Optional<DoodleConfig> response;
252 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::PARSING_ERROR));
253 EXPECT_FALSE(response.has_value());
254 }
255
256 TEST_F(DoodleFetcherTest, ResponseContainsValidBaseInformation) {
257 SetResponse(kSampleResponse);
258
259 base::Optional<DoodleConfig> response;
260 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::AVAILABLE));
261 ASSERT_TRUE(response.has_value());
262 DoodleConfig config = response.value();
263
264 EXPECT_TRUE(config.search_url.is_valid());
265 EXPECT_THAT(config.search_url, Eq(Resolve("/search?q\u003dtest")));
266 EXPECT_THAT(config.fullpage_interactive_url, Eq(GURL()));
267 EXPECT_TRUE(config.target_url.is_valid());
268 EXPECT_THAT(config.target_url,
269 Eq(Resolve("/search?q\u003dtest\u0026sa\u003dX\u0026ved\u003d"
270 "0ahUKEwjm")));
271 EXPECT_THAT(config.doodle_type, Eq(DoodleType::SIMPLE));
272 EXPECT_THAT(config.alt_text, Eq("Mouseover Text"));
273 EXPECT_THAT(config.interactive_html,
274 Eq("\u003cscript\u003e\u003c/script\u003e"));
275
276 EXPECT_THAT(config.expiry_date, Eq(TimeFromNow(55000)));
277 }
278
279 TEST_F(DoodleFetcherTest, ResponseContainsExpiresWithinThirtyDays) {
280 base::Optional<DoodleConfig> response;
281 SetResponse(R"json({"ddljson": {
282 "time_to_live_ms":5184000000,
283 "large_image": {"url":"/logos/doodles/2015/some.gif"}
284 }})json"); // 60 days
285
286 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::AVAILABLE));
287 ASSERT_TRUE(response.has_value());
288 EXPECT_THAT(response.value().expiry_date,
289 Eq(TimeFromNow(30ul * 24 * 60 * 60 * 1000 /* ms */))); // 30 days
290 }
291
292 TEST_F(DoodleFetcherTest, AvailableDoodleExpiresNowWithoutValidTTL) {
293 base::Optional<DoodleConfig> response;
294 SetResponse(R"json({"ddljson": {
295 "time_to_live_ms":-1,
296 "large_image": {"url":"/logos/doodles/2015/some.gif"}
297 }})json");
298
299 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::AVAILABLE));
300 ASSERT_TRUE(response.has_value());
301 EXPECT_THAT(response.value().expiry_date, Eq(TimeFromNow(0)));
302
303 SetResponse(kMinimalAvailableDoodleResponse);
304
305 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::AVAILABLE));
306 ASSERT_TRUE(response.has_value());
307 DoodleConfig config = response.value();
308
309 EXPECT_THAT(config.expiry_date, Eq(TimeFromNow(0)));
310 }
311
312 TEST_F(DoodleFetcherTest, ExpectToReturnNoDoodleForMalfomedImageUrls) {
313 SetResponse(kMalformedImageUrlResponse);
314
315 base::Optional<DoodleConfig> response;
316 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::NO_DOODLE));
317 EXPECT_FALSE(response.has_value());
318 }
319
320 TEST_F(DoodleFetcherTest, EmptyResponsesCausesNoDoodleState) {
321 SetResponse("{\"ddljson\":{}}");
322
323 base::Optional<DoodleConfig> response;
324 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::NO_DOODLE));
325 EXPECT_FALSE(response.has_value());
326 }
327
328 TEST_F(DoodleFetcherTest, ResponseContainsExactlyTheSampleImages) {
329 SetResponse(kSampleResponse);
330
331 base::Optional<DoodleConfig> response;
332 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::AVAILABLE));
333 ASSERT_TRUE(response.has_value());
334 DoodleConfig config = response.value();
335
336 EXPECT_TRUE(config.transparent_large_image.url.is_valid());
337 EXPECT_THAT(config.transparent_large_image.url.spec(),
338 Eq(Resolve("/logos/doodles/2015/new-years-eve-2015-5985438795"
339 "825152-thp.png")));
340 EXPECT_THAT(config.transparent_large_image.width, Eq(510));
341 EXPECT_THAT(config.transparent_large_image.height, Eq(225));
342 EXPECT_FALSE(config.transparent_large_image.is_animated_gif);
343 EXPECT_FALSE(config.transparent_large_image.is_cta);
344
345 EXPECT_TRUE(config.large_image.url.is_valid());
346 EXPECT_THAT(config.large_image.url.spec(),
347 Eq(Resolve("/logos/doodles/2015/new-years-eve-2015-5985438795"
348 "825152-hp.gif")));
349 EXPECT_THAT(config.large_image.width, Eq(489));
350 EXPECT_THAT(config.large_image.height, Eq(225));
351 EXPECT_TRUE(config.large_image.is_animated_gif);
352 EXPECT_FALSE(config.large_image.is_cta);
353
354 EXPECT_TRUE(config.large_cta_image.url.is_valid());
355 EXPECT_THAT(config.large_cta_image.url.spec(),
356 Eq(Resolve("/logos/doodles/2015/new-years-eve-2015-5985438795"
357 "825152-cta.gif")));
358 EXPECT_THAT(config.large_cta_image.width, Eq(489));
359 EXPECT_THAT(config.large_cta_image.height, Eq(225));
360 EXPECT_TRUE(config.large_cta_image.is_animated_gif);
361 EXPECT_TRUE(config.large_cta_image.is_cta);
362 }
363
364 TEST_F(DoodleFetcherTest, RespondsToMultipleRequestsWithSameResponse) {
365 SetResponse(kSampleResponse);
366
367 DoodleState state1;
368 DoodleState state2;
369 base::Optional<DoodleConfig> response1;
370 base::Optional<DoodleConfig> response2;
371 std::unique_ptr<DoodleFetcher> fetcher = CreateDoodleFetcher();
372
373 TriggerFetch(fetcher.get(), &state1, &response1);
374 TriggerFetch(fetcher.get(), &state2, &response2);
375
376 EXPECT_THAT(NumberOfPendingTasks(), Eq(1ul));
377 WaitForCallbacksToReturn();
378
379 EXPECT_THAT(state1, Eq(DoodleState::AVAILABLE));
380 EXPECT_THAT(state2, Eq(DoodleState::AVAILABLE));
381 EXPECT_TRUE(response1.has_value());
382 EXPECT_TRUE(response2.has_value());
383 }
384
385 TEST_F(DoodleFetcherTest, DefaultsToTrackerBaseUrlWithoutGoogleUrlTracker) {
386 DoodleState state;
387 base::Optional<DoodleConfig> response;
388 SetResponseForUrl(GURL(GoogleURLTracker::kDefaultGoogleHomepage),
389 kSampleResponse);
390
391 std::unique_ptr<DoodleFetcher> fetcher =
392 CreateDoodleFetcherWithTracker(/*google_url_tracker=*/nullptr);
393 TriggerFetch(fetcher.get(), &state, &response);
394 WaitForCallbacksToReturn();
395
396 EXPECT_THAT(state, Eq(DoodleState::AVAILABLE));
397 EXPECT_TRUE(response.has_value());
398 }
399
400 TEST_F(DoodleFetcherTest, OverridesBaseUrlWithCommandLineArgument) {
401 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
402 switches::kGoogleBaseURL, "http://www.google.kz");
403 SetResponseForUrl(GURL("http://www.google.kz"), kSampleResponse);
404 base::Optional<DoodleConfig> response;
405 EXPECT_THAT(FetchDoodle(&response), Eq(DoodleState::AVAILABLE));
406 EXPECT_TRUE(response.has_value());
407 }
408
409 } // namespace doodle
OLDNEW
« no previous file with comments | « components/doodle/doodle_fetcher.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698