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