| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/offline_pages/offline_page_mhtml_archiver.h" |
| |
| #include <stdint.h> |
| #include <memory> |
| #include <string> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "components/offline_pages/core/client_namespace_constants.h" |
| #include "components/offline_pages/core/model/offline_page_model_utils.h" |
| #include "components/offline_pages/core/offline_clock.h" |
| #include "components/offline_pages/core/test_scoped_offline_clock.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace offline_pages { |
| |
| namespace { |
| |
| const char kTestURL[] = "http://example.com/hello.mhtml"; |
| const char kNonExistentURL[] = "http://example.com/non_existent.mhtml"; |
| // Size of chrome/test/data/offline_pages/hello.mhtml |
| const int64_t kTestFileSize = 471LL; |
| const std::u16string kTestTitle = u"a title"; |
| // SHA256 Hash of chrome/test/data/offline_pages/hello.mhtml |
| const std::string kTestDigest( |
| "\x43\x60\x62\x02\x06\x15\x0f\x3e\x77\x99\x3d\xed\xdc\xd4\xe2\x0d\xbe\xbd" |
| "\x77\x1a\xfb\x32\x00\x51\x7e\x63\x7d\x3b\x2e\x46\x63\xf6", |
| 32); |
| |
| constexpr base::TimeDelta kTimeToSaveMhtml = base::Milliseconds(1000); |
| constexpr base::TimeDelta kTimeToComputeDigest = base::Milliseconds(10); |
| |
| class TestMHTMLArchiver : public OfflinePageMHTMLArchiver { |
| public: |
| enum class TestScenario { |
| SUCCESS, |
| NOT_ABLE_TO_ARCHIVE, |
| WEB_CONTENTS_MISSING, |
| }; |
| |
| TestMHTMLArchiver(const GURL& url, |
| const TestScenario test_scenario, |
| TestScopedOfflineClock* clock); |
| |
| TestMHTMLArchiver(const TestMHTMLArchiver&) = delete; |
| TestMHTMLArchiver& operator=(const TestMHTMLArchiver&) = delete; |
| |
| ~TestMHTMLArchiver() override; |
| |
| private: |
| void GenerateMHTML(const base::FilePath& archives_dir, |
| content::WebContents* web_contents, |
| const CreateArchiveParams& create_archive_params) override; |
| |
| const GURL url_; |
| const TestScenario test_scenario_; |
| // Not owned. |
| raw_ptr<TestScopedOfflineClock> clock_; |
| }; |
| |
| TestMHTMLArchiver::TestMHTMLArchiver(const GURL& url, |
| const TestScenario test_scenario, |
| TestScopedOfflineClock* clock) |
| : url_(url), test_scenario_(test_scenario), clock_(clock) {} |
| |
| TestMHTMLArchiver::~TestMHTMLArchiver() { |
| } |
| |
| void TestMHTMLArchiver::GenerateMHTML( |
| const base::FilePath& archives_dir, |
| content::WebContents* web_contents, |
| const CreateArchiveParams& create_archive_params) { |
| if (test_scenario_ == TestScenario::WEB_CONTENTS_MISSING) { |
| ReportFailure(ArchiverResult::ERROR_CONTENT_UNAVAILABLE); |
| return; |
| } |
| |
| if (test_scenario_ == TestScenario::NOT_ABLE_TO_ARCHIVE) { |
| ReportFailure(ArchiverResult::ERROR_ARCHIVE_CREATION_FAILED); |
| return; |
| } |
| |
| EXPECT_EQ(kDownloadNamespace, create_archive_params.name_space); |
| base::FilePath archive_file_path = |
| archives_dir.AppendASCII(url_.ExtractFileName()); |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&TestMHTMLArchiver::OnGenerateMHTMLDone, |
| base::Unretained(this), url_, archive_file_path, |
| kTestTitle, create_archive_params.name_space, |
| OfflineTimeNow(), |
| content::MHTMLGenerationResult(kTestFileSize, nullptr))); |
| |
| clock_->Advance(kTimeToSaveMhtml); |
| } |
| |
| } // namespace |
| |
| class OfflinePageMHTMLArchiverTest : public testing::Test { |
| public: |
| OfflinePageMHTMLArchiverTest(); |
| |
| OfflinePageMHTMLArchiverTest(const OfflinePageMHTMLArchiverTest&) = delete; |
| OfflinePageMHTMLArchiverTest& operator=(const OfflinePageMHTMLArchiverTest&) = |
| delete; |
| |
| ~OfflinePageMHTMLArchiverTest() override; |
| |
| void SetUp() override; |
| |
| // Creates an archiver for testing scenario and uses it to create an archive. |
| void CreateArchive(const GURL& url, TestMHTMLArchiver::TestScenario scenario); |
| |
| // Test tooling methods. |
| void PumpLoop(); |
| void WaitForAsyncOperation(); |
| |
| base::FilePath GetTestFilePath(const GURL& url) const { |
| return archive_dir_path_.AppendASCII(url.ExtractFileName()); |
| } |
| base::HistogramTester* histogram_tester() { return &histogram_tester_; } |
| |
| OfflinePageArchiver::ArchiverResult last_result() const { |
| return last_result_; |
| } |
| const base::FilePath& last_file_path() const { return last_file_path_; } |
| int64_t last_file_size() const { return last_file_size_; } |
| const std::string& last_digest() const { return last_digest_; } |
| |
| OfflinePageArchiver::CreateArchiveCallback callback() { |
| return base::BindOnce(&OfflinePageMHTMLArchiverTest::OnCreateArchiveDone, |
| base::Unretained(this)); |
| } |
| |
| private: |
| void OnCreateArchiveDone(OfflinePageArchiver::ArchiverResult result, |
| const GURL& url, |
| const base::FilePath& file_path, |
| const std::u16string& title, |
| int64_t file_size, |
| const std::string& digest); |
| |
| content::BrowserTaskEnvironment task_environment_; |
| base::FilePath archive_dir_path_; |
| base::HistogramTester histogram_tester_; |
| |
| OfflinePageArchiver::ArchiverResult last_result_; |
| GURL last_url_; |
| base::FilePath last_file_path_; |
| int64_t last_file_size_; |
| std::string last_digest_; |
| bool async_operation_completed_ = false; |
| base::OnceClosure async_operation_completed_callback_; |
| |
| TestScopedOfflineClock clock_; |
| }; |
| |
| OfflinePageMHTMLArchiverTest::OfflinePageMHTMLArchiverTest() |
| : task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD), |
| last_result_(OfflinePageArchiver::ArchiverResult::ERROR_DEVICE_FULL), |
| last_file_size_(0L) {} |
| |
| OfflinePageMHTMLArchiverTest::~OfflinePageMHTMLArchiverTest() { |
| } |
| |
| void OfflinePageMHTMLArchiverTest::SetUp() { |
| base::FilePath test_data_dir_path; |
| ASSERT_TRUE( |
| base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_path)); |
| archive_dir_path_ = test_data_dir_path.AppendASCII("offline_pages"); |
| clock_.SetNow(base::Time::Now()); |
| } |
| |
| void OfflinePageMHTMLArchiverTest::CreateArchive( |
| const GURL& url, |
| TestMHTMLArchiver::TestScenario scenario) { |
| TestMHTMLArchiver archiver(url, scenario, &clock_); |
| archiver.CreateArchive( |
| archive_dir_path_, |
| OfflinePageArchiver::CreateArchiveParams(kDownloadNamespace), nullptr, |
| callback()); |
| PumpLoop(); |
| clock_.Advance(kTimeToComputeDigest); |
| WaitForAsyncOperation(); |
| } |
| |
| void OfflinePageMHTMLArchiverTest::OnCreateArchiveDone( |
| OfflinePageArchiver::ArchiverResult result, |
| const GURL& url, |
| const base::FilePath& file_path, |
| const std::u16string& title, |
| int64_t file_size, |
| const std::string& digest) { |
| DCHECK(!async_operation_completed_); |
| async_operation_completed_ = true; |
| last_url_ = url; |
| last_result_ = result; |
| last_file_path_ = file_path; |
| last_file_size_ = file_size; |
| last_digest_ = digest; |
| if (!async_operation_completed_callback_.is_null()) |
| std::move(async_operation_completed_callback_).Run(); |
| } |
| |
| void OfflinePageMHTMLArchiverTest::PumpLoop() { |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void OfflinePageMHTMLArchiverTest::WaitForAsyncOperation() { |
| // No need to wait if async operation is not needed. |
| if (async_operation_completed_) |
| return; |
| base::RunLoop run_loop; |
| async_operation_completed_callback_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| // Tests that creation of an archiver fails when web contents is missing. |
| TEST_F(OfflinePageMHTMLArchiverTest, WebContentsMissing) { |
| GURL page_url = GURL(kTestURL); |
| CreateArchive(page_url, |
| TestMHTMLArchiver::TestScenario::WEB_CONTENTS_MISSING); |
| |
| EXPECT_EQ(OfflinePageArchiver::ArchiverResult::ERROR_CONTENT_UNAVAILABLE, |
| last_result()); |
| EXPECT_EQ(base::FilePath(), last_file_path()); |
| } |
| |
| // Tests for archiver failing save an archive. |
| TEST_F(OfflinePageMHTMLArchiverTest, NotAbleToGenerateArchive) { |
| GURL page_url = GURL(kTestURL); |
| CreateArchive(page_url, TestMHTMLArchiver::TestScenario::NOT_ABLE_TO_ARCHIVE); |
| |
| EXPECT_EQ(OfflinePageArchiver::ArchiverResult::ERROR_ARCHIVE_CREATION_FAILED, |
| last_result()); |
| EXPECT_EQ(base::FilePath(), last_file_path()); |
| EXPECT_EQ(0LL, last_file_size()); |
| } |
| |
| // Tests for failing to compute digest for archive file. |
| TEST_F(OfflinePageMHTMLArchiverTest, DigestError) { |
| GURL page_url = GURL(kNonExistentURL); |
| CreateArchive(page_url, TestMHTMLArchiver::TestScenario::SUCCESS); |
| |
| EXPECT_EQ( |
| OfflinePageArchiver::ArchiverResult::ERROR_DIGEST_CALCULATION_FAILED, |
| last_result()); |
| EXPECT_EQ(base::FilePath(), last_file_path()); |
| EXPECT_EQ(0LL, last_file_size()); |
| } |
| |
| // Tests for successful creation of the offline page archive. |
| TEST_F(OfflinePageMHTMLArchiverTest, SuccessfullyCreateOfflineArchive) { |
| GURL page_url = GURL(kTestURL); |
| CreateArchive(page_url, TestMHTMLArchiver::TestScenario::SUCCESS); |
| |
| EXPECT_EQ(OfflinePageArchiver::ArchiverResult::SUCCESSFULLY_CREATED, |
| last_result()); |
| EXPECT_EQ(GetTestFilePath(page_url), last_file_path()); |
| EXPECT_EQ(kTestFileSize, last_file_size()); |
| EXPECT_EQ(kTestDigest, last_digest()); |
| } |
| |
| } // namespace offline_pages |