chromium/chrome/browser/ash/wallpaper/wallpaper_drivefs_delegate_impl_browsertest.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ash/wallpaper/wallpaper_drivefs_delegate_impl.h"

#include <memory>
#include <vector>

#include "ash/shell.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "base/barrier_closure.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/drive_integration_service_browser_test_base.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chromeos/ash/components/drivefs/fake_drivefs.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/user.h"
#include "content/public/test/browser_test.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_unittest_util.h"

namespace ash {

namespace {

scoped_refptr<base::RefCountedBytes> EncodeImage(const gfx::ImageSkia& image) {
  auto output = base::MakeRefCounted<base::RefCountedBytes>();
  SkBitmap bitmap = *(image.bitmap());
  gfx::JPEGCodec::Encode(bitmap, /*quality=*/90, &(output)->as_vector());
  return output;
}

WallpaperDriveFsDelegate* GetWallpaperDriveFsDelegate() {
  Shell* shell = Shell::Get();
  auto* wallpaper_controller = shell->wallpaper_controller();
  return wallpaper_controller->drivefs_delegate_for_testing();
}

// Saves a test wallpaper file. If `target` is empty, will default to the
// DriveFS wallpaper path.
void SaveTestWallpaperFile(const AccountId& account_id, base::FilePath target) {
  ASSERT_FALSE(target.empty()) << "target FilePath is required to be non-empty";
  base::ScopedAllowBlockingForTesting allow_blocking;
  if (!base::DirectoryExists(target.DirName())) {
    ASSERT_TRUE(base::CreateDirectory(target.DirName()));
  }
  auto data = EncodeImage(gfx::test::CreateImageSkia(/*size=*/16));
  ASSERT_TRUE(
      base::WriteFile(target, base::make_span(data->front(), data->size())));
}

}  // namespace

class WallpaperDriveFsDelegateImplBrowserTest
    : public drive::DriveIntegrationServiceBrowserTestBase {
 public:
  WallpaperDriveFsDelegateImplBrowserTest() = default;

  WallpaperDriveFsDelegateImplBrowserTest(
      const WallpaperDriveFsDelegateImplBrowserTest&) = delete;
  WallpaperDriveFsDelegateImplBrowserTest& operator=(
      const WallpaperDriveFsDelegateImplBrowserTest&) = delete;

  ~WallpaperDriveFsDelegateImplBrowserTest() override = default;

  const AccountId& GetAccountId() const {
    user_manager::User* user =
        ProfileHelper::Get()->GetUserByProfile(browser()->profile());
    DCHECK(user);
    return user->GetAccountId();
  }

  bool SaveWallpaperSync(const AccountId& account_id,
                         const base::FilePath& source) {
    base::RunLoop loop;
    bool out = false;
    GetWallpaperDriveFsDelegate()->SaveWallpaper(
        account_id, source,
        base::BindLambdaForTesting([&out, &loop](bool success) {
          out = success;
          loop.Quit();
        }));
    loop.Run();
    return out;
  }

  base::Time GetWallpaperModificationTimeSync(const AccountId& account_id) {
    base::RunLoop loop;
    base::Time out;
    GetWallpaperDriveFsDelegate()->GetWallpaperModificationTime(
        account_id, base::BindLambdaForTesting([&out, &loop](base::Time time) {
          out = time;
          loop.Quit();
        }));
    loop.Run();
    return out;
  }

  std::vector<drivefs::mojom::FileChangePtr> CreateWallpaperFileChange() {
    std::vector<drivefs::mojom::FileChangePtr> file_changes;
    drive::DriveIntegrationService* drive_integration_service =
        drive::util::GetIntegrationServiceByProfile(browser()->profile());

    base::FilePath fake_wallpaper_notification_path(
        &base::FilePath::kSeparators[0]);

    EXPECT_TRUE(
        drive_integration_service->GetMountPointPath().AppendRelativePath(
            GetWallpaperDriveFsDelegate()->GetWallpaperPath(GetAccountId()),
            &fake_wallpaper_notification_path));

    drivefs::mojom::FileChangePtr wallpaper_change =
        drivefs::mojom::FileChange::New(
            fake_wallpaper_notification_path,
            drivefs::mojom::FileChange::Type::kModify);

    file_changes.push_back(std::move(wallpaper_change));
    return file_changes;
  }
};

IN_PROC_BROWSER_TEST_F(WallpaperDriveFsDelegateImplBrowserTest,
                       EmptyBaseTimeIfNoDriveFs) {
  InitTestFileMountRoot(browser()->profile());
  SaveTestWallpaperFile(
      GetAccountId(),
      GetWallpaperDriveFsDelegate()->GetWallpaperPath(GetAccountId()));

  drive::DriveIntegrationService* drive_integration_service =
      drive::util::GetIntegrationServiceByProfile(browser()->profile());
  ASSERT_TRUE(drive_integration_service);
  drive_integration_service->SetEnabled(false);

  const base::Time modification_time =
      GetWallpaperModificationTimeSync(GetAccountId());
  EXPECT_EQ(modification_time, base::Time())
      << "DriveFS disabled should result in empty time";
}

IN_PROC_BROWSER_TEST_F(WallpaperDriveFsDelegateImplBrowserTest,
                       RespondsWithModifiedAtTime) {
  InitTestFileMountRoot(browser()->profile());

  base::ScopedAllowBlockingForTesting allow_blocking;
  const base::FilePath drivefs_wallpaper_path =
      GetWallpaperDriveFsDelegate()->GetWallpaperPath(GetAccountId());
  ASSERT_FALSE(base::PathExists(drivefs_wallpaper_path));

  SaveTestWallpaperFile(GetAccountId(), drivefs_wallpaper_path);

  base::File::Info file_info;
  ASSERT_TRUE(base::GetFileInfo(drivefs_wallpaper_path, &file_info));

  const base::Time drivefs_modification_time =
      GetWallpaperModificationTimeSync(GetAccountId());

  EXPECT_EQ(drivefs_modification_time, file_info.last_modified)
      << "modification_time matches file info time";
}

IN_PROC_BROWSER_TEST_F(WallpaperDriveFsDelegateImplBrowserTest, SaveWallpaper) {
  InitTestFileMountRoot(browser()->profile());

  base::FilePath drivefs_wallpaper_path =
      GetWallpaperDriveFsDelegate()->GetWallpaperPath(GetAccountId());
  ASSERT_FALSE(drivefs_wallpaper_path.empty());

  base::ScopedAllowBlockingForTesting scoped_allow_blocking;
  // Write a jpg file to a tmp directory. This file will be copied into DriveFS.
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  base::FilePath source_jpg = scoped_temp_dir.GetPath().Append("source.jpg");
  SaveTestWallpaperFile(GetAccountId(), source_jpg);

  // `SaveWallpaper` should succeed while DriveFS is enabled.
  EXPECT_TRUE(SaveWallpaperSync(GetAccountId(), source_jpg));
  // source.jpg was copied to DriveFS wallpaper path.
  EXPECT_TRUE(base::PathExists(drivefs_wallpaper_path));
}

IN_PROC_BROWSER_TEST_F(WallpaperDriveFsDelegateImplBrowserTest,
                       SaveWallpaperDriveFsDisabled) {
  InitTestFileMountRoot(browser()->profile());
  base::ScopedAllowBlockingForTesting scoped_allow_blocking;

  // Write a jpg file to a tmp directory. This file will be copied into DriveFS.
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  base::FilePath source_jpg = scoped_temp_dir.GetPath().Append("source.jpg");
  SaveTestWallpaperFile(GetAccountId(), source_jpg);

  base::FilePath drivefs_wallpaper_path =
      GetWallpaperDriveFsDelegate()->GetWallpaperPath(GetAccountId());
  ASSERT_FALSE(drivefs_wallpaper_path.empty());

  // Call `SaveWallpaper` while DriveFS is disabled. No file should be written.
  auto* drive_integration_service =
      drive::util::GetIntegrationServiceByProfile(browser()->profile());
  drive_integration_service->SetEnabled(false);
  EXPECT_FALSE(SaveWallpaperSync(GetAccountId(), source_jpg));
  EXPECT_FALSE(base::PathExists(drivefs_wallpaper_path));
}

IN_PROC_BROWSER_TEST_F(WallpaperDriveFsDelegateImplBrowserTest,
                       WaitForWallpaperChange) {
  InitTestFileMountRoot(browser()->profile());

  base::RunLoop loop;

  GetWallpaperDriveFsDelegate()->WaitForWallpaperChange(
      GetAccountId(), base::BindLambdaForTesting([&loop](bool success) {
        EXPECT_TRUE(success);
        loop.Quit();
      }));

  // Send the fake wallpaper file change notification.
  drivefs::FakeDriveFs* fake_drivefs =
      GetFakeDriveFsForProfile(browser()->profile());
  fake_drivefs->delegate()->OnFilesChanged(CreateWallpaperFileChange());

  loop.Run();
}

IN_PROC_BROWSER_TEST_F(WallpaperDriveFsDelegateImplBrowserTest,
                       WaitForWallpaperChangeWithDriveFsDisabled) {
  drive::DriveIntegrationService* drive_integration_service =
      drive::util::GetIntegrationServiceByProfile(browser()->profile());
  drive_integration_service->SetEnabled(false);

  base::RunLoop loop;

  // Responds immediately with `success=false` even without receiving any file
  // change notifications because DriveFS is disabled.
  GetWallpaperDriveFsDelegate()->WaitForWallpaperChange(
      GetAccountId(), base::BindLambdaForTesting([&loop](bool success) {
        EXPECT_FALSE(success);
        loop.Quit();
      }));

  loop.Run();
}

IN_PROC_BROWSER_TEST_F(WallpaperDriveFsDelegateImplBrowserTest,
                       WaitForWallpaperChangeMultipleTimes) {
  InitTestFileMountRoot(browser()->profile());

  base::RunLoop loop;
  // Make sure that closure is called twice before `loop` quits.
  base::RepeatingClosure barrier =
      base::BarrierClosure(/*num_closures=*/2, loop.QuitClosure());

  // The first `WaitForWallpaperChange` call should respond with false when
  // `WaitForWallpaperChange` is called a second time before receiving a file
  // change notification.
  GetWallpaperDriveFsDelegate()->WaitForWallpaperChange(
      GetAccountId(), base::BindLambdaForTesting([&barrier](bool success) {
        EXPECT_FALSE(success);
        barrier.Run();
      }));

  // This call to `WaitForWallpaperChange` should succeed upon receiving the
  // file change notification.
  GetWallpaperDriveFsDelegate()->WaitForWallpaperChange(
      GetAccountId(), base::BindLambdaForTesting([&barrier](bool success) {
        EXPECT_TRUE(success);
        barrier.Run();
      }));

  // Send the fake wallpaper file change notification.
  drivefs::FakeDriveFs* fake_drivefs =
      GetFakeDriveFsForProfile(browser()->profile());
  fake_drivefs->delegate()->OnFilesChanged(CreateWallpaperFileChange());

  loop.Run();
}

}  // namespace ash