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

Side by Side Diff: components/ntp_snippets/ntp_snippets_service_unittest.cc

Issue 2225753003: Revert of Add per-section clearing and dismissed suggestions to snippets-internals (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 4 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
1 // Copyright 2015 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/ntp_snippets/ntp_snippets_service.h" 5 #include "components/ntp_snippets/ntp_snippets_service.h"
6 6
7 #include <memory> 7 #include <memory>
8 #include <vector> 8 #include <vector>
9 9
10 #include "base/command_line.h" 10 #include "base/command_line.h"
(...skipping 323 matching lines...) Expand 10 before | Expand all | Expand 10 after
334 &scheduler_, std::move(snippets_fetcher), /*image_fetcher=*/nullptr, 334 &scheduler_, std::move(snippets_fetcher), /*image_fetcher=*/nullptr,
335 /*image_fetcher=*/nullptr, base::MakeUnique<NTPSnippetsDatabase>( 335 /*image_fetcher=*/nullptr, base::MakeUnique<NTPSnippetsDatabase>(
336 database_dir_.path(), task_runner), 336 database_dir_.path(), task_runner),
337 base::MakeUnique<NTPSnippetsStatusService>(fake_signin_manager(), 337 base::MakeUnique<NTPSnippetsStatusService>(fake_signin_manager(),
338 pref_service()))); 338 pref_service())));
339 339
340 WaitForDBLoad(&observer_, service_.get()); 340 WaitForDBLoad(&observer_, service_.get());
341 } 341 }
342 342
343 std::string MakeUniqueID(const std::string& within_category_id) { 343 std::string MakeUniqueID(const std::string& within_category_id) {
344 return service()->MakeUniqueID(articles_category(), within_category_id); 344 return service()->MakeUniqueID(
345 } 345 category_factory_.FromKnownCategory(KnownCategories::ARTICLES),
346 346 within_category_id);
347 Category articles_category() {
348 return category_factory_.FromKnownCategory(KnownCategories::ARTICLES);
349 } 347 }
350 348
351 protected: 349 protected:
352 const GURL& test_url() { return test_url_; } 350 const GURL& test_url() { return test_url_; }
353 NTPSnippetsService* service() { return service_.get(); } 351 NTPSnippetsService* service() { return service_.get(); }
354 MockProviderObserver& observer() { return observer_; } 352 MockProviderObserver& observer() { return observer_; }
355 MockScheduler& mock_scheduler() { return scheduler_; } 353 MockScheduler& mock_scheduler() { return scheduler_; }
356 354
357 // Provide the json to be returned by the fake fetcher. 355 // Provide the json to be returned by the fake fetcher.
358 void SetUpFetchResponse(const std::string& json) { 356 void SetUpFetchResponse(const std::string& json) {
(...skipping 30 matching lines...) Expand all
389 387
390 // When we have no snippets are all, loading the service initiates a fetch. 388 // When we have no snippets are all, loading the service initiates a fetch.
391 base::RunLoop().RunUntilIdle(); 389 base::RunLoop().RunUntilIdle();
392 ASSERT_EQ("OK", service()->snippets_fetcher()->last_status()); 390 ASSERT_EQ("OK", service()->snippets_fetcher()->last_status());
393 } 391 }
394 392
395 TEST_F(NTPSnippetsServiceTest, Full) { 393 TEST_F(NTPSnippetsServiceTest, Full) {
396 std::string json_str(GetTestJson({GetSnippet()})); 394 std::string json_str(GetTestJson({GetSnippet()}));
397 395
398 LoadFromJSONString(json_str); 396 LoadFromJSONString(json_str);
399 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 397 ASSERT_THAT(service()->snippets(), SizeIs(1));
400 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 398 const NTPSnippet& snippet = *service()->snippets().front();
401 399
402 EXPECT_EQ(snippet.id(), kSnippetUrl); 400 EXPECT_EQ(snippet.id(), kSnippetUrl);
403 EXPECT_EQ(snippet.title(), kSnippetTitle); 401 EXPECT_EQ(snippet.title(), kSnippetTitle);
404 EXPECT_EQ(snippet.snippet(), kSnippetText); 402 EXPECT_EQ(snippet.snippet(), kSnippetText);
405 EXPECT_EQ(snippet.salient_image_url(), GURL(kSnippetSalientImage)); 403 EXPECT_EQ(snippet.salient_image_url(), GURL(kSnippetSalientImage));
406 EXPECT_EQ(GetDefaultCreationTime(), snippet.publish_date()); 404 EXPECT_EQ(GetDefaultCreationTime(), snippet.publish_date());
407 EXPECT_EQ(snippet.best_source().publisher_name, kSnippetPublisherName); 405 EXPECT_EQ(snippet.best_source().publisher_name, kSnippetPublisherName);
408 EXPECT_EQ(snippet.best_source().amp_url, GURL(kSnippetAmpUrl)); 406 EXPECT_EQ(snippet.best_source().amp_url, GURL(kSnippetAmpUrl));
409 } 407 }
410 408
411 TEST_F(NTPSnippetsServiceTest, Clear) { 409 TEST_F(NTPSnippetsServiceTest, Clear) {
412 std::string json_str(GetTestJson({GetSnippet()})); 410 std::string json_str(GetTestJson({GetSnippet()}));
413 411
414 LoadFromJSONString(json_str); 412 LoadFromJSONString(json_str);
415 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 413 EXPECT_THAT(service()->snippets(), SizeIs(1));
416 414
417 service()->ClearCachedSuggestionsForDebugging(articles_category()); 415 service()->ClearCachedSuggestionsForDebugging();
418 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 416 EXPECT_THAT(service()->snippets(), IsEmpty());
419 } 417 }
420 418
421 TEST_F(NTPSnippetsServiceTest, InsertAtFront) { 419 TEST_F(NTPSnippetsServiceTest, InsertAtFront) {
422 std::string first("http://first"); 420 std::string first("http://first");
423 LoadFromJSONString(GetTestJson({GetSnippetWithUrl(first)})); 421 LoadFromJSONString(GetTestJson({GetSnippetWithUrl(first)}));
424 EXPECT_THAT(service()->GetSnippetsForTesting(), ElementsAre(IdEq(first))); 422 EXPECT_THAT(service()->snippets(), ElementsAre(IdEq(first)));
425 423
426 std::string second("http://second"); 424 std::string second("http://second");
427 LoadFromJSONString(GetTestJson({GetSnippetWithUrl(second)})); 425 LoadFromJSONString(GetTestJson({GetSnippetWithUrl(second)}));
428 // The snippet loaded last should be at the first position in the list now. 426 // The snippet loaded last should be at the first position in the list now.
429 EXPECT_THAT(service()->GetSnippetsForTesting(), 427 EXPECT_THAT(service()->snippets(), ElementsAre(IdEq(second), IdEq(first)));
430 ElementsAre(IdEq(second), IdEq(first)));
431 } 428 }
432 429
433 TEST_F(NTPSnippetsServiceTest, LimitNumSnippets) { 430 TEST_F(NTPSnippetsServiceTest, LimitNumSnippets) {
434 int max_snippet_count = NTPSnippetsService::GetMaxSnippetCountForTesting(); 431 int max_snippet_count = NTPSnippetsService::GetMaxSnippetCountForTesting();
435 int snippets_per_load = max_snippet_count / 2 + 1; 432 int snippets_per_load = max_snippet_count / 2 + 1;
436 char url_format[] = "http://localhost/%i"; 433 char url_format[] = "http://localhost/%i";
437 434
438 std::vector<std::string> snippets1; 435 std::vector<std::string> snippets1;
439 std::vector<std::string> snippets2; 436 std::vector<std::string> snippets2;
440 for (int i = 0; i < snippets_per_load; i++) { 437 for (int i = 0; i < snippets_per_load; i++) {
441 snippets1.push_back(GetSnippetWithUrl(base::StringPrintf(url_format, i))); 438 snippets1.push_back(GetSnippetWithUrl(base::StringPrintf(url_format, i)));
442 snippets2.push_back(GetSnippetWithUrl( 439 snippets2.push_back(GetSnippetWithUrl(
443 base::StringPrintf(url_format, snippets_per_load + i))); 440 base::StringPrintf(url_format, snippets_per_load + i)));
444 } 441 }
445 442
446 LoadFromJSONString(GetTestJson(snippets1)); 443 LoadFromJSONString(GetTestJson(snippets1));
447 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(snippets1.size())); 444 ASSERT_THAT(service()->snippets(), SizeIs(snippets1.size()));
448 445
449 LoadFromJSONString(GetTestJson(snippets2)); 446 LoadFromJSONString(GetTestJson(snippets2));
450 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(max_snippet_count)); 447 EXPECT_THAT(service()->snippets(), SizeIs(max_snippet_count));
451 } 448 }
452 449
453 TEST_F(NTPSnippetsServiceTest, LoadInvalidJson) { 450 TEST_F(NTPSnippetsServiceTest, LoadInvalidJson) {
454 LoadFromJSONString(GetTestJson({GetInvalidSnippet()})); 451 LoadFromJSONString(GetTestJson({GetInvalidSnippet()}));
455 EXPECT_THAT(service()->snippets_fetcher()->last_status(), 452 EXPECT_THAT(service()->snippets_fetcher()->last_status(),
456 StartsWith("Received invalid JSON")); 453 StartsWith("Received invalid JSON"));
457 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 454 EXPECT_THAT(service()->snippets(), IsEmpty());
458 } 455 }
459 456
460 TEST_F(NTPSnippetsServiceTest, LoadInvalidJsonWithExistingSnippets) { 457 TEST_F(NTPSnippetsServiceTest, LoadInvalidJsonWithExistingSnippets) {
461 LoadFromJSONString(GetTestJson({GetSnippet()})); 458 LoadFromJSONString(GetTestJson({GetSnippet()}));
462 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 459 ASSERT_THAT(service()->snippets(), SizeIs(1));
463 ASSERT_EQ("OK", service()->snippets_fetcher()->last_status()); 460 ASSERT_EQ("OK", service()->snippets_fetcher()->last_status());
464 461
465 LoadFromJSONString(GetTestJson({GetInvalidSnippet()})); 462 LoadFromJSONString(GetTestJson({GetInvalidSnippet()}));
466 EXPECT_THAT(service()->snippets_fetcher()->last_status(), 463 EXPECT_THAT(service()->snippets_fetcher()->last_status(),
467 StartsWith("Received invalid JSON")); 464 StartsWith("Received invalid JSON"));
468 // This should not have changed the existing snippets. 465 // This should not have changed the existing snippets.
469 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 466 EXPECT_THAT(service()->snippets(), SizeIs(1));
470 } 467 }
471 468
472 TEST_F(NTPSnippetsServiceTest, LoadIncompleteJson) { 469 TEST_F(NTPSnippetsServiceTest, LoadIncompleteJson) {
473 LoadFromJSONString(GetTestJson({GetIncompleteSnippet()})); 470 LoadFromJSONString(GetTestJson({GetIncompleteSnippet()}));
474 EXPECT_EQ("Invalid / empty list.", 471 EXPECT_EQ("Invalid / empty list.",
475 service()->snippets_fetcher()->last_status()); 472 service()->snippets_fetcher()->last_status());
476 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 473 EXPECT_THAT(service()->snippets(), IsEmpty());
477 } 474 }
478 475
479 TEST_F(NTPSnippetsServiceTest, LoadIncompleteJsonWithExistingSnippets) { 476 TEST_F(NTPSnippetsServiceTest, LoadIncompleteJsonWithExistingSnippets) {
480 LoadFromJSONString(GetTestJson({GetSnippet()})); 477 LoadFromJSONString(GetTestJson({GetSnippet()}));
481 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 478 ASSERT_THAT(service()->snippets(), SizeIs(1));
482 479
483 LoadFromJSONString(GetTestJson({GetIncompleteSnippet()})); 480 LoadFromJSONString(GetTestJson({GetIncompleteSnippet()}));
484 EXPECT_EQ("Invalid / empty list.", 481 EXPECT_EQ("Invalid / empty list.",
485 service()->snippets_fetcher()->last_status()); 482 service()->snippets_fetcher()->last_status());
486 // This should not have changed the existing snippets. 483 // This should not have changed the existing snippets.
487 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 484 EXPECT_THAT(service()->snippets(), SizeIs(1));
488 } 485 }
489 486
490 TEST_F(NTPSnippetsServiceTest, Dismiss) { 487 TEST_F(NTPSnippetsServiceTest, Dismiss) {
491 std::vector<std::string> source_urls, publishers, amp_urls; 488 std::vector<std::string> source_urls, publishers, amp_urls;
492 source_urls.push_back(std::string("http://site.com")); 489 source_urls.push_back(std::string("http://site.com"));
493 publishers.push_back(std::string("Source 1")); 490 publishers.push_back(std::string("Source 1"));
494 amp_urls.push_back(std::string()); 491 amp_urls.push_back(std::string());
495 std::string json_str( 492 std::string json_str(
496 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); 493 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
497 494
498 LoadFromJSONString(json_str); 495 LoadFromJSONString(json_str);
499 496
500 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 497 ASSERT_THAT(service()->snippets(), SizeIs(1));
501 498
502 // Dismissing a non-existent snippet shouldn't do anything. 499 // Dismissing a non-existent snippet shouldn't do anything.
503 service()->DismissSuggestion(MakeUniqueID("http://othersite.com")); 500 service()->DismissSuggestion(MakeUniqueID("http://othersite.com"));
504 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 501 EXPECT_THAT(service()->snippets(), SizeIs(1));
505 502
506 // Dismiss the snippet. 503 // Dismiss the snippet.
507 service()->DismissSuggestion(MakeUniqueID(kSnippetUrl)); 504 service()->DismissSuggestion(MakeUniqueID(kSnippetUrl));
508 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 505 EXPECT_THAT(service()->snippets(), IsEmpty());
509 506
510 // Make sure that fetching the same snippet again does not re-add it. 507 // Make sure that fetching the same snippet again does not re-add it.
511 LoadFromJSONString(json_str); 508 LoadFromJSONString(json_str);
512 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 509 EXPECT_THAT(service()->snippets(), IsEmpty());
513 510
514 // The snippet should stay dismissed even after re-creating the service. 511 // The snippet should stay dismissed even after re-creating the service.
515 RecreateSnippetsService(); 512 RecreateSnippetsService();
516 LoadFromJSONString(json_str); 513 LoadFromJSONString(json_str);
517 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 514 EXPECT_THAT(service()->snippets(), IsEmpty());
518 515
519 // The snippet can be added again after clearing dismissed snippets. 516 // The snippet can be added again after clearing dismissed snippets.
520 service()->ClearDismissedSuggestionsForDebugging(articles_category()); 517 service()->ClearDismissedSuggestionsForDebugging();
521 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 518 EXPECT_THAT(service()->snippets(), IsEmpty());
522 LoadFromJSONString(json_str); 519 LoadFromJSONString(json_str);
523 EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 520 EXPECT_THAT(service()->snippets(), SizeIs(1));
524 } 521 }
525 522
526 TEST_F(NTPSnippetsServiceTest, GetDismissed) { 523 TEST_F(NTPSnippetsServiceTest, GetDismissed) {
527 LoadFromJSONString(GetTestJson({GetSnippet()})); 524 LoadFromJSONString(GetTestJson({GetSnippet()}));
528 525
529 service()->DismissSuggestion(MakeUniqueID(kSnippetUrl)); 526 service()->DismissSuggestion(MakeUniqueID(kSnippetUrl));
530 std::vector<ContentSuggestion> suggestions = 527 const NTPSnippet::PtrVector& snippets = service()->dismissed_snippets();
531 service()->GetDismissedSuggestionsForDebugging(articles_category()); 528 EXPECT_EQ(1u, snippets.size());
532 EXPECT_EQ(1u, suggestions.size()); 529 for (auto& snippet : snippets) {
533 for (auto& suggestion : suggestions) { 530 EXPECT_EQ(kSnippetUrl, snippet->id());
534 EXPECT_EQ(MakeUniqueID(kSnippetUrl), suggestion.id());
535 } 531 }
536 532
537 // There should be no dismissed snippet after clearing the list. 533 // There should be no dismissed snippet after clearing the list.
538 service()->ClearDismissedSuggestionsForDebugging(articles_category()); 534 service()->ClearDismissedSuggestionsForDebugging();
539 EXPECT_EQ(0u, service() 535 EXPECT_EQ(0u, service()->dismissed_snippets().size());
540 ->GetDismissedSuggestionsForDebugging(articles_category())
541 .size());
542 } 536 }
543 537
544 TEST_F(NTPSnippetsServiceTest, CreationTimestampParseFail) { 538 TEST_F(NTPSnippetsServiceTest, CreationTimestampParseFail) {
545 std::string json_str(GetTestJson({GetSnippetWithTimes( 539 std::string json_str(GetTestJson({GetSnippetWithTimes(
546 "aaa1448459205", 540 "aaa1448459205",
547 NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()))})); 541 NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()))}));
548 542
549 LoadFromJSONString(json_str); 543 LoadFromJSONString(json_str);
550 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 544 ASSERT_THAT(service()->snippets(), SizeIs(1));
551 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 545 const NTPSnippet& snippet = *service()->snippets().front();
552 EXPECT_EQ(snippet.id(), kSnippetUrl); 546 EXPECT_EQ(snippet.id(), kSnippetUrl);
553 EXPECT_EQ(snippet.title(), kSnippetTitle); 547 EXPECT_EQ(snippet.title(), kSnippetTitle);
554 EXPECT_EQ(snippet.snippet(), kSnippetText); 548 EXPECT_EQ(snippet.snippet(), kSnippetText);
555 EXPECT_EQ(base::Time::UnixEpoch(), snippet.publish_date()); 549 EXPECT_EQ(base::Time::UnixEpoch(), snippet.publish_date());
556 } 550 }
557 551
558 TEST_F(NTPSnippetsServiceTest, RemoveExpiredContent) { 552 TEST_F(NTPSnippetsServiceTest, RemoveExpiredContent) {
559 std::string json_str(GetTestJson({GetExpiredSnippet()})); 553 std::string json_str(GetTestJson({GetExpiredSnippet()}));
560 554
561 LoadFromJSONString(json_str); 555 LoadFromJSONString(json_str);
562 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 556 EXPECT_THAT(service()->snippets(), IsEmpty());
563 } 557 }
564 558
565 TEST_F(NTPSnippetsServiceTest, TestSingleSource) { 559 TEST_F(NTPSnippetsServiceTest, TestSingleSource) {
566 std::vector<std::string> source_urls, publishers, amp_urls; 560 std::vector<std::string> source_urls, publishers, amp_urls;
567 source_urls.push_back(std::string("http://source1.com")); 561 source_urls.push_back(std::string("http://source1.com"));
568 publishers.push_back(std::string("Source 1")); 562 publishers.push_back(std::string("Source 1"));
569 amp_urls.push_back(std::string("http://source1.amp.com")); 563 amp_urls.push_back(std::string("http://source1.amp.com"));
570 std::string json_str( 564 std::string json_str(
571 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); 565 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
572 566
573 LoadFromJSONString(json_str); 567 LoadFromJSONString(json_str);
574 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 568 ASSERT_THAT(service()->snippets(), SizeIs(1));
575 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 569 const NTPSnippet& snippet = *service()->snippets().front();
576 EXPECT_EQ(snippet.sources().size(), 1u); 570 EXPECT_EQ(snippet.sources().size(), 1u);
577 EXPECT_EQ(snippet.id(), kSnippetUrl); 571 EXPECT_EQ(snippet.id(), kSnippetUrl);
578 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com")); 572 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
579 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1")); 573 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
580 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com")); 574 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com"));
581 } 575 }
582 576
583 TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMalformedUrl) { 577 TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMalformedUrl) {
584 std::vector<std::string> source_urls, publishers, amp_urls; 578 std::vector<std::string> source_urls, publishers, amp_urls;
585 source_urls.push_back(std::string("aaaa")); 579 source_urls.push_back(std::string("aaaa"));
586 publishers.push_back(std::string("Source 1")); 580 publishers.push_back(std::string("Source 1"));
587 amp_urls.push_back(std::string("http://source1.amp.com")); 581 amp_urls.push_back(std::string("http://source1.amp.com"));
588 std::string json_str( 582 std::string json_str(
589 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); 583 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
590 584
591 LoadFromJSONString(json_str); 585 LoadFromJSONString(json_str);
592 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 586 EXPECT_THAT(service()->snippets(), IsEmpty());
593 } 587 }
594 588
595 TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMissingData) { 589 TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMissingData) {
596 std::vector<std::string> source_urls, publishers, amp_urls; 590 std::vector<std::string> source_urls, publishers, amp_urls;
597 source_urls.push_back(std::string("http://source1.com")); 591 source_urls.push_back(std::string("http://source1.com"));
598 publishers.push_back(std::string()); 592 publishers.push_back(std::string());
599 amp_urls.push_back(std::string()); 593 amp_urls.push_back(std::string());
600 std::string json_str( 594 std::string json_str(
601 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); 595 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
602 596
603 LoadFromJSONString(json_str); 597 LoadFromJSONString(json_str);
604 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 598 EXPECT_THAT(service()->snippets(), IsEmpty());
605 } 599 }
606 600
607 TEST_F(NTPSnippetsServiceTest, TestMultipleSources) { 601 TEST_F(NTPSnippetsServiceTest, TestMultipleSources) {
608 std::vector<std::string> source_urls, publishers, amp_urls; 602 std::vector<std::string> source_urls, publishers, amp_urls;
609 source_urls.push_back(std::string("http://source1.com")); 603 source_urls.push_back(std::string("http://source1.com"));
610 source_urls.push_back(std::string("http://source2.com")); 604 source_urls.push_back(std::string("http://source2.com"));
611 publishers.push_back(std::string("Source 1")); 605 publishers.push_back(std::string("Source 1"));
612 publishers.push_back(std::string("Source 2")); 606 publishers.push_back(std::string("Source 2"));
613 amp_urls.push_back(std::string("http://source1.amp.com")); 607 amp_urls.push_back(std::string("http://source1.amp.com"));
614 amp_urls.push_back(std::string("http://source2.amp.com")); 608 amp_urls.push_back(std::string("http://source2.amp.com"));
615 std::string json_str( 609 std::string json_str(
616 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); 610 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
617 611
618 LoadFromJSONString(json_str); 612 LoadFromJSONString(json_str);
619 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 613 ASSERT_THAT(service()->snippets(), SizeIs(1));
620 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 614 const NTPSnippet& snippet = *service()->snippets().front();
621 // Expect the first source to be chosen 615 // Expect the first source to be chosen
622 EXPECT_EQ(snippet.sources().size(), 2u); 616 EXPECT_EQ(snippet.sources().size(), 2u);
623 EXPECT_EQ(snippet.id(), kSnippetUrl); 617 EXPECT_EQ(snippet.id(), kSnippetUrl);
624 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com")); 618 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
625 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1")); 619 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
626 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com")); 620 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com"));
627 } 621 }
628 622
629 TEST_F(NTPSnippetsServiceTest, TestMultipleIncompleteSources) { 623 TEST_F(NTPSnippetsServiceTest, TestMultipleIncompleteSources) {
630 // Set Source 2 to have no AMP url, and Source 1 to have no publisher name 624 // Set Source 2 to have no AMP url, and Source 1 to have no publisher name
631 // Source 2 should win since we favor publisher name over amp url 625 // Source 2 should win since we favor publisher name over amp url
632 std::vector<std::string> source_urls, publishers, amp_urls; 626 std::vector<std::string> source_urls, publishers, amp_urls;
633 source_urls.push_back(std::string("http://source1.com")); 627 source_urls.push_back(std::string("http://source1.com"));
634 source_urls.push_back(std::string("http://source2.com")); 628 source_urls.push_back(std::string("http://source2.com"));
635 publishers.push_back(std::string()); 629 publishers.push_back(std::string());
636 publishers.push_back(std::string("Source 2")); 630 publishers.push_back(std::string("Source 2"));
637 amp_urls.push_back(std::string("http://source1.amp.com")); 631 amp_urls.push_back(std::string("http://source1.amp.com"));
638 amp_urls.push_back(std::string()); 632 amp_urls.push_back(std::string());
639 std::string json_str( 633 std::string json_str(
640 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); 634 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
641 635
642 LoadFromJSONString(json_str); 636 LoadFromJSONString(json_str);
643 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 637 ASSERT_THAT(service()->snippets(), SizeIs(1));
644 { 638 {
645 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 639 const NTPSnippet& snippet = *service()->snippets().front();
646 EXPECT_EQ(snippet.sources().size(), 2u); 640 EXPECT_EQ(snippet.sources().size(), 2u);
647 EXPECT_EQ(snippet.id(), kSnippetUrl); 641 EXPECT_EQ(snippet.id(), kSnippetUrl);
648 EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com")); 642 EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com"));
649 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2")); 643 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2"));
650 EXPECT_EQ(snippet.best_source().amp_url, GURL()); 644 EXPECT_EQ(snippet.best_source().amp_url, GURL());
651 } 645 }
652 646
653 service()->ClearCachedSuggestionsForDebugging(articles_category()); 647 service()->ClearCachedSuggestionsForDebugging();
654 // Set Source 1 to have no AMP url, and Source 2 to have no publisher name 648 // Set Source 1 to have no AMP url, and Source 2 to have no publisher name
655 // Source 1 should win in this case since we prefer publisher name to AMP url 649 // Source 1 should win in this case since we prefer publisher name to AMP url
656 source_urls.clear(); 650 source_urls.clear();
657 source_urls.push_back(std::string("http://source1.com")); 651 source_urls.push_back(std::string("http://source1.com"));
658 source_urls.push_back(std::string("http://source2.com")); 652 source_urls.push_back(std::string("http://source2.com"));
659 publishers.clear(); 653 publishers.clear();
660 publishers.push_back(std::string("Source 1")); 654 publishers.push_back(std::string("Source 1"));
661 publishers.push_back(std::string()); 655 publishers.push_back(std::string());
662 amp_urls.clear(); 656 amp_urls.clear();
663 amp_urls.push_back(std::string()); 657 amp_urls.push_back(std::string());
664 amp_urls.push_back(std::string("http://source2.amp.com")); 658 amp_urls.push_back(std::string("http://source2.amp.com"));
665 json_str = 659 json_str =
666 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}); 660 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
667 661
668 LoadFromJSONString(json_str); 662 LoadFromJSONString(json_str);
669 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 663 ASSERT_THAT(service()->snippets(), SizeIs(1));
670 { 664 {
671 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 665 const NTPSnippet& snippet = *service()->snippets().front();
672 EXPECT_EQ(snippet.sources().size(), 2u); 666 EXPECT_EQ(snippet.sources().size(), 2u);
673 EXPECT_EQ(snippet.id(), kSnippetUrl); 667 EXPECT_EQ(snippet.id(), kSnippetUrl);
674 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com")); 668 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
675 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1")); 669 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
676 EXPECT_EQ(snippet.best_source().amp_url, GURL()); 670 EXPECT_EQ(snippet.best_source().amp_url, GURL());
677 } 671 }
678 672
679 service()->ClearCachedSuggestionsForDebugging(articles_category()); 673 service()->ClearCachedSuggestionsForDebugging();
680 // Set source 1 to have no AMP url and no source, and source 2 to only have 674 // Set source 1 to have no AMP url and no source, and source 2 to only have
681 // amp url. There should be no snippets since we only add sources we consider 675 // amp url. There should be no snippets since we only add sources we consider
682 // complete 676 // complete
683 source_urls.clear(); 677 source_urls.clear();
684 source_urls.push_back(std::string("http://source1.com")); 678 source_urls.push_back(std::string("http://source1.com"));
685 source_urls.push_back(std::string("http://source2.com")); 679 source_urls.push_back(std::string("http://source2.com"));
686 publishers.clear(); 680 publishers.clear();
687 publishers.push_back(std::string()); 681 publishers.push_back(std::string());
688 publishers.push_back(std::string()); 682 publishers.push_back(std::string());
689 amp_urls.clear(); 683 amp_urls.clear();
690 amp_urls.push_back(std::string()); 684 amp_urls.push_back(std::string());
691 amp_urls.push_back(std::string("http://source2.amp.com")); 685 amp_urls.push_back(std::string("http://source2.amp.com"));
692 json_str = 686 json_str =
693 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}); 687 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
694 688
695 LoadFromJSONString(json_str); 689 LoadFromJSONString(json_str);
696 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 690 EXPECT_THAT(service()->snippets(), IsEmpty());
697 } 691 }
698 692
699 TEST_F(NTPSnippetsServiceTest, TestMultipleCompleteSources) { 693 TEST_F(NTPSnippetsServiceTest, TestMultipleCompleteSources) {
700 // Test 2 complete sources, we should choose the first complete source 694 // Test 2 complete sources, we should choose the first complete source
701 std::vector<std::string> source_urls, publishers, amp_urls; 695 std::vector<std::string> source_urls, publishers, amp_urls;
702 source_urls.push_back(std::string("http://source1.com")); 696 source_urls.push_back(std::string("http://source1.com"));
703 source_urls.push_back(std::string("http://source2.com")); 697 source_urls.push_back(std::string("http://source2.com"));
704 source_urls.push_back(std::string("http://source3.com")); 698 source_urls.push_back(std::string("http://source3.com"));
705 publishers.push_back(std::string("Source 1")); 699 publishers.push_back(std::string("Source 1"));
706 publishers.push_back(std::string()); 700 publishers.push_back(std::string());
707 publishers.push_back(std::string("Source 3")); 701 publishers.push_back(std::string("Source 3"));
708 amp_urls.push_back(std::string("http://source1.amp.com")); 702 amp_urls.push_back(std::string("http://source1.amp.com"));
709 amp_urls.push_back(std::string("http://source2.amp.com")); 703 amp_urls.push_back(std::string("http://source2.amp.com"));
710 amp_urls.push_back(std::string("http://source3.amp.com")); 704 amp_urls.push_back(std::string("http://source3.amp.com"));
711 std::string json_str( 705 std::string json_str(
712 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); 706 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
713 707
714 LoadFromJSONString(json_str); 708 LoadFromJSONString(json_str);
715 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 709 ASSERT_THAT(service()->snippets(), SizeIs(1));
716 { 710 {
717 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 711 const NTPSnippet& snippet = *service()->snippets().front();
718 EXPECT_EQ(snippet.sources().size(), 3u); 712 EXPECT_EQ(snippet.sources().size(), 3u);
719 EXPECT_EQ(snippet.id(), kSnippetUrl); 713 EXPECT_EQ(snippet.id(), kSnippetUrl);
720 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com")); 714 EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
721 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1")); 715 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
722 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com")); 716 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com"));
723 } 717 }
724 718
725 // Test 2 complete sources, we should choose the first complete source 719 // Test 2 complete sources, we should choose the first complete source
726 service()->ClearCachedSuggestionsForDebugging(articles_category()); 720 service()->ClearCachedSuggestionsForDebugging();
727 source_urls.clear(); 721 source_urls.clear();
728 source_urls.push_back(std::string("http://source1.com")); 722 source_urls.push_back(std::string("http://source1.com"));
729 source_urls.push_back(std::string("http://source2.com")); 723 source_urls.push_back(std::string("http://source2.com"));
730 source_urls.push_back(std::string("http://source3.com")); 724 source_urls.push_back(std::string("http://source3.com"));
731 publishers.clear(); 725 publishers.clear();
732 publishers.push_back(std::string()); 726 publishers.push_back(std::string());
733 publishers.push_back(std::string("Source 2")); 727 publishers.push_back(std::string("Source 2"));
734 publishers.push_back(std::string("Source 3")); 728 publishers.push_back(std::string("Source 3"));
735 amp_urls.clear(); 729 amp_urls.clear();
736 amp_urls.push_back(std::string("http://source1.amp.com")); 730 amp_urls.push_back(std::string("http://source1.amp.com"));
737 amp_urls.push_back(std::string("http://source2.amp.com")); 731 amp_urls.push_back(std::string("http://source2.amp.com"));
738 amp_urls.push_back(std::string("http://source3.amp.com")); 732 amp_urls.push_back(std::string("http://source3.amp.com"));
739 json_str = 733 json_str =
740 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}); 734 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
741 735
742 LoadFromJSONString(json_str); 736 LoadFromJSONString(json_str);
743 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 737 ASSERT_THAT(service()->snippets(), SizeIs(1));
744 { 738 {
745 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 739 const NTPSnippet& snippet = *service()->snippets().front();
746 EXPECT_EQ(snippet.sources().size(), 3u); 740 EXPECT_EQ(snippet.sources().size(), 3u);
747 EXPECT_EQ(snippet.id(), kSnippetUrl); 741 EXPECT_EQ(snippet.id(), kSnippetUrl);
748 EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com")); 742 EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com"));
749 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2")); 743 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2"));
750 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source2.amp.com")); 744 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source2.amp.com"));
751 } 745 }
752 746
753 // Test 3 complete sources, we should choose the first complete source 747 // Test 3 complete sources, we should choose the first complete source
754 service()->ClearCachedSuggestionsForDebugging(articles_category()); 748 service()->ClearCachedSuggestionsForDebugging();
755 source_urls.clear(); 749 source_urls.clear();
756 source_urls.push_back(std::string("http://source1.com")); 750 source_urls.push_back(std::string("http://source1.com"));
757 source_urls.push_back(std::string("http://source2.com")); 751 source_urls.push_back(std::string("http://source2.com"));
758 source_urls.push_back(std::string("http://source3.com")); 752 source_urls.push_back(std::string("http://source3.com"));
759 publishers.clear(); 753 publishers.clear();
760 publishers.push_back(std::string("Source 1")); 754 publishers.push_back(std::string("Source 1"));
761 publishers.push_back(std::string("Source 2")); 755 publishers.push_back(std::string("Source 2"));
762 publishers.push_back(std::string("Source 3")); 756 publishers.push_back(std::string("Source 3"));
763 amp_urls.clear(); 757 amp_urls.clear();
764 amp_urls.push_back(std::string()); 758 amp_urls.push_back(std::string());
765 amp_urls.push_back(std::string("http://source2.amp.com")); 759 amp_urls.push_back(std::string("http://source2.amp.com"));
766 amp_urls.push_back(std::string("http://source3.amp.com")); 760 amp_urls.push_back(std::string("http://source3.amp.com"));
767 json_str = 761 json_str =
768 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}); 762 GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
769 763
770 LoadFromJSONString(json_str); 764 LoadFromJSONString(json_str);
771 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 765 ASSERT_THAT(service()->snippets(), SizeIs(1));
772 { 766 {
773 const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); 767 const NTPSnippet& snippet = *service()->snippets().front();
774 EXPECT_EQ(snippet.sources().size(), 3u); 768 EXPECT_EQ(snippet.sources().size(), 3u);
775 EXPECT_EQ(snippet.id(), kSnippetUrl); 769 EXPECT_EQ(snippet.id(), kSnippetUrl);
776 EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com")); 770 EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com"));
777 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2")); 771 EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2"));
778 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source2.amp.com")); 772 EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source2.amp.com"));
779 } 773 }
780 } 774 }
781 775
782 TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) { 776 TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) {
783 base::HistogramTester tester; 777 base::HistogramTester tester;
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
844 "http://mashable.com/2016/05/11/stolen?utm_cid=1"}; 838 "http://mashable.com/2016/05/11/stolen?utm_cid=1"};
845 const std::vector<std::string> publishers = {"Mashable", "AOL", "Mashable"}; 839 const std::vector<std::string> publishers = {"Mashable", "AOL", "Mashable"};
846 const std::vector<std::string> amp_urls = { 840 const std::vector<std::string> amp_urls = {
847 "http://mashable-amphtml.googleusercontent.com/1", 841 "http://mashable-amphtml.googleusercontent.com/1",
848 "http://t2.gstatic.com/images?q=tbn:3", 842 "http://t2.gstatic.com/images?q=tbn:3",
849 "http://t2.gstatic.com/images?q=tbn:3"}; 843 "http://t2.gstatic.com/images?q=tbn:3"};
850 844
851 // Add the snippet from the mashable domain. 845 // Add the snippet from the mashable domain.
852 LoadFromJSONString(GetTestJson({GetSnippetWithUrlAndTimesAndSources( 846 LoadFromJSONString(GetTestJson({GetSnippetWithUrlAndTimesAndSources(
853 source_urls[0], creation, expiry, source_urls, publishers, amp_urls)})); 847 source_urls[0], creation, expiry, source_urls, publishers, amp_urls)}));
854 ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); 848 ASSERT_THAT(service()->snippets(), SizeIs(1));
855 // Dismiss the snippet via the mashable source corpus ID. 849 // Dismiss the snippet via the mashable source corpus ID.
856 service()->DismissSuggestion(MakeUniqueID(source_urls[0])); 850 service()->DismissSuggestion(MakeUniqueID(source_urls[0]));
857 EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 851 EXPECT_THAT(service()->snippets(), IsEmpty());
858 852
859 // The same article from the AOL domain should now be detected as dismissed. 853 // The same article from the AOL domain should now be detected as dismissed.
860 LoadFromJSONString(GetTestJson({GetSnippetWithUrlAndTimesAndSources( 854 LoadFromJSONString(GetTestJson({GetSnippetWithUrlAndTimesAndSources(
861 source_urls[1], creation, expiry, source_urls, publishers, amp_urls)})); 855 source_urls[1], creation, expiry, source_urls, publishers, amp_urls)}));
862 ASSERT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); 856 ASSERT_THAT(service()->snippets(), IsEmpty());
863 } 857 }
864 858
865 TEST_F(NTPSnippetsServiceTest, StatusChanges) { 859 TEST_F(NTPSnippetsServiceTest, StatusChanges) {
866 // Simulate user signed out 860 // Simulate user signed out
867 SetUpFetchResponse(GetTestJson({GetSnippet()})); 861 SetUpFetchResponse(GetTestJson({GetSnippet()}));
868 EXPECT_CALL(observer(), 862 EXPECT_CALL(observer(),
869 OnCategoryStatusChanged(_, _, CategoryStatus::SIGNED_OUT)); 863 OnCategoryStatusChanged(_, _, CategoryStatus::SIGNED_OUT));
870 service()->OnDisabledReasonChanged(DisabledReason::SIGNED_OUT); 864 service()->OnDisabledReasonChanged(DisabledReason::SIGNED_OUT);
871 base::RunLoop().RunUntilIdle(); 865 base::RunLoop().RunUntilIdle();
872 EXPECT_EQ(NTPSnippetsService::State::DISABLED, service()->state_); 866 EXPECT_EQ(NTPSnippetsService::State::DISABLED, service()->state_);
873 EXPECT_THAT(service()->GetSnippetsForTesting(), 867 EXPECT_THAT(service()->snippets(), IsEmpty()); // No fetch should be made.
874 IsEmpty()); // No fetch should be made.
875 868
876 // Simulate user sign in. The service should be ready again and load snippets. 869 // Simulate user sign in. The service should be ready again and load snippets.
877 SetUpFetchResponse(GetTestJson({GetSnippet()})); 870 SetUpFetchResponse(GetTestJson({GetSnippet()}));
878 EXPECT_CALL(observer(), 871 EXPECT_CALL(observer(),
879 OnCategoryStatusChanged(_, _, CategoryStatus::AVAILABLE_LOADING)); 872 OnCategoryStatusChanged(_, _, CategoryStatus::AVAILABLE_LOADING));
880 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(1); 873 EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(1);
881 service()->OnDisabledReasonChanged(DisabledReason::NONE); 874 service()->OnDisabledReasonChanged(DisabledReason::NONE);
882 EXPECT_CALL(observer(), 875 EXPECT_CALL(observer(),
883 OnCategoryStatusChanged(_, _, CategoryStatus::AVAILABLE)); 876 OnCategoryStatusChanged(_, _, CategoryStatus::AVAILABLE));
884 base::RunLoop().RunUntilIdle(); 877 base::RunLoop().RunUntilIdle();
885 EXPECT_EQ(NTPSnippetsService::State::READY, service()->state_); 878 EXPECT_EQ(NTPSnippetsService::State::READY, service()->state_);
886 EXPECT_FALSE(service()->GetSnippetsForTesting().empty()); 879 EXPECT_FALSE(service()->snippets().empty());
887 } 880 }
888 881
889 } // namespace ntp_snippets 882 } // namespace ntp_snippets
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698