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

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

Issue 2660883002: Introduce a Doodle Fetcher for NTP (Closed)
Patch Set: Rebase. 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
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/message_loop/message_loop.h"
15 #include "base/test/simple_test_clock.h"
16 #include "base/values.h"
17 #include "components/google/core/browser/google_switches.h"
18 #include "components/google/core/browser/google_url_tracker.h"
19 #include "net/http/http_status_code.h"
20 #include "net/url_request/test_url_fetcher_factory.h"
21 #include "net/url_request/url_request_status.h"
22 #include "net/url_request/url_request_test_util.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25
26 using testing::Eq;
27
28 namespace doodle {
29
30 namespace {
31
32 const char kDoodleConfigPath[] = "/async/ddljson";
33
34 const char kSampleResponse[] = R"json()]}'{
35 "ddljson": {
36 "alt_text":"Mouseover Text",
37 "doodle_type":"SIMPLE",
38 "id":0,
39 "interactive_html":"\u003cstyle\u003e\u003c\/style\u003e",
40 "interactive_js":"(function(){}).call(this);",
41 "search_url":"/search?q\u003dtest",
42 "share_text":"Share Text #GoogleDoodle\nhttps://g.co/doodle/8hfqzq",
43 "short_link":"//g.co/doodle/8hfqzq",
44 "show_now_header_search_affordance":false,
45 "show_now_header_share_button":true,
46 "target_url":"/search?q\u003dtest\u0026sa\u003dX\u0026ved\u003d0ahUKEwjm",
47 "time_to_live_ms":55000,
48 "large_image": {
49 "background_color":"#ffffff",
50 "height":225,
51 "image_id":0,
52 "is_animated_gif":true,
53 "is_cta":false,
54 "slot":1,
55 "url":"/logos/doodles/2015/new-years-eve-2015-5985438795825152-hp.gif",
56 "width":489
57 },
58 "large_cta_image": {
59 "background_color":"#ffffff",
60 "height":225,
61 "image_id":0,
62 "is_animated_gif":true,
63 "is_cta":true,
64 "slot":8,
65 "url":"/logos/doodles/2015/new-years-eve-2015-5985438795825152-cta.gif",
66 "width":489
67 },
68 "transparent_large_image": {
69 "background_color":"",
70 "height":225,
71 "image_id":0,
72 "is_animated_gif":false,
73 "is_cta":false,
74 "slot":2,
75 "url":"/logos/doodles/2015/new-years-eve-2015-5985438795825152-thp.png",
76 "width":510
77 }
78 }})json";
79
80 const char kMinimalAvailableDoodleResponse[] = R"json({"ddljson": {
81 "large_image": {
82 "url":"/logos/doodles/2015/new-years-eve-2015-5985438795825152-hp.gif"
83 }}})json";
84
85 const char kMalformedImageUrlResponse[] = R"json({"ddljson": {
86 "time_to_live_ms":55000,
87 "large_image": {
88 "height":225,
89 "width":489
90 }}})json";
91
92 // Required to instantiate a GoogleUrlTracker in UNIT_TEST_MODE.
93 class GoogleURLTrackerClientStub : public GoogleURLTrackerClient {
94 public:
95 GoogleURLTrackerClientStub() {}
96 ~GoogleURLTrackerClientStub() override {}
97
98 bool IsBackgroundNetworkingEnabled() override { return true; }
99
100 PrefService* GetPrefs() override { return nullptr; }
101
102 net::URLRequestContextGetter* GetRequestContext() override { return nullptr; }
103
104 private:
105 DISALLOW_COPY_AND_ASSIGN(GoogleURLTrackerClientStub);
106 };
107
108 std::string Resolve(const std::string& relative_url) {
109 return GURL(GoogleURLTracker::kDefaultGoogleHomepage)
110 .Resolve(relative_url)
111 .spec();
112 }
113
114 void ParseJson(
115 const std::string& json,
116 const base::Callback<void(std::unique_ptr<base::Value> json)>& success,
117 const base::Callback<void(const std::string&)>& error) {
118 base::JSONReader json_reader;
119 std::unique_ptr<base::Value> value = json_reader.ReadToValue(json);
120 if (value) {
121 success.Run(std::move(value));
122 } else {
123 error.Run(json_reader.GetErrorMessage());
124 }
125 }
126
127 } // namespace
128
129 class DoodleFetcherTest : public testing::Test {
130 public:
131 DoodleFetcherTest()
132 : url_(GURL(GoogleURLTracker::kDefaultGoogleHomepage)),
133 last_state_(DoodleState::NO_DOODLE),
134 last_response_(base::nullopt),
Marc Treib 2017/02/08 10:51:36 Isn't this the default value anyway?
fhorschig 2017/02/08 18:37:26 Documentation says you are right. Removed.
135 context_getter(new net::TestURLRequestContextGetter(
136 base::ThreadTaskRunnerHandle::Get())),
Marc Treib 2017/02/08 10:51:36 I think it'd be clearer to explicitly pass in the
fhorschig 2017/02/08 18:37:25 Reverted to how I did this intermediately. It doe
137 google_url_tracker_(base::MakeUnique<GoogleURLTrackerClientStub>(),
138 GoogleURLTracker::UNIT_TEST_MODE),
139 doodle_fetcher_(context_getter.get(),
140 &google_url_tracker_,
141 base::Bind(ParseJson)) {
142 // Random difference to 0 ensures that expiry_dates are really relative.
143 clock_.Advance(base::TimeDelta::FromMilliseconds(1123581321));
144 auto clock = base::MakeUnique<base::SimpleTestClock>();
145 clock->SetNow(clock_.Now());
146 doodle_fetcher_.SetClockForTesting(std::move(clock));
Marc Treib 2017/02/08 10:51:37 I guess we never need to set anything in the clock
fhorschig 2017/02/08 18:37:26 Exactly. I just noticed that we have exactly one f
147 }
148
149 void RespondWithData(const std::string& data) {
150 RespondToFetcherWithData(GetRunningFetcher(), data);
151 }
152
153 void RespondToFetcherWithData(net::TestURLFetcher* url_fetcher,
154 const std::string& data) {
155 url_fetcher->set_status(net::URLRequestStatus());
156 url_fetcher->set_response_code(net::HTTP_OK);
157 url_fetcher->SetResponseString(data);
158 // Call the URLFetcher delegate to continue the test.
159 url_fetcher->delegate()->OnURLFetchComplete(url_fetcher);
160 }
161
162 void RespondWithError() {
163 net::TestURLFetcher* url_fetcher = GetRunningFetcher();
164 url_fetcher->set_status(
165 net::URLRequestStatus::FromError(net::ERR_INVALID_URL));
166 url_fetcher->SetResponseString("");
167 // Call the URLFetcher delegate to continue the test.
168 url_fetcher->delegate()->OnURLFetchComplete(url_fetcher);
169 }
170
171 net::TestURLFetcher* GetRunningFetcher() {
172 // All created TestURLFetchers have ID 0 by default.
Marc Treib 2017/02/08 10:51:36 I guess there's never more than one URLFetcher at
fhorschig 2017/02/08 18:37:25 A test would break (RespondsToMultipleRequestsWith
Marc Treib 2017/02/09 10:36:24 Thanks! (I was mostly wondering what GetFetcherByI
fhorschig 2017/02/09 13:23:56 Exactly.
173 net::TestURLFetcher* url_fetcher = url_fetcher_factory_.GetFetcherByID(0);
174 DCHECK(url_fetcher);
175 return url_fetcher;
176 }
177
178 void TriggerFetch() {
Marc Treib 2017/02/08 10:51:36 I think this is a case where it's better to put th
fhorschig 2017/02/08 18:37:26 I did this to make the tests readable like natural
Marc Treib 2017/02/09 10:36:24 I think that's okay. The alternative would be to u
fhorschig 2017/02/09 13:23:56 I agree, the benefit would not be very huge.
179 doodle_fetcher_.FetchDoodle(base::BindOnce(
180 [](DoodleState* state_out, base::Optional<DoodleConfig>* config_out,
181 DoodleState state, const base::Optional<DoodleConfig>& config) {
182 *state_out = state;
183 *config_out = config;
184 },
185 &last_state_, &last_response_));
186 }
187
188 base::Time TimeFromNow(uint64_t milliseconds) {
189 return clock_.Now() + base::TimeDelta::FromMilliseconds(milliseconds);
190 }
191
192 void SetLastState(DoodleState state) { last_state_ = state; }
193
194 DoodleState last_state() const { return last_state_; }
195
196 const base::Optional<DoodleConfig>& last_response() const {
197 return last_response_;
198 }
199
200 private:
201 base::MessageLoop message_loop_;
202 GURL url_;
203 DoodleState last_state_;
204 base::Optional<DoodleConfig> last_response_;
205 net::TestURLFetcherFactory url_fetcher_factory_;
206 base::SimpleTestClock clock_;
207 scoped_refptr<net::TestURLRequestContextGetter> context_getter;
208 GoogleURLTracker google_url_tracker_;
209 DoodleFetcher doodle_fetcher_;
210 };
211
212 TEST_F(DoodleFetcherTest, ReturnsFromFetchWithoutError) {
213 TriggerFetch();
214 RespondWithData(kSampleResponse);
215 EXPECT_THAT(last_state(), Eq(DoodleState::AVAILABLE));
216 EXPECT_TRUE(last_response().has_value());
Marc Treib 2017/02/08 10:51:37 This is what I mean above: Here we're EXPECTing st
fhorschig 2017/02/08 18:37:25 I think I get your thought.
217 }
218
219 TEST_F(DoodleFetcherTest, ReturnsFrom404FetchWithError) {
Marc Treib 2017/02/08 10:51:36 RespondWithError doesn't actually produce a 404. H
fhorschig 2017/02/08 18:37:26 Done. (And thanks for pointing out the wrong code.
220 TriggerFetch();
221 RespondWithError();
222 EXPECT_THAT(last_state(), Eq(DoodleState::DOWNLOAD_ERROR));
223 EXPECT_FALSE(last_response().has_value());
224 }
225
226 TEST_F(DoodleFetcherTest, ReturnsErrorForInvalidJson) {
227 TriggerFetch();
228 RespondWithData("{}");
229 EXPECT_THAT(last_state(), Eq(DoodleState::PARSING_ERROR));
230 EXPECT_FALSE(last_response().has_value());
231 }
232
233 TEST_F(DoodleFetcherTest, ResponseContainsValidBaseInformation) {
234 TriggerFetch();
235 RespondWithData(kSampleResponse);
236 EXPECT_THAT(last_state(), Eq(DoodleState::AVAILABLE));
237 ASSERT_TRUE(last_response().has_value());
238 DoodleConfig config = last_response().value();
239
240 EXPECT_TRUE(config.search_url.is_valid());
241 EXPECT_THAT(config.search_url, Eq(Resolve("/search?q\u003dtest")));
242 EXPECT_THAT(config.fullpage_interactive_url, Eq(GURL()));
Marc Treib 2017/02/08 10:51:37 nit: EXPECT_TRUE(...is_empty()) ?
fhorschig 2017/02/08 18:37:25 Done.
243 EXPECT_TRUE(config.target_url.is_valid());
244 EXPECT_THAT(config.target_url,
245 Eq(Resolve("/search?q\u003dtest\u0026sa\u003dX\u0026ved\u003d"
246 "0ahUKEwjm")));
247 EXPECT_THAT(config.doodle_type, Eq(DoodleType::SIMPLE));
248 EXPECT_THAT(config.alt_text, Eq("Mouseover Text"));
249 EXPECT_THAT(config.interactive_html,
250 Eq("\u003cstyle\u003e\u003c/style\u003e"));
251 EXPECT_THAT(config.interactive_js, Eq("(function(){}).call(this);"));
252
253 EXPECT_THAT(config.expiry_date, Eq(TimeFromNow(55000)));
254 }
255
256 TEST_F(DoodleFetcherTest, ResponseContainsExpiresWithinThirtyDays) {
257 TriggerFetch();
258 RespondWithData(R"json({"ddljson": {
259 "time_to_live_ms":5184000000,
260 "large_image": {"url":"/logos/doodles/2015/some.gif"}
261 }})json"); // 60 days
262
263 EXPECT_THAT(last_state(), Eq(DoodleState::AVAILABLE));
264 ASSERT_TRUE(last_response().has_value());
265 EXPECT_THAT(last_response().value().expiry_date,
266 Eq(TimeFromNow(24ul * 60 * 60 * 1000 /* ms */))); // 1 day
267 }
268
269 TEST_F(DoodleFetcherTest, AvailableDoodleExpiresNowWithNegativeTTL) {
270 TriggerFetch();
271 RespondWithData(R"json({"ddljson": {
272 "time_to_live_ms":-1,
273 "large_image": {"url":"/logos/doodles/2015/some.gif"}
274 }})json");
275
276 EXPECT_THAT(last_state(), Eq(DoodleState::AVAILABLE));
277 ASSERT_TRUE(last_response().has_value());
278 EXPECT_THAT(last_response().value().expiry_date, Eq(TimeFromNow(0)));
279 }
280
281 TEST_F(DoodleFetcherTest, AvailableDoodleExpiresNowWithoutValidTTL) {
282 TriggerFetch();
283 RespondWithData(kMinimalAvailableDoodleResponse);
284
285 EXPECT_THAT(last_state(), Eq(DoodleState::AVAILABLE));
286 ASSERT_TRUE(last_response().has_value());
287 EXPECT_THAT(last_response().value().expiry_date, Eq(TimeFromNow(0)));
288 }
289
290 TEST_F(DoodleFetcherTest, ExpectToReturnNoDoodleForMalfomedImageUrls) {
291 SetLastState(DoodleState::AVAILABLE);
292
293 TriggerFetch();
294 RespondWithData(kMalformedImageUrlResponse);
295
296 EXPECT_THAT(last_state(), Eq(DoodleState::NO_DOODLE));
297 EXPECT_FALSE(last_response().has_value());
298 }
299
300 TEST_F(DoodleFetcherTest, EmptyResponsesCausesNoDoodleState) {
301 SetLastState(DoodleState::AVAILABLE);
302
303 TriggerFetch();
304 RespondWithData("{\"ddljson\":{}}");
305
306 EXPECT_THAT(last_state(), Eq(DoodleState::NO_DOODLE));
307 EXPECT_FALSE(last_response().has_value());
308 }
309
310 TEST_F(DoodleFetcherTest, ResponseContainsExactlyTheSampleImages) {
311 TriggerFetch();
312 RespondWithData(kSampleResponse);
313
314 EXPECT_THAT(last_state(), Eq(DoodleState::AVAILABLE));
315 ASSERT_TRUE(last_response().has_value());
316 DoodleConfig config = last_response().value();
317
318 EXPECT_TRUE(config.transparent_large_image.url.is_valid());
319 EXPECT_THAT(config.transparent_large_image.url.spec(),
320 Eq(Resolve("/logos/doodles/2015/new-years-eve-2015-5985438795"
321 "825152-thp.png")));
322 EXPECT_THAT(config.transparent_large_image.width, Eq(510));
323 EXPECT_THAT(config.transparent_large_image.height, Eq(225));
324 EXPECT_FALSE(config.transparent_large_image.is_animated_gif);
325 EXPECT_FALSE(config.transparent_large_image.is_cta);
326
327 EXPECT_TRUE(config.large_image.url.is_valid());
328 EXPECT_THAT(config.large_image.url.spec(),
329 Eq(Resolve("/logos/doodles/2015/new-years-eve-2015-5985438795"
330 "825152-hp.gif")));
331 EXPECT_THAT(config.large_image.width, Eq(489));
332 EXPECT_THAT(config.large_image.height, Eq(225));
333 EXPECT_TRUE(config.large_image.is_animated_gif);
334 EXPECT_FALSE(config.large_image.is_cta);
335
336 EXPECT_TRUE(config.large_cta_image.url.is_valid());
337 EXPECT_THAT(config.large_cta_image.url.spec(),
338 Eq(Resolve("/logos/doodles/2015/new-years-eve-2015-5985438795"
339 "825152-cta.gif")));
340 EXPECT_THAT(config.large_cta_image.width, Eq(489));
341 EXPECT_THAT(config.large_cta_image.height, Eq(225));
342 EXPECT_TRUE(config.large_cta_image.is_animated_gif);
343 EXPECT_TRUE(config.large_cta_image.is_cta);
344 }
345
346 TEST_F(DoodleFetcherTest, RespondsToMultipleRequestsWithSameFetcher) {
347 TriggerFetch();
348 net::URLFetcher* first_created_fetcher = GetRunningFetcher();
349 TriggerFetch();
350 net::URLFetcher* second_created_fetcher = GetRunningFetcher();
351
352 EXPECT_THAT(first_created_fetcher, Eq(second_created_fetcher));
353 }
354
355 TEST_F(DoodleFetcherTest, ReceivesBaseUrlFromTracker) {
356 TriggerFetch();
357
358 EXPECT_THAT(GetRunningFetcher()->GetOriginalURL(),
359 Eq(GURL(GoogleURLTracker::kDefaultGoogleHomepage)
Marc Treib 2017/02/08 10:51:36 google_url_tracker_->google_url() ?
fhorschig 2017/02/08 18:37:25 Done. Kind of.
360 .Resolve(kDoodleConfigPath)));
361 }
362
363 TEST_F(DoodleFetcherTest, OverridesBaseUrlWithCommandLineArgument) {
364 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
365 switches::kGoogleBaseURL, "http://www.google.kz");
366
367 TriggerFetch();
368
369 EXPECT_THAT(GetRunningFetcher()->GetOriginalURL(),
370 Eq(GURL("http://www.google.kz").Resolve(kDoodleConfigPath)));
371 }
372
373 } // namespace doodle
OLDNEW
« components/doodle/doodle_fetcher.cc ('K') | « components/doodle/doodle_fetcher.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698