1 // Copyright 2015 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 "base/command_line.h"
6 #include "base/test/simple_test_clock.h"
7 #include "base/values.h"
8 #include "chrome/browser/engagement/site_engagement_helper.h"
9 #include "chrome/browser/engagement/site_engagement_service.h"
10 #include "chrome/browser/engagement/site_engagement_service_factory.h"
11 #include "chrome/common/chrome_switches.h"
12 #include "chrome/test/base/browser_with_test_window_test.h"
13 #include "testing/gtest/include/gtest/gtest.h"
17 const int kLessNavigationsThanNeededToMaxDailyEngagement
= 2;
18 const int kMoreNavigationsThanNeededToMaxDailyEngagement
= 20;
19 const int kMoreNavigationsThanNeededToMaxTotalEngagement
= 200;
20 const int kLessDaysThanNeededToMaxTotalEngagement
= 4;
21 const int kMoreDaysThanNeededToMaxTotalEngagement
= 40;
22 const int kLessPeriodsThanNeededToDecayMaxScore
= 2;
23 const int kMorePeriodsThanNeededToDecayMaxScore
= 40;
25 base::Time
GetReferenceTime() {
26 base::Time::Exploded exploded_reference_time
;
27 exploded_reference_time
.year
= 2015;
28 exploded_reference_time
.month
= 1;
29 exploded_reference_time
.day_of_month
= 30;
30 exploded_reference_time
.day_of_week
= 5;
31 exploded_reference_time
.hour
= 11;
32 exploded_reference_time
.minute
= 0;
33 exploded_reference_time
.second
= 0;
34 exploded_reference_time
.millisecond
= 0;
36 return base::Time::FromLocalExploded(exploded_reference_time
);
41 class SiteEngagementScoreTest
: public testing::Test
{
43 SiteEngagementScoreTest() : score_(&test_clock_
) {}
46 void VerifyScore(const SiteEngagementScore
& score
,
47 double expected_raw_score
,
48 double expected_points_added_today
,
49 base::Time expected_last_engagement_time
) {
50 EXPECT_EQ(expected_raw_score
, score
.raw_score_
);
51 EXPECT_EQ(expected_points_added_today
, score
.points_added_today_
);
52 EXPECT_EQ(expected_last_engagement_time
, score
.last_engagement_time_
);
55 void UpdateScore(SiteEngagementScore
* score
,
57 double points_added_today
,
58 base::Time last_engagement_time
) {
59 score
->raw_score_
= raw_score
;
60 score
->points_added_today_
= points_added_today
;
61 score
->last_engagement_time_
= last_engagement_time
;
64 void TestScoreInitializesAndUpdates(
65 base::DictionaryValue
* score_dict
,
66 double expected_raw_score
,
67 double expected_points_added_today
,
68 base::Time expected_last_engagement_time
) {
69 SiteEngagementScore
initial_score(&test_clock_
, *score_dict
);
70 VerifyScore(initial_score
, expected_raw_score
, expected_points_added_today
,
71 expected_last_engagement_time
);
73 // Updating the score dict should return false, as the score shouldn't
74 // have changed at this point.
75 EXPECT_FALSE(initial_score
.UpdateScoreDict(score_dict
));
77 // Update the score to new values and verify it updates the score dict
79 base::Time different_day
=
80 GetReferenceTime() + base::TimeDelta::FromDays(1);
81 UpdateScore(&initial_score
, 5, 10, different_day
);
82 EXPECT_TRUE(initial_score
.UpdateScoreDict(score_dict
));
83 SiteEngagementScore
updated_score(&test_clock_
, *score_dict
);
84 VerifyScore(updated_score
, 5, 10, different_day
);
87 base::SimpleTestClock test_clock_
;
88 SiteEngagementScore score_
;
91 // Navigate many times on the same day. Ensure each time the score goes up by
92 // kNavigationPoints, but not more than kMaxPointsPerDay.
93 TEST_F(SiteEngagementScoreTest
, NavigateOnSameDay
) {
94 base::Time reference_time
= GetReferenceTime();
96 test_clock_
.SetNow(reference_time
);
97 for (int i
= 0; i
< kMoreNavigationsThanNeededToMaxDailyEngagement
; ++i
) {
98 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
99 EXPECT_EQ(std::min(SiteEngagementScore::kMaxPointsPerDay
,
100 (i
+ 1) * SiteEngagementScore::kNavigationPoints
),
104 EXPECT_EQ(SiteEngagementScore::kMaxPointsPerDay
, score_
.Score());
107 // Navigate on the first day to max that day's engagement, then navigate on a
109 TEST_F(SiteEngagementScoreTest
, NavigateOnTwoDays
) {
110 base::Time reference_time
= GetReferenceTime();
111 base::Time later_date
= reference_time
+ base::TimeDelta::FromDays(2);
113 test_clock_
.SetNow(reference_time
);
114 for (int i
= 0; i
< kMoreNavigationsThanNeededToMaxDailyEngagement
; ++i
)
115 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
117 EXPECT_EQ(SiteEngagementScore::kMaxPointsPerDay
, score_
.Score());
119 test_clock_
.SetNow(later_date
);
120 for (int i
= 0; i
< kMoreNavigationsThanNeededToMaxDailyEngagement
; ++i
) {
121 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
123 std::min(SiteEngagementScore::kMaxPointsPerDay
,
124 (i
+ 1) * SiteEngagementScore::kNavigationPoints
);
125 EXPECT_EQ(day_score
+ SiteEngagementScore::kMaxPointsPerDay
,
129 EXPECT_EQ(2 * SiteEngagementScore::kMaxPointsPerDay
, score_
.Score());
132 // Navigate a lot on many consecutive days and ensure the score doesn't exceed
133 // the maximum allowed.
134 TEST_F(SiteEngagementScoreTest
, NavigateALotOnManyDays
) {
135 base::Time current_day
= GetReferenceTime();
137 for (int i
= 0; i
< kMoreDaysThanNeededToMaxTotalEngagement
; ++i
) {
138 current_day
+= base::TimeDelta::FromDays(1);
139 test_clock_
.SetNow(current_day
);
140 for (int j
= 0; j
< kMoreNavigationsThanNeededToMaxDailyEngagement
; ++j
)
141 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
143 EXPECT_EQ(std::min(SiteEngagementScore::kMaxPoints
,
144 (i
+ 1) * SiteEngagementScore::kMaxPointsPerDay
),
148 EXPECT_EQ(SiteEngagementScore::kMaxPoints
, score_
.Score());
151 // Navigate a little on many consecutive days and ensure the score doesn't
152 // exceed the maximum allowed.
153 TEST_F(SiteEngagementScoreTest
, NavigateALittleOnManyDays
) {
154 base::Time current_day
= GetReferenceTime();
156 for (int i
= 0; i
< kMoreNavigationsThanNeededToMaxTotalEngagement
; ++i
) {
157 current_day
+= base::TimeDelta::FromDays(1);
158 test_clock_
.SetNow(current_day
);
160 for (int j
= 0; j
< kLessNavigationsThanNeededToMaxDailyEngagement
; ++j
)
161 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
164 std::min(SiteEngagementScore::kMaxPoints
,
165 (i
+ 1) * kLessNavigationsThanNeededToMaxDailyEngagement
*
166 SiteEngagementScore::kNavigationPoints
),
170 EXPECT_EQ(SiteEngagementScore::kMaxPoints
, score_
.Score());
173 // Navigate a bit, then check the score decays properly for a range of times.
174 TEST_F(SiteEngagementScoreTest
, ScoresDecayOverTime
) {
175 base::Time current_day
= GetReferenceTime();
177 // First max the score.
178 for (int i
= 0; i
< kMoreDaysThanNeededToMaxTotalEngagement
; ++i
) {
179 current_day
+= base::TimeDelta::FromDays(1);
180 test_clock_
.SetNow(current_day
);
182 for (int j
= 0; j
< kMoreNavigationsThanNeededToMaxDailyEngagement
; ++j
)
183 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
186 EXPECT_EQ(SiteEngagementScore::kMaxPoints
, score_
.Score());
188 // The score should not have decayed before the first decay period has
192 base::TimeDelta::FromDays(SiteEngagementScore::kDecayPeriodInDays
- 1));
193 EXPECT_EQ(SiteEngagementScore::kMaxPoints
, score_
.Score());
195 // The score should have decayed by one chunk after one decay period has
199 base::TimeDelta::FromDays(SiteEngagementScore::kDecayPeriodInDays
));
200 EXPECT_EQ(SiteEngagementScore::kMaxPoints
- SiteEngagementScore::kDecayPoints
,
203 // The score should have decayed by the right number of chunks after a few
204 // decay periods have elapsed.
207 base::TimeDelta::FromDays(kLessPeriodsThanNeededToDecayMaxScore
*
208 SiteEngagementScore::kDecayPeriodInDays
));
209 EXPECT_EQ(SiteEngagementScore::kMaxPoints
-
210 kLessPeriodsThanNeededToDecayMaxScore
*
211 SiteEngagementScore::kDecayPoints
,
214 // The score should not decay below zero.
217 base::TimeDelta::FromDays(kMorePeriodsThanNeededToDecayMaxScore
*
218 SiteEngagementScore::kDecayPeriodInDays
));
219 EXPECT_EQ(0, score_
.Score());
222 // Test that any expected decays are applied before adding points.
223 TEST_F(SiteEngagementScoreTest
, DecaysAppliedBeforeAdd
) {
224 base::Time current_day
= GetReferenceTime();
226 // Get the score up to something that can handle a bit of decay before
227 for (int i
= 0; i
< kLessDaysThanNeededToMaxTotalEngagement
; ++i
) {
228 current_day
+= base::TimeDelta::FromDays(1);
229 test_clock_
.SetNow(current_day
);
231 for (int j
= 0; j
< kMoreNavigationsThanNeededToMaxDailyEngagement
; ++j
)
232 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
235 double initial_score
= kLessDaysThanNeededToMaxTotalEngagement
*
236 SiteEngagementScore::kMaxPointsPerDay
;
237 EXPECT_EQ(initial_score
, score_
.Score());
239 // Go forward a few decay periods.
242 base::TimeDelta::FromDays(kLessPeriodsThanNeededToDecayMaxScore
*
243 SiteEngagementScore::kDecayPeriodInDays
));
245 double decayed_score
=
247 kLessPeriodsThanNeededToDecayMaxScore
* SiteEngagementScore::kDecayPoints
;
248 EXPECT_EQ(decayed_score
, score_
.Score());
250 // Now add some points.
251 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
252 EXPECT_EQ(decayed_score
+ SiteEngagementScore::kNavigationPoints
,
256 // Test that going back in time is handled properly.
257 TEST_F(SiteEngagementScoreTest
, GoBackInTime
) {
258 base::Time current_day
= GetReferenceTime();
260 test_clock_
.SetNow(current_day
);
261 for (int i
= 0; i
< kMoreNavigationsThanNeededToMaxDailyEngagement
; ++i
)
262 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
264 EXPECT_EQ(SiteEngagementScore::kMaxPointsPerDay
, score_
.Score());
266 // Adding to the score on an earlier date should be treated like another day,
267 // and should not cause any decay.
268 test_clock_
.SetNow(current_day
- base::TimeDelta::FromDays(
269 kMorePeriodsThanNeededToDecayMaxScore
*
270 SiteEngagementScore::kDecayPoints
));
271 for (int i
= 0; i
< kMoreNavigationsThanNeededToMaxDailyEngagement
; ++i
) {
272 score_
.AddPoints(SiteEngagementScore::kNavigationPoints
);
274 std::min(SiteEngagementScore::kMaxPointsPerDay
,
275 (i
+ 1) * SiteEngagementScore::kNavigationPoints
);
276 EXPECT_EQ(day_score
+ SiteEngagementScore::kMaxPointsPerDay
,
280 EXPECT_EQ(2 * SiteEngagementScore::kMaxPointsPerDay
, score_
.Score());
283 // Test that scores are read / written correctly from / to empty score
285 TEST_F(SiteEngagementScoreTest
, EmptyDictionary
) {
286 base::DictionaryValue dict
;
287 TestScoreInitializesAndUpdates(&dict
, 0, 0, base::Time());
290 // Test that scores are read / written correctly from / to partially empty
291 // score dictionaries.
292 TEST_F(SiteEngagementScoreTest
, PartiallyEmptyDictionary
) {
293 base::DictionaryValue dict
;
294 dict
.SetDouble(SiteEngagementScore::kPointsAddedTodayKey
, 2);
296 TestScoreInitializesAndUpdates(&dict
, 0, 2, base::Time());
299 // Test that scores are read / written correctly from / to populated score
301 TEST_F(SiteEngagementScoreTest
, PopulatedDictionary
) {
302 base::DictionaryValue dict
;
303 dict
.SetDouble(SiteEngagementScore::kRawScoreKey
, 1);
304 dict
.SetDouble(SiteEngagementScore::kPointsAddedTodayKey
, 2);
305 dict
.SetDouble(SiteEngagementScore::kLastEngagementTimeKey
,
306 GetReferenceTime().ToInternalValue());
308 TestScoreInitializesAndUpdates(&dict
, 1, 2, GetReferenceTime());
312 class SiteEngagementServiceTest
: public BrowserWithTestWindowTest
{
314 SiteEngagementServiceTest() {}
316 void SetUp() override
{
317 BrowserWithTestWindowTest::SetUp();
318 base::CommandLine::ForCurrentProcess()->AppendSwitch(
319 switches::kEnableSiteEngagementService
);
324 // Tests that the Site Engagement service is hooked up properly to navigations
325 // by performing two navigations and checking the engagement score increases
327 TEST_F(SiteEngagementServiceTest
, ScoreIncrementsOnPageRequest
) {
328 SiteEngagementService
* service
=
329 SiteEngagementServiceFactory::GetForProfile(profile());
332 GURL
url("http://www.google.com/");
334 AddTab(browser(), GURL("about:blank"));
335 EXPECT_EQ(0, service
->GetScore(url
));
336 int prev_score
= service
->GetScore(url
);
338 NavigateAndCommitActiveTab(url
);
339 EXPECT_LT(prev_score
, service
->GetScore(url
));
340 prev_score
= service
->GetScore(url
);
342 NavigateAndCommitActiveTab(url
);
343 EXPECT_LT(prev_score
, service
->GetScore(url
));
346 // Expect that site engagement scores for several sites are correctly aggregated
347 // by GetTotalEngagementPoints().
348 TEST_F(SiteEngagementServiceTest
, GetTotalEngagementPoints
) {
349 SiteEngagementService
* service
=
350 SiteEngagementServiceFactory::GetForProfile(profile());
353 // The https and http versions of www.google.com should be separate.
354 GURL
url1("https://www.google.com/");
355 GURL
url2("http://www.google.com/");
356 GURL
url3("http://drive.google.com/");
358 EXPECT_EQ(0, service
->GetScore(url1
));
359 EXPECT_EQ(0, service
->GetScore(url2
));
360 EXPECT_EQ(0, service
->GetScore(url3
));
362 service
->HandleNavigation(url1
);
363 EXPECT_EQ(1, service
->GetScore(url1
));
364 EXPECT_EQ(1, service
->GetTotalEngagementPoints());
366 service
->HandleNavigation(url2
);
367 service
->HandleNavigation(url2
);
368 EXPECT_EQ(2, service
->GetScore(url2
));
369 EXPECT_EQ(3, service
->GetTotalEngagementPoints());
371 service
->HandleNavigation(url3
);
372 EXPECT_EQ(1, service
->GetScore(url3
));
373 EXPECT_EQ(4, service
->GetTotalEngagementPoints());
375 service
->HandleNavigation(url1
);
376 service
->HandleNavigation(url1
);
377 EXPECT_EQ(3, service
->GetScore(url1
));
378 EXPECT_EQ(6, service
->GetTotalEngagementPoints());