1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/feed/core/v2/feed_stream.h"
6
7 #include <memory>
8 #include <string>
9 #include <utility>
10
11 #include "base/optional.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/test/bind_test_util.h"
14 #include "base/test/scoped_run_loop_timeout.h"
15 #include "base/test/simple_test_clock.h"
16 #include "base/test/simple_test_tick_clock.h"
17 #include "base/test/task_environment.h"
18 #include "base/threading/sequenced_task_runner_handle.h"
19 #include "components/feed/core/common/pref_names.h"
20 #include "components/feed/core/proto/v2/store.pb.h"
21 #include "components/feed/core/proto/v2/ui.pb.h"
22 #include "components/feed/core/proto/v2/wire/request.pb.h"
23 #include "components/feed/core/shared_prefs/pref_names.h"
24 #include "components/feed/core/v2/feed_network.h"
25 #include "components/feed/core/v2/refresh_task_scheduler.h"
26 #include "components/feed/core/v2/scheduling.h"
27 #include "components/feed/core/v2/stream_model.h"
28 #include "components/feed/core/v2/stream_model_update_request.h"
29 #include "components/feed/core/v2/tasks/load_stream_from_store_task.h"
30 #include "components/feed/core/v2/test/stream_builder.h"
31 #include "components/leveldb_proto/public/proto_database_provider.h"
32 #include "components/prefs/pref_registry_simple.h"
33 #include "components/prefs/testing_pref_service.h"
34 #include "testing/gtest/include/gtest/gtest.h"
35
36 namespace feed {
37 namespace {
38
LoadModelFromStore(FeedStore * store)39 std::unique_ptr<StreamModel> LoadModelFromStore(FeedStore* store) {
40 LoadStreamFromStoreTask::Result result;
41 auto complete = [&](LoadStreamFromStoreTask::Result task_result) {
42 result = std::move(task_result);
43 };
44 LoadStreamFromStoreTask load_task(
45 store, /*clock=*/nullptr,
46 UserClass::kActiveSuggestionsConsumer, // Has no effect.
47 base::BindLambdaForTesting(complete));
48 // We want to load the data no matter how stale.
49 load_task.IgnoreStalenessForTesting();
50
51 base::RunLoop run_loop;
52 load_task.Execute(run_loop.QuitClosure());
53 run_loop.Run();
54
55 if (result.status == LoadStreamStatus::kLoadedFromStore) {
56 auto model = std::make_unique<StreamModel>();
57 model->Update(std::move(result.update_request));
58 return model;
59 }
60 LOG(WARNING) << "LoadModelFromStore failed with " << result.status;
61 return nullptr;
62 }
63
64 // Returns the model state string (|StreamModel::DumpStateForTesting()|),
65 // given a model initialized with |update_request| and having |operations|
66 // applied.
ModelStateFor(std::unique_ptr<StreamModelUpdateRequest> update_request,std::vector<feedstore::DataOperation> operations={},std::vector<feedstore::DataOperation> more_operations={})67 std::string ModelStateFor(
68 std::unique_ptr<StreamModelUpdateRequest> update_request,
69 std::vector<feedstore::DataOperation> operations = {},
70 std::vector<feedstore::DataOperation> more_operations = {}) {
71 StreamModel model;
72 model.Update(std::move(update_request));
73 model.ExecuteOperations(operations);
74 model.ExecuteOperations(more_operations);
75 return model.DumpStateForTesting();
76 }
77
78 // Returns the model state string (|StreamModel::DumpStateForTesting()|),
79 // given a model initialized with |store|.
ModelStateFor(FeedStore * store)80 std::string ModelStateFor(FeedStore* store) {
81 auto model = LoadModelFromStore(store);
82 if (model) {
83 return model->DumpStateForTesting();
84 }
85 return "{Failed to load model from store}";
86 }
87
88 // This is EXPECT_EQ, but also dumps the string values for ease of reading.
89 #define EXPECT_STRINGS_EQUAL(WANT, GOT) \
90 { \
91 std::string want = (WANT), got = (GOT); \
92 EXPECT_EQ(want, got) << "Wanted:\n" << (want) << "\nBut got:\n" << (got); \
93 }
94
95 class TestSurface : public FeedStream::SurfaceInterface {
96 public:
97 // FeedStream::SurfaceInterface.
StreamUpdate(const feedui::StreamUpdate & stream_update)98 void StreamUpdate(const feedui::StreamUpdate& stream_update) override {
99 if (!initial_state)
100 initial_state = stream_update;
101 update = stream_update;
102 ++update_count_;
103 }
104
105 // Test functions.
106
Clear()107 void Clear() {
108 initial_state = base::nullopt;
109 update = base::nullopt;
110 update_count_ = 0;
111 }
112
113 // Describe what is shown on the surface in a format that can be easily
114 // asserted against.
Describe()115 std::string Describe() {
116 if (!initial_state)
117 return "empty";
118
119 if (update->updated_slices().size() == 1 &&
120 update->updated_slices()[0].has_slice() &&
121 update->updated_slices()[0].slice().has_zero_state_slice()) {
122 return "zero-state";
123 }
124
125 std::stringstream ss;
126 ss << update->updated_slices().size() << " slices";
127 // If there's more than one update, we want to know that.
128 if (update_count_ > 1) {
129 ss << " " << update_count_ << " updates";
130 }
131 return ss.str();
132 }
133
134 base::Optional<feedui::StreamUpdate> initial_state;
135 base::Optional<feedui::StreamUpdate> update;
136
137 private:
138 int update_count_ = 0;
139 };
140
141 class TestUserClassifier : public UserClassifier {
142 public:
TestUserClassifier(PrefService * pref_service,const base::Clock * clock)143 TestUserClassifier(PrefService* pref_service, const base::Clock* clock)
144 : UserClassifier(pref_service, clock) {}
145 // UserClassifier.
GetUserClass() const146 UserClass GetUserClass() const override {
147 return overridden_user_class_.value_or(UserClassifier::GetUserClass());
148 }
149
150 // Test use.
OverrideUserClass(UserClass user_class)151 void OverrideUserClass(UserClass user_class) {
152 overridden_user_class_ = user_class;
153 }
154
155 private:
156 base::Optional<UserClass> overridden_user_class_;
157 };
158
159 class TestFeedNetwork : public FeedNetwork {
160 public:
161 // FeedNetwork implementation.
SendQueryRequest(const feedwire::Request & request,base::OnceCallback<void (QueryRequestResult)> callback)162 void SendQueryRequest(
163 const feedwire::Request& request,
164 base::OnceCallback<void(QueryRequestResult)> callback) override {
165 ++send_query_call_count;
166 // Emulate a successful response.
167 // The response body is currently an empty message, because most of the
168 // time we want to inject a translated response for ease of test-writing.
169 query_request_sent = request;
170 QueryRequestResult result;
171 result.status_code = 200;
172 result.response_body = std::make_unique<feedwire::Response>();
173 base::SequencedTaskRunnerHandle::Get()->PostTask(
174 FROM_HERE, base::BindOnce(std::move(callback), std::move(result)));
175 }
SendActionRequest(const feedwire::ActionRequest & request,base::OnceCallback<void (ActionRequestResult)> callback)176 void SendActionRequest(
177 const feedwire::ActionRequest& request,
178 base::OnceCallback<void(ActionRequestResult)> callback) override {
179 NOTIMPLEMENTED();
180 }
CancelRequests()181 void CancelRequests() override { NOTIMPLEMENTED(); }
182
183 base::Optional<feedwire::Request> query_request_sent;
184 int send_query_call_count = 0;
185 };
186
187 // Forwards to |FeedStream::WireResponseTranslator| unless a response is
188 // injected.
189 class TestWireResponseTranslator : public FeedStream::WireResponseTranslator {
190 public:
TranslateWireResponse(feedwire::Response response,base::TimeDelta response_time,base::Time current_time)191 std::unique_ptr<StreamModelUpdateRequest> TranslateWireResponse(
192 feedwire::Response response,
193 base::TimeDelta response_time,
194 base::Time current_time) override {
195 if (injected_response_) {
196 return std::move(injected_response_);
197 }
198 return FeedStream::WireResponseTranslator::TranslateWireResponse(
199 std::move(response), response_time, current_time);
200 }
InjectResponse(std::unique_ptr<StreamModelUpdateRequest> response)201 void InjectResponse(std::unique_ptr<StreamModelUpdateRequest> response) {
202 injected_response_ = std::move(response);
203 }
InjectedResponseConsumed() const204 bool InjectedResponseConsumed() const { return !injected_response_; }
205
206 private:
207 std::unique_ptr<StreamModelUpdateRequest> injected_response_;
208 };
209
210 class FakeRefreshTaskScheduler : public RefreshTaskScheduler {
211 public:
212 // RefreshTaskScheduler implementation.
EnsureScheduled(base::TimeDelta period)213 void EnsureScheduled(base::TimeDelta period) override {
214 scheduled_period = period;
215 }
Cancel()216 void Cancel() override { canceled = true; }
RefreshTaskComplete()217 void RefreshTaskComplete() override { refresh_task_complete = true; }
218
219 base::Optional<base::TimeDelta> scheduled_period;
220 bool canceled = false;
221 bool refresh_task_complete = false;
222 };
223
224 class TestEventObserver : public FeedStream::EventObserver {
225 public:
226 // FeedStreamUnittest::StreamEventObserver.
OnLoadStream(LoadStreamStatus load_from_store_status,LoadStreamStatus final_status)227 void OnLoadStream(LoadStreamStatus load_from_store_status,
228 LoadStreamStatus final_status) override {
229 load_stream_status = final_status;
230 LOG(INFO) << "OnLoadStream: " << final_status
231 << " (store status: " << load_from_store_status << ")";
232 }
OnMaybeTriggerRefresh(TriggerType trigger,bool clear_all_before_refresh)233 void OnMaybeTriggerRefresh(TriggerType trigger,
234 bool clear_all_before_refresh) override {
235 refresh_trigger_type = trigger;
236 }
OnClearAll(base::TimeDelta time_since_last_clear)237 void OnClearAll(base::TimeDelta time_since_last_clear) override {
238 this->time_since_last_clear = time_since_last_clear;
239 }
240
241 // Test access.
242
243 base::Optional<LoadStreamStatus> load_stream_status;
244 base::Optional<base::TimeDelta> time_since_last_clear;
245 base::Optional<TriggerType> refresh_trigger_type;
246 };
247
248 class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
249 public:
SetUp()250 void SetUp() override {
251 feed::prefs::RegisterFeedSharedProfilePrefs(profile_prefs_.registry());
252 feed::RegisterProfilePrefs(profile_prefs_.registry());
253 CHECK_EQ(kTestTimeEpoch, task_environment_.GetMockClock()->Now());
254 stream_ = std::make_unique<FeedStream>(
255 &refresh_scheduler_, &event_observer_, this, &profile_prefs_, &network_,
256 store_.get(), task_environment_.GetMockClock(),
257 task_environment_.GetMockTickClock(),
258 task_environment_.GetMainThreadTaskRunner());
259
260 // Set the user classifier.
261 auto user_classifier = std::make_unique<TestUserClassifier>(
262 &profile_prefs_, task_environment_.GetMockClock());
263 user_classifier_ = user_classifier.get();
264 stream_->SetUserClassifierForTesting(std::move(user_classifier));
265
266 WaitForIdleTaskQueue(); // Wait for any initialization.
267
268 stream_->SetWireResponseTranslatorForTesting(&response_translator_);
269 }
270
TearDown()271 void TearDown() override {
272 // Ensure the task queue can return to idle. Failure to do so may be due
273 // to a stuck task that never called |TaskComplete()|.
274 WaitForIdleTaskQueue();
275 // Store requires PostTask to clean up.
276 store_.reset();
277 task_environment_.RunUntilIdle();
278 }
279
280 // FeedStream::Delegate.
IsEulaAccepted()281 bool IsEulaAccepted() override { return is_eula_accepted_; }
IsOffline()282 bool IsOffline() override { return is_offline_; }
283
284 // For tests.
285
IsTaskQueueIdle() const286 bool IsTaskQueueIdle() const {
287 return !stream_->GetTaskQueueForTesting()->HasPendingTasks() &&
288 !stream_->GetTaskQueueForTesting()->HasRunningTask();
289 }
290
WaitForIdleTaskQueue()291 void WaitForIdleTaskQueue() {
292 if (IsTaskQueueIdle())
293 return;
294 base::test::ScopedRunLoopTimeout run_timeout(
295 FROM_HERE, base::TimeDelta::FromSeconds(1));
296 base::RunLoop run_loop;
297 stream_->SetIdleCallbackForTesting(run_loop.QuitClosure());
298 run_loop.Run();
299 }
300
UnloadModel()301 void UnloadModel() {
302 WaitForIdleTaskQueue();
303 stream_->UnloadModelForTesting();
304 }
305
306 protected:
307 base::test::TaskEnvironment task_environment_{
308 base::test::TaskEnvironment::TimeSource::MOCK_TIME};
309 TestUserClassifier* user_classifier_;
310 TestEventObserver event_observer_;
311 TestingPrefServiceSimple profile_prefs_;
312 TestFeedNetwork network_;
313 TestWireResponseTranslator response_translator_;
314
315 std::unique_ptr<FeedStore> store_ = std::make_unique<FeedStore>(
316 leveldb_proto::ProtoDatabaseProvider::GetUniqueDB<feedstore::Record>(
317 leveldb_proto::ProtoDbType::FEED_STREAM_DATABASE,
318 /*file_path=*/{},
319 task_environment_.GetMainThreadTaskRunner()));
320 FakeRefreshTaskScheduler refresh_scheduler_;
321 std::unique_ptr<FeedStream> stream_;
322 bool is_eula_accepted_ = true;
323 bool is_offline_ = false;
324 };
325
TEST_F(FeedStreamTest,IsArticlesListVisibleByDefault)326 TEST_F(FeedStreamTest, IsArticlesListVisibleByDefault) {
327 EXPECT_TRUE(stream_->IsArticlesListVisible());
328 }
329
TEST_F(FeedStreamTest,SetArticlesListVisible)330 TEST_F(FeedStreamTest, SetArticlesListVisible) {
331 EXPECT_TRUE(stream_->IsArticlesListVisible());
332 stream_->SetArticlesListVisible(false);
333 EXPECT_FALSE(stream_->IsArticlesListVisible());
334 stream_->SetArticlesListVisible(true);
335 EXPECT_TRUE(stream_->IsArticlesListVisible());
336 }
337
TEST_F(FeedStreamTest,RefreshIsScheduledOnInitialize)338 TEST_F(FeedStreamTest, RefreshIsScheduledOnInitialize) {
339 stream_->InitializeScheduling();
340 EXPECT_TRUE(refresh_scheduler_.scheduled_period);
341 }
342
TEST_F(FeedStreamTest,ScheduledRefreshTriggersRefresh)343 TEST_F(FeedStreamTest, ScheduledRefreshTriggersRefresh) {
344 stream_->InitializeScheduling();
345 stream_->ExecuteRefreshTask();
346
347 EXPECT_EQ(TriggerType::kFixedTimer, event_observer_.refresh_trigger_type);
348 // TODO(harringtond): Once we actually perform the refresh, make sure
349 // RefreshTaskComplete() is called.
350 // EXPECT_TRUE(refresh_scheduler_.refresh_task_complete);
351 }
352
TEST_F(FeedStreamTest,DoNotRefreshIfArticlesListIsHidden)353 TEST_F(FeedStreamTest, DoNotRefreshIfArticlesListIsHidden) {
354 stream_->SetArticlesListVisible(false);
355 stream_->InitializeScheduling();
356 stream_->ExecuteRefreshTask();
357
358 EXPECT_TRUE(refresh_scheduler_.canceled);
359 EXPECT_FALSE(event_observer_.refresh_trigger_type);
360 }
361
TEST_F(FeedStreamTest,SurfaceReceivesInitialContent)362 TEST_F(FeedStreamTest, SurfaceReceivesInitialContent) {
363 {
364 auto model = std::make_unique<StreamModel>();
365 model->Update(MakeTypicalInitialModelState());
366 stream_->LoadModelForTesting(std::move(model));
367 }
368 TestSurface surface;
369 stream_->AttachSurface(&surface);
370 ASSERT_TRUE(surface.initial_state);
371 const feedui::StreamUpdate& initial_state = surface.initial_state.value();
372 ASSERT_EQ(2, initial_state.updated_slices().size());
373 EXPECT_NE("", initial_state.updated_slices(0).slice().slice_id());
374 EXPECT_EQ("f:0", initial_state.updated_slices(0)
375 .slice()
376 .xsurface_slice()
377 .xsurface_frame());
378 EXPECT_NE("", initial_state.updated_slices(1).slice().slice_id());
379 EXPECT_EQ("f:1", initial_state.updated_slices(1)
380 .slice()
381 .xsurface_slice()
382 .xsurface_frame());
383 ASSERT_EQ(1, initial_state.new_shared_states().size());
384 EXPECT_EQ("ss:0",
385 initial_state.new_shared_states()[0].xsurface_shared_state());
386 }
387
TEST_F(FeedStreamTest,SurfaceReceivesInitialContentLoadedAfterAttach)388 TEST_F(FeedStreamTest, SurfaceReceivesInitialContentLoadedAfterAttach) {
389 TestSurface surface;
390 stream_->AttachSurface(&surface);
391 ASSERT_FALSE(surface.initial_state);
392 {
393 auto model = std::make_unique<StreamModel>();
394 model->Update(MakeTypicalInitialModelState());
395 stream_->LoadModelForTesting(std::move(model));
396 }
397
398 ASSERT_EQ("2 slices", surface.Describe());
399 const feedui::StreamUpdate& initial_state = surface.initial_state.value();
400
401 EXPECT_NE("", initial_state.updated_slices(0).slice().slice_id());
402 EXPECT_EQ("f:0", initial_state.updated_slices(0)
403 .slice()
404 .xsurface_slice()
405 .xsurface_frame());
406 EXPECT_NE("", initial_state.updated_slices(1).slice().slice_id());
407 EXPECT_EQ("f:1", initial_state.updated_slices(1)
408 .slice()
409 .xsurface_slice()
410 .xsurface_frame());
411 ASSERT_EQ(1, initial_state.new_shared_states().size());
412 EXPECT_EQ("ss:0",
413 initial_state.new_shared_states()[0].xsurface_shared_state());
414 }
415
TEST_F(FeedStreamTest,SurfaceReceivesUpdatedContent)416 TEST_F(FeedStreamTest, SurfaceReceivesUpdatedContent) {
417 {
418 auto model = std::make_unique<StreamModel>();
419 model->ExecuteOperations(MakeTypicalStreamOperations());
420 stream_->LoadModelForTesting(std::move(model));
421 }
422 TestSurface surface;
423 stream_->AttachSurface(&surface);
424 // Remove #1, add #2.
425 stream_->ExecuteOperations({
426 MakeOperation(MakeRemove(MakeClusterId(1))),
427 MakeOperation(MakeCluster(2, MakeRootId())),
428 MakeOperation(MakeContentNode(2, MakeClusterId(2))),
429 MakeOperation(MakeContent(2)),
430 });
431 ASSERT_TRUE(surface.update);
432 const feedui::StreamUpdate& initial_state = surface.initial_state.value();
433 const feedui::StreamUpdate& update = surface.update.value();
434
435 ASSERT_EQ("2 slices 2 updates", surface.Describe());
436 // First slice is just an ID that matches the old 1st slice ID.
437 EXPECT_EQ(initial_state.updated_slices(0).slice().slice_id(),
438 update.updated_slices(0).slice_id());
439 // Second slice is a new xsurface slice.
440 EXPECT_NE("", update.updated_slices(1).slice().slice_id());
441 EXPECT_EQ("f:2",
442 update.updated_slices(1).slice().xsurface_slice().xsurface_frame());
443 }
444
TEST_F(FeedStreamTest,SurfaceReceivesSecondUpdatedContent)445 TEST_F(FeedStreamTest, SurfaceReceivesSecondUpdatedContent) {
446 {
447 auto model = std::make_unique<StreamModel>();
448 model->ExecuteOperations(MakeTypicalStreamOperations());
449 stream_->LoadModelForTesting(std::move(model));
450 }
451 TestSurface surface;
452 stream_->AttachSurface(&surface);
453 // Add #2.
454 stream_->ExecuteOperations({
455 MakeOperation(MakeCluster(2, MakeRootId())),
456 MakeOperation(MakeContentNode(2, MakeClusterId(2))),
457 MakeOperation(MakeContent(2)),
458 });
459
460 // Clear the last update and add #3.
461 stream_->ExecuteOperations({
462 MakeOperation(MakeCluster(3, MakeRootId())),
463 MakeOperation(MakeContentNode(3, MakeClusterId(3))),
464 MakeOperation(MakeContent(3)),
465 });
466
467 // The last update should have only one new piece of content.
468 // This verifies the current content set is tracked properly.
469 ASSERT_EQ("4 slices 3 updates", surface.Describe());
470
471 ASSERT_EQ(4, surface.update->updated_slices().size());
472 EXPECT_FALSE(surface.update->updated_slices(0).has_slice());
473 EXPECT_FALSE(surface.update->updated_slices(1).has_slice());
474 EXPECT_FALSE(surface.update->updated_slices(2).has_slice());
475 EXPECT_EQ("f:3", surface.update->updated_slices(3)
476 .slice()
477 .xsurface_slice()
478 .xsurface_frame());
479 }
480
TEST_F(FeedStreamTest,DetachSurface)481 TEST_F(FeedStreamTest, DetachSurface) {
482 {
483 auto model = std::make_unique<StreamModel>();
484 model->ExecuteOperations(MakeTypicalStreamOperations());
485 stream_->LoadModelForTesting(std::move(model));
486 }
487 TestSurface surface;
488 stream_->AttachSurface(&surface);
489 EXPECT_TRUE(surface.initial_state);
490 stream_->DetachSurface(&surface);
491 surface.Clear();
492
493 // Arbitrary stream change. Surface should not see the update.
494 stream_->ExecuteOperations({
495 MakeOperation(MakeRemove(MakeClusterId(1))),
496 });
497 EXPECT_FALSE(surface.update);
498 }
499
TEST_F(FeedStreamTest,LoadFromNetwork)500 TEST_F(FeedStreamTest, LoadFromNetwork) {
501 // Store is empty, so we should fallback to a network request.
502 response_translator_.InjectResponse(MakeTypicalInitialModelState());
503 TestSurface surface;
504 stream_->AttachSurface(&surface);
505 WaitForIdleTaskQueue();
506
507 EXPECT_TRUE(network_.query_request_sent);
508 EXPECT_TRUE(response_translator_.InjectedResponseConsumed());
509 EXPECT_EQ("2 slices", surface.Describe());
510 // Verify the model is filled correctly.
511 EXPECT_STRINGS_EQUAL(ModelStateFor(MakeTypicalInitialModelState()),
512 stream_->GetModel()->DumpStateForTesting());
513 // Verify the data was written to the store.
514 EXPECT_STRINGS_EQUAL(ModelStateFor(store_.get()),
515 ModelStateFor(MakeTypicalInitialModelState()));
516 }
517
TEST_F(FeedStreamTest,LoadFromNetworkBecauseStoreIsStale)518 TEST_F(FeedStreamTest, LoadFromNetworkBecauseStoreIsStale) {
519 // Fill the store with stream data that is just barely stale, and verify we
520 // fetch new data over the network.
521 user_classifier_->OverrideUserClass(UserClass::kActiveSuggestionsConsumer);
522 store_->SaveFullStream(MakeTypicalInitialModelState(
523
524 kTestTimeEpoch - base::TimeDelta::FromHours(12) -
525 base::TimeDelta::FromMinutes(1)),
526 base::DoNothing());
527
528 // Store is stale, so we should fallback to a network request.
529 response_translator_.InjectResponse(MakeTypicalInitialModelState());
530 TestSurface surface;
531 stream_->AttachSurface(&surface);
532 WaitForIdleTaskQueue();
533
534 EXPECT_TRUE(network_.query_request_sent);
535 EXPECT_TRUE(response_translator_.InjectedResponseConsumed());
536 ASSERT_TRUE(surface.initial_state);
537 }
538
TEST_F(FeedStreamTest,LoadFromNetworkFailsDueToProtoTranslation)539 TEST_F(FeedStreamTest, LoadFromNetworkFailsDueToProtoTranslation) {
540 // No data in the store, so we should fetch from the network.
541 // The network will respond with an empty response, which should fail proto
542 // translation.
543 TestSurface surface;
544 stream_->AttachSurface(&surface);
545 WaitForIdleTaskQueue();
546
547 EXPECT_EQ(LoadStreamStatus::kProtoTranslationFailed,
548 event_observer_.load_stream_status);
549 }
550
TEST_F(FeedStreamTest,DoNotLoadFromNetworkWhenOffline)551 TEST_F(FeedStreamTest, DoNotLoadFromNetworkWhenOffline) {
552 is_offline_ = true;
553 response_translator_.InjectResponse(MakeTypicalInitialModelState());
554 TestSurface surface;
555 stream_->AttachSurface(&surface);
556 WaitForIdleTaskQueue();
557
558 EXPECT_EQ(LoadStreamStatus::kCannotLoadFromNetworkOffline,
559 event_observer_.load_stream_status);
560 EXPECT_EQ("zero-state", surface.Describe());
561 }
562
TEST_F(FeedStreamTest,DoNotLoadStreamWhenArticleListIsHidden)563 TEST_F(FeedStreamTest, DoNotLoadStreamWhenArticleListIsHidden) {
564 stream_->SetArticlesListVisible(false);
565 response_translator_.InjectResponse(MakeTypicalInitialModelState());
566 TestSurface surface;
567 stream_->AttachSurface(&surface);
568 WaitForIdleTaskQueue();
569
570 EXPECT_EQ(LoadStreamStatus::kLoadNotAllowedArticlesListHidden,
571 event_observer_.load_stream_status);
572 EXPECT_EQ("zero-state", surface.Describe());
573 }
574
TEST_F(FeedStreamTest,DoNotLoadStreamWhenEulaIsNotAccepted)575 TEST_F(FeedStreamTest, DoNotLoadStreamWhenEulaIsNotAccepted) {
576 is_eula_accepted_ = false;
577 response_translator_.InjectResponse(MakeTypicalInitialModelState());
578 TestSurface surface;
579 stream_->AttachSurface(&surface);
580 WaitForIdleTaskQueue();
581
582 EXPECT_EQ(LoadStreamStatus::kLoadNotAllowedEulaNotAccepted,
583 event_observer_.load_stream_status);
584 EXPECT_EQ("zero-state", surface.Describe());
585 }
586
TEST_F(FeedStreamTest,DoNotLoadFromNetworkAfterHistoryIsDeleted)587 TEST_F(FeedStreamTest, DoNotLoadFromNetworkAfterHistoryIsDeleted) {
588 stream_->OnHistoryDeleted();
589 task_environment_.FastForwardBy(kSuppressRefreshDuration -
590 base::TimeDelta::FromSeconds(1));
591 response_translator_.InjectResponse(MakeTypicalInitialModelState());
592 TestSurface surface;
593 stream_->AttachSurface(&surface);
594 WaitForIdleTaskQueue();
595
596 EXPECT_EQ("zero-state", surface.Describe());
597
598 EXPECT_EQ(LoadStreamStatus::kCannotLoadFromNetworkSupressedForHistoryDelete,
599 event_observer_.load_stream_status);
600
601 stream_->DetachSurface(&surface);
602 task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(2));
603 stream_->AttachSurface(&surface);
604 WaitForIdleTaskQueue();
605
606 EXPECT_EQ("2 slices 2 updates", surface.Describe());
607 }
608
TEST_F(FeedStreamTest,ShouldMakeFeedQueryRequestConsumesQuota)609 TEST_F(FeedStreamTest, ShouldMakeFeedQueryRequestConsumesQuota) {
610 LoadStreamStatus status = LoadStreamStatus::kNoStatus;
611 for (; status == LoadStreamStatus::kNoStatus;
612 status = stream_->ShouldMakeFeedQueryRequest()) {
613 }
614
615 ASSERT_EQ(LoadStreamStatus::kCannotLoadFromNetworkThrottled, status);
616 }
617
TEST_F(FeedStreamTest,LoadStreamFromStore)618 TEST_F(FeedStreamTest, LoadStreamFromStore) {
619 // Fill the store with stream data that is just barely fresh, and verify it
620 // loads.
621 user_classifier_->OverrideUserClass(UserClass::kActiveSuggestionsConsumer);
622 store_->SaveFullStream(MakeTypicalInitialModelState(
623 kTestTimeEpoch - base::TimeDelta::FromHours(12) +
624 base::TimeDelta::FromMinutes(1)),
625 base::DoNothing());
626 TestSurface surface;
627 stream_->AttachSurface(&surface);
628 WaitForIdleTaskQueue();
629
630 ASSERT_EQ("2 slices", surface.Describe());
631 EXPECT_FALSE(network_.query_request_sent);
632 // Verify the model is filled correctly.
633 EXPECT_STRINGS_EQUAL(ModelStateFor(MakeTypicalInitialModelState()),
634 stream_->GetModel()->DumpStateForTesting());
635 }
636
TEST_F(FeedStreamTest,DetachSurfaceWhileLoadingModel)637 TEST_F(FeedStreamTest, DetachSurfaceWhileLoadingModel) {
638 response_translator_.InjectResponse(MakeTypicalInitialModelState());
639 TestSurface surface;
640 stream_->AttachSurface(&surface);
641 stream_->DetachSurface(&surface);
642 WaitForIdleTaskQueue();
643
644 EXPECT_EQ("empty", surface.Describe());
645 EXPECT_TRUE(network_.query_request_sent);
646 }
647
TEST_F(FeedStreamTest,AttachMultipleSurfacesLoadsModelOnce)648 TEST_F(FeedStreamTest, AttachMultipleSurfacesLoadsModelOnce) {
649 response_translator_.InjectResponse(MakeTypicalInitialModelState());
650 TestSurface surface;
651 TestSurface other_surface;
652 stream_->AttachSurface(&surface);
653 stream_->AttachSurface(&other_surface);
654 WaitForIdleTaskQueue();
655
656 ASSERT_EQ(1, network_.send_query_call_count);
657
658 // After load, another surface doesn't trigger any tasks.
659 TestSurface later_surface;
660 stream_->AttachSurface(&later_surface);
661
662 EXPECT_TRUE(IsTaskQueueIdle());
663 }
664
TEST_F(FeedStreamTest,ModelChangesAreSavedToStorage)665 TEST_F(FeedStreamTest, ModelChangesAreSavedToStorage) {
666 store_->SaveFullStream(MakeTypicalInitialModelState(), base::DoNothing());
667 TestSurface surface;
668 stream_->AttachSurface(&surface);
669 WaitForIdleTaskQueue();
670 ASSERT_TRUE(surface.initial_state);
671
672 // Remove #1, add #2.
673 const std::vector<feedstore::DataOperation> operations = {
674 MakeOperation(MakeRemove(MakeClusterId(1))),
675 MakeOperation(MakeCluster(2, MakeRootId())),
676 MakeOperation(MakeContentNode(2, MakeClusterId(2))),
677 MakeOperation(MakeContent(2)),
678 };
679 stream_->ExecuteOperations(operations);
680
681 WaitForIdleTaskQueue();
682
683 // Verify changes are applied to storage.
684 EXPECT_STRINGS_EQUAL(
685 ModelStateFor(MakeTypicalInitialModelState(), operations),
686 ModelStateFor(store_.get()));
687
688 // Unload and reload the model from the store, and verify we can still apply
689 // operations correctly.
690 stream_->DetachSurface(&surface);
691 surface.Clear();
692 UnloadModel();
693 stream_->AttachSurface(&surface);
694 WaitForIdleTaskQueue();
695 ASSERT_TRUE(surface.initial_state);
696
697 // Remove #2, add #3.
698 const std::vector<feedstore::DataOperation> operations2 = {
699 MakeOperation(MakeRemove(MakeClusterId(2))),
700 MakeOperation(MakeCluster(3, MakeRootId())),
701 MakeOperation(MakeContentNode(3, MakeClusterId(3))),
702 MakeOperation(MakeContent(3)),
703 };
704 stream_->ExecuteOperations(operations2);
705
706 WaitForIdleTaskQueue();
707 EXPECT_STRINGS_EQUAL(
708 ModelStateFor(MakeTypicalInitialModelState(), operations, operations2),
709 ModelStateFor(store_.get()));
710 }
711
712 } // namespace
713 } // namespace feed
714