remote_suggestions_provider_impl_unittest.cc 137 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289
  1. // Copyright 2015 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
  5. #include <limits>
  6. #include <map>
  7. #include <memory>
  8. #include <string>
  9. #include <utility>
  10. #include <vector>
  11. #include "base/bind.h"
  12. #include "base/command_line.h"
  13. #include "base/i18n/rtl.h"
  14. #include "base/json/json_reader.h"
  15. #include "base/memory/raw_ptr.h"
  16. #include "base/run_loop.h"
  17. #include "base/strings/string_number_conversions.h"
  18. #include "base/strings/string_util.h"
  19. #include "base/strings/stringprintf.h"
  20. #include "base/strings/utf_string_conversions.h"
  21. #include "base/test/metrics/histogram_tester.h"
  22. #include "base/test/scoped_feature_list.h"
  23. #include "base/test/simple_test_clock.h"
  24. #include "base/test/task_environment.h"
  25. #include "base/test/test_mock_time_task_runner.h"
  26. #include "base/time/default_clock.h"
  27. #include "base/time/tick_clock.h"
  28. #include "base/time/time.h"
  29. #include "base/timer/timer.h"
  30. #include "components/feed/core/shared_prefs/pref_names.h"
  31. #include "components/image_fetcher/core/fake_image_decoder.h"
  32. #include "components/image_fetcher/core/image_decoder.h"
  33. #include "components/image_fetcher/core/image_fetcher.h"
  34. #include "components/image_fetcher/core/mock_image_fetcher.h"
  35. #include "components/image_fetcher/core/request_metadata.h"
  36. #include "components/leveldb_proto/testing/fake_db.h"
  37. #include "components/ntp_snippets/category.h"
  38. #include "components/ntp_snippets/category_info.h"
  39. #include "components/ntp_snippets/category_rankers/category_ranker.h"
  40. #include "components/ntp_snippets/category_rankers/constant_category_ranker.h"
  41. #include "components/ntp_snippets/category_rankers/mock_category_ranker.h"
  42. #include "components/ntp_snippets/fake_content_suggestions_provider_observer.h"
  43. #include "components/ntp_snippets/features.h"
  44. #include "components/ntp_snippets/ntp_snippets_constants.h"
  45. #include "components/ntp_snippets/pref_names.h"
  46. #include "components/ntp_snippets/remote/json_to_categories.h"
  47. #include "components/ntp_snippets/remote/persistent_scheduler.h"
  48. #include "components/ntp_snippets/remote/proto/ntp_snippets.pb.h"
  49. #include "components/ntp_snippets/remote/remote_suggestion.h"
  50. #include "components/ntp_snippets/remote/remote_suggestion_builder.h"
  51. #include "components/ntp_snippets/remote/remote_suggestions_database.h"
  52. #include "components/ntp_snippets/remote/remote_suggestions_fetcher_impl.h"
  53. #include "components/ntp_snippets/remote/remote_suggestions_scheduler.h"
  54. #include "components/ntp_snippets/remote/remote_suggestions_status_service.h"
  55. #include "components/ntp_snippets/remote/remote_suggestions_status_service_impl.h"
  56. #include "components/ntp_snippets/remote/test_utils.h"
  57. #include "components/ntp_snippets/time_serialization.h"
  58. #include "components/ntp_snippets/user_classifier.h"
  59. #include "components/prefs/testing_pref_service.h"
  60. #include "components/strings/grit/components_strings.h"
  61. #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
  62. #include "testing/gmock/include/gmock/gmock.h"
  63. #include "testing/gtest/include/gtest/gtest.h"
  64. #include "ui/base/l10n/l10n_util.h"
  65. #include "ui/gfx/geometry/size.h"
  66. #include "ui/gfx/image/image.h"
  67. #include "ui/gfx/image/image_unittest_util.h"
  68. using base::TestMockTimeTaskRunner;
  69. using image_fetcher::ImageFetcher;
  70. using image_fetcher::MockImageFetcher;
  71. using leveldb_proto::test::FakeDB;
  72. using ntp_snippets::test::FetchedCategoryBuilder;
  73. using ntp_snippets::test::RemoteSuggestionBuilder;
  74. using testing::_;
  75. using testing::AnyNumber;
  76. using testing::AtMost;
  77. using testing::Contains;
  78. using testing::ElementsAre;
  79. using testing::ElementsAreArray;
  80. using testing::Eq;
  81. using testing::Field;
  82. using testing::InSequence;
  83. using testing::Invoke;
  84. using testing::IsEmpty;
  85. using testing::Matcher;
  86. using testing::Mock;
  87. using testing::MockFunction;
  88. using testing::NiceMock;
  89. using testing::Not;
  90. using testing::Property;
  91. using testing::Return;
  92. using testing::SaveArg;
  93. using testing::SizeIs;
  94. using testing::StartsWith;
  95. using testing::StrictMock;
  96. using testing::WithArgs;
  97. namespace ntp_snippets {
  98. namespace {
  99. ACTION_P(MoveFirstArgumentPointeeTo, ptr) {
  100. // 0-based indexation.
  101. *ptr = std::move(*arg0);
  102. }
  103. ACTION_P(MoveSecondArgumentPointeeTo, ptr) {
  104. // 0-based indexation.
  105. *ptr = std::move(*arg1);
  106. }
  107. const int kMaxExcludedDismissedIds = 100;
  108. const base::Time::Exploded kDefaultCreationTime = {2015, 11, 4, 25, 13, 46, 45};
  109. const char kSuggestionUrl[] = "http://localhost/foobar";
  110. const char kSuggestionTitle[] = "Title";
  111. const char kSuggestionText[] = "Suggestion";
  112. const char kSuggestionPublisherName[] = "Foo News";
  113. const char kImageUrl[] = "http://image/image.png";
  114. const char kSuggestionUrl2[] = "http://foo.com/bar";
  115. const char16_t kTestJsonDefaultCategoryTitle[] = u"Some title";
  116. const int kOtherCategoryId = 2;
  117. const int kUnknownRemoteCategoryId = 1234;
  118. const int kTimeoutForRefetchWhileDisplayingSeconds = 5;
  119. base::Time GetDefaultCreationTime() {
  120. base::Time out_time;
  121. EXPECT_TRUE(base::Time::FromUTCExploded(kDefaultCreationTime, &out_time));
  122. return out_time;
  123. }
  124. base::Time GetDefaultExpirationTime() {
  125. return base::Time::Now() + base::Hours(1);
  126. }
  127. // TODO(vitaliii): Remove this and use RemoteSuggestionBuilder instead.
  128. std::unique_ptr<RemoteSuggestion> CreateTestRemoteSuggestion(
  129. const std::string& url) {
  130. SnippetProto snippet_proto;
  131. snippet_proto.add_ids(url);
  132. snippet_proto.set_title("title");
  133. snippet_proto.set_snippet("snippet");
  134. snippet_proto.set_salient_image_url(url + "p.jpg");
  135. snippet_proto.set_publish_date(SerializeTime(GetDefaultCreationTime()));
  136. snippet_proto.set_expiry_date(SerializeTime(GetDefaultExpirationTime()));
  137. snippet_proto.set_remote_category_id(1);
  138. auto* source = snippet_proto.mutable_source();
  139. source->set_url(url);
  140. source->set_publisher_name("Publisher");
  141. source->set_amp_url(url + "amp");
  142. return RemoteSuggestion::CreateFromProto(snippet_proto);
  143. }
  144. void ServeOneByOneImage(
  145. image_fetcher::ImageDataFetcherCallback* image_data_callback,
  146. image_fetcher::ImageFetcherCallback* callback) {
  147. std::move(*image_data_callback)
  148. .Run("1-by-1-image-data", image_fetcher::RequestMetadata());
  149. base::ThreadTaskRunnerHandle::Get()->PostTask(
  150. FROM_HERE,
  151. base::BindOnce(std::move(*callback), gfx::test::CreateImage(1, 1),
  152. image_fetcher::RequestMetadata()));
  153. }
  154. class MockScheduler : public RemoteSuggestionsScheduler {
  155. public:
  156. MOCK_METHOD1(SetProvider, void(RemoteSuggestionsProvider* provider));
  157. MOCK_METHOD0(OnProviderActivated, void());
  158. MOCK_METHOD0(OnProviderDeactivated, void());
  159. MOCK_METHOD0(OnSuggestionsCleared, void());
  160. MOCK_METHOD0(OnHistoryCleared, void());
  161. MOCK_METHOD0(AcquireQuotaForInteractiveFetch, bool());
  162. MOCK_METHOD1(OnInteractiveFetchFinished, void(Status fetch_status));
  163. MOCK_METHOD0(OnBrowserForegrounded, void());
  164. MOCK_METHOD0(OnBrowserColdStart, void());
  165. MOCK_METHOD0(OnSuggestionsSurfaceOpened, void());
  166. MOCK_METHOD0(OnPersistentSchedulerWakeUp, void());
  167. MOCK_METHOD0(OnBrowserUpgraded, void());
  168. };
  169. class MockRemoteSuggestionsFetcher : public RemoteSuggestionsFetcher {
  170. public:
  171. // GMock does not support movable-only types (SnippetsAvailableCallback is
  172. // OnceCallback), therefore, the call is redirected to a mock method with a
  173. // pointer to the callback.
  174. void FetchSnippets(const RequestParams& params,
  175. SnippetsAvailableCallback callback) override {
  176. FetchSnippets(params, &callback);
  177. }
  178. MOCK_METHOD2(FetchSnippets,
  179. void(const RequestParams& params,
  180. SnippetsAvailableCallback* callback));
  181. MOCK_CONST_METHOD0(GetLastStatusForDebugging, const std::string&());
  182. MOCK_CONST_METHOD0(GetLastJsonForDebugging, const std::string&());
  183. MOCK_CONST_METHOD0(WasLastFetchAuthenticatedForDebugging, bool());
  184. MOCK_CONST_METHOD0(GetFetchUrlForDebugging, const GURL&());
  185. };
  186. class MockRemoteSuggestionsStatusService
  187. : public RemoteSuggestionsStatusService {
  188. public:
  189. ~MockRemoteSuggestionsStatusService() override = default;
  190. MOCK_METHOD1(Init, void(const StatusChangeCallback& callback));
  191. MOCK_METHOD1(OnSignInStateChanged, void(bool));
  192. MOCK_METHOD1(OnListVisibilityToggled, void(bool));
  193. };
  194. std::string BoolToString(bool value) {
  195. return value ? "true" : "false";
  196. }
  197. base::Time GetDummyNow() {
  198. base::Time out_time;
  199. EXPECT_TRUE(base::Time::FromUTCString("2017-01-02T00:00:01Z", &out_time));
  200. return out_time;
  201. }
  202. } // namespace
  203. class RemoteSuggestionsProviderImplTest : public ::testing::Test {
  204. public:
  205. RemoteSuggestionsProviderImplTest()
  206. : category_ranker_(std::make_unique<ConstantCategoryRanker>()),
  207. user_classifier_(/*pref_service=*/nullptr,
  208. base::DefaultClock::GetInstance()),
  209. mock_suggestions_fetcher_(nullptr),
  210. image_fetcher_(nullptr),
  211. scheduler_(std::make_unique<NiceMock<MockScheduler>>()),
  212. database_(nullptr),
  213. timer_mock_task_runner_(
  214. (new TestMockTimeTaskRunner(GetDummyNow(),
  215. base::TimeTicks::Now()))) {
  216. RemoteSuggestionsProviderImpl::RegisterProfilePrefs(
  217. utils_.pref_service()->registry());
  218. feed::prefs::RegisterFeedSharedProfilePrefs(
  219. utils_.pref_service()->registry());
  220. RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry());
  221. }
  222. RemoteSuggestionsProviderImplTest(const RemoteSuggestionsProviderImplTest&) =
  223. delete;
  224. RemoteSuggestionsProviderImplTest& operator=(
  225. const RemoteSuggestionsProviderImplTest&) = delete;
  226. ~RemoteSuggestionsProviderImplTest() override {
  227. provider_.reset();
  228. observer_.reset();
  229. // We need to run until idle after deleting the database, because
  230. // ProtoDatabase deletes the actual LevelDB asynchronously on the task
  231. // runner. Without this, we'd get reports of memory leaks.
  232. RunUntilIdle();
  233. }
  234. void MakeSuggestionsProvider(
  235. bool use_mock_remote_suggestions_status_service) {
  236. MakeSuggestionsProviderWithoutInitialization(
  237. use_mock_remote_suggestions_status_service);
  238. WaitForSuggestionsProviderInitialization();
  239. }
  240. void MakeSuggestionsProviderWithoutInitialization(
  241. bool use_mock_remote_suggestions_status_service) {
  242. auto mock_suggestions_fetcher =
  243. std::make_unique<StrictMock<MockRemoteSuggestionsFetcher>>();
  244. mock_suggestions_fetcher_ = mock_suggestions_fetcher.get();
  245. std::unique_ptr<RemoteSuggestionsStatusService>
  246. remote_suggestions_status_service;
  247. if (use_mock_remote_suggestions_status_service) {
  248. auto mock_remote_suggestions_status_service =
  249. std::make_unique<StrictMock<MockRemoteSuggestionsStatusService>>();
  250. EXPECT_CALL(*mock_remote_suggestions_status_service, Init(_))
  251. .WillOnce(SaveArg<0>(&status_change_callback_));
  252. remote_suggestions_status_service =
  253. std::move(mock_remote_suggestions_status_service);
  254. } else {
  255. remote_suggestions_status_service =
  256. std::make_unique<RemoteSuggestionsStatusServiceImpl>(
  257. /*has_signed_in=*/false, utils_.pref_service(),
  258. std::vector<std::string>());
  259. }
  260. remote_suggestions_status_service_ =
  261. remote_suggestions_status_service.get();
  262. auto image_fetcher = std::make_unique<NiceMock<MockImageFetcher>>();
  263. image_fetcher_ = image_fetcher.get();
  264. ON_CALL(*image_fetcher, GetImageDecoder())
  265. .WillByDefault(Return(&image_decoder_));
  266. EXPECT_FALSE(observer_);
  267. observer_ = std::make_unique<FakeContentSuggestionsProviderObserver>();
  268. // Setup RemoteSuggestionsDatabase with fake ProtoDBs.
  269. auto suggestion_db =
  270. std::make_unique<FakeDB<SnippetProto>>(&suggestion_db_storage_);
  271. auto image_db =
  272. std::make_unique<FakeDB<SnippetImageProto>>(&image_db_storage_);
  273. suggestion_db_ = suggestion_db.get();
  274. image_db_ = image_db.get();
  275. auto database = std::make_unique<RemoteSuggestionsDatabase>(
  276. std::move(suggestion_db), std::move(image_db));
  277. database_ = database.get();
  278. suggestion_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
  279. image_db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
  280. auto fetch_timeout_timer = std::make_unique<base::OneShotTimer>(
  281. timer_mock_task_runner_->GetMockTickClock());
  282. fetch_timeout_timer->SetTaskRunner(timer_mock_task_runner_);
  283. provider_ = std::make_unique<RemoteSuggestionsProviderImpl>(
  284. observer_.get(), utils_.pref_service(), "fr", category_ranker_.get(),
  285. scheduler_.get(), std::move(mock_suggestions_fetcher),
  286. std::move(image_fetcher), std::move(database),
  287. std::move(remote_suggestions_status_service),
  288. std::move(fetch_timeout_timer));
  289. }
  290. void MakeSuggestionsProviderWithoutInitializationWithStrictScheduler() {
  291. scheduler_ = std::make_unique<StrictMock<MockScheduler>>();
  292. MakeSuggestionsProviderWithoutInitialization(
  293. /*use_mock_remote_suggestions_status_service=*/false);
  294. }
  295. void WaitForSuggestionsProviderInitialization() {
  296. EXPECT_EQ(RemoteSuggestionsProviderImpl::State::NOT_INITED,
  297. provider_->state_);
  298. suggestion_db()->LoadCallback(true);
  299. }
  300. void ResetSuggestionsProvider(
  301. bool use_mock_remote_suggestions_status_service) {
  302. provider_.reset();
  303. observer_.reset();
  304. MakeSuggestionsProvider(use_mock_remote_suggestions_status_service);
  305. }
  306. void ResetSuggestionsProviderWithoutInitialization(
  307. bool use_mock_remote_suggestions_status_service) {
  308. provider_.reset();
  309. observer_.reset();
  310. MakeSuggestionsProviderWithoutInitialization(
  311. use_mock_remote_suggestions_status_service);
  312. }
  313. void RunUntilIdle() {
  314. timer_mock_task_runner_->RunUntilIdle();
  315. task_environment_.RunUntilIdle();
  316. }
  317. void SetCategoryRanker(std::unique_ptr<CategoryRanker> category_ranker) {
  318. category_ranker_ = std::move(category_ranker);
  319. }
  320. ContentSuggestion::ID MakeArticleID(const std::string& id_within_category) {
  321. return ContentSuggestion::ID(articles_category(), id_within_category);
  322. }
  323. Category articles_category() {
  324. return Category::FromKnownCategory(KnownCategories::ARTICLES);
  325. }
  326. ContentSuggestion::ID MakeOtherID(const std::string& id_within_category) {
  327. return ContentSuggestion::ID(Category::FromRemoteCategory(kOtherCategoryId),
  328. id_within_category);
  329. }
  330. FakeDB<SnippetProto>* suggestion_db() { return suggestion_db_; }
  331. FakeDB<SnippetImageProto>* image_db() { return image_db_; }
  332. RemoteSuggestionsProviderImpl* provider() { return provider_.get(); }
  333. MOCK_METHOD1(OnImageFetched, void(const gfx::Image&));
  334. protected:
  335. FakeContentSuggestionsProviderObserver& observer() { return *observer_; }
  336. StrictMock<MockRemoteSuggestionsFetcher>* mock_suggestions_fetcher() {
  337. return mock_suggestions_fetcher_;
  338. }
  339. // TODO(tschumann): Make this a strict-mock. We want to avoid unneccesary
  340. // network requests.
  341. NiceMock<MockImageFetcher>* image_fetcher() { return image_fetcher_; }
  342. image_fetcher::FakeImageDecoder* image_decoder() { return &image_decoder_; }
  343. PrefService* pref_service() { return utils_.pref_service(); }
  344. RemoteSuggestionsDatabase* database() { return database_; }
  345. MockScheduler* scheduler() { return scheduler_.get(); }
  346. void FetchTheseSuggestions(
  347. bool interactive_request,
  348. Status status,
  349. absl::optional<std::vector<FetchedCategory>> fetched_categories) {
  350. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
  351. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  352. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
  353. .RetiresOnSaturation();
  354. provider_->FetchSuggestions(
  355. interactive_request, RemoteSuggestionsProvider::FetchStatusCallback());
  356. std::move(snippets_callback).Run(status, std::move(fetched_categories));
  357. }
  358. void FetchMoreTheseSuggestions(
  359. const Category& category,
  360. const std::set<std::string>& known_suggestion_ids,
  361. FetchDoneCallback fetch_done_callback,
  362. Status status,
  363. absl::optional<std::vector<FetchedCategory>> fetched_categories) {
  364. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
  365. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  366. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
  367. .RetiresOnSaturation();
  368. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  369. .WillOnce(Return(true))
  370. .RetiresOnSaturation();
  371. provider_->Fetch(category, known_suggestion_ids,
  372. std::move(fetch_done_callback));
  373. std::move(snippets_callback).Run(status, std::move(fetched_categories));
  374. }
  375. RemoteSuggestionsFetcher::SnippetsAvailableCallback
  376. FetchSuggestionsAndGetResponseCallback(
  377. bool interactive_request) {
  378. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
  379. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  380. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
  381. .RetiresOnSaturation();
  382. provider_->FetchSuggestions(
  383. interactive_request, RemoteSuggestionsProvider::FetchStatusCallback());
  384. return snippets_callback;
  385. }
  386. RemoteSuggestionsFetcher::SnippetsAvailableCallback
  387. RefetchWhileDisplayingAndGetResponseCallback() {
  388. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
  389. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  390. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
  391. .RetiresOnSaturation();
  392. provider_->RefetchWhileDisplaying(
  393. RemoteSuggestionsProvider::FetchStatusCallback());
  394. return snippets_callback;
  395. }
  396. RemoteSuggestionsFetcher::SnippetsAvailableCallback
  397. ReloadSuggestionsAndGetResponseCallback() {
  398. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  399. .WillOnce(Return(true))
  400. .RetiresOnSaturation();
  401. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
  402. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  403. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
  404. .RetiresOnSaturation();
  405. provider_->ReloadSuggestions();
  406. return snippets_callback;
  407. }
  408. void ChangeRemoteSuggestionsStatus(RemoteSuggestionsStatus old_status,
  409. RemoteSuggestionsStatus new_status) {
  410. EXPECT_FALSE(status_change_callback_.is_null());
  411. status_change_callback_.Run(old_status, new_status);
  412. }
  413. void SetOrderNewRemoteCategoriesBasedOnArticlesCategoryParam(bool value) {
  414. scoped_feature_list_.Reset();
  415. scoped_feature_list_.InitAndEnableFeatureWithParameters(
  416. kArticleSuggestionsFeature,
  417. {{"order_new_remote_categories_based_on_articles_category",
  418. value ? "true" : "false"}});
  419. }
  420. void SetTriggeringNotificationsAndSubscriptionParams(
  421. bool fetched_notifications_enabled,
  422. bool pushed_notifications_enabled,
  423. bool subscribe_signed_in,
  424. bool subscribe_signed_out) {
  425. scoped_feature_list_.Reset();
  426. scoped_feature_list_.InitAndEnableFeatureWithParameters(
  427. kNotificationsFeature,
  428. {
  429. {"enable_fetched_suggestions_notifications",
  430. BoolToString(fetched_notifications_enabled)},
  431. {"enable_pushed_suggestions_notifications",
  432. BoolToString(pushed_notifications_enabled)},
  433. {"enable_signed_in_users_subscription_for_pushed_suggestions",
  434. BoolToString(subscribe_signed_in)},
  435. {"enable_signed_out_users_subscription_for_pushed_suggestions",
  436. BoolToString(subscribe_signed_out)},
  437. });
  438. }
  439. void SetFetchedNotificationsParams(bool enable, bool force) {
  440. scoped_feature_list_.Reset();
  441. scoped_feature_list_.InitAndEnableFeatureWithParameters(
  442. kNotificationsFeature,
  443. {
  444. {"enable_fetched_suggestions_notifications", BoolToString(enable)},
  445. {"force_fetched_suggestions_notifications", BoolToString(force)},
  446. });
  447. }
  448. void SetFetchMoreSuggestionsCount(int count) {
  449. scoped_feature_list_.Reset();
  450. scoped_feature_list_.InitAndEnableFeatureWithParameters(
  451. kArticleSuggestionsFeature,
  452. {{"fetch_more_suggestions_count", base::NumberToString(count)}});
  453. }
  454. void FastForwardBy(const base::TimeDelta& delta) {
  455. timer_mock_task_runner_->FastForwardBy(delta);
  456. }
  457. gfx::Image FetchImage(const ContentSuggestion::ID& suggestion_id) {
  458. gfx::Image result;
  459. provider_->FetchSuggestionImage(
  460. suggestion_id,
  461. base::BindOnce([](gfx::Image* output,
  462. const gfx::Image& loaded) { *output = loaded; },
  463. &result));
  464. image_db_->GetCallback(true);
  465. RunUntilIdle();
  466. return result;
  467. }
  468. private:
  469. std::unique_ptr<RemoteSuggestionsProviderImpl> provider_;
  470. base::test::ScopedFeatureList scoped_feature_list_;
  471. test::RemoteSuggestionsTestUtils utils_;
  472. std::unique_ptr<CategoryRanker> category_ranker_;
  473. UserClassifier user_classifier_;
  474. std::unique_ptr<FakeContentSuggestionsProviderObserver> observer_;
  475. raw_ptr<StrictMock<MockRemoteSuggestionsFetcher>> mock_suggestions_fetcher_;
  476. raw_ptr<NiceMock<MockImageFetcher>> image_fetcher_;
  477. image_fetcher::FakeImageDecoder image_decoder_;
  478. std::unique_ptr<MockScheduler> scheduler_;
  479. raw_ptr<RemoteSuggestionsStatusService> remote_suggestions_status_service_;
  480. base::test::TaskEnvironment task_environment_;
  481. RemoteSuggestionsStatusService::StatusChangeCallback status_change_callback_;
  482. raw_ptr<RemoteSuggestionsDatabase> database_;
  483. std::map<std::string, SnippetProto> suggestion_db_storage_;
  484. std::map<std::string, SnippetImageProto> image_db_storage_;
  485. // Owned by |database_|.
  486. raw_ptr<FakeDB<SnippetProto>> suggestion_db_;
  487. raw_ptr<FakeDB<SnippetImageProto>> image_db_;
  488. scoped_refptr<TestMockTimeTaskRunner> timer_mock_task_runner_;
  489. };
  490. TEST_F(RemoteSuggestionsProviderImplTest, Full) {
  491. MakeSuggestionsProvider(
  492. /*use_mock_remote_suggestions_status_service=*/false);
  493. std::vector<FetchedCategory> fetched_categories;
  494. fetched_categories.push_back(
  495. FetchedCategoryBuilder()
  496. .SetCategory(articles_category())
  497. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  498. .AddId(kSuggestionUrl)
  499. .SetTitle(kSuggestionTitle)
  500. .SetSnippet(kSuggestionText)
  501. .SetImageUrl(kImageUrl)
  502. .SetPublishDate(GetDefaultCreationTime())
  503. .SetPublisher(kSuggestionPublisherName))
  504. .Build());
  505. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  506. std::move(fetched_categories));
  507. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  508. SizeIs(1));
  509. ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  510. SizeIs(1));
  511. const ContentSuggestion& suggestion =
  512. observer().SuggestionsForCategory(articles_category()).front();
  513. EXPECT_EQ(MakeArticleID(kSuggestionUrl), suggestion.id());
  514. EXPECT_EQ(kSuggestionTitle, base::UTF16ToUTF8(suggestion.title()));
  515. EXPECT_EQ(kSuggestionText, base::UTF16ToUTF8(suggestion.snippet_text()));
  516. EXPECT_EQ(kImageUrl, suggestion.salient_image_url());
  517. EXPECT_EQ(GetDefaultCreationTime(), suggestion.publish_date());
  518. EXPECT_EQ(kSuggestionPublisherName,
  519. base::UTF16ToUTF8(suggestion.publisher_name()));
  520. }
  521. TEST_F(RemoteSuggestionsProviderImplTest, CategoryTitle) {
  522. const std::u16string test_default_title = kTestJsonDefaultCategoryTitle;
  523. // Don't send an initial response -- we want to test what happens without any
  524. // server status.
  525. MakeSuggestionsProvider(
  526. /*use_mock_remote_suggestions_status_service=*/false);
  527. // The articles category should be there by default, and have a title.
  528. CategoryInfo info_before = provider()->GetCategoryInfo(articles_category());
  529. ASSERT_THAT(info_before.title(), Not(IsEmpty()));
  530. ASSERT_THAT(info_before.title(), Not(Eq(test_default_title)));
  531. EXPECT_THAT(info_before.additional_action(),
  532. Eq(ContentSuggestionsAdditionalAction::FETCH));
  533. EXPECT_THAT(info_before.show_if_empty(), Eq(true));
  534. std::vector<FetchedCategory> fetched_categories;
  535. fetched_categories.push_back(
  536. FetchedCategoryBuilder()
  537. .SetCategory(articles_category())
  538. .SetTitle(base::UTF16ToUTF8(test_default_title))
  539. .AddSuggestionViaBuilder(RemoteSuggestionBuilder())
  540. .Build());
  541. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  542. std::move(fetched_categories));
  543. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  544. SizeIs(1));
  545. ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  546. SizeIs(1));
  547. // The response contained a title, |kTestJsonDefaultCategoryTitle|.
  548. // Make sure we updated the title in the CategoryInfo.
  549. CategoryInfo info_with_title =
  550. provider()->GetCategoryInfo(articles_category());
  551. EXPECT_THAT(info_before.title(), Not(Eq(info_with_title.title())));
  552. EXPECT_THAT(test_default_title, Eq(info_with_title.title()));
  553. EXPECT_THAT(info_before.additional_action(),
  554. Eq(ContentSuggestionsAdditionalAction::FETCH));
  555. EXPECT_THAT(info_before.show_if_empty(), Eq(true));
  556. }
  557. TEST_F(RemoteSuggestionsProviderImplTest, MultipleCategories) {
  558. MakeSuggestionsProvider(
  559. /*use_mock_remote_suggestions_status_service=*/false);
  560. std::vector<FetchedCategory> fetched_categories;
  561. fetched_categories.push_back(
  562. FetchedCategoryBuilder()
  563. .SetCategory(Category::FromRemoteCategory(1))
  564. .AddSuggestionViaBuilder(
  565. RemoteSuggestionBuilder()
  566. .AddId(base::StringPrintf("%s/%d", kSuggestionUrl, 0))
  567. .SetTitle(kSuggestionTitle)
  568. .SetSnippet(kSuggestionText)
  569. .SetPublishDate(GetDefaultCreationTime())
  570. .SetPublisher(kSuggestionPublisherName))
  571. .Build());
  572. fetched_categories.push_back(
  573. FetchedCategoryBuilder()
  574. .SetCategory(Category::FromRemoteCategory(2))
  575. .AddSuggestionViaBuilder(
  576. RemoteSuggestionBuilder()
  577. .AddId(base::StringPrintf("%s/%d", kSuggestionUrl, 1))
  578. .SetTitle(kSuggestionTitle)
  579. .SetSnippet(kSuggestionText)
  580. .SetPublishDate(GetDefaultCreationTime())
  581. .SetPublisher(kSuggestionPublisherName))
  582. .Build());
  583. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  584. std::move(fetched_categories));
  585. ASSERT_THAT(observer().statuses(),
  586. Eq(std::map<Category, CategoryStatus, Category::CompareByID>{
  587. {articles_category(), CategoryStatus::AVAILABLE},
  588. {Category::FromRemoteCategory(kOtherCategoryId),
  589. CategoryStatus::AVAILABLE},
  590. }));
  591. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  592. SizeIs(1));
  593. EXPECT_THAT(provider()->GetSuggestionsForTesting(
  594. Category::FromRemoteCategory(kOtherCategoryId)),
  595. SizeIs(1));
  596. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  597. SizeIs(1));
  598. ASSERT_THAT(observer().SuggestionsForCategory(
  599. Category::FromRemoteCategory(kOtherCategoryId)),
  600. SizeIs(1));
  601. {
  602. const ContentSuggestion& suggestion =
  603. observer().SuggestionsForCategory(articles_category()).front();
  604. EXPECT_EQ(MakeArticleID(std::string(kSuggestionUrl) + "/0"),
  605. suggestion.id());
  606. EXPECT_EQ(kSuggestionTitle, base::UTF16ToUTF8(suggestion.title()));
  607. EXPECT_EQ(kSuggestionText, base::UTF16ToUTF8(suggestion.snippet_text()));
  608. EXPECT_EQ(GetDefaultCreationTime(), suggestion.publish_date());
  609. EXPECT_EQ(kSuggestionPublisherName,
  610. base::UTF16ToUTF8(suggestion.publisher_name()));
  611. }
  612. {
  613. const ContentSuggestion& suggestion =
  614. observer()
  615. .SuggestionsForCategory(
  616. Category::FromRemoteCategory(kOtherCategoryId))
  617. .front();
  618. EXPECT_EQ(MakeOtherID(std::string(kSuggestionUrl) + "/1"), suggestion.id());
  619. EXPECT_EQ(kSuggestionTitle, base::UTF16ToUTF8(suggestion.title()));
  620. EXPECT_EQ(kSuggestionText, base::UTF16ToUTF8(suggestion.snippet_text()));
  621. EXPECT_EQ(GetDefaultCreationTime(), suggestion.publish_date());
  622. EXPECT_EQ(kSuggestionPublisherName,
  623. base::UTF16ToUTF8(suggestion.publisher_name()));
  624. }
  625. }
  626. TEST_F(RemoteSuggestionsProviderImplTest, ArticleCategoryInfo) {
  627. MakeSuggestionsProvider(
  628. /*use_mock_remote_suggestions_status_service=*/false);
  629. CategoryInfo article_info = provider()->GetCategoryInfo(articles_category());
  630. EXPECT_THAT(article_info.additional_action(),
  631. Eq(ContentSuggestionsAdditionalAction::FETCH));
  632. EXPECT_THAT(article_info.show_if_empty(), Eq(true));
  633. }
  634. TEST_F(RemoteSuggestionsProviderImplTest, ExperimentalCategoryInfo) {
  635. MakeSuggestionsProvider(
  636. /*use_mock_remote_suggestions_status_service=*/false);
  637. std::vector<FetchedCategory> fetched_categories;
  638. fetched_categories.push_back(
  639. FetchedCategoryBuilder()
  640. .SetCategory(Category::FromRemoteCategory(1))
  641. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
  642. .Build());
  643. fetched_categories.push_back(
  644. FetchedCategoryBuilder()
  645. .SetCategory(Category::FromRemoteCategory(kUnknownRemoteCategoryId))
  646. .SetAdditionalAction(ContentSuggestionsAdditionalAction::NONE)
  647. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("2"))
  648. .Build());
  649. // Load data with multiple categories so that a new experimental category gets
  650. // registered.
  651. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  652. std::move(fetched_categories));
  653. CategoryInfo info = provider()->GetCategoryInfo(
  654. Category::FromRemoteCategory(kUnknownRemoteCategoryId));
  655. EXPECT_THAT(info.additional_action(),
  656. Eq(ContentSuggestionsAdditionalAction::NONE));
  657. EXPECT_THAT(info.show_if_empty(), Eq(false));
  658. }
  659. TEST_F(RemoteSuggestionsProviderImplTest, AddRemoteCategoriesToCategoryRanker) {
  660. auto mock_ranker = std::make_unique<NiceMock<MockCategoryRanker>>();
  661. MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
  662. SetCategoryRanker(std::move(mock_ranker));
  663. std::vector<FetchedCategory> fetched_categories;
  664. fetched_categories.push_back(
  665. FetchedCategoryBuilder()
  666. .SetCategory(Category::FromRemoteCategory(11))
  667. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("11"))
  668. .Build());
  669. fetched_categories.push_back(
  670. FetchedCategoryBuilder()
  671. .SetCategory(Category::FromRemoteCategory(13))
  672. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("13"))
  673. .Build());
  674. fetched_categories.push_back(
  675. FetchedCategoryBuilder()
  676. .SetCategory(Category::FromRemoteCategory(12))
  677. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("12"))
  678. .Build());
  679. {
  680. // The order of categories is determined by the order in which they are
  681. // added. Thus, the latter is tested here.
  682. InSequence s;
  683. EXPECT_CALL(*raw_mock_ranker,
  684. AppendCategoryIfNecessary(Category::FromRemoteCategory(11)));
  685. EXPECT_CALL(*raw_mock_ranker,
  686. AppendCategoryIfNecessary(Category::FromRemoteCategory(13)));
  687. EXPECT_CALL(*raw_mock_ranker,
  688. AppendCategoryIfNecessary(Category::FromRemoteCategory(12)));
  689. }
  690. MakeSuggestionsProvider(
  691. /*use_mock_remote_suggestions_status_service=*/false);
  692. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  693. std::move(fetched_categories));
  694. }
  695. TEST_F(RemoteSuggestionsProviderImplTest,
  696. AddRemoteCategoriesToCategoryRankerRelativeToArticles) {
  697. SetOrderNewRemoteCategoriesBasedOnArticlesCategoryParam(true);
  698. auto mock_ranker = std::make_unique<NiceMock<MockCategoryRanker>>();
  699. MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
  700. SetCategoryRanker(std::move(mock_ranker));
  701. std::vector<FetchedCategory> fetched_categories;
  702. fetched_categories.push_back(
  703. FetchedCategoryBuilder()
  704. .SetCategory(Category::FromRemoteCategory(14))
  705. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("14"))
  706. .Build());
  707. fetched_categories.push_back(
  708. FetchedCategoryBuilder()
  709. .SetCategory(Category::FromRemoteCategory(13))
  710. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("13"))
  711. .Build());
  712. fetched_categories.push_back(
  713. FetchedCategoryBuilder()
  714. .SetCategory(Category::FromRemoteCategory(1))
  715. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
  716. .Build());
  717. fetched_categories.push_back(
  718. FetchedCategoryBuilder()
  719. .SetCategory(Category::FromRemoteCategory(12))
  720. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("12"))
  721. .Build());
  722. fetched_categories.push_back(
  723. FetchedCategoryBuilder()
  724. .SetCategory(Category::FromRemoteCategory(11))
  725. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("11"))
  726. .Build());
  727. {
  728. InSequence s;
  729. EXPECT_CALL(*raw_mock_ranker,
  730. InsertCategoryBeforeIfNecessary(
  731. Category::FromRemoteCategory(14), articles_category()));
  732. EXPECT_CALL(*raw_mock_ranker,
  733. InsertCategoryBeforeIfNecessary(
  734. Category::FromRemoteCategory(13), articles_category()));
  735. EXPECT_CALL(*raw_mock_ranker,
  736. InsertCategoryAfterIfNecessary(Category::FromRemoteCategory(11),
  737. articles_category()));
  738. EXPECT_CALL(*raw_mock_ranker,
  739. InsertCategoryAfterIfNecessary(Category::FromRemoteCategory(12),
  740. articles_category()));
  741. }
  742. MakeSuggestionsProvider(
  743. /*use_mock_remote_suggestions_status_service=*/false);
  744. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  745. std::move(fetched_categories));
  746. }
  747. TEST_F(
  748. RemoteSuggestionsProviderImplTest,
  749. AddRemoteCategoriesToCategoryRankerRelativeToArticlesWithArticlesAbsent) {
  750. SetOrderNewRemoteCategoriesBasedOnArticlesCategoryParam(true);
  751. auto mock_ranker = std::make_unique<NiceMock<MockCategoryRanker>>();
  752. MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
  753. SetCategoryRanker(std::move(mock_ranker));
  754. std::vector<FetchedCategory> fetched_categories;
  755. fetched_categories.push_back(
  756. FetchedCategoryBuilder()
  757. .SetCategory(Category::FromRemoteCategory(11))
  758. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("11"))
  759. .Build());
  760. EXPECT_CALL(*raw_mock_ranker, InsertCategoryBeforeIfNecessary(_, _)).Times(0);
  761. EXPECT_CALL(*raw_mock_ranker,
  762. AppendCategoryIfNecessary(Category::FromRemoteCategory(11)));
  763. MakeSuggestionsProvider(
  764. /*use_mock_remote_suggestions_status_service=*/false);
  765. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  766. std::move(fetched_categories));
  767. }
  768. TEST_F(RemoteSuggestionsProviderImplTest, PersistCategoryInfos) {
  769. MakeSuggestionsProvider(
  770. /*use_mock_remote_suggestions_status_service=*/false);
  771. std::vector<FetchedCategory> fetched_categories;
  772. fetched_categories.push_back(
  773. FetchedCategoryBuilder()
  774. .SetCategory(articles_category())
  775. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
  776. .Build());
  777. fetched_categories.push_back(
  778. FetchedCategoryBuilder()
  779. .SetCategory(Category::FromRemoteCategory(kUnknownRemoteCategoryId))
  780. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("2"))
  781. .Build());
  782. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  783. std::move(fetched_categories));
  784. ASSERT_EQ(observer().StatusForCategory(articles_category()),
  785. CategoryStatus::AVAILABLE);
  786. ASSERT_EQ(observer().StatusForCategory(
  787. Category::FromRemoteCategory(kUnknownRemoteCategoryId)),
  788. CategoryStatus::AVAILABLE);
  789. CategoryInfo info_articles_before =
  790. provider()->GetCategoryInfo(articles_category());
  791. CategoryInfo info_unknown_before = provider()->GetCategoryInfo(
  792. Category::FromRemoteCategory(kUnknownRemoteCategoryId));
  793. base::i18n::SetICUDefaultLocale("de");
  794. // Recreate the provider to simulate a Chrome restart.
  795. ResetSuggestionsProvider(
  796. /*use_mock_remote_suggestions_status_service=*/false);
  797. // The categories should have been restored.
  798. ASSERT_NE(observer().StatusForCategory(articles_category()),
  799. CategoryStatus::NOT_PROVIDED);
  800. ASSERT_NE(observer().StatusForCategory(
  801. Category::FromRemoteCategory(kUnknownRemoteCategoryId)),
  802. CategoryStatus::NOT_PROVIDED);
  803. EXPECT_EQ(observer().StatusForCategory(articles_category()),
  804. CategoryStatus::AVAILABLE);
  805. EXPECT_EQ(observer().StatusForCategory(
  806. Category::FromRemoteCategory(kUnknownRemoteCategoryId)),
  807. CategoryStatus::AVAILABLE);
  808. CategoryInfo info_articles_after =
  809. provider()->GetCategoryInfo(articles_category());
  810. CategoryInfo info_unknown_after = provider()->GetCategoryInfo(
  811. Category::FromRemoteCategory(kUnknownRemoteCategoryId));
  812. // The new articles section title should reflect the current locale, not what
  813. // we persisted earlier.
  814. EXPECT_NE(info_articles_before.title(), info_articles_after.title());
  815. EXPECT_EQ(
  816. info_articles_after.title(),
  817. l10n_util::GetStringUTF16(IDS_NTP_ARTICLE_SUGGESTIONS_SECTION_HEADER));
  818. EXPECT_EQ(info_unknown_before.title(), info_unknown_after.title());
  819. }
  820. TEST_F(RemoteSuggestionsProviderImplTest, PersistRemoteCategoryOrder) {
  821. // We create a provider with a normal ranker to store the order.
  822. MakeSuggestionsProvider(
  823. /*use_mock_remote_suggestions_status_service=*/false);
  824. std::vector<FetchedCategory> fetched_categories;
  825. fetched_categories.push_back(
  826. FetchedCategoryBuilder()
  827. .SetCategory(Category::FromRemoteCategory(11))
  828. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("11"))
  829. .Build());
  830. fetched_categories.push_back(
  831. FetchedCategoryBuilder()
  832. .SetCategory(Category::FromRemoteCategory(13))
  833. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("13"))
  834. .Build());
  835. fetched_categories.push_back(
  836. FetchedCategoryBuilder()
  837. .SetCategory(Category::FromRemoteCategory(12))
  838. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("12"))
  839. .Build());
  840. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  841. std::move(fetched_categories));
  842. // We manually recreate the provider to simulate Chrome restart and enforce a
  843. // mock ranker.
  844. auto mock_ranker = std::make_unique<NiceMock<MockCategoryRanker>>();
  845. MockCategoryRanker* raw_mock_ranker = mock_ranker.get();
  846. SetCategoryRanker(std::move(mock_ranker));
  847. // Ensure that the order is not fetched.
  848. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(0);
  849. {
  850. // The order of categories is determined by the order in which they are
  851. // added. Thus, the latter is tested here.
  852. InSequence s;
  853. // Article category always exists and, therefore, it is stored in prefs too.
  854. EXPECT_CALL(*raw_mock_ranker,
  855. AppendCategoryIfNecessary(articles_category()));
  856. EXPECT_CALL(*raw_mock_ranker,
  857. AppendCategoryIfNecessary(Category::FromRemoteCategory(11)));
  858. EXPECT_CALL(*raw_mock_ranker,
  859. AppendCategoryIfNecessary(Category::FromRemoteCategory(13)));
  860. EXPECT_CALL(*raw_mock_ranker,
  861. AppendCategoryIfNecessary(Category::FromRemoteCategory(12)));
  862. }
  863. ResetSuggestionsProvider(
  864. /*use_mock_remote_suggestions_status_service=*/false);
  865. }
  866. TEST_F(RemoteSuggestionsProviderImplTest, PersistSuggestions) {
  867. MakeSuggestionsProvider(
  868. /*use_mock_remote_suggestions_status_service=*/false);
  869. std::vector<FetchedCategory> fetched_categories;
  870. fetched_categories.push_back(
  871. FetchedCategoryBuilder()
  872. .SetCategory(Category::FromRemoteCategory(1))
  873. .AddSuggestionViaBuilder(
  874. RemoteSuggestionBuilder().AddId("1").SetRemoteCategoryId(1))
  875. .Build());
  876. fetched_categories.push_back(
  877. FetchedCategoryBuilder()
  878. .SetCategory(Category::FromRemoteCategory(2))
  879. .AddSuggestionViaBuilder(
  880. RemoteSuggestionBuilder().AddId("2").SetRemoteCategoryId(2))
  881. .Build());
  882. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  883. std::move(fetched_categories));
  884. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  885. SizeIs(1));
  886. ASSERT_THAT(observer().SuggestionsForCategory(
  887. Category::FromRemoteCategory(kOtherCategoryId)),
  888. SizeIs(1));
  889. // Recreate the provider to simulate a Chrome restart.
  890. ResetSuggestionsProvider(
  891. /*use_mock_remote_suggestions_status_service=*/false);
  892. // The suggestions in both categories should have been restored.
  893. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  894. SizeIs(1));
  895. EXPECT_THAT(observer().SuggestionsForCategory(
  896. Category::FromRemoteCategory(kOtherCategoryId)),
  897. SizeIs(1));
  898. }
  899. TEST_F(RemoteSuggestionsProviderImplTest, ClearSuggestionsOnInit) {
  900. // Add suggestions.
  901. MakeSuggestionsProvider(
  902. /*use_mock_remote_suggestions_status_service=*/false);
  903. std::vector<FetchedCategory> fetched_categories;
  904. fetched_categories.push_back(
  905. FetchedCategoryBuilder()
  906. .SetCategory(Category::FromRemoteCategory(1))
  907. .AddSuggestionViaBuilder(
  908. RemoteSuggestionBuilder().AddId("1").SetRemoteCategoryId(1))
  909. .Build());
  910. fetched_categories.push_back(
  911. FetchedCategoryBuilder()
  912. .SetCategory(Category::FromRemoteCategory(2))
  913. .AddSuggestionViaBuilder(
  914. RemoteSuggestionBuilder().AddId("2").SetRemoteCategoryId(2))
  915. .Build());
  916. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  917. std::move(fetched_categories));
  918. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  919. SizeIs(1));
  920. ASSERT_THAT(observer().SuggestionsForCategory(
  921. Category::FromRemoteCategory(kOtherCategoryId)),
  922. SizeIs(1));
  923. // Reset the provider and clear the suggestions before it is inited.
  924. ResetSuggestionsProviderWithoutInitialization(
  925. /*use_mock_remote_suggestions_status_service=*/false);
  926. provider()->ClearCachedSuggestions();
  927. // The suggestions in both categories should have been cleared after the init.
  928. WaitForSuggestionsProviderInitialization();
  929. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  930. SizeIs(0));
  931. EXPECT_THAT(observer().SuggestionsForCategory(
  932. Category::FromRemoteCategory(kOtherCategoryId)),
  933. SizeIs(0));
  934. }
  935. TEST_F(RemoteSuggestionsProviderImplTest, DontNotifyIfNotAvailable) {
  936. // Get some suggestions into the database.
  937. MakeSuggestionsProvider(
  938. /*use_mock_remote_suggestions_status_service=*/false);
  939. std::vector<FetchedCategory> fetched_categories;
  940. fetched_categories.push_back(
  941. FetchedCategoryBuilder()
  942. .SetCategory(Category::FromRemoteCategory(1))
  943. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
  944. .Build());
  945. fetched_categories.push_back(
  946. FetchedCategoryBuilder()
  947. .SetCategory(Category::FromRemoteCategory(2))
  948. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("2"))
  949. .Build());
  950. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  951. std::move(fetched_categories));
  952. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  953. SizeIs(1));
  954. ASSERT_THAT(observer().SuggestionsForCategory(
  955. Category::FromRemoteCategory(kOtherCategoryId)),
  956. SizeIs(1));
  957. // Set the pref that disables remote suggestions.
  958. pref_service()->SetBoolean(feed::prefs::kEnableSnippets, false);
  959. // Recreate the provider to simulate a Chrome start.
  960. ResetSuggestionsProvider(
  961. /*use_mock_remote_suggestions_status_service=*/false);
  962. ASSERT_THAT(RemoteSuggestionsProviderImpl::State::DISABLED,
  963. Eq(provider()->state_));
  964. // Now the observer should not have received any suggestions.
  965. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  966. IsEmpty());
  967. EXPECT_THAT(observer().SuggestionsForCategory(
  968. Category::FromRemoteCategory(kOtherCategoryId)),
  969. IsEmpty());
  970. }
  971. TEST_F(RemoteSuggestionsProviderImplTest, Clear) {
  972. MakeSuggestionsProvider(
  973. /*use_mock_remote_suggestions_status_service=*/false);
  974. std::vector<FetchedCategory> fetched_categories;
  975. fetched_categories.push_back(
  976. FetchedCategoryBuilder()
  977. .SetCategory(articles_category())
  978. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("1"))
  979. .Build());
  980. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  981. std::move(fetched_categories));
  982. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  983. SizeIs(1));
  984. provider()->ClearCachedSuggestions();
  985. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  986. IsEmpty());
  987. }
  988. TEST_F(RemoteSuggestionsProviderImplTest, ReplaceSuggestions) {
  989. MakeSuggestionsProvider(
  990. /*use_mock_remote_suggestions_status_service=*/false);
  991. std::string first("http://first");
  992. std::vector<FetchedCategory> fetched_categories;
  993. fetched_categories.push_back(
  994. FetchedCategoryBuilder()
  995. .SetCategory(articles_category())
  996. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(first))
  997. .Build());
  998. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  999. std::move(fetched_categories));
  1000. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1001. ElementsAre(Pointee(Property(&RemoteSuggestion::id, first))));
  1002. std::string second("http://second");
  1003. fetched_categories.clear();
  1004. fetched_categories.push_back(
  1005. FetchedCategoryBuilder()
  1006. .SetCategory(articles_category())
  1007. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(second))
  1008. .Build());
  1009. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1010. std::move(fetched_categories));
  1011. // The suggestions loaded last replace all that was loaded previously.
  1012. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1013. ElementsAre(Pointee(Property(&RemoteSuggestion::id, second))));
  1014. }
  1015. TEST_F(RemoteSuggestionsProviderImplTest,
  1016. ShouldResolveFetchedSuggestionThumbnail) {
  1017. MakeSuggestionsProvider(
  1018. /*use_mock_remote_suggestions_status_service=*/false);
  1019. std::vector<FetchedCategory> fetched_categories;
  1020. fetched_categories.push_back(
  1021. FetchedCategoryBuilder()
  1022. .SetCategory(articles_category())
  1023. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("id"))
  1024. .Build());
  1025. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1026. std::move(fetched_categories));
  1027. ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1028. ElementsAre(Pointee(Property(&RemoteSuggestion::id, "id"))));
  1029. image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
  1030. EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
  1031. .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
  1032. gfx::Image image = FetchImage(MakeArticleID("id"));
  1033. ASSERT_FALSE(image.IsEmpty());
  1034. EXPECT_EQ(1, image.Width());
  1035. }
  1036. TEST_F(RemoteSuggestionsProviderImplTest, ShouldFetchMore) {
  1037. MakeSuggestionsProvider(
  1038. /*use_mock_remote_suggestions_status_service=*/false);
  1039. std::vector<FetchedCategory> fetched_categories;
  1040. fetched_categories.push_back(
  1041. FetchedCategoryBuilder()
  1042. .SetCategory(articles_category())
  1043. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("first"))
  1044. .Build());
  1045. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1046. std::move(fetched_categories));
  1047. ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1048. ElementsAre(Pointee(Property(&RemoteSuggestion::id, "first"))));
  1049. auto expect_only_second_suggestion_received = base::BindOnce(
  1050. [](Status status, std::vector<ContentSuggestion> suggestions) {
  1051. EXPECT_THAT(suggestions, SizeIs(1));
  1052. EXPECT_THAT(suggestions[0].id().id_within_category(), Eq("second"));
  1053. });
  1054. fetched_categories.clear();
  1055. fetched_categories.push_back(
  1056. FetchedCategoryBuilder()
  1057. .SetCategory(articles_category())
  1058. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("second"))
  1059. .Build());
  1060. FetchMoreTheseSuggestions(
  1061. articles_category(),
  1062. /*known_suggestion_ids=*/std::set<std::string>(),
  1063. /*fetch_done_callback=*/std::move(expect_only_second_suggestion_received),
  1064. Status::Success(), std::move(fetched_categories));
  1065. }
  1066. TEST_F(RemoteSuggestionsProviderImplTest,
  1067. ShouldResolveFetchedMoreSuggestionThumbnail) {
  1068. MakeSuggestionsProvider(
  1069. /*use_mock_remote_suggestions_status_service=*/false);
  1070. std::vector<FetchedCategory> fetched_categories;
  1071. fetched_categories.push_back(
  1072. FetchedCategoryBuilder()
  1073. .SetCategory(articles_category())
  1074. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId("id"))
  1075. .Build());
  1076. auto assert_only_first_suggestion_received = base::BindOnce(
  1077. [](Status status, std::vector<ContentSuggestion> suggestions) {
  1078. ASSERT_THAT(suggestions, SizeIs(1));
  1079. ASSERT_THAT(suggestions[0].id().id_within_category(), Eq("id"));
  1080. });
  1081. FetchMoreTheseSuggestions(
  1082. articles_category(),
  1083. /*known_suggestion_ids=*/std::set<std::string>(),
  1084. /*fetch_done_callback=*/std::move(assert_only_first_suggestion_received),
  1085. Status::Success(), std::move(fetched_categories));
  1086. image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
  1087. EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
  1088. .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
  1089. gfx::Image image = FetchImage(MakeArticleID("id"));
  1090. ASSERT_FALSE(image.IsEmpty());
  1091. EXPECT_EQ(1, image.Width());
  1092. }
  1093. // Imagine that we have surfaces A and B. The user fetches more in A, this
  1094. // should not add any suggestions to B.
  1095. TEST_F(RemoteSuggestionsProviderImplTest,
  1096. ShouldNotChangeSuggestionsInOtherSurfacesWhenFetchingMore) {
  1097. MakeSuggestionsProvider(
  1098. /*use_mock_remote_suggestions_status_service=*/false);
  1099. // Fetch a suggestion.
  1100. std::vector<FetchedCategory> fetched_categories;
  1101. fetched_categories.push_back(
  1102. FetchedCategoryBuilder()
  1103. .SetCategory(articles_category())
  1104. .AddSuggestionViaBuilder(
  1105. RemoteSuggestionBuilder().AddId("http://old.com/"))
  1106. .Build());
  1107. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1108. std::move(fetched_categories));
  1109. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  1110. ElementsAre(Property(&ContentSuggestion::id,
  1111. MakeArticleID("http://old.com/"))));
  1112. // Now fetch more, but first prepare a response.
  1113. fetched_categories.clear();
  1114. fetched_categories.push_back(
  1115. FetchedCategoryBuilder()
  1116. .SetCategory(articles_category())
  1117. .AddSuggestionViaBuilder(
  1118. RemoteSuggestionBuilder().AddId("http://fetched-more.com/"))
  1119. .Build());
  1120. // The surface issuing the fetch more gets response via callback.
  1121. auto assert_receiving_one_new_suggestion = base::BindOnce(
  1122. [](Status status, std::vector<ContentSuggestion> suggestions) {
  1123. ASSERT_THAT(suggestions, SizeIs(1));
  1124. ASSERT_THAT(suggestions[0].id().id_within_category(),
  1125. Eq("http://fetched-more.com/"));
  1126. });
  1127. FetchMoreTheseSuggestions(
  1128. articles_category(),
  1129. /*known_suggestion_ids=*/{"http://old.com/"},
  1130. /*fetch_done_callback=*/std::move(assert_receiving_one_new_suggestion),
  1131. Status::Success(), std::move(fetched_categories));
  1132. // Other surfaces should remain the same.
  1133. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  1134. ElementsAre(Property(&ContentSuggestion::id,
  1135. MakeArticleID("http://old.com/"))));
  1136. }
  1137. // Imagine that we have surfaces A and B. The user fetches more in A. This
  1138. // should not affect the next fetch more in B, i.e. assuming the same server
  1139. // response the same suggestions must be fetched in B if the user fetches more
  1140. // there as well.
  1141. TEST_F(RemoteSuggestionsProviderImplTest,
  1142. ShouldNotAffectFetchMoreInOtherSurfacesWhenFetchingMore) {
  1143. MakeSuggestionsProvider(
  1144. /*use_mock_remote_suggestions_status_service=*/false);
  1145. // Fetch more on the surface A.
  1146. std::vector<FetchedCategory> fetched_categories;
  1147. fetched_categories.push_back(FetchedCategory(
  1148. articles_category(),
  1149. BuildRemoteCategoryInfo(u"title",
  1150. /*allow_fetching_more_results=*/true)));
  1151. fetched_categories[0].suggestions.push_back(
  1152. CreateTestRemoteSuggestion("http://fetched-more.com/"));
  1153. auto assert_receiving_one_new_suggestion = base::BindOnce(
  1154. [](Status status, std::vector<ContentSuggestion> suggestions) {
  1155. ASSERT_THAT(suggestions, SizeIs(1));
  1156. ASSERT_THAT(suggestions[0].id().id_within_category(),
  1157. Eq("http://fetched-more.com/"));
  1158. });
  1159. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
  1160. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  1161. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
  1162. .RetiresOnSaturation();
  1163. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  1164. .WillOnce(Return(true))
  1165. .RetiresOnSaturation();
  1166. provider()->Fetch(articles_category(),
  1167. /*known_suggestion_ids=*/std::set<std::string>(),
  1168. std::move(assert_receiving_one_new_suggestion));
  1169. std::move(snippets_callback)
  1170. .Run(Status::Success(), std::move(fetched_categories));
  1171. // Now fetch more on the surface B. The response is the same as before.
  1172. fetched_categories.clear();
  1173. fetched_categories.push_back(FetchedCategory(
  1174. articles_category(),
  1175. BuildRemoteCategoryInfo(u"title",
  1176. /*allow_fetching_more_results=*/true)));
  1177. fetched_categories[0].suggestions.push_back(
  1178. CreateTestRemoteSuggestion("http://fetched-more.com/"));
  1179. // B should receive the same suggestion as was fetched more on A.
  1180. auto expect_receiving_same_suggestion = base::BindOnce(
  1181. [](Status status, std::vector<ContentSuggestion> suggestions) {
  1182. ASSERT_THAT(suggestions, SizeIs(1));
  1183. EXPECT_THAT(suggestions[0].id().id_within_category(),
  1184. Eq("http://fetched-more.com/"));
  1185. });
  1186. // The provider should not ask the fetcher to exclude the suggestion fetched
  1187. // more on A.
  1188. EXPECT_CALL(*mock_suggestions_fetcher(),
  1189. FetchSnippets(Field(&RequestParams::excluded_ids,
  1190. Not(Contains("http://fetched-more.com/"))),
  1191. _))
  1192. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
  1193. .RetiresOnSaturation();
  1194. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  1195. .WillOnce(Return(true))
  1196. .RetiresOnSaturation();
  1197. provider()->Fetch(articles_category(),
  1198. /*known_suggestion_ids=*/std::set<std::string>(),
  1199. std::move(expect_receiving_same_suggestion));
  1200. std::move(snippets_callback)
  1201. .Run(Status::Success(), std::move(fetched_categories));
  1202. }
  1203. TEST_F(RemoteSuggestionsProviderImplTest,
  1204. ClearHistoryShouldDeleteArchivedSuggestions) {
  1205. MakeSuggestionsProvider(
  1206. /*use_mock_remote_suggestions_status_service=*/false);
  1207. // First get suggestions into the archived state which happens through
  1208. // subsequent fetches. Then we verify the entries are gone from the 'archived'
  1209. // state by trying to load their images (and we shouldn't even know the URLs
  1210. // anymore).
  1211. std::vector<FetchedCategory> fetched_categories;
  1212. fetched_categories.push_back(
  1213. FetchedCategoryBuilder()
  1214. .SetCategory(articles_category())
  1215. .AddSuggestionViaBuilder(
  1216. RemoteSuggestionBuilder().AddId("http://id-1"))
  1217. .AddSuggestionViaBuilder(
  1218. RemoteSuggestionBuilder().AddId("http://id-2"))
  1219. .Build());
  1220. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1221. std::move(fetched_categories));
  1222. fetched_categories.clear();
  1223. fetched_categories.push_back(
  1224. FetchedCategoryBuilder()
  1225. .SetCategory(articles_category())
  1226. .AddSuggestionViaBuilder(
  1227. RemoteSuggestionBuilder().AddId("http://new-id-1"))
  1228. .AddSuggestionViaBuilder(
  1229. RemoteSuggestionBuilder().AddId("http://new-id-2"))
  1230. .Build());
  1231. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1232. std::move(fetched_categories));
  1233. // Make sure images of both batches are available. This is to sanity check our
  1234. // assumptions for the test are right.
  1235. EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
  1236. .Times(2)
  1237. .WillRepeatedly(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
  1238. image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
  1239. gfx::Image image = FetchImage(MakeArticleID("http://id-1"));
  1240. ASSERT_FALSE(image.IsEmpty());
  1241. ASSERT_EQ(1, image.Width());
  1242. image = FetchImage(MakeArticleID("http://new-id-1"));
  1243. ASSERT_FALSE(image.IsEmpty());
  1244. ASSERT_EQ(1, image.Width());
  1245. provider()->ClearHistory(base::Time::UnixEpoch(), base::Time::Max(),
  1246. base::RepeatingCallback<bool(const GURL& url)>());
  1247. // Make sure images of both batches are gone.
  1248. // Verify we cannot resolve the image of the new suggestions.
  1249. image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
  1250. EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true))))
  1251. .Times(2);
  1252. provider()->FetchSuggestionImage(
  1253. MakeArticleID("http://id-1"),
  1254. base::BindOnce(&RemoteSuggestionsProviderImplTest::OnImageFetched,
  1255. base::Unretained(this)));
  1256. provider()->FetchSuggestionImage(
  1257. MakeArticleID("http://new-id-1"),
  1258. base::BindOnce(&RemoteSuggestionsProviderImplTest::OnImageFetched,
  1259. base::Unretained(this)));
  1260. }
  1261. namespace {
  1262. // Workaround for gMock's lack of support for movable types.
  1263. void SuggestionsLoaded(
  1264. MockFunction<void(Status, const std::vector<ContentSuggestion>&)>* loaded,
  1265. Status status,
  1266. std::vector<ContentSuggestion> suggestions) {
  1267. loaded->Call(status, suggestions);
  1268. }
  1269. } // namespace
  1270. TEST_F(RemoteSuggestionsProviderImplTest, ReturnFetchRequestEmptyBeforeInit) {
  1271. MakeSuggestionsProviderWithoutInitialization(
  1272. /*use_mock_remote_suggestions_status_service=*/false);
  1273. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(0);
  1274. MockFunction<void(Status, const std::vector<ContentSuggestion>&)> loaded;
  1275. EXPECT_CALL(loaded, Call(Field(&Status::code, StatusCode::TEMPORARY_ERROR),
  1276. IsEmpty()));
  1277. provider()->Fetch(articles_category(), std::set<std::string>(),
  1278. base::BindOnce(&SuggestionsLoaded, &loaded));
  1279. RunUntilIdle();
  1280. }
  1281. TEST_F(RemoteSuggestionsProviderImplTest, ReturnRefetchRequestEmptyBeforeInit) {
  1282. MakeSuggestionsProviderWithoutInitialization(
  1283. /*use_mock_remote_suggestions_status_service=*/false);
  1284. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(0);
  1285. MockFunction<void(Status)> loaded;
  1286. EXPECT_CALL(loaded, Call(Field(&Status::code, StatusCode::TEMPORARY_ERROR)));
  1287. provider()->RefetchInTheBackground(base::BindOnce(
  1288. &MockFunction<void(Status)>::Call, base::Unretained(&loaded)));
  1289. RunUntilIdle();
  1290. }
  1291. TEST_F(RemoteSuggestionsProviderImplTest, IgnoreRefetchRequestEmptyBeforeInit) {
  1292. MakeSuggestionsProviderWithoutInitialization(
  1293. /*use_mock_remote_suggestions_status_service=*/false);
  1294. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(0);
  1295. provider()->RefetchInTheBackground(
  1296. RemoteSuggestionsProvider::FetchStatusCallback());
  1297. RunUntilIdle();
  1298. }
  1299. TEST_F(RemoteSuggestionsProviderImplTest,
  1300. ShouldForwardTemporaryErrorFromFetcher) {
  1301. MakeSuggestionsProvider(
  1302. /*use_mock_remote_suggestions_status_service=*/false);
  1303. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
  1304. MockFunction<void(Status, const std::vector<ContentSuggestion>&)> loaded;
  1305. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  1306. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback));
  1307. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  1308. .WillOnce(Return(true))
  1309. .RetiresOnSaturation();
  1310. provider()->Fetch(articles_category(),
  1311. /*known_ids=*/std::set<std::string>(),
  1312. base::BindOnce(&SuggestionsLoaded, &loaded));
  1313. EXPECT_CALL(loaded, Call(Field(&Status::code, StatusCode::TEMPORARY_ERROR),
  1314. IsEmpty()));
  1315. ASSERT_FALSE(snippets_callback.is_null());
  1316. std::move(snippets_callback)
  1317. .Run(Status(StatusCode::TEMPORARY_ERROR, "Received invalid JSON"),
  1318. absl::nullopt);
  1319. }
  1320. TEST_F(RemoteSuggestionsProviderImplTest,
  1321. ShouldNotAddNewSuggestionsAfterFetchError) {
  1322. MakeSuggestionsProvider(
  1323. /*use_mock_remote_suggestions_status_service=*/false);
  1324. FetchTheseSuggestions(
  1325. /*interactive_request=*/false,
  1326. Status(StatusCode::TEMPORARY_ERROR, "Received invalid JSON"),
  1327. absl::nullopt);
  1328. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1329. IsEmpty());
  1330. }
  1331. TEST_F(RemoteSuggestionsProviderImplTest,
  1332. ShouldNotClearOldSuggestionsAfterFetchError) {
  1333. MakeSuggestionsProvider(
  1334. /*use_mock_remote_suggestions_status_service=*/false);
  1335. std::vector<FetchedCategory> fetched_categories;
  1336. fetched_categories.push_back(FetchedCategory(
  1337. articles_category(),
  1338. BuildRemoteCategoryInfo(u"title",
  1339. /*allow_fetching_more_results=*/true)));
  1340. fetched_categories[0].suggestions.push_back(
  1341. CreateTestRemoteSuggestion(base::StringPrintf("http://abc.com/")));
  1342. FetchTheseSuggestions(/*interactive_request=*/false, Status::Success(),
  1343. std::move(fetched_categories));
  1344. ASSERT_THAT(
  1345. provider()->GetSuggestionsForTesting(articles_category()),
  1346. ElementsAre(Pointee(Property(&RemoteSuggestion::id, "http://abc.com/"))));
  1347. FetchTheseSuggestions(
  1348. /*interactive_request=*/false,
  1349. Status(StatusCode::TEMPORARY_ERROR, "Received invalid JSON"),
  1350. absl::nullopt);
  1351. // This should not have changed the existing suggestions.
  1352. EXPECT_THAT(
  1353. provider()->GetSuggestionsForTesting(articles_category()),
  1354. ElementsAre(Pointee(Property(&RemoteSuggestion::id, "http://abc.com/"))));
  1355. }
  1356. TEST_F(RemoteSuggestionsProviderImplTest, Dismiss) {
  1357. MakeSuggestionsProvider(
  1358. /*use_mock_remote_suggestions_status_service=*/false);
  1359. std::vector<FetchedCategory> fetched_categories;
  1360. const FetchedCategoryBuilder category_builder =
  1361. FetchedCategoryBuilder()
  1362. .SetCategory(articles_category())
  1363. .AddSuggestionViaBuilder(
  1364. RemoteSuggestionBuilder().AddId("http://site.com"));
  1365. fetched_categories.push_back(category_builder.Build());
  1366. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1367. std::move(fetched_categories));
  1368. ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1369. SizeIs(1));
  1370. // Load the image to store it in the database.
  1371. EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
  1372. .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
  1373. image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
  1374. gfx::Image image = FetchImage(MakeArticleID("http://site.com"));
  1375. EXPECT_FALSE(image.IsEmpty());
  1376. EXPECT_EQ(1, image.Width());
  1377. // Dismissing a non-existent suggestion shouldn't do anything.
  1378. provider()->DismissSuggestion(MakeArticleID("http://othersite.com"));
  1379. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1380. SizeIs(1));
  1381. // Dismiss the suggestion.
  1382. provider()->DismissSuggestion(MakeArticleID("http://site.com"));
  1383. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1384. IsEmpty());
  1385. // Verify we can still load the image of the discarded suggestion (other NTPs
  1386. // might still reference it). This should come from the database -- no network
  1387. // fetch necessary.
  1388. image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
  1389. image = FetchImage(MakeArticleID("http://site.com"));
  1390. EXPECT_FALSE(image.IsEmpty());
  1391. EXPECT_EQ(1, image.Width());
  1392. // Make sure that fetching the same suggestion again does not re-add it.
  1393. fetched_categories.clear();
  1394. fetched_categories.push_back(category_builder.Build());
  1395. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1396. std::move(fetched_categories));
  1397. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1398. IsEmpty());
  1399. // The suggestion should stay dismissed even after re-creating the provider.
  1400. ResetSuggestionsProvider(
  1401. /*use_mock_remote_suggestions_status_service=*/false);
  1402. fetched_categories.clear();
  1403. fetched_categories.push_back(category_builder.Build());
  1404. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1405. std::move(fetched_categories));
  1406. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1407. IsEmpty());
  1408. // The suggestion can be added again after clearing dismissed suggestions.
  1409. provider()->ClearDismissedSuggestionsForDebugging(articles_category());
  1410. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1411. IsEmpty());
  1412. fetched_categories.clear();
  1413. fetched_categories.push_back(category_builder.Build());
  1414. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1415. std::move(fetched_categories));
  1416. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1417. SizeIs(1));
  1418. }
  1419. TEST_F(RemoteSuggestionsProviderImplTest, GetDismissed) {
  1420. MakeSuggestionsProvider(
  1421. /*use_mock_remote_suggestions_status_service=*/false);
  1422. std::vector<FetchedCategory> fetched_categories;
  1423. fetched_categories.push_back(
  1424. FetchedCategoryBuilder()
  1425. .SetCategory(articles_category())
  1426. .AddSuggestionViaBuilder(
  1427. RemoteSuggestionBuilder().AddId("http://site.com"))
  1428. .Build());
  1429. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1430. std::move(fetched_categories));
  1431. provider()->DismissSuggestion(MakeArticleID("http://site.com"));
  1432. provider()->GetDismissedSuggestionsForDebugging(
  1433. articles_category(),
  1434. base::BindOnce(
  1435. [](RemoteSuggestionsProviderImpl* provider,
  1436. RemoteSuggestionsProviderImplTest* test,
  1437. std::vector<ContentSuggestion> dismissed_suggestions) {
  1438. EXPECT_EQ(1u, dismissed_suggestions.size());
  1439. for (auto& suggestion : dismissed_suggestions) {
  1440. EXPECT_EQ(test->MakeArticleID("http://site.com"),
  1441. suggestion.id());
  1442. }
  1443. },
  1444. provider(), this));
  1445. RunUntilIdle();
  1446. // There should be no dismissed suggestion after clearing the list.
  1447. provider()->ClearDismissedSuggestionsForDebugging(articles_category());
  1448. provider()->GetDismissedSuggestionsForDebugging(
  1449. articles_category(),
  1450. base::BindOnce(
  1451. [](RemoteSuggestionsProviderImpl* provider,
  1452. RemoteSuggestionsProviderImplTest* test,
  1453. std::vector<ContentSuggestion> dismissed_suggestions) {
  1454. EXPECT_EQ(0u, dismissed_suggestions.size());
  1455. },
  1456. provider(), this));
  1457. RunUntilIdle();
  1458. }
  1459. TEST_F(RemoteSuggestionsProviderImplTest, RemoveExpiredDismissedContent) {
  1460. MakeSuggestionsProvider(
  1461. /*use_mock_remote_suggestions_status_service=*/false);
  1462. std::vector<FetchedCategory> fetched_categories;
  1463. fetched_categories.push_back(
  1464. FetchedCategoryBuilder()
  1465. .SetCategory(articles_category())
  1466. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  1467. .AddId("http://first/")
  1468. .SetExpiryDate(base::Time::Now()))
  1469. .Build());
  1470. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1471. std::move(fetched_categories));
  1472. // Load the image to store it in the database.
  1473. // TODO(tschumann): Introduce some abstraction to nicely work with image
  1474. // fetching expectations.
  1475. EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
  1476. .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
  1477. image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
  1478. gfx::Image image = FetchImage(MakeArticleID("http://first/"));
  1479. EXPECT_FALSE(image.IsEmpty());
  1480. EXPECT_EQ(1, image.Width());
  1481. // Dismiss the suggestion
  1482. provider()->DismissSuggestion(
  1483. ContentSuggestion::ID(articles_category(), "http://first/"));
  1484. // Load a different suggestion - this will clear the expired dismissed ones.
  1485. fetched_categories.clear();
  1486. fetched_categories.push_back(
  1487. FetchedCategoryBuilder()
  1488. .SetCategory(articles_category())
  1489. .AddSuggestionViaBuilder(
  1490. RemoteSuggestionBuilder().AddId("http://second/"))
  1491. .Build());
  1492. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1493. std::move(fetched_categories));
  1494. EXPECT_THAT(
  1495. provider()->GetDismissedSuggestionsForTesting(articles_category()),
  1496. IsEmpty());
  1497. // Verify the image got removed, too.
  1498. EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true))));
  1499. provider()->FetchSuggestionImage(
  1500. MakeArticleID("http://first/"),
  1501. base::BindOnce(&RemoteSuggestionsProviderImplTest::OnImageFetched,
  1502. base::Unretained(this)));
  1503. }
  1504. TEST_F(RemoteSuggestionsProviderImplTest, ExpiredContentNotRemoved) {
  1505. MakeSuggestionsProvider(
  1506. /*use_mock_remote_suggestions_status_service=*/false);
  1507. std::vector<FetchedCategory> fetched_categories;
  1508. fetched_categories.push_back(
  1509. FetchedCategoryBuilder()
  1510. .SetCategory(articles_category())
  1511. .AddSuggestionViaBuilder(
  1512. RemoteSuggestionBuilder().SetExpiryDate(base::Time::Now()))
  1513. .Build());
  1514. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1515. std::move(fetched_categories));
  1516. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1517. SizeIs(1));
  1518. }
  1519. TEST_F(RemoteSuggestionsProviderImplTest, TestSingleSource) {
  1520. MakeSuggestionsProvider(
  1521. /*use_mock_remote_suggestions_status_service=*/false);
  1522. std::vector<FetchedCategory> fetched_categories;
  1523. fetched_categories.push_back(
  1524. FetchedCategoryBuilder()
  1525. .SetCategory(articles_category())
  1526. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  1527. .AddId("http://source1.com")
  1528. .SetUrl("http://source1.com")
  1529. .SetPublisher("Source 1")
  1530. .SetAmpUrl("http://source1.amp.com"))
  1531. .Build());
  1532. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1533. std::move(fetched_categories));
  1534. ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1535. SizeIs(1));
  1536. const RemoteSuggestion& suggestion =
  1537. *provider()->GetSuggestionsForTesting(articles_category()).front();
  1538. EXPECT_EQ(suggestion.id(), "http://source1.com");
  1539. EXPECT_EQ(suggestion.url(), GURL("http://source1.com"));
  1540. EXPECT_EQ(suggestion.publisher_name(), std::string("Source 1"));
  1541. EXPECT_EQ(suggestion.amp_url(), GURL("http://source1.amp.com"));
  1542. }
  1543. TEST_F(RemoteSuggestionsProviderImplTest, TestSingleSourceWithMissingData) {
  1544. MakeSuggestionsProvider(
  1545. /*use_mock_remote_suggestions_status_service=*/false);
  1546. std::vector<FetchedCategory> fetched_categories;
  1547. fetched_categories.push_back(
  1548. FetchedCategoryBuilder()
  1549. .SetCategory(articles_category())
  1550. .AddSuggestionViaBuilder(
  1551. RemoteSuggestionBuilder().SetPublisher("").SetAmpUrl(""))
  1552. .Build());
  1553. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1554. std::move(fetched_categories));
  1555. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1556. IsEmpty());
  1557. }
  1558. TEST_F(RemoteSuggestionsProviderImplTest, LogNumArticlesHistogram) {
  1559. MakeSuggestionsProvider(
  1560. /*use_mock_remote_suggestions_status_service=*/false);
  1561. base::HistogramTester tester;
  1562. FetchTheseSuggestions(/*interactive_request=*/true,
  1563. Status(StatusCode::TEMPORARY_ERROR, "message"),
  1564. absl::nullopt);
  1565. // Error responses don't update the list of suggestions and shouldn't
  1566. // influence these metrics.
  1567. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
  1568. IsEmpty());
  1569. // Fetch error shouldn't contribute to NumArticlesFetched.
  1570. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
  1571. IsEmpty());
  1572. // TODO(tschumann): The expectations in these tests have high dependencies on
  1573. // the sequence of unrelated events. This test should be split up into
  1574. // multiple tests.
  1575. // Empty categories list.
  1576. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1577. std::vector<FetchedCategory>());
  1578. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
  1579. ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
  1580. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
  1581. IsEmpty());
  1582. // Empty articles category.
  1583. std::vector<FetchedCategory> fetched_categories;
  1584. fetched_categories.push_back(
  1585. FetchedCategoryBuilder().SetCategory(articles_category()).Build());
  1586. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1587. std::move(fetched_categories));
  1588. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
  1589. ElementsAre(base::Bucket(/*min=*/0, /*count=*/2)));
  1590. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
  1591. ElementsAre(base::Bucket(/*min=*/0, /*count=*/1)));
  1592. // Suggestion list should be populated with size 1.
  1593. const FetchedCategoryBuilder category_builder =
  1594. FetchedCategoryBuilder()
  1595. .SetCategory(articles_category())
  1596. .AddSuggestionViaBuilder(
  1597. RemoteSuggestionBuilder().AddId("http://site.com/"));
  1598. fetched_categories.clear();
  1599. fetched_categories.push_back(category_builder.Build());
  1600. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1601. std::move(fetched_categories));
  1602. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
  1603. ElementsAre(base::Bucket(/*min=*/0, /*count=*/2),
  1604. base::Bucket(/*min=*/1, /*count=*/1)));
  1605. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
  1606. ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
  1607. base::Bucket(/*min=*/1, /*count=*/1)));
  1608. // Duplicate suggestion shouldn't increase the list size.
  1609. fetched_categories.clear();
  1610. fetched_categories.push_back(category_builder.Build());
  1611. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1612. std::move(fetched_categories));
  1613. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
  1614. ElementsAre(base::Bucket(/*min=*/0, /*count=*/2),
  1615. base::Bucket(/*min=*/1, /*count=*/2)));
  1616. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
  1617. ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
  1618. base::Bucket(/*min=*/1, /*count=*/2)));
  1619. EXPECT_THAT(
  1620. tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
  1621. IsEmpty());
  1622. // Dismissing a suggestion should decrease the list size. This will only be
  1623. // logged after the next fetch.
  1624. provider()->DismissSuggestion(MakeArticleID("http://site.com/"));
  1625. fetched_categories.clear();
  1626. fetched_categories.push_back(category_builder.Build());
  1627. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1628. std::move(fetched_categories));
  1629. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticles"),
  1630. ElementsAre(base::Bucket(/*min=*/0, /*count=*/3),
  1631. base::Bucket(/*min=*/1, /*count=*/2)));
  1632. // Dismissed suggestions shouldn't influence NumArticlesFetched.
  1633. EXPECT_THAT(tester.GetAllSamples("NewTabPage.Snippets.NumArticlesFetched"),
  1634. ElementsAre(base::Bucket(/*min=*/0, /*count=*/1),
  1635. base::Bucket(/*min=*/1, /*count=*/3)));
  1636. EXPECT_THAT(
  1637. tester.GetAllSamples("NewTabPage.Snippets.NumArticlesZeroDueToDiscarded"),
  1638. ElementsAre(base::Bucket(/*min=*/1, /*count=*/1)));
  1639. }
  1640. TEST_F(RemoteSuggestionsProviderImplTest, DismissShouldRespectAllKnownUrls) {
  1641. MakeSuggestionsProvider(
  1642. /*use_mock_remote_suggestions_status_service=*/false);
  1643. const std::vector<std::string> source_urls = {
  1644. "http://mashable.com/2016/05/11/stolen",
  1645. "http://www.aol.com/article/2016/05/stolen-doggie"};
  1646. const std::vector<std::string> publishers = {"Mashable", "AOL"};
  1647. const std::vector<std::string> amp_urls = {
  1648. "http://mashable-amphtml.googleusercontent.com/1",
  1649. "http://t2.gstatic.com/images?q=tbn:3"};
  1650. // Add the suggestion from the mashable domain.
  1651. std::vector<FetchedCategory> fetched_categories;
  1652. fetched_categories.push_back(
  1653. FetchedCategoryBuilder()
  1654. .SetCategory(articles_category())
  1655. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  1656. .AddId(source_urls[0])
  1657. .AddId(source_urls[1])
  1658. .SetUrl(source_urls[0])
  1659. .SetAmpUrl(amp_urls[0])
  1660. .SetPublisher(publishers[0]))
  1661. .Build());
  1662. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1663. std::move(fetched_categories));
  1664. ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1665. SizeIs(1));
  1666. // Dismiss the suggestion via the mashable source corpus ID.
  1667. provider()->DismissSuggestion(MakeArticleID(source_urls[0]));
  1668. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1669. IsEmpty());
  1670. // The same article from the AOL domain should now be detected as dismissed.
  1671. fetched_categories.clear();
  1672. fetched_categories.push_back(
  1673. FetchedCategoryBuilder()
  1674. .SetCategory(articles_category())
  1675. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  1676. .AddId(source_urls[0])
  1677. .AddId(source_urls[1])
  1678. .SetUrl(source_urls[1])
  1679. .SetAmpUrl(amp_urls[1])
  1680. .SetPublisher(publishers[1]))
  1681. .Build());
  1682. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1683. std::move(fetched_categories));
  1684. EXPECT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1685. IsEmpty());
  1686. }
  1687. TEST_F(RemoteSuggestionsProviderImplTest, ImageReturnedWithTheSameId) {
  1688. MakeSuggestionsProvider(
  1689. /*use_mock_remote_suggestions_status_service=*/false);
  1690. std::vector<FetchedCategory> fetched_categories;
  1691. fetched_categories.push_back(
  1692. FetchedCategoryBuilder()
  1693. .SetCategory(articles_category())
  1694. .AddSuggestionViaBuilder(
  1695. RemoteSuggestionBuilder().AddId(kSuggestionUrl))
  1696. .Build());
  1697. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1698. std::move(fetched_categories));
  1699. EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
  1700. .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
  1701. gfx::Image image = FetchImage(MakeArticleID(kSuggestionUrl));
  1702. // Check that the image by ServeOneByOneImage is really served.
  1703. EXPECT_EQ(1, image.Width());
  1704. }
  1705. TEST_F(RemoteSuggestionsProviderImplTest, EmptyImageReturnedForNonExistentId) {
  1706. MakeSuggestionsProvider(
  1707. /*use_mock_remote_suggestions_status_service=*/false);
  1708. // Create a non-empty image so that we can test the image gets updated.
  1709. gfx::Image image = gfx::test::CreateImage(1, 1);
  1710. MockFunction<void(const gfx::Image&)> image_fetched;
  1711. EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
  1712. provider()->FetchSuggestionImage(
  1713. MakeArticleID("nonexistent"),
  1714. base::BindOnce(&MockFunction<void(const gfx::Image&)>::Call,
  1715. base::Unretained(&image_fetched)));
  1716. RunUntilIdle();
  1717. EXPECT_TRUE(image.IsEmpty());
  1718. }
  1719. TEST_F(RemoteSuggestionsProviderImplTest,
  1720. FetchingUnknownImageIdShouldNotHitDatabase) {
  1721. // Testing that the provider is not accessing the database is tricky.
  1722. // Therefore, we simply put in some data making sure that if the provider asks
  1723. // the database, it will get a wrong answer.
  1724. MakeSuggestionsProvider(
  1725. /*use_mock_remote_suggestions_status_service=*/false);
  1726. ContentSuggestion::ID unknown_id = MakeArticleID(kSuggestionUrl2);
  1727. database()->SaveImage(unknown_id.id_within_category(), "some image blob");
  1728. // Set up the image decoder to always return the 1x1 test image.
  1729. image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
  1730. // Create a non-empty image so that we can test the image gets updated.
  1731. gfx::Image image = gfx::test::CreateImage(2, 2);
  1732. MockFunction<void(const gfx::Image&)> image_fetched;
  1733. EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
  1734. provider()->FetchSuggestionImage(
  1735. MakeArticleID(kSuggestionUrl2),
  1736. base::BindOnce(&MockFunction<void(const gfx::Image&)>::Call,
  1737. base::Unretained(&image_fetched)));
  1738. RunUntilIdle();
  1739. EXPECT_TRUE(image.IsEmpty()) << "got image with width: " << image.Width();
  1740. }
  1741. TEST_F(RemoteSuggestionsProviderImplTest, ClearHistoryRemovesAllSuggestions) {
  1742. MakeSuggestionsProvider(
  1743. /*use_mock_remote_suggestions_status_service=*/false);
  1744. std::vector<FetchedCategory> fetched_categories;
  1745. fetched_categories.push_back(
  1746. FetchedCategoryBuilder()
  1747. .SetCategory(articles_category())
  1748. .AddSuggestionViaBuilder(
  1749. RemoteSuggestionBuilder().AddId("http://first/"))
  1750. .AddSuggestionViaBuilder(
  1751. RemoteSuggestionBuilder().AddId("http://second/"))
  1752. .Build());
  1753. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1754. std::move(fetched_categories));
  1755. ASSERT_THAT(provider()->GetSuggestionsForTesting(articles_category()),
  1756. SizeIs(2));
  1757. provider()->DismissSuggestion(MakeArticleID("http://first/"));
  1758. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  1759. Not(IsEmpty()));
  1760. ASSERT_THAT(
  1761. provider()->GetDismissedSuggestionsForTesting(articles_category()),
  1762. SizeIs(1));
  1763. base::Time begin = base::Time::FromTimeT(123),
  1764. end = base::Time::FromTimeT(456);
  1765. base::RepeatingCallback<bool(const GURL& url)> filter;
  1766. provider()->ClearHistory(begin, end, filter);
  1767. // Verify that the observer received the update with the empty data as well.
  1768. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  1769. IsEmpty());
  1770. EXPECT_THAT(
  1771. provider()->GetDismissedSuggestionsForTesting(articles_category()),
  1772. IsEmpty());
  1773. }
  1774. TEST_F(RemoteSuggestionsProviderImplTest,
  1775. ShouldKeepArticlesCategoryAvailableAfterClearHistory) {
  1776. // If the provider marks that category as NOT_PROVIDED, then it won't be shown
  1777. // at all in the UI and the user cannot load new data :-/.
  1778. MakeSuggestionsProvider(
  1779. /*use_mock_remote_suggestions_status_service=*/false);
  1780. ASSERT_THAT(observer().StatusForCategory(articles_category()),
  1781. Eq(CategoryStatus::AVAILABLE));
  1782. provider()->ClearHistory(base::Time::UnixEpoch(), base::Time::Max(),
  1783. base::RepeatingCallback<bool(const GURL& url)>());
  1784. EXPECT_THAT(observer().StatusForCategory(articles_category()),
  1785. Eq(CategoryStatus::AVAILABLE));
  1786. }
  1787. TEST_F(RemoteSuggestionsProviderImplTest, ShouldClearOrphanedImagesOnRestart) {
  1788. MakeSuggestionsProvider(
  1789. /*use_mock_remote_suggestions_status_service=*/false);
  1790. std::vector<FetchedCategory> fetched_categories;
  1791. fetched_categories.push_back(
  1792. FetchedCategoryBuilder()
  1793. .SetCategory(articles_category())
  1794. .AddSuggestionViaBuilder(
  1795. RemoteSuggestionBuilder().AddId(kSuggestionUrl))
  1796. .Build());
  1797. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1798. std::move(fetched_categories));
  1799. EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _))
  1800. .WillOnce(WithArgs<1, 2>(Invoke(&ServeOneByOneImage)));
  1801. image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
  1802. gfx::Image image = FetchImage(MakeArticleID(kSuggestionUrl));
  1803. EXPECT_EQ(1, image.Width());
  1804. EXPECT_FALSE(image.IsEmpty());
  1805. // Send new suggestion which don't include the suggestion referencing the
  1806. // image.
  1807. fetched_categories.clear();
  1808. fetched_categories.push_back(
  1809. FetchedCategoryBuilder()
  1810. .SetCategory(articles_category())
  1811. .AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
  1812. "http://something.com/pletely/unrelated"))
  1813. .Build());
  1814. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1815. std::move(fetched_categories));
  1816. // The image should still be available until a restart happens.
  1817. EXPECT_FALSE(FetchImage(MakeArticleID(kSuggestionUrl)).IsEmpty());
  1818. ResetSuggestionsProvider(
  1819. /*use_mock_remote_suggestions_status_service=*/false);
  1820. // After the restart, the image should be garbage collected.
  1821. EXPECT_CALL(*this, OnImageFetched(Property(&gfx::Image::IsEmpty, Eq(true))));
  1822. provider()->FetchSuggestionImage(
  1823. MakeArticleID(kSuggestionUrl),
  1824. base::BindOnce(&RemoteSuggestionsProviderImplTest::OnImageFetched,
  1825. base::Unretained(this)));
  1826. }
  1827. TEST_F(RemoteSuggestionsProviderImplTest,
  1828. ShouldHandleMoreThanMaxSuggestionsInResponse) {
  1829. MakeSuggestionsProvider(
  1830. /*use_mock_remote_suggestions_status_service=*/false);
  1831. std::vector<FetchedCategory> fetched_categories;
  1832. FetchedCategoryBuilder category_builder;
  1833. category_builder.SetCategory(articles_category());
  1834. for (int i = 0;
  1835. i < provider()->GetMaxNormalFetchSuggestionCountForTesting() + 1; ++i) {
  1836. category_builder.AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
  1837. base::StringPrintf("http://localhost/suggestion-id-%d", i)));
  1838. }
  1839. fetched_categories.push_back(category_builder.Build());
  1840. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  1841. std::move(fetched_categories));
  1842. // TODO(tschumann): We should probably trim out any additional results and
  1843. // only serve the MaxSuggestionCount items.
  1844. EXPECT_THAT(
  1845. provider()->GetSuggestionsForTesting(articles_category()),
  1846. SizeIs(provider()->GetMaxNormalFetchSuggestionCountForTesting() + 1));
  1847. }
  1848. TEST_F(RemoteSuggestionsProviderImplTest,
  1849. StoreLastSuccessfullBackgroundFetchTime) {
  1850. // On initialization of the RemoteSuggestionsProviderImpl a background fetch
  1851. // is triggered since the suggestions DB is empty. Therefore the provider must
  1852. // not be initialized until the test clock is set.
  1853. MakeSuggestionsProviderWithoutInitialization(
  1854. /*use_mock_remote_suggestions_status_service=*/false);
  1855. base::SimpleTestClock simple_test_clock;
  1856. provider()->SetClockForTesting(&simple_test_clock);
  1857. // Test that the preference is correctly initialized with the default value 0.
  1858. EXPECT_EQ(
  1859. 0, pref_service()->GetInt64(prefs::kLastSuccessfulBackgroundFetchTime));
  1860. WaitForSuggestionsProviderInitialization();
  1861. EXPECT_EQ(
  1862. SerializeTime(simple_test_clock.Now()),
  1863. pref_service()->GetInt64(prefs::kLastSuccessfulBackgroundFetchTime));
  1864. // Advance the time and check whether the time was updated correctly after the
  1865. // background fetch.
  1866. simple_test_clock.Advance(base::Hours(1));
  1867. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
  1868. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  1869. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
  1870. .RetiresOnSaturation();
  1871. provider()->RefetchInTheBackground(
  1872. RemoteSuggestionsProvider::FetchStatusCallback());
  1873. RunUntilIdle();
  1874. std::move(snippets_callback).Run(Status::Success(), absl::nullopt);
  1875. // TODO(jkrcal): Move together with the pref storage into the scheduler.
  1876. EXPECT_EQ(
  1877. SerializeTime(simple_test_clock.Now()),
  1878. pref_service()->GetInt64(prefs::kLastSuccessfulBackgroundFetchTime));
  1879. // TODO(markusheintz): Add a test that simulates a browser restart once the
  1880. // scheduler refactoring is done (crbug.com/672434).
  1881. }
  1882. TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenReady) {
  1883. MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
  1884. // Should be called when becoming ready.
  1885. EXPECT_CALL(*scheduler(), OnProviderActivated());
  1886. WaitForSuggestionsProviderInitialization();
  1887. }
  1888. TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerOnError) {
  1889. MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
  1890. // Should be called on error.
  1891. EXPECT_CALL(*scheduler(), OnProviderDeactivated());
  1892. provider()->EnterState(RemoteSuggestionsProviderImpl::State::ERROR_OCCURRED);
  1893. }
  1894. TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenDisabled) {
  1895. MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
  1896. // Should be called when becoming disabled. First deactivate and only after
  1897. // that clear the suggestions so that they are not fetched again.
  1898. {
  1899. InSequence s;
  1900. EXPECT_CALL(*scheduler(), OnProviderDeactivated());
  1901. ASSERT_THAT(provider()->ready(), Eq(false));
  1902. EXPECT_CALL(*scheduler(), OnSuggestionsCleared());
  1903. }
  1904. provider()->EnterState(RemoteSuggestionsProviderImpl::State::DISABLED);
  1905. }
  1906. TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenHistoryCleared) {
  1907. MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
  1908. // Initiate the provider so that it is already READY.
  1909. EXPECT_CALL(*scheduler(), OnProviderActivated());
  1910. WaitForSuggestionsProviderInitialization();
  1911. // The scheduler should be notified of clearing the history.
  1912. EXPECT_CALL(*scheduler(), OnHistoryCleared());
  1913. provider()->ClearHistory(GetDefaultCreationTime(), GetDefaultExpirationTime(),
  1914. base::RepeatingCallback<bool(const GURL& url)>());
  1915. }
  1916. TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenSignedIn) {
  1917. MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
  1918. // Initiate the provider so that it is already READY.
  1919. EXPECT_CALL(*scheduler(), OnProviderActivated());
  1920. WaitForSuggestionsProviderInitialization();
  1921. // The scheduler should be notified of clearing the history.
  1922. EXPECT_CALL(*scheduler(), OnSuggestionsCleared());
  1923. provider()->OnStatusChanged(RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN,
  1924. RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT);
  1925. }
  1926. TEST_F(RemoteSuggestionsProviderImplTest, CallsSchedulerWhenSignedOut) {
  1927. MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
  1928. // Initiate the provider so that it is already READY.
  1929. EXPECT_CALL(*scheduler(), OnProviderActivated());
  1930. WaitForSuggestionsProviderInitialization();
  1931. // The scheduler should be notified of clearing the history.
  1932. EXPECT_CALL(*scheduler(), OnSuggestionsCleared());
  1933. provider()->OnStatusChanged(RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT,
  1934. RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN);
  1935. }
  1936. TEST_F(RemoteSuggestionsProviderImplTest,
  1937. RestartsFetchWhenSignedInWhileFetching) {
  1938. MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
  1939. // Initiate the provider so that it is already READY.
  1940. EXPECT_CALL(*scheduler(), OnProviderActivated());
  1941. WaitForSuggestionsProviderInitialization();
  1942. // Initiate the fetch.
  1943. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback;
  1944. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  1945. .WillOnce(MoveSecondArgumentPointeeTo(&snippets_callback))
  1946. .RetiresOnSaturation();
  1947. provider()->FetchSuggestions(
  1948. /*interactive_request=*/false,
  1949. RemoteSuggestionsProvider::FetchStatusCallback());
  1950. // The scheduler should be notified of clearing the suggestions.
  1951. EXPECT_CALL(*scheduler(), OnSuggestionsCleared());
  1952. provider()->OnStatusChanged(RemoteSuggestionsStatus::ENABLED_AND_SIGNED_OUT,
  1953. RemoteSuggestionsStatus::ENABLED_AND_SIGNED_IN);
  1954. // Once we signal the first fetch to be finished (calling snippets_callback
  1955. // below), a new fetch should get triggered.
  1956. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _)).Times(1);
  1957. std::move(snippets_callback)
  1958. .Run(Status::Success(), std::vector<FetchedCategory>());
  1959. }
  1960. TEST_F(RemoteSuggestionsProviderImplTest,
  1961. IgnoresResultsWhenHistoryClearedWhileFetching) {
  1962. MakeSuggestionsProviderWithoutInitializationWithStrictScheduler();
  1963. // Initiate the provider so that it is already READY.
  1964. EXPECT_CALL(*scheduler(), OnProviderActivated());
  1965. WaitForSuggestionsProviderInitialization();
  1966. // Initiate the fetch.
  1967. RemoteSuggestionsFetcher::SnippetsAvailableCallback snippets_callback =
  1968. FetchSuggestionsAndGetResponseCallback(/*interactive_request=*/false);
  1969. // The scheduler should be notified of clearing the history.
  1970. EXPECT_CALL(*scheduler(), OnHistoryCleared());
  1971. provider()->ClearHistory(GetDefaultCreationTime(), GetDefaultExpirationTime(),
  1972. base::RepeatingCallback<bool(const GURL& url)>());
  1973. // Once the fetch finishes, the returned suggestions are ignored.
  1974. FetchedCategoryBuilder category_builder;
  1975. category_builder.SetCategory(articles_category());
  1976. category_builder.AddSuggestionViaBuilder(
  1977. RemoteSuggestionBuilder().AddId(base::StringPrintf("http://abc.com")));
  1978. std::vector<FetchedCategory> fetched_categories;
  1979. fetched_categories.push_back(category_builder.Build());
  1980. std::move(snippets_callback)
  1981. .Run(Status::Success(), std::move(fetched_categories));
  1982. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  1983. SizeIs(0));
  1984. }
  1985. TEST_F(RemoteSuggestionsProviderImplTest,
  1986. ShouldExcludeKnownSuggestionsWithoutTruncatingWhenFetchingMore) {
  1987. MakeSuggestionsProvider(
  1988. /*use_mock_remote_suggestions_status_service=*/false);
  1989. std::set<std::string> known_ids;
  1990. for (int i = 0; i < 200; ++i) {
  1991. known_ids.insert(base::NumberToString(i));
  1992. }
  1993. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  1994. .WillOnce(Return(true))
  1995. .RetiresOnSaturation();
  1996. EXPECT_CALL(*mock_suggestions_fetcher(),
  1997. FetchSnippets(Field(&RequestParams::excluded_ids, known_ids), _));
  1998. provider()->Fetch(
  1999. articles_category(), known_ids,
  2000. base::BindOnce([](Status status_code,
  2001. std::vector<ContentSuggestion> suggestions) {}));
  2002. }
  2003. TEST_F(RemoteSuggestionsProviderImplTest,
  2004. ShouldExcludeDismissedSuggestionsWhenFetchingMore) {
  2005. MakeSuggestionsProvider(
  2006. /*use_mock_remote_suggestions_status_service=*/false);
  2007. std::vector<FetchedCategory> fetched_categories;
  2008. fetched_categories.push_back(
  2009. FetchedCategoryBuilder()
  2010. .SetCategory(articles_category())
  2011. .AddSuggestionViaBuilder(
  2012. RemoteSuggestionBuilder().AddId("http://abc.com/"))
  2013. .Build());
  2014. ASSERT_TRUE(fetched_categories[0].suggestions[0]->is_complete());
  2015. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2016. std::move(fetched_categories));
  2017. provider()->DismissSuggestion(MakeArticleID("http://abc.com/"));
  2018. std::set<std::string> expected_excluded_ids({"http://abc.com/"});
  2019. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  2020. .WillOnce(Return(true))
  2021. .RetiresOnSaturation();
  2022. EXPECT_CALL(
  2023. *mock_suggestions_fetcher(),
  2024. FetchSnippets(Field(&RequestParams::excluded_ids, expected_excluded_ids),
  2025. _));
  2026. provider()->Fetch(
  2027. articles_category(), std::set<std::string>(),
  2028. base::BindOnce([](Status status_code,
  2029. std::vector<ContentSuggestion> suggestions) {}));
  2030. }
  2031. TEST_F(RemoteSuggestionsProviderImplTest,
  2032. ShouldTruncateExcludedDismissedSuggestionsWhenFetchingMore) {
  2033. MakeSuggestionsProvider(
  2034. /*use_mock_remote_suggestions_status_service=*/false);
  2035. std::vector<FetchedCategory> fetched_categories;
  2036. FetchedCategoryBuilder category_builder;
  2037. category_builder.SetCategory(articles_category());
  2038. const int kSuggestionsCount = kMaxExcludedDismissedIds + 1;
  2039. for (int i = 0; i < kSuggestionsCount; ++i) {
  2040. category_builder.AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
  2041. base::StringPrintf("http://abc.com/%d/", i)));
  2042. }
  2043. fetched_categories.push_back(category_builder.Build());
  2044. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2045. std::move(fetched_categories));
  2046. // Dismiss them.
  2047. for (int i = 0; i < kSuggestionsCount; ++i) {
  2048. provider()->DismissSuggestion(
  2049. MakeArticleID(base::StringPrintf("http://abc.com/%d/", i)));
  2050. }
  2051. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  2052. .WillOnce(Return(true))
  2053. .RetiresOnSaturation();
  2054. EXPECT_CALL(*mock_suggestions_fetcher(),
  2055. FetchSnippets(Field(&RequestParams::excluded_ids,
  2056. SizeIs(kMaxExcludedDismissedIds)),
  2057. _));
  2058. provider()->Fetch(
  2059. articles_category(), std::set<std::string>(),
  2060. base::BindOnce([](Status status_code,
  2061. std::vector<ContentSuggestion> suggestions) {}));
  2062. }
  2063. TEST_F(RemoteSuggestionsProviderImplTest,
  2064. ShouldPreferLatestExcludedDismissedSuggestionsWhenFetchingMore) {
  2065. MakeSuggestionsProvider(
  2066. /*use_mock_remote_suggestions_status_service=*/false);
  2067. std::vector<FetchedCategory> fetched_categories;
  2068. FetchedCategoryBuilder category_builder;
  2069. category_builder.SetCategory(articles_category());
  2070. const int kSuggestionsCount = kMaxExcludedDismissedIds + 1;
  2071. for (int i = 0; i < kSuggestionsCount; ++i) {
  2072. category_builder.AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
  2073. base::StringPrintf("http://abc.com/%d/", i)));
  2074. }
  2075. fetched_categories.push_back(category_builder.Build());
  2076. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2077. std::move(fetched_categories));
  2078. // Dismiss them in reverse order.
  2079. std::string first_dismissed_suggestion_id;
  2080. for (int i = kSuggestionsCount - 1; i >= 0; --i) {
  2081. const std::string id = base::StringPrintf("http://abc.com/%d/", i);
  2082. provider()->DismissSuggestion(MakeArticleID(id));
  2083. if (first_dismissed_suggestion_id.empty()) {
  2084. first_dismissed_suggestion_id = id;
  2085. }
  2086. }
  2087. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  2088. .WillOnce(Return(true))
  2089. .RetiresOnSaturation();
  2090. // The oldest dismissed suggestion should be absent, because there are
  2091. // |kMaxExcludedDismissedIds| newer dismissed suggestions.
  2092. EXPECT_CALL(*mock_suggestions_fetcher(),
  2093. FetchSnippets(Field(&RequestParams::excluded_ids,
  2094. Not(Contains(first_dismissed_suggestion_id))),
  2095. _));
  2096. provider()->Fetch(
  2097. articles_category(), std::set<std::string>(),
  2098. base::BindOnce([](Status status_code,
  2099. std::vector<ContentSuggestion> suggestions) {}));
  2100. }
  2101. TEST_F(RemoteSuggestionsProviderImplTest,
  2102. ShouldExcludeDismissedFetchedMoreSuggestions) {
  2103. // This tests verifies that dismissing an article seen in the fetch-more state
  2104. // (i.e., an article that has been fetched via fetch-more) will be excluded in
  2105. // future fetches.
  2106. MakeSuggestionsProvider(
  2107. /*use_mock_remote_suggestions_status_service=*/false);
  2108. FetchedCategoryBuilder category_builder;
  2109. category_builder.SetCategory(articles_category());
  2110. const int kSuggestionsCount = 5;
  2111. for (int i = 0; i < kSuggestionsCount; ++i) {
  2112. category_builder.AddSuggestionViaBuilder(RemoteSuggestionBuilder().AddId(
  2113. base::StringPrintf("http://abc.com/%d", i)));
  2114. }
  2115. std::vector<FetchedCategory> fetched_categories;
  2116. fetched_categories.push_back(category_builder.Build());
  2117. FetchMoreTheseSuggestions(
  2118. articles_category(),
  2119. /*known_suggestion_ids=*/std::set<std::string>(),
  2120. /*fetch_done_callback=*/
  2121. base::BindOnce(
  2122. [](Status status, std::vector<ContentSuggestion> suggestions) {
  2123. ASSERT_THAT(suggestions, SizeIs(5));
  2124. }),
  2125. Status::Success(), std::move(fetched_categories));
  2126. // Dismiss them.
  2127. for (int i = 0; i < kSuggestionsCount; ++i) {
  2128. provider()->DismissSuggestion(
  2129. MakeArticleID(base::StringPrintf("http://abc.com/%d", i)));
  2130. }
  2131. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  2132. .WillOnce(Return(true))
  2133. .RetiresOnSaturation();
  2134. EXPECT_CALL(
  2135. *mock_suggestions_fetcher(),
  2136. FetchSnippets(Field(&RequestParams::excluded_ids,
  2137. ElementsAre("http://abc.com/0", "http://abc.com/1",
  2138. "http://abc.com/2", "http://abc.com/3",
  2139. "http://abc.com/4")),
  2140. _));
  2141. provider()->Fetch(
  2142. articles_category(), std::set<std::string>(),
  2143. base::BindOnce([](Status status_code,
  2144. std::vector<ContentSuggestion> suggestions) {}));
  2145. }
  2146. TEST_F(RemoteSuggestionsProviderImplTest, ClearDismissedAfterFetchMore) {
  2147. MakeSuggestionsProvider(
  2148. /*use_mock_remote_suggestions_status_service=*/false);
  2149. FetchedCategoryBuilder category_builder;
  2150. category_builder.SetCategory(articles_category());
  2151. category_builder.AddSuggestionViaBuilder(
  2152. RemoteSuggestionBuilder().AddId(base::StringPrintf("http://abc.com")));
  2153. std::vector<FetchedCategory> fetched_categories;
  2154. fetched_categories.push_back(category_builder.Build());
  2155. FetchMoreTheseSuggestions(
  2156. articles_category(),
  2157. /*known_suggestion_ids=*/std::set<std::string>(),
  2158. /*fetch_done_callback=*/
  2159. base::BindOnce(
  2160. [](Status status, std::vector<ContentSuggestion> suggestions) {}),
  2161. Status::Success(), std::move(fetched_categories));
  2162. provider()->DismissSuggestion(MakeArticleID("http://abc.com"));
  2163. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  2164. .WillRepeatedly(Return(true));
  2165. // Make sure the article got marked as dismissed.
  2166. InSequence s;
  2167. EXPECT_CALL(*mock_suggestions_fetcher(),
  2168. FetchSnippets(Field(&RequestParams::excluded_ids,
  2169. ElementsAre("http://abc.com")),
  2170. _));
  2171. provider()->Fetch(
  2172. articles_category(), std::set<std::string>(),
  2173. base::BindOnce([](Status status_code,
  2174. std::vector<ContentSuggestion> suggestions) {}));
  2175. // Clear dismissals.
  2176. provider()->ClearDismissedSuggestionsForDebugging(articles_category());
  2177. // Fetch and verify the article is not marked as dismissed anymore.
  2178. EXPECT_CALL(*mock_suggestions_fetcher(),
  2179. FetchSnippets(Field(&RequestParams::excluded_ids, IsEmpty()), _));
  2180. provider()->Fetch(
  2181. articles_category(), std::set<std::string>(),
  2182. base::BindOnce([](Status status_code,
  2183. std::vector<ContentSuggestion> suggestions) {}));
  2184. }
  2185. TEST_F(RemoteSuggestionsProviderImplTest,
  2186. ShouldExcludeDismissedSuggestionsFromAllCategoriesWhenFetchingMore) {
  2187. MakeSuggestionsProvider(
  2188. /*use_mock_remote_suggestions_status_service=*/false);
  2189. // Add article suggestions.
  2190. std::vector<FetchedCategory> fetched_categories;
  2191. FetchedCategoryBuilder first_category_builder;
  2192. first_category_builder.SetCategory(articles_category());
  2193. const int kSuggestionsPerCategory = 2;
  2194. for (int i = 0; i < kSuggestionsPerCategory; ++i) {
  2195. first_category_builder.AddSuggestionViaBuilder(
  2196. RemoteSuggestionBuilder().AddId(
  2197. base::StringPrintf("http://abc.com/%d/", i)));
  2198. }
  2199. fetched_categories.push_back(first_category_builder.Build());
  2200. // Add other category suggestions.
  2201. FetchedCategoryBuilder second_category_builder;
  2202. second_category_builder.SetCategory(
  2203. Category::FromRemoteCategory(kOtherCategoryId));
  2204. for (int i = 0; i < kSuggestionsPerCategory; ++i) {
  2205. second_category_builder.AddSuggestionViaBuilder(
  2206. RemoteSuggestionBuilder().AddId(
  2207. base::StringPrintf("http://other.com/%d/", i)));
  2208. }
  2209. fetched_categories.push_back(second_category_builder.Build());
  2210. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2211. std::move(fetched_categories));
  2212. // Dismiss all suggestions.
  2213. std::set<std::string> expected_excluded_ids;
  2214. for (int i = 0; i < kSuggestionsPerCategory; ++i) {
  2215. const std::string article_id = base::StringPrintf("http://abc.com/%d/", i);
  2216. provider()->DismissSuggestion(MakeArticleID(article_id));
  2217. expected_excluded_ids.insert(article_id);
  2218. const std::string other_id = base::StringPrintf("http://other.com/%d/", i);
  2219. provider()->DismissSuggestion(MakeOtherID(other_id));
  2220. expected_excluded_ids.insert(other_id);
  2221. }
  2222. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  2223. .WillOnce(Return(true))
  2224. .RetiresOnSaturation();
  2225. // Dismissed suggestions from all categories must be excluded (but not only
  2226. // target category).
  2227. EXPECT_CALL(
  2228. *mock_suggestions_fetcher(),
  2229. FetchSnippets(Field(&RequestParams::excluded_ids, expected_excluded_ids),
  2230. _));
  2231. provider()->Fetch(
  2232. articles_category(), std::set<std::string>(),
  2233. base::BindOnce([](Status status_code,
  2234. std::vector<ContentSuggestion> suggestions) {}));
  2235. }
  2236. TEST_F(RemoteSuggestionsProviderImplTest,
  2237. ShouldPreferTargetCategoryExcludedDismissedSuggestionsWhenFetchingMore) {
  2238. MakeSuggestionsProvider(
  2239. /*use_mock_remote_suggestions_status_service=*/false);
  2240. // Add article suggestions.
  2241. std::vector<FetchedCategory> fetched_categories;
  2242. fetched_categories.push_back(FetchedCategory(
  2243. articles_category(),
  2244. BuildRemoteCategoryInfo(u"title",
  2245. /*allow_fetching_more_results=*/true)));
  2246. for (int i = 0; i < kMaxExcludedDismissedIds; ++i) {
  2247. fetched_categories[0].suggestions.push_back(CreateTestRemoteSuggestion(
  2248. base::StringPrintf("http://abc.com/%d/", i)));
  2249. }
  2250. // Add other category suggestion.
  2251. fetched_categories.push_back(FetchedCategory(
  2252. Category::FromRemoteCategory(kOtherCategoryId),
  2253. BuildRemoteCategoryInfo(u"title",
  2254. /*allow_fetching_more_results=*/true)));
  2255. fetched_categories[1].suggestions.push_back(
  2256. CreateTestRemoteSuggestion("http://other.com/"));
  2257. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2258. std::move(fetched_categories));
  2259. // Dismiss article suggestions first.
  2260. for (int i = 0; i < kMaxExcludedDismissedIds; ++i) {
  2261. provider()->DismissSuggestion(
  2262. MakeArticleID(base::StringPrintf("http://abc.com/%d/", i)));
  2263. }
  2264. // Then dismiss other category suggestion.
  2265. provider()->DismissSuggestion(MakeOtherID("http://other.com/"));
  2266. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  2267. .WillOnce(Return(true))
  2268. .RetiresOnSaturation();
  2269. // The other category dismissed suggestion should be absent, because the fetch
  2270. // is for articles and there are |kMaxExcludedDismissedIds| dismissed
  2271. // suggestions there.
  2272. EXPECT_CALL(*mock_suggestions_fetcher(),
  2273. FetchSnippets(Field(&RequestParams::excluded_ids,
  2274. Not(Contains("http://other.com/"))),
  2275. _));
  2276. provider()->Fetch(
  2277. articles_category(), std::set<std::string>(),
  2278. base::BindOnce([](Status status_code,
  2279. std::vector<ContentSuggestion> suggestions) {}));
  2280. }
  2281. TEST_F(RemoteSuggestionsProviderImplTest,
  2282. ShouldRestoreSuggestionsFromDatabaseInSameOrderAsFetched) {
  2283. MakeSuggestionsProvider(
  2284. /*use_mock_remote_suggestions_status_service=*/false);
  2285. std::vector<FetchedCategory> fetched_categories;
  2286. fetched_categories.push_back(
  2287. FetchedCategoryBuilder()
  2288. .SetCategory(articles_category())
  2289. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  2290. .AddId("http://1.com")
  2291. .SetUrl("http://1.com")
  2292. .SetScore(1))
  2293. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  2294. .AddId("http://3.com")
  2295. .SetUrl("http://3.com")
  2296. .SetScore(3))
  2297. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  2298. .AddId("http://2.com")
  2299. .SetUrl("http://2.com")
  2300. .SetScore(2))
  2301. .Build());
  2302. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2303. std::move(fetched_categories));
  2304. ASSERT_THAT(
  2305. observer().SuggestionsForCategory(articles_category()),
  2306. ElementsAre(
  2307. Property(&ContentSuggestion::id, MakeArticleID("http://1.com")),
  2308. Property(&ContentSuggestion::id, MakeArticleID("http://3.com")),
  2309. Property(&ContentSuggestion::id, MakeArticleID("http://2.com"))));
  2310. ResetSuggestionsProvider(
  2311. /*use_mock_remote_suggestions_status_service=*/false);
  2312. EXPECT_THAT(
  2313. observer().SuggestionsForCategory(articles_category()),
  2314. ElementsAre(
  2315. Property(&ContentSuggestion::id, MakeArticleID("http://1.com")),
  2316. Property(&ContentSuggestion::id, MakeArticleID("http://3.com")),
  2317. Property(&ContentSuggestion::id, MakeArticleID("http://2.com"))));
  2318. }
  2319. // TODO(vitaliii): Remove this test (as well as the score fallback) in M64.
  2320. TEST_F(RemoteSuggestionsProviderImplTest,
  2321. ShouldSortSuggestionsWithoutRanksByScore) {
  2322. MakeSuggestionsProvider(
  2323. /*use_mock_remote_suggestions_status_service=*/false);
  2324. // Write suggestions without ranks (i.e. with default values) directly to
  2325. // database to simulate behaviour of M61.
  2326. std::vector<std::unique_ptr<RemoteSuggestion>> suggestions;
  2327. suggestions.push_back(RemoteSuggestionBuilder()
  2328. .AddId("http://1.com")
  2329. .SetUrl("http://1.com")
  2330. .SetScore(1)
  2331. .SetRank(std::numeric_limits<int>::max())
  2332. .Build());
  2333. suggestions.push_back(RemoteSuggestionBuilder()
  2334. .AddId("http://3.com")
  2335. .SetUrl("http://3.com")
  2336. .SetScore(3)
  2337. .SetRank(std::numeric_limits<int>::max())
  2338. .Build());
  2339. suggestions.push_back(RemoteSuggestionBuilder()
  2340. .AddId("http://2.com")
  2341. .SetUrl("http://2.com")
  2342. .SetScore(2)
  2343. .SetRank(std::numeric_limits<int>::max())
  2344. .Build());
  2345. database()->SaveSnippets(suggestions);
  2346. ResetSuggestionsProvider(
  2347. /*use_mock_remote_suggestions_status_service=*/false);
  2348. EXPECT_THAT(
  2349. observer().SuggestionsForCategory(articles_category()),
  2350. ElementsAre(
  2351. Property(&ContentSuggestion::id, MakeArticleID("http://3.com")),
  2352. Property(&ContentSuggestion::id, MakeArticleID("http://2.com")),
  2353. Property(&ContentSuggestion::id, MakeArticleID("http://1.com"))));
  2354. }
  2355. TEST_F(RemoteSuggestionsProviderImplTest,
  2356. FetchingShouldNotTriggerNotificationWhenDisabled) {
  2357. SetTriggeringNotificationsAndSubscriptionParams(
  2358. /*fetched_notifications_enabled=*/false,
  2359. /*pushed_notifications_enabled=*/true,
  2360. /*subscribe_signed_in=*/true,
  2361. /*subscribe_signed_out=*/true);
  2362. MakeSuggestionsProvider(
  2363. /*use_mock_remote_suggestions_status_service=*/false);
  2364. // Fetch a suggestion triggering a notification.
  2365. std::vector<FetchedCategory> fetched_categories;
  2366. fetched_categories.push_back(
  2367. FetchedCategoryBuilder()
  2368. .SetCategory(articles_category())
  2369. .AddSuggestionViaBuilder(
  2370. RemoteSuggestionBuilder()
  2371. .AddId("http://fetched.com/")
  2372. .SetUrl("http://fetched.com/")
  2373. .SetShouldNotify(true)
  2374. .SetNotificationDeadline(GetDefaultExpirationTime()))
  2375. .Build());
  2376. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2377. std::move(fetched_categories));
  2378. // The fetched suggestion should not trigger a notification because such
  2379. // notifications are disabled.
  2380. EXPECT_THAT(
  2381. observer().SuggestionsForCategory(articles_category()),
  2382. ElementsAre(Property(&ContentSuggestion::notification_extra, nullptr)));
  2383. }
  2384. TEST_F(RemoteSuggestionsProviderImplTest,
  2385. FetchingShouldTriggerNotificationEvenIfPrependedNotificationsDisabled) {
  2386. SetTriggeringNotificationsAndSubscriptionParams(
  2387. /*fetched_notifications_enabled=*/true,
  2388. /*pushed_notifications_enabled=*/false,
  2389. /*subscribe_signed_in=*/true,
  2390. /*subscribe_signed_out=*/true);
  2391. MakeSuggestionsProvider(
  2392. /*use_mock_remote_suggestions_status_service=*/false);
  2393. // Fetch a suggestion triggering a notification.
  2394. std::vector<FetchedCategory> fetched_categories;
  2395. fetched_categories.push_back(
  2396. FetchedCategoryBuilder()
  2397. .SetCategory(articles_category())
  2398. .AddSuggestionViaBuilder(
  2399. RemoteSuggestionBuilder()
  2400. .AddId("http://fetched.com/")
  2401. .SetUrl("http://fetched.com/")
  2402. .SetShouldNotify(true)
  2403. .SetNotificationDeadline(GetDefaultExpirationTime()))
  2404. .Build());
  2405. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2406. std::move(fetched_categories));
  2407. // The fetched suggestion should trigger a notification even though prepended
  2408. // notifications are disabled.
  2409. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  2410. ElementsAre(Property(&ContentSuggestion::notification_extra,
  2411. Not(nullptr))));
  2412. }
  2413. TEST_F(RemoteSuggestionsProviderImplTest,
  2414. ShouldForceFetchedSuggestionsNotificationsWhenEnabled) {
  2415. SetFetchedNotificationsParams(
  2416. /*enabled=*/true, /*force=*/true);
  2417. // Initialize the provider with two article suggestions - one with a
  2418. // notification and one - without.
  2419. MakeSuggestionsProvider(
  2420. /*use_mock_remote_suggestions_status_service=*/false);
  2421. std::vector<FetchedCategory> fetched_categories;
  2422. fetched_categories.push_back(
  2423. FetchedCategoryBuilder()
  2424. .SetCategory(articles_category())
  2425. .AddSuggestionViaBuilder(
  2426. RemoteSuggestionBuilder()
  2427. .SetUrl("http://article_with_notification.com")
  2428. .SetShouldNotify(true)
  2429. .SetNotificationDeadline(GetDefaultExpirationTime()))
  2430. .AddSuggestionViaBuilder(
  2431. RemoteSuggestionBuilder()
  2432. .SetUrl("http://article_without_notification.com")
  2433. .SetShouldNotify(false))
  2434. .Build());
  2435. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2436. std::move(fetched_categories));
  2437. // For the observer, both suggestions must have notifications, because they
  2438. // are forced via a feature param.
  2439. EXPECT_THAT(
  2440. observer().SuggestionsForCategory(articles_category()),
  2441. ElementsAre(
  2442. Property(&ContentSuggestion::notification_extra, Not(nullptr)),
  2443. Property(&ContentSuggestion::notification_extra, Not(nullptr))));
  2444. }
  2445. TEST_F(RemoteSuggestionsProviderImplTest,
  2446. ShouldNotForceFetchedSuggestionsNotificationsWhenExplicitlyDisabled) {
  2447. SetFetchedNotificationsParams(
  2448. /*enabled=*/false, /*force=*/true);
  2449. // Initialize the provider with an article suggestions without a notification.
  2450. MakeSuggestionsProvider(
  2451. /*use_mock_remote_suggestions_status_service=*/false);
  2452. std::vector<FetchedCategory> fetched_categories;
  2453. fetched_categories.push_back(
  2454. FetchedCategoryBuilder()
  2455. .SetCategory(articles_category())
  2456. .AddSuggestionViaBuilder(
  2457. RemoteSuggestionBuilder()
  2458. .SetUrl("http://article_without_notification.com")
  2459. .SetShouldNotify(false))
  2460. .Build());
  2461. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2462. std::move(fetched_categories));
  2463. // For the observer, the suggestion still must not have a notification (even
  2464. // though they are forced via a feature param), because the fetched
  2465. // notifications are explicitly disabled via another feature param.
  2466. EXPECT_THAT(
  2467. observer().SuggestionsForCategory(articles_category()),
  2468. ElementsAre(Property(&ContentSuggestion::notification_extra, nullptr)));
  2469. }
  2470. TEST_F(RemoteSuggestionsProviderImplTest,
  2471. ShouldDeleteNotFetchedCategoryWhenDeletionEnabled) {
  2472. // Initialize the provider with two categories.
  2473. MakeSuggestionsProvider(
  2474. /*use_mock_remote_suggestions_status_service=*/false);
  2475. std::vector<FetchedCategory> fetched_categories;
  2476. const FetchedCategoryBuilder articles_category_builder =
  2477. FetchedCategoryBuilder()
  2478. .SetCategory(articles_category())
  2479. .AddSuggestionViaBuilder(
  2480. RemoteSuggestionBuilder().SetUrl("http://articles.com"));
  2481. fetched_categories.push_back(articles_category_builder.Build());
  2482. fetched_categories.push_back(
  2483. FetchedCategoryBuilder()
  2484. .SetCategory(Category::FromRemoteCategory(kOtherCategoryId))
  2485. .AddSuggestionViaBuilder(
  2486. RemoteSuggestionBuilder().SetUrl("http://not_articles.com"))
  2487. .Build());
  2488. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2489. std::move(fetched_categories));
  2490. fetched_categories.clear();
  2491. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2492. observer().StatusForCategory(
  2493. Category::FromRemoteCategory(kOtherCategoryId)));
  2494. // Fetch only one category - articles.
  2495. fetched_categories.push_back(articles_category_builder.Build());
  2496. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2497. std::move(fetched_categories));
  2498. fetched_categories.clear();
  2499. // The other category must be gone, because it was not included in the last
  2500. // fetch and the deletion is enabled via feature params.
  2501. EXPECT_EQ(CategoryStatus::NOT_PROVIDED,
  2502. observer().StatusForCategory(
  2503. Category::FromRemoteCategory(kOtherCategoryId)));
  2504. }
  2505. TEST_F(RemoteSuggestionsProviderImplTest,
  2506. ShouldKeepFetchedCategoryWhenDeletionEnabled) {
  2507. // Initialize the provider with two categories.
  2508. MakeSuggestionsProvider(
  2509. /*use_mock_remote_suggestions_status_service=*/false);
  2510. std::vector<FetchedCategory> fetched_categories;
  2511. const FetchedCategoryBuilder articles_category_builder =
  2512. FetchedCategoryBuilder()
  2513. .SetCategory(articles_category())
  2514. .AddSuggestionViaBuilder(
  2515. RemoteSuggestionBuilder().SetUrl("http://articles.com"));
  2516. fetched_categories.push_back(articles_category_builder.Build());
  2517. const FetchedCategoryBuilder other_category_builder =
  2518. FetchedCategoryBuilder()
  2519. .SetCategory(Category::FromRemoteCategory(kOtherCategoryId))
  2520. .AddSuggestionViaBuilder(
  2521. RemoteSuggestionBuilder().SetUrl("http://not_articles.com"));
  2522. fetched_categories.push_back(other_category_builder.Build());
  2523. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2524. std::move(fetched_categories));
  2525. fetched_categories.clear();
  2526. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2527. observer().StatusForCategory(
  2528. Category::FromRemoteCategory(kOtherCategoryId)));
  2529. // Fetch the same two categories again.
  2530. fetched_categories.push_back(articles_category_builder.Build());
  2531. fetched_categories.push_back(other_category_builder.Build());
  2532. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2533. std::move(fetched_categories));
  2534. fetched_categories.clear();
  2535. // The other category must remain, because it was included in the last fetch.
  2536. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2537. observer().StatusForCategory(
  2538. Category::FromRemoteCategory(kOtherCategoryId)));
  2539. }
  2540. TEST_F(RemoteSuggestionsProviderImplTest,
  2541. ShouldKeepArticleCategoryEvenWhenNotFetchedAndDeletionEnabled) {
  2542. // Initialize the provider with two categories.
  2543. MakeSuggestionsProvider(
  2544. /*use_mock_remote_suggestions_status_service=*/false);
  2545. std::vector<FetchedCategory> fetched_categories;
  2546. fetched_categories.push_back(
  2547. FetchedCategoryBuilder()
  2548. .SetCategory(articles_category())
  2549. .AddSuggestionViaBuilder(
  2550. RemoteSuggestionBuilder().SetUrl("http://articles.com"))
  2551. .Build());
  2552. const FetchedCategoryBuilder other_category_builder =
  2553. FetchedCategoryBuilder()
  2554. .SetCategory(Category::FromRemoteCategory(kOtherCategoryId))
  2555. .AddSuggestionViaBuilder(
  2556. RemoteSuggestionBuilder().SetUrl("http://not_articles.com"));
  2557. fetched_categories.push_back(other_category_builder.Build());
  2558. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2559. std::move(fetched_categories));
  2560. fetched_categories.clear();
  2561. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2562. observer().StatusForCategory(articles_category()));
  2563. // Fetch only one other category.
  2564. fetched_categories.push_back(other_category_builder.Build());
  2565. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2566. std::move(fetched_categories));
  2567. fetched_categories.clear();
  2568. // Articles category still must be provided (it is an exception) even though
  2569. // it was not included in the last fetch and the deletion is enabled via
  2570. // feature params.
  2571. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2572. observer().StatusForCategory(articles_category()));
  2573. }
  2574. TEST_F(RemoteSuggestionsProviderImplTest,
  2575. EmptySectionResponseShouldClearSection) {
  2576. // Initialize the provider with two categories.
  2577. MakeSuggestionsProvider(
  2578. /*use_mock_remote_suggestions_status_service=*/false);
  2579. // Set up state with present suggestions.
  2580. // Unfortunately, we cannot create the fetched_categories inline, as some part
  2581. // requires a copy of FetchedCategory which is not supported :-/.
  2582. std::vector<FetchedCategory> fetched_categories;
  2583. fetched_categories.push_back(
  2584. FetchedCategoryBuilder()
  2585. .SetCategory(articles_category())
  2586. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  2587. .SetUrl("http://articles.com")
  2588. .SetAmpUrl(""))
  2589. .Build());
  2590. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2591. std::move(fetched_categories));
  2592. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2593. observer().StatusForCategory(articles_category()));
  2594. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  2595. SizeIs(1));
  2596. // Next fetch returns an empty article section.
  2597. fetched_categories.clear();
  2598. fetched_categories.push_back(
  2599. FetchedCategoryBuilder().SetCategory(articles_category()).Build());
  2600. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2601. std::move(fetched_categories));
  2602. // Articles category still must be provided, but empty.
  2603. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2604. observer().StatusForCategory(articles_category()));
  2605. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  2606. IsEmpty());
  2607. }
  2608. TEST_F(RemoteSuggestionsProviderImplTest,
  2609. FetchErrorShouldLeaveSuggestionsUnchangedEmptySection) {
  2610. // Tests that we don't interpret the response value in error cases.
  2611. // Note, that the contract of the callback guarantees that we always send
  2612. // a null value in error cases. However, such a contract is brittle and the
  2613. // code is not too clear on the receiving side.
  2614. // TODO(tschumann): Establish and enforce a clear and robust error handling
  2615. // contract.
  2616. // Initialize the provider with two categories.
  2617. MakeSuggestionsProvider(
  2618. /*use_mock_remote_suggestions_status_service=*/false);
  2619. // Set up state with present suggestions.
  2620. std::vector<FetchedCategory> fetched_categories;
  2621. fetched_categories.push_back(
  2622. FetchedCategoryBuilder()
  2623. .SetCategory(articles_category())
  2624. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  2625. .SetUrl("http://articles.com")
  2626. .SetAmpUrl(""))
  2627. .Build());
  2628. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2629. std::move(fetched_categories));
  2630. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2631. observer().StatusForCategory(articles_category()));
  2632. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  2633. SizeIs(1));
  2634. // Next fetch returns an error (with an empty section).
  2635. fetched_categories.clear();
  2636. fetched_categories.push_back(
  2637. FetchedCategoryBuilder().SetCategory(articles_category()).Build());
  2638. FetchTheseSuggestions(/*interactive_request=*/true,
  2639. Status(StatusCode::TEMPORARY_ERROR, "some error"),
  2640. std::move(fetched_categories));
  2641. // Articles category should stay unchanged.
  2642. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2643. observer().StatusForCategory(articles_category()));
  2644. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  2645. SizeIs(1));
  2646. EXPECT_THAT(observer().SuggestionsForCategory(articles_category())[0].url(),
  2647. GURL("http://articles.com"));
  2648. }
  2649. TEST_F(RemoteSuggestionsProviderImplTest,
  2650. FetchErrorShouldLeaveSuggestionsUnchangedNullResponse) {
  2651. // Initialize the provider with two categories.
  2652. MakeSuggestionsProvider(
  2653. /*use_mock_remote_suggestions_status_service=*/false);
  2654. // Set up state with present suggestions.
  2655. std::vector<FetchedCategory> fetched_categories;
  2656. fetched_categories.push_back(
  2657. FetchedCategoryBuilder()
  2658. .SetCategory(articles_category())
  2659. .AddSuggestionViaBuilder(RemoteSuggestionBuilder()
  2660. .SetUrl("http://articles.com")
  2661. .SetAmpUrl(""))
  2662. .Build());
  2663. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2664. std::move(fetched_categories));
  2665. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2666. observer().StatusForCategory(articles_category()));
  2667. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  2668. SizeIs(1));
  2669. // Next fetch returns an error (with an empty section).
  2670. FetchTheseSuggestions(/*interactive_request=*/true,
  2671. Status(StatusCode::TEMPORARY_ERROR, "some error"),
  2672. absl::nullopt);
  2673. // Articles category should stay unchanged.
  2674. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2675. observer().StatusForCategory(articles_category()));
  2676. ASSERT_THAT(observer().SuggestionsForCategory(articles_category()),
  2677. SizeIs(1));
  2678. EXPECT_THAT(observer().SuggestionsForCategory(articles_category())[0].url(),
  2679. GURL("http://articles.com"));
  2680. }
  2681. TEST_F(RemoteSuggestionsProviderImplTest,
  2682. ShouldNotSetExclusiveCategoryWhenFetchingSuggestions) {
  2683. MakeSuggestionsProvider(
  2684. /*use_mock_remote_suggestions_status_service=*/false);
  2685. RequestParams params;
  2686. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  2687. .WillOnce(SaveArg<0>(&params));
  2688. provider()->FetchSuggestions(
  2689. /*interactive_request=*/true,
  2690. RemoteSuggestionsProvider::FetchStatusCallback());
  2691. EXPECT_FALSE(params.exclusive_category.has_value());
  2692. EXPECT_EQ(params.count_to_fetch, 10);
  2693. }
  2694. TEST_F(RemoteSuggestionsProviderImplTest,
  2695. ShouldSetExclusiveCategoryAndCountToFetchWhenFetchingMoreSuggestions) {
  2696. SetFetchMoreSuggestionsCount(35);
  2697. MakeSuggestionsProvider(
  2698. /*use_mock_remote_suggestions_status_service=*/false);
  2699. RequestParams params;
  2700. EXPECT_CALL(*mock_suggestions_fetcher(), FetchSnippets(_, _))
  2701. .WillOnce(SaveArg<0>(&params));
  2702. EXPECT_CALL(*scheduler(), AcquireQuotaForInteractiveFetch())
  2703. .WillOnce(Return(true))
  2704. .RetiresOnSaturation();
  2705. provider()->Fetch(
  2706. articles_category(), /*known_suggestion_ids=*/std::set<std::string>(),
  2707. /*fetch_done_callback=*/
  2708. base::BindOnce(
  2709. [](Status status_code,
  2710. std::vector<ContentSuggestion> suggestions) -> void {}));
  2711. ASSERT_TRUE(params.exclusive_category.has_value());
  2712. EXPECT_EQ(*params.exclusive_category, articles_category());
  2713. EXPECT_EQ(params.count_to_fetch, 35);
  2714. }
  2715. TEST_F(RemoteSuggestionsProviderImplTest,
  2716. ShouldToggleStatusIfRefetchWhileDisplayingSucceeds) {
  2717. MakeSuggestionsProvider(
  2718. /*use_mock_remote_suggestions_status_service=*/false);
  2719. std::vector<FetchedCategory> fetched_categories;
  2720. const FetchedCategoryBuilder articles_category_builder =
  2721. FetchedCategoryBuilder()
  2722. .SetCategory(articles_category())
  2723. .AddSuggestionViaBuilder(
  2724. RemoteSuggestionBuilder().SetUrl("http://articles.com"));
  2725. fetched_categories.push_back(articles_category_builder.Build());
  2726. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2727. std::move(fetched_categories));
  2728. fetched_categories.clear();
  2729. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2730. observer().StatusForCategory(articles_category()));
  2731. auto response_callback = RefetchWhileDisplayingAndGetResponseCallback();
  2732. // The timeout does not fire earlier than it should.
  2733. FastForwardBy(base::Seconds(kTimeoutForRefetchWhileDisplayingSeconds) -
  2734. base::Milliseconds(1));
  2735. // Before the results come, the status is AVAILABLE_LOADING.
  2736. ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
  2737. observer().StatusForCategory(articles_category()));
  2738. fetched_categories.push_back(articles_category_builder.Build());
  2739. std::move(response_callback)
  2740. .Run(Status::Success(), std::move(fetched_categories));
  2741. fetched_categories.clear();
  2742. // After the results come, the status is flipped back to AVAILABLE.
  2743. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2744. observer().StatusForCategory(articles_category()));
  2745. }
  2746. TEST_F(RemoteSuggestionsProviderImplTest,
  2747. ShouldToggleStatusIfRefetchWhileDisplayingFails) {
  2748. MakeSuggestionsProvider(
  2749. /*use_mock_remote_suggestions_status_service=*/false);
  2750. std::vector<FetchedCategory> fetched_categories;
  2751. const FetchedCategoryBuilder articles_category_builder =
  2752. FetchedCategoryBuilder()
  2753. .SetCategory(articles_category())
  2754. .AddSuggestionViaBuilder(
  2755. RemoteSuggestionBuilder().SetUrl("http://articles.com"));
  2756. fetched_categories.push_back(articles_category_builder.Build());
  2757. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2758. std::move(fetched_categories));
  2759. fetched_categories.clear();
  2760. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2761. observer().StatusForCategory(articles_category()));
  2762. auto response_callback = RefetchWhileDisplayingAndGetResponseCallback();
  2763. // Before the results come, the status is flipped to AVAILABLE_LOADING.
  2764. ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
  2765. observer().StatusForCategory(articles_category()));
  2766. // After the results come, the status is flipped back to AVAILABLE.
  2767. std::move(response_callback)
  2768. .Run(Status(StatusCode::TEMPORARY_ERROR, "some error"), absl::nullopt);
  2769. // The category is available with the previous suggestion.
  2770. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2771. observer().StatusForCategory(articles_category()));
  2772. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  2773. SizeIs(1));
  2774. }
  2775. TEST_F(RemoteSuggestionsProviderImplTest,
  2776. ShouldToggleStatusIfRefetchWhileDisplayingTimeouts) {
  2777. MakeSuggestionsProvider(
  2778. /*use_mock_remote_suggestions_status_service=*/false);
  2779. std::vector<FetchedCategory> fetched_categories;
  2780. const FetchedCategoryBuilder articles_category_builder =
  2781. FetchedCategoryBuilder()
  2782. .SetCategory(articles_category())
  2783. .AddSuggestionViaBuilder(
  2784. RemoteSuggestionBuilder().SetUrl("http://articles.com"));
  2785. fetched_categories.push_back(articles_category_builder.Build());
  2786. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2787. std::move(fetched_categories));
  2788. fetched_categories.clear();
  2789. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2790. observer().StatusForCategory(articles_category()));
  2791. // No need to finish the fetch, we ignore the response callback.
  2792. RefetchWhileDisplayingAndGetResponseCallback();
  2793. FastForwardBy(base::Seconds(kTimeoutForRefetchWhileDisplayingSeconds) -
  2794. base::Milliseconds(1));
  2795. // Before the timeout, the status is flipped to AVAILABLE_LOADING.
  2796. ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
  2797. observer().StatusForCategory(articles_category()));
  2798. FastForwardBy(base::Milliseconds(2));
  2799. // After the timeout, the status is flipped back to AVAILABLE, with the
  2800. // previous suggestion.
  2801. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2802. observer().StatusForCategory(articles_category()));
  2803. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  2804. SizeIs(1));
  2805. }
  2806. TEST_F(RemoteSuggestionsProviderImplTest,
  2807. ShouldHandleCategoryDisabledBeforeTimeout) {
  2808. MakeSuggestionsProvider(
  2809. /*use_mock_remote_suggestions_status_service=*/false);
  2810. std::vector<FetchedCategory> fetched_categories;
  2811. const FetchedCategoryBuilder articles_category_builder =
  2812. FetchedCategoryBuilder()
  2813. .SetCategory(articles_category())
  2814. .AddSuggestionViaBuilder(
  2815. RemoteSuggestionBuilder().SetUrl("http://articles.com"));
  2816. fetched_categories.push_back(articles_category_builder.Build());
  2817. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2818. std::move(fetched_categories));
  2819. fetched_categories.clear();
  2820. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2821. observer().StatusForCategory(articles_category()));
  2822. // No need to finish the fetch, we ignore the response callback.
  2823. RefetchWhileDisplayingAndGetResponseCallback();
  2824. FastForwardBy(base::Seconds(kTimeoutForRefetchWhileDisplayingSeconds) -
  2825. base::Milliseconds(1));
  2826. // Before the timeout, the status is flipped to AVAILABLE_LOADING.
  2827. ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
  2828. observer().StatusForCategory(articles_category()));
  2829. // Disable the provider; this will put the category into the
  2830. // CATEGORY_EXPLICITLY_DISABLED status.
  2831. provider()->EnterState(RemoteSuggestionsProviderImpl::State::DISABLED);
  2832. ASSERT_EQ(CategoryStatus::CATEGORY_EXPLICITLY_DISABLED,
  2833. observer().StatusForCategory(articles_category()));
  2834. // Trigger the timeout. The provider should gracefully handle(i.e. not crash
  2835. // because of) the category being disabled in the interim.
  2836. FastForwardBy(base::Milliseconds(2));
  2837. }
  2838. TEST_F(RemoteSuggestionsProviderImplTest,
  2839. ShouldNotUpdateTimeoutIfRefetchWhileDisplayingCalledAgain) {
  2840. MakeSuggestionsProvider(
  2841. /*use_mock_remote_suggestions_status_service=*/false);
  2842. std::vector<FetchedCategory> fetched_categories;
  2843. const FetchedCategoryBuilder articles_category_builder =
  2844. FetchedCategoryBuilder()
  2845. .SetCategory(articles_category())
  2846. .AddSuggestionViaBuilder(
  2847. RemoteSuggestionBuilder().SetUrl("http://articles.com"));
  2848. fetched_categories.push_back(articles_category_builder.Build());
  2849. FetchTheseSuggestions(/*interactive_request=*/true, Status::Success(),
  2850. std::move(fetched_categories));
  2851. fetched_categories.clear();
  2852. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2853. observer().StatusForCategory(articles_category()));
  2854. // No need to finish the fetch, we ignore the response callback.
  2855. RefetchWhileDisplayingAndGetResponseCallback();
  2856. FastForwardBy(base::Seconds(kTimeoutForRefetchWhileDisplayingSeconds) -
  2857. base::Milliseconds(1));
  2858. // Another fetch does nothing to the deadline.
  2859. RefetchWhileDisplayingAndGetResponseCallback();
  2860. FastForwardBy(base::Milliseconds(2));
  2861. // After the timeout, the status is flipped back to AVAILABLE, with the
  2862. // previous suggestion.
  2863. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2864. observer().StatusForCategory(articles_category()));
  2865. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  2866. SizeIs(1));
  2867. }
  2868. TEST_F(RemoteSuggestionsProviderImplTest,
  2869. ShouldToggleStatusIfReloadSuggestionsFails) {
  2870. MakeSuggestionsProvider(
  2871. /*use_mock_remote_suggestions_status_service=*/false);
  2872. ASSERT_EQ(CategoryStatus::AVAILABLE,
  2873. observer().StatusForCategory(articles_category()));
  2874. auto response_callback = ReloadSuggestionsAndGetResponseCallback();
  2875. // Before the results come, the status is flipped to AVAILABLE_LOADING.
  2876. ASSERT_EQ(CategoryStatus::AVAILABLE_LOADING,
  2877. observer().StatusForCategory(articles_category()));
  2878. // After the results come, the status is flipped back to AVAILABLE.
  2879. std::move(response_callback)
  2880. .Run(Status(StatusCode::TEMPORARY_ERROR, "some error"), absl::nullopt);
  2881. // The category is available, with no suggestions.
  2882. EXPECT_EQ(CategoryStatus::AVAILABLE,
  2883. observer().StatusForCategory(articles_category()));
  2884. EXPECT_THAT(observer().SuggestionsForCategory(articles_category()),
  2885. IsEmpty());
  2886. }
  2887. } // namespace ntp_snippets