Move variations component code to variations namespace.
[chromium-blink-merge.git] / components / suggestions / suggestions_service_unittest.cc
blobc6d888400824bba642458b82bb4e6e4b56f330a2
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.
5 #include "components/suggestions/suggestions_service.h"
7 #include <map>
8 #include <sstream>
9 #include <string>
11 #include "base/bind.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/metrics/field_trial.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "components/suggestions/blacklist_store.h"
18 #include "components/suggestions/image_manager.h"
19 #include "components/suggestions/proto/suggestions.pb.h"
20 #include "components/suggestions/suggestions_store.h"
21 #include "components/variations/entropy_provider.h"
22 #include "components/variations/variations_associated_data.h"
23 #include "net/http/http_response_headers.h"
24 #include "net/http/http_status_code.h"
25 #include "net/url_request/test_url_fetcher_factory.h"
26 #include "net/url_request/url_request_status.h"
27 #include "net/url_request/url_request_test_util.h"
28 #include "testing/gmock/include/gmock/gmock.h"
29 #include "testing/gtest/include/gtest/gtest.h"
31 using testing::DoAll;
32 using ::testing::Eq;
33 using ::testing::Return;
34 using testing::SetArgPointee;
35 using ::testing::NiceMock;
36 using ::testing::StrictMock;
37 using ::testing::_;
39 namespace {
41 const char kFakeSuggestionsURL[] = "https://mysuggestions.com/proto";
42 const char kFakeSuggestionsCommonParams[] = "foo=bar";
43 const char kFakeBlacklistPath[] = "/blacklist";
44 const char kFakeBlacklistUrlParam[] = "baz";
46 const char kTestTitle[] = "a title";
47 const char kTestUrl[] = "http://go.com";
48 const char kBlacklistUrl[] = "http://blacklist.com";
50 scoped_ptr<net::FakeURLFetcher> CreateURLFetcher(
51 const GURL& url, net::URLFetcherDelegate* delegate,
52 const std::string& response_data, net::HttpStatusCode response_code,
53 net::URLRequestStatus::Status status) {
54 scoped_ptr<net::FakeURLFetcher> fetcher(new net::FakeURLFetcher(
55 url, delegate, response_data, response_code, status));
57 if (response_code == net::HTTP_OK) {
58 scoped_refptr<net::HttpResponseHeaders> download_headers(
59 new net::HttpResponseHeaders(""));
60 download_headers->AddHeader("Content-Type: text/html");
61 fetcher->set_response_headers(download_headers);
63 return fetcher.Pass();
66 std::string GetExpectedBlacklistRequestUrl(const GURL& blacklist_url) {
67 std::stringstream request_url;
68 request_url << kFakeSuggestionsURL << kFakeBlacklistPath << "?"
69 << kFakeSuggestionsCommonParams << "&" << kFakeBlacklistUrlParam
70 << "=" << net::EscapeQueryParamValue(blacklist_url.spec(), true);
71 return request_url.str();
74 // GMock matcher for protobuf equality.
75 MATCHER_P(EqualsProto, message, "") {
76 // This implementation assumes protobuf serialization is deterministic, which
77 // is true in practice but technically not something that code is supposed
78 // to rely on. However, it vastly simplifies the implementation.
79 std::string expected_serialized, actual_serialized;
80 message.SerializeToString(&expected_serialized);
81 arg.SerializeToString(&actual_serialized);
82 return expected_serialized == actual_serialized;
85 } // namespace
87 namespace suggestions {
89 scoped_ptr<SuggestionsProfile> CreateSuggestionsProfile() {
90 scoped_ptr<SuggestionsProfile> profile(new SuggestionsProfile());
91 ChromeSuggestion* suggestion = profile->add_suggestions();
92 suggestion->set_title(kTestTitle);
93 suggestion->set_url(kTestUrl);
94 return profile.Pass();
97 class MockSuggestionsStore : public suggestions::SuggestionsStore {
98 public:
99 MOCK_METHOD1(LoadSuggestions, bool(SuggestionsProfile*));
100 MOCK_METHOD1(StoreSuggestions, bool(const SuggestionsProfile&));
101 MOCK_METHOD0(ClearSuggestions, void());
104 class MockImageManager : public suggestions::ImageManager {
105 public:
106 MockImageManager() {}
107 virtual ~MockImageManager() {}
108 MOCK_METHOD1(Initialize, void(const SuggestionsProfile&));
109 MOCK_METHOD2(GetImageForURL,
110 void(const GURL&,
111 base::Callback<void(const GURL&, const SkBitmap*)>));
114 class MockBlacklistStore : public suggestions::BlacklistStore {
115 public:
116 MOCK_METHOD1(BlacklistUrl, bool(const GURL&));
117 MOCK_METHOD1(GetFirstUrlFromBlacklist, bool(GURL*));
118 MOCK_METHOD1(RemoveUrl, bool(const GURL&));
119 MOCK_METHOD1(FilterSuggestions, void(SuggestionsProfile*));
122 class SuggestionsServiceTest : public testing::Test {
123 public:
124 void CheckSuggestionsData(const SuggestionsProfile& suggestions_profile) {
125 EXPECT_EQ(1, suggestions_profile.suggestions_size());
126 EXPECT_EQ(kTestTitle, suggestions_profile.suggestions(0).title());
127 EXPECT_EQ(kTestUrl, suggestions_profile.suggestions(0).url());
128 ++suggestions_data_check_count_;
131 void ExpectEmptySuggestionsProfile(const SuggestionsProfile& profile) {
132 EXPECT_EQ(0, profile.suggestions_size());
133 ++suggestions_empty_data_count_;
136 int suggestions_data_check_count_;
137 int suggestions_empty_data_count_;
139 protected:
140 SuggestionsServiceTest()
141 : suggestions_data_check_count_(0),
142 suggestions_empty_data_count_(0),
143 factory_(NULL, base::Bind(&CreateURLFetcher)),
144 mock_suggestions_store_(NULL),
145 mock_thumbnail_manager_(NULL) {}
147 virtual ~SuggestionsServiceTest() {}
149 virtual void SetUp() OVERRIDE {
150 request_context_ = new net::TestURLRequestContextGetter(
151 io_message_loop_.message_loop_proxy());
154 // Enables the "ChromeSuggestions.Group1" field trial.
155 void EnableFieldTrial(const std::string& url,
156 const std::string& common_params,
157 const std::string& blacklist_path,
158 const std::string& blacklist_url_param,
159 bool control_group) {
160 // Clear the existing |field_trial_list_| to avoid firing a DCHECK.
161 field_trial_list_.reset(NULL);
162 field_trial_list_.reset(
163 new base::FieldTrialList(new metrics::SHA1EntropyProvider("foo")));
165 variations::testing::ClearAllVariationParams();
166 std::map<std::string, std::string> params;
167 params[kSuggestionsFieldTrialStateParam] =
168 kSuggestionsFieldTrialStateEnabled;
169 if (control_group) {
170 params[kSuggestionsFieldTrialControlParam] =
171 kSuggestionsFieldTrialStateEnabled;
173 params[kSuggestionsFieldTrialURLParam] = url;
174 params[kSuggestionsFieldTrialCommonParamsParam] = common_params;
175 params[kSuggestionsFieldTrialBlacklistPathParam] = blacklist_path;
176 params[kSuggestionsFieldTrialBlacklistUrlParam] = blacklist_url_param;
177 variations::AssociateVariationParams(kSuggestionsFieldTrialName, "Group1",
178 params);
179 field_trial_ = base::FieldTrialList::CreateFieldTrial(
180 kSuggestionsFieldTrialName, "Group1");
181 field_trial_->group();
184 // Should not be called more than once per test since it stashes the
185 // SuggestionsStore in |mock_suggestions_store_|.
186 SuggestionsService* CreateSuggestionsServiceWithMocks() {
187 mock_suggestions_store_ = new StrictMock<MockSuggestionsStore>();
188 mock_thumbnail_manager_ = new NiceMock<MockImageManager>();
189 mock_blacklist_store_ = new MockBlacklistStore();
190 return new SuggestionsService(
191 request_context_, scoped_ptr<SuggestionsStore>(mock_suggestions_store_),
192 scoped_ptr<ImageManager>(mock_thumbnail_manager_),
193 scoped_ptr<BlacklistStore>(mock_blacklist_store_));
196 void FetchSuggestionsDataNoTimeoutHelper(bool interleaved_requests) {
197 // Field trial enabled with a specific suggestions URL.
198 EnableFieldTrial(kFakeSuggestionsURL, kFakeSuggestionsCommonParams,
199 kFakeBlacklistPath, kFakeBlacklistUrlParam, false);
200 scoped_ptr<SuggestionsService> suggestions_service(
201 CreateSuggestionsServiceWithMocks());
202 EXPECT_TRUE(suggestions_service != NULL);
203 scoped_ptr<SuggestionsProfile> suggestions_profile(
204 CreateSuggestionsProfile());
206 // Set up net::FakeURLFetcherFactory.
207 std::string expected_url =
208 (std::string(kFakeSuggestionsURL) + "?") + kFakeSuggestionsCommonParams;
209 factory_.SetFakeResponse(GURL(expected_url),
210 suggestions_profile->SerializeAsString(),
211 net::HTTP_OK, net::URLRequestStatus::SUCCESS);
213 // Set up expectations on the SuggestionsStore. The number depends on
214 // whether the second request is issued (it won't be issued if the second
215 // fetch occurs before the first request has completed).
216 int expected_count = interleaved_requests ? 1 : 2;
217 EXPECT_CALL(*mock_suggestions_store_,
218 StoreSuggestions(EqualsProto(*suggestions_profile)))
219 .Times(expected_count)
220 .WillRepeatedly(Return(true));
222 // Expect a call to the blacklist store. Return that there's nothing to
223 // blacklist.
224 EXPECT_CALL(*mock_blacklist_store_, FilterSuggestions(_))
225 .Times(expected_count);
226 EXPECT_CALL(*mock_blacklist_store_, GetFirstUrlFromBlacklist(_))
227 .Times(expected_count)
228 .WillRepeatedly(Return(false));
230 // Send the request. The data will be returned to the callback.
231 suggestions_service->FetchSuggestionsDataNoTimeout(base::Bind(
232 &SuggestionsServiceTest::CheckSuggestionsData, base::Unretained(this)));
234 if (!interleaved_requests)
235 io_message_loop_.RunUntilIdle(); // Let request complete.
237 // Send the request a second time.
238 suggestions_service->FetchSuggestionsDataNoTimeout(base::Bind(
239 &SuggestionsServiceTest::CheckSuggestionsData, base::Unretained(this)));
241 // (Testing only) wait until suggestion fetch is complete.
242 io_message_loop_.RunUntilIdle();
244 // Ensure that CheckSuggestionsData() ran twice.
245 EXPECT_EQ(2, suggestions_data_check_count_);
248 protected:
249 base::MessageLoopForIO io_message_loop_;
250 net::FakeURLFetcherFactory factory_;
251 // Only used if the SuggestionsService is built with mocks. Not owned.
252 MockSuggestionsStore* mock_suggestions_store_;
253 MockImageManager* mock_thumbnail_manager_;
254 MockBlacklistStore* mock_blacklist_store_;
255 scoped_refptr<net::TestURLRequestContextGetter> request_context_;
257 private:
258 scoped_ptr<base::FieldTrialList> field_trial_list_;
259 scoped_refptr<base::FieldTrial> field_trial_;
261 DISALLOW_COPY_AND_ASSIGN(SuggestionsServiceTest);
264 TEST_F(SuggestionsServiceTest, IsControlGroup) {
265 // Field trial enabled.
266 EnableFieldTrial("", "", "", "", false);
267 EXPECT_FALSE(SuggestionsService::IsControlGroup());
269 EnableFieldTrial("", "", "", "", true);
270 EXPECT_TRUE(SuggestionsService::IsControlGroup());
273 TEST_F(SuggestionsServiceTest, FetchSuggestionsDataNoTimeout) {
274 FetchSuggestionsDataNoTimeoutHelper(false);
277 TEST_F(SuggestionsServiceTest, FetchSuggestionsDataNoTimeoutInterleaved) {
278 FetchSuggestionsDataNoTimeoutHelper(true);
281 TEST_F(SuggestionsServiceTest, FetchSuggestionsDataRequestError) {
282 // Field trial enabled with a specific suggestions URL.
283 EnableFieldTrial(kFakeSuggestionsURL, kFakeSuggestionsCommonParams,
284 kFakeBlacklistPath, kFakeBlacklistUrlParam, false);
285 scoped_ptr<SuggestionsService> suggestions_service(
286 CreateSuggestionsServiceWithMocks());
287 EXPECT_TRUE(suggestions_service != NULL);
289 // Fake a request error.
290 std::string expected_url =
291 (std::string(kFakeSuggestionsURL) + "?") + kFakeSuggestionsCommonParams;
292 factory_.SetFakeResponse(GURL(expected_url), "irrelevant", net::HTTP_OK,
293 net::URLRequestStatus::FAILED);
295 // Set up expectations on the SuggestionsStore.
296 EXPECT_CALL(*mock_suggestions_store_, LoadSuggestions(_))
297 .WillOnce(Return(true));
299 // Expect a call to the blacklist store. Return that there's nothing to
300 // blacklist.
301 EXPECT_CALL(*mock_blacklist_store_, FilterSuggestions(_));
302 EXPECT_CALL(*mock_blacklist_store_, GetFirstUrlFromBlacklist(_))
303 .WillOnce(Return(false));
305 // Send the request. Empty data will be returned to the callback.
306 suggestions_service->FetchSuggestionsData(
307 base::Bind(&SuggestionsServiceTest::ExpectEmptySuggestionsProfile,
308 base::Unretained(this)));
310 // (Testing only) wait until suggestion fetch is complete.
311 io_message_loop_.RunUntilIdle();
313 // Ensure that ExpectEmptySuggestionsProfile ran once.
314 EXPECT_EQ(1, suggestions_empty_data_count_);
317 TEST_F(SuggestionsServiceTest, FetchSuggestionsDataResponseNotOK) {
318 // Field trial enabled with a specific suggestions URL.
319 EnableFieldTrial(kFakeSuggestionsURL, kFakeSuggestionsCommonParams,
320 kFakeBlacklistPath, kFakeBlacklistUrlParam, false);
321 scoped_ptr<SuggestionsService> suggestions_service(
322 CreateSuggestionsServiceWithMocks());
323 EXPECT_TRUE(suggestions_service != NULL);
325 // Response code != 200.
326 std::string expected_url =
327 (std::string(kFakeSuggestionsURL) + "?") + kFakeSuggestionsCommonParams;
328 factory_.SetFakeResponse(GURL(expected_url), "irrelevant",
329 net::HTTP_BAD_REQUEST,
330 net::URLRequestStatus::SUCCESS);
332 // Set up expectations on the SuggestionsStore.
333 EXPECT_CALL(*mock_suggestions_store_, ClearSuggestions());
335 // Expect a call to the blacklist store. Return that there's nothing to
336 // blacklist.
337 EXPECT_CALL(*mock_blacklist_store_, GetFirstUrlFromBlacklist(_))
338 .WillOnce(Return(false));
340 // Send the request. Empty data will be returned to the callback.
341 suggestions_service->FetchSuggestionsData(
342 base::Bind(&SuggestionsServiceTest::ExpectEmptySuggestionsProfile,
343 base::Unretained(this)));
345 // (Testing only) wait until suggestion fetch is complete.
346 io_message_loop_.RunUntilIdle();
348 // Ensure that ExpectEmptySuggestionsProfile ran once.
349 EXPECT_EQ(1, suggestions_empty_data_count_);
352 TEST_F(SuggestionsServiceTest, BlacklistURL) {
353 EnableFieldTrial(kFakeSuggestionsURL, kFakeSuggestionsCommonParams,
354 kFakeBlacklistPath, kFakeBlacklistUrlParam, false);
355 scoped_ptr<SuggestionsService> suggestions_service(
356 CreateSuggestionsServiceWithMocks());
357 EXPECT_TRUE(suggestions_service != NULL);
359 GURL blacklist_url(kBlacklistUrl);
360 std::string request_url = GetExpectedBlacklistRequestUrl(blacklist_url);
361 scoped_ptr<SuggestionsProfile> suggestions_profile(
362 CreateSuggestionsProfile());
363 factory_.SetFakeResponse(GURL(request_url),
364 suggestions_profile->SerializeAsString(),
365 net::HTTP_OK, net::URLRequestStatus::SUCCESS);
367 // Set up expectations on the SuggestionsStore.
368 EXPECT_CALL(*mock_suggestions_store_,
369 StoreSuggestions(EqualsProto(*suggestions_profile)))
370 .WillOnce(Return(true));
372 // Expected calls to the blacklist store.
373 EXPECT_CALL(*mock_blacklist_store_, BlacklistUrl(Eq(blacklist_url)))
374 .WillOnce(Return(true));
375 EXPECT_CALL(*mock_blacklist_store_, RemoveUrl(Eq(blacklist_url)))
376 .WillOnce(Return(true));
377 EXPECT_CALL(*mock_blacklist_store_, FilterSuggestions(_));
378 EXPECT_CALL(*mock_blacklist_store_, GetFirstUrlFromBlacklist(_))
379 .WillOnce(Return(false));
381 // Send the request. The data will be returned to the callback.
382 suggestions_service->BlacklistURL(
383 blacklist_url, base::Bind(&SuggestionsServiceTest::CheckSuggestionsData,
384 base::Unretained(this)));
386 // (Testing only) wait until blacklist request is complete.
387 io_message_loop_.RunUntilIdle();
389 // Ensure that CheckSuggestionsData() ran once.
390 EXPECT_EQ(1, suggestions_data_check_count_);
393 // Initial blacklist request fails, triggering a scheduled upload which
394 // succeeds.
395 TEST_F(SuggestionsServiceTest, BlacklistURLFails) {
396 EnableFieldTrial(kFakeSuggestionsURL, kFakeSuggestionsCommonParams,
397 kFakeBlacklistPath, kFakeBlacklistUrlParam, false);
398 scoped_ptr<SuggestionsService> suggestions_service(
399 CreateSuggestionsServiceWithMocks());
400 EXPECT_TRUE(suggestions_service != NULL);
401 suggestions_service->set_blacklist_delay(0); // Don't wait during a test!
402 scoped_ptr<SuggestionsProfile> suggestions_profile(
403 CreateSuggestionsProfile());
404 GURL blacklist_url(kBlacklistUrl);
406 // Set up behavior for the first call to blacklist.
407 std::string request_url = GetExpectedBlacklistRequestUrl(blacklist_url);
408 factory_.SetFakeResponse(GURL(request_url), "irrelevant", net::HTTP_OK,
409 net::URLRequestStatus::FAILED);
411 // Expectations specific to the first request.
412 EXPECT_CALL(*mock_blacklist_store_, BlacklistUrl(Eq(blacklist_url)))
413 .WillOnce(Return(true));
414 EXPECT_CALL(*mock_suggestions_store_, LoadSuggestions(_))
415 .WillOnce(DoAll(SetArgPointee<0>(*suggestions_profile), Return(true)));
417 // Expectations specific to the second request.
418 EXPECT_CALL(*mock_suggestions_store_,
419 StoreSuggestions(EqualsProto(*suggestions_profile)))
420 .WillOnce(Return(true));
421 EXPECT_CALL(*mock_blacklist_store_, RemoveUrl(Eq(blacklist_url)))
422 .WillOnce(Return(true));
424 // Expectations pertaining to both requests.
425 EXPECT_CALL(*mock_blacklist_store_, FilterSuggestions(_)).Times(2);
426 EXPECT_CALL(*mock_blacklist_store_, GetFirstUrlFromBlacklist(_))
427 .WillOnce(Return(true))
428 .WillOnce(DoAll(SetArgPointee<0>(blacklist_url), Return(true)))
429 .WillOnce(Return(false));
431 // Send the request. The data will be returned to the callback.
432 suggestions_service->BlacklistURL(
433 blacklist_url, base::Bind(&SuggestionsServiceTest::CheckSuggestionsData,
434 base::Unretained(this)));
436 // The first FakeURLFetcher was created; we can now set up behavior for the
437 // second call to blacklist.
438 factory_.SetFakeResponse(GURL(request_url),
439 suggestions_profile->SerializeAsString(),
440 net::HTTP_OK, net::URLRequestStatus::SUCCESS);
442 // (Testing only) wait until both requests are complete.
443 io_message_loop_.RunUntilIdle();
444 // ... Other task gets posted to the message loop.
445 base::MessageLoop::current()->RunUntilIdle();
446 // ... And completes.
447 io_message_loop_.RunUntilIdle();
449 // Ensure that CheckSuggestionsData() ran once.
450 EXPECT_EQ(1, suggestions_data_check_count_);
453 TEST_F(SuggestionsServiceTest, GetBlacklistedUrl) {
454 EnableFieldTrial(kFakeSuggestionsURL, kFakeSuggestionsCommonParams,
455 kFakeBlacklistPath, kFakeBlacklistUrlParam, false);
457 scoped_ptr<GURL> request_url;
458 scoped_ptr<net::FakeURLFetcher> fetcher;
459 GURL retrieved_url;
461 // Not a blacklist request.
462 request_url.reset(new GURL("http://not-blacklisting.com/a?b=c"));
463 fetcher = CreateURLFetcher(*request_url, NULL, "", net::HTTP_OK,
464 net::URLRequestStatus::SUCCESS);
465 EXPECT_FALSE(SuggestionsService::GetBlacklistedUrl(*fetcher, &retrieved_url));
467 // An actual blacklist request.
468 string blacklisted_url = "http://blacklisted.com/a?b=c&d=e";
469 string encoded_blacklisted_url =
470 "http%3A%2F%2Fblacklisted.com%2Fa%3Fb%3Dc%26d%3De";
471 string blacklist_request_prefix =
472 "https://mysuggestions.com/proto/blacklist?foo=bar&baz=";
473 request_url.reset(
474 new GURL(blacklist_request_prefix + encoded_blacklisted_url));
475 fetcher.reset();
476 fetcher = CreateURLFetcher(*request_url, NULL, "", net::HTTP_OK,
477 net::URLRequestStatus::SUCCESS);
478 EXPECT_TRUE(SuggestionsService::GetBlacklistedUrl(*fetcher, &retrieved_url));
479 EXPECT_EQ(blacklisted_url, retrieved_url.spec());
482 TEST_F(SuggestionsServiceTest, UpdateBlacklistDelay) {
483 scoped_ptr<SuggestionsService> suggestions_service(
484 CreateSuggestionsServiceWithMocks());
485 int initial_delay = suggestions_service->blacklist_delay();
487 // Delay unchanged on success.
488 suggestions_service->UpdateBlacklistDelay(true);
489 EXPECT_EQ(initial_delay, suggestions_service->blacklist_delay());
491 // Delay increases on failure.
492 suggestions_service->UpdateBlacklistDelay(false);
493 EXPECT_GT(suggestions_service->blacklist_delay(), initial_delay);
495 // Delay resets on success.
496 suggestions_service->UpdateBlacklistDelay(true);
497 EXPECT_EQ(initial_delay, suggestions_service->blacklist_delay());
500 } // namespace suggestions