chromium/chrome/browser/ash/file_manager/fileapi_util_unittest.cc

// Copyright 2014 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/ash/file_manager/fileapi_util.h"

#include <memory>
#include <string>

#include "base/files/file_error_or.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/file_system_provider/fake_extension_provider.h"
#include "chrome/browser/ash/file_system_provider/fake_provided_file_system.h"
#include "chrome/browser/ash/file_system_provider/service.h"
#include "chrome/browser/ash/file_system_provider/service_factory.h"
#include "chrome/browser/ash/fileapi/file_system_backend.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/browser/test/async_file_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
#include "ui/shell_dialogs/selected_file_info.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace file_manager {
namespace util {
namespace {

// Helper class that sets up a temporary file system.
class TempFileSystem {
 public:
  TempFileSystem(Profile* profile, const GURL& appURL)
      : name_(base::UnguessableToken::Create().ToString()),
        appURL_(appURL),
        origin_(url::Origin::Create(appURL)),
        file_system_context_(
            GetFileSystemContextForSourceURL(profile, appURL)) {}

  ~TempFileSystem() {
    storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(name_);
  }

  // Finishes setting up the temporary file system. Must be called before use.
  bool SetUp() {
    if (!temp_dir_.CreateUniqueTempDir()) {
      return false;
    }
    if (!storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
            name_, storage::kFileSystemTypeLocal,
            storage::FileSystemMountOption(), temp_dir_.GetPath())) {
      return false;
    }

    // Grant the test extension the ability to access the just created
    // file system.
    ash::FileSystemBackend::Get(*file_system_context_)
        ->GrantFileAccessToOrigin(origin_, base::FilePath(name_));
    return true;
  }

  bool TearDown() {
    return storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
        name_);
  }

  // For the given FileSystemURL creates a file.
  base::File::Error CreateFile(const storage::FileSystemURL& url) {
    return storage::AsyncFileTestHelper::CreateFile(file_system_context_, url);
  }

  // For the given FileSystemURL creates a directory.
  base::File::Error CreateDirectory(const storage::FileSystemURL& url) {
    return storage::AsyncFileTestHelper::CreateDirectory(file_system_context_,
                                                         url);
  }

  // Creates an external file system URL for the given path.
  storage::FileSystemURL CreateFileSystemURL(const std::string& path) {
    return file_system_context_->CreateCrackedFileSystemURL(
        blink::StorageKey::CreateFirstParty(origin_),
        storage::kFileSystemTypeExternal,
        base::FilePath().Append(name_).Append(
            base::FilePath::FromUTF8Unsafe(path)));
  }

 private:
  const std::string name_;
  const GURL appURL_;
  const url::Origin origin_;
  const raw_ptr<storage::FileSystemContext> file_system_context_;
  base::ScopedTempDir temp_dir_;
};

class FileManagerFileAPIUtilTest : public ::testing::Test {
 public:
  // Carries information on how to create a FileSystemURL for a given file name.
  // For !valid orders we create a test URL. Otherwise, we use temp file system.
  struct FileSystemURLOrder {
    std::string file_name;
    bool valid;
  };

  void SetUp() override {
    testing::Test::SetUp();
    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    ASSERT_TRUE(profile_manager_->SetUp());
    profile_ = profile_manager_->CreateTestingProfile("testing_profile");
  }

  void TearDown() override {
    profile_manager_->DeleteAllTestingProfiles();
    profile_ = nullptr;
    profile_manager_.reset();
  }

  TestingProfile* GetProfile() { return profile_; }

 protected:
  // Checks if the conversion of FileDefinition to EntryDefinition works
  // correctly for the given |appURLStr| and a set of |orders|. If the
  // order indicates that the file should not be created, we expect the
  // conversion to return base::File::FILE_ERROR_NOT_FOUND error. Otherwise,
  // we expect base::File::FILE_OK status.
  void CheckConvertFileDefinitionListToEntryDefinitionList(
      const std::string& appURLStr,
      const std::vector<FileSystemURLOrder>& orders) {
    GURL appURL(appURLStr);
    ASSERT_TRUE(appURL.is_valid());
    auto temp_file_system = std::make_unique<TempFileSystem>(profile_, appURL);
    ASSERT_TRUE(temp_file_system->SetUp());

    std::vector<base::File::Error> errors;
    std::vector<FileDefinition> file_definitions;
    for (const FileSystemURLOrder& order : orders) {
      storage::FileSystemURL fs_url;
      if (order.valid) {
        fs_url = temp_file_system->CreateFileSystemURL(order.file_name);
        errors.push_back(base::File::FILE_OK);
      } else {
        fs_url = storage::FileSystemURL::CreateForTest(
            blink::StorageKey::CreateFirstParty(url::Origin::Create(appURL)),
            storage::kFileSystemTypeExternal, base::FilePath(order.file_name));
        errors.push_back(base::File::FILE_ERROR_NOT_FOUND);
      }
      file_definitions.push_back({.virtual_path = fs_url.virtual_path()});
    }

    base::RunLoop run_loop;
    EntryDefinitionListCallback callback = base::BindOnce(
        [](std::unique_ptr<TempFileSystem> temp_file_system,
           std::vector<base::File::Error> errors,
           base::OnceClosure quit_closure,
           std::unique_ptr<EntryDefinitionList> entries) {
          ASSERT_EQ(errors.size(), entries->size());
          for (size_t i = 0; i < errors.size(); ++i) {
            const EntryDefinition& entry_def = (*entries)[i];
            EXPECT_EQ(errors[i], entry_def.error)
                << "for " << entry_def.full_path << " at " << i;
          }

          EXPECT_TRUE(temp_file_system->TearDown());
          std::move(quit_closure).Run();
        },
        std::move(temp_file_system), std::move(errors), run_loop.QuitClosure());
    ConvertFileDefinitionListToEntryDefinitionList(
        GetFileSystemContextForSourceURL(profile_, appURL),
        url::Origin::Create(appURL), file_definitions, std::move(callback));
    run_loop.Run();
  }

  void TestGenerateUnusedFilename(std::vector<std::string> existing_files,
                                  std::string target_filename,
                                  base::FileErrorOr<std::string> expected);

  const std::string file_system_id_ = "test-filesystem";

 private:
  base::test::ScopedFeatureList feature_list_;
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestingProfileManager> profile_manager_;
  raw_ptr<TestingProfile, DanglingUntriaged> profile_;
};

// Passes the |result| to the |output| pointer.
void PassFileChooserFileInfoList(FileChooserFileInfoList* output,
                                 FileChooserFileInfoList result) {
  for (const auto& file : result) {
    output->push_back(file->Clone());
  }
}

TEST_F(FileManagerFileAPIUtilTest,
       ConvertSelectedFileInfoListToFileChooserFileInfoList) {
  Profile* const profile = GetProfile();
  const std::string extension_id = "abc";
  auto fake_provider =
      ash::file_system_provider::FakeExtensionProvider::Create(extension_id);
  const auto kProviderId = fake_provider->GetId();
  auto* service = ash::file_system_provider::Service::Get(profile);
  service->RegisterProvider(std::move(fake_provider));
  service->MountFileSystem(
      kProviderId, ash::file_system_provider::MountOptions(file_system_id_,
                                                           "Test FileSystem"));

  // Obtain the file system context.
  content::StoragePartition* const partition =
      profile->GetStoragePartitionForUrl(GURL("http://example.com"));
  ASSERT_TRUE(partition);
  storage::FileSystemContext* const context = partition->GetFileSystemContext();
  ASSERT_TRUE(context);

  // Prepare the test input.
  SelectedFileInfoList selected_info_list;

  // Native file.
  {
    ui::SelectedFileInfo info;
    info.file_path = base::FilePath(FILE_PATH_LITERAL("/native/File 1.txt"));
    info.local_path = base::FilePath(FILE_PATH_LITERAL("/native/File 1.txt"));
    info.display_name = "display_name";
    selected_info_list.push_back(info);
  }

  const std::string path = FILE_PATH_LITERAL(base::StrCat(
      {"/provided/", extension_id, ":", file_system_id_, ":/hello.txt"}));
  // Non-native file with cache.
  {
    ui::SelectedFileInfo info;
    info.file_path = base::FilePath(path);
    info.local_path = base::FilePath(FILE_PATH_LITERAL("/native/cache/xxx"));
    info.display_name = "display_name";
    selected_info_list.push_back(info);
  }

  // Non-native file without.
  {
    ui::SelectedFileInfo info;
    info.file_path = base::FilePath(path);
    selected_info_list.push_back(info);
  }

  // Run the test target.
  FileChooserFileInfoList result;
  ConvertSelectedFileInfoListToFileChooserFileInfoList(
      context, url::Origin::Create(GURL("http://example.com")),
      selected_info_list,
      base::BindOnce(&PassFileChooserFileInfoList, &result));
  content::RunAllTasksUntilIdle();

  // Check the result.
  ASSERT_EQ(3u, result.size());

  EXPECT_TRUE(result[0]->is_native_file());
  EXPECT_EQ(FILE_PATH_LITERAL("/native/File 1.txt"),
            result[0]->get_native_file()->file_path.value());
  EXPECT_EQ(u"display_name", result[0]->get_native_file()->display_name);

  EXPECT_TRUE(result[1]->is_native_file());
  EXPECT_EQ(FILE_PATH_LITERAL("/native/cache/xxx"),
            result[1]->get_native_file()->file_path.value());
  EXPECT_EQ(u"display_name", result[1]->get_native_file()->display_name);

  EXPECT_TRUE(result[2]->is_file_system());
  EXPECT_TRUE(result[2]->get_file_system()->url.is_valid());
  const storage::FileSystemURL url =
      context->CrackURLInFirstPartyContext(result[2]->get_file_system()->url);
  EXPECT_EQ(GURL("http://example.com"), url.origin().GetURL());
  EXPECT_EQ(storage::kFileSystemTypeIsolated, url.mount_type());
  EXPECT_EQ(storage::kFileSystemTypeProvided, url.type());
  EXPECT_EQ(55u, result[2]->get_file_system()->length);
}

TEST_F(FileManagerFileAPIUtilTest,
       ConvertFileDefinitionListToEntryDefinitionListExtension) {
  std::vector<FileSystemURLOrder> orders = {
      {.file_name = "x.txt", .valid = true},
      {.file_name = "no-such-file.txt", .valid = false},
      {.file_name = "z.txt", .valid = true},
  };
  CheckConvertFileDefinitionListToEntryDefinitionList("chrome-extension://abc",
                                                      orders);
  CheckConvertFileDefinitionListToEntryDefinitionList("chrome-extension://abc/",
                                                      orders);
  CheckConvertFileDefinitionListToEntryDefinitionList(
      "chrome-extension://abc/efg", orders);
}

TEST_F(FileManagerFileAPIUtilTest,
       ConvertFileDefinitionListToEntryDefinitionListApp) {
  std::vector<FileSystemURLOrder> orders = {
      {.file_name = "a.txt", .valid = false},
      {.file_name = "b.txt", .valid = false},
      {.file_name = "i-am-a-file.txt", .valid = true},
  };
  CheckConvertFileDefinitionListToEntryDefinitionList("chrome://file-manager",
                                                      orders);
  CheckConvertFileDefinitionListToEntryDefinitionList("chrome://file-manager/",
                                                      orders);
  CheckConvertFileDefinitionListToEntryDefinitionList(
      "chrome://file-manager/abc", orders);
}

TEST_F(FileManagerFileAPIUtilTest,
       ConvertFileDefinitionListToEntryDefinitionNullContext) {
  Profile* const profile = GetProfile();
  const GURL appURL("chrome-extension://abc/");
  auto temp_file_system = std::make_unique<TempFileSystem>(profile, appURL);
  ASSERT_TRUE(temp_file_system->SetUp());
  storage::FileSystemURL x_file_url =
      temp_file_system->CreateFileSystemURL(".");
  FileDefinition x_fd = {.virtual_path = x_file_url.virtual_path()};

  // Check a simple case where the context is already null before we have
  // a chance to call the conversion function.
  base::RunLoop run_loop;
  EntryDefinitionListCallback callback = base::BindOnce(
      [](std::unique_ptr<TempFileSystem> temp_file_system,
         base::OnceClosure quit_closure,
         std::unique_ptr<EntryDefinitionList> entries) {
        ASSERT_EQ(1u, entries->size());
        EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION,
                  entries->at(0).error);
        EXPECT_TRUE(temp_file_system->TearDown());
        std::move(quit_closure).Run();
      },
      std::move(temp_file_system), run_loop.QuitClosure());

  ConvertFileDefinitionListToEntryDefinitionList(
      nullptr, url::Origin::Create(appURL), {x_fd}, std::move(callback));
  run_loop.Run();
}

TEST_F(FileManagerFileAPIUtilTest,
       ConvertFileDefinitionListToEntryDefinitionContextReset) {
  Profile* const profile = GetProfile();
  const GURL appURL("chrome-extension://abc/");
  auto temp_file_system = std::make_unique<TempFileSystem>(profile, appURL);
  ASSERT_TRUE(temp_file_system->SetUp());
  storage::FileSystemURL x_file_url =
      temp_file_system->CreateFileSystemURL(".");
  FileDefinition x_fd = {.virtual_path = x_file_url.virtual_path()};
  scoped_refptr<storage::FileSystemContext> file_system_context =
      GetFileSystemContextForSourceURL(profile, appURL);

  base::RunLoop run_loop;
  EntryDefinitionListCallback callback = base::BindOnce(
      [](std::unique_ptr<TempFileSystem> temp_file_system,
         base::OnceClosure quit_closure,
         std::unique_ptr<EntryDefinitionList> entries) {
        ASSERT_EQ(1u, entries->size());
        EXPECT_EQ(base::File::FILE_OK, entries->at(0).error);
        EXPECT_TRUE(temp_file_system->TearDown());
        std::move(quit_closure).Run();
      },
      std::move(temp_file_system), run_loop.QuitClosure());

  // Check the case where the context is not null, but is reset to null as
  // soon as function call is completed. Conversion takes place on a
  // different thread, after the function call returns. However, since
  // it holds to a copy of a scoped pointer we expect it to succeed.
  ConvertFileDefinitionListToEntryDefinitionList(file_system_context,
                                                 url::Origin::Create(appURL),
                                                 {x_fd}, std::move(callback));
  file_system_context.reset();

  run_loop.Run();
}

TEST_F(FileManagerFileAPIUtilTest, IsFileManagerURL) {
  EXPECT_TRUE(IsFileManagerURL(GetFileManagerURL()));
  EXPECT_TRUE(IsFileManagerURL(GetFileManagerURL().Resolve("/some/path")));
  EXPECT_TRUE(IsFileManagerURL(
      GetFileManagerURL().Resolve("/some/path").Resolve("#anchor")));
  EXPECT_TRUE(IsFileManagerURL(GetFileManagerURL()
                                   .Resolve("/some/path")
                                   .Resolve("#anchor")
                                   .Resolve("?a=b")));
  EXPECT_FALSE(IsFileManagerURL(GURL("chrome://not-file-manager")));
  EXPECT_FALSE(IsFileManagerURL(GURL("chrome://not-file-manager/")));
  EXPECT_FALSE(IsFileManagerURL(
      GURL("chrome-extension://iamnotafilemanagerextensionid")));
  EXPECT_FALSE(IsFileManagerURL(
      GURL("chrome-extension://iamnotafilemanagerextensionid/")));
}

void FileManagerFileAPIUtilTest::TestGenerateUnusedFilename(
    std::vector<std::string> existing_files,
    std::string target_filename,
    base::FileErrorOr<std::string> expected) {
  const GURL appURL("chrome-extension://abc/");
  auto temp_file_system =
      std::make_unique<TempFileSystem>(GetProfile(), appURL);
  ASSERT_TRUE(temp_file_system->SetUp());
  storage::FileSystemURL root_url = temp_file_system->CreateFileSystemURL("");
  scoped_refptr<storage::FileSystemContext> file_system_context =
      GetFileSystemContextForSourceURL(GetProfile(), appURL);

  for (const std::string& file : existing_files) {
    if (file.back() == '/') {
      temp_file_system->CreateDirectory(
          temp_file_system->CreateFileSystemURL(file));
    } else {
      temp_file_system->CreateFile(temp_file_system->CreateFileSystemURL(file));
    }
  }

  base::RunLoop run_loop;
  GenerateUnusedFilename(
      root_url, base::FilePath(target_filename), file_system_context,
      base::BindLambdaForTesting(
          [&](base::FileErrorOr<storage::FileSystemURL> result) {
            if (!expected.has_value()) {
              EXPECT_FALSE(result.has_value())
                  << "Unexpected result " << result->ToGURL();
              EXPECT_EQ(expected.error(), result.error());
            } else {
              EXPECT_TRUE(result.has_value())
                  << "Unexpected error " << result.error();
              EXPECT_EQ(temp_file_system->CreateFileSystemURL(expected.value())
                            .ToGURL(),
                        result->ToGURL());
            }
            run_loop.Quit();
          }));
  run_loop.Run();
}

TEST_F(FileManagerFileAPIUtilTest, GenerateUnusedFilenameBasic) {
  TestGenerateUnusedFilename({}, "foo.bar", {"foo.bar"});
  TestGenerateUnusedFilename({"foo.bar"}, "foo.bar", {"foo (1).bar"});
  TestGenerateUnusedFilename({"foo.bar/"}, "foo.bar", {"foo (1).bar"});
  TestGenerateUnusedFilename({"foo (1).bar"}, "foo.bar", {"foo.bar"});
  TestGenerateUnusedFilename({"foo.bar", "foo (1).bar"}, "foo.bar",
                             {"foo (2).bar"});
  TestGenerateUnusedFilename({"foo.bar", "foo (1).bar/"}, "foo.bar",
                             {"foo (2).bar"});
  TestGenerateUnusedFilename({"foo.bar", "foo (2).bar"}, "foo.bar",
                             {"foo (1).bar"});
  TestGenerateUnusedFilename({"foo.bar/", "foo (1).bar"}, "foo (1).bar",
                             {"foo (2).bar"});
  TestGenerateUnusedFilename({"foo (3).bar"}, "foo (3).bar", {"foo (1).bar"});
  TestGenerateUnusedFilename({"foo (2).bar"}, "foo (1).bar", {"foo (1).bar"});
  TestGenerateUnusedFilename({"foo (2) (1).bar"}, "foo (2) (1).bar",
                             {"foo (2) (2).bar"});
  TestGenerateUnusedFilename({"foo (2) (2).bar"}, "foo (2) (2).bar",
                             {"foo (2) (1).bar"});
  TestGenerateUnusedFilename({}, " foo.bar", {" foo.bar"});
  TestGenerateUnusedFilename({" foo.bar"}, " foo.bar", {" foo (1).bar"});
  TestGenerateUnusedFilename({"foo.bar"}, " foo.bar", {" foo.bar"});
}

TEST_F(FileManagerFileAPIUtilTest, GenerateUnusedFilenameNewLine) {
  TestGenerateUnusedFilename({}, "new\nline.bar", {"new\nline.bar"});
  TestGenerateUnusedFilename({"new\nline.bar"}, "new\nline.bar",
                             {"new\nline (1).bar"});
  TestGenerateUnusedFilename({"new\nline.bar", "new\nline (1).bar"},
                             "new\nline.bar", {"new\nline (2).bar"});
  TestGenerateUnusedFilename({}, "new\nline (\n)", {"new\nline (\n)"});
  TestGenerateUnusedFilename({"new\nline (\n)"}, "new\nline (\n)",
                             {"new\nline (\n) (1)"});
}

TEST_F(FileManagerFileAPIUtilTest, GenerateUnusedFilenameUnicode) {
  TestGenerateUnusedFilename({}, "é è ê ô œ.txt€", {"é è ê ô œ.txt€"});
  TestGenerateUnusedFilename({"é è ê ô œ.txt€"}, "é è ê ô œ.txt€",
                             {"é è ê ô œ (1).txt€"});
}

TEST_F(FileManagerFileAPIUtilTest, GenerateUnusedFilenameNoExtension) {
  TestGenerateUnusedFilename({}, "no-ext", {"no-ext"});
  TestGenerateUnusedFilename({"no-ext"}, "no-ext", {"no-ext (1)"});
  TestGenerateUnusedFilename({"no-ext/"}, "no-ext", {"no-ext (1)"});
  TestGenerateUnusedFilename({"no-ext (1)"}, "no-ext (1)", {"no-ext (2)"});

  TestGenerateUnusedFilename({}, "a", {"a"});
  TestGenerateUnusedFilename({"a"}, "a", {"a (1)"});
  TestGenerateUnusedFilename({"a/"}, "a", {"a (1)"});
}

TEST_F(FileManagerFileAPIUtilTest, GenerateUnusedFilenameDoubleExtension) {
  TestGenerateUnusedFilename({}, "double.ext.10.13.txt",
                             {"double.ext.10.13.txt"});
  TestGenerateUnusedFilename({"double.ext.10.13.txt"}, "double.ext.10.13.txt",
                             {"double.ext.10.13 (1).txt"});
  TestGenerateUnusedFilename({"double.ext.10.13.txt/"}, "double.ext.10.13.txt",
                             {"double.ext.10.13 (1).txt"});

  TestGenerateUnusedFilename({}, "archive.tar.gz", {"archive.tar.gz"});
  TestGenerateUnusedFilename({"archive.tar.gz"}, "archive.tar.gz",
                             {"archive (1).tar.gz"});
}

TEST_F(FileManagerFileAPIUtilTest, GenerateUnusedFilenameInvalidFilename) {
  TestGenerateUnusedFilename(
      {}, "", base::unexpected(base::File::FILE_ERROR_INVALID_OPERATION));
  TestGenerateUnusedFilename(
      {}, "path/with/slashes",
      base::unexpected(base::File::FILE_ERROR_INVALID_OPERATION));
}

TEST_F(FileManagerFileAPIUtilTest, GenerateUnusedFilenameFileSystemProvider) {
  Profile* const profile = GetProfile();
  const std::string extension_id = "abc";

  // Create and mount the FileSystemProvider.
  auto fake_provider =
      ash::file_system_provider::FakeExtensionProvider::Create(extension_id);
  const auto kProviderId = fake_provider->GetId();
  auto* service = ash::file_system_provider::Service::Get(profile);
  service->RegisterProvider(std::move(fake_provider));
  const base::File::Error result = service->MountFileSystem(
      kProviderId, ash::file_system_provider::MountOptions(file_system_id_,
                                                           "Test FileSystem"));
  ASSERT_EQ(base::File::FILE_OK, result);

  auto* provided_file_system =
      static_cast<ash::file_system_provider::FakeProvidedFileSystem*>(
          service->GetProvidedFileSystem(kProviderId, file_system_id_));
  ASSERT_TRUE(provided_file_system);
  const base::FilePath mount_point_name =
      provided_file_system->GetFileSystemInfo().mount_path().BaseName();

  const std::string origin = "chrome-extension://abc/";
  storage::FileSystemContext* const context =
      GetFileSystemContextForSourceURL(profile, GURL(origin));
  ASSERT_TRUE(context);

  // Make sure we can access the filesystem from the above origin.
  ash::FileSystemBackend::Get(*context)->GrantFileAccessToOrigin(
      url::Origin::Create(GURL(origin)), base::FilePath(mount_point_name));

  const storage::ExternalMountPoints* const mount_points =
      storage::ExternalMountPoints::GetSystemInstance();
  auto destination_folder_url = mount_points->CreateCrackedFileSystemURL(
      blink::StorageKey::CreateFromStringForTesting(origin),
      storage::kFileSystemTypeExternal, mount_point_name);
  auto expected_url = mount_points->CreateCrackedFileSystemURL(
      blink::StorageKey::CreateFromStringForTesting(origin),
      storage::kFileSystemTypeExternal,
      mount_point_name.Append("hello (1).txt"));

  base::RunLoop run_loop;
  GenerateUnusedFilename(
      destination_folder_url,
      base::FilePath(ash::file_system_provider::kFakeFilePath).BaseName(),
      context,
      base::BindLambdaForTesting(
          [&](base::FileErrorOr<storage::FileSystemURL> result) {
            EXPECT_TRUE(result.has_value())
                << "Unexpected error " << result.error();
            EXPECT_EQ(expected_url.ToGURL(), result->ToGURL());
            run_loop.Quit();
          }));
  run_loop.Run();
}

}  // namespace
}  // namespace util
}  // namespace file_manager