// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/ambient/managed/screensaver_images_policy_handler.h"
#include <memory>
#include <optional>
#include <vector>
#include "ash/ambient/managed/screensaver_image_downloader.h"
#include "ash/ambient/test/ambient_ash_test_helper.h"
#include "ash/ambient/test/test_ambient_client.h"
#include "ash/constants/ash_paths.h"
#include "ash/public/cpp/ambient/ambient_prefs.h"
#include "ash/public/cpp/ash_prefs.h"
#include "ash/session/session_controller_impl.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_helper.h"
#include "base/base_paths.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/hash/sha1.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_path_override.h"
#include "base/test/test_future.h"
#include "components/prefs/testing_pref_service.h"
#include "components/user_manager/user_type.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace {
constexpr char kUserEmail[] = "[email protected]";
constexpr char kFakeFilePath1[] = "/path/to/file1";
constexpr char kFakeFilePath2[] = "/path/to/file2";
constexpr char kCacheDirectoryName[] = "managed_screensaver";
constexpr char kSigninDirectoryName[] = "signin";
constexpr char kManagedGuestDirectoryName[] = "guest";
constexpr char kCacheFileExt[] = ".cache";
constexpr char kImageUrl1[] = "https://example.com/1.jpg";
constexpr char kImageUrl1Alt[] = "https://EXAMPLE.com/1.jpg";
constexpr char kImageUrl2[] = "https://example.com/2.jpg";
constexpr char kFileContents1[] = "file contents 1";
constexpr char kFileContents2[] = "file contents 2";
constexpr size_t kMaxUrlsToProcessFromPolicy = 25u;
} // namespace
struct ScreensaverImagesPolicyHandlerTestCase {
std::string test_name;
ScreensaverImagesPolicyHandler::HandlerType handle_type;
std::string base_directory;
};
class ScreensaverImagesPolicyHandlerTest : public AshTestBase {
public:
ScreensaverImagesPolicyHandlerTest() = default;
ScreensaverImagesPolicyHandlerTest(
const ScreensaverImagesPolicyHandlerTest&) = delete;
ScreensaverImagesPolicyHandlerTest& operator=(
const ScreensaverImagesPolicyHandlerTest&) = delete;
~ScreensaverImagesPolicyHandlerTest() override = default;
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
home_dir_override_ = std::make_unique<base::ScopedPathOverride>(
base::DIR_HOME, temp_dir_.GetPath());
managed_screensaver_dir_override_ =
std::make_unique<base::ScopedPathOverride>(
ash::DIR_DEVICE_POLICY_SCREENSAVER_DATA, temp_dir_.GetPath());
}
void TearDown() override {
policy_handler_.reset();
// Remove the custom overrides to test directory behavior before the tear
// down so that original override can be restored.
home_dir_override_.reset();
managed_screensaver_dir_override_.reset();
AshTestBase::TearDown();
}
void TriggerOnDownloadedImageListUpdated(
const std::vector<base::FilePath>& image_list) {
ASSERT_TRUE(policy_handler());
policy_handler_->OnDownloadedImageListUpdated(image_list);
}
ScreensaverImageDownloader* GetPrivateImageDownloader(
const ScreensaverImagesPolicyHandler& policy_handler) {
return policy_handler.image_downloader_.get();
}
void VerifyPrivateImageDownloaderDownloadFolder(
ScreensaverImagesPolicyHandler& policy_handler,
const base::FilePath& expected_path) {
ASSERT_TRUE(policy_handler.image_downloader_.get());
EXPECT_EQ(expected_path,
policy_handler.image_downloader_->GetDowloadDirForTesting());
}
void RegisterUserWithUserPrefs(const AccountId& account_id,
user_manager::UserType user_type) {
// Create a fake user prefs map.
auto user_prefs = std::make_unique<TestingPrefServiceSimple>();
RegisterUserProfilePrefs(user_prefs->registry(), /*country=*/"",
/*for_test=*/true);
// Keep a raw pointer to the user prefs before transferring ownership.
active_prefs_ = user_prefs.get();
GetSessionControllerClient()->Reset();
GetSessionControllerClient()->AddUserSession(
kUserEmail, user_type,
/*provide_pref_service=*/false);
GetSessionControllerClient()->SetUserPrefService(account_id,
std::move(user_prefs));
GetSessionControllerClient()->SwitchActiveUser(account_id);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
}
TestingPrefServiceSimple* active_prefs() {
CHECK(active_prefs_);
return active_prefs_;
}
network::TestURLLoaderFactory& url_loader_factory() {
return GetAmbientAshTestHelper()
->ambient_client()
.test_url_loader_factory();
}
ScreensaverImagesPolicyHandler* policy_handler() {
return policy_handler_.get();
}
protected:
// Temp directory to simulate the download directory.
base::ScopedTempDir temp_dir_;
// Used to override the base download path to |temp_dir_|
std::unique_ptr<base::ScopedPathOverride> home_dir_override_;
std::unique_ptr<base::ScopedPathOverride> managed_screensaver_dir_override_;
// Ownership of this pref service is transferred to the session controller
raw_ptr<TestingPrefServiceSimple, DanglingUntriaged> active_prefs_ = nullptr;
// Class under test
std::unique_ptr<ScreensaverImagesPolicyHandler> policy_handler_;
};
TEST_F(ScreensaverImagesPolicyHandlerTest, FactoryFunctionTestSignin) {
// Signin
auto handler = ScreensaverImagesPolicyHandler::Create(
Shell::Get()->session_controller()->GetSigninScreenPrefService());
// Verify that the policy handler detected the new user and created a new
// image downloader instance.
EXPECT_TRUE(GetPrivateImageDownloader(*handler));
VerifyPrivateImageDownloaderDownloadFolder(
*handler, temp_dir_.GetPath().AppendASCII(kSigninDirectoryName));
}
TEST_F(ScreensaverImagesPolicyHandlerTest, FactoryFunctionTestUser) {
// User
RegisterUserWithUserPrefs(AccountId::FromUserEmail(kUserEmail),
user_manager::UserType::kRegular);
auto handler = ScreensaverImagesPolicyHandler::Create(active_prefs());
// Verify that the policy handler detected the new user and created a new
// image downloader instance.
EXPECT_TRUE(GetPrivateImageDownloader(*handler));
VerifyPrivateImageDownloaderDownloadFolder(
*handler, temp_dir_.GetPath().AppendASCII(kCacheDirectoryName));
}
TEST_F(ScreensaverImagesPolicyHandlerTest, FactoryFunctionTestManagedGuest) {
// Managed Guest
GetSessionControllerClient()->RequestSignOut();
RegisterUserWithUserPrefs(AccountId::FromUserEmail(kUserEmail),
user_manager::UserType::kPublicAccount);
auto handler = ScreensaverImagesPolicyHandler::Create(
Shell::Get()->session_controller()->GetActivePrefService());
// Verify that the policy handler detected the new user and created a new
// image downloader instance.
EXPECT_TRUE(GetPrivateImageDownloader(*handler));
VerifyPrivateImageDownloaderDownloadFolder(
*handler, temp_dir_.GetPath().AppendASCII(kManagedGuestDirectoryName));
}
class ScreensaverImagesPolicyHandlerForAnySessionTest
: public ScreensaverImagesPolicyHandlerTest,
public ::testing::WithParamInterface<
ScreensaverImagesPolicyHandlerTestCase> {
public:
ScreensaverImagesPolicyHandlerForAnySessionTest() {
TestSessionControllerClient::DisableAutomaticallyProvideSigninPref();
}
// ScreensaverImagesPolicyHandlerTest:
void SetUp() override {
ScreensaverImagesPolicyHandlerTest::SetUp();
RegisterProfilePrefs();
CreateScreensaverImagesPolicyHandler();
}
void RegisterProfilePrefs() {
ScreensaverImagesPolicyHandlerTestCase test_case = GetParam();
switch (test_case.handle_type) {
case ScreensaverImagesPolicyHandler::HandlerType::kUser:
RegisterUserWithUserPrefs(AccountId::FromUserEmail(kUserEmail),
user_manager::UserType::kRegular);
break;
case ScreensaverImagesPolicyHandler::HandlerType::kSignin: {
auto pref_service = std::make_unique<TestingPrefServiceSimple>();
active_prefs_ = pref_service.get();
RegisterSigninProfilePrefs(pref_service->registry(), /*country=*/"",
/*for_test=*/true);
TestSessionControllerClient* client = GetSessionControllerClient();
client->SetSigninScreenPrefService(std::move(pref_service));
} break;
case ScreensaverImagesPolicyHandler::HandlerType::kManagedGuest:
RegisterUserWithUserPrefs(AccountId::FromUserEmail(kUserEmail),
user_manager::UserType::kPublicAccount);
break;
}
}
void CreateScreensaverImagesPolicyHandler() {
ScreensaverImagesPolicyHandlerTestCase test_case = GetParam();
switch (test_case.handle_type) {
case ScreensaverImagesPolicyHandler::HandlerType::kUser:
policy_handler_ = std::make_unique<ScreensaverImagesPolicyHandler>(
active_prefs_, ScreensaverImagesPolicyHandler::HandlerType::kUser);
break;
case ScreensaverImagesPolicyHandler::HandlerType::kSignin: {
policy_handler_ = std::make_unique<ScreensaverImagesPolicyHandler>(
active_prefs_,
ScreensaverImagesPolicyHandler::HandlerType::kSignin);
VerifyPrivateImageDownloaderDownloadFolder(
*policy_handler_,
temp_dir_.GetPath().AppendASCII(kSigninDirectoryName));
} break;
case ScreensaverImagesPolicyHandler::HandlerType::kManagedGuest:
policy_handler_ = std::make_unique<ScreensaverImagesPolicyHandler>(
active_prefs_,
ScreensaverImagesPolicyHandler::HandlerType::kManagedGuest);
break;
}
}
void ResetScreensaverImagesPolicyHandler() { policy_handler_.reset(); }
base::FilePath GetExpectedFilePath(const std::string url) {
auto hash = base::SHA1Hash(base::as_byte_span(url));
return temp_dir_.GetPath()
.AppendASCII(GetParam().base_directory)
.AppendASCII(base::HexEncode(hash) + kCacheFileExt);
}
};
INSTANTIATE_TEST_SUITE_P(
ScreensaverImagesPolicyHandlerForAnySessionTests,
ScreensaverImagesPolicyHandlerForAnySessionTest,
::testing::ValuesIn<ScreensaverImagesPolicyHandlerTestCase>({
{"Signin", ScreensaverImagesPolicyHandler::HandlerType::kSignin,
kSigninDirectoryName},
{"User", ScreensaverImagesPolicyHandler::HandlerType::kUser,
kCacheDirectoryName},
{"ManagedGuest",
ScreensaverImagesPolicyHandler::HandlerType::kManagedGuest,
kManagedGuestDirectoryName},
}),
[](const ::testing::TestParamInfo<
ScreensaverImagesPolicyHandlerForAnySessionTest::ParamType>& info) {
return info.param.test_name;
});
TEST_P(ScreensaverImagesPolicyHandlerForAnySessionTest, DirectoryTest) {
// Verify that the policy handler creates the new
// image downloader instance.
ASSERT_TRUE(GetPrivateImageDownloader(*policy_handler_));
VerifyPrivateImageDownloaderDownloadFolder(
*policy_handler_,
temp_dir_.GetPath().AppendASCII(GetParam().base_directory));
}
TEST_P(ScreensaverImagesPolicyHandlerForAnySessionTest,
ShouldRunCallbackIfImagesUpdated) {
base::test::TestFuture<std::vector<base::FilePath>> test_future;
policy_handler()->SetScreensaverImagesUpdatedCallback(
test_future.GetRepeatingCallback<const std::vector<base::FilePath>&>());
// Expect callbacks when images are downloaded.
base::FilePath file_path1(kFakeFilePath1);
{
TriggerOnDownloadedImageListUpdated({file_path1});
EXPECT_TRUE(test_future.Wait());
std::vector<base::FilePath> file_paths = test_future.Take();
ASSERT_EQ(1u, file_paths.size());
EXPECT_EQ(file_path1, file_paths.front());
}
base::FilePath file_path2(kFakeFilePath2);
{
TriggerOnDownloadedImageListUpdated({file_path1, file_path2});
EXPECT_TRUE(test_future.Wait());
std::vector<base::FilePath> file_paths = test_future.Take();
ASSERT_EQ(2u, file_paths.size());
EXPECT_TRUE(base::Contains(file_paths, file_path1));
EXPECT_TRUE(base::Contains(file_paths, file_path2));
}
EXPECT_FALSE(test_future.IsReady());
}
TEST_P(ScreensaverImagesPolicyHandlerForAnySessionTest, DownloadImagesTest) {
base::test::TestFuture<std::vector<base::FilePath>> test_future;
policy_handler()->SetScreensaverImagesUpdatedCallback(
test_future.GetRepeatingCallback<const std::vector<base::FilePath>&>());
ASSERT_NE(GetExpectedFilePath(kImageUrl1),
GetExpectedFilePath(kImageUrl1Alt));
base::Value::List image_urls;
image_urls.Append(kImageUrl1);
image_urls.Append(kImageUrl1Alt);
image_urls.Append(kImageUrl2);
// Fill the pref service to trigger the logic under test.
active_prefs()->SetManagedPref(
ambient::prefs::kAmbientModeManagedScreensaverImages, image_urls.Clone());
// Verify that request 1 is resolved
{
url_loader_factory().AddResponse(image_urls[0].GetString(), kFileContents1);
EXPECT_TRUE(test_future.Wait());
std::vector<base::FilePath> file_paths = test_future.Take();
ASSERT_EQ(1u, file_paths.size());
EXPECT_EQ(GetExpectedFilePath(kImageUrl1), file_paths.front());
}
// Verify that request 2 resolves to the same file as request 1.
{
url_loader_factory().AddResponse(image_urls[1].GetString(), kFileContents1);
EXPECT_TRUE(test_future.Wait());
std::vector<base::FilePath> file_paths = test_future.Take();
ASSERT_EQ(1u, file_paths.size());
EXPECT_NE(GetExpectedFilePath(kImageUrl1Alt), file_paths.front());
}
// Verify that request 3 is resolved and both file paths are present.
{
url_loader_factory().AddResponse(image_urls[2].GetString(), kFileContents2);
EXPECT_TRUE(test_future.Wait());
std::vector<base::FilePath> file_paths = test_future.Take();
EXPECT_EQ(2u, file_paths.size());
EXPECT_TRUE(base::Contains(file_paths, GetExpectedFilePath(kImageUrl1)));
EXPECT_TRUE(base::Contains(file_paths, GetExpectedFilePath(kImageUrl2)));
}
}
TEST_P(ScreensaverImagesPolicyHandlerForAnySessionTest, VerifyPolicyLimit) {
base::test::TestFuture<std::vector<base::FilePath>> test_future;
policy_handler()->SetScreensaverImagesUpdatedCallback(
test_future.GetRepeatingCallback<const std::vector<base::FilePath>&>());
base::Value::List image_urls;
// Append the same URL request `kMaxUrlsToProcessFromPolicy` times. This
// should be the only URL that can be requested.
for (size_t i = 0; i < kMaxUrlsToProcessFromPolicy; ++i) {
image_urls.Append(kImageUrl1);
}
// Append a new URL that must be ignored.
image_urls.Append(kImageUrl2);
// Add both responses in the URL factory.
url_loader_factory().AddResponse(image_urls[0].GetString(), kFileContents1);
url_loader_factory().AddResponse(image_urls[1].GetString(), kFileContents2);
// Fill the pref service to trigger the logic under test.
active_prefs()->SetManagedPref(
ambient::prefs::kAmbientModeManagedScreensaverImages, image_urls.Clone());
const base::FilePath expected_file_path = GetExpectedFilePath(kImageUrl1);
for (size_t i = 0; i < kMaxUrlsToProcessFromPolicy; ++i) {
EXPECT_TRUE(test_future.Wait());
std::vector<base::FilePath> file_paths = test_future.Take();
ASSERT_TRUE(file_paths.size());
ASSERT_GT(kMaxUrlsToProcessFromPolicy, file_paths.size());
for (const base::FilePath& p : file_paths) {
EXPECT_EQ(expected_file_path, p);
}
}
}
TEST_P(ScreensaverImagesPolicyHandlerForAnySessionTest,
VerifyUnsetImageListPrefBehavior) {
// Simulate a populated cache
{
base::CreateDirectory(GetExpectedFilePath(kImageUrl1).DirName());
EXPECT_TRUE(
base::WriteFile(GetExpectedFilePath(kImageUrl1), kFileContents1));
EXPECT_TRUE(
base::WriteFile(GetExpectedFilePath(kImageUrl2), kFileContents2));
}
// It is assumed that the image list pref is unset.
// Case 1: The enabled policy is unset.
// Expectation: The unset image list pref should not cause a cache cleanup.
{
CreateScreensaverImagesPolicyHandler();
task_environment()->RunUntilIdle();
EXPECT_TRUE(base::PathExists(GetExpectedFilePath(kImageUrl1)));
EXPECT_TRUE(base::PathExists(GetExpectedFilePath(kImageUrl2)));
}
// Case 2: The enabled policy is set to true.
// Expectation: The unset image list pref should not cause a cache cleanup.
{
ResetScreensaverImagesPolicyHandler();
active_prefs()->SetManagedPref(
ambient::prefs::kAmbientModeManagedScreensaverEnabled,
base::Value(true));
CreateScreensaverImagesPolicyHandler();
task_environment()->RunUntilIdle();
EXPECT_TRUE(base::PathExists(GetExpectedFilePath(kImageUrl1)));
EXPECT_TRUE(base::PathExists(GetExpectedFilePath(kImageUrl2)));
}
// Case 3: The enabled policy is set to false.
// Expectation: The unset image list pref should cause a cache cleanup.
{
ResetScreensaverImagesPolicyHandler();
active_prefs()->SetManagedPref(
ambient::prefs::kAmbientModeManagedScreensaverEnabled,
base::Value(false));
CreateScreensaverImagesPolicyHandler();
task_environment()->RunUntilIdle();
EXPECT_FALSE(base::PathExists(GetExpectedFilePath(kImageUrl1)));
EXPECT_FALSE(base::PathExists(GetExpectedFilePath(kImageUrl2)));
}
}
} // namespace ash