1 // Copyright (c) 2012 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 <utility> 6 #include <vector> 7 8 #include "base/json/json_reader.h" 9 #include "base/message_loop/message_loop.h" 10 #include "base/prefs/pref_service.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/string_util.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/time/time.h" 15 #include "base/values.h" 16 #include "chrome/browser/browser_process.h" 17 #include "chrome/browser/chrome_notification_types.h" 18 #include "chrome/browser/prefs/browser_prefs.h" 19 #include "chrome/browser/web_resource/notification_promo.h" 20 #include "chrome/browser/web_resource/promo_resource_service.h" 21 #include "chrome/common/pref_names.h" 22 #include "chrome/common/url_constants.h" 23 #include "chrome/test/base/scoped_testing_local_state.h" 24 #include "chrome/test/base/testing_browser_process.h" 25 #include "content/public/browser/notification_registrar.h" 26 #include "content/public/browser/notification_service.h" 27 #include "net/url_request/test_url_fetcher_factory.h" 28 #include "testing/gtest/include/gtest/gtest.h" 29 #include "third_party/icu/source/i18n/unicode/smpdtfmt.h" 30 31 namespace { 32 33 const char kDateFormat[] = "dd MMM yyyy HH:mm:ss zzzz"; 34 35 bool YearFromNow(double* date_epoch, std::string* date_string) { 36 *date_epoch = (base::Time::Now() + base::TimeDelta::FromDays(365)).ToTimeT(); 37 38 UErrorCode status = U_ZERO_ERROR; 39 icu::SimpleDateFormat simple_formatter(icu::UnicodeString(kDateFormat), 40 icu::Locale("en_US"), 41 status); 42 if (!U_SUCCESS(status)) 43 return false; 44 45 icu::UnicodeString date_unicode_string; 46 simple_formatter.format(static_cast<UDate>(*date_epoch * 1000), 47 date_unicode_string, 48 status); 49 if (!U_SUCCESS(status)) 50 return false; 51 52 return base::UTF16ToUTF8(date_unicode_string.getBuffer(), 53 static_cast<size_t>(date_unicode_string.length()), 54 date_string); 55 } 56 57 } // namespace 58 59 class PromoResourceServiceTest : public testing::Test { 60 public: 61 // |promo_resource_service_| must be created after |local_state_|. 62 PromoResourceServiceTest() 63 : local_state_(TestingBrowserProcess::GetGlobal()), 64 promo_resource_service_(new PromoResourceService) {} 65 66 protected: 67 ScopedTestingLocalState local_state_; 68 scoped_refptr<PromoResourceService> promo_resource_service_; 69 base::MessageLoop loop_; 70 }; 71 72 class NotificationPromoTest { 73 public: 74 NotificationPromoTest() 75 : received_notification_(false), 76 start_(0.0), 77 end_(0.0), 78 num_groups_(0), 79 initial_segment_(0), 80 increment_(1), 81 time_slice_(0), 82 max_group_(0), 83 max_views_(0), 84 closed_(false) {} 85 86 void Init(const std::string& json, 87 const std::string& promo_text, 88 double start, 89 int num_groups, int initial_segment, int increment, 90 int time_slice, int max_group, int max_views) { 91 double year_from_now_epoch; 92 std::string year_from_now_string; 93 ASSERT_TRUE(YearFromNow(&year_from_now_epoch, &year_from_now_string)); 94 95 std::vector<std::string> replacements; 96 replacements.push_back(year_from_now_string); 97 98 std::string json_with_end_date( 99 ReplaceStringPlaceholders(json, replacements, NULL)); 100 base::Value* value(base::JSONReader::Read(json_with_end_date)); 101 ASSERT_TRUE(value); 102 103 base::DictionaryValue* dict = NULL; 104 value->GetAsDictionary(&dict); 105 ASSERT_TRUE(dict); 106 test_json_.reset(dict); 107 108 promo_type_ = NotificationPromo::NTP_NOTIFICATION_PROMO; 109 promo_text_ = promo_text; 110 111 start_ = start; 112 end_ = year_from_now_epoch; 113 114 num_groups_ = num_groups; 115 initial_segment_ = initial_segment; 116 increment_ = increment; 117 time_slice_ = time_slice; 118 max_group_ = max_group; 119 120 max_views_ = max_views; 121 122 closed_ = false; 123 received_notification_ = false; 124 } 125 126 void InitPromoFromJson(bool should_receive_notification) { 127 notification_promo_.InitFromJson(*test_json_, promo_type_); 128 EXPECT_EQ(should_receive_notification, 129 notification_promo_.new_notification()); 130 131 // Test the fields. 132 TestNotification(); 133 } 134 135 void TestNotification() { 136 // Check values. 137 EXPECT_EQ(notification_promo_.promo_text_, promo_text_); 138 139 EXPECT_EQ(notification_promo_.start_, start_); 140 EXPECT_EQ(notification_promo_.end_, end_); 141 142 EXPECT_EQ(notification_promo_.num_groups_, num_groups_); 143 EXPECT_EQ(notification_promo_.initial_segment_, initial_segment_); 144 EXPECT_EQ(notification_promo_.increment_, increment_); 145 EXPECT_EQ(notification_promo_.time_slice_, time_slice_); 146 EXPECT_EQ(notification_promo_.max_group_, max_group_); 147 148 EXPECT_EQ(notification_promo_.max_views_, max_views_); 149 EXPECT_EQ(notification_promo_.closed_, closed_); 150 151 // Check group within bounds. 152 EXPECT_GE(notification_promo_.group_, 0); 153 EXPECT_LT(notification_promo_.group_, num_groups_); 154 155 // Views should be 0 for now. 156 EXPECT_EQ(notification_promo_.views_, 0); 157 } 158 159 // Create a new NotificationPromo from prefs and compare to current 160 // notification. 161 void TestInitFromPrefs() { 162 NotificationPromo prefs_notification_promo; 163 prefs_notification_promo.InitFromPrefs(promo_type_); 164 165 EXPECT_EQ(notification_promo_.prefs_, 166 prefs_notification_promo.prefs_); 167 EXPECT_EQ(notification_promo_.promo_text_, 168 prefs_notification_promo.promo_text_); 169 EXPECT_EQ(notification_promo_.start_, 170 prefs_notification_promo.start_); 171 EXPECT_EQ(notification_promo_.end_, 172 prefs_notification_promo.end_); 173 EXPECT_EQ(notification_promo_.num_groups_, 174 prefs_notification_promo.num_groups_); 175 EXPECT_EQ(notification_promo_.initial_segment_, 176 prefs_notification_promo.initial_segment_); 177 EXPECT_EQ(notification_promo_.increment_, 178 prefs_notification_promo.increment_); 179 EXPECT_EQ(notification_promo_.time_slice_, 180 prefs_notification_promo.time_slice_); 181 EXPECT_EQ(notification_promo_.max_group_, 182 prefs_notification_promo.max_group_); 183 EXPECT_EQ(notification_promo_.max_views_, 184 prefs_notification_promo.max_views_); 185 EXPECT_EQ(notification_promo_.group_, 186 prefs_notification_promo.group_); 187 EXPECT_EQ(notification_promo_.views_, 188 prefs_notification_promo.views_); 189 EXPECT_EQ(notification_promo_.closed_, 190 prefs_notification_promo.closed_); 191 } 192 193 void TestGroup() { 194 // Test out of range groups. 195 const int incr = num_groups_ / 20; 196 for (int i = max_group_; i < num_groups_; i += incr) { 197 notification_promo_.group_ = i; 198 EXPECT_FALSE(notification_promo_.CanShow()); 199 } 200 201 // Test in-range groups. 202 for (int i = 0; i < max_group_; i += incr) { 203 notification_promo_.group_ = i; 204 EXPECT_TRUE(notification_promo_.CanShow()); 205 } 206 207 // When max_group_ is 0, all groups pass. 208 notification_promo_.max_group_ = 0; 209 for (int i = 0; i < num_groups_; i += incr) { 210 notification_promo_.group_ = i; 211 EXPECT_TRUE(notification_promo_.CanShow()); 212 } 213 notification_promo_.WritePrefs(); 214 } 215 216 void TestViews() { 217 notification_promo_.views_ = notification_promo_.max_views_ - 2; 218 notification_promo_.WritePrefs(); 219 220 NotificationPromo::HandleViewed(promo_type_); 221 NotificationPromo new_promo; 222 new_promo.InitFromPrefs(promo_type_); 223 EXPECT_EQ(new_promo.max_views_ - 1, new_promo.views_); 224 EXPECT_TRUE(new_promo.CanShow()); 225 NotificationPromo::HandleViewed(promo_type_); 226 new_promo.InitFromPrefs(promo_type_); 227 EXPECT_EQ(new_promo.max_views_, new_promo.views_); 228 EXPECT_FALSE(new_promo.CanShow()); 229 230 // Test out of range views. 231 for (int i = max_views_; i < max_views_ * 2; ++i) { 232 new_promo.views_ = i; 233 EXPECT_FALSE(new_promo.CanShow()); 234 } 235 236 // Test in range views. 237 for (int i = 0; i < max_views_; ++i) { 238 new_promo.views_ = i; 239 EXPECT_TRUE(new_promo.CanShow()); 240 } 241 new_promo.WritePrefs(); 242 } 243 244 void TestClosed() { 245 NotificationPromo new_promo; 246 new_promo.InitFromPrefs(promo_type_); 247 EXPECT_FALSE(new_promo.closed_); 248 EXPECT_TRUE(new_promo.CanShow()); 249 250 NotificationPromo::HandleClosed(promo_type_); 251 new_promo.InitFromPrefs(promo_type_); 252 EXPECT_TRUE(new_promo.closed_); 253 EXPECT_FALSE(new_promo.CanShow()); 254 255 new_promo.closed_ = false; 256 EXPECT_TRUE(new_promo.CanShow()); 257 new_promo.WritePrefs(); 258 } 259 260 void TestPromoText() { 261 notification_promo_.promo_text_.clear(); 262 EXPECT_FALSE(notification_promo_.CanShow()); 263 264 notification_promo_.promo_text_ = promo_text_; 265 EXPECT_TRUE(notification_promo_.CanShow()); 266 } 267 268 void TestTime() { 269 const double now = base::Time::Now().ToDoubleT(); 270 const double qhour = 15 * 60; 271 272 notification_promo_.group_ = 0; // For simplicity. 273 274 notification_promo_.start_ = now - qhour; 275 notification_promo_.end_ = now + qhour; 276 EXPECT_TRUE(notification_promo_.CanShow()); 277 278 // Start time has not arrived. 279 notification_promo_.start_ = now + qhour; 280 notification_promo_.end_ = now + qhour; 281 EXPECT_FALSE(notification_promo_.CanShow()); 282 283 // End time has past. 284 notification_promo_.start_ = now - qhour; 285 notification_promo_.end_ = now - qhour; 286 EXPECT_FALSE(notification_promo_.CanShow()); 287 288 notification_promo_.start_ = start_; 289 notification_promo_.end_ = end_; 290 EXPECT_TRUE(notification_promo_.CanShow()); 291 } 292 293 void TestIncrement() { 294 const double now = base::Time::Now().ToDoubleT(); 295 const double slice = 60; 296 297 notification_promo_.num_groups_ = 18; 298 notification_promo_.initial_segment_ = 5; 299 notification_promo_.increment_ = 3; 300 notification_promo_.time_slice_ = slice; 301 302 notification_promo_.start_ = now - 1; 303 notification_promo_.end_ = now + slice; 304 305 // Test initial segment. 306 notification_promo_.group_ = 4; 307 EXPECT_TRUE(notification_promo_.CanShow()); 308 notification_promo_.group_ = 5; 309 EXPECT_FALSE(notification_promo_.CanShow()); 310 311 // Test first increment. 312 notification_promo_.start_ -= slice; 313 notification_promo_.group_ = 7; 314 EXPECT_TRUE(notification_promo_.CanShow()); 315 notification_promo_.group_ = 8; 316 EXPECT_FALSE(notification_promo_.CanShow()); 317 318 // Test second increment. 319 notification_promo_.start_ -= slice; 320 notification_promo_.group_ = 10; 321 EXPECT_TRUE(notification_promo_.CanShow()); 322 notification_promo_.group_ = 11; 323 EXPECT_FALSE(notification_promo_.CanShow()); 324 325 // Test penultimate increment. 326 notification_promo_.start_ -= 2 * slice; 327 notification_promo_.group_ = 16; 328 EXPECT_TRUE(notification_promo_.CanShow()); 329 notification_promo_.group_ = 17; 330 EXPECT_FALSE(notification_promo_.CanShow()); 331 332 // Test last increment. 333 notification_promo_.start_ -= slice; 334 EXPECT_TRUE(notification_promo_.CanShow()); 335 } 336 337 const NotificationPromo& promo() const { return notification_promo_; } 338 339 private: 340 NotificationPromo notification_promo_; 341 bool received_notification_; 342 scoped_ptr<base::DictionaryValue> test_json_; 343 344 NotificationPromo::PromoType promo_type_; 345 std::string promo_text_; 346 347 double start_; 348 double end_; 349 350 int num_groups_; 351 int initial_segment_; 352 int increment_; 353 int time_slice_; 354 int max_group_; 355 356 int max_views_; 357 358 bool closed_; 359 }; 360 361 // Test that everything gets parsed correctly, notifications are sent, 362 // and CanShow() is handled correctly under variety of conditions. 363 // Additionally, test that the first string in |strings| is used if 364 // no payload.promo_short_message is specified in the JSON response. 365 TEST_F(PromoResourceServiceTest, NotificationPromoTest) { 366 // Check that prefs are set correctly. 367 NotificationPromoTest promo_test; 368 369 // Set up start date and promo line in a Dictionary as if parsed from the 370 // service. date[0].end is replaced with a date 1 year in the future. 371 promo_test.Init("{" 372 " \"ntp_notification_promo\": [" 373 " {" 374 " \"date\":" 375 " [" 376 " {" 377 " \"start\":\"3 Aug 1999 9:26:06 GMT\"," 378 " \"end\":\"$1\"" 379 " }" 380 " ]," 381 " \"strings\":" 382 " {" 383 " \"NTP4_HOW_DO_YOU_FEEL_ABOUT_CHROME\":" 384 " \"What do you think of Chrome?\"" 385 " }," 386 " \"grouping\":" 387 " {" 388 " \"buckets\":1000," 389 " \"segment\":200," 390 " \"increment\":100," 391 " \"increment_frequency\":3600," 392 " \"increment_max\":400" 393 " }," 394 " \"payload\":" 395 " {" 396 " \"days_active\":7," 397 " \"install_age_days\":21" 398 " }," 399 " \"max_views\":30" 400 " }" 401 " ]" 402 "}", 403 "What do you think of Chrome?", 404 // The starting date is in 1999 to make tests pass 405 // on Android devices with incorrect or unset date/time. 406 933672366, // unix epoch for 3 Aug 1999 9:26:06 GMT. 407 1000, 200, 100, 3600, 400, 30); 408 409 promo_test.InitPromoFromJson(true); 410 411 // Second time should not trigger a notification. 412 promo_test.InitPromoFromJson(false); 413 414 promo_test.TestInitFromPrefs(); 415 416 // Test various conditions of CanShow. 417 // TestGroup Has the side effect of setting us to a passing group. 418 promo_test.TestGroup(); 419 promo_test.TestViews(); 420 promo_test.TestClosed(); 421 promo_test.TestPromoText(); 422 promo_test.TestTime(); 423 promo_test.TestIncrement(); 424 } 425 426 // Test that payload.promo_message_short is used if present. 427 TEST_F(PromoResourceServiceTest, NotificationPromoCompatNoStringsTest) { 428 // Check that prefs are set correctly. 429 NotificationPromoTest promo_test; 430 431 // Set up start date and promo line in a Dictionary as if parsed from the 432 // service. date[0].end is replaced with a date 1 year in the future. 433 promo_test.Init("{" 434 " \"ntp_notification_promo\": [" 435 " {" 436 " \"date\":" 437 " [" 438 " {" 439 " \"start\":\"3 Aug 1999 9:26:06 GMT\"," 440 " \"end\":\"$1\"" 441 " }" 442 " ]," 443 " \"grouping\":" 444 " {" 445 " \"buckets\":1000," 446 " \"segment\":200," 447 " \"increment\":100," 448 " \"increment_frequency\":3600," 449 " \"increment_max\":400" 450 " }," 451 " \"payload\":" 452 " {" 453 " \"promo_message_short\":" 454 " \"What do you think of Chrome?\"," 455 " \"days_active\":7," 456 " \"install_age_days\":21" 457 " }," 458 " \"max_views\":30" 459 " }" 460 " ]" 461 "}", 462 "What do you think of Chrome?", 463 // The starting date is in 1999 to make tests pass 464 // on Android devices with incorrect or unset date/time. 465 933672366, // unix epoch for 3 Aug 1999 9:26:06 GMT. 466 1000, 200, 100, 3600, 400, 30); 467 468 promo_test.InitPromoFromJson(true); 469 // Second time should not trigger a notification. 470 promo_test.InitPromoFromJson(false); 471 promo_test.TestInitFromPrefs(); 472 } 473 474 // Test that strings.|payload.promo_message_short| is used if present. 475 TEST_F(PromoResourceServiceTest, NotificationPromoCompatPayloadStringsTest) { 476 // Check that prefs are set correctly. 477 NotificationPromoTest promo_test; 478 479 // Set up start date and promo line in a Dictionary as if parsed from the 480 // service. date[0].end is replaced with a date 1 year in the future. 481 promo_test.Init("{" 482 " \"ntp_notification_promo\": [" 483 " {" 484 " \"date\":" 485 " [" 486 " {" 487 " \"start\":\"3 Aug 1999 9:26:06 GMT\"," 488 " \"end\":\"$1\"" 489 " }" 490 " ]," 491 " \"grouping\":" 492 " {" 493 " \"buckets\":1000," 494 " \"segment\":200," 495 " \"increment\":100," 496 " \"increment_frequency\":3600," 497 " \"increment_max\":400" 498 " }," 499 " \"strings\":" 500 " {" 501 " \"bogus\":\"string\"," 502 " \"GOOD_STRING\":" 503 " \"What do you think of Chrome?\"" 504 " }," 505 " \"payload\":" 506 " {" 507 " \"promo_message_short\":" 508 " \"GOOD_STRING\"," 509 " \"days_active\":7," 510 " \"install_age_days\":21" 511 " }," 512 " \"max_views\":30" 513 " }" 514 " ]" 515 "}", 516 "What do you think of Chrome?", 517 // The starting date is in 1999 to make tests pass 518 // on Android devices with incorrect or unset date/time. 519 933672366, // unix epoch for 3 Aug 1999 9:26:06 GMT. 520 1000, 200, 100, 3600, 400, 30); 521 522 promo_test.InitPromoFromJson(true); 523 // Second time should not trigger a notification. 524 promo_test.InitPromoFromJson(false); 525 promo_test.TestInitFromPrefs(); 526 } 527 528 TEST_F(PromoResourceServiceTest, PromoServerURLTest) { 529 GURL promo_server_url = NotificationPromo::PromoServerURL(); 530 EXPECT_FALSE(promo_server_url.is_empty()); 531 EXPECT_TRUE(promo_server_url.is_valid()); 532 EXPECT_TRUE(promo_server_url.SchemeIs(url::kHttpsScheme)); 533 // TODO(achuith): Test this better. 534 } 535 536 #if defined(ENABLE_APP_LIST) 537 TEST_F(PromoResourceServiceTest, AppLauncherPromoTest) { 538 // Check that prefs are set correctly. 539 NotificationPromoTest promo_test; 540 541 // Set up start date and promo line in a Dictionary as if parsed from the 542 // service. date[0].end is replaced with a date 1 year in the future. 543 promo_test.Init("{" 544 " \"ntp_notification_promo\": [" 545 " {" 546 " \"date\":" 547 " [" 548 " {" 549 " \"start\":\"3 Aug 1999 9:26:06 GMT\"," 550 " \"end\":\"$1\"" 551 " }" 552 " ]," 553 " \"grouping\":" 554 " {" 555 " \"buckets\":1000," 556 " \"segment\":200," 557 " \"increment\":100," 558 " \"increment_frequency\":3600," 559 " \"increment_max\":400" 560 " }," 561 " \"payload\":" 562 " {" 563 " \"promo_message_short\":" 564 " \"What do you think of Chrome?\"," 565 " \"days_active\":7," 566 " \"install_age_days\":21," 567 " \"is_app_launcher_promo\":true" 568 " }," 569 " \"max_views\":30" 570 " }" 571 " ]" 572 "}", 573 "What do you think of Chrome?", 574 // The starting date is in 1999 to make tests pass 575 // on Android devices with incorrect or unset date/time. 576 933672366, // unix epoch for 3 Aug 1999 9:26:06 GMT. 577 1000, 200, 100, 3600, 400, 30); 578 promo_test.InitPromoFromJson(true); 579 local_state_.Get()->SetBoolean(prefs::kAppLauncherIsEnabled, true); 580 EXPECT_FALSE(promo_test.promo().CanShow()); 581 } 582 #endif 583