chromium/chrome/browser/ash/file_system_provider/registry_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_system_provider/registry.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ash/file_system_provider/icon_set.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
#include "chrome/common/extensions/api/file_system_provider_capabilities/file_system_provider_capabilities_handler.h"
#include "chrome/common/pref_names.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 "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::file_system_provider {
namespace {

const char kTemporaryOrigin[] =
    "chrome-extension://abcabcabcabcabcabcabcabcabcabcabcabca/";
const char kPersistentOrigin[] =
    "chrome-extension://efgefgefgefgefgefgefgefgefgefgefgefge/";
const char kExtensionId[] = "mbflcebpggnecokmikipoihdbecnjfoj";
const char kDisplayName[] = "Camera Pictures";
const ProviderId kProviderId = ProviderId::CreateFromExtensionId(kExtensionId);

// The dot in the file system ID is there in order to check that saving to
// preferences works correctly. File System ID is used as a key in
// a base::Value, so it has to be stored without path expansion.
const char kFileSystemId[] = "camera/pictures/id .!@#$%^&*()_+";

const int kOpenedFilesLimit = 5;

// Stores a provided file system information in preferences together with a
// fake watcher.
void RememberFakeFileSystem(TestingProfile* profile,
                            const ProviderId& provider_id,
                            const std::string& file_system_id,
                            const std::string& display_name,
                            bool writable,
                            bool supports_notify_tag,
                            int opened_files_limit,
                            const Watcher& watcher) {
  // Warning. Updating this code means that backward compatibility may be
  // broken, what is unexpected and should be avoided.
  sync_preferences::TestingPrefServiceSyncable* const pref_service =
      profile->GetTestingPrefService();
  ASSERT_TRUE(pref_service);

  base::Value::Dict extensions;
  base::Value::Dict file_system;
  file_system.Set(kPrefKeyFileSystemId, kFileSystemId);
  file_system.Set(kPrefKeyDisplayName, kDisplayName);
  file_system.Set(kPrefKeyWritable, writable);
  file_system.Set(kPrefKeySupportsNotifyTag, supports_notify_tag);
  file_system.Set(kPrefKeyOpenedFilesLimit, opened_files_limit);

  // Remember watchers.
  base::Value::Dict watcher_value;
  watcher_value.Set(kPrefKeyWatcherEntryPath, watcher.entry_path.value());
  watcher_value.Set(kPrefKeyWatcherRecursive, watcher.recursive);
  watcher_value.Set(kPrefKeyWatcherLastTag, watcher.last_tag);
  base::Value::List persistent_origins_value;
  for (const auto& subscriber_it : watcher.subscribers) {
    if (subscriber_it.second.persistent)
      persistent_origins_value.Append(subscriber_it.first.spec());
  }

  watcher_value.Set(kPrefKeyWatcherPersistentOrigins,
                    std::move(persistent_origins_value));
  base::Value::Dict watchers;
  watchers.Set(watcher.entry_path.value(), std::move(watcher_value));
  file_system.Set(kPrefKeyWatchers, std::move(watchers));
  base::Value::Dict file_systems;
  file_systems.Set(kFileSystemId, std::move(file_system));
  extensions.Set(kProviderId.ToString(), std::move(file_systems));
  pref_service->SetDict(prefs::kFileSystemProviderMounted,
                        std::move(extensions));
}

}  // namespace

class FileSystemProviderRegistryTest : public testing::Test {
 protected:
  FileSystemProviderRegistryTest() : profile_(nullptr) {}

  ~FileSystemProviderRegistryTest() override = default;

  void SetUp() override {
    profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    ASSERT_TRUE(profile_manager_->SetUp());
    profile_ = profile_manager_->CreateTestingProfile("[email protected]");
    registry_ = std::make_unique<Registry>(profile_);
    fake_watcher_.entry_path = base::FilePath(FILE_PATH_LITERAL("/a/b/c"));
    fake_watcher_.recursive = true;
    fake_watcher_.subscribers[GURL(kTemporaryOrigin)].origin =
        GURL(kTemporaryOrigin);
    fake_watcher_.subscribers[GURL(kTemporaryOrigin)].persistent = false;
    fake_watcher_.subscribers[GURL(kPersistentOrigin)].origin =
        GURL(kPersistentOrigin);
    fake_watcher_.subscribers[GURL(kPersistentOrigin)].persistent = true;
    fake_watcher_.last_tag = "hello-world";
  }

  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestingProfileManager> profile_manager_;
  raw_ptr<TestingProfile> profile_;
  std::unique_ptr<RegistryInterface> registry_;
  Watcher fake_watcher_;
};

TEST_F(FileSystemProviderRegistryTest, RestoreFileSystems) {
  // Create a fake entry in the preferences.
  RememberFakeFileSystem(profile_, kProviderId, kFileSystemId, kDisplayName,
                         /*writable=*/true, /*supports_notify_tag=*/true,
                         kOpenedFilesLimit, fake_watcher_);

  std::unique_ptr<RegistryInterface::RestoredFileSystems>
      restored_file_systems = registry_->RestoreFileSystems(kProviderId);

  ASSERT_EQ(1u, restored_file_systems->size());
  const RegistryInterface::RestoredFileSystem& restored_file_system =
      restored_file_systems->at(0);
  EXPECT_EQ(kProviderId, restored_file_system.provider_id);
  EXPECT_EQ(kFileSystemId, restored_file_system.options.file_system_id);
  EXPECT_EQ(kDisplayName, restored_file_system.options.display_name);
  EXPECT_TRUE(restored_file_system.options.writable);
  EXPECT_TRUE(restored_file_system.options.supports_notify_tag);
  EXPECT_EQ(kOpenedFilesLimit, restored_file_system.options.opened_files_limit);

  ASSERT_EQ(1u, restored_file_system.watchers.size());
  const auto& restored_watcher_it = restored_file_system.watchers.find(
      WatcherKey(fake_watcher_.entry_path, fake_watcher_.recursive));
  ASSERT_NE(restored_file_system.watchers.end(), restored_watcher_it);

  EXPECT_EQ(fake_watcher_.entry_path, restored_watcher_it->second.entry_path);
  EXPECT_EQ(fake_watcher_.recursive, restored_watcher_it->second.recursive);
  EXPECT_EQ(fake_watcher_.last_tag, restored_watcher_it->second.last_tag);
}

TEST_F(FileSystemProviderRegistryTest, RememberFileSystem) {
  MountOptions options(kFileSystemId, kDisplayName);
  options.writable = true;
  options.supports_notify_tag = true;
  options.opened_files_limit = kOpenedFilesLimit;

  ProvidedFileSystemInfo file_system_info(
      kProviderId, options, base::FilePath(FILE_PATH_LITERAL("/a/b/c")),
      /*configurable=*/false, /*watchable=*/true, extensions::SOURCE_FILE,
      IconSet());

  Watchers watchers;
  watchers[WatcherKey(fake_watcher_.entry_path, fake_watcher_.recursive)] =
      fake_watcher_;

  registry_->RememberFileSystem(file_system_info, watchers);

  sync_preferences::TestingPrefServiceSyncable* const pref_service =
      profile_->GetTestingPrefService();
  ASSERT_TRUE(pref_service);

  const base::Value::Dict& extensions =
      pref_service->GetDict(prefs::kFileSystemProviderMounted);

  const base::Value::Dict* file_systems =
      extensions.FindDict(kProviderId.ToString());
  ASSERT_TRUE(file_systems);
  EXPECT_EQ(1u, file_systems->size());

  const base::Value::Dict* file_system = file_systems->FindDict(kFileSystemId);
  ASSERT_TRUE(file_system);

  const std::string* file_system_id =
      file_system->FindString(kPrefKeyFileSystemId);
  EXPECT_TRUE(file_system_id);
  EXPECT_EQ(kFileSystemId, *file_system_id);

  const std::string* display_name =
      file_system->FindString(kPrefKeyDisplayName);
  EXPECT_TRUE(display_name);
  EXPECT_EQ(kDisplayName, *display_name);

  std::optional<bool> writable = file_system->FindBool(kPrefKeyWritable);
  EXPECT_TRUE(writable.has_value());
  EXPECT_TRUE(writable.value());

  std::optional<bool> supports_notify_tag =
      file_system->FindBool(kPrefKeySupportsNotifyTag);
  EXPECT_TRUE(supports_notify_tag.has_value());
  EXPECT_TRUE(supports_notify_tag.value());

  std::optional<int> opened_files_limit =
      file_system->FindInt(kPrefKeyOpenedFilesLimit);
  EXPECT_TRUE(opened_files_limit.has_value());
  EXPECT_EQ(kOpenedFilesLimit, opened_files_limit.value());

  const base::Value::Dict* watchers_dict =
      file_system->FindDict(kPrefKeyWatchers);
  ASSERT_TRUE(watchers_dict);

  const base::Value::Dict* watcher =
      watchers_dict->FindDict(fake_watcher_.entry_path.value());
  ASSERT_TRUE(watcher);

  const std::string* entry_path = watcher->FindString(kPrefKeyWatcherEntryPath);
  EXPECT_TRUE(entry_path);
  EXPECT_EQ(fake_watcher_.entry_path.value(), *entry_path);

  std::optional<bool> recursive = watcher->FindBool(kPrefKeyWatcherRecursive);
  EXPECT_TRUE(recursive.has_value());
  EXPECT_EQ(fake_watcher_.recursive, recursive.value());

  const std::string* last_tag = watcher->FindString(kPrefKeyWatcherLastTag);
  EXPECT_TRUE(last_tag);
  EXPECT_EQ(fake_watcher_.last_tag, *last_tag);

  const base::Value::List* persistent_origins =
      watcher->FindList(kPrefKeyWatcherPersistentOrigins);
  ASSERT_TRUE(persistent_origins);
  ASSERT_GT(fake_watcher_.subscribers.size(), persistent_origins->size());
  ASSERT_EQ(1u, persistent_origins->size());
  const std::string* persistent_origin =
      persistent_origins->front().GetIfString();
  ASSERT_TRUE(persistent_origin);
  const auto& fake_subscriber_it =
      fake_watcher_.subscribers.find(GURL(*persistent_origin));
  ASSERT_NE(fake_watcher_.subscribers.end(), fake_subscriber_it);
  EXPECT_TRUE(fake_subscriber_it->second.persistent);
}

TEST_F(FileSystemProviderRegistryTest, ForgetFileSystem) {
  // Create a fake file systems in the preferences.
  RememberFakeFileSystem(profile_, kProviderId, kFileSystemId, kDisplayName,
                         /*writable=*/true, /*supports_notify_tag=*/true,
                         kOpenedFilesLimit, fake_watcher_);

  registry_->ForgetFileSystem(kProviderId, kFileSystemId);

  sync_preferences::TestingPrefServiceSyncable* const pref_service =
      profile_->GetTestingPrefService();
  ASSERT_TRUE(pref_service);

  const base::Value::Dict& extensions =
      pref_service->GetDict(prefs::kFileSystemProviderMounted);

  const base::Value::Dict* file_systems =
      extensions.FindDict(kProviderId.GetExtensionId());
  EXPECT_FALSE(file_systems);
}

TEST_F(FileSystemProviderRegistryTest, UpdateWatcherTag) {
  MountOptions options(kFileSystemId, kDisplayName);
  options.writable = true;
  options.supports_notify_tag = true;

  ProvidedFileSystemInfo file_system_info(
      kProviderId, options, base::FilePath(FILE_PATH_LITERAL("/a/b/c")),
      /*configurable=*/false, /*watchable=*/true, extensions::SOURCE_FILE,
      IconSet());

  Watchers watchers;
  watchers[WatcherKey(fake_watcher_.entry_path, fake_watcher_.recursive)] =
      fake_watcher_;

  registry_->RememberFileSystem(file_system_info, watchers);

  fake_watcher_.last_tag = "updated-tag";
  registry_->UpdateWatcherTag(file_system_info, fake_watcher_);

  sync_preferences::TestingPrefServiceSyncable* const pref_service =
      profile_->GetTestingPrefService();
  ASSERT_TRUE(pref_service);

  const base::Value::Dict& extensions =
      pref_service->GetDict(prefs::kFileSystemProviderMounted);

  const base::Value::Dict* file_systems =
      extensions.FindDict(kProviderId.ToString());
  ASSERT_TRUE(file_systems);
  EXPECT_EQ(1u, file_systems->size());

  const base::Value::Dict* file_system = file_systems->FindDict(kFileSystemId);
  ASSERT_TRUE(file_system);

  const base::Value::Dict* watchers_dict =
      file_system->FindDict(kPrefKeyWatchers);
  ASSERT_TRUE(watchers_dict);

  const base::Value::Dict* watcher =
      watchers_dict->FindDict(fake_watcher_.entry_path.value());
  ASSERT_TRUE(watcher);

  const std::string* last_tag = watcher->FindString(kPrefKeyWatcherLastTag);
  EXPECT_TRUE(last_tag);
  EXPECT_EQ(fake_watcher_.last_tag, *last_tag);
}

}  // namespace ash::file_system_provider