Commit e88d1680 authored by sfiera's avatar sfiera Committed by Commit bot

Overhaul ntp_snippets_service_unittest.cc.

A partial list of things done here:

  * Move tests that verified NTPSnippet::best_source() behavior into
    ntp_snippet_test.cc; they don't belong here.
  * Switch it to use the chromecontentsuggestions-pa. Testing new
    features like server-provided categories is not possible without
    this.
  * Replace MockContentSuggestionsProviderObserver with a fake. It
    shouldn't have been a mock, since we weren't using it to test
    behavior, only state.
  * In one case, test using what the observer sees, instead of peeking
    in through debugging hooks.
  * Get rid of special test base class and Mock::VerifyAndClear calls; I
    consider these anti-patterns.
  * Initialize the service better; give it an empty list of snippets for
    its first fetch, and wait for it to complete the fetch before
    returning (I think this is what was most strongly implicated by the
    failures with the other CL).
  * Create the service and expect the scheduling directly from each
    case, so they are more readable individually.

Review-Url: https://codereview.chromium.org/2285133004
Cr-Commit-Position: refs/heads/master@{#415358}
parent 740cb043
...@@ -12,6 +12,16 @@ ...@@ -12,6 +12,16 @@
namespace ntp_snippets { namespace ntp_snippets {
namespace { namespace {
std::unique_ptr<NTPSnippet> SnippetFromContentSuggestionJSON(
const std::string& json) {
auto json_value = base::JSONReader::Read(json);
base::DictionaryValue* json_dict;
if (!json_value->GetAsDictionary(&json_dict)) {
return nullptr;
}
return NTPSnippet::CreateFromContentSuggestionsDictionary(*json_dict);
}
TEST(NTPSnippetTest, FromChromeContentSuggestionsDictionary) { TEST(NTPSnippetTest, FromChromeContentSuggestionsDictionary) {
const std::string kJsonStr = const std::string kJsonStr =
"{" "{"
...@@ -26,11 +36,7 @@ TEST(NTPSnippetTest, FromChromeContentSuggestionsDictionary) { ...@@ -26,11 +36,7 @@ TEST(NTPSnippetTest, FromChromeContentSuggestionsDictionary) {
" \"ampUrl\" : \"http://localhost/amp\"," " \"ampUrl\" : \"http://localhost/amp\","
" \"faviconUrl\" : \"http://localhost/favicon.ico\" " " \"faviconUrl\" : \"http://localhost/favicon.ico\" "
"}"; "}";
auto json_value = base::JSONReader::Read(kJsonStr); auto snippet = SnippetFromContentSuggestionJSON(kJsonStr);
base::DictionaryValue* json_dict;
ASSERT_TRUE(json_value->GetAsDictionary(&json_dict));
auto snippet = NTPSnippet::CreateFromContentSuggestionsDictionary(*json_dict);
ASSERT_THAT(snippet, testing::NotNull()); ASSERT_THAT(snippet, testing::NotNull());
EXPECT_EQ(snippet->id(), "http://localhost/foobar"); EXPECT_EQ(snippet->id(), "http://localhost/foobar");
...@@ -47,5 +53,215 @@ TEST(NTPSnippetTest, FromChromeContentSuggestionsDictionary) { ...@@ -47,5 +53,215 @@ TEST(NTPSnippetTest, FromChromeContentSuggestionsDictionary) {
EXPECT_EQ(snippet->best_source().amp_url, GURL("http://localhost/amp")); EXPECT_EQ(snippet->best_source().amp_url, GURL("http://localhost/amp"));
} }
std::unique_ptr<NTPSnippet> SnippetFromChromeReaderDict(
std::unique_ptr<base::DictionaryValue> dict) {
if (!dict) {
return nullptr;
}
return NTPSnippet::CreateFromChromeReaderDictionary(*dict);
}
std::unique_ptr<base::DictionaryValue> SnippetWithTwoSources() {
const std::string kJsonStr =
"{\n"
" \"contentInfo\": {\n"
" \"url\": \"http://url.com\",\n"
" \"title\": \"Source 1 Title\",\n"
" \"snippet\": \"Source 1 Snippet\",\n"
" \"thumbnailUrl\": \"http://url.com/thumbnail\",\n"
" \"creationTimestampSec\": 1234567890,\n"
" \"expiryTimestampSec\": 2345678901,\n"
" \"sourceCorpusInfo\": [{\n"
" \"corpusId\": \"http://source1.com\",\n"
" \"publisherData\": {\n"
" \"sourceName\": \"Source 1\"\n"
" },\n"
" \"ampUrl\": \"http://source1.amp.com\"\n"
" }, {\n"
" \"corpusId\": \"http://source2.com\",\n"
" \"publisherData\": {\n"
" \"sourceName\": \"Source 2\"\n"
" },\n"
" \"ampUrl\": \"http://source2.amp.com\"\n"
" }]\n"
" },\n"
" \"score\": 5.0\n"
"}\n";
auto json_value = base::JSONReader::Read(kJsonStr);
base::DictionaryValue* json_dict;
if (!json_value->GetAsDictionary(&json_dict)) {
return nullptr;
}
return json_dict->CreateDeepCopy();
}
TEST(NTPSnippetTest, TestMultipleSources) {
auto snippet = SnippetFromChromeReaderDict(SnippetWithTwoSources());
ASSERT_THAT(snippet, testing::NotNull());
// Expect the first source to be chosen.
EXPECT_EQ(snippet->sources().size(), 2u);
EXPECT_EQ(snippet->id(), "http://url.com");
EXPECT_EQ(snippet->best_source().url, GURL("http://source1.com"));
EXPECT_EQ(snippet->best_source().publisher_name, std::string("Source 1"));
EXPECT_EQ(snippet->best_source().amp_url, GURL("http://source1.amp.com"));
}
TEST(NTPSnippetTest, TestMultipleIncompleteSources1) {
// Set Source 2 to have no AMP url, and Source 1 to have no publisher name
// Source 2 should win since we favor publisher name over amp url
auto dict = SnippetWithTwoSources();
base::ListValue* sources;
ASSERT_TRUE(dict->GetList("contentInfo.sourceCorpusInfo", &sources));
base::DictionaryValue* source;
ASSERT_TRUE(sources->GetDictionary(0, &source));
source->Remove("publisherData.sourceName", nullptr);
ASSERT_TRUE(sources->GetDictionary(1, &source));
source->Remove("ampUrl", nullptr);
auto snippet = SnippetFromChromeReaderDict(std::move(dict));
ASSERT_THAT(snippet, testing::NotNull());
EXPECT_EQ(snippet->sources().size(), 2u);
EXPECT_EQ(snippet->id(), "http://url.com");
EXPECT_EQ(snippet->best_source().url, GURL("http://source2.com"));
EXPECT_EQ(snippet->best_source().publisher_name, std::string("Source 2"));
EXPECT_EQ(snippet->best_source().amp_url, GURL());
}
TEST(NTPSnippetTest, TestMultipleIncompleteSources2) {
// Set Source 1 to have no AMP url, and Source 2 to have no publisher name
// Source 1 should win in this case since we prefer publisher name to AMP url
auto dict = SnippetWithTwoSources();
base::ListValue* sources;
ASSERT_TRUE(dict->GetList("contentInfo.sourceCorpusInfo", &sources));
base::DictionaryValue* source;
ASSERT_TRUE(sources->GetDictionary(0, &source));
source->Remove("ampUrl", nullptr);
ASSERT_TRUE(sources->GetDictionary(1, &source));
source->Remove("publisherData.sourceName", nullptr);
auto snippet = SnippetFromChromeReaderDict(std::move(dict));
ASSERT_THAT(snippet, testing::NotNull());
EXPECT_EQ(snippet->sources().size(), 2u);
EXPECT_EQ(snippet->id(), "http://url.com");
EXPECT_EQ(snippet->best_source().url, GURL("http://source1.com"));
EXPECT_EQ(snippet->best_source().publisher_name, std::string("Source 1"));
EXPECT_EQ(snippet->best_source().amp_url, GURL());
}
TEST(NTPSnippetTest, TestMultipleIncompleteSources3) {
// Set source 1 to have no AMP url and no source, and source 2 to only have
// amp url. There should be no snippets since we only add sources we consider
// complete
auto dict = SnippetWithTwoSources();
base::ListValue* sources;
ASSERT_TRUE(dict->GetList("contentInfo.sourceCorpusInfo", &sources));
base::DictionaryValue* source;
ASSERT_TRUE(sources->GetDictionary(0, &source));
source->Remove("publisherData.sourceName", nullptr);
source->Remove("ampUrl", nullptr);
ASSERT_TRUE(sources->GetDictionary(1, &source));
source->Remove("publisherData.sourceName", nullptr);
auto snippet = SnippetFromChromeReaderDict(std::move(dict));
ASSERT_THAT(snippet, testing::NotNull());
ASSERT_FALSE(snippet->is_complete());
}
std::unique_ptr<base::DictionaryValue> SnippetWithThreeSources() {
const std::string kJsonStr =
"{\n"
" \"contentInfo\": {\n"
" \"url\": \"http://url.com\",\n"
" \"title\": \"Source 1 Title\",\n"
" \"snippet\": \"Source 1 Snippet\",\n"
" \"thumbnailUrl\": \"http://url.com/thumbnail\",\n"
" \"creationTimestampSec\": 1234567890,\n"
" \"expiryTimestampSec\": 2345678901,\n"
" \"sourceCorpusInfo\": [{\n"
" \"corpusId\": \"http://source1.com\",\n"
" \"publisherData\": {\n"
" \"sourceName\": \"Source 1\"\n"
" },\n"
" \"ampUrl\": \"http://source1.amp.com\"\n"
" }, {\n"
" \"corpusId\": \"http://source2.com\",\n"
" \"publisherData\": {\n"
" \"sourceName\": \"Source 2\"\n"
" },\n"
" \"ampUrl\": \"http://source2.amp.com\"\n"
" }, {\n"
" \"corpusId\": \"http://source3.com\",\n"
" \"publisherData\": {\n"
" \"sourceName\": \"Source 3\"\n"
" },\n"
" \"ampUrl\": \"http://source3.amp.com\"\n"
" }]\n"
" },\n"
" \"score\": 5.0\n"
"}\n";
auto json_value = base::JSONReader::Read(kJsonStr);
base::DictionaryValue* json_dict;
if (!json_value->GetAsDictionary(&json_dict)) {
return nullptr;
}
return json_dict->CreateDeepCopy();
}
TEST(NTPSnippetTest, TestMultipleCompleteSources1) {
// Test 2 complete sources, we should choose the first complete source
auto dict = SnippetWithThreeSources();
base::ListValue* sources;
ASSERT_TRUE(dict->GetList("contentInfo.sourceCorpusInfo", &sources));
base::DictionaryValue* source;
ASSERT_TRUE(sources->GetDictionary(1, &source));
source->Remove("publisherData.sourceName", nullptr);
auto snippet = SnippetFromChromeReaderDict(std::move(dict));
ASSERT_THAT(snippet, testing::NotNull());
EXPECT_EQ(snippet->sources().size(), 3u);
EXPECT_EQ(snippet->id(), "http://url.com");
EXPECT_EQ(snippet->best_source().url, GURL("http://source1.com"));
EXPECT_EQ(snippet->best_source().publisher_name, std::string("Source 1"));
EXPECT_EQ(snippet->best_source().amp_url, GURL("http://source1.amp.com"));
}
TEST(NTPSnippetTest, TestMultipleCompleteSources2) {
// Test 2 complete sources, we should choose the first complete source
auto dict = SnippetWithThreeSources();
base::ListValue* sources;
ASSERT_TRUE(dict->GetList("contentInfo.sourceCorpusInfo", &sources));
base::DictionaryValue* source;
ASSERT_TRUE(sources->GetDictionary(0, &source));
source->Remove("publisherData.sourceName", nullptr);
auto snippet = SnippetFromChromeReaderDict(std::move(dict));
ASSERT_THAT(snippet, testing::NotNull());
EXPECT_EQ(snippet->sources().size(), 3u);
EXPECT_EQ(snippet->id(), "http://url.com");
EXPECT_EQ(snippet->best_source().url, GURL("http://source2.com"));
EXPECT_EQ(snippet->best_source().publisher_name, std::string("Source 2"));
EXPECT_EQ(snippet->best_source().amp_url, GURL("http://source2.amp.com"));
}
TEST(NTPSnippetTest, TestMultipleCompleteSources3) {
// Test 3 complete sources, we should choose the first complete source
auto dict = SnippetWithThreeSources();
auto snippet = SnippetFromChromeReaderDict(std::move(dict));
ASSERT_THAT(snippet, testing::NotNull());
EXPECT_EQ(snippet->sources().size(), 3u);
EXPECT_EQ(snippet->id(), "http://url.com");
EXPECT_EQ(snippet->best_source().url, GURL("http://source1.com"));
EXPECT_EQ(snippet->best_source().publisher_name, std::string("Source 1"));
EXPECT_EQ(snippet->best_source().amp_url, GURL("http://source1.amp.com"));
}
} // namespace } // namespace
} // namespace ntp_snippets } // namespace ntp_snippets
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
#include "components/image_fetcher/image_fetcher.h" #include "components/image_fetcher/image_fetcher.h"
#include "components/image_fetcher/image_fetcher_delegate.h" #include "components/image_fetcher/image_fetcher_delegate.h"
#include "components/ntp_snippets/category_factory.h" #include "components/ntp_snippets/category_factory.h"
#include "components/ntp_snippets/mock_content_suggestions_provider_observer.h"
#include "components/ntp_snippets/ntp_snippet.h" #include "components/ntp_snippets/ntp_snippet.h"
#include "components/ntp_snippets/ntp_snippets_constants.h"
#include "components/ntp_snippets/ntp_snippets_database.h" #include "components/ntp_snippets/ntp_snippets_database.h"
#include "components/ntp_snippets/ntp_snippets_fetcher.h" #include "components/ntp_snippets/ntp_snippets_fetcher.h"
#include "components/ntp_snippets/ntp_snippets_scheduler.h" #include "components/ntp_snippets/ntp_snippets_scheduler.h"
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
#include "components/prefs/testing_pref_service.h" #include "components/prefs/testing_pref_service.h"
#include "components/signin/core/browser/fake_profile_oauth2_token_service.h" #include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
#include "components/signin/core/browser/fake_signin_manager.h" #include "components/signin/core/browser/fake_signin_manager.h"
#include "components/variations/variations_associated_data.h"
#include "google_apis/google_api_keys.h" #include "google_apis/google_api_keys.h"
#include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_test_util.h" #include "net/url_request/url_request_test_util.h"
...@@ -48,12 +49,17 @@ using image_fetcher::ImageFetcher; ...@@ -48,12 +49,17 @@ using image_fetcher::ImageFetcher;
using image_fetcher::ImageFetcherDelegate; using image_fetcher::ImageFetcherDelegate;
using testing::ElementsAre; using testing::ElementsAre;
using testing::Eq; using testing::Eq;
using testing::InSequence;
using testing::Invoke; using testing::Invoke;
using testing::IsEmpty; using testing::IsEmpty;
using testing::Mock; using testing::Mock;
using testing::MockFunction;
using testing::NiceMock;
using testing::Return; using testing::Return;
using testing::SaveArg;
using testing::SizeIs; using testing::SizeIs;
using testing::StartsWith; using testing::StartsWith;
using testing::WithArgs;
using testing::_; using testing::_;
namespace ntp_snippets { namespace ntp_snippets {
...@@ -65,8 +71,12 @@ MATCHER_P(IdEq, value, "") { ...@@ -65,8 +71,12 @@ MATCHER_P(IdEq, value, "") {
} }
const base::Time::Exploded kDefaultCreationTime = {2015, 11, 4, 25, 13, 46, 45}; const base::Time::Exploded kDefaultCreationTime = {2015, 11, 4, 25, 13, 46, 45};
const char kTestContentSnippetsServerFormat[] = const char kTestContentSuggestionsServerEndpoint[] =
"https://chromereader-pa.googleapis.com/v1/fetch?key=%s"; "https://localunittest-chromecontentsuggestions-pa.googleapis.com/v1/"
"suggestions/fetch";
const char kTestContentSuggestionsServerFormat[] =
"https://localunittest-chromecontentsuggestions-pa.googleapis.com/v1/"
"suggestions/fetch?key=%s";
const char kSnippetUrl[] = "http://localhost/foobar"; const char kSnippetUrl[] = "http://localhost/foobar";
const char kSnippetTitle[] = "Title"; const char kSnippetTitle[] = "Title";
...@@ -74,7 +84,6 @@ const char kSnippetText[] = "Snippet"; ...@@ -74,7 +84,6 @@ const char kSnippetText[] = "Snippet";
const char kSnippetSalientImage[] = "http://localhost/salient_image"; const char kSnippetSalientImage[] = "http://localhost/salient_image";
const char kSnippetPublisherName[] = "Foo News"; const char kSnippetPublisherName[] = "Foo News";
const char kSnippetAmpUrl[] = "http://localhost/amp"; const char kSnippetAmpUrl[] = "http://localhost/amp";
const float kSnippetScore = 5.0;
const char kSnippetUrl2[] = "http://foo.com/bar"; const char kSnippetUrl2[] = "http://foo.com/bar";
...@@ -89,96 +98,85 @@ base::Time GetDefaultExpirationTime() { ...@@ -89,96 +98,85 @@ base::Time GetDefaultExpirationTime() {
} }
std::string GetTestJson(const std::vector<std::string>& snippets) { std::string GetTestJson(const std::vector<std::string>& snippets) {
return base::StringPrintf("{\"recos\":[%s]}", return base::StringPrintf(
base::JoinString(snippets, ", ").c_str()); "{\n"
} " \"categories\": [{\n"
" \"id\": 1,\n"
std::string GetSnippetWithUrlAndTimesAndSources( " \"localizedTitle\": \"Articles for You\",\n"
const std::string& url, " \"suggestions\": [%s]\n"
const std::string& content_creation_time_str, " }]\n"
const std::string& expiry_time_str, "}\n",
const std::vector<std::string>& source_urls, base::JoinString(snippets, ", ").c_str());
const std::vector<std::string>& publishers, }
const std::vector<std::string>& amp_urls) {
char json_str_format[] = std::string FormatTime(const base::Time& t) {
"{ \"contentInfo\": {" base::Time::Exploded x;
"\"url\" : \"%s\"," t.UTCExplode(&x);
"\"title\" : \"%s\"," return base::StringPrintf("%04d-%02d-%02dT%02d:%02d:%02dZ", x.year, x.month,
"\"snippet\" : \"%s\"," x.day_of_month, x.hour, x.minute, x.second);
"\"thumbnailUrl\" : \"%s\"," }
"\"creationTimestampSec\" : \"%s\","
"\"expiryTimestampSec\" : \"%s\"," std::string GetSnippetWithUrlAndTimesAndSource(
"\"sourceCorpusInfo\" : [%s]" const std::vector<std::string>& ids,
"}, "
"\"score\" : %f}";
char source_corpus_info_format[] =
"{\"corpusId\": \"%s\","
"\"publisherData\": {"
"\"sourceName\": \"%s\""
"},"
"\"ampUrl\": \"%s\"}";
std::string source_corpus_info_list_str;
for (size_t i = 0; i < source_urls.size(); ++i) {
std::string source_corpus_info_str =
base::StringPrintf(source_corpus_info_format,
source_urls[i].empty() ? "" : source_urls[i].c_str(),
publishers[i].empty() ? "" : publishers[i].c_str(),
amp_urls[i].empty() ? "" : amp_urls[i].c_str());
source_corpus_info_list_str.append(source_corpus_info_str);
source_corpus_info_list_str.append(",");
}
// Remove the last comma
source_corpus_info_list_str.erase(source_corpus_info_list_str.size()-1, 1);
return base::StringPrintf(json_str_format, url.c_str(), kSnippetTitle,
kSnippetText, kSnippetSalientImage,
content_creation_time_str.c_str(),
expiry_time_str.c_str(),
source_corpus_info_list_str.c_str(), kSnippetScore);
}
std::string GetSnippetWithSources(const std::vector<std::string>& source_urls,
const std::vector<std::string>& publishers,
const std::vector<std::string>& amp_urls) {
return GetSnippetWithUrlAndTimesAndSources(
kSnippetUrl, NTPSnippet::TimeToJsonString(GetDefaultCreationTime()),
NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()), source_urls,
publishers, amp_urls);
}
std::string GetSnippetWithUrlAndTimes(
const std::string& url, const std::string& url,
const std::string& content_creation_time_str, const base::Time& creation_time,
const std::string& expiry_time_str) { const base::Time& expiry_time,
return GetSnippetWithUrlAndTimesAndSources( const std::string& publisher,
url, content_creation_time_str, expiry_time_str, const std::string& amp_url) {
{std::string(url)}, {std::string(kSnippetPublisherName)}, const std::string ids_string = base::JoinString(ids, "\",\n \"");
{std::string(kSnippetAmpUrl)}); return base::StringPrintf(
} "{\n"
" \"ids\": [\n"
std::string GetSnippetWithTimes(const std::string& content_creation_time_str, " \"%s\"\n"
const std::string& expiry_time_str) { " ],\n"
return GetSnippetWithUrlAndTimes(kSnippetUrl, content_creation_time_str, " \"title\": \"%s\",\n"
expiry_time_str); " \"snippet\": \"%s\",\n"
" \"fullPageUrl\": \"%s\",\n"
" \"creationTime\": \"%s\",\n"
" \"expirationTime\": \"%s\",\n"
" \"attribution\": \"%s\",\n"
" \"imageUrl\": \"%s\",\n"
" \"ampUrl\": \"%s\"\n"
" }",
ids_string.c_str(), kSnippetTitle, kSnippetText, url.c_str(),
FormatTime(creation_time).c_str(), FormatTime(expiry_time).c_str(),
publisher.c_str(), kSnippetSalientImage, amp_url.c_str());
}
std::string GetSnippetWithSources(const std::string& source_url,
const std::string& publisher,
const std::string& amp_url) {
return GetSnippetWithUrlAndTimesAndSource(
{kSnippetUrl}, source_url, GetDefaultCreationTime(),
GetDefaultExpirationTime(), publisher, amp_url);
}
std::string GetSnippetWithUrlAndTimes(const std::string& url,
const base::Time& content_creation_time,
const base::Time& expiry_time) {
return GetSnippetWithUrlAndTimesAndSource({url}, url, content_creation_time,
expiry_time, kSnippetPublisherName,
kSnippetAmpUrl);
}
std::string GetSnippetWithTimes(const base::Time& content_creation_time,
const base::Time& expiry_time) {
return GetSnippetWithUrlAndTimes(kSnippetUrl, content_creation_time,
expiry_time);
} }
std::string GetSnippetWithUrl(const std::string& url) { std::string GetSnippetWithUrl(const std::string& url) {
return GetSnippetWithUrlAndTimes( return GetSnippetWithUrlAndTimes(url, GetDefaultCreationTime(),
url, NTPSnippet::TimeToJsonString(GetDefaultCreationTime()), GetDefaultExpirationTime());
NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()));
} }
std::string GetSnippet() { std::string GetSnippet() {
return GetSnippetWithUrlAndTimes( return GetSnippetWithUrlAndTimes(kSnippetUrl, GetDefaultCreationTime(),
kSnippetUrl, NTPSnippet::TimeToJsonString(GetDefaultCreationTime()), GetDefaultExpirationTime());
NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()));
} }
std::string GetExpiredSnippet() { std::string GetExpiredSnippet() {
return GetSnippetWithTimes( return GetSnippetWithTimes(GetDefaultCreationTime(), base::Time::Now());
NTPSnippet::TimeToJsonString(GetDefaultCreationTime()),
NTPSnippet::TimeToJsonString(base::Time::Now()));
} }
std::string GetInvalidSnippet() { std::string GetInvalidSnippet() {
...@@ -191,7 +189,7 @@ std::string GetIncompleteSnippet() { ...@@ -191,7 +189,7 @@ std::string GetIncompleteSnippet() {
std::string json_str = GetSnippet(); std::string json_str = GetSnippet();
// Rename the "url" entry. The result is syntactically valid json that will // Rename the "url" entry. The result is syntactically valid json that will
// fail to parse as snippets. // fail to parse as snippets.
size_t pos = json_str.find("\"url\""); size_t pos = json_str.find("\"fullPageUrl\"");
if (pos == std::string::npos) { if (pos == std::string::npos) {
NOTREACHED(); NOTREACHED();
return std::string(); return std::string();
...@@ -242,33 +240,6 @@ class MockScheduler : public NTPSnippetsScheduler { ...@@ -242,33 +240,6 @@ class MockScheduler : public NTPSnippetsScheduler {
MOCK_METHOD0(Unschedule, bool()); MOCK_METHOD0(Unschedule, bool());
}; };
class WaitForDBLoad {
public:
WaitForDBLoad(MockContentSuggestionsProviderObserver* observer,
NTPSnippetsService* service)
: observer_(observer) {
EXPECT_CALL(*observer_, OnCategoryStatusChanged(_, _, _))
.WillOnce(Invoke(this, &WaitForDBLoad::OnCategoryStatusChanged));
if (!service->ready())
run_loop_.Run();
}
~WaitForDBLoad() { Mock::VerifyAndClearExpectations(observer_); }
private:
void OnCategoryStatusChanged(ContentSuggestionsProvider* provider,
Category category,
CategoryStatus new_status) {
EXPECT_EQ(new_status, CategoryStatus::AVAILABLE_LOADING);
run_loop_.Quit();
}
MockContentSuggestionsProviderObserver* observer_;
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(WaitForDBLoad);
};
class MockImageFetcher : public ImageFetcher { class MockImageFetcher : public ImageFetcher {
public: public:
MOCK_METHOD1(SetImageFetcherDelegate, void(ImageFetcherDelegate*)); MOCK_METHOD1(SetImageFetcherDelegate, void(ImageFetcherDelegate*));
...@@ -280,29 +251,76 @@ class MockImageFetcher : public ImageFetcher { ...@@ -280,29 +251,76 @@ class MockImageFetcher : public ImageFetcher {
base::Callback<void(const std::string&, const gfx::Image&)>)); base::Callback<void(const std::string&, const gfx::Image&)>));
}; };
class MockDismissedSuggestionsCallback class FakeContentSuggestionsProviderObserver
: public ContentSuggestionsProvider::DismissedSuggestionsCallback { : public ContentSuggestionsProvider::Observer {
public: public:
MOCK_METHOD2(MockRun, FakeContentSuggestionsProviderObserver()
void(Category category, : loaded_(base::WaitableEvent::ResetPolicy::MANUAL,
std::vector<ContentSuggestion>* dismissed_suggestions)); base::WaitableEvent::InitialState::NOT_SIGNALED) {}
void Run(Category category,
std::vector<ContentSuggestion> dismissed_suggestions) { void OnNewSuggestions(ContentSuggestionsProvider* provider,
MockRun(category, &dismissed_suggestions); Category category,
std::vector<ContentSuggestion> suggestions) override {
suggestions_[category] = std::move(suggestions);
}
void OnCategoryStatusChanged(ContentSuggestionsProvider* provider,
Category category,
CategoryStatus new_status) override {
loaded_.Signal();
statuses_[category] = new_status;
}
void OnSuggestionInvalidated(ContentSuggestionsProvider* provider,
Category category,
const std::string& suggestion_id) override {}
CategoryStatus StatusForCategory(Category category) const {
auto it = statuses_.find(category);
if (it == statuses_.end()) {
return CategoryStatus::NOT_PROVIDED;
}
return it->second;
} }
const std::vector<ContentSuggestion>& SuggestionsForCategory(
Category category) {
return suggestions_[category];
}
void WaitForLoad() { loaded_.Wait(); }
bool Loaded() { return loaded_.IsSignaled(); }
void Reset() {
loaded_.Reset();
statuses_.clear();
}
private:
base::WaitableEvent loaded_;
std::map<Category, CategoryStatus, Category::CompareByID> statuses_;
std::map<Category, std::vector<ContentSuggestion>, Category::CompareByID>
suggestions_;
DISALLOW_COPY_AND_ASSIGN(FakeContentSuggestionsProviderObserver);
}; };
} // namespace } // namespace
class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase { class NTPSnippetsServiceTest : public ::testing::Test {
public: public:
NTPSnippetsServiceTest() NTPSnippetsServiceTest()
: fake_url_fetcher_factory_( : params_manager_(ntp_snippets::kStudyName,
{{"content_suggestions_backend",
kTestContentSuggestionsServerEndpoint}}),
fake_url_fetcher_factory_(
/*default_factory=*/&failing_url_fetcher_factory_), /*default_factory=*/&failing_url_fetcher_factory_),
test_url_(base::StringPrintf(kTestContentSnippetsServerFormat, test_url_(base::StringPrintf(kTestContentSuggestionsServerFormat,
google_apis::GetAPIKey().c_str())) { google_apis::GetAPIKey().c_str())),
NTPSnippetsService::RegisterProfilePrefs(pref_service()->registry());
RequestThrottler::RegisterProfilePrefs(pref_service()->registry()); observer_(base::MakeUnique<FakeContentSuggestionsProviderObserver>()) {
NTPSnippetsService::RegisterProfilePrefs(utils_.pref_service()->registry());
RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry());
// Since no SuggestionsService is injected in tests, we need to force the // Since no SuggestionsService is injected in tests, we need to force the
// service to fetch from all hosts. // service to fetch from all hosts.
...@@ -315,67 +333,59 @@ class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase { ...@@ -315,67 +333,59 @@ class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase {
// We need to run the message loop after deleting the database, because // We need to run the message loop after deleting the database, because
// ProtoDatabaseImpl deletes the actual LevelDB asynchronously on the task // ProtoDatabaseImpl deletes the actual LevelDB asynchronously on the task
// runner. Without this, we'd get reports of memory leaks. // runner. Without this, we'd get reports of memory leaks.
Mock::VerifyAndClear(&mock_scheduler());
service_.reset();
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
void SetUp() override { std::unique_ptr<NTPSnippetsService> MakeSnippetsService() {
test::NTPSnippetsTestBase::SetUp(); CHECK(!observer_->Loaded());
EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(1);
CreateSnippetsService();
}
void RecreateSnippetsService() {
Mock::VerifyAndClear(&mock_scheduler());
service_.reset();
base::RunLoop().RunUntilIdle();
EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(1);
CreateSnippetsService();
}
void CreateSnippetsService() {
DCHECK(!service_);
scoped_refptr<base::SingleThreadTaskRunner> task_runner( scoped_refptr<base::SingleThreadTaskRunner> task_runner(
base::ThreadTaskRunnerHandle::Get()); base::ThreadTaskRunnerHandle::Get());
scoped_refptr<net::TestURLRequestContextGetter> request_context_getter = scoped_refptr<net::TestURLRequestContextGetter> request_context_getter =
new net::TestURLRequestContextGetter(task_runner.get()); new net::TestURLRequestContextGetter(task_runner.get());
ResetSigninManager(); utils_.ResetSigninManager();
std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher = std::unique_ptr<NTPSnippetsFetcher> snippets_fetcher =
base::MakeUnique<NTPSnippetsFetcher>( base::MakeUnique<NTPSnippetsFetcher>(
fake_signin_manager(), fake_token_service_.get(), utils_.fake_signin_manager(), fake_token_service_.get(),
std::move(request_context_getter), pref_service(), std::move(request_context_getter), utils_.pref_service(),
&category_factory_, base::Bind(&ParseJson), &category_factory_, base::Bind(&ParseJson),
/*is_stable_channel=*/true); /*is_stable_channel=*/true);
fake_signin_manager()->SignIn("foo@bar.com"); utils_.fake_signin_manager()->SignIn("foo@bar.com");
snippets_fetcher->SetPersonalizationForTesting( snippets_fetcher->SetPersonalizationForTesting(
NTPSnippetsFetcher::Personalization::kNonPersonal); NTPSnippetsFetcher::Personalization::kNonPersonal);
auto image_fetcher = auto image_fetcher = base::MakeUnique<NiceMock<MockImageFetcher>>();
base::MakeUnique<testing::NiceMock<MockImageFetcher>>();
image_fetcher_ = image_fetcher.get(); image_fetcher_ = image_fetcher.get();
// Add an initial fetch response, as the service tries to fetch when there // Add an initial fetch response, as the service tries to fetch when there
// is nothing in the DB. // is nothing in the DB.
SetUpFetchResponse(GetTestJson({GetSnippet()})); SetUpFetchResponse(GetTestJson(std::vector<std::string>()));
service_.reset(new NTPSnippetsService( auto service = base::MakeUnique<NTPSnippetsService>(
&observer_, &category_factory_, pref_service(), nullptr, nullptr, "fr", observer_.get(), &category_factory_, utils_.pref_service(), nullptr,
&scheduler_, std::move(snippets_fetcher), nullptr, "fr", &scheduler_, std::move(snippets_fetcher),
std::move(image_fetcher), /*image_decoder=*/nullptr, std::move(image_fetcher), /*image_decoder=*/nullptr,
base::MakeUnique<NTPSnippetsDatabase>(database_dir_.path(), base::MakeUnique<NTPSnippetsDatabase>(database_dir_.path(),
task_runner), task_runner),
base::MakeUnique<NTPSnippetsStatusService>(fake_signin_manager(), base::MakeUnique<NTPSnippetsStatusService>(utils_.fake_signin_manager(),
pref_service()))); utils_.pref_service()));
WaitForDBLoad(&observer_, service_.get()); base::RunLoop().RunUntilIdle();
observer_->WaitForLoad();
return service;
} }
std::string MakeUniqueID(const std::string& within_category_id) { void ResetSnippetsService(std::unique_ptr<NTPSnippetsService>* service) {
return service()->MakeUniqueID(articles_category(), within_category_id); service->reset();
observer_ = base::MakeUnique<FakeContentSuggestionsProviderObserver>();
*service = MakeSnippetsService();
}
std::string MakeUniqueID(const NTPSnippetsService& service,
const std::string& within_category_id) {
return service.MakeUniqueID(articles_category(), within_category_id);
} }
Category articles_category() { Category articles_category() {
...@@ -384,12 +394,9 @@ class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase { ...@@ -384,12 +394,9 @@ class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase {
protected: protected:
const GURL& test_url() { return test_url_; } const GURL& test_url() { return test_url_; }
NTPSnippetsService* service() { return service_.get(); } FakeContentSuggestionsProviderObserver& observer() { return *observer_; }
MockContentSuggestionsProviderObserver& observer() { return observer_; }
MockScheduler& mock_scheduler() { return scheduler_; } MockScheduler& mock_scheduler() { return scheduler_; }
testing::NiceMock<MockImageFetcher>* image_fetcher() { NiceMock<MockImageFetcher>* image_fetcher() { return image_fetcher_; }
return image_fetcher_;
}
// Provide the json to be returned by the fake fetcher. // Provide the json to be returned by the fake fetcher.
void SetUpFetchResponse(const std::string& json) { void SetUpFetchResponse(const std::string& json) {
...@@ -397,25 +404,26 @@ class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase { ...@@ -397,25 +404,26 @@ class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase {
net::URLRequestStatus::SUCCESS); net::URLRequestStatus::SUCCESS);
} }
void LoadFromJSONString(const std::string& json) { void LoadFromJSONString(NTPSnippetsService* service,
const std::string& json) {
SetUpFetchResponse(json); SetUpFetchResponse(json);
service()->FetchSnippets(true); service->FetchSnippets(true);
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
} }
private: private:
variations::testing::VariationParamsManager params_manager_;
test::NTPSnippetsTestUtils utils_;
base::MessageLoop message_loop_; base::MessageLoop message_loop_;
FailingFakeURLFetcherFactory failing_url_fetcher_factory_; FailingFakeURLFetcherFactory failing_url_fetcher_factory_;
// Instantiation of factory automatically sets itself as URLFetcher's factory. // Instantiation of factory automatically sets itself as URLFetcher's factory.
net::FakeURLFetcherFactory fake_url_fetcher_factory_; net::FakeURLFetcherFactory fake_url_fetcher_factory_;
const GURL test_url_; const GURL test_url_;
std::unique_ptr<OAuth2TokenService> fake_token_service_; std::unique_ptr<OAuth2TokenService> fake_token_service_;
MockScheduler scheduler_; NiceMock<MockScheduler> scheduler_;
MockContentSuggestionsProviderObserver observer_; std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_;
CategoryFactory category_factory_; CategoryFactory category_factory_;
testing::NiceMock<MockImageFetcher>* image_fetcher_; NiceMock<MockImageFetcher>* image_fetcher_;
// Last so that the dependencies are deleted after the service.
std::unique_ptr<NTPSnippetsService> service_;
base::ScopedTempDir database_dir_; base::ScopedTempDir database_dir_;
...@@ -423,52 +431,68 @@ class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase { ...@@ -423,52 +431,68 @@ class NTPSnippetsServiceTest : public test::NTPSnippetsTestBase {
}; };
TEST_F(NTPSnippetsServiceTest, ScheduleOnStart) { TEST_F(NTPSnippetsServiceTest, ScheduleOnStart) {
// SetUp() checks that Schedule is called. EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
auto service = MakeSnippetsService();
// When we have no snippets are all, loading the service initiates a fetch. // When we have no snippets are all, loading the service initiates a fetch.
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
ASSERT_EQ("OK", service()->snippets_fetcher()->last_status()); ASSERT_EQ("OK", service->snippets_fetcher()->last_status());
} }
TEST_F(NTPSnippetsServiceTest, Full) { TEST_F(NTPSnippetsServiceTest, Full) {
std::string json_str(GetTestJson({GetSnippet()})); std::string json_str(GetTestJson({GetSnippet()}));
LoadFromJSONString(json_str); EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); auto service = MakeSnippetsService();
const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
EXPECT_EQ(snippet.id(), kSnippetUrl); LoadFromJSONString(service.get(), json_str);
EXPECT_EQ(snippet.title(), kSnippetTitle); ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
EXPECT_EQ(snippet.snippet(), kSnippetText); SizeIs(1));
EXPECT_EQ(snippet.salient_image_url(), GURL(kSnippetSalientImage)); ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
EXPECT_EQ(GetDefaultCreationTime(), snippet.publish_date()); const ContentSuggestion& suggestion =
EXPECT_EQ(snippet.best_source().publisher_name, kSnippetPublisherName); observer().SuggestionsForCategory(articles_category()).front();
EXPECT_EQ(snippet.best_source().amp_url, GURL(kSnippetAmpUrl));
EXPECT_EQ(MakeUniqueID(*service, kSnippetUrl), suggestion.id());
EXPECT_EQ(kSnippetTitle, base::UTF16ToUTF8(suggestion.title()));
EXPECT_EQ(kSnippetText, base::UTF16ToUTF8(suggestion.snippet_text()));
// EXPECT_EQ(GURL(kSnippetSalientImage), suggestion.salient_image_url());
EXPECT_EQ(GetDefaultCreationTime(), suggestion.publish_date());
EXPECT_EQ(kSnippetPublisherName,
base::UTF16ToUTF8(suggestion.publisher_name()));
EXPECT_EQ(GURL(kSnippetAmpUrl), suggestion.amp_url());
} }
TEST_F(NTPSnippetsServiceTest, Clear) { TEST_F(NTPSnippetsServiceTest, Clear) {
EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
auto service = MakeSnippetsService();
std::string json_str(GetTestJson({GetSnippet()})); std::string json_str(GetTestJson({GetSnippet()}));
LoadFromJSONString(json_str); LoadFromJSONString(service.get(), json_str);
EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
service()->ClearCachedSuggestions(articles_category()); service->ClearCachedSuggestions(articles_category());
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
} }
TEST_F(NTPSnippetsServiceTest, InsertAtFront) { TEST_F(NTPSnippetsServiceTest, InsertAtFront) {
EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
auto service = MakeSnippetsService();
std::string first("http://first"); std::string first("http://first");
LoadFromJSONString(GetTestJson({GetSnippetWithUrl(first)})); LoadFromJSONString(service.get(), GetTestJson({GetSnippetWithUrl(first)}));
EXPECT_THAT(service()->GetSnippetsForTesting(), ElementsAre(IdEq(first))); EXPECT_THAT(service->GetSnippetsForTesting(), ElementsAre(IdEq(first)));
std::string second("http://second"); std::string second("http://second");
LoadFromJSONString(GetTestJson({GetSnippetWithUrl(second)})); LoadFromJSONString(service.get(), GetTestJson({GetSnippetWithUrl(second)}));
// The snippet loaded last should be at the first position in the list now. // The snippet loaded last should be at the first position in the list now.
EXPECT_THAT(service()->GetSnippetsForTesting(), EXPECT_THAT(service->GetSnippetsForTesting(),
ElementsAre(IdEq(second), IdEq(first))); ElementsAre(IdEq(second), IdEq(first)));
} }
TEST_F(NTPSnippetsServiceTest, LimitNumSnippets) { TEST_F(NTPSnippetsServiceTest, LimitNumSnippets) {
auto service = MakeSnippetsService();
int max_snippet_count = NTPSnippetsService::GetMaxSnippetCountForTesting(); int max_snippet_count = NTPSnippetsService::GetMaxSnippetCountForTesting();
int snippets_per_load = max_snippet_count / 2 + 1; int snippets_per_load = max_snippet_count / 2 + 1;
char url_format[] = "http://localhost/%i"; char url_format[] = "http://localhost/%i";
...@@ -481,154 +505,158 @@ TEST_F(NTPSnippetsServiceTest, LimitNumSnippets) { ...@@ -481,154 +505,158 @@ TEST_F(NTPSnippetsServiceTest, LimitNumSnippets) {
base::StringPrintf(url_format, snippets_per_load + i))); base::StringPrintf(url_format, snippets_per_load + i)));
} }
LoadFromJSONString(GetTestJson(snippets1)); LoadFromJSONString(service.get(), GetTestJson(snippets1));
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(snippets1.size())); ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(snippets1.size()));
LoadFromJSONString(GetTestJson(snippets2)); LoadFromJSONString(service.get(), GetTestJson(snippets2));
EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(max_snippet_count)); EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(max_snippet_count));
} }
TEST_F(NTPSnippetsServiceTest, LoadInvalidJson) { TEST_F(NTPSnippetsServiceTest, LoadInvalidJson) {
LoadFromJSONString(GetTestJson({GetInvalidSnippet()})); auto service = MakeSnippetsService();
EXPECT_THAT(service()->snippets_fetcher()->last_status(),
LoadFromJSONString(service.get(), GetTestJson({GetInvalidSnippet()}));
EXPECT_THAT(service->snippets_fetcher()->last_status(),
StartsWith("Received invalid JSON")); StartsWith("Received invalid JSON"));
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
} }
TEST_F(NTPSnippetsServiceTest, LoadInvalidJsonWithExistingSnippets) { TEST_F(NTPSnippetsServiceTest, LoadInvalidJsonWithExistingSnippets) {
LoadFromJSONString(GetTestJson({GetSnippet()})); auto service = MakeSnippetsService();
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
ASSERT_EQ("OK", service()->snippets_fetcher()->last_status());
LoadFromJSONString(GetTestJson({GetInvalidSnippet()})); LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
EXPECT_THAT(service()->snippets_fetcher()->last_status(), ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
ASSERT_EQ("OK", service->snippets_fetcher()->last_status());
LoadFromJSONString(service.get(), GetTestJson({GetInvalidSnippet()}));
EXPECT_THAT(service->snippets_fetcher()->last_status(),
StartsWith("Received invalid JSON")); StartsWith("Received invalid JSON"));
// This should not have changed the existing snippets. // This should not have changed the existing snippets.
EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
} }
TEST_F(NTPSnippetsServiceTest, LoadIncompleteJson) { TEST_F(NTPSnippetsServiceTest, LoadIncompleteJson) {
LoadFromJSONString(GetTestJson({GetIncompleteSnippet()})); auto service = MakeSnippetsService();
LoadFromJSONString(service.get(), GetTestJson({GetIncompleteSnippet()}));
EXPECT_EQ("Invalid / empty list.", EXPECT_EQ("Invalid / empty list.",
service()->snippets_fetcher()->last_status()); service->snippets_fetcher()->last_status());
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
} }
TEST_F(NTPSnippetsServiceTest, LoadIncompleteJsonWithExistingSnippets) { TEST_F(NTPSnippetsServiceTest, LoadIncompleteJsonWithExistingSnippets) {
LoadFromJSONString(GetTestJson({GetSnippet()})); auto service = MakeSnippetsService();
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
LoadFromJSONString(GetTestJson({GetIncompleteSnippet()})); LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
LoadFromJSONString(service.get(), GetTestJson({GetIncompleteSnippet()}));
EXPECT_EQ("Invalid / empty list.", EXPECT_EQ("Invalid / empty list.",
service()->snippets_fetcher()->last_status()); service->snippets_fetcher()->last_status());
// This should not have changed the existing snippets. // This should not have changed the existing snippets.
EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
} }
TEST_F(NTPSnippetsServiceTest, Dismiss) { TEST_F(NTPSnippetsServiceTest, Dismiss) {
std::vector<std::string> source_urls, publishers, amp_urls; EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(2);
source_urls.push_back(std::string("http://site.com")); auto service = MakeSnippetsService();
publishers.push_back(std::string("Source 1"));
amp_urls.push_back(std::string());
std::string json_str( std::string json_str(
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); GetTestJson({GetSnippetWithSources("http://site.com", "Source 1", "")}));
LoadFromJSONString(json_str); LoadFromJSONString(service.get(), json_str);
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
// Dismissing a non-existent snippet shouldn't do anything. // Dismissing a non-existent snippet shouldn't do anything.
service()->DismissSuggestion(MakeUniqueID("http://othersite.com")); service->DismissSuggestion(MakeUniqueID(*service, "http://othersite.com"));
EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
// Dismiss the snippet. // Dismiss the snippet.
service()->DismissSuggestion(MakeUniqueID(kSnippetUrl)); service->DismissSuggestion(MakeUniqueID(*service, kSnippetUrl));
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
// Make sure that fetching the same snippet again does not re-add it. // Make sure that fetching the same snippet again does not re-add it.
LoadFromJSONString(json_str); LoadFromJSONString(service.get(), json_str);
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
// The snippet should stay dismissed even after re-creating the service. // The snippet should stay dismissed even after re-creating the service.
RecreateSnippetsService(); ResetSnippetsService(&service);
LoadFromJSONString(json_str); LoadFromJSONString(service.get(), json_str);
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
// The snippet can be added again after clearing dismissed snippets. // The snippet can be added again after clearing dismissed snippets.
service()->ClearDismissedSuggestionsForDebugging(articles_category()); service->ClearDismissedSuggestionsForDebugging(articles_category());
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
LoadFromJSONString(json_str); LoadFromJSONString(service.get(), json_str);
EXPECT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); EXPECT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
} }
TEST_F(NTPSnippetsServiceTest, GetDismissed) { TEST_F(NTPSnippetsServiceTest, GetDismissed) {
LoadFromJSONString(GetTestJson({GetSnippet()})); auto service = MakeSnippetsService();
service()->DismissSuggestion(MakeUniqueID(kSnippetUrl)); LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
MockDismissedSuggestionsCallback callback; service->DismissSuggestion(MakeUniqueID(*service, kSnippetUrl));
EXPECT_CALL(callback, MockRun(_, _)) service->GetDismissedSuggestionsForDebugging(
.WillOnce(
Invoke([this](Category category,
std::vector<ContentSuggestion>* dismissed_suggestions) {
EXPECT_EQ(1u, dismissed_suggestions->size());
for (auto& suggestion : *dismissed_suggestions) {
EXPECT_EQ(MakeUniqueID(kSnippetUrl), suggestion.id());
}
}));
service()->GetDismissedSuggestionsForDebugging(
articles_category(), articles_category(),
base::Bind(&MockDismissedSuggestionsCallback::Run, base::Bind(
base::Unretained(&callback), articles_category())); [](NTPSnippetsService* service, NTPSnippetsServiceTest* test,
Mock::VerifyAndClearExpectations(&callback); std::vector<ContentSuggestion> dismissed_suggestions) {
EXPECT_EQ(1u, dismissed_suggestions.size());
for (auto& suggestion : dismissed_suggestions) {
EXPECT_EQ(test->MakeUniqueID(*service, kSnippetUrl),
suggestion.id());
}
},
service.get(), this));
base::RunLoop().RunUntilIdle();
// There should be no dismissed snippet after clearing the list. // There should be no dismissed snippet after clearing the list.
EXPECT_CALL(callback, MockRun(_, _)) service->ClearDismissedSuggestionsForDebugging(articles_category());
.WillOnce( service->GetDismissedSuggestionsForDebugging(
Invoke([this](Category category,
std::vector<ContentSuggestion>* dismissed_suggestions) {
EXPECT_EQ(0u, dismissed_suggestions->size());
}));
service()->ClearDismissedSuggestionsForDebugging(articles_category());
service()->GetDismissedSuggestionsForDebugging(
articles_category(), articles_category(),
base::Bind(&MockDismissedSuggestionsCallback::Run, base::Bind(
base::Unretained(&callback), articles_category())); [](NTPSnippetsService* service, NTPSnippetsServiceTest* test,
std::vector<ContentSuggestion> dismissed_suggestions) {
EXPECT_EQ(0u, dismissed_suggestions.size());
},
service.get(), this));
base::RunLoop().RunUntilIdle();
} }
TEST_F(NTPSnippetsServiceTest, CreationTimestampParseFail) { TEST_F(NTPSnippetsServiceTest, CreationTimestampParseFail) {
std::string json_str(GetTestJson({GetSnippetWithTimes( auto service = MakeSnippetsService();
"aaa1448459205",
NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()))}));
LoadFromJSONString(json_str); std::string json =
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); GetSnippetWithTimes(GetDefaultCreationTime(), GetDefaultExpirationTime());
const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); base::ReplaceFirstSubstringAfterOffset(
EXPECT_EQ(snippet.id(), kSnippetUrl); &json, 0, FormatTime(GetDefaultCreationTime()), "aaa1448459205");
EXPECT_EQ(snippet.title(), kSnippetTitle); std::string json_str(GetTestJson({json}));
EXPECT_EQ(snippet.snippet(), kSnippetText);
EXPECT_EQ(base::Time::UnixEpoch(), snippet.publish_date()); LoadFromJSONString(service.get(), json_str);
EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
} }
TEST_F(NTPSnippetsServiceTest, RemoveExpiredContent) { TEST_F(NTPSnippetsServiceTest, RemoveExpiredContent) {
auto service = MakeSnippetsService();
std::string json_str(GetTestJson({GetExpiredSnippet()})); std::string json_str(GetTestJson({GetExpiredSnippet()}));
LoadFromJSONString(json_str); LoadFromJSONString(service.get(), json_str);
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
} }
TEST_F(NTPSnippetsServiceTest, TestSingleSource) { TEST_F(NTPSnippetsServiceTest, TestSingleSource) {
std::vector<std::string> source_urls, publishers, amp_urls; auto service = MakeSnippetsService();
source_urls.push_back(std::string("http://source1.com"));
publishers.push_back(std::string("Source 1")); std::string json_str(GetTestJson({GetSnippetWithSources(
amp_urls.push_back(std::string("http://source1.amp.com")); "http://source1.com", "Source 1", "http://source1.amp.com")}));
std::string json_str(
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
LoadFromJSONString(json_str); LoadFromJSONString(service.get(), json_str);
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front(); const NTPSnippet& snippet = *service->GetSnippetsForTesting().front();
EXPECT_EQ(snippet.sources().size(), 1u); EXPECT_EQ(snippet.sources().size(), 1u);
EXPECT_EQ(snippet.id(), kSnippetUrl); EXPECT_EQ(snippet.id(), kSnippetUrl);
EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com")); EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
...@@ -637,229 +665,56 @@ TEST_F(NTPSnippetsServiceTest, TestSingleSource) { ...@@ -637,229 +665,56 @@ TEST_F(NTPSnippetsServiceTest, TestSingleSource) {
} }
TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMalformedUrl) { TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMalformedUrl) {
std::vector<std::string> source_urls, publishers, amp_urls; auto service = MakeSnippetsService();
source_urls.push_back(std::string("aaaa"));
publishers.push_back(std::string("Source 1"));
amp_urls.push_back(std::string("http://source1.amp.com"));
std::string json_str(
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
LoadFromJSONString(json_str); std::string json_str(GetTestJson({GetSnippetWithSources(
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); "ceci n'est pas un url", "Source 1", "http://source1.amp.com")}));
}
TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMissingData) { LoadFromJSONString(service.get(), json_str);
std::vector<std::string> source_urls, publishers, amp_urls; EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
source_urls.push_back(std::string("http://source1.com"));
publishers.push_back(std::string());
amp_urls.push_back(std::string());
std::string json_str(
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
LoadFromJSONString(json_str);
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty());
} }
TEST_F(NTPSnippetsServiceTest, TestMultipleSources) { TEST_F(NTPSnippetsServiceTest, TestSingleSourceWithMissingData) {
std::vector<std::string> source_urls, publishers, amp_urls; auto service = MakeSnippetsService();
source_urls.push_back(std::string("http://source1.com"));
source_urls.push_back(std::string("http://source2.com"));
publishers.push_back(std::string("Source 1"));
publishers.push_back(std::string("Source 2"));
amp_urls.push_back(std::string("http://source1.amp.com"));
amp_urls.push_back(std::string("http://source2.amp.com"));
std::string json_str(
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
LoadFromJSONString(json_str);
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
// Expect the first source to be chosen
EXPECT_EQ(snippet.sources().size(), 2u);
EXPECT_EQ(snippet.id(), kSnippetUrl);
EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com"));
}
TEST_F(NTPSnippetsServiceTest, TestMultipleIncompleteSources) {
// Set Source 2 to have no AMP url, and Source 1 to have no publisher name
// Source 2 should win since we favor publisher name over amp url
std::vector<std::string> source_urls, publishers, amp_urls;
source_urls.push_back(std::string("http://source1.com"));
source_urls.push_back(std::string("http://source2.com"));
publishers.push_back(std::string());
publishers.push_back(std::string("Source 2"));
amp_urls.push_back(std::string("http://source1.amp.com"));
amp_urls.push_back(std::string());
std::string json_str(
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)}));
LoadFromJSONString(json_str);
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
{
const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
EXPECT_EQ(snippet.sources().size(), 2u);
EXPECT_EQ(snippet.id(), kSnippetUrl);
EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com"));
EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2"));
EXPECT_EQ(snippet.best_source().amp_url, GURL());
}
service()->ClearCachedSuggestions(articles_category());
// Set Source 1 to have no AMP url, and Source 2 to have no publisher name
// Source 1 should win in this case since we prefer publisher name to AMP url
source_urls.clear();
source_urls.push_back(std::string("http://source1.com"));
source_urls.push_back(std::string("http://source2.com"));
publishers.clear();
publishers.push_back(std::string("Source 1"));
publishers.push_back(std::string());
amp_urls.clear();
amp_urls.push_back(std::string());
amp_urls.push_back(std::string("http://source2.amp.com"));
json_str =
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
LoadFromJSONString(json_str);
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
{
const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
EXPECT_EQ(snippet.sources().size(), 2u);
EXPECT_EQ(snippet.id(), kSnippetUrl);
EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
EXPECT_EQ(snippet.best_source().amp_url, GURL());
}
service()->ClearCachedSuggestions(articles_category());
// Set source 1 to have no AMP url and no source, and source 2 to only have
// amp url. There should be no snippets since we only add sources we consider
// complete
source_urls.clear();
source_urls.push_back(std::string("http://source1.com"));
source_urls.push_back(std::string("http://source2.com"));
publishers.clear();
publishers.push_back(std::string());
publishers.push_back(std::string());
amp_urls.clear();
amp_urls.push_back(std::string());
amp_urls.push_back(std::string("http://source2.amp.com"));
json_str =
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
LoadFromJSONString(json_str);
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty());
}
TEST_F(NTPSnippetsServiceTest, TestMultipleCompleteSources) {
// Test 2 complete sources, we should choose the first complete source
std::vector<std::string> source_urls, publishers, amp_urls;
source_urls.push_back(std::string("http://source1.com"));
source_urls.push_back(std::string("http://source2.com"));
source_urls.push_back(std::string("http://source3.com"));
publishers.push_back(std::string("Source 1"));
publishers.push_back(std::string());
publishers.push_back(std::string("Source 3"));
amp_urls.push_back(std::string("http://source1.amp.com"));
amp_urls.push_back(std::string("http://source2.amp.com"));
amp_urls.push_back(std::string("http://source3.amp.com"));
std::string json_str( std::string json_str(
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)})); GetTestJson({GetSnippetWithSources("http://source1.com", "", "")}));
LoadFromJSONString(json_str); LoadFromJSONString(service.get(), json_str);
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
{
const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
EXPECT_EQ(snippet.sources().size(), 3u);
EXPECT_EQ(snippet.id(), kSnippetUrl);
EXPECT_EQ(snippet.best_source().url, GURL("http://source1.com"));
EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 1"));
EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source1.amp.com"));
}
// Test 2 complete sources, we should choose the first complete source
service()->ClearCachedSuggestions(articles_category());
source_urls.clear();
source_urls.push_back(std::string("http://source1.com"));
source_urls.push_back(std::string("http://source2.com"));
source_urls.push_back(std::string("http://source3.com"));
publishers.clear();
publishers.push_back(std::string());
publishers.push_back(std::string("Source 2"));
publishers.push_back(std::string("Source 3"));
amp_urls.clear();
amp_urls.push_back(std::string("http://source1.amp.com"));
amp_urls.push_back(std::string("http://source2.amp.com"));
amp_urls.push_back(std::string("http://source3.amp.com"));
json_str =
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
LoadFromJSONString(json_str);
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
{
const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
EXPECT_EQ(snippet.sources().size(), 3u);
EXPECT_EQ(snippet.id(), kSnippetUrl);
EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com"));
EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2"));
EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source2.amp.com"));
}
// Test 3 complete sources, we should choose the first complete source
service()->ClearCachedSuggestions(articles_category());
source_urls.clear();
source_urls.push_back(std::string("http://source1.com"));
source_urls.push_back(std::string("http://source2.com"));
source_urls.push_back(std::string("http://source3.com"));
publishers.clear();
publishers.push_back(std::string("Source 1"));
publishers.push_back(std::string("Source 2"));
publishers.push_back(std::string("Source 3"));
amp_urls.clear();
amp_urls.push_back(std::string());
amp_urls.push_back(std::string("http://source2.amp.com"));
amp_urls.push_back(std::string("http://source3.amp.com"));
json_str =
GetTestJson({GetSnippetWithSources(source_urls, publishers, amp_urls)});
LoadFromJSONString(json_str);
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1));
{
const NTPSnippet& snippet = *service()->GetSnippetsForTesting().front();
EXPECT_EQ(snippet.sources().size(), 3u);
EXPECT_EQ(snippet.id(), kSnippetUrl);
EXPECT_EQ(snippet.best_source().url, GURL("http://source2.com"));
EXPECT_EQ(snippet.best_source().publisher_name, std::string("Source 2"));
EXPECT_EQ(snippet.best_source().amp_url, GURL("http://source2.amp.com"));
}
} }
TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) { TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) {
auto service = MakeSnippetsService();
base::HistogramTester tester; base::HistogramTester tester;
LoadFromJSONString(GetTestJson({GetInvalidSnippet()})); LoadFromJSONString(service.get(), GetTestJson({GetInvalidSnippet()}));
EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
ElementsAre(base::Bucket(/*min=*/0, /*count=*/1))); ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
// Invalid JSON shouldn't contribute to NumArticlesFetched. // Invalid JSON shouldn't contribute to NumArticlesFetched.
EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"), EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
IsEmpty()); IsEmpty());
// Valid JSON with empty list. // Valid JSON with empty list.
LoadFromJSONString(GetTestJson(std::vector<std::string>())); LoadFromJSONString(service.get(), GetTestJson(std::vector<std::string>()));
EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
ElementsAre(base::Bucket(/*min=*/0, /*count=*/2))); ElementsAre(base::Bucket(/*min=*/0, /*count=*/2)));
EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"), EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
ElementsAre(base::Bucket(/*min=*/0, /*count=*/1))); ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
// Snippet list should be populated with size 1. // Snippet list should be populated with size 1.
LoadFromJSONString(GetTestJson({GetSnippet()})); LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
ElementsAre(base::Bucket(/*min=*/0, /*count=*/2), ElementsAre(base::Bucket(/*min=*/0, /*count=*/2),
base::Bucket(/*min=*/1, /*count=*/1))); base::Bucket(/*min=*/1, /*count=*/1)));
EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"), EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
ElementsAre(base::Bucket(/*min=*/0, /*count=*/1), ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
base::Bucket(/*min=*/1, /*count=*/1))); base::Bucket(/*min=*/1, /*count=*/1)));
// Duplicate snippet shouldn't increase the list size. // Duplicate snippet shouldn't increase the list size.
LoadFromJSONString(GetTestJson({GetSnippet()})); LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
ElementsAre(base::Bucket(/*min=*/0, /*count=*/2), ElementsAre(base::Bucket(/*min=*/0, /*count=*/2),
base::Bucket(/*min=*/1, /*count=*/2))); base::Bucket(/*min=*/1, /*count=*/2)));
...@@ -869,10 +724,11 @@ TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) { ...@@ -869,10 +724,11 @@ TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) {
EXPECT_THAT( EXPECT_THAT(
tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"), tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
IsEmpty()); IsEmpty());
// Dismissing a snippet should decrease the list size. This will only be // Dismissing a snippet should decrease the list size. This will only be
// logged after the next fetch. // logged after the next fetch.
service()->DismissSuggestion(MakeUniqueID(kSnippetUrl)); service->DismissSuggestion(MakeUniqueID(*service, kSnippetUrl));
LoadFromJSONString(GetTestJson({GetSnippet()})); LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"), EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
ElementsAre(base::Bucket(/*min=*/0, /*count=*/3), ElementsAre(base::Bucket(/*min=*/0, /*count=*/3),
base::Bucket(/*min=*/1, /*count=*/2))); base::Bucket(/*min=*/1, /*count=*/2)));
...@@ -883,77 +739,109 @@ TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) { ...@@ -883,77 +739,109 @@ TEST_F(NTPSnippetsServiceTest, LogNumArticlesHistogram) {
EXPECT_THAT( EXPECT_THAT(
tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"), tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
ElementsAre(base::Bucket(/*min=*/1, /*count=*/1))); ElementsAre(base::Bucket(/*min=*/1, /*count=*/1)));
// Recreating the service and loading from prefs shouldn't count as fetched
// articles. // There is only a single, dismissed snippet in the database, so recreating
RecreateSnippetsService(); // the service will require us to re-fetch.
tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 4); tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 4);
ResetSnippetsService(&service);
EXPECT_EQ(observer().StatusForCategory(articles_category()),
CategoryStatus::AVAILABLE);
tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 5);
EXPECT_THAT(
tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
ElementsAre(base::Bucket(/*min=*/1, /*count=*/2)));
// But if there's a non-dismissed snippet in the database, recreating it
// shouldn't trigger a fetch.
LoadFromJSONString(
service.get(),
GetTestJson({GetSnippetWithUrl("http://not-dismissed.com")}));
tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 6);
ResetSnippetsService(&service);
tester.ExpectTotalCount("NewTabPage.Snippets.NumArticlesFetched", 6);
} }
TEST_F(NTPSnippetsServiceTest, DismissShouldRespectAllKnownUrls) { TEST_F(NTPSnippetsServiceTest, DismissShouldRespectAllKnownUrls) {
const std::string creation = auto service = MakeSnippetsService();
NTPSnippet::TimeToJsonString(GetDefaultCreationTime());
const std::string expiry = const base::Time creation = GetDefaultCreationTime();
NTPSnippet::TimeToJsonString(GetDefaultExpirationTime()); const base::Time expiry = GetDefaultExpirationTime();
const std::vector<std::string> source_urls = { const std::vector<std::string> source_urls = {
"http://mashable.com/2016/05/11/stolen", "http://mashable.com/2016/05/11/stolen",
"http://www.aol.com/article/2016/05/stolen-doggie", "http://www.aol.com/article/2016/05/stolen-doggie"};
"http://mashable.com/2016/05/11/stolen?utm_cid=1"}; const std::vector<std::string> publishers = {"Mashable", "AOL"};
const std::vector<std::string> publishers = {"Mashable", "AOL", "Mashable"};
const std::vector<std::string> amp_urls = { const std::vector<std::string> amp_urls = {
"http://mashable-amphtml.googleusercontent.com/1", "http://mashable-amphtml.googleusercontent.com/1",
"http://t2.gstatic.com/images?q=tbn:3",
"http://t2.gstatic.com/images?q=tbn:3"}; "http://t2.gstatic.com/images?q=tbn:3"};
// Add the snippet from the mashable domain. // Add the snippet from the mashable domain.
LoadFromJSONString(GetTestJson({GetSnippetWithUrlAndTimesAndSources( LoadFromJSONString(service.get(),
source_urls[0], creation, expiry, source_urls, publishers, amp_urls)})); GetTestJson({GetSnippetWithUrlAndTimesAndSource(
ASSERT_THAT(service()->GetSnippetsForTesting(), SizeIs(1)); source_urls, source_urls[0], creation, expiry,
publishers[0], amp_urls[0])}));
ASSERT_THAT(service->GetSnippetsForTesting(), SizeIs(1));
// Dismiss the snippet via the mashable source corpus ID. // Dismiss the snippet via the mashable source corpus ID.
service()->DismissSuggestion(MakeUniqueID(source_urls[0])); service->DismissSuggestion(MakeUniqueID(*service, source_urls[0]));
EXPECT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
// The same article from the AOL domain should now be detected as dismissed. // The same article from the AOL domain should now be detected as dismissed.
LoadFromJSONString(GetTestJson({GetSnippetWithUrlAndTimesAndSources( LoadFromJSONString(service.get(),
source_urls[1], creation, expiry, source_urls, publishers, amp_urls)})); GetTestJson({GetSnippetWithUrlAndTimesAndSource(
ASSERT_THAT(service()->GetSnippetsForTesting(), IsEmpty()); source_urls, source_urls[1], creation, expiry,
publishers[1], amp_urls[1])}));
EXPECT_THAT(service->GetSnippetsForTesting(), IsEmpty());
} }
TEST_F(NTPSnippetsServiceTest, StatusChanges) { TEST_F(NTPSnippetsServiceTest, StatusChanges) {
{
InSequence s;
EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
EXPECT_CALL(mock_scheduler(), Unschedule());
EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _));
}
auto service = MakeSnippetsService();
// Simulate user signed out // Simulate user signed out
SetUpFetchResponse(GetTestJson({GetSnippet()})); SetUpFetchResponse(GetTestJson({GetSnippet()}));
EXPECT_CALL(observer(), service->OnDisabledReasonChanged(DisabledReason::SIGNED_OUT);
OnCategoryStatusChanged(_, _, CategoryStatus::SIGNED_OUT));
service()->OnDisabledReasonChanged(DisabledReason::SIGNED_OUT);
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
EXPECT_EQ(NTPSnippetsService::State::DISABLED, service()->state_); EXPECT_THAT(observer().StatusForCategory(articles_category()),
EXPECT_THAT(service()->GetSnippetsForTesting(), Eq(CategoryStatus::SIGNED_OUT));
EXPECT_THAT(NTPSnippetsService::State::DISABLED, Eq(service->state_));
EXPECT_THAT(service->GetSnippetsForTesting(),
IsEmpty()); // No fetch should be made. IsEmpty()); // No fetch should be made.
// Simulate user sign in. The service should be ready again and load snippets. // Simulate user sign in. The service should be ready again and load snippets.
SetUpFetchResponse(GetTestJson({GetSnippet()})); SetUpFetchResponse(GetTestJson({GetSnippet()}));
EXPECT_CALL(observer(), service->OnDisabledReasonChanged(DisabledReason::NONE);
OnCategoryStatusChanged(_, _, CategoryStatus::AVAILABLE_LOADING)); EXPECT_THAT(observer().StatusForCategory(articles_category()),
EXPECT_CALL(mock_scheduler(), Schedule(_, _, _, _)).Times(1); Eq(CategoryStatus::AVAILABLE_LOADING));
service()->OnDisabledReasonChanged(DisabledReason::NONE);
EXPECT_CALL(observer(),
OnCategoryStatusChanged(_, _, CategoryStatus::AVAILABLE));
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
EXPECT_EQ(NTPSnippetsService::State::READY, service()->state_); EXPECT_THAT(observer().StatusForCategory(articles_category()),
EXPECT_FALSE(service()->GetSnippetsForTesting().empty()); Eq(CategoryStatus::AVAILABLE));
EXPECT_THAT(NTPSnippetsService::State::READY, Eq(service->state_));
EXPECT_FALSE(service->GetSnippetsForTesting().empty());
} }
TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) { TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) {
LoadFromJSONString(GetTestJson({GetSnippet()})); auto service = MakeSnippetsService();
LoadFromJSONString(service.get(), GetTestJson({GetSnippet()}));
gfx::Image image; gfx::Image image;
EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _)) MockFunction<void(const gfx::Image&)> image_fetched;
.WillOnce(testing::WithArgs<0, 2>(Invoke(ServeOneByOneImage))); {
testing::MockFunction<void(const gfx::Image&)> image_fetched; InSequence s;
EXPECT_CALL(image_fetched, Call(_)).WillOnce(testing::SaveArg<0>(&image)); EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _))
.WillOnce(WithArgs<0, 2>(Invoke(ServeOneByOneImage)));
service()->FetchSuggestionImage( EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
MakeUniqueID(kSnippetUrl), }
base::Bind(&testing::MockFunction<void(const gfx::Image&)>::Call,
service->FetchSuggestionImage(
MakeUniqueID(*service, kSnippetUrl),
base::Bind(&MockFunction<void(const gfx::Image&)>::Call,
base::Unretained(&image_fetched))); base::Unretained(&image_fetched)));
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
// Check that the image by ServeOneByOneImage is really served. // Check that the image by ServeOneByOneImage is really served.
...@@ -961,14 +849,16 @@ TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) { ...@@ -961,14 +849,16 @@ TEST_F(NTPSnippetsServiceTest, ImageReturnedWithTheSameId) {
} }
TEST_F(NTPSnippetsServiceTest, EmptyImageReturnedForNonExistentId) { TEST_F(NTPSnippetsServiceTest, EmptyImageReturnedForNonExistentId) {
auto service = MakeSnippetsService();
// Create a non-empty image so that we can test the image gets updated. // Create a non-empty image so that we can test the image gets updated.
gfx::Image image = gfx::test::CreateImage(1, 1); gfx::Image image = gfx::test::CreateImage(1, 1);
testing::MockFunction<void(const gfx::Image&)> image_fetched; MockFunction<void(const gfx::Image&)> image_fetched;
EXPECT_CALL(image_fetched, Call(_)).WillOnce(testing::SaveArg<0>(&image)); EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
service()->FetchSuggestionImage( service->FetchSuggestionImage(
MakeUniqueID(kSnippetUrl2), MakeUniqueID(*service, kSnippetUrl2),
base::Bind(&testing::MockFunction<void(const gfx::Image&)>::Call, base::Bind(&MockFunction<void(const gfx::Image&)>::Call,
base::Unretained(&image_fetched))); base::Unretained(&image_fetched)));
base::RunLoop().RunUntilIdle(); base::RunLoop().RunUntilIdle();
......
...@@ -21,48 +21,48 @@ using testing::Return; ...@@ -21,48 +21,48 @@ using testing::Return;
namespace ntp_snippets { namespace ntp_snippets {
class NTPSnippetsStatusServiceTest : public test::NTPSnippetsTestBase { class NTPSnippetsStatusServiceTest : public ::testing::Test {
public: public:
NTPSnippetsStatusServiceTest() { NTPSnippetsStatusServiceTest() {
NTPSnippetsStatusService::RegisterProfilePrefs(pref_service()->registry()); NTPSnippetsStatusService::RegisterProfilePrefs(
utils_.pref_service()->registry());
} }
void SetUp() override { std::unique_ptr<NTPSnippetsStatusService> MakeService() {
test::NTPSnippetsTestBase::SetUp(); return base::MakeUnique<NTPSnippetsStatusService>(
utils_.fake_signin_manager(), utils_.pref_service());
service_.reset(
new NTPSnippetsStatusService(fake_signin_manager(), pref_service()));
} }
protected: protected:
NTPSnippetsStatusService* service() { return service_.get(); } test::NTPSnippetsTestUtils utils_;
private:
std::unique_ptr<NTPSnippetsStatusService> service_;
}; };
TEST_F(NTPSnippetsStatusServiceTest, SigninStateCompatibility) { TEST_F(NTPSnippetsStatusServiceTest, SigninStateCompatibility) {
auto service = MakeService();
// The default test setup is signed out. // The default test setup is signed out.
EXPECT_EQ(DisabledReason::SIGNED_OUT, service()->GetDisabledReasonFromDeps()); EXPECT_EQ(DisabledReason::SIGNED_OUT, service->GetDisabledReasonFromDeps());
// Once signed in, we should be in a compatible state. // Once signed in, we should be in a compatible state.
fake_signin_manager()->SignIn("foo@bar.com"); utils_.fake_signin_manager()->SignIn("foo@bar.com");
EXPECT_EQ(DisabledReason::NONE, service()->GetDisabledReasonFromDeps()); EXPECT_EQ(DisabledReason::NONE, service->GetDisabledReasonFromDeps());
} }
TEST_F(NTPSnippetsStatusServiceTest, DisabledViaPref) { TEST_F(NTPSnippetsStatusServiceTest, DisabledViaPref) {
auto service = MakeService();
// The default test setup is signed out. // The default test setup is signed out.
ASSERT_EQ(DisabledReason::SIGNED_OUT, service()->GetDisabledReasonFromDeps()); ASSERT_EQ(DisabledReason::SIGNED_OUT, service->GetDisabledReasonFromDeps());
// Once the enabled pref is set to false, we should be disabled. // Once the enabled pref is set to false, we should be disabled.
pref_service()->SetBoolean(prefs::kEnableSnippets, false); utils_.pref_service()->SetBoolean(prefs::kEnableSnippets, false);
EXPECT_EQ(DisabledReason::EXPLICITLY_DISABLED, EXPECT_EQ(DisabledReason::EXPLICITLY_DISABLED,
service()->GetDisabledReasonFromDeps()); service->GetDisabledReasonFromDeps());
// The other dependencies shouldn't matter anymore. // The other dependencies shouldn't matter anymore.
fake_signin_manager()->SignIn("foo@bar.com"); utils_.fake_signin_manager()->SignIn("foo@bar.com");
EXPECT_EQ(DisabledReason::EXPLICITLY_DISABLED, EXPECT_EQ(DisabledReason::EXPLICITLY_DISABLED,
service()->GetDisabledReasonFromDeps()); service->GetDisabledReasonFromDeps());
} }
} // namespace ntp_snippets } // namespace ntp_snippets
...@@ -17,36 +17,36 @@ ...@@ -17,36 +17,36 @@
namespace ntp_snippets { namespace ntp_snippets {
namespace test { namespace test {
MockSyncService::MockSyncService() FakeSyncService::FakeSyncService()
: can_sync_start_(true), : can_sync_start_(true),
is_sync_active_(true), is_sync_active_(true),
configuration_done_(true), configuration_done_(true),
is_encrypt_everything_enabled_(false), is_encrypt_everything_enabled_(false),
active_data_types_(syncer::HISTORY_DELETE_DIRECTIVES) {} active_data_types_(syncer::HISTORY_DELETE_DIRECTIVES) {}
MockSyncService::~MockSyncService() {} FakeSyncService::~FakeSyncService() {}
bool MockSyncService::CanSyncStart() const { bool FakeSyncService::CanSyncStart() const {
return can_sync_start_; return can_sync_start_;
} }
bool MockSyncService::IsSyncActive() const { bool FakeSyncService::IsSyncActive() const {
return is_sync_active_; return is_sync_active_;
} }
bool MockSyncService::ConfigurationDone() const { bool FakeSyncService::ConfigurationDone() const {
return configuration_done_; return configuration_done_;
} }
bool MockSyncService::IsEncryptEverythingEnabled() const { bool FakeSyncService::IsEncryptEverythingEnabled() const {
return is_encrypt_everything_enabled_; return is_encrypt_everything_enabled_;
} }
syncer::ModelTypeSet MockSyncService::GetActiveDataTypes() const { syncer::ModelTypeSet FakeSyncService::GetActiveDataTypes() const {
return active_data_types_; return active_data_types_;
} }
NTPSnippetsTestBase::NTPSnippetsTestBase() NTPSnippetsTestUtils::NTPSnippetsTestUtils()
: pref_service_(new TestingPrefServiceSimple()) { : pref_service_(new TestingPrefServiceSimple()) {
pref_service_->registry()->RegisterStringPref(prefs::kGoogleServicesAccountId, pref_service_->registry()->RegisterStringPref(prefs::kGoogleServicesAccountId,
std::string()); std::string());
...@@ -54,18 +54,15 @@ NTPSnippetsTestBase::NTPSnippetsTestBase() ...@@ -54,18 +54,15 @@ NTPSnippetsTestBase::NTPSnippetsTestBase()
prefs::kGoogleServicesLastAccountId, std::string()); prefs::kGoogleServicesLastAccountId, std::string());
pref_service_->registry()->RegisterStringPref( pref_service_->registry()->RegisterStringPref(
prefs::kGoogleServicesLastUsername, std::string()); prefs::kGoogleServicesLastUsername, std::string());
}
NTPSnippetsTestBase::~NTPSnippetsTestBase() {}
void NTPSnippetsTestBase::SetUp() {
signin_client_.reset(new TestSigninClient(pref_service_.get())); signin_client_.reset(new TestSigninClient(pref_service_.get()));
account_tracker_.reset(new AccountTrackerService()); account_tracker_.reset(new AccountTrackerService());
mock_sync_service_.reset(new MockSyncService()); fake_sync_service_.reset(new FakeSyncService());
ResetSigninManager(); ResetSigninManager();
} }
void NTPSnippetsTestBase::ResetSigninManager() { NTPSnippetsTestUtils::~NTPSnippetsTestUtils() = default;
void NTPSnippetsTestUtils::ResetSigninManager() {
fake_signin_manager_.reset( fake_signin_manager_.reset(
new FakeSigninManagerBase(signin_client_.get(), account_tracker_.get())); new FakeSigninManagerBase(signin_client_.get(), account_tracker_.get()));
} }
......
...@@ -19,10 +19,10 @@ class TestSigninClient; ...@@ -19,10 +19,10 @@ class TestSigninClient;
namespace ntp_snippets { namespace ntp_snippets {
namespace test { namespace test {
class MockSyncService : public sync_driver::FakeSyncService { class FakeSyncService : public sync_driver::FakeSyncService {
public: public:
MockSyncService(); FakeSyncService();
~MockSyncService() override; ~FakeSyncService() override;
bool CanSyncStart() const override; bool CanSyncStart() const override;
bool IsSyncActive() const override; bool IsSyncActive() const override;
...@@ -37,18 +37,16 @@ class MockSyncService : public sync_driver::FakeSyncService { ...@@ -37,18 +37,16 @@ class MockSyncService : public sync_driver::FakeSyncService {
syncer::ModelTypeSet active_data_types_; syncer::ModelTypeSet active_data_types_;
}; };
// Common base for snippet tests, handles initializing mocks for sync and // Common utilities for snippet tests, handles initializing fakes for sync and
// signin. |SetUp()| should be called if a subclass overrides it. // signin.
class NTPSnippetsTestBase : public testing::Test { class NTPSnippetsTestUtils {
public: public:
void SetUp() override; NTPSnippetsTestUtils();
~NTPSnippetsTestUtils();
protected:
NTPSnippetsTestBase();
~NTPSnippetsTestBase() override;
void ResetSigninManager(); void ResetSigninManager();
MockSyncService* mock_sync_service() { return mock_sync_service_.get(); } FakeSyncService* fake_sync_service() { return fake_sync_service_.get(); }
FakeSigninManagerBase* fake_signin_manager() { FakeSigninManagerBase* fake_signin_manager() {
return fake_signin_manager_.get(); return fake_signin_manager_.get();
} }
...@@ -56,7 +54,7 @@ class NTPSnippetsTestBase : public testing::Test { ...@@ -56,7 +54,7 @@ class NTPSnippetsTestBase : public testing::Test {
private: private:
std::unique_ptr<FakeSigninManagerBase> fake_signin_manager_; std::unique_ptr<FakeSigninManagerBase> fake_signin_manager_;
std::unique_ptr<MockSyncService> mock_sync_service_; std::unique_ptr<FakeSyncService> fake_sync_service_;
std::unique_ptr<TestingPrefServiceSimple> pref_service_; std::unique_ptr<TestingPrefServiceSimple> pref_service_;
std::unique_ptr<TestSigninClient> signin_client_; std::unique_ptr<TestSigninClient> signin_client_;
std::unique_ptr<AccountTrackerService> account_tracker_; std::unique_ptr<AccountTrackerService> account_tracker_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment