blob: 1842bccef8989a155fbaea0b070e2a63785ca00c [file] [log] [blame]
// Copyright 2024 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/ui/ash/picker/picker_client_impl.h"
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "ash/picker/picker_controller.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/drivefs_test_support.h"
#include "chrome/browser/ash/fileapi/recent_model.h"
#include "chrome/browser/ash/fileapi/recent_model_factory.h"
#include "chrome/browser/ash/fileapi/test/fake_recent_source.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/test/base/browser_with_test_window_test.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 "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/ash/components/disks/fake_disk_mount_manager.h"
#include "chromeos/ash/components/drivefs/fake_drivefs.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/test_history_database.h"
#include "components/user_manager/fake_user_manager.h"
#include "content/public/test/test_utils.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyNumber;
using ::testing::Contains;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::IsSupersetOf;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Property;
using ::testing::UnorderedElementsAre;
using ::testing::VariantWith;
using MockSearchResultsCallback =
testing::MockFunction<PickerClientImpl::CrosSearchResultsCallback>;
bool CreateTestFile(const base::FilePath& path) {
base::ScopedAllowBlockingForTesting allow_blocking;
if (!base::WriteFile(path, "test_file")) {
return false;
}
return true;
}
std::unique_ptr<KeyedService> BuildTestHistoryService(
base::FilePath profile_path,
content::BrowserContext* context) {
auto service = std::make_unique<history::HistoryService>();
service->Init(history::TestHistoryDatabaseParamsForPath(profile_path));
return std::move(service);
}
std::unique_ptr<KeyedService> BuildTestRecentModelFactory(
std::vector<ash::RecentFile> files,
content::BrowserContext* context) {
const size_t max_files = files.size();
auto source = std::make_unique<ash::FakeRecentSource>();
source->AddProducer(std::make_unique<ash::FileProducer>(
/*lag=*/base::Milliseconds(0), std::move(files)));
std::vector<std::unique_ptr<ash::RecentSource>> sources;
sources.push_back(std::move(source));
return ash::RecentModel::CreateForTest(std::move(sources), max_files);
}
std::unique_ptr<KeyedService> BuildTestDriveIntegrationService(
const base::FilePath& profile_path,
std::unique_ptr<drive::FakeDriveFsHelper>& fake_drivefs_helper,
content::BrowserContext* context) {
Profile* profile = Profile::FromBrowserContext(context);
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath mount_path = profile_path.Append("drivefs");
static_cast<ash::disks::FakeDiskMountManager*>(
ash::disks::DiskMountManager::GetInstance())
->RegisterMountPointForNetworkStorageScheme("drivefs",
mount_path.value());
fake_drivefs_helper =
std::make_unique<drive::FakeDriveFsHelper>(profile, mount_path);
auto service = std::make_unique<drive::DriveIntegrationService>(
profile, "drivefs", mount_path,
fake_drivefs_helper->CreateFakeDriveFsListenerFactory());
// Wait until the DriveIntegrationService is initialized.
while (!service->IsMounted() || !service->GetDriveFsInterface()) {
base::RunLoop().RunUntilIdle();
}
return service;
}
void AddSearchToHistory(TestingProfile* profile, GURL url) {
history::HistoryService* history = HistoryServiceFactory::GetForProfile(
profile, ServiceAccessType::EXPLICIT_ACCESS);
history->AddPageWithDetails(url, /*title=*/u"", /*visit_count=*/1,
/*typed_count=*/1,
/*last_visit=*/base::Time::Now(),
/*hidden=*/false, history::SOURCE_BROWSED);
profile->BlockUntilHistoryProcessesPendingRequests();
}
void AddBookmarks(TestingProfile* profile,
std::u16string_view title,
GURL url) {
auto* bookmark_model = BookmarkModelFactory::GetForBrowserContext(profile);
bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model);
bookmark_model->AddURL(bookmark_model->bookmark_bar_node(), 0,
std::u16string(title), url);
}
ash::RecentFile CreateRecentFile(const base::FilePath& path,
storage::FileSystemType type,
base::Time last_modified = base::Time::Now()) {
storage::FileSystemURL url =
storage::FileSystemURL::CreateForTest(blink::StorageKey(), type, path);
return ash::RecentFile(url, last_modified);
}
void SetRecentFiles(TestingProfile* profile,
std::vector<ash::RecentFile> files) {
ash::RecentModelFactory::GetInstance()->SetTestingFactoryAndUse(
profile,
base::BindRepeating(BuildTestRecentModelFactory, std::move(files)));
}
drivefs::FakeMetadata CreateFakeDriveFsMetadata(const base::FilePath& path) {
drivefs::FakeMetadata metadata;
metadata.path = path;
return metadata;
}
class PickerClientImplTest : public BrowserWithTestWindowTest {
public:
PickerClientImplTest() = default;
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ash::CrosDisksClient::InitializeFake();
ash::disks::DiskMountManager::InitializeForTesting(
new ash::disks::FakeDiskMountManager());
BrowserWithTestWindowTest::SetUp();
}
void TearDown() override {
BrowserWithTestWindowTest::TearDown();
ash::disks::DiskMountManager::Shutdown();
ash::CrosDisksClient::Shutdown();
}
scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory() {
return test_shared_url_loader_factory_;
}
drivefs::FakeDriveFs& GetFakeDriveFs() {
return fake_drivefs_helper_->fake_drivefs();
}
TestingProfile* CreateProfile(const std::string& profile_name) override {
auto* profile = profile_manager()->CreateTestingProfile(
profile_name, GetTestingFactories(), /*is_main_profile=*/false,
test_shared_url_loader_factory_);
OnUserProfileCreated(profile_name, profile);
return profile;
}
TestingProfile::TestingFactories GetTestingFactories() override {
return {
{HistoryServiceFactory::GetInstance(),
base::BindRepeating(&BuildTestHistoryService, temp_dir_.GetPath())},
{BookmarkModelFactory::GetInstance(),
BookmarkModelFactory::GetDefaultFactory()},
{TemplateURLServiceFactory::GetInstance(),
base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor)},
{ash::RecentModelFactory::GetInstance(),
base::BindRepeating(&BuildTestRecentModelFactory,
std::vector<ash::RecentFile>{})},
{drive::DriveIntegrationServiceFactory::GetInstance(),
base::BindRepeating(&BuildTestDriveIntegrationService,
temp_dir_.GetPath(),
std::ref(fake_drivefs_helper_))}};
}
void LogIn(const std::string& email) override {
// DriveFS needs the account to have an ID.
const AccountId account_id =
AccountId::FromUserEmailGaiaId(email, "test gaia");
user_manager()->AddUser(account_id);
ash_test_helper()->test_session_controller_client()->AddUserSession(email);
user_manager()->UserLoggedIn(
account_id,
user_manager::FakeUserManager::GetFakeUsernameHash(account_id),
/*browser_restart=*/false,
/*is_child=*/false);
}
private:
base::ScopedTempDir temp_dir_;
scoped_refptr<network::SharedURLLoaderFactory>
test_shared_url_loader_factory_;
std::unique_ptr<drive::FakeDriveFsHelper> fake_drivefs_helper_;
};
TEST_F(PickerClientImplTest, GetsSharedURLLoaderFactory) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
EXPECT_EQ(client.GetSharedURLLoaderFactory(), GetSharedURLLoaderFactory());
}
TEST_F(PickerClientImplTest, StartCrosSearch) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
AddSearchToHistory(profile(), GURL("http://foo.com/history"));
AddBookmarks(profile(), u"Foobaz", GURL("http://foo.com/bookmarks"));
AddTab(browser(), GURL("http://foo.com/tab"));
base::test::TestFuture<void> test_done;
NiceMock<MockSearchResultsCallback> mock_search_callback;
EXPECT_CALL(mock_search_callback, Call(_, _)).Times(AnyNumber());
EXPECT_CALL(
mock_search_callback,
Call(ash::AppListSearchResultType::kOmnibox,
IsSupersetOf({
Property(
"data", &ash::PickerSearchResult::data,
VariantWith<ash::PickerSearchResult::BrowsingHistoryData>(
Field("url",
&ash::PickerSearchResult::BrowsingHistoryData::url,
GURL("http://foo.com/history")))),
Property(
"data", &ash::PickerSearchResult::data,
VariantWith<ash::PickerSearchResult::BrowsingHistoryData>(
Field("url",
&ash::PickerSearchResult::BrowsingHistoryData::url,
GURL("http://foo.com/tab")))),
Property(
"data", &ash::PickerSearchResult::data,
VariantWith<
ash::PickerSearchResult::BrowsingHistoryData>(AllOf(
Field(
"title",
&ash::PickerSearchResult::BrowsingHistoryData::title,
u"Foobaz"),
Field("url",
&ash::PickerSearchResult::BrowsingHistoryData::url,
GURL("http://foo.com/bookmarks"))))),
})))
.WillOnce([&]() { test_done.SetValue(); });
client.StartCrosSearch(
u"foo", /*category=*/std::nullopt,
base::BindRepeating(&MockSearchResultsCallback::Call,
base::Unretained(&mock_search_callback)));
ASSERT_TRUE(test_done.Wait());
}
TEST_F(PickerClientImplTest, GetRecentLocalFilesWithNoFiles) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
client.GetRecentLocalFileResults(future.GetCallback());
EXPECT_THAT(future.Get(), IsEmpty());
}
TEST_F(PickerClientImplTest, GetRecentLocalFilesReturnsFilteredFiles) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
SetRecentFiles(profile(),
{
CreateRecentFile(base::FilePath("aaa.jpg"),
storage::kFileSystemTypeLocal),
CreateRecentFile(base::FilePath("bbb.mp4"),
storage::kFileSystemTypeLocal),
CreateRecentFile(base::FilePath("ccc.png"),
storage::kFileSystemTypeLocal),
CreateRecentFile(base::FilePath("ddd.png"),
storage::kFileSystemTypeDriveFs),
});
client.GetRecentLocalFileResults(future.GetCallback());
EXPECT_THAT(
future.Get(),
UnorderedElementsAre(
Property(
"data", &ash::PickerSearchResult::data,
VariantWith<ash::PickerSearchResult::LocalFileData>(AllOf(
Field("title", &ash::PickerSearchResult::LocalFileData::title,
u"aaa.jpg"),
Field("file_path",
&ash::PickerSearchResult::LocalFileData::file_path,
base::FilePath("aaa.jpg"))))),
Property(
"data", &ash::PickerSearchResult::data,
VariantWith<ash::PickerSearchResult::LocalFileData>(AllOf(
Field("title", &ash::PickerSearchResult::LocalFileData::title,
u"ccc.png"),
Field("file_path",
&ash::PickerSearchResult::LocalFileData::file_path,
base::FilePath("ccc.png")))))));
}
TEST_F(PickerClientImplTest, GetRecentLocalFilesDoesNotReturnOldFiles) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
SetRecentFiles(profile(),
{
CreateRecentFile(base::FilePath("abc.jpg"),
storage::kFileSystemTypeLocal,
base::Time::Now() - base::Days(31)),
});
client.GetRecentDriveFileResults(future.GetCallback());
EXPECT_THAT(future.Get(), IsEmpty());
}
TEST_F(PickerClientImplTest, GetRecentDriveFilesWithNoFiles) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
client.GetRecentLocalFileResults(future.GetCallback());
EXPECT_THAT(future.Get(), IsEmpty());
}
TEST_F(PickerClientImplTest, GetRecentDriveFilesReturnsDriveFiles) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
const base::FilePath mount_path = GetFakeDriveFs().mount_path();
ASSERT_TRUE(CreateTestFile(mount_path.AppendASCII("aaa.jpg")));
ASSERT_TRUE(CreateTestFile(mount_path.AppendASCII("bbb.mp4")));
ASSERT_TRUE(CreateTestFile(mount_path.AppendASCII("ccc.png")));
GetFakeDriveFs().SetMetadata(
CreateFakeDriveFsMetadata(base::FilePath("aaa.jpg")));
GetFakeDriveFs().SetMetadata(
CreateFakeDriveFsMetadata(base::FilePath("bbb.mp4")));
GetFakeDriveFs().SetMetadata(
CreateFakeDriveFsMetadata(base::FilePath("ccc.png")));
SetRecentFiles(profile(),
{
CreateRecentFile(mount_path.AppendASCII("aaa.jpg"),
storage::kFileSystemTypeDriveFs),
CreateRecentFile(mount_path.AppendASCII("bbb.mp4"),
storage::kFileSystemTypeDriveFs),
CreateRecentFile(mount_path.AppendASCII("ccc.png"),
storage::kFileSystemTypeLocal),
});
client.GetRecentDriveFileResults(future.GetCallback());
EXPECT_THAT(
future.Get(),
UnorderedElementsAre(
Property(
"data", &ash::PickerSearchResult::data,
VariantWith<ash::PickerSearchResult::DriveFileData>(AllOf(
Field("title", &ash::PickerSearchResult::DriveFileData::title,
u"aaa.jpg"),
Field("url", &ash::PickerSearchResult::DriveFileData::url,
GURL("https://file_alternate_link/aaa.jpg"))))),
Property(
"data", &ash::PickerSearchResult::data,
VariantWith<ash::PickerSearchResult::DriveFileData>(AllOf(
Field("title", &ash::PickerSearchResult::DriveFileData::title,
u"bbb.mp4"),
Field("url", &ash::PickerSearchResult::DriveFileData::url,
GURL("https://file_alternate_link/bbb.mp4")))))));
}
TEST_F(PickerClientImplTest,
GetRecentDriveFilesDoesNotReturnFilesWithNoMetadata) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
SetRecentFiles(profile(),
{
CreateRecentFile(base::FilePath("abc.jpg"),
storage::kFileSystemTypeDriveFs),
});
client.GetRecentLocalFileResults(future.GetCallback());
EXPECT_THAT(future.Get(), IsEmpty());
}
TEST_F(PickerClientImplTest, GetRecentDriveFilesDoesNotReturnOldFiles) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
SetRecentFiles(profile(),
{
CreateRecentFile(base::FilePath("abc.jpg"),
storage::kFileSystemTypeDriveFs,
base::Time::Now() - base::Days(31)),
});
client.GetRecentLocalFileResults(future.GetCallback());
EXPECT_THAT(future.Get(), IsEmpty());
}
TEST_F(PickerClientImplTest, GetSuggestedLinkResultsReturnsLinks) {
ash::PickerController controller;
PickerClientImpl client(&controller, user_manager());
AddSearchToHistory(profile(), GURL("http://foo.com/history"));
base::test::TestFuture<std::vector<ash::PickerSearchResult>> future;
client.GetSuggestedLinkResults(future.GetRepeatingCallback());
EXPECT_THAT(
future.Get(),
IsSupersetOf({Property(
"data", &ash::PickerSearchResult::data,
VariantWith<ash::PickerSearchResult::BrowsingHistoryData>(
Field("url", &ash::PickerSearchResult::BrowsingHistoryData::url,
GURL("http://foo.com/history"))))}));
}
// TODO: b/325540366 - Add PickerClientImpl tests.
} // namespace