chromium/ios/chrome/browser/reading_list/model/offline_page_tab_helper_unittest.mm

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/reading_list/model/offline_page_tab_helper.h"

#import <memory>
#import <vector>

#import "base/memory/scoped_refptr.h"
#import "base/run_loop.h"
#import "base/test/ios/wait_util.h"
#import "base/time/default_clock.h"
#import "components/reading_list/core/fake_reading_list_model_storage.h"
#import "components/reading_list/core/reading_list_entry.h"
#import "components/reading_list/core/reading_list_model_impl.h"
#import "components/sync/base/storage_type.h"
#import "components/sync/model/wipe_model_upon_sync_disabled_behavior.h"
#import "ios/chrome/browser/reading_list/model/reading_list_model_factory.h"
#import "ios/chrome/browser/reading_list/model/reading_list_test_utils.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/web/public/test/fakes/fake_navigation_context.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"

namespace {
const char kTestURL[] = "http://foo.test";
const char kTestSecondURL[] = "http://bar.test";
const char kTestTitle[] = "title";
const char kTestDistilledPath[] = "distilled.html";
const char kTestDistilledURL[] = "http://foo.bar/distilled";
}  // namespace

// Test fixture to test loading of Reading list offline pages.
class OfflinePageTabHelperTest : public PlatformTest {
 public:
  void SetUp() override {
    PlatformTest::SetUp();

    std::vector<scoped_refptr<ReadingListEntry>> initial_entries;
    initial_entries.push_back(base::MakeRefCounted<ReadingListEntry>(
        GURL(kTestURL), kTestTitle, base::Time::Now()));

    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(
        ReadingListModelFactory::GetInstance(),
        base::BindRepeating(&BuildReadingListModelWithFakeStorage,
                            std::move(initial_entries)));
    browser_state_ = std::move(builder).Build();

    fake_web_state_.SetBrowserState(browser_state_.get());
    fake_web_state_.SetNavigationManager(
        std::make_unique<web::FakeNavigationManager>());

    OfflinePageTabHelper::CreateForWebState(&fake_web_state_,
                                            reading_list_model());
  }

  ReadingListModel* reading_list_model() {
    return ReadingListModelFactory::GetForBrowserState(browser_state_.get());
  }

 protected:
  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  web::FakeWebState fake_web_state_;
};

// Test fixture to test loading of Reading list offline pages with a delayed
// ReadingListModel.
class OfflinePageTabHelperDelayedModelTest : public PlatformTest {
 public:
  void SetUp() override {
    PlatformTest::SetUp();

    auto storage = std::make_unique<FakeReadingListModelStorage>();
    fake_reading_list_model_storage_ = storage->AsWeakPtr();

    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(
        ReadingListModelFactory::GetInstance(),
        base::BindRepeating(
            [](std::unique_ptr<FakeReadingListModelStorage>& storage,
               web::BrowserState*) -> std::unique_ptr<KeyedService> {
              DCHECK(storage.get());
              return std::make_unique<ReadingListModelImpl>(
                  std::move(storage), syncer::StorageType::kUnspecified,
                  syncer::WipeModelUponSyncDisabledBehavior::kNever,
                  base::DefaultClock::GetInstance());
            },
            base::OwnedRef(std::move(storage))));
    browser_state_ = std::move(builder).Build();

    fake_web_state_.SetBrowserState(browser_state_.get());
    fake_web_state_.SetNavigationManager(
        std::make_unique<web::FakeNavigationManager>());

    OfflinePageTabHelper::CreateForWebState(&fake_web_state_,
                                            reading_list_model());
  }

  ReadingListModel* reading_list_model() {
    return ReadingListModelFactory::GetForBrowserState(browser_state_.get());
  }

  FakeReadingListModelStorage* fake_reading_list_model_storage() {
    return fake_reading_list_model_storage_.get();
  }

 protected:
  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  web::FakeWebState fake_web_state_;
  base::WeakPtr<FakeReadingListModelStorage> fake_reading_list_model_storage_;
};

// Tests that loading an online version does mark it read.
TEST_F(OfflinePageTabHelperTest, TestLoadReadingListSuccess) {
  GURL url(kTestURL);
  scoped_refptr<const ReadingListEntry> entry =
      reading_list_model()->GetEntryByURL(url);
  fake_web_state_.SetCurrentURL(url);
  web::FakeNavigationContext context;
  context.SetUrl(url);
  context.SetHasCommitted(true);
  fake_web_state_.OnNavigationStarted(&context);
  fake_web_state_.OnNavigationFinished(&context);
  fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS);
  EXPECT_FALSE(base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForFileOperationTimeout, ^bool {
        base::RunLoop().RunUntilIdle();
        return fake_web_state_.GetLastLoadedData();
      }));
  EXPECT_FALSE(fake_web_state_.GetLastLoadedData());
  EXPECT_TRUE(entry->IsRead());
  EXPECT_FALSE(OfflinePageTabHelper::FromWebState(&fake_web_state_)
                   ->presenting_offline_page());
}

// Tests that failing loading an online version does not mark it read.
TEST_F(OfflinePageTabHelperTest, TestLoadReadingListFailure) {
  GURL url(kTestURL);
  scoped_refptr<const ReadingListEntry> entry =
      reading_list_model()->GetEntryByURL(url);
  web::FakeNavigationContext context;
  context.SetUrl(url);
  context.SetHasCommitted(true);
  fake_web_state_.OnNavigationStarted(&context);
  fake_web_state_.OnNavigationFinished(&context);
  fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::FAILURE);
  EXPECT_FALSE(base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForFileOperationTimeout, ^bool {
        base::RunLoop().RunUntilIdle();
        return fake_web_state_.GetLastLoadedData();
      }));
  EXPECT_FALSE(fake_web_state_.GetLastLoadedData());
  EXPECT_FALSE(entry->IsRead());
  EXPECT_FALSE(OfflinePageTabHelper::FromWebState(&fake_web_state_)
                   ->presenting_offline_page());
}

// Tests that failing loading an online version will load the distilled version
// and mark it read.
TEST_F(OfflinePageTabHelperTest, TestLoadReadingListDistilled) {
  GURL url(kTestURL);
  std::string distilled_path = kTestDistilledPath;
  reading_list_model()->SetEntryDistilledInfoIfExists(
      url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
      base::Time::FromTimeT(100));
  scoped_refptr<const ReadingListEntry> entry =
      reading_list_model()->GetEntryByURL(url);
  fake_web_state_.SetCurrentURL(url);
  web::FakeNavigationContext context;
  context.SetHasCommitted(true);
  std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
  static_cast<web::FakeNavigationManager*>(
      fake_web_state_.GetNavigationManager())
      ->SetLastCommittedItem(item.get());
  context.SetUrl(url);
  fake_web_state_.OnNavigationStarted(&context);
  fake_web_state_.OnNavigationFinished(&context);
  fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::FAILURE);
  EXPECT_FALSE(fake_web_state_.GetLastLoadedData());
  EXPECT_FALSE(entry->IsRead());
  EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForFileOperationTimeout, ^bool {
        base::RunLoop().RunUntilIdle();
        return fake_web_state_.GetLastLoadedData();
      }));
  EXPECT_TRUE(entry->IsRead());
  EXPECT_TRUE(OfflinePageTabHelper::FromWebState(&fake_web_state_)
                  ->presenting_offline_page());
}

// Tests that failing loading an online version does not load distilled
// version if another navigation started.
TEST_F(OfflinePageTabHelperTest, TestLoadReadingListFailureThenNavigate) {
  GURL url(kTestURL);
  GURL second_url(kTestSecondURL);
  scoped_refptr<const ReadingListEntry> entry =
      reading_list_model()->GetEntryByURL(url);
  web::FakeNavigationContext context;
  context.SetHasCommitted(true);
  context.SetUrl(url);
  fake_web_state_.OnNavigationStarted(&context);
  fake_web_state_.OnNavigationFinished(&context);
  fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::FAILURE);

  web::FakeNavigationContext second_context;
  second_context.SetUrl(second_url);
  second_context.SetHasCommitted(true);
  fake_web_state_.OnNavigationStarted(&second_context);
  EXPECT_FALSE(base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForFileOperationTimeout, ^bool {
        base::RunLoop().RunUntilIdle();
        return fake_web_state_.GetLastLoadedData();
      }));
  EXPECT_FALSE(fake_web_state_.GetLastLoadedData());
  EXPECT_FALSE(entry->IsRead());
  EXPECT_FALSE(OfflinePageTabHelper::FromWebState(&fake_web_state_)
                   ->presenting_offline_page());
}

// Tests that OfflinePageTabHelper correctly reports existence of a distilled
// version.
TEST_F(OfflinePageTabHelperTest, TestHasDistilledVersionForOnlineUrl) {
  OfflinePageTabHelper* offline_page_tab_helper =
      OfflinePageTabHelper::FromWebState(&fake_web_state_);
  GURL url(kTestURL);
  EXPECT_FALSE(offline_page_tab_helper->HasDistilledVersionForOnlineUrl(url));
  GURL second_url(kTestSecondURL);
  EXPECT_FALSE(
      offline_page_tab_helper->HasDistilledVersionForOnlineUrl(second_url));

  std::string distilled_path = kTestDistilledPath;
  reading_list_model()->SetEntryDistilledInfoIfExists(
      url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
      base::Time::FromTimeT(100));
  EXPECT_TRUE(offline_page_tab_helper->HasDistilledVersionForOnlineUrl(url));
}

// Tests that OfflinePageTabHelper correctly shows Offline page if model takes
// a long time to load.
TEST_F(OfflinePageTabHelperDelayedModelTest, TestLateReadingListModelLoading) {
  OfflinePageTabHelper* offline_page_tab_helper =
      OfflinePageTabHelper::FromWebState(&fake_web_state_);
  GURL url(kTestURL);
  EXPECT_FALSE(offline_page_tab_helper->HasDistilledVersionForOnlineUrl(url));
  web::FakeNavigationContext context;

  context.SetHasCommitted(true);
  std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
  static_cast<web::FakeNavigationManager*>(
      fake_web_state_.GetNavigationManager())
      ->SetLastCommittedItem(item.get());
  context.SetUrl(url);
  fake_web_state_.OnNavigationStarted(&context);
  fake_web_state_.OnNavigationFinished(&context);
  fake_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::FAILURE);
  EXPECT_FALSE(base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForFileOperationTimeout, ^bool {
        base::RunLoop().RunUntilIdle();
        return fake_web_state_.GetLastLoadedData();
      }));
  EXPECT_FALSE(offline_page_tab_helper->presenting_offline_page());
  // Complete the reading list model load from storage.
  std::vector<scoped_refptr<ReadingListEntry>> initial_entries;
  initial_entries.push_back(base::MakeRefCounted<ReadingListEntry>(
      GURL(kTestURL), kTestTitle, base::Time()));
  initial_entries.back()->SetDistilledInfo(base::FilePath(kTestDistilledPath),
                                           GURL(kTestDistilledURL), 50,
                                           base::Time::FromTimeT(100));
  fake_reading_list_model_storage()->TriggerLoadCompletion(
      std::move(initial_entries));
  EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
      base::test::ios::kWaitForFileOperationTimeout, ^bool {
        base::RunLoop().RunUntilIdle();
        return fake_web_state_.GetLastLoadedData();
      }));
  scoped_refptr<const ReadingListEntry> entry =
      reading_list_model()->GetEntryByURL(url);
  EXPECT_TRUE(entry->IsRead());
  EXPECT_TRUE(offline_page_tab_helper->presenting_offline_page());
}