chromium/chrome/browser/ash/fileapi/recent_disk_source_unittest.cc

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

#include <memory>
#include <string>
#include <vector>

#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/fileapi/recent_disk_source.h"
#include "chrome/browser/ash/fileapi/recent_file.h"
#include "chrome/browser/ash/fileapi/recent_source.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_task_environment.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/browser/test/test_file_system_context.h"
#include "storage/common/file_system/file_system_mount_option.h"
#include "storage/common/file_system/file_system_types.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace ash {

namespace {

// Allows one to specify just the parameters that are relevant to a given test.
struct TestParams {
  TestParams& Query(const std::string query) {
    query_ = query;
    return *this;
  }

  TestParams& CutoffTime(const base::Time& cutoff_time) {
    cutoff_time_ = cutoff_time;
    return *this;
  }

  TestParams& FileType(const RecentSource::FileType type) {
    file_type_ = type;
    return *this;
  }

  TestParams& IgnoreDotFiles(bool ignore_dot_files) {
    ignore_dot_files_ = ignore_dot_files;
    return *this;
  }

  TestParams& MaxDepth(int max_depth) {
    max_depth_ = max_depth;
    return *this;
  }

  TestParams& MaxFiles(int max_files) {
    max_files_ = max_files;
    return *this;
  }

  RecentSource::Params MakeParams(storage::FileSystemContext* context,
                                  const int32_t call_id,
                                  const GURL& origin) {
    return RecentSource::Params(context, call_id, origin, query_, max_files_,
                                cutoff_time_, base::TimeTicks::Max(),
                                file_type_);
  }

  std::unique_ptr<RecentDiskSource> MakeSource(
      const std::string& mount_point_name,
      const std::string& uma_histogram_name) {
    return std::make_unique<RecentDiskSource>(
        extensions::api::file_manager_private::VolumeType::kTesting,
        mount_point_name, ignore_dot_files_, max_depth_, uma_histogram_name);
  }

  std::string query_ = "";
  base::Time cutoff_time_ = base::Time::Min();
  RecentSource::FileType file_type_ = RecentSource::FileType::kAll;
  bool ignore_dot_files_ = false;
  int max_depth_ = 0;
  size_t max_files_ = 100;
};

class RecentDiskSourceTest : public testing::Test {
 public:
  RecentDiskSourceTest() : origin_("https://example.com/") {}

  void SetUp() override {
    profile_ = std::make_unique<TestingProfile>();

    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());

    file_system_context_ = storage::CreateFileSystemContextForTesting(
        /*quota_manager_proxy=*/nullptr, temp_dir_.GetPath());

    mount_point_name_ =
        file_manager::util::GetDownloadsMountPointName(profile_.get());

    ASSERT_TRUE(
        storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
            mount_point_name_, storage::kFileSystemTypeTest,
            storage::FileSystemMountOption(), base::FilePath()));
  }

  void TearDown() override {
    storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
        mount_point_name_);
  }

 protected:
  bool CreateEmptyFile(const std::string& filename, const base::Time& time) {
    base::File file(temp_dir_.GetPath().Append(filename),
                    base::File::FLAG_CREATE | base::File::FLAG_WRITE);
    if (!file.IsValid()) {
      return false;
    }

    return file.SetTimes(time, time);
  }

  std::vector<RecentFile> GetRecentFiles(TestParams params) {
    std::vector<RecentFile> files;
    base::RunLoop run_loop;

    auto source = params.MakeSource(mount_point_name_, uma_histogram_name_);
    source->GetRecentFiles(
        params.MakeParams(file_system_context_.get(), 0, origin_),
        base::BindOnce(
            [](base::RunLoop* run_loop, std::vector<RecentFile>* out_files,
               std::vector<RecentFile> files) {
              run_loop->Quit();
              *out_files = std::move(files);
            },
            &run_loop, &files));

    run_loop.Run();

    return files;
  }

  content::BrowserTaskEnvironment task_environment_;
  const GURL origin_;
  std::unique_ptr<TestingProfile> profile_;
  base::ScopedTempDir temp_dir_;
  scoped_refptr<storage::FileSystemContext> file_system_context_;
  std::string mount_point_name_;
  const std::string uma_histogram_name_ = "uma_histogram_name";
  base::Time base_time_;
};

TEST_F(RecentDiskSourceTest, GetRecentFiles) {
  // Oldest
  ASSERT_TRUE(
      CreateEmptyFile("1.jpg", base::Time::FromSecondsSinceUnixEpoch(1)));
  ASSERT_TRUE(
      CreateEmptyFile("2.jpg", base::Time::FromSecondsSinceUnixEpoch(2)));
  ASSERT_TRUE(
      CreateEmptyFile("3.jpg", base::Time::FromSecondsSinceUnixEpoch(3)));
  ASSERT_TRUE(
      CreateEmptyFile("4.jpg", base::Time::FromSecondsSinceUnixEpoch(4)));
  // Newest

  std::vector<RecentFile> files = GetRecentFiles(TestParams().MaxFiles(3));

  ASSERT_EQ(3u, files.size());
  EXPECT_EQ("4.jpg", files[0].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(4), files[0].last_modified());
  EXPECT_EQ("3.jpg", files[1].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(3), files[1].last_modified());
  EXPECT_EQ("2.jpg", files[2].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(2), files[2].last_modified());

  files = GetRecentFiles(TestParams().Query("4").MaxFiles(3));
  ASSERT_EQ(1u, files.size());
  EXPECT_EQ("4.jpg", files[0].url().path().BaseName().value());

  files = GetRecentFiles(TestParams().Query("foo").MaxFiles(3));
  ASSERT_EQ(0u, files.size());
}

TEST_F(RecentDiskSourceTest, GetRecentFiles_CutoffTime) {
  // Oldest
  ASSERT_TRUE(
      CreateEmptyFile("1.jpg", base::Time::FromSecondsSinceUnixEpoch(1)));
  ASSERT_TRUE(
      CreateEmptyFile("2.jpg", base::Time::FromSecondsSinceUnixEpoch(2)));
  ASSERT_TRUE(
      CreateEmptyFile("3.jpg", base::Time::FromSecondsSinceUnixEpoch(3)));
  ASSERT_TRUE(
      CreateEmptyFile("4.jpg", base::Time::FromSecondsSinceUnixEpoch(4)));
  // Newest

  std::vector<RecentFile> files = GetRecentFiles(
      TestParams()
          .CutoffTime(base::Time::FromMillisecondsSinceUnixEpoch(2500))
          .MaxFiles(3));

  ASSERT_EQ(2u, files.size());
  EXPECT_EQ("4.jpg", files[0].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(4), files[0].last_modified());
  EXPECT_EQ("3.jpg", files[1].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(3), files[1].last_modified());
}

TEST_F(RecentDiskSourceTest, IgnoreDotFiles) {
  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append(".ignore")));
  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append("noignore")));

  // Oldest
  ASSERT_TRUE(CreateEmptyFile("noignore/1.jpg",
                              base::Time::FromSecondsSinceUnixEpoch(1)));
  ASSERT_TRUE(CreateEmptyFile(".ignore/2.jpg",
                              base::Time::FromSecondsSinceUnixEpoch(2)));
  ASSERT_TRUE(
      CreateEmptyFile("3.jpg", base::Time::FromSecondsSinceUnixEpoch(3)));
  ASSERT_TRUE(
      CreateEmptyFile(".4.jpg", base::Time::FromSecondsSinceUnixEpoch(4)));
  // Newest

  std::vector<RecentFile> files = GetRecentFiles(TestParams().MaxFiles(4));

  ASSERT_EQ(4u, files.size());
  EXPECT_EQ(".4.jpg", files[0].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(4), files[0].last_modified());
  EXPECT_EQ("3.jpg", files[1].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(3), files[1].last_modified());
  EXPECT_EQ("2.jpg", files[2].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(2), files[2].last_modified());
  EXPECT_EQ("1.jpg", files[3].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(1), files[3].last_modified());

  files = GetRecentFiles(TestParams().IgnoreDotFiles(true).MaxFiles(4));

  ASSERT_EQ(2u, files.size());
  EXPECT_EQ("3.jpg", files[0].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(3), files[0].last_modified());
  EXPECT_EQ("1.jpg", files[1].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(1), files[1].last_modified());
}

TEST_F(RecentDiskSourceTest, MaxDepth) {
  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append("a")));
  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append("a/b")));
  ASSERT_TRUE(base::CreateDirectory(temp_dir_.GetPath().Append("a/b/c")));

  // Oldest
  ASSERT_TRUE(
      CreateEmptyFile("1.jpg", base::Time::FromSecondsSinceUnixEpoch(1)));
  ASSERT_TRUE(
      CreateEmptyFile("a/2.jpg", base::Time::FromSecondsSinceUnixEpoch(2)));
  ASSERT_TRUE(
      CreateEmptyFile("a/b/3.jpg", base::Time::FromSecondsSinceUnixEpoch(3)));
  ASSERT_TRUE(
      CreateEmptyFile("a/b/c/4.jpg", base::Time::FromSecondsSinceUnixEpoch(4)));
  // Newest

  std::vector<RecentFile> files = GetRecentFiles(TestParams().MaxFiles(4));
  ASSERT_EQ(4u, files.size());

  files = GetRecentFiles(TestParams().MaxFiles(4).MaxDepth(2));

  ASSERT_EQ(2u, files.size());
  EXPECT_EQ("2.jpg", files[0].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(2), files[0].last_modified());
  EXPECT_EQ("1.jpg", files[1].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(1), files[1].last_modified());
}

TEST_F(RecentDiskSourceTest, GetAudioFiles) {
  // Oldest
  ASSERT_TRUE(
      CreateEmptyFile("1.jpg", base::Time::FromSecondsSinceUnixEpoch(1)));
  ASSERT_TRUE(
      CreateEmptyFile("2.mp4", base::Time::FromSecondsSinceUnixEpoch(2)));
  ASSERT_TRUE(
      CreateEmptyFile("3.png", base::Time::FromSecondsSinceUnixEpoch(3)));
  ASSERT_TRUE(
      CreateEmptyFile("4.mp3", base::Time::FromSecondsSinceUnixEpoch(4)));
  ASSERT_TRUE(
      CreateEmptyFile("5.gif", base::Time::FromSecondsSinceUnixEpoch(5)));
  ASSERT_TRUE(
      CreateEmptyFile("6.webm", base::Time::FromSecondsSinceUnixEpoch(6)));
  ASSERT_TRUE(
      CreateEmptyFile("7.amr", base::Time::FromSecondsSinceUnixEpoch(7)));
  // Newest

  std::vector<RecentFile> files =
      GetRecentFiles(TestParams().FileType(RecentSource::FileType::kAudio));

  ASSERT_EQ(2u, files.size());
  EXPECT_EQ("7.amr", files[0].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(7), files[0].last_modified());
  EXPECT_EQ("4.mp3", files[1].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(4), files[1].last_modified());

  files = GetRecentFiles(
      TestParams().Query("7").FileType(RecentSource::FileType::kAudio));
  ASSERT_EQ(1u, files.size());
  EXPECT_EQ("7.amr", files[0].url().path().BaseName().value());

  files = GetRecentFiles(
      TestParams().Query("6").FileType(RecentSource::FileType::kAudio));
  ASSERT_EQ(0u, files.size());
}

TEST_F(RecentDiskSourceTest, GetImageFiles) {
  // Oldest
  ASSERT_TRUE(
      CreateEmptyFile("1.jpg", base::Time::FromSecondsSinceUnixEpoch(1)));
  ASSERT_TRUE(
      CreateEmptyFile("2.mp4", base::Time::FromSecondsSinceUnixEpoch(2)));
  ASSERT_TRUE(
      CreateEmptyFile("3.png", base::Time::FromSecondsSinceUnixEpoch(3)));
  ASSERT_TRUE(
      CreateEmptyFile("4.mp3", base::Time::FromSecondsSinceUnixEpoch(4)));
  ASSERT_TRUE(
      CreateEmptyFile("5.gif", base::Time::FromSecondsSinceUnixEpoch(5)));
  ASSERT_TRUE(
      CreateEmptyFile("6.webm", base::Time::FromSecondsSinceUnixEpoch(6)));
  // RAW images are supported
  ASSERT_TRUE(
      CreateEmptyFile("7.dng", base::Time::FromSecondsSinceUnixEpoch(7)));
  ASSERT_TRUE(
      CreateEmptyFile("8.nef", base::Time::FromSecondsSinceUnixEpoch(8)));
  // Newest

  std::vector<RecentFile> files =
      GetRecentFiles(TestParams().FileType(RecentSource::FileType::kImage));

  ASSERT_EQ(5u, files.size());
  EXPECT_EQ("8.nef", files[0].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(8), files[0].last_modified());
  EXPECT_EQ("7.dng", files[1].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(7), files[1].last_modified());
  EXPECT_EQ("5.gif", files[2].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(5), files[2].last_modified());
  EXPECT_EQ("3.png", files[3].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(3), files[3].last_modified());
  EXPECT_EQ("1.jpg", files[4].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(1), files[4].last_modified());
}

TEST_F(RecentDiskSourceTest, GetVideoFiles) {
  // Oldest
  ASSERT_TRUE(
      CreateEmptyFile("1.jpg", base::Time::FromSecondsSinceUnixEpoch(1)));
  ASSERT_TRUE(
      CreateEmptyFile("2.mp4", base::Time::FromSecondsSinceUnixEpoch(2)));
  ASSERT_TRUE(
      CreateEmptyFile("3.png", base::Time::FromSecondsSinceUnixEpoch(3)));
  ASSERT_TRUE(
      CreateEmptyFile("4.mp3", base::Time::FromSecondsSinceUnixEpoch(4)));
  ASSERT_TRUE(
      CreateEmptyFile("5.gif", base::Time::FromSecondsSinceUnixEpoch(5)));
  ASSERT_TRUE(
      CreateEmptyFile("6.webm", base::Time::FromSecondsSinceUnixEpoch(6)));
  ASSERT_TRUE(
      CreateEmptyFile("7.avi", base::Time::FromSecondsSinceUnixEpoch(7)));
  ASSERT_TRUE(
      CreateEmptyFile("8.mov", base::Time::FromSecondsSinceUnixEpoch(8)));
  // *.wmv is not supported yet.
  ASSERT_TRUE(
      CreateEmptyFile("9.wmv", base::Time::FromSecondsSinceUnixEpoch(9)));
  // Newest

  std::vector<RecentFile> files =
      GetRecentFiles(TestParams().FileType(RecentSource::FileType::kVideo));

  ASSERT_EQ(4u, files.size());
  EXPECT_EQ("8.mov", files[0].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(8), files[0].last_modified());
  EXPECT_EQ("7.avi", files[1].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(7), files[1].last_modified());
  EXPECT_EQ("6.webm", files[2].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(6), files[2].last_modified());
  EXPECT_EQ("2.mp4", files[3].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(2), files[3].last_modified());
}

TEST_F(RecentDiskSourceTest, GetDocumentFiles) {
  // Oldest
  ASSERT_TRUE(
      CreateEmptyFile("1.jpg", base::Time::FromSecondsSinceUnixEpoch(1)));
  ASSERT_TRUE(
      CreateEmptyFile("2.mp4", base::Time::FromSecondsSinceUnixEpoch(2)));
  ASSERT_TRUE(
      CreateEmptyFile("3.png", base::Time::FromSecondsSinceUnixEpoch(3)));
  ASSERT_TRUE(
      CreateEmptyFile("4.doc", base::Time::FromSecondsSinceUnixEpoch(4)));
  ASSERT_TRUE(
      CreateEmptyFile("5.gif", base::Time::FromSecondsSinceUnixEpoch(5)));
  ASSERT_TRUE(
      CreateEmptyFile("6.txt", base::Time::FromSecondsSinceUnixEpoch(6)));
  ASSERT_TRUE(
      CreateEmptyFile("7.avi", base::Time::FromSecondsSinceUnixEpoch(7)));
  ASSERT_TRUE(
      CreateEmptyFile("8.gdoc", base::Time::FromSecondsSinceUnixEpoch(8)));
  // Newest

  std::vector<RecentFile> files =
      GetRecentFiles(TestParams().FileType(RecentSource::FileType::kDocument));

  ASSERT_EQ(3u, files.size());
  EXPECT_EQ("8.gdoc", files[0].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(8), files[0].last_modified());
  EXPECT_EQ("6.txt", files[1].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(6), files[1].last_modified());
  EXPECT_EQ("4.doc", files[2].url().path().BaseName().value());
  EXPECT_EQ(base::Time::FromSecondsSinceUnixEpoch(4), files[2].last_modified());
}

TEST_F(RecentDiskSourceTest, GetRecentFiles_UmaStats) {
  base::HistogramTester histogram_tester;

  GetRecentFiles(TestParams());

  histogram_tester.ExpectTotalCount(uma_histogram_name_, 1);
}

}  // namespace

}  // namespace ash