chromium/components/download/internal/background_service/ios/background_download_service_impl_unittest.cc

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

#include "components/download/internal/background_service/ios/background_download_service_impl.h"

#include <memory>
#include <string>
#include <utility>

#include "base/files/scoped_temp_dir.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "components/download/internal/background_service/client_set.h"
#include "components/download/internal/background_service/ios/background_download_task_helper.h"
#include "components/download/internal/background_service/test/black_hole_log_sink.h"
#include "components/download/internal/background_service/test/mock_file_monitor.h"
#include "components/download/internal/background_service/test/test_store.h"
#include "components/download/public/background_service/test/empty_logger.h"
#include "components/download/public/background_service/test/mock_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"

using ::base::test::RunCallback;
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::NiceMock;
using ServiceStatus = download::BackgroundDownloadService::ServiceStatus;
using StartResult = download::DownloadParams::StartResult;

const char kURL[] = "https://www.example.com/test";
const char kGuid[] = "1234";
const char kGuid1[] = "5678";
const char kGuid2[] = "aabb";
const char kGuid3[] = "yyds";
const base::FilePath::CharType kFilePath[] =
    FILE_PATH_LITERAL("downloaded_file.zip");
const char kCompletionHistogram[] = "Download.Service.Finish.Type";
const char kStartResultHistogram[] = "Download.Service.Request.StartResult";
const char kServiceStartUpResultHistogram[] =
    "Download.Service.StartUpStatus.Initialization";

namespace download {
namespace {

MATCHER_P(CompletionInfoIs, file_path, "") {
  return arg.path == file_path;
}

class MockBackgroundDownloadTaskHelper : public BackgroundDownloadTaskHelper {
 public:
  MockBackgroundDownloadTaskHelper() = default;
  ~MockBackgroundDownloadTaskHelper() override = default;
  MOCK_METHOD(void,
              StartDownload,
              (const std::string& guid,
               const base::FilePath& target_path,
               const RequestParams&,
               const SchedulingParams&,
               CompletionCallback,
               UpdateCallback),
              (override));
};

// Test fixture for BackgroundDownloadServiceImpl.
class BackgroundDownloadServiceImplTest : public PlatformTest {
 protected:
  BackgroundDownloadServiceImplTest() {}
  ~BackgroundDownloadServiceImplTest() override = default;

  void SetUp() override {
    ASSERT_TRUE(dir_.CreateUniqueTempDir());
    auto store = std::make_unique<test::TestStore>();
    store_ = store.get();
    auto model = std::make_unique<ModelImpl>(std::move(store));
    model_ = model.get();
    auto client = std::make_unique<NiceMock<test::MockClient>>();
    client_ = client.get();
    auto clients = std::make_unique<DownloadClientMap>();
    clients->insert(std::make_pair(DownloadClient::TEST, std::move(client)));
    auto client_set = std::make_unique<ClientSet>(std::move(clients));
    auto download_helper = std::make_unique<MockBackgroundDownloadTaskHelper>();
    download_helper_ = download_helper.get();
    auto file_monitor = std::make_unique<NiceMock<MockFileMonitor>>();
    file_monitor_ = file_monitor.get();
    auto logger = std::make_unique<test::EmptyLogger>();
    service_ = std::make_unique<BackgroundDownloadServiceImpl>(
        std::move(client_set), std::move(model), std::move(download_helper),
        std::move(file_monitor), dir_.GetPath(), std::move(logger), &log_sink_,
        &clock_);
    ON_CALL(*file_monitor_, DeleteUnknownFiles(_, _, _))
        .WillByDefault(base::test::RunOnceCallbackRepeatedly<2>());
    service_->Initialize(base::DoNothing());
  }

  InitializableBackgroundDownloadService* service() { return service_.get(); }
  std::unique_ptr<std::vector<Entry>> empty_entries() {
    return std::make_unique<std::vector<Entry>>();
  }
  DownloadParams CreateDownloadParams(const std::string& url) {
    DownloadParams download_params;
    download_params.client = DownloadClient::TEST;
    download_params.guid = kGuid;
    download_params.callback = start_callback_.Get();
    download_params.request_params.url = GURL(url);
    download_params.custom_data["foo"] = "foobar";
    return download_params;
  }

  void Init() { InitWithData(empty_entries()); }

  // Initializes with preloaded |entries| from the database.
  void InitWithData(std::unique_ptr<std::vector<Entry>> entries) {
    store_->TriggerInit(/*success=*/true, std::move(entries));
    file_monitor_->TriggerInit(/*success=*/true);
  }

  base::test::TaskEnvironment task_environment_;
  base::ScopedTempDir dir_;
  base::SimpleTestClock clock_;
  MockBackgroundDownloadTaskHelper* download_helper_;
  test::TestStore* store_;
  Model* model_;
  test::MockClient* client_;
  base::MockCallback<DownloadParams::StartCallback> start_callback_;
  MockFileMonitor* file_monitor_;
  base::HistogramTester histogram_tester_;

 private:
  test::BlackHoleLogSink log_sink_;
  std::unique_ptr<InitializableBackgroundDownloadService> service_;
};

TEST_F(BackgroundDownloadServiceImplTest, InitSuccess) {
  EXPECT_EQ(ServiceStatus::STARTING_UP, service()->GetStatus());
  EXPECT_CALL(*client_, OnServiceInitialized(false, _));
  Init();
  EXPECT_EQ(ServiceStatus::READY, service()->GetStatus());
  histogram_tester_.ExpectBucketCount(kServiceStartUpResultHistogram,
                                      stats::StartUpResult::SUCCESS, 1);
}

TEST_F(BackgroundDownloadServiceImplTest, InitDbFailure) {
  EXPECT_EQ(ServiceStatus::STARTING_UP, service()->GetStatus());
  EXPECT_CALL(*client_, OnServiceUnavailable());
  store_->TriggerInit(/*success=*/false, empty_entries());
  task_environment_.RunUntilIdle();
  EXPECT_EQ(ServiceStatus::UNAVAILABLE, service()->GetStatus());
  histogram_tester_.ExpectBucketCount(
      kServiceStartUpResultHistogram,
      stats::StartUpResult::FAILURE_REASON_MODEL, 1);
}

TEST_F(BackgroundDownloadServiceImplTest, InitFileMonitorFailure) {
  EXPECT_EQ(ServiceStatus::STARTING_UP, service()->GetStatus());
  EXPECT_CALL(*client_, OnServiceUnavailable());
  store_->TriggerInit(/*success=*/true, empty_entries());
  file_monitor_->TriggerInit(/*success=*/false);
  task_environment_.RunUntilIdle();
  EXPECT_EQ(ServiceStatus::UNAVAILABLE, service()->GetStatus());
  histogram_tester_.ExpectBucketCount(
      kServiceStartUpResultHistogram,
      stats::StartUpResult::FAILURE_REASON_FILE_MONITOR, 1);
}

// Db records that are not associated with any registered clients or unfinished
// downloads should be pruned.
TEST_F(BackgroundDownloadServiceImplTest, InitDbPruned) {
  EXPECT_EQ(ServiceStatus::STARTING_UP, service()->GetStatus());
  EXPECT_CALL(*client_, OnServiceInitialized(false, _));
  std::unique_ptr<std::vector<Entry>> entries =
      std::make_unique<std::vector<Entry>>();

  // Build an entry without a valid client.
  Entry entry_invalid_client;
  entry_invalid_client.state = Entry::State::COMPLETE;
  entry_invalid_client.guid = kGuid;
  entry_invalid_client.create_time = clock_.Now();
  entries->emplace_back(std::move(entry_invalid_client));

  // Build unfinished entry.
  Entry entry_unfinished;
  entry_unfinished.client = DownloadClient::TEST;
  entry_unfinished.state = Entry::State::ACTIVE;
  entry_unfinished.guid = kGuid1;
  entry_unfinished.create_time = clock_.Now();
  entries->emplace_back(std::move(entry_unfinished));

  // Build an expired entry.
  Entry entry_expired;
  entry_expired.client = DownloadClient::TEST;
  entry_expired.state = Entry::State::COMPLETE;
  entry_expired.guid = kGuid2;
  entry_expired.create_time = clock_.Now() - base::Days(300);
  entries->emplace_back(std::move(entry_expired));

  // Build a completed entry that should be kept.
  Entry entry;
  entry.client = DownloadClient::TEST;
  entry.state = Entry::State::COMPLETE;
  entry.guid = kGuid3;
  entry.create_time = clock_.Now();
  entries->emplace_back(std::move(entry));

  InitWithData(std::move(entries));
  EXPECT_EQ(ServiceStatus::READY, service()->GetStatus());
  EXPECT_FALSE(model_->Get(kGuid))
      << "Entry with invalid client should be pruned.";
  EXPECT_FALSE(model_->Get(kGuid1)) << "Unfinished entry should be pruned.";
  EXPECT_FALSE(model_->Get(kGuid2)) << "Expired entry should be pruned.";
  EXPECT_TRUE(model_->Get(kGuid3)) << "This entry should be kept.";
}

TEST_F(BackgroundDownloadServiceImplTest, StartDownloadDbFailure) {
  Init();
  EXPECT_CALL(start_callback_, Run(kGuid, StartResult::INTERNAL_ERROR));
  auto download_params = CreateDownloadParams(kURL);
  service()->StartDownload(std::move(download_params));
  store_->TriggerUpdate(/*success=*/false);
  EXPECT_EQ(kGuid, store_->LastUpdatedEntry()->guid);
  task_environment_.RunUntilIdle();
  histogram_tester_.ExpectBucketCount(
      kStartResultHistogram, DownloadParams::StartResult::INTERNAL_ERROR, 1);
}

// Verifies the case for failure in platform api.
TEST_F(BackgroundDownloadServiceImplTest, StartDownloadHelperFailure) {
  Init();
  EXPECT_CALL(start_callback_, Run(kGuid, StartResult::ACCEPTED));
  EXPECT_CALL(*download_helper_, StartDownload(_, _, _, _, _, _))
      .WillOnce(RunOnceCallback<4>(/*success=*/false, base::FilePath(), 0));
  EXPECT_CALL(*client_,
              OnDownloadFailed(kGuid, CompletionInfoIs(base::FilePath()),
                               download::Client::FailureReason::UNKNOWN));
  auto download_params = CreateDownloadParams(kURL);
  service()->StartDownload(std::move(download_params));
  store_->TriggerUpdate(/*success=*/true);
  EXPECT_EQ(kGuid, store_->LastRemovedEntry());
  task_environment_.RunUntilIdle();
  histogram_tester_.ExpectBucketCount(kCompletionHistogram,
                                      CompletionType::FAIL, 1);
}

// Verifies the case for a successful download.
TEST_F(BackgroundDownloadServiceImplTest, StartDownloadSuccess) {
  Init();
  EXPECT_CALL(start_callback_, Run(kGuid, StartResult::ACCEPTED));
  EXPECT_CALL(*download_helper_, StartDownload(_, _, _, _, _, _))
      .WillOnce(
          RunOnceCallback<4>(/*success=*/true, base::FilePath(kFilePath), 0));
  EXPECT_CALL(
      *client_,
      OnDownloadSucceeded(kGuid, CompletionInfoIs(base::FilePath(kFilePath))));
  auto download_params = CreateDownloadParams(kURL);
  service()->StartDownload(std::move(download_params));
  store_->TriggerUpdate(/*success=*/true);
  EXPECT_EQ(kGuid, store_->LastUpdatedEntry()->guid);
  EXPECT_EQ(clock_.Now(), store_->LastUpdatedEntry()->create_time);
  EXPECT_EQ(clock_.Now(), store_->LastUpdatedEntry()->completion_time);
  EXPECT_EQ(Entry::State::COMPLETE, store_->LastUpdatedEntry()->state);
  EXPECT_EQ(dir_.GetPath().AppendASCII(kGuid),
            store_->LastUpdatedEntry()->target_file_path);
  EXPECT_EQ("foobar", store_->LastUpdatedEntry()->custom_data.at("foo"));
  task_environment_.RunUntilIdle();
  histogram_tester_.ExpectBucketCount(kCompletionHistogram,
                                      CompletionType::SUCCEED, 1);
}

// Verifies Client::OnDownloadUpdated() is called.
TEST_F(BackgroundDownloadServiceImplTest, OnDownloadUpdated) {
  Init();
  EXPECT_CALL(start_callback_, Run(kGuid, StartResult::ACCEPTED));
  EXPECT_CALL(*download_helper_, StartDownload(_, _, _, _, _, _))
      .WillOnce(RunCallback<5>(10u));
  EXPECT_CALL(*client_, OnDownloadUpdated(kGuid, 0u, 10u));
  auto download_params = CreateDownloadParams(kURL);
  service()->StartDownload(std::move(download_params));

  // Advance the time to make sure the update is not throttled.
  clock_.Advance(base::Seconds(11));
  store_->TriggerUpdate(/*success=*/true);
  EXPECT_EQ(kGuid, store_->LastUpdatedEntry()->guid);
  EXPECT_EQ(10u, store_->LastUpdatedEntry()->bytes_downloaded);
  EXPECT_EQ("foobar", store_->LastUpdatedEntry()->custom_data.at("foo"));
  task_environment_.RunUntilIdle();
}

}  // namespace
}  // namespace download