// Copyright 2022 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 "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/ash/file_suggest/file_suggest_keyed_service_factory.h"
#include "chrome/browser/ash/file_suggest/file_suggest_test_util.h"
#include "chrome/browser/ash/file_suggest/file_suggest_util.h"
#include "chrome/browser/ash/file_suggest/mock_file_suggest_keyed_service.h"
#include "chrome/browser/ash/file_suggest/mock_file_suggest_keyed_service_observer.h"
#include "chrome/browser/ui/ash/holding_space/scoped_test_mount_point.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/prefs/pref_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash::test {
// TODO(https://crbug.com/1370774): move `ScopedTestMountPoint` out of holding
// space to remove the dependency on holding space code.
using ash::holding_space::ScopedTestMountPoint;
class FileSuggestKeyedServiceTest : public testing::Test {
protected:
// testing::Test:
void SetUp() override {
testing_profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
EXPECT_TRUE(testing_profile_manager_->SetUp());
profile_ = testing_profile_manager_->CreateTestingProfile(
"primary_profile@test", GetTestingFactories());
WaitUntilFileSuggestServiceReady(
FileSuggestKeyedServiceFactory::GetInstance()->GetService(profile_));
}
virtual TestingProfile::TestingFactories GetTestingFactories() { return {}; }
PrefService* GetPrefService() { return profile_->GetTestingPrefService(); }
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<TestingProfileManager> testing_profile_manager_;
raw_ptr<TestingProfile> profile_ = nullptr;
};
TEST_F(FileSuggestKeyedServiceTest, GetSuggestData) {
base::HistogramTester tester;
if (features::IsForestFeatureEnabled()) {
drive::DriveIntegrationServiceFactory::GetInstance()
->GetForProfile(profile_)
->SetEnabled(true);
}
FileSuggestKeyedServiceFactory::GetInstance()
->GetService(profile_)
->GetSuggestFileData(
FileSuggestionType::kDriveFile,
base::BindOnce([](const std::optional<std::vector<FileSuggestData>>&
suggest_data) {
EXPECT_FALSE(suggest_data.has_value());
}));
tester.ExpectBucketCount(
"Ash.Search.DriveFileSuggestDataValidation.Status",
/*sample=*/DriveSuggestValidationStatus::kDriveFSNotMounted,
/*expected_count=*/
(features::IsLauncherContinueSectionWithRecentsEnabled() ||
features::IsForestFeatureEnabled())
? 0
: 1);
}
TEST_F(FileSuggestKeyedServiceTest, DisabledByPolicy) {
base::HistogramTester tester;
if (features::IsForestFeatureEnabled()) {
drive::DriveIntegrationServiceFactory::GetInstance()
->GetForProfile(profile_)
->SetEnabled(true);
}
FileSuggestKeyedServiceFactory::GetInstance()
->GetService(profile_)
->GetSuggestFileData(
FileSuggestionType::kDriveFile,
base::BindOnce([](const std::optional<std::vector<FileSuggestData>>&
suggest_data) {
EXPECT_FALSE(suggest_data.has_value());
}));
// Disable file suggestion integration by policy.
GetPrefService()->SetList(prefs::kContextualGoogleIntegrationsConfiguration,
{});
FileSuggestKeyedServiceFactory::GetInstance()
->GetService(profile_)
->GetSuggestFileData(
FileSuggestionType::kDriveFile,
base::BindOnce([](const std::optional<std::vector<FileSuggestData>>&
suggest_data) {
// Disabling by policy should ensure a list with zero items is
// returned.
EXPECT_TRUE(suggest_data.has_value());
EXPECT_EQ(suggest_data->size(), 0u);
}));
}
class FileSuggestKeyedServiceRemoveTest : public FileSuggestKeyedServiceTest {
protected:
// FileSuggestKeyedServiceTest:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
FileSuggestKeyedServiceTest::SetUp();
file_suggest_service_ = static_cast<MockFileSuggestKeyedService*>(
FileSuggestKeyedServiceFactory::GetInstance()->GetService(profile_));
// Initialize the local file mount point.
local_fs_mount_point_ = std::make_unique<ScopedTestMountPoint>(
/*name=*/"test_mount", storage::kFileSystemTypeLocal,
file_manager::VOLUME_TYPE_TESTING);
local_fs_mount_point_->Mount(profile_);
// Initialize the drive file mount point.
drive_fs_mount_point_ = std::make_unique<ScopedTestMountPoint>(
/*name=*/"drivefs-delayed_mount", storage::kFileSystemTypeDriveFs,
file_manager::VOLUME_TYPE_GOOGLE_DRIVE);
drive_fs_mount_point_->Mount(profile_);
}
void TearDown() override {
drive_fs_mount_point_.reset();
local_fs_mount_point_.reset();
FileSuggestKeyedServiceTest::TearDown();
}
TestingProfile::TestingFactories GetTestingFactories() override {
return {TestingProfile::TestingFactory{
FileSuggestKeyedServiceFactory::GetInstance(),
base::BindRepeating(
&MockFileSuggestKeyedService::BuildMockFileSuggestKeyedService,
temp_dir_.GetPath().Append("proto"))}};
}
std::optional<std::vector<FileSuggestData>> GetSuggestionsForType(
FileSuggestionType type) {
std::optional<std::vector<FileSuggestData>> suggestions;
base::RunLoop run_loop;
file_suggest_service_->GetSuggestFileData(
type, base::BindOnce(
[](base::RunLoop* run_loop,
std::optional<std::vector<FileSuggestData>>* suggestions,
const std::optional<std::vector<FileSuggestData>>&
fetched_suggestions) {
*suggestions = fetched_suggestions;
run_loop->Quit();
},
&run_loop, &suggestions));
run_loop.Run();
return suggestions;
}
// Creates files and suggests these files through the file suggest keyed
// service. Returns paths to these files.
std::vector<base::FilePath> PrepareFileSuggestionsForType(
FileSuggestionType type,
size_t count) {
ScopedTestMountPoint* mount_point = nullptr;
switch (type) {
case FileSuggestionType::kDriveFile:
mount_point = drive_fs_mount_point_.get();
break;
case FileSuggestionType::kLocalFile:
mount_point = local_fs_mount_point_.get();
break;
}
std::vector<base::FilePath> suggested_file_paths;
std::vector<FileSuggestData> suggestions;
for (size_t index = 0; index < count; ++index) {
suggested_file_paths.push_back(mount_point->CreateArbitraryFile());
suggestions.emplace_back(type, suggested_file_paths.back(),
/*title=*/std::nullopt,
/*new_prediction_reason=*/std::nullopt,
/*modified_time=*/std::nullopt,
/*viewed_time=*/std::nullopt,
/*shared_time=*/std::nullopt,
/*new_score=*/std::nullopt,
/*drive_file_id=*/std::nullopt,
/*icon_url=*/std::nullopt);
}
file_suggest_service_->SetSuggestionsForType(type, suggestions);
return suggested_file_paths;
}
// Hosts the proto file.
base::ScopedTempDir temp_dir_;
// This test verifies the suggestion removal only. Therefore, a mock file
// suggest keyed service is sufficient.
raw_ptr<MockFileSuggestKeyedService> file_suggest_service_ = nullptr;
// The mount point for local files.
std::unique_ptr<ScopedTestMountPoint> local_fs_mount_point_;
// The mount point for drive files.
std::unique_ptr<ScopedTestMountPoint> drive_fs_mount_point_;
};
// Verifies removing drive file suggestions.
TEST_F(FileSuggestKeyedServiceRemoveTest, RemoveDriveFileSuggestions) {
const std::vector<base::FilePath> drive_file_suggestions =
PrepareFileSuggestionsForType(FileSuggestionType::kDriveFile,
/*count=*/2);
const base::FilePath& file_path_1 = drive_file_suggestions[0];
const base::FilePath& file_path_2 = drive_file_suggestions[1];
std::optional<std::vector<FileSuggestData>> suggestions =
GetSuggestionsForType(FileSuggestionType::kDriveFile);
EXPECT_EQ(suggestions->size(), 2u);
EXPECT_EQ(suggestions->at(0).file_path.value(), file_path_1.value());
EXPECT_EQ(suggestions->at(0).id, "zero_state_drive://" + file_path_1.value());
EXPECT_EQ(suggestions->at(1).file_path.value(), file_path_2.value());
EXPECT_EQ(suggestions->at(1).id, "zero_state_drive://" + file_path_2.value());
MockFileSuggestKeyedServiceObserver observer_mocker;
base::ScopedObservation<FileSuggestKeyedService,
FileSuggestKeyedService::Observer>
scoped_observation(&observer_mocker);
scoped_observation.Observe(file_suggest_service_.get());
// The observer should be notified of the drive file suggestion update.
EXPECT_CALL(observer_mocker,
OnFileSuggestionUpdated(FileSuggestionType::kDriveFile));
file_suggest_service_->RemoveSuggestionsAndNotify(
/*absolute_file_paths=*/{{file_path_1}});
// Check the suggested files after suggestion removal.
suggestions = GetSuggestionsForType(FileSuggestionType::kDriveFile);
EXPECT_EQ(suggestions->size(), 1u);
EXPECT_EQ(suggestions->at(0).file_path.value(), file_path_2.value());
EXPECT_EQ(suggestions->at(0).id, "zero_state_drive://" + file_path_2.value());
}
// Verifies removing local file suggestions.
TEST_F(FileSuggestKeyedServiceRemoveTest, RemoveLocalFileSuggestions) {
const std::vector<base::FilePath> local_file_suggestions =
PrepareFileSuggestionsForType(FileSuggestionType::kLocalFile,
/*count=*/2);
const base::FilePath& file_path_1 = local_file_suggestions[0];
const base::FilePath& file_path_2 = local_file_suggestions[1];
std::optional<std::vector<FileSuggestData>> suggestions =
GetSuggestionsForType(FileSuggestionType::kLocalFile);
EXPECT_EQ(suggestions->size(), 2u);
EXPECT_EQ(suggestions->at(0).file_path.value(), file_path_1.value());
EXPECT_EQ(suggestions->at(0).id, "zero_state_file://" + file_path_1.value());
EXPECT_EQ(suggestions->at(1).file_path.value(), file_path_2.value());
EXPECT_EQ(suggestions->at(1).id, "zero_state_file://" + file_path_2.value());
MockFileSuggestKeyedServiceObserver observer_mocker;
base::ScopedObservation<FileSuggestKeyedService,
FileSuggestKeyedService::Observer>
scoped_observation(&observer_mocker);
scoped_observation.Observe(file_suggest_service_.get());
// The observer should be notified of the local file suggestion update.
EXPECT_CALL(observer_mocker,
OnFileSuggestionUpdated(FileSuggestionType::kLocalFile));
file_suggest_service_->RemoveSuggestionsAndNotify(
/*absolute_file_paths=*/{file_path_2});
// Check the suggested files after suggestion removal.
suggestions = GetSuggestionsForType(FileSuggestionType::kLocalFile);
EXPECT_EQ(suggestions->size(), 1u);
EXPECT_EQ(suggestions->at(0).file_path.value(), file_path_1.value());
EXPECT_EQ(suggestions->at(0).id, "zero_state_file://" + file_path_1.value());
}
// Verifies removing drive and local file suggestions at the same time.
TEST_F(FileSuggestKeyedServiceRemoveTest, RemoveMixedFileSuggestions) {
const std::vector<base::FilePath> drive_file_suggestions =
PrepareFileSuggestionsForType(FileSuggestionType::kDriveFile,
/*count=*/1);
const std::vector<base::FilePath> local_file_suggestions =
PrepareFileSuggestionsForType(FileSuggestionType::kLocalFile,
/*count=*/1);
MockFileSuggestKeyedServiceObserver observer_mocker;
base::ScopedObservation<FileSuggestKeyedService,
FileSuggestKeyedService::Observer>
scoped_observation(&observer_mocker);
scoped_observation.Observe(file_suggest_service_.get());
// The observer should be notified of the updates in drive and local file
// suggestions.
EXPECT_CALL(observer_mocker,
OnFileSuggestionUpdated(FileSuggestionType::kDriveFile));
EXPECT_CALL(observer_mocker,
OnFileSuggestionUpdated(FileSuggestionType::kLocalFile));
file_suggest_service_->RemoveSuggestionsAndNotify(
/*absolute_file_paths=*/{drive_file_suggestions[0],
local_file_suggestions[0]});
// Check the suggested files after suggestion removal.
EXPECT_TRUE(GetSuggestionsForType(FileSuggestionType::kDriveFile)->empty());
EXPECT_TRUE(GetSuggestionsForType(FileSuggestionType::kLocalFile)->empty());
}
} // namespace ash::test