1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289 |
- // Copyright 2015 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
- #include <limits>
- #include <map>
- #include <memory>
- #include <string>
- #include <utility>
- #include <vector>
- #include "base/bind.h"
- #include "base/command_line.h"
- #include "base/i18n/rtl.h"
- #include "base/json/json_reader.h"
- #include "base/memory/raw_ptr.h"
- #include "base/run_loop.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_util.h"
- #include "base/strings/stringprintf.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/test/metrics/histogram_tester.h"
- #include "base/test/scoped_feature_list.h"
- #include "base/test/simple_test_clock.h"
- #include "base/test/task_environment.h"
- #include "base/test/test_mock_time_task_runner.h"
- #include "base/time/default_clock.h"
- #include "base/time/tick_clock.h"
- #include "base/time/time.h"
- #include "base/timer/timer.h"
- #include "components/feed/core/shared_prefs/pref_names.h"
- #include "components/image_fetcher/core/fake_image_decoder.h"
- #include "components/image_fetcher/core/image_decoder.h"
- #include "components/image_fetcher/core/image_fetcher.h"
- #include "components/image_fetcher/core/mock_image_fetcher.h"
- #include "components/image_fetcher/core/request_metadata.h"
- #include "components/leveldb_proto/testing/fake_db.h"
- #include "components/ntp_snippets/category.h"
- #include "components/ntp_snippets/category_info.h"
- #include "components/ntp_snippets/category_rankers/category_ranker.h"
- #include "components/ntp_snippets/category_rankers/constant_category_ranker.h"
- #include "components/ntp_snippets/category_rankers/mock_category_ranker.h"
- #include "components/ntp_snippets/fake_content_suggestions_provider_observer.h"
- #include "components/ntp_snippets/features.h"
- #include "components/ntp_snippets/ntp_snippets_constants.h"
- #include "components/ntp_snippets/pref_names.h"
- #include "components/ntp_snippets/remote/json_to_categories.h"
- #include "components/ntp_snippets/remote/persistent_scheduler.h"
- #include "components/ntp_snippets/remote/proto/ntp_snippets.pb.h"
- #include "components/ntp_snippets/remote/remote_suggestion.h"
- #include "components/ntp_snippets/remote/remote_suggestion_builder.h"
- #include "components/ntp_snippets/remote/remote_suggestions_database.h"
- #include "components/ntp_snippets/remote/remote_suggestions_fetcher_impl.h"
- #include "components/ntp_snippets/remote/remote_suggestions_scheduler.h"
- #include "components/ntp_snippets/remote/remote_suggestions_status_service.h"
- #include "components/ntp_snippets/remote/remote_suggestions_status_service_impl.h"
- #include "components/ntp_snippets/remote/test_utils.h"
- #include "components/ntp_snippets/time_serialization.h"
- #include "components/ntp_snippets/user_classifier.h"
- #include "components/prefs/testing_pref_service.h"
- #include "components/strings/grit/components_strings.h"
- #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
- #include "testing/gmock/include/gmock/gmock.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #include "ui/base/l10n/l10n_util.h"
- #include "ui/gfx/geometry/size.h"
- #include "ui/gfx/image/image.h"
- #include "ui/gfx/image/image_unittest_util.h"
- using base::TestMockTimeTaskRunner;
- using image_fetcher::ImageFetcher;
- using image_fetcher::MockImageFetcher;
- using leveldb_proto::test::FakeDB;
- using ntp_snippets::test::FetchedCategoryBuilder;
- using ntp_snippets::test::RemoteSuggestionBuilder;
- using testing::_;
- using testing::AnyNumber;
- using testing::AtMost;
- using testing::Contains;
- using testing::ElementsAre;
- using testing::ElementsAreArray;
- using testing::Eq;
- using testing::Field;
- using testing::InSequence;
- using testing::Invoke;
- using testing::IsEmpty;
- using testing::Matcher;
- using testing::Mock;
- using testing::MockFunction;
- using testing::NiceMock;
- using testing::Not;
- using testing::Property;
- using testing::Return;
- using testing::SaveArg;
- using testing::SizeIs;
- using testing::StartsWith;
- using testing::StrictMock;
- using testing::WithArgs;
- namespace ntp_snippets {
- namespace {
- ACTION_P(MoveFirstArgumentPointeeTo, ptr) {
- // 0-based indexation.
- *ptr = std::move(*arg0);
- }
- ACTION_P(MoveSecondArgumentPointeeTo, ptr) {
- // 0-based indexation.
- *ptr = std::move(*arg1);
- }
- const int kMaxExcludedDismissedIds = 100;
- const base::Time::Exploded kDefaultCreationTime = {2015, 11, 4, 25, 13, 46, 45};
- const char kSuggestionUrl[] = "http://localhost/foobar";
- const char kSuggestionTitle[] = "Title";
- const char kSuggestionText[] = "Suggestion";
- const char kSuggestionPublisherName[] = "Foo News";
- const char kImageUrl[] = "http://image/image.png";
- const char kSuggestionUrl2[] = "http://foo.com/bar";
- const char16_t kTestJsonDefaultCategoryTitle[] = u"Some title";
- const int kOtherCategoryId = 2;
- const int kUnknownRemoteCategoryId = 1234;
- const int kTimeoutForRefetchWhileDisplayingSeconds = 5;
- base::Time GetDefaultCreationTime() {
- base::Time out_time;
- EXPECT_TRUE(base::Time::FromUTCExploded(kDefaultCreationTime, &out_time));
- return out_time;
- }
- base::Time GetDefaultExpirationTime() {
- return base::Time::Now() + base::Hours(1);
- }
- // TODO(vitaliii): Remove this and use RemoteSuggestionBuilder instead.
- std::unique_ptr<RemoteSuggestion> CreateTestRemoteSuggestion(
- const std::string& url) {
- SnippetProto snippet_proto;
- snippet_proto.add_ids(url);
- snippet_proto.set_title("title");
- snippet_proto.set_snippet("snippet");
- snippet_proto.set_salient_image_url(url + "p.jpg");
- snippet_proto.set_publish_date(SerializeTime(GetDefaultCreationTime()));
- snippet_proto.set_expiry_date(SerializeTime(GetDefaultExpirationTime()));
- snippet_proto.set_remote_category_id(1);
- auto* source = snippet_proto.mutable_source();
- source->set_url(url);
- source->set_publisher_name("Publisher");
- source->set_amp_url(url + "amp");
- return RemoteSuggestion::CreateFromProto(snippet_proto);
- }
- void ServeOneByOneImage(
- image_fetcher::ImageDataFetcherCallback* image_data_callback,
- image_fetcher::ImageFetcherCallback* callback) {
- std::move(*image_data_callback)
- .Run("1-by-1-image-data", image_fetcher::RequestMetadata());
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE,
- base::BindOnce(std::move(*callback), gfx::test::CreateImage(1, 1),
- image_fetcher::RequestMetadata()));
- }
- class MockScheduler : public RemoteSuggestionsScheduler {
- public:
- MOCK_METHOD1(SetProvider, void(RemoteSuggestionsProvider* provider));
- MOCK_METHOD0(OnProviderActivated, void());
- MOCK_METHOD0(OnProviderDeactivated, void());
- MOCK_METHOD0(OnSuggestionsCleared, void());
- MOCK_METHOD0(OnHistoryCleared, void());
- MOCK_METHOD0(AcquireQuotaForInteractiveFetch, bool());
- MOCK_METHOD1(OnInteractiveFetchFinished, void(Status fetch_status));
- MOCK_METHOD0(OnBrowserForegrounded, void());
- MOCK_METHOD0(OnBrowserColdStart, void());
- MOCK_METHOD0(OnSuggestionsSurfaceOpened, void());
- MOCK_METHOD0(OnPersistentSchedulerWakeUp, void());
- MOCK_METHOD0(OnBrowserUpgraded, void());
- };
- class MockRemoteSuggestionsFetcher : public RemoteSuggestionsFetcher {
- public:
- // GMock does not support movable-only types (SnippetsAvailableCallback is
- // OnceCallback), therefore, the call is redirected to a mock method with a
- // pointer to the callback.
- void FetchSnippets(const RequestParams& params,
- SnippetsAvailableCallback callback) override {
- FetchSnippets(params, &callback);
- }
- MOCK_METHOD2(FetchSnippets,
- void(const RequestParams& params,
- SnippetsAvailableCallback* callback));
- MOCK_CONST_METHOD0(GetLastStatusForDebugging, const std::string&());
- MOCK_CONST_METHOD0(GetLastJsonForDebugging, const std::string&());
- MOCK_CONST_METHOD0(WasLastFetchAuthenticatedForDebugging, bool());
- MOCK_CONST_METHOD0(GetFetchUrlForDebugging, const GURL&());
- };
- class MockRemoteSuggestionsStatusService
- : public RemoteSuggestionsStatusService {
- public:
- ~MockRemoteSuggestionsStatusService() override = default;
- MOCK_METHOD1(Init, void(const StatusChangeCallback& callback));
- MOCK_METHOD1(OnSignInStateChanged, void(bool));
- MOCK_METHOD1(OnListVisibilityToggled, void(bool));
- };
- std::string BoolToString(bool value) {
- return value ? "true" : "false";
- }
- base::Time GetDummyNow() {
- base::Time out_time;
- EXPECT_TRUE(base::Time::FromUTCString("2017-01-02T00:00:01Z", &out_time));
- return out_time;
- }
- } // namespace
- class RemoteSuggestionsProviderImplTest : public ::testing::Test {
- public:
- RemoteSuggestionsProviderImplTest()
- : category_ranker_(std::make_unique<ConstantCategoryRanker>()),
- user_classifier_(/*pref_service=*/nullptr,
- base::DefaultClock::GetInstance()),
- mock_suggestions_fetcher_(nullptr),
- image_fetcher_(nullptr),
- scheduler_(std::make_unique<NiceMock<MockScheduler>>()),
- database_(nullptr),
- timer_mock_task_runner_(
- (new TestMockTimeTaskRunner(GetDummyNow(),
- base::TimeTicks::Now()))) {
- RemoteSuggestionsProviderImpl::RegisterProfilePrefs(
- utils_.pref_service()->registry());
- feed::prefs::RegisterFeedSharedProfilePrefs(
- utils_.pref_service()->registry());
- RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry());
- }
- RemoteSuggestionsProviderImplTest(const RemoteSuggestionsProviderImplTest&) =
- delete;
- RemoteSuggestionsProviderImplTest& operator=(
- const RemoteSuggestionsProviderImplTest&) = delete;
- ~RemoteSuggestionsProviderImplTest() override {
- provider_.reset();
- observer_.reset();
- // We need to run until idle after deleting the database, because
- // ProtoDatabase deletes the actual LevelDB asynchronously on the task
- // runner. Without this, we'd get reports of memory leaks.
- RunUntilIdle();
- }
- void MakeSuggestionsProvider(
- bool use_mock_remote_suggestions_status_service) {
- MakeSuggestionsProviderWithoutInitialization(
- use_mock_remote_suggestions_status_service);
- WaitForSuggestionsProviderInitialization();
- }
- void MakeSuggestionsProviderWithoutInitialization(
- bool use_mock_remote_suggestions_status_service) {
- auto mock_suggestions_fetcher =
- std::make_unique<StrictMock<MockRemoteSuggestionsFetcher>>();
- mock_suggestions_fetcher_ = mock_suggestions_fetcher.get();
- std::unique_ptr<RemoteSuggestionsStatusService>
- remote_suggestions_status_service;
- if (use_mock_remote_suggestions_status_service) {
- auto mock_remote_suggestions_status_service =
- std::make_unique<StrictMock<MockRemoteSuggestionsStatusService>>();
- EXPECT_CALL(*mock_remote_suggestions_status_service, Init(_))
- .WillOnce(SaveArg<0>(&status_change_callback_));
- remote_suggestions_status_service =
- std::move(mock_remote_suggestions_status_service);
- } else {
- remote_suggestions_status_service =
- std::make_unique<RemoteSuggestionsStatusServiceImpl>(
- /*has_signed_in=*/false, utils_.pref_service(),
- std::vector<std::string>());
- }
- remote_suggestions_status_service_ =
- remote_suggestions_status_service.get();
- auto image_fetcher = std::make_unique<NiceMock<MockImageFetcher>>();
- image_fetcher_ = image_fetcher.get();
- ON_CALL(*image_fetcher, GetImageDecoder())
- .WillByDefault(Return(&image_decoder_));
- EXPECT_FALSE(observer_);
- observer_ = std::make_unique<FakeContentSuggestionsProviderObserver>();
- // Setup RemoteSuggestionsDatabase with fake ProtoDBs.
- auto suggestion_db =
- std::make_unique<FakeDB<SnippetProto>>(&suggestion_db_storage_);
- auto image_db =
- std::make_unique<FakeDB<SnippetImageProto>>(&image_db_storage_);
- suggestion_db_ = suggestion_db.get();
- image_db_ = image_db.get();
- auto database = std::make_unique<RemoteSuggestionsDatabase>(
- std::move(suggestion_db), std::move(image_db));
- database_ = database.get();
- suggestion_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
- image_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
- auto fetch_timeout_timer = std::make_unique<base::OneShotTimer>(
- timer_mock_task_runner_->GetMockTickClock());
- fetch_timeout_timer->SetTaskRunner(timer_mock_task_runner_);
- provider_ = std::make_unique<RemoteSuggestionsProviderImpl>(
- observer_.get(), utils_.pref_service(), "fr", category_ranker_.get(),
- scheduler_.get(), std::move(mock_suggestions_fetcher),
- std::move(image_fetcher), std::move(database),
- std::move(remote_suggestions_status_service),
- std::move(fetch_timeout_timer));
- }
- void MakeSuggestionsProviderWithoutInitializationWithStrictScheduler() {
- scheduler_ = std::make_unique<StrictMock<MockScheduler>>();
- MakeSuggestionsProviderWithoutInitialization(
- /*use_mock_remote_suggestions_status_service=*/false);
- }
- void WaitForSuggestionsProviderInitialization() {
- EXPECT_EQ(RemoteSuggestionsProviderImpl::State::NOT_INITED,
- provider_->state_);
- suggestion_db()->LoadCallback(true);
- }
- void ResetSuggestionsProvider(
- bool use_mock_remote_suggestions_status_service) {
- provider_.reset();
- observer_.reset();
- MakeSuggestionsProvider(use_mock_remote_suggestions_status_service);
- }
- void ResetSuggestionsProviderWithoutInitialization(
- bool use_mock_remote_suggestions_status_service) {
- provider_.reset();
- observer_.reset();
- MakeSuggestionsProviderWithoutInitialization(
- use_mock_remote_suggestions_status_service);
- }
- void RunUntilIdle() {
- timer_mock_task_runner_->RunUntilIdle();
- task_environment_.RunUntilIdle();
- }
- void SetCategoryRanker(std::unique_ptr<CategoryRanker> category_ranker) {
- category_ranker_ = std::move(category_ranker);
- }
- ContentSuggestion::ID MakeArticleID(const std::string& id_within_category) {
- return ContentSuggestion::ID(articles_category(), id_within_category);
- }
- Category articles_category() {
- return Category::FromKnownCategory(KnownCategories::ARTICLES);
- }
- ContentSuggestion::ID MakeOtherID(const std::string& id_within_category) {
- return ContentSuggestion::ID(Category::FromRemoteCategory(kOtherCategoryId),
- id_within_category);
- }
- FakeDB<SnippetProto>* suggestion_db() { return suggestion_db_; }
- FakeDB<SnippetImageProto>* image_db() { return image_db_; }
- RemoteSuggestionsProviderImpl* provider() { return provider_.get(); }
- MOCK_METHOD1(OnImageFetched, void(const gfx::Image&));
- protected:
- FakeContentSuggestionsProviderObserver& observer() { return *observer_; }
- StrictMock<MockRemoteSuggestionsFetcher>* mock_suggestions_fetcher() {
- return mock_suggestions_fetcher_;
- }
- // TODO(tschumann): Make this a strict-mock. We want to avoid unneccesary
- // network requests.
- NiceMock<MockImageFetcher>* image_fetcher() { return image_fetcher_; }
- image_fetcher::FakeImageDecoder* image_decoder() { return &image_decoder_; }
- PrefService* pref_service() { return utils_.pref_service(); }
- RemoteSuggestionsDatabase* database() { return database_; }
- MockScheduler* scheduler() { return scheduler_.get(); }
- void FetchTheseSuggestions(
- bool interactive_request,
- Status status,
- absl::optional<std::vector<FetchedCategory>> fetched_categories) {
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
- .RetiresOnSaturation();
- provider_->FetchSuggestions(
- interactive_request, RemoteSuggestionsProvider::FetchStatusCallback());
- std::move(snippets_callback).Run(status, std::move(fetched_categories));
- }
- void FetchMoreTheseSuggestions(
- const Category& category,
- const std::set<std::string>& known_suggestion_ids,
- FetchDoneCallback fetch_done_callback,
- Status status,
- absl::optional<std::vector<FetchedCategory>> fetched_categories) {
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
- .RetiresOnSaturation();
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- provider_->Fetch(category, known_suggestion_ids,
- std::move(fetch_done_callback));
- std::move(snippets_callback).Run(status, std::move(fetched_categories));
- }
- RemoteSuggestionsFetcher::SnippetsAvailableCallback
- FetchSuggestionsAndGetResponseCallback(
- bool interactive_request) {
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
- .RetiresOnSaturation();
- provider_->FetchSuggestions(
- interactive_request, RemoteSuggestionsProvider::FetchStatusCallback());
- return snippets_callback;
- }
- RemoteSuggestionsFetcher::SnippetsAvailableCallback
- RefetchWhileDisplayingAndGetResponseCallback() {
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
- .RetiresOnSaturation();
- provider_->RefetchWhileDisplaying(
- RemoteSuggestionsProvider::FetchStatusCallback());
- return snippets_callback;
- }
- RemoteSuggestionsFetcher::SnippetsAvailableCallback
- ReloadSuggestionsAndGetResponseCallback() {
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
- .RetiresOnSaturation();
- provider_->ReloadSuggestions();
- return snippets_callback;
- }
- void ChangeRemoteSuggestionsStatus(RemoteSuggestionsStatus old_status,
- RemoteSuggestionsStatus new_status) {
- EXPECT_FALSE(status_change_callback_.is_null());
- status_change_callback_.Run(old_status, new_status);
- }
- void SetOrderNewRemoteCategoriesBasedOnArticlesCategoryParam(bool value) {
- scoped_feature_list_.Reset();
- scoped_feature_list_.InitAndEnableFeatureWithParameters(
- kArticleSuggestionsFeature,
- {{"order_new_remote_categories_based_on_articles_category",
- value ? "true" : "false"}});
- }
- void SetTriggeringNotificationsAndSubscriptionParams(
- bool fetched_notifications_enabled,
- bool pushed_notifications_enabled,
- bool subscribe_signed_in,
- bool subscribe_signed_out) {
- scoped_feature_list_.Reset();
- scoped_feature_list_.InitAndEnableFeatureWithParameters(
- kNotificationsFeature,
- {
- {"enable_fetched_suggestions_notifications",
- BoolToString(fetched_notifications_enabled)},
- {"enable_pushed_suggestions_notifications",
- BoolToString(pushed_notifications_enabled)},
- {"enable_signed_in_users_subscription_for_pushed_suggestions",
- BoolToString(subscribe_signed_in)},
- {"enable_signed_out_users_subscription_for_pushed_suggestions",
- BoolToString(subscribe_signed_out)},
- });
- }
- void SetFetchedNotificationsParams(bool enable, bool force) {
- scoped_feature_list_.Reset();
- scoped_feature_list_.InitAndEnableFeatureWithParameters(
- kNotificationsFeature,
- {
- {"enable_fetched_suggestions_notifications", BoolToString(enable)},
- {"force_fetched_suggestions_notifications", BoolToString(force)},
- });
- }
- void SetFetchMoreSuggestionsCount(int count) {
- scoped_feature_list_.Reset();
- scoped_feature_list_.InitAndEnableFeatureWithParameters(
- kArticleSuggestionsFeature,
- {{"fetch_more_suggestions_count", base::NumberToString(count)}});
- }
- void FastForwardBy(const base::TimeDelta& delta) {
- timer_mock_task_runner_->FastForwardBy(delta);
- }
- gfx::Image FetchImage(const ContentSuggestion::ID& suggestion_id) {
- gfx::Image result;
- provider_->FetchSuggestionImage(
- suggestion_id,
- base::BindOnce([](gfx::Image* output,
- const gfx::Image& loaded) { *output = loaded; },
- &result));
- image_db_->GetCallback(true);
- RunUntilIdle();
- return result;
- }
- private:
- std::unique_ptr<RemoteSuggestionsProviderImpl> provider_;
- base::test::ScopedFeatureList scoped_feature_list_;
- test::RemoteSuggestionsTestUtils utils_;
- std::unique_ptr<CategoryRanker> category_ranker_;
- UserClassifier user_classifier_;
- std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_;
- raw_ptr<StrictMock<MockRemoteSuggestionsFetcher>> mock_suggestions_fetcher_;
- raw_ptr<NiceMock<MockImageFetcher>> image_fetcher_;
- image_fetcher::FakeImageDecoder image_decoder_;
- std::unique_ptr<MockScheduler> scheduler_;
- raw_ptr<RemoteSuggestionsStatusService> remote_suggestions_status_service_;
- base::test::TaskEnvironment task_environment_;
- RemoteSuggestionsStatusService::StatusChangeCallback status_change_callback_;
- raw_ptr<RemoteSuggestionsDatabase> database_;
- std::map<std::string, SnippetProto> suggestion_db_storage_;
- std::map<std::string, SnippetImageProto> image_db_storage_;
- // Owned by |database_|.
- raw_ptr<FakeDB<SnippetProto>> suggestion_db_;
- raw_ptr<FakeDB<SnippetImageProto>> image_db_;
- scoped_refptr<TestMockTimeTaskRunner> timer_mock_task_runner_;
- };
- TEST_F(RemoteSuggestionsProviderImplTest, Full) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .AddId(kSuggestionUrl)
- .SetTitle(kSuggestionTitle)
- .SetSnippet(kSuggestionText)
- .SetImageUrl(kImageUrl)
- .SetPublishDate(GetDefaultCreationTime())
- .SetPublisher(kSuggestionPublisherName))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- const ContentSuggestion& suggestion =
- observer().SuggestionsForCategory(articles_category()).front();
- EXPECT_EQ(MakeArticleID(kSuggestionUrl), suggestion.id());
- EXPECT_EQ(kSuggestionTitle, base::UTF16ToUTF8(suggestion.title()));
- EXPECT_EQ(kSuggestionText, base::UTF16ToUTF8(suggestion.snippet_text()));
- EXPECT_EQ(kImageUrl, suggestion.salient_image_url());
- EXPECT_EQ(GetDefaultCreationTime(), suggestion.publish_date());
- EXPECT_EQ(kSuggestionPublisherName,
- base::UTF16ToUTF8(suggestion.publisher_name()));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, CategoryTitle) {
- const std::u16string test_default_title = kTestJsonDefaultCategoryTitle;
- // Don't send an initial response -- we want to test what happens without any
- // server status.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // The articles category should be there by default, and have a title.
- CategoryInfo info_before = provider()->GetCategoryInfo(articles_category());
- ASSERT_THAT(info_before.title(), Not(IsEmpty()));
- ASSERT_THAT(info_before.title(), Not(Eq(test_default_title)));
- EXPECT_THAT(info_before.additional_action(),
- Eq(ContentSuggestionsAdditionalAction::FETCH));
- EXPECT_THAT(info_before.show_if_empty(), Eq(true));
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .SetTitle(base::UTF16ToUTF8(test_default_title))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder())
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- // The response contained a title, |kTestJsonDefaultCategoryTitle|.
- // Make sure we updated the title in the CategoryInfo.
- CategoryInfo info_with_title =
- provider()->GetCategoryInfo(articles_category());
- EXPECT_THAT(info_before.title(), Not(Eq(info_with_title.title())));
- EXPECT_THAT(test_default_title, Eq(info_with_title.title()));
- EXPECT_THAT(info_before.additional_action(),
- Eq(ContentSuggestionsAdditionalAction::FETCH));
- EXPECT_THAT(info_before.show_if_empty(), Eq(true));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, MultipleCategories) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(1))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder()
- .AddId(base::StringPrintf("%s/%d", kSuggestionUrl, 0))
- .SetTitle(kSuggestionTitle)
- .SetSnippet(kSuggestionText)
- .SetPublishDate(GetDefaultCreationTime())
- .SetPublisher(kSuggestionPublisherName))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(2))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder()
- .AddId(base::StringPrintf("%s/%d", kSuggestionUrl, 1))
- .SetTitle(kSuggestionTitle)
- .SetSnippet(kSuggestionText)
- .SetPublishDate(GetDefaultCreationTime())
- .SetPublisher(kSuggestionPublisherName))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(observer().statuses(),
- Eq(std::map<Category, CategoryStatus, Category::CompareByID>{
- {articles_category(), CategoryStatus::AVAILABLE},
- {Category::FromRemoteCategory(kOtherCategoryId),
- CategoryStatus::AVAILABLE},
- }));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(
- Category::FromRemoteCategory(kOtherCategoryId)),
- SizeIs(1));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- ASSERT_THAT(observer().SuggestionsForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)),
- SizeIs(1));
- {
- const ContentSuggestion& suggestion =
- observer().SuggestionsForCategory(articles_category()).front();
- EXPECT_EQ(MakeArticleID(std::string(kSuggestionUrl) + "/0"),
- suggestion.id());
- EXPECT_EQ(kSuggestionTitle, base::UTF16ToUTF8(suggestion.title()));
- EXPECT_EQ(kSuggestionText, base::UTF16ToUTF8(suggestion.snippet_text()));
- EXPECT_EQ(GetDefaultCreationTime(), suggestion.publish_date());
- EXPECT_EQ(kSuggestionPublisherName,
- base::UTF16ToUTF8(suggestion.publisher_name()));
- }
- {
- const ContentSuggestion& suggestion =
- observer()
- .SuggestionsForCategory(
- Category::FromRemoteCategory(kOtherCategoryId))
- .front();
- EXPECT_EQ(MakeOtherID(std::string(kSuggestionUrl) + "/1"), suggestion.id());
- EXPECT_EQ(kSuggestionTitle, base::UTF16ToUTF8(suggestion.title()));
- EXPECT_EQ(kSuggestionText, base::UTF16ToUTF8(suggestion.snippet_text()));
- EXPECT_EQ(GetDefaultCreationTime(), suggestion.publish_date());
- EXPECT_EQ(kSuggestionPublisherName,
- base::UTF16ToUTF8(suggestion.publisher_name()));
- }
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ArticleCategoryInfo) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- CategoryInfo article_info = provider()->GetCategoryInfo(articles_category());
- EXPECT_THAT(article_info.additional_action(),
- Eq(ContentSuggestionsAdditionalAction::FETCH));
- EXPECT_THAT(article_info.show_if_empty(), Eq(true));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ExperimentalCategoryInfo) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(1))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(kUnknownRemoteCategoryId))
- .SetAdditionalAction(ContentSuggestionsAdditionalAction::NONE)
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("2"))
- .Build());
- // Load data with multiple categories so that a new experimental category gets
- // registered.
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- CategoryInfo info = provider()->GetCategoryInfo(
- Category::FromRemoteCategory(kUnknownRemoteCategoryId));
- EXPECT_THAT(info.additional_action(),
- Eq(ContentSuggestionsAdditionalAction::NONE));
- EXPECT_THAT(info.show_if_empty(), Eq(false));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, AddRemoteCategoriesToCategoryRanker) {
- auto mock_ranker = std::make_unique<NiceMock<MockCategoryRanker>>();
- MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
- SetCategoryRanker(std::move(mock_ranker));
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(11))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("11"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(13))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("13"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(12))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("12"))
- .Build());
- {
- // The order of categories is determined by the order in which they are
- // added. Thus, the latter is tested here.
- InSequence s;
- EXPECT_CALL(*raw_mock_ranker,
- AppendCategoryIfNecessary(Category::FromRemoteCategory(11)));
- EXPECT_CALL(*raw_mock_ranker,
- AppendCategoryIfNecessary(Category::FromRemoteCategory(13)));
- EXPECT_CALL(*raw_mock_ranker,
- AppendCategoryIfNecessary(Category::FromRemoteCategory(12)));
- }
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- AddRemoteCategoriesToCategoryRankerRelativeToArticles) {
- SetOrderNewRemoteCategoriesBasedOnArticlesCategoryParam(true);
- auto mock_ranker = std::make_unique<NiceMock<MockCategoryRanker>>();
- MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
- SetCategoryRanker(std::move(mock_ranker));
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(14))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("14"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(13))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("13"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(1))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(12))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("12"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(11))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("11"))
- .Build());
- {
- InSequence s;
- EXPECT_CALL(*raw_mock_ranker,
- InsertCategoryBeforeIfNecessary(
- Category::FromRemoteCategory(14), articles_category()));
- EXPECT_CALL(*raw_mock_ranker,
- InsertCategoryBeforeIfNecessary(
- Category::FromRemoteCategory(13), articles_category()));
- EXPECT_CALL(*raw_mock_ranker,
- InsertCategoryAfterIfNecessary(Category::FromRemoteCategory(11),
- articles_category()));
- EXPECT_CALL(*raw_mock_ranker,
- InsertCategoryAfterIfNecessary(Category::FromRemoteCategory(12),
- articles_category()));
- }
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- }
- TEST_F(
- RemoteSuggestionsProviderImplTest,
- AddRemoteCategoriesToCategoryRankerRelativeToArticlesWithArticlesAbsent) {
- SetOrderNewRemoteCategoriesBasedOnArticlesCategoryParam(true);
- auto mock_ranker = std::make_unique<NiceMock<MockCategoryRanker>>();
- MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
- SetCategoryRanker(std::move(mock_ranker));
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(11))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("11"))
- .Build());
- EXPECT_CALL(*raw_mock_ranker, InsertCategoryBeforeIfNecessary(_, _)).Times(0);
- EXPECT_CALL(*raw_mock_ranker,
- AppendCategoryIfNecessary(Category::FromRemoteCategory(11)));
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, PersistCategoryInfos) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(kUnknownRemoteCategoryId))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("2"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_EQ(observer().StatusForCategory(articles_category()),
- CategoryStatus::AVAILABLE);
- ASSERT_EQ(observer().StatusForCategory(
- Category::FromRemoteCategory(kUnknownRemoteCategoryId)),
- CategoryStatus::AVAILABLE);
- CategoryInfo info_articles_before =
- provider()->GetCategoryInfo(articles_category());
- CategoryInfo info_unknown_before = provider()->GetCategoryInfo(
- Category::FromRemoteCategory(kUnknownRemoteCategoryId));
- base::i18n::SetICUDefaultLocale("de");
- // Recreate the provider to simulate a Chrome restart.
- ResetSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // The categories should have been restored.
- ASSERT_NE(observer().StatusForCategory(articles_category()),
- CategoryStatus::NOT_PROVIDED);
- ASSERT_NE(observer().StatusForCategory(
- Category::FromRemoteCategory(kUnknownRemoteCategoryId)),
- CategoryStatus::NOT_PROVIDED);
- EXPECT_EQ(observer().StatusForCategory(articles_category()),
- CategoryStatus::AVAILABLE);
- EXPECT_EQ(observer().StatusForCategory(
- Category::FromRemoteCategory(kUnknownRemoteCategoryId)),
- CategoryStatus::AVAILABLE);
- CategoryInfo info_articles_after =
- provider()->GetCategoryInfo(articles_category());
- CategoryInfo info_unknown_after = provider()->GetCategoryInfo(
- Category::FromRemoteCategory(kUnknownRemoteCategoryId));
- // The new articles section title should reflect the current locale, not what
- // we persisted earlier.
- EXPECT_NE(info_articles_before.title(), info_articles_after.title());
- EXPECT_EQ(
- info_articles_after.title(),
- l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER));
- EXPECT_EQ(info_unknown_before.title(), info_unknown_after.title());
- }
- TEST_F(RemoteSuggestionsProviderImplTest, PersistRemoteCategoryOrder) {
- // We create a provider with a normal ranker to store the order.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(11))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("11"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(13))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("13"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(12))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("12"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // We manually recreate the provider to simulate Chrome restart and enforce a
- // mock ranker.
- auto mock_ranker = std::make_unique<NiceMock<MockCategoryRanker>>();
- MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
- SetCategoryRanker(std::move(mock_ranker));
- // Ensure that the order is not fetched.
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(0);
- {
- // The order of categories is determined by the order in which they are
- // added. Thus, the latter is tested here.
- InSequence s;
- // Article category always exists and, therefore, it is stored in prefs too.
- EXPECT_CALL(*raw_mock_ranker,
- AppendCategoryIfNecessary(articles_category()));
- EXPECT_CALL(*raw_mock_ranker,
- AppendCategoryIfNecessary(Category::FromRemoteCategory(11)));
- EXPECT_CALL(*raw_mock_ranker,
- AppendCategoryIfNecessary(Category::FromRemoteCategory(13)));
- EXPECT_CALL(*raw_mock_ranker,
- AppendCategoryIfNecessary(Category::FromRemoteCategory(12)));
- }
- ResetSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- }
- TEST_F(RemoteSuggestionsProviderImplTest, PersistSuggestions) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(1))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("1").SetRemoteCategoryId(1))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(2))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("2").SetRemoteCategoryId(2))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- ASSERT_THAT(observer().SuggestionsForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)),
- SizeIs(1));
- // Recreate the provider to simulate a Chrome restart.
- ResetSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // The suggestions in both categories should have been restored.
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- EXPECT_THAT(observer().SuggestionsForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)),
- SizeIs(1));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ClearSuggestionsOnInit) {
- // Add suggestions.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(1))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("1").SetRemoteCategoryId(1))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(2))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("2").SetRemoteCategoryId(2))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- ASSERT_THAT(observer().SuggestionsForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)),
- SizeIs(1));
- // Reset the provider and clear the suggestions before it is inited.
- ResetSuggestionsProviderWithoutInitialization(
- /*use_mock_remote_suggestions_status_service=*/false);
- provider()->ClearCachedSuggestions();
- // The suggestions in both categories should have been cleared after the init.
- WaitForSuggestionsProviderInitialization();
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(0));
- EXPECT_THAT(observer().SuggestionsForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)),
- SizeIs(0));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, DontNotifyIfNotAvailable) {
- // Get some suggestions into the database.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(1))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
- .Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(2))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("2"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- ASSERT_THAT(observer().SuggestionsForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)),
- SizeIs(1));
- // Set the pref that disables remote suggestions.
- pref_service()->SetBoolean(feed::prefs::kEnableSnippets, false);
- // Recreate the provider to simulate a Chrome start.
- ResetSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- ASSERT_THAT(RemoteSuggestionsProviderImpl::State::DISABLED,
- Eq(provider()->state_));
- // Now the observer should not have received any suggestions.
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- IsEmpty());
- EXPECT_THAT(observer().SuggestionsForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)),
- IsEmpty());
- }
- TEST_F(RemoteSuggestionsProviderImplTest, Clear) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- provider()->ClearCachedSuggestions();
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- IsEmpty());
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ReplaceSuggestions) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::string first("http://first");
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(first))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- ElementsAre(Pointee(Property(&RemoteSuggestion::id, first))));
- std::string second("http://second");
- fetched_categories.clear();
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(second))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // The suggestions loaded last replace all that was loaded previously.
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- ElementsAre(Pointee(Property(&RemoteSuggestion::id, second))));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldResolveFetchedSuggestionThumbnail) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("id"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- ElementsAre(Pointee(Property(&RemoteSuggestion::id, "id"))));
- image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
- .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
- gfx::Image image = FetchImage(MakeArticleID("id"));
- ASSERT_FALSE(image.IsEmpty());
- EXPECT_EQ(1, image.Width());
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ShouldFetchMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("first"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- ElementsAre(Pointee(Property(&RemoteSuggestion::id, "first"))));
- auto expect_only_second_suggestion_received = base::BindOnce(
- [](Status status, std::vector<ContentSuggestion> suggestions) {
- EXPECT_THAT(suggestions, SizeIs(1));
- EXPECT_THAT(suggestions[0].id().id_within_category(), Eq("second"));
- });
- fetched_categories.clear();
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("second"))
- .Build());
- FetchMoreTheseSuggestions(
- articles_category(),
- /*known_suggestion_ids=*/std::set<std::string>(),
- /*fetch_done_callback=*/std::move(expect_only_second_suggestion_received),
- Status::Success(), std::move(fetched_categories));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldResolveFetchedMoreSuggestionThumbnail) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("id"))
- .Build());
- auto assert_only_first_suggestion_received = base::BindOnce(
- [](Status status, std::vector<ContentSuggestion> suggestions) {
- ASSERT_THAT(suggestions, SizeIs(1));
- ASSERT_THAT(suggestions[0].id().id_within_category(), Eq("id"));
- });
- FetchMoreTheseSuggestions(
- articles_category(),
- /*known_suggestion_ids=*/std::set<std::string>(),
- /*fetch_done_callback=*/std::move(assert_only_first_suggestion_received),
- Status::Success(), std::move(fetched_categories));
- image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
- .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
- gfx::Image image = FetchImage(MakeArticleID("id"));
- ASSERT_FALSE(image.IsEmpty());
- EXPECT_EQ(1, image.Width());
- }
- // Imagine that we have surfaces A and B. The user fetches more in A, this
- // should not add any suggestions to B.
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldNotChangeSuggestionsInOtherSurfacesWhenFetchingMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Fetch a suggestion.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://old.com/"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- ElementsAre(Property(&ContentSuggestion::id,
- MakeArticleID("http://old.com/"))));
- // Now fetch more, but first prepare a response.
- fetched_categories.clear();
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://fetched-more.com/"))
- .Build());
- // The surface issuing the fetch more gets response via callback.
- auto assert_receiving_one_new_suggestion = base::BindOnce(
- [](Status status, std::vector<ContentSuggestion> suggestions) {
- ASSERT_THAT(suggestions, SizeIs(1));
- ASSERT_THAT(suggestions[0].id().id_within_category(),
- Eq("http://fetched-more.com/"));
- });
- FetchMoreTheseSuggestions(
- articles_category(),
- /*known_suggestion_ids=*/{"http://old.com/"},
- /*fetch_done_callback=*/std::move(assert_receiving_one_new_suggestion),
- Status::Success(), std::move(fetched_categories));
- // Other surfaces should remain the same.
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- ElementsAre(Property(&ContentSuggestion::id,
- MakeArticleID("http://old.com/"))));
- }
- // Imagine that we have surfaces A and B. The user fetches more in A. This
- // should not affect the next fetch more in B, i.e. assuming the same server
- // response the same suggestions must be fetched in B if the user fetches more
- // there as well.
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldNotAffectFetchMoreInOtherSurfacesWhenFetchingMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Fetch more on the surface A.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(FetchedCategory(
- articles_category(),
- BuildRemoteCategoryInfo(u"title",
- /*allow_fetching_more_results=*/true)));
- fetched_categories[0].suggestions.push_back(
- CreateTestRemoteSuggestion("http://fetched-more.com/"));
- auto assert_receiving_one_new_suggestion = base::BindOnce(
- [](Status status, std::vector<ContentSuggestion> suggestions) {
- ASSERT_THAT(suggestions, SizeIs(1));
- ASSERT_THAT(suggestions[0].id().id_within_category(),
- Eq("http://fetched-more.com/"));
- });
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
- .RetiresOnSaturation();
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- provider()->Fetch(articles_category(),
- /*known_suggestion_ids=*/std::set<std::string>(),
- std::move(assert_receiving_one_new_suggestion));
- std::move(snippets_callback)
- .Run(Status::Success(), std::move(fetched_categories));
- // Now fetch more on the surface B. The response is the same as before.
- fetched_categories.clear();
- fetched_categories.push_back(FetchedCategory(
- articles_category(),
- BuildRemoteCategoryInfo(u"title",
- /*allow_fetching_more_results=*/true)));
- fetched_categories[0].suggestions.push_back(
- CreateTestRemoteSuggestion("http://fetched-more.com/"));
- // B should receive the same suggestion as was fetched more on A.
- auto expect_receiving_same_suggestion = base::BindOnce(
- [](Status status, std::vector<ContentSuggestion> suggestions) {
- ASSERT_THAT(suggestions, SizeIs(1));
- EXPECT_THAT(suggestions[0].id().id_within_category(),
- Eq("http://fetched-more.com/"));
- });
- // The provider should not ask the fetcher to exclude the suggestion fetched
- // more on A.
- EXPECT_CALL(*mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids,
- Not(Contains("http://fetched-more.com/"))),
- _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
- .RetiresOnSaturation();
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- provider()->Fetch(articles_category(),
- /*known_suggestion_ids=*/std::set<std::string>(),
- std::move(expect_receiving_same_suggestion));
- std::move(snippets_callback)
- .Run(Status::Success(), std::move(fetched_categories));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ClearHistoryShouldDeleteArchivedSuggestions) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // First get suggestions into the archived state which happens through
- // subsequent fetches. Then we verify the entries are gone from the 'archived'
- // state by trying to load their images (and we shouldn't even know the URLs
- // anymore).
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://id-1"))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://id-2"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://new-id-1"))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://new-id-2"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // Make sure images of both batches are available. This is to sanity check our
- // assumptions for the test are right.
- EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
- .Times(2)
- .WillRepeatedly(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
- image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- gfx::Image image = FetchImage(MakeArticleID("http://id-1"));
- ASSERT_FALSE(image.IsEmpty());
- ASSERT_EQ(1, image.Width());
- image = FetchImage(MakeArticleID("http://new-id-1"));
- ASSERT_FALSE(image.IsEmpty());
- ASSERT_EQ(1, image.Width());
- provider()->ClearHistory(base::Time::UnixEpoch(), base::Time::Max(),
- base::RepeatingCallback<bool(const GURL& url)>());
- // Make sure images of both batches are gone.
- // Verify we cannot resolve the image of the new suggestions.
- image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true))))
- .Times(2);
- provider()->FetchSuggestionImage(
- MakeArticleID("http://id-1"),
- base::BindOnce(&RemoteSuggestionsProviderImplTest::OnImageFetched,
- base::Unretained(this)));
- provider()->FetchSuggestionImage(
- MakeArticleID("http://new-id-1"),
- base::BindOnce(&RemoteSuggestionsProviderImplTest::OnImageFetched,
- base::Unretained(this)));
- }
- namespace {
- // Workaround for gMock's lack of support for movable types.
- void SuggestionsLoaded(
- MockFunction<void(Status, const std::vector<ContentSuggestion>&)>* loaded,
- Status status,
- std::vector<ContentSuggestion> suggestions) {
- loaded->Call(status, suggestions);
- }
- } // namespace
- TEST_F(RemoteSuggestionsProviderImplTest, ReturnFetchRequestEmptyBeforeInit) {
- MakeSuggestionsProviderWithoutInitialization(
- /*use_mock_remote_suggestions_status_service=*/false);
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(0);
- MockFunction<void(Status, const std::vector<ContentSuggestion>&)> loaded;
- EXPECT_CALL(loaded, Call(Field(&Status::code, StatusCode::TEMPORARY_ERROR),
- IsEmpty()));
- provider()->Fetch(articles_category(), std::set<std::string>(),
- base::BindOnce(&SuggestionsLoaded, &loaded));
- RunUntilIdle();
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ReturnRefetchRequestEmptyBeforeInit) {
- MakeSuggestionsProviderWithoutInitialization(
- /*use_mock_remote_suggestions_status_service=*/false);
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(0);
- MockFunction<void(Status)> loaded;
- EXPECT_CALL(loaded, Call(Field(&Status::code, StatusCode::TEMPORARY_ERROR)));
- provider()->RefetchInTheBackground(base::BindOnce(
- &MockFunction<void(Status)>::Call, base::Unretained(&loaded)));
- RunUntilIdle();
- }
- TEST_F(RemoteSuggestionsProviderImplTest, IgnoreRefetchRequestEmptyBeforeInit) {
- MakeSuggestionsProviderWithoutInitialization(
- /*use_mock_remote_suggestions_status_service=*/false);
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(0);
- provider()->RefetchInTheBackground(
- RemoteSuggestionsProvider::FetchStatusCallback());
- RunUntilIdle();
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldForwardTemporaryErrorFromFetcher) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
- MockFunction<void(Status, const std::vector<ContentSuggestion>&)> loaded;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback));
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- provider()->Fetch(articles_category(),
- /*known_ids=*/std::set<std::string>(),
- base::BindOnce(&SuggestionsLoaded, &loaded));
- EXPECT_CALL(loaded, Call(Field(&Status::code, StatusCode::TEMPORARY_ERROR),
- IsEmpty()));
- ASSERT_FALSE(snippets_callback.is_null());
- std::move(snippets_callback)
- .Run(Status(StatusCode::TEMPORARY_ERROR, "Received invalid JSON"),
- absl::nullopt);
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldNotAddNewSuggestionsAfterFetchError) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- FetchTheseSuggestions(
- /*interactive_request=*/false,
- Status(StatusCode::TEMPORARY_ERROR, "Received invalid JSON"),
- absl::nullopt);
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- IsEmpty());
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldNotClearOldSuggestionsAfterFetchError) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(FetchedCategory(
- articles_category(),
- BuildRemoteCategoryInfo(u"title",
- /*allow_fetching_more_results=*/true)));
- fetched_categories[0].suggestions.push_back(
- CreateTestRemoteSuggestion(base::StringPrintf("http://abc.com/")));
- FetchTheseSuggestions(/*interactive_request=*/false, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(
- provider()->GetSuggestionsForTesting(articles_category()),
- ElementsAre(Pointee(Property(&RemoteSuggestion::id, "http://abc.com/"))));
- FetchTheseSuggestions(
- /*interactive_request=*/false,
- Status(StatusCode::TEMPORARY_ERROR, "Received invalid JSON"),
- absl::nullopt);
- // This should not have changed the existing suggestions.
- EXPECT_THAT(
- provider()->GetSuggestionsForTesting(articles_category()),
- ElementsAre(Pointee(Property(&RemoteSuggestion::id, "http://abc.com/"))));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, Dismiss) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- const FetchedCategoryBuilder category_builder =
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://site.com"));
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- // Load the image to store it in the database.
- EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
- .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
- image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- gfx::Image image = FetchImage(MakeArticleID("http://site.com"));
- EXPECT_FALSE(image.IsEmpty());
- EXPECT_EQ(1, image.Width());
- // Dismissing a non-existent suggestion shouldn't do anything.
- provider()->DismissSuggestion(MakeArticleID("http://othersite.com"));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- // Dismiss the suggestion.
- provider()->DismissSuggestion(MakeArticleID("http://site.com"));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- IsEmpty());
- // Verify we can still load the image of the discarded suggestion (other NTPs
- // might still reference it). This should come from the database -- no network
- // fetch necessary.
- image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- image = FetchImage(MakeArticleID("http://site.com"));
- EXPECT_FALSE(image.IsEmpty());
- EXPECT_EQ(1, image.Width());
- // Make sure that fetching the same suggestion again does not re-add it.
- fetched_categories.clear();
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- IsEmpty());
- // The suggestion should stay dismissed even after re-creating the provider.
- ResetSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- fetched_categories.clear();
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- IsEmpty());
- // The suggestion can be added again after clearing dismissed suggestions.
- provider()->ClearDismissedSuggestionsForDebugging(articles_category());
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- IsEmpty());
- fetched_categories.clear();
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, GetDismissed) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://site.com"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- provider()->DismissSuggestion(MakeArticleID("http://site.com"));
- provider()->GetDismissedSuggestionsForDebugging(
- articles_category(),
- base::BindOnce(
- [](RemoteSuggestionsProviderImpl* provider,
- RemoteSuggestionsProviderImplTest* test,
- std::vector<ContentSuggestion> dismissed_suggestions) {
- EXPECT_EQ(1u, dismissed_suggestions.size());
- for (auto& suggestion : dismissed_suggestions) {
- EXPECT_EQ(test->MakeArticleID("http://site.com"),
- suggestion.id());
- }
- },
- provider(), this));
- RunUntilIdle();
- // There should be no dismissed suggestion after clearing the list.
- provider()->ClearDismissedSuggestionsForDebugging(articles_category());
- provider()->GetDismissedSuggestionsForDebugging(
- articles_category(),
- base::BindOnce(
- [](RemoteSuggestionsProviderImpl* provider,
- RemoteSuggestionsProviderImplTest* test,
- std::vector<ContentSuggestion> dismissed_suggestions) {
- EXPECT_EQ(0u, dismissed_suggestions.size());
- },
- provider(), this));
- RunUntilIdle();
- }
- TEST_F(RemoteSuggestionsProviderImplTest, RemoveExpiredDismissedContent) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .AddId("http://first/")
- .SetExpiryDate(base::Time::Now()))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // Load the image to store it in the database.
- // TODO(tschumann): Introduce some abstraction to nicely work with image
- // fetching expectations.
- EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
- .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
- image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- gfx::Image image = FetchImage(MakeArticleID("http://first/"));
- EXPECT_FALSE(image.IsEmpty());
- EXPECT_EQ(1, image.Width());
- // Dismiss the suggestion
- provider()->DismissSuggestion(
- ContentSuggestion::ID(articles_category(), "http://first/"));
- // Load a different suggestion - this will clear the expired dismissed ones.
- fetched_categories.clear();
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://second/"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(
- provider()->GetDismissedSuggestionsForTesting(articles_category()),
- IsEmpty());
- // Verify the image got removed, too.
- EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true))));
- provider()->FetchSuggestionImage(
- MakeArticleID("http://first/"),
- base::BindOnce(&RemoteSuggestionsProviderImplTest::OnImageFetched,
- base::Unretained(this)));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ExpiredContentNotRemoved) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetExpiryDate(base::Time::Now()))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, TestSingleSource) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .AddId("http://source1.com")
- .SetUrl("http://source1.com")
- .SetPublisher("Source 1")
- .SetAmpUrl("http://source1.amp.com"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- const RemoteSuggestion& suggestion =
- *provider()->GetSuggestionsForTesting(articles_category()).front();
- EXPECT_EQ(suggestion.id(), "http://source1.com");
- EXPECT_EQ(suggestion.url(), GURL("http://source1.com"));
- EXPECT_EQ(suggestion.publisher_name(), std::string("Source 1"));
- EXPECT_EQ(suggestion.amp_url(), GURL("http://source1.amp.com"));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, TestSingleSourceWithMissingData) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetPublisher("").SetAmpUrl(""))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- IsEmpty());
- }
- TEST_F(RemoteSuggestionsProviderImplTest, LogNumArticlesHistogram) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- base::HistogramTester tester;
- FetchTheseSuggestions(/*interactive_request=*/true,
- Status(StatusCode::TEMPORARY_ERROR, "message"),
- absl::nullopt);
- // Error responses don't update the list of suggestions and shouldn't
- // influence these metrics.
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
- IsEmpty());
- // Fetch error shouldn't contribute to NumArticlesFetched.
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
- IsEmpty());
- // TODO(tschumann): The expectations in these tests have high dependencies on
- // the sequence of unrelated events. This test should be split up into
- // multiple tests.
- // Empty categories list.
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::vector<FetchedCategory>());
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
- ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
- IsEmpty());
- // Empty articles category.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder().SetCategory(articles_category()).Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
- ElementsAre(base::Bucket(/*min=*/0, /*count=*/2)));
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
- ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
- // Suggestion list should be populated with size 1.
- const FetchedCategoryBuilder category_builder =
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://site.com/"));
- fetched_categories.clear();
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
- ElementsAre(base::Bucket(/*min=*/0, /*count=*/2),
- base::Bucket(/*min=*/1, /*count=*/1)));
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
- ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
- base::Bucket(/*min=*/1, /*count=*/1)));
- // Duplicate suggestion shouldn't increase the list size.
- fetched_categories.clear();
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
- ElementsAre(base::Bucket(/*min=*/0, /*count=*/2),
- base::Bucket(/*min=*/1, /*count=*/2)));
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
- ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
- base::Bucket(/*min=*/1, /*count=*/2)));
- EXPECT_THAT(
- tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
- IsEmpty());
- // Dismissing a suggestion should decrease the list size. This will only be
- // logged after the next fetch.
- provider()->DismissSuggestion(MakeArticleID("http://site.com/"));
- fetched_categories.clear();
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
- ElementsAre(base::Bucket(/*min=*/0, /*count=*/3),
- base::Bucket(/*min=*/1, /*count=*/2)));
- // Dismissed suggestions shouldn't influence NumArticlesFetched.
- EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
- ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
- base::Bucket(/*min=*/1, /*count=*/3)));
- EXPECT_THAT(
- tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
- ElementsAre(base::Bucket(/*min=*/1, /*count=*/1)));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, DismissShouldRespectAllKnownUrls) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- const std::vector<std::string> source_urls = {
- "http://mashable.com/2016/05/11/stolen",
- "http://www.aol.com/article/2016/05/stolen-doggie"};
- const std::vector<std::string> publishers = {"Mashable", "AOL"};
- const std::vector<std::string> amp_urls = {
- "http://mashable-amphtml.googleusercontent.com/1",
- "http://t2.gstatic.com/images?q=tbn:3"};
- // Add the suggestion from the mashable domain.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .AddId(source_urls[0])
- .AddId(source_urls[1])
- .SetUrl(source_urls[0])
- .SetAmpUrl(amp_urls[0])
- .SetPublisher(publishers[0]))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(1));
- // Dismiss the suggestion via the mashable source corpus ID.
- provider()->DismissSuggestion(MakeArticleID(source_urls[0]));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- IsEmpty());
- // The same article from the AOL domain should now be detected as dismissed.
- fetched_categories.clear();
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .AddId(source_urls[0])
- .AddId(source_urls[1])
- .SetUrl(source_urls[1])
- .SetAmpUrl(amp_urls[1])
- .SetPublisher(publishers[1]))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- IsEmpty());
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ImageReturnedWithTheSameId) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId(kSuggestionUrl))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
- .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
- gfx::Image image = FetchImage(MakeArticleID(kSuggestionUrl));
- // Check that the image by ServeOneByOneImage is really served.
- EXPECT_EQ(1, image.Width());
- }
- TEST_F(RemoteSuggestionsProviderImplTest, EmptyImageReturnedForNonExistentId) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Create a non-empty image so that we can test the image gets updated.
- gfx::Image image = gfx::test::CreateImage(1, 1);
- MockFunction<void(const gfx::Image&)> image_fetched;
- EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
- provider()->FetchSuggestionImage(
- MakeArticleID("nonexistent"),
- base::BindOnce(&MockFunction<void(const gfx::Image&)>::Call,
- base::Unretained(&image_fetched)));
- RunUntilIdle();
- EXPECT_TRUE(image.IsEmpty());
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- FetchingUnknownImageIdShouldNotHitDatabase) {
- // Testing that the provider is not accessing the database is tricky.
- // Therefore, we simply put in some data making sure that if the provider asks
- // the database, it will get a wrong answer.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- ContentSuggestion::ID unknown_id = MakeArticleID(kSuggestionUrl2);
- database()->SaveImage(unknown_id.id_within_category(), "some image blob");
- // Set up the image decoder to always return the 1x1 test image.
- image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- // Create a non-empty image so that we can test the image gets updated.
- gfx::Image image = gfx::test::CreateImage(2, 2);
- MockFunction<void(const gfx::Image&)> image_fetched;
- EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
- provider()->FetchSuggestionImage(
- MakeArticleID(kSuggestionUrl2),
- base::BindOnce(&MockFunction<void(const gfx::Image&)>::Call,
- base::Unretained(&image_fetched)));
- RunUntilIdle();
- EXPECT_TRUE(image.IsEmpty()) << "got image with width: " << image.Width();
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ClearHistoryRemovesAllSuggestions) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://first/"))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://second/"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(2));
- provider()->DismissSuggestion(MakeArticleID("http://first/"));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- Not(IsEmpty()));
- ASSERT_THAT(
- provider()->GetDismissedSuggestionsForTesting(articles_category()),
- SizeIs(1));
- base::Time begin = base::Time::FromTimeT(123),
- end = base::Time::FromTimeT(456);
- base::RepeatingCallback<bool(const GURL& url)> filter;
- provider()->ClearHistory(begin, end, filter);
- // Verify that the observer received the update with the empty data as well.
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- IsEmpty());
- EXPECT_THAT(
- provider()->GetDismissedSuggestionsForTesting(articles_category()),
- IsEmpty());
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldKeepArticlesCategoryAvailableAfterClearHistory) {
- // If the provider marks that category as NOT_PROVIDED, then it won't be shown
- // at all in the UI and the user cannot load new data :-/.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- ASSERT_THAT(observer().StatusForCategory(articles_category()),
- Eq(CategoryStatus::AVAILABLE));
- provider()->ClearHistory(base::Time::UnixEpoch(), base::Time::Max(),
- base::RepeatingCallback<bool(const GURL& url)>());
- EXPECT_THAT(observer().StatusForCategory(articles_category()),
- Eq(CategoryStatus::AVAILABLE));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ShouldClearOrphanedImagesOnRestart) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId(kSuggestionUrl))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
- .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
- image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- gfx::Image image = FetchImage(MakeArticleID(kSuggestionUrl));
- EXPECT_EQ(1, image.Width());
- EXPECT_FALSE(image.IsEmpty());
- // Send new suggestion which don't include the suggestion referencing the
- // image.
- fetched_categories.clear();
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
- "http://something.com/pletely/unrelated"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // The image should still be available until a restart happens.
- EXPECT_FALSE(FetchImage(MakeArticleID(kSuggestionUrl)).IsEmpty());
- ResetSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // After the restart, the image should be garbage collected.
- EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true))));
- provider()->FetchSuggestionImage(
- MakeArticleID(kSuggestionUrl),
- base::BindOnce(&RemoteSuggestionsProviderImplTest::OnImageFetched,
- base::Unretained(this)));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldHandleMoreThanMaxSuggestionsInResponse) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- FetchedCategoryBuilder category_builder;
- category_builder.SetCategory(articles_category());
- for (int i = 0;
- i < provider()->GetMaxNormalFetchSuggestionCountForTesting() + 1; ++i) {
- category_builder.AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
- base::StringPrintf("http://localhost/suggestion-id-%d", i)));
- }
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // TODO(tschumann): We should probably trim out any additional results and
- // only serve the MaxSuggestionCount items.
- EXPECT_THAT(
- provider()->GetSuggestionsForTesting(articles_category()),
- SizeIs(provider()->GetMaxNormalFetchSuggestionCountForTesting() + 1));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- StoreLastSuccessfullBackgroundFetchTime) {
- // On initialization of the RemoteSuggestionsProviderImpl a background fetch
- // is triggered since the suggestions DB is empty. Therefore the provider must
- // not be initialized until the test clock is set.
- MakeSuggestionsProviderWithoutInitialization(
- /*use_mock_remote_suggestions_status_service=*/false);
- base::SimpleTestClock simple_test_clock;
- provider()->SetClockForTesting(&simple_test_clock);
- // Test that the preference is correctly initialized with the default value 0.
- EXPECT_EQ(
- 0, pref_service()->GetInt64(prefs::kLastSuccessfulBackgroundFetchTime));
- WaitForSuggestionsProviderInitialization();
- EXPECT_EQ(
- SerializeTime(simple_test_clock.Now()),
- pref_service()->GetInt64(prefs::kLastSuccessfulBackgroundFetchTime));
- // Advance the time and check whether the time was updated correctly after the
- // background fetch.
- simple_test_clock.Advance(base::Hours(1));
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
- .RetiresOnSaturation();
- provider()->RefetchInTheBackground(
- RemoteSuggestionsProvider::FetchStatusCallback());
- RunUntilIdle();
- std::move(snippets_callback).Run(Status::Success(), absl::nullopt);
- // TODO(jkrcal): Move together with the pref storage into the scheduler.
- EXPECT_EQ(
- SerializeTime(simple_test_clock.Now()),
- pref_service()->GetInt64(prefs::kLastSuccessfulBackgroundFetchTime));
- // TODO(markusheintz): Add a test that simulates a browser restart once the
- // scheduler refactoring is done (crbug.com/672434).
- }
- TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenReady) {
- MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
- // Should be called when becoming ready.
- EXPECT_CALL(*scheduler(), OnProviderActivated());
- WaitForSuggestionsProviderInitialization();
- }
- TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerOnError) {
- MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
- // Should be called on error.
- EXPECT_CALL(*scheduler(), OnProviderDeactivated());
- provider()->EnterState(RemoteSuggestionsProviderImpl::State::ERROR_OCCURRED);
- }
- TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenDisabled) {
- MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
- // Should be called when becoming disabled. First deactivate and only after
- // that clear the suggestions so that they are not fetched again.
- {
- InSequence s;
- EXPECT_CALL(*scheduler(), OnProviderDeactivated());
- ASSERT_THAT(provider()->ready(), Eq(false));
- EXPECT_CALL(*scheduler(), OnSuggestionsCleared());
- }
- provider()->EnterState(RemoteSuggestionsProviderImpl::State::DISABLED);
- }
- TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenHistoryCleared) {
- MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
- // Initiate the provider so that it is already READY.
- EXPECT_CALL(*scheduler(), OnProviderActivated());
- WaitForSuggestionsProviderInitialization();
- // The scheduler should be notified of clearing the history.
- EXPECT_CALL(*scheduler(), OnHistoryCleared());
- provider()->ClearHistory(GetDefaultCreationTime(), GetDefaultExpirationTime(),
- base::RepeatingCallback<bool(const GURL& url)>());
- }
- TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenSignedIn) {
- MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
- // Initiate the provider so that it is already READY.
- EXPECT_CALL(*scheduler(), OnProviderActivated());
- WaitForSuggestionsProviderInitialization();
- // The scheduler should be notified of clearing the history.
- EXPECT_CALL(*scheduler(), OnSuggestionsCleared());
- provider()->OnStatusChanged(RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN,
- RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT);
- }
- TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenSignedOut) {
- MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
- // Initiate the provider so that it is already READY.
- EXPECT_CALL(*scheduler(), OnProviderActivated());
- WaitForSuggestionsProviderInitialization();
- // The scheduler should be notified of clearing the history.
- EXPECT_CALL(*scheduler(), OnSuggestionsCleared());
- provider()->OnStatusChanged(RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT,
- RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN);
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- RestartsFetchWhenSignedInWhileFetching) {
- MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
- // Initiate the provider so that it is already READY.
- EXPECT_CALL(*scheduler(), OnProviderActivated());
- WaitForSuggestionsProviderInitialization();
- // Initiate the fetch.
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
- .RetiresOnSaturation();
- provider()->FetchSuggestions(
- /*interactive_request=*/false,
- RemoteSuggestionsProvider::FetchStatusCallback());
- // The scheduler should be notified of clearing the suggestions.
- EXPECT_CALL(*scheduler(), OnSuggestionsCleared());
- provider()->OnStatusChanged(RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT,
- RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN);
- // Once we signal the first fetch to be finished (calling snippets_callback
- // below), a new fetch should get triggered.
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(1);
- std::move(snippets_callback)
- .Run(Status::Success(), std::vector<FetchedCategory>());
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- IgnoresResultsWhenHistoryClearedWhileFetching) {
- MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
- // Initiate the provider so that it is already READY.
- EXPECT_CALL(*scheduler(), OnProviderActivated());
- WaitForSuggestionsProviderInitialization();
- // Initiate the fetch.
- RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback =
- FetchSuggestionsAndGetResponseCallback(/*interactive_request=*/false);
- // The scheduler should be notified of clearing the history.
- EXPECT_CALL(*scheduler(), OnHistoryCleared());
- provider()->ClearHistory(GetDefaultCreationTime(), GetDefaultExpirationTime(),
- base::RepeatingCallback<bool(const GURL& url)>());
- // Once the fetch finishes, the returned suggestions are ignored.
- FetchedCategoryBuilder category_builder;
- category_builder.SetCategory(articles_category());
- category_builder.AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId(base::StringPrintf("http://abc.com")));
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(category_builder.Build());
- std::move(snippets_callback)
- .Run(Status::Success(), std::move(fetched_categories));
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(0));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldExcludeKnownSuggestionsWithoutTruncatingWhenFetchingMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::set<std::string> known_ids;
- for (int i = 0; i < 200; ++i) {
- known_ids.insert(base::NumberToString(i));
- }
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- EXPECT_CALL(*mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids, known_ids), _));
- provider()->Fetch(
- articles_category(), known_ids,
- base::BindOnce([](Status status_code,
- std::vector<ContentSuggestion> suggestions) {}));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldExcludeDismissedSuggestionsWhenFetchingMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId("http://abc.com/"))
- .Build());
- ASSERT_TRUE(fetched_categories[0].suggestions[0]->is_complete());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- provider()->DismissSuggestion(MakeArticleID("http://abc.com/"));
- std::set<std::string> expected_excluded_ids({"http://abc.com/"});
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- EXPECT_CALL(
- *mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids, expected_excluded_ids),
- _));
- provider()->Fetch(
- articles_category(), std::set<std::string>(),
- base::BindOnce([](Status status_code,
- std::vector<ContentSuggestion> suggestions) {}));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldTruncateExcludedDismissedSuggestionsWhenFetchingMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- FetchedCategoryBuilder category_builder;
- category_builder.SetCategory(articles_category());
- const int kSuggestionsCount = kMaxExcludedDismissedIds + 1;
- for (int i = 0; i < kSuggestionsCount; ++i) {
- category_builder.AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
- base::StringPrintf("http://abc.com/%d/", i)));
- }
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // Dismiss them.
- for (int i = 0; i < kSuggestionsCount; ++i) {
- provider()->DismissSuggestion(
- MakeArticleID(base::StringPrintf("http://abc.com/%d/", i)));
- }
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- EXPECT_CALL(*mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids,
- SizeIs(kMaxExcludedDismissedIds)),
- _));
- provider()->Fetch(
- articles_category(), std::set<std::string>(),
- base::BindOnce([](Status status_code,
- std::vector<ContentSuggestion> suggestions) {}));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldPreferLatestExcludedDismissedSuggestionsWhenFetchingMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- FetchedCategoryBuilder category_builder;
- category_builder.SetCategory(articles_category());
- const int kSuggestionsCount = kMaxExcludedDismissedIds + 1;
- for (int i = 0; i < kSuggestionsCount; ++i) {
- category_builder.AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
- base::StringPrintf("http://abc.com/%d/", i)));
- }
- fetched_categories.push_back(category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // Dismiss them in reverse order.
- std::string first_dismissed_suggestion_id;
- for (int i = kSuggestionsCount - 1; i >= 0; --i) {
- const std::string id = base::StringPrintf("http://abc.com/%d/", i);
- provider()->DismissSuggestion(MakeArticleID(id));
- if (first_dismissed_suggestion_id.empty()) {
- first_dismissed_suggestion_id = id;
- }
- }
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- // The oldest dismissed suggestion should be absent, because there are
- // |kMaxExcludedDismissedIds| newer dismissed suggestions.
- EXPECT_CALL(*mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids,
- Not(Contains(first_dismissed_suggestion_id))),
- _));
- provider()->Fetch(
- articles_category(), std::set<std::string>(),
- base::BindOnce([](Status status_code,
- std::vector<ContentSuggestion> suggestions) {}));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldExcludeDismissedFetchedMoreSuggestions) {
- // This tests verifies that dismissing an article seen in the fetch-more state
- // (i.e., an article that has been fetched via fetch-more) will be excluded in
- // future fetches.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- FetchedCategoryBuilder category_builder;
- category_builder.SetCategory(articles_category());
- const int kSuggestionsCount = 5;
- for (int i = 0; i < kSuggestionsCount; ++i) {
- category_builder.AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
- base::StringPrintf("http://abc.com/%d", i)));
- }
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(category_builder.Build());
- FetchMoreTheseSuggestions(
- articles_category(),
- /*known_suggestion_ids=*/std::set<std::string>(),
- /*fetch_done_callback=*/
- base::BindOnce(
- [](Status status, std::vector<ContentSuggestion> suggestions) {
- ASSERT_THAT(suggestions, SizeIs(5));
- }),
- Status::Success(), std::move(fetched_categories));
- // Dismiss them.
- for (int i = 0; i < kSuggestionsCount; ++i) {
- provider()->DismissSuggestion(
- MakeArticleID(base::StringPrintf("http://abc.com/%d", i)));
- }
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- EXPECT_CALL(
- *mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids,
- ElementsAre("http://abc.com/0", "http://abc.com/1",
- "http://abc.com/2", "http://abc.com/3",
- "http://abc.com/4")),
- _));
- provider()->Fetch(
- articles_category(), std::set<std::string>(),
- base::BindOnce([](Status status_code,
- std::vector<ContentSuggestion> suggestions) {}));
- }
- TEST_F(RemoteSuggestionsProviderImplTest, ClearDismissedAfterFetchMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- FetchedCategoryBuilder category_builder;
- category_builder.SetCategory(articles_category());
- category_builder.AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId(base::StringPrintf("http://abc.com")));
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(category_builder.Build());
- FetchMoreTheseSuggestions(
- articles_category(),
- /*known_suggestion_ids=*/std::set<std::string>(),
- /*fetch_done_callback=*/
- base::BindOnce(
- [](Status status, std::vector<ContentSuggestion> suggestions) {}),
- Status::Success(), std::move(fetched_categories));
- provider()->DismissSuggestion(MakeArticleID("http://abc.com"));
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillRepeatedly(Return(true));
- // Make sure the article got marked as dismissed.
- InSequence s;
- EXPECT_CALL(*mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids,
- ElementsAre("http://abc.com")),
- _));
- provider()->Fetch(
- articles_category(), std::set<std::string>(),
- base::BindOnce([](Status status_code,
- std::vector<ContentSuggestion> suggestions) {}));
- // Clear dismissals.
- provider()->ClearDismissedSuggestionsForDebugging(articles_category());
- // Fetch and verify the article is not marked as dismissed anymore.
- EXPECT_CALL(*mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids, IsEmpty()), _));
- provider()->Fetch(
- articles_category(), std::set<std::string>(),
- base::BindOnce([](Status status_code,
- std::vector<ContentSuggestion> suggestions) {}));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldExcludeDismissedSuggestionsFromAllCategoriesWhenFetchingMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Add article suggestions.
- std::vector<FetchedCategory> fetched_categories;
- FetchedCategoryBuilder first_category_builder;
- first_category_builder.SetCategory(articles_category());
- const int kSuggestionsPerCategory = 2;
- for (int i = 0; i < kSuggestionsPerCategory; ++i) {
- first_category_builder.AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId(
- base::StringPrintf("http://abc.com/%d/", i)));
- }
- fetched_categories.push_back(first_category_builder.Build());
- // Add other category suggestions.
- FetchedCategoryBuilder second_category_builder;
- second_category_builder.SetCategory(
- Category::FromRemoteCategory(kOtherCategoryId));
- for (int i = 0; i < kSuggestionsPerCategory; ++i) {
- second_category_builder.AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().AddId(
- base::StringPrintf("http://other.com/%d/", i)));
- }
- fetched_categories.push_back(second_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // Dismiss all suggestions.
- std::set<std::string> expected_excluded_ids;
- for (int i = 0; i < kSuggestionsPerCategory; ++i) {
- const std::string article_id = base::StringPrintf("http://abc.com/%d/", i);
- provider()->DismissSuggestion(MakeArticleID(article_id));
- expected_excluded_ids.insert(article_id);
- const std::string other_id = base::StringPrintf("http://other.com/%d/", i);
- provider()->DismissSuggestion(MakeOtherID(other_id));
- expected_excluded_ids.insert(other_id);
- }
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- // Dismissed suggestions from all categories must be excluded (but not only
- // target category).
- EXPECT_CALL(
- *mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids, expected_excluded_ids),
- _));
- provider()->Fetch(
- articles_category(), std::set<std::string>(),
- base::BindOnce([](Status status_code,
- std::vector<ContentSuggestion> suggestions) {}));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldPreferTargetCategoryExcludedDismissedSuggestionsWhenFetchingMore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Add article suggestions.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(FetchedCategory(
- articles_category(),
- BuildRemoteCategoryInfo(u"title",
- /*allow_fetching_more_results=*/true)));
- for (int i = 0; i < kMaxExcludedDismissedIds; ++i) {
- fetched_categories[0].suggestions.push_back(CreateTestRemoteSuggestion(
- base::StringPrintf("http://abc.com/%d/", i)));
- }
- // Add other category suggestion.
- fetched_categories.push_back(FetchedCategory(
- Category::FromRemoteCategory(kOtherCategoryId),
- BuildRemoteCategoryInfo(u"title",
- /*allow_fetching_more_results=*/true)));
- fetched_categories[1].suggestions.push_back(
- CreateTestRemoteSuggestion("http://other.com/"));
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // Dismiss article suggestions first.
- for (int i = 0; i < kMaxExcludedDismissedIds; ++i) {
- provider()->DismissSuggestion(
- MakeArticleID(base::StringPrintf("http://abc.com/%d/", i)));
- }
- // Then dismiss other category suggestion.
- provider()->DismissSuggestion(MakeOtherID("http://other.com/"));
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- // The other category dismissed suggestion should be absent, because the fetch
- // is for articles and there are |kMaxExcludedDismissedIds| dismissed
- // suggestions there.
- EXPECT_CALL(*mock_suggestions_fetcher(),
- FetchSnippets(Field(&RequestParams::excluded_ids,
- Not(Contains("http://other.com/"))),
- _));
- provider()->Fetch(
- articles_category(), std::set<std::string>(),
- base::BindOnce([](Status status_code,
- std::vector<ContentSuggestion> suggestions) {}));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldRestoreSuggestionsFromDatabaseInSameOrderAsFetched) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .AddId("http://1.com")
- .SetUrl("http://1.com")
- .SetScore(1))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .AddId("http://3.com")
- .SetUrl("http://3.com")
- .SetScore(3))
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .AddId("http://2.com")
- .SetUrl("http://2.com")
- .SetScore(2))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_THAT(
- observer().SuggestionsForCategory(articles_category()),
- ElementsAre(
- Property(&ContentSuggestion::id, MakeArticleID("http://1.com")),
- Property(&ContentSuggestion::id, MakeArticleID("http://3.com")),
- Property(&ContentSuggestion::id, MakeArticleID("http://2.com"))));
- ResetSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- EXPECT_THAT(
- observer().SuggestionsForCategory(articles_category()),
- ElementsAre(
- Property(&ContentSuggestion::id, MakeArticleID("http://1.com")),
- Property(&ContentSuggestion::id, MakeArticleID("http://3.com")),
- Property(&ContentSuggestion::id, MakeArticleID("http://2.com"))));
- }
- // TODO(vitaliii): Remove this test (as well as the score fallback) in M64.
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldSortSuggestionsWithoutRanksByScore) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Write suggestions without ranks (i.e. with default values) directly to
- // database to simulate behaviour of M61.
- std::vector<std::unique_ptr<RemoteSuggestion>> suggestions;
- suggestions.push_back(RemoteSuggestionBuilder()
- .AddId("http://1.com")
- .SetUrl("http://1.com")
- .SetScore(1)
- .SetRank(std::numeric_limits<int>::max())
- .Build());
- suggestions.push_back(RemoteSuggestionBuilder()
- .AddId("http://3.com")
- .SetUrl("http://3.com")
- .SetScore(3)
- .SetRank(std::numeric_limits<int>::max())
- .Build());
- suggestions.push_back(RemoteSuggestionBuilder()
- .AddId("http://2.com")
- .SetUrl("http://2.com")
- .SetScore(2)
- .SetRank(std::numeric_limits<int>::max())
- .Build());
- database()->SaveSnippets(suggestions);
- ResetSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- EXPECT_THAT(
- observer().SuggestionsForCategory(articles_category()),
- ElementsAre(
- Property(&ContentSuggestion::id, MakeArticleID("http://3.com")),
- Property(&ContentSuggestion::id, MakeArticleID("http://2.com")),
- Property(&ContentSuggestion::id, MakeArticleID("http://1.com"))));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- FetchingShouldNotTriggerNotificationWhenDisabled) {
- SetTriggeringNotificationsAndSubscriptionParams(
- /*fetched_notifications_enabled=*/false,
- /*pushed_notifications_enabled=*/true,
- /*subscribe_signed_in=*/true,
- /*subscribe_signed_out=*/true);
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Fetch a suggestion triggering a notification.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder()
- .AddId("http://fetched.com/")
- .SetUrl("http://fetched.com/")
- .SetShouldNotify(true)
- .SetNotificationDeadline(GetDefaultExpirationTime()))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // The fetched suggestion should not trigger a notification because such
- // notifications are disabled.
- EXPECT_THAT(
- observer().SuggestionsForCategory(articles_category()),
- ElementsAre(Property(&ContentSuggestion::notification_extra, nullptr)));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- FetchingShouldTriggerNotificationEvenIfPrependedNotificationsDisabled) {
- SetTriggeringNotificationsAndSubscriptionParams(
- /*fetched_notifications_enabled=*/true,
- /*pushed_notifications_enabled=*/false,
- /*subscribe_signed_in=*/true,
- /*subscribe_signed_out=*/true);
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Fetch a suggestion triggering a notification.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder()
- .AddId("http://fetched.com/")
- .SetUrl("http://fetched.com/")
- .SetShouldNotify(true)
- .SetNotificationDeadline(GetDefaultExpirationTime()))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // The fetched suggestion should trigger a notification even though prepended
- // notifications are disabled.
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- ElementsAre(Property(&ContentSuggestion::notification_extra,
- Not(nullptr))));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldForceFetchedSuggestionsNotificationsWhenEnabled) {
- SetFetchedNotificationsParams(
- /*enabled=*/true, /*force=*/true);
- // Initialize the provider with two article suggestions - one with a
- // notification and one - without.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder()
- .SetUrl("http://article_with_notification.com")
- .SetShouldNotify(true)
- .SetNotificationDeadline(GetDefaultExpirationTime()))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder()
- .SetUrl("http://article_without_notification.com")
- .SetShouldNotify(false))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // For the observer, both suggestions must have notifications, because they
- // are forced via a feature param.
- EXPECT_THAT(
- observer().SuggestionsForCategory(articles_category()),
- ElementsAre(
- Property(&ContentSuggestion::notification_extra, Not(nullptr)),
- Property(&ContentSuggestion::notification_extra, Not(nullptr))));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldNotForceFetchedSuggestionsNotificationsWhenExplicitlyDisabled) {
- SetFetchedNotificationsParams(
- /*enabled=*/false, /*force=*/true);
- // Initialize the provider with an article suggestions without a notification.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder()
- .SetUrl("http://article_without_notification.com")
- .SetShouldNotify(false))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // For the observer, the suggestion still must not have a notification (even
- // though they are forced via a feature param), because the fetched
- // notifications are explicitly disabled via another feature param.
- EXPECT_THAT(
- observer().SuggestionsForCategory(articles_category()),
- ElementsAre(Property(&ContentSuggestion::notification_extra, nullptr)));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldDeleteNotFetchedCategoryWhenDeletionEnabled) {
- // Initialize the provider with two categories.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- const FetchedCategoryBuilder articles_category_builder =
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://articles.com"));
- fetched_categories.push_back(articles_category_builder.Build());
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(kOtherCategoryId))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://not_articles.com"))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)));
- // Fetch only one category - articles.
- fetched_categories.push_back(articles_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- // The other category must be gone, because it was not included in the last
- // fetch and the deletion is enabled via feature params.
- EXPECT_EQ(CategoryStatus::NOT_PROVIDED,
- observer().StatusForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldKeepFetchedCategoryWhenDeletionEnabled) {
- // Initialize the provider with two categories.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- const FetchedCategoryBuilder articles_category_builder =
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://articles.com"));
- fetched_categories.push_back(articles_category_builder.Build());
- const FetchedCategoryBuilder other_category_builder =
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(kOtherCategoryId))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://not_articles.com"));
- fetched_categories.push_back(other_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)));
- // Fetch the same two categories again.
- fetched_categories.push_back(articles_category_builder.Build());
- fetched_categories.push_back(other_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- // The other category must remain, because it was included in the last fetch.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(
- Category::FromRemoteCategory(kOtherCategoryId)));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldKeepArticleCategoryEvenWhenNotFetchedAndDeletionEnabled) {
- // Initialize the provider with two categories.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://articles.com"))
- .Build());
- const FetchedCategoryBuilder other_category_builder =
- FetchedCategoryBuilder()
- .SetCategory(Category::FromRemoteCategory(kOtherCategoryId))
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://not_articles.com"));
- fetched_categories.push_back(other_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- // Fetch only one other category.
- fetched_categories.push_back(other_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- // Articles category still must be provided (it is an exception) even though
- // it was not included in the last fetch and the deletion is enabled via
- // feature params.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- EmptySectionResponseShouldClearSection) {
- // Initialize the provider with two categories.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Set up state with present suggestions.
- // Unfortunately, we cannot create the fetched_categories inline, as some part
- // requires a copy of FetchedCategory which is not supported :-/.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .SetUrl("http://articles.com")
- .SetAmpUrl(""))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- // Next fetch returns an empty article section.
- fetched_categories.clear();
- fetched_categories.push_back(
- FetchedCategoryBuilder().SetCategory(articles_category()).Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- // Articles category still must be provided, but empty.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- IsEmpty());
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- FetchErrorShouldLeaveSuggestionsUnchangedEmptySection) {
- // Tests that we don't interpret the response value in error cases.
- // Note, that the contract of the callback guarantees that we always send
- // a null value in error cases. However, such a contract is brittle and the
- // code is not too clear on the receiving side.
- // TODO(tschumann): Establish and enforce a clear and robust error handling
- // contract.
- // Initialize the provider with two categories.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Set up state with present suggestions.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .SetUrl("http://articles.com")
- .SetAmpUrl(""))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- // Next fetch returns an error (with an empty section).
- fetched_categories.clear();
- fetched_categories.push_back(
- FetchedCategoryBuilder().SetCategory(articles_category()).Build());
- FetchTheseSuggestions(/*interactive_request=*/true,
- Status(StatusCode::TEMPORARY_ERROR, "some error"),
- std::move(fetched_categories));
- // Articles category should stay unchanged.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category())[0].url(),
- GURL("http://articles.com"));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- FetchErrorShouldLeaveSuggestionsUnchangedNullResponse) {
- // Initialize the provider with two categories.
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- // Set up state with present suggestions.
- std::vector<FetchedCategory> fetched_categories;
- fetched_categories.push_back(
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
- .SetUrl("http://articles.com")
- .SetAmpUrl(""))
- .Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- // Next fetch returns an error (with an empty section).
- FetchTheseSuggestions(/*interactive_request=*/true,
- Status(StatusCode::TEMPORARY_ERROR, "some error"),
- absl::nullopt);
- // Articles category should stay unchanged.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category())[0].url(),
- GURL("http://articles.com"));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldNotSetExclusiveCategoryWhenFetchingSuggestions) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- RequestParams params;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(SaveArg<0>(¶ms));
- provider()->FetchSuggestions(
- /*interactive_request=*/true,
- RemoteSuggestionsProvider::FetchStatusCallback());
- EXPECT_FALSE(params.exclusive_category.has_value());
- EXPECT_EQ(params.count_to_fetch, 10);
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldSetExclusiveCategoryAndCountToFetchWhenFetchingMoreSuggestions) {
- SetFetchMoreSuggestionsCount(35);
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- RequestParams params;
- EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
- .WillOnce(SaveArg<0>(¶ms));
- EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
- .WillOnce(Return(true))
- .RetiresOnSaturation();
- provider()->Fetch(
- articles_category(), /*known_suggestion_ids=*/std::set<std::string>(),
- /*fetch_done_callback=*/
- base::BindOnce(
- [](Status status_code,
- std::vector<ContentSuggestion> suggestions) -> void {}));
- ASSERT_TRUE(params.exclusive_category.has_value());
- EXPECT_EQ(*params.exclusive_category, articles_category());
- EXPECT_EQ(params.count_to_fetch, 35);
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldToggleStatusIfRefetchWhileDisplayingSucceeds) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- const FetchedCategoryBuilder articles_category_builder =
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://articles.com"));
- fetched_categories.push_back(articles_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- auto response_callback = RefetchWhileDisplayingAndGetResponseCallback();
- // The timeout does not fire earlier than it should.
- FastForwardBy(base::Seconds(kTimeoutForRefetchWhileDisplayingSeconds) -
- base::Milliseconds(1));
- // Before the results come, the status is AVAILABLE_LOADING.
- ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
- observer().StatusForCategory(articles_category()));
- fetched_categories.push_back(articles_category_builder.Build());
- std::move(response_callback)
- .Run(Status::Success(), std::move(fetched_categories));
- fetched_categories.clear();
- // After the results come, the status is flipped back to AVAILABLE.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldToggleStatusIfRefetchWhileDisplayingFails) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- const FetchedCategoryBuilder articles_category_builder =
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://articles.com"));
- fetched_categories.push_back(articles_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- auto response_callback = RefetchWhileDisplayingAndGetResponseCallback();
- // Before the results come, the status is flipped to AVAILABLE_LOADING.
- ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
- observer().StatusForCategory(articles_category()));
- // After the results come, the status is flipped back to AVAILABLE.
- std::move(response_callback)
- .Run(Status(StatusCode::TEMPORARY_ERROR, "some error"), absl::nullopt);
- // The category is available with the previous suggestion.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldToggleStatusIfRefetchWhileDisplayingTimeouts) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- const FetchedCategoryBuilder articles_category_builder =
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://articles.com"));
- fetched_categories.push_back(articles_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- // No need to finish the fetch, we ignore the response callback.
- RefetchWhileDisplayingAndGetResponseCallback();
- FastForwardBy(base::Seconds(kTimeoutForRefetchWhileDisplayingSeconds) -
- base::Milliseconds(1));
- // Before the timeout, the status is flipped to AVAILABLE_LOADING.
- ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
- observer().StatusForCategory(articles_category()));
- FastForwardBy(base::Milliseconds(2));
- // After the timeout, the status is flipped back to AVAILABLE, with the
- // previous suggestion.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldHandleCategoryDisabledBeforeTimeout) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- const FetchedCategoryBuilder articles_category_builder =
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://articles.com"));
- fetched_categories.push_back(articles_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- // No need to finish the fetch, we ignore the response callback.
- RefetchWhileDisplayingAndGetResponseCallback();
- FastForwardBy(base::Seconds(kTimeoutForRefetchWhileDisplayingSeconds) -
- base::Milliseconds(1));
- // Before the timeout, the status is flipped to AVAILABLE_LOADING.
- ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
- observer().StatusForCategory(articles_category()));
- // Disable the provider; this will put the category into the
- // CATEGORY_EXPLICITLY_DISABLED status.
- provider()->EnterState(RemoteSuggestionsProviderImpl::State::DISABLED);
- ASSERT_EQ(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED,
- observer().StatusForCategory(articles_category()));
- // Trigger the timeout. The provider should gracefully handle(i.e. not crash
- // because of) the category being disabled in the interim.
- FastForwardBy(base::Milliseconds(2));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldNotUpdateTimeoutIfRefetchWhileDisplayingCalledAgain) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- std::vector<FetchedCategory> fetched_categories;
- const FetchedCategoryBuilder articles_category_builder =
- FetchedCategoryBuilder()
- .SetCategory(articles_category())
- .AddSuggestionViaBuilder(
- RemoteSuggestionBuilder().SetUrl("http://articles.com"));
- fetched_categories.push_back(articles_category_builder.Build());
- FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
- std::move(fetched_categories));
- fetched_categories.clear();
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- // No need to finish the fetch, we ignore the response callback.
- RefetchWhileDisplayingAndGetResponseCallback();
- FastForwardBy(base::Seconds(kTimeoutForRefetchWhileDisplayingSeconds) -
- base::Milliseconds(1));
- // Another fetch does nothing to the deadline.
- RefetchWhileDisplayingAndGetResponseCallback();
- FastForwardBy(base::Milliseconds(2));
- // After the timeout, the status is flipped back to AVAILABLE, with the
- // previous suggestion.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- SizeIs(1));
- }
- TEST_F(RemoteSuggestionsProviderImplTest,
- ShouldToggleStatusIfReloadSuggestionsFails) {
- MakeSuggestionsProvider(
- /*use_mock_remote_suggestions_status_service=*/false);
- ASSERT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- auto response_callback = ReloadSuggestionsAndGetResponseCallback();
- // Before the results come, the status is flipped to AVAILABLE_LOADING.
- ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
- observer().StatusForCategory(articles_category()));
- // After the results come, the status is flipped back to AVAILABLE.
- std::move(response_callback)
- .Run(Status(StatusCode::TEMPORARY_ERROR, "some error"), absl::nullopt);
- // The category is available, with no suggestions.
- EXPECT_EQ(CategoryStatus::AVAILABLE,
- observer().StatusForCategory(articles_category()));
- EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
- IsEmpty());
- }
- } // namespace ntp_snippets
|