chromium/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl_unittest.cc

// Copyright 2021 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/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.h"

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

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/test/in_process_data_decoder.h"
#include "ash/public/cpp/wallpaper/online_wallpaper_params.h"
#include "ash/public/cpp/wallpaper/online_wallpaper_variant.h"
#include "ash/public/cpp/wallpaper/wallpaper_controller_client.h"
#include "ash/public/cpp/wallpaper/wallpaper_info.h"
#include "ash/wallpaper/sea_pen_wallpaper_manager.h"
#include "ash/wallpaper/test_sea_pen_wallpaper_manager_session_delegate.h"
#include "ash/wallpaper/wallpaper_constants.h"
#include "ash/wallpaper/wallpaper_pref_manager.h"
#include "ash/webui/common/mojom/sea_pen.mojom-forward.h"
#include "ash/webui/common/mojom/sea_pen.mojom.h"
#include "ash/webui/common/mojom/sea_pen_generated.mojom-shared.h"
#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
#include "base/containers/flat_map.h"
#include "base/files/file_util.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/policy/external_data/handlers/device_wallpaper_image_external_data_handler.h"
#include "chrome/browser/ash/settings/cros_settings_holder.h"
#include "chrome/browser/ash/settings/device_settings_cache.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/mock_personalization_app_manager.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_manager_factory.h"
#include "chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h"
#include "chrome/browser/ash/wallpaper_handlers/test_wallpaper_fetcher_delegate.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/ui/ash/wallpaper/test_wallpaper_controller.h"
#include "chrome/browser/ui/ash/wallpaper/wallpaper_controller_client_impl.h"
#include "chrome/test/base/scoped_testing_local_state.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/strings/grit/chromeos_strings.h"
#include "components/account_id/account_id.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/prefs/testing_pref_service.h"
#include "components/user_manager/known_user.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_web_ui.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/test_support/test_utils.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/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"

namespace ash::personalization_app {

namespace {

constexpr char kFakeTestEmail[] = "fakeemail@personalization";
constexpr char kTestGaiaId[] = "1234567890";

// Create fake Jpg image bytes.
std::string CreateJpgBytes() {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(1, 1);
  bitmap.eraseARGB(255, 31, 63, 127);
  std::vector<unsigned char> data;
  gfx::JPEGCodec::Encode(bitmap, /*quality=*/100, &data);
  return std::string(data.begin(), data.end());
}

TestingPrefServiceSimple* RegisterPrefs(TestingPrefServiceSimple* local_state) {
  ash::device_settings_cache::RegisterPrefs(local_state->registry());
  user_manager::KnownUser::RegisterPrefs(local_state->registry());
  ash::WallpaperPrefManager::RegisterLocalStatePrefs(local_state->registry());
  policy::DeviceWallpaperImageExternalDataHandler::RegisterPrefs(
      local_state->registry());
  ProfileAttributesStorage::RegisterPrefs(local_state->registry());
  return local_state;
}

void AddAndLoginUser(const AccountId& account_id) {
  ash::FakeChromeUserManager* user_manager =
      static_cast<ash::FakeChromeUserManager*>(
          user_manager::UserManager::Get());
  user_manager->AddUser(account_id);
  user_manager->LoginUser(account_id);
  user_manager->SwitchActiveUser(account_id);
}

AccountId GetTestAccountId() {
  return AccountId::FromUserEmailGaiaId(kFakeTestEmail, kTestGaiaId);
}

// Create a test 1x1 image with a given |color|.
gfx::ImageSkia CreateSolidImageSkia(int width, int height, SkColor color) {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(width, height);
  bitmap.eraseColor(color);
  return gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
}

std::unique_ptr<KeyedService> MakeMockPersonalizationAppManager(
    content::BrowserContext* context) {
  return std::make_unique<::testing::NiceMock<MockPersonalizationAppManager>>();
}

class TestWallpaperObserver
    : public ash::personalization_app::mojom::WallpaperObserver {
 public:
  void WaitForAttributionChange() {
    ASSERT_FALSE(on_attribution_changed_callback_);
    base::RunLoop loop;
    on_attribution_changed_callback_ = loop.QuitClosure();
    loop.Run();
  }

  // WallpaperObserver:
  void OnWallpaperPreviewEnded() override {}

  void OnWallpaperChanged(
      ash::personalization_app::mojom::CurrentWallpaperPtr image) override {
    current_wallpaper_ = std::move(image);
  }

  void OnAttributionChanged(
      ash::personalization_app::mojom::CurrentAttributionPtr attribution)
      override {
    current_attribution_ = std::move(attribution);
    if (on_attribution_changed_callback_) {
      std::move(on_attribution_changed_callback_).Run();
    }
  }

  mojo::PendingRemote<ash::personalization_app::mojom::WallpaperObserver>
  pending_remote() {
    DCHECK(!wallpaper_observer_receiver_.is_bound());
    return wallpaper_observer_receiver_.BindNewPipeAndPassRemote();
  }

  ash::personalization_app::mojom::CurrentWallpaper* current_wallpaper() {
    if (!wallpaper_observer_receiver_.is_bound()) {
      return nullptr;
    }

    wallpaper_observer_receiver_.FlushForTesting();
    return current_wallpaper_.get();
  }

  ash::personalization_app::mojom::CurrentAttribution* current_attribution() {
    if (!wallpaper_observer_receiver_.is_bound()) {
      return nullptr;
    }

    wallpaper_observer_receiver_.FlushForTesting();
    return current_attribution_.get();
  }

 private:
  mojo::Receiver<ash::personalization_app::mojom::WallpaperObserver>
      wallpaper_observer_receiver_{this};

  ash::personalization_app::mojom::CurrentWallpaperPtr current_wallpaper_ =
      nullptr;

  ash::personalization_app::mojom::CurrentAttributionPtr current_attribution_ =
      nullptr;

  base::OnceClosure on_attribution_changed_callback_;
};

}  // namespace

class PersonalizationAppWallpaperProviderImplTest : public testing::Test {
 public:
  PersonalizationAppWallpaperProviderImplTest()
      : scoped_user_manager_(std::make_unique<ash::FakeChromeUserManager>()),
        profile_manager_(TestingBrowserProcess::GetGlobal()) {}

  PersonalizationAppWallpaperProviderImplTest(
      const PersonalizationAppWallpaperProviderImplTest&) = delete;
  PersonalizationAppWallpaperProviderImplTest& operator=(
      const PersonalizationAppWallpaperProviderImplTest&) = delete;
  ~PersonalizationAppWallpaperProviderImplTest() override = default;

 protected:
  // testing::Test:
  void SetUp() override {
    sea_pen_wallpaper_manager()->SetSessionDelegateForTesting(
        std::make_unique<TestSeaPenWallpaperManagerSessionDelegate>());

    wallpaper_controller_client_ = std::make_unique<
        WallpaperControllerClientImpl>(
        std::make_unique<wallpaper_handlers::TestWallpaperFetcherDelegate>());
    wallpaper_controller_client_->InitForTesting(&test_wallpaper_controller_);

    ASSERT_TRUE(profile_manager_.SetUp());
    profile_ = profile_manager_.CreateTestingProfile(
        kFakeTestEmail,
        {TestingProfile::TestingFactory{
            ash::personalization_app::PersonalizationAppManagerFactory::
                GetInstance(),
            base::BindRepeating(&MakeMockPersonalizationAppManager)}});

    AddAndLoginUser(GetTestAccountId());
    test_wallpaper_controller()->SetCurrentUser(GetTestAccountId());

    web_contents_ = content::WebContents::Create(
        content::WebContents::CreateParams(profile_));
    web_ui_.set_web_contents(web_contents_.get());

    wallpaper_provider_ = std::make_unique<
        PersonalizationAppWallpaperProviderImpl>(
        &web_ui_,
        std::make_unique<wallpaper_handlers::TestWallpaperFetcherDelegate>());

    wallpaper_provider_->BindInterface(
        wallpaper_provider_remote_.BindNewPipeAndPassReceiver());
  }

  PersonalizationAppWallpaperProviderImpl::ImageInfo GetDefaultImageInfo() {
    return {
        /*in_image_url=*/GURL("http://test_url"),
        /*in_collection_id=*/"collection_id",
        /*in_asset_id=*/1,
        /*in_unit_id=*/1,
        /*in_type=*/backdrop::Image::IMAGE_TYPE_UNKNOWN,
    };
  }

  void AddWallpaperImage(
      const PersonalizationAppWallpaperProviderImpl::ImageInfo& image_info) {
    wallpaper_provider_->image_unit_id_map_.insert(
        {image_info.unit_id, {image_info}});
  }

  SeaPenWallpaperManager* sea_pen_wallpaper_manager() {
    return &sea_pen_wallpaper_manager_;
  }

  TestSeaPenWallpaperManagerSessionDelegate*
  sea_pen_wallpaper_manager_session_delegate() {
    return static_cast<TestSeaPenWallpaperManagerSessionDelegate*>(
        sea_pen_wallpaper_manager()->session_delegate_for_testing());
  }

  TestWallpaperController* test_wallpaper_controller() {
    return &test_wallpaper_controller_;
  }

  TestingProfile* profile() { return profile_; }

  mojo::Remote<ash::personalization_app::mojom::WallpaperProvider>&
  wallpaper_provider_remote() {
    return wallpaper_provider_remote_;
  }

  PersonalizationAppWallpaperProviderImpl* delegate() {
    return wallpaper_provider_.get();
  }

  void ResetWallpaperProvider() { wallpaper_provider_.reset(); }

  ::testing::NiceMock<MockPersonalizationAppManager>*
  MockPersonalizationAppManager() {
    return static_cast<::testing::NiceMock<
        ::ash::personalization_app::MockPersonalizationAppManager>*>(
        ::ash::personalization_app::PersonalizationAppManagerFactory::
            GetForBrowserContext(profile_));
  }

  void SetWallpaperObserver() {
    wallpaper_provider_remote_->SetWallpaperObserver(
        test_wallpaper_observer_.pending_remote());
  }

  TestWallpaperObserver* test_wallpaper_observer() {
    return &test_wallpaper_observer_;
  }

  ash::personalization_app::mojom::CurrentWallpaper* current_wallpaper() {
    wallpaper_provider_remote_.FlushForTesting();
    return test_wallpaper_observer_.current_wallpaper();
  }

  ash::personalization_app::mojom::CurrentAttribution* current_attribution() {
    wallpaper_provider_remote_.FlushForTesting();
    return test_wallpaper_observer_.current_attribution();
  }

 private:
  // Note: `scoped_feature_list_` should be destroyed after `task_environment_`
  // (see crbug.com/846380).
  base::test::ScopedFeatureList scoped_feature_list_;
  content::BrowserTaskEnvironment task_environment_;
  InProcessDataDecoder in_process_data_decoder_;
  TestingPrefServiceSimple pref_service_;
  // Required for CrosSettings.
  ash::ScopedStubInstallAttributes scoped_stub_install_attributes_;
  // Required for CrosSettings.
  ash::ScopedTestDeviceSettingsService scoped_device_settings_;
  // Required for |WallpaperControllerClientImpl|.
  ash::CrosSettingsHolder cros_settings_holder_{
      ash::DeviceSettingsService::Get(), RegisterPrefs(&pref_service_)};
  user_manager::ScopedUserManager scoped_user_manager_;
  TestingProfileManager profile_manager_;
  raw_ptr<TestingProfile> profile_;
  SeaPenWallpaperManager sea_pen_wallpaper_manager_;
  TestWallpaperController test_wallpaper_controller_;
  // |wallpaper_controller_client_| must be destructed before
  // |test_wallpaper_controller_|.
  std::unique_ptr<WallpaperControllerClientImpl> wallpaper_controller_client_;
  content::TestWebUI web_ui_;
  std::unique_ptr<content::WebContents> web_contents_;
  mojo::Remote<ash::personalization_app::mojom::WallpaperProvider>
      wallpaper_provider_remote_;
  TestWallpaperObserver test_wallpaper_observer_;
  std::unique_ptr<PersonalizationAppWallpaperProviderImpl> wallpaper_provider_;
};

TEST_F(PersonalizationAppWallpaperProviderImplTest, SelectWallpaper) {
  test_wallpaper_controller()->ClearCounts();

  auto image_info = GetDefaultImageInfo();
  std::vector<ash::OnlineWallpaperVariant> variants;
  variants.emplace_back(image_info.asset_id, image_info.image_url,
                        backdrop::Image::IMAGE_TYPE_UNKNOWN);

  AddWallpaperImage(image_info);

  base::test::TestFuture<bool> success_future;
  wallpaper_provider_remote()->SelectWallpaper(image_info.asset_id,
                                               /*preview_mode=*/false,
                                               success_future.GetCallback());
  EXPECT_TRUE(success_future.Take());

  EXPECT_EQ(1, test_wallpaper_controller()->set_online_wallpaper_count());
  EXPECT_TRUE(
      test_wallpaper_controller()->wallpaper_info().value().MatchesSelection(
          ash::WallpaperInfo(
              {GetTestAccountId(), "collection_id",
               ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
               /*preview_mode=*/false, /*from_user=*/true,
               /*daily_refresh_enabled=*/false, image_info.unit_id, variants},
              variants.front())));
}

TEST_F(PersonalizationAppWallpaperProviderImplTest, SelectWallpaperWhenBanned) {
  test_wallpaper_controller()->set_can_set_user_wallpaper(false);
  auto image_info = GetDefaultImageInfo();
  std::vector<ash::OnlineWallpaperVariant> variants;
  variants.emplace_back(image_info.asset_id, image_info.image_url,
                        backdrop::Image::IMAGE_TYPE_UNKNOWN);

  AddWallpaperImage(image_info);

  mojo::test::BadMessageObserver bad_message_observer;

  wallpaper_provider_remote()->SelectWallpaper(
      image_info.asset_id, /*preview_mode=*/false,
      base::BindLambdaForTesting(
          [](bool success) { NOTREACHED_IN_MIGRATION(); }));

  EXPECT_EQ("Invalid request to set wallpaper",
            bad_message_observer.WaitForBadMessage());
}

TEST_F(PersonalizationAppWallpaperProviderImplTest, PreviewWallpaper) {
  test_wallpaper_controller()->ClearCounts();

  auto image_info = GetDefaultImageInfo();
  std::vector<ash::OnlineWallpaperVariant> variants;
  variants.emplace_back(image_info.asset_id, image_info.image_url,
                        image_info.type);

  AddWallpaperImage(image_info);

  base::test::TestFuture<bool> success_future;
  wallpaper_provider_remote()->SelectWallpaper(image_info.asset_id,
                                               /*preview_mode=*/true,
                                               success_future.GetCallback());
  EXPECT_TRUE(success_future.Take());

  EXPECT_EQ(1, test_wallpaper_controller()->set_online_wallpaper_count());
  EXPECT_TRUE(
      test_wallpaper_controller()->wallpaper_info().value().MatchesSelection(
          ash::WallpaperInfo(
              {GetTestAccountId(), "collection_id",
               ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
               /*preview_mode=*/true, /*from_user=*/true,
               /*daily_refresh_enabled=*/false, image_info.unit_id, variants},
              variants.front())));
}

TEST_F(PersonalizationAppWallpaperProviderImplTest,
       ObserveWallpaperFiresWhenBound) {
  test_wallpaper_controller()->ShowWallpaperImage(
      CreateSolidImageSkia(/*width=*/1, /*height=*/1, SK_ColorBLACK));

  auto image_info = GetDefaultImageInfo();
  std::vector<ash::OnlineWallpaperVariant> variants;
  variants.emplace_back(image_info.asset_id, image_info.image_url,
                        backdrop::Image::IMAGE_TYPE_UNKNOWN);

  AddWallpaperImage(image_info);

  test_wallpaper_controller()->SetOnlineWallpaper(
      {GetTestAccountId(), "collection_id",
       ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
       /*preview_mode=*/false, /*from_user=*/true,
       /*daily_refresh_enabled=*/false, image_info.unit_id, variants},
      base::DoNothing());

  EXPECT_EQ(nullptr, current_wallpaper());

  SetWallpaperObserver();

  // WallpaperObserver Should have received an image through mojom.
  ash::personalization_app::mojom::CurrentWallpaper* current =
      current_wallpaper();

  EXPECT_EQ(ash::WallpaperType::kOnline, current->type);
  EXPECT_EQ(ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
            current->layout);
}

TEST_F(PersonalizationAppWallpaperProviderImplTest,
       IgnoresWallpaperResizeForOtherUser) {
  const AccountId other_account_id = AccountId::FromUserEmailGaiaId(
      "otherfakeemail@personalization", "0987654321");
  test_wallpaper_controller()->SetCurrentUser(other_account_id);

  test_wallpaper_controller()->ShowWallpaperImage(
      CreateSolidImageSkia(/*width=*/1, /*height=*/1, SK_ColorBLACK));

  auto image_info = GetDefaultImageInfo();
  std::vector<ash::OnlineWallpaperVariant> variants;
  variants.emplace_back(image_info.asset_id, image_info.image_url,
                        backdrop::Image::IMAGE_TYPE_UNKNOWN);

  AddWallpaperImage(image_info);

  test_wallpaper_controller()->SetOnlineWallpaper(
      {other_account_id, "collection_id",
       ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
       /*preview_mode=*/false, /*from_user=*/true,
       /*daily_refresh_enabled=*/false, image_info.unit_id, variants},
      base::DoNothing());

  EXPECT_EQ(nullptr, current_wallpaper());

  SetWallpaperObserver();

  EXPECT_EQ(nullptr, current_wallpaper());
}

TEST_F(PersonalizationAppWallpaperProviderImplTest, ValidSeaPenAttribution) {
  {
    // Save the image and metadata to disk.
    mojom::SeaPenQueryPtr sea_pen_query_ptr =
        mojom::SeaPenQuery::NewTemplateQuery(mojom::SeaPenTemplateQuery::New(
            mojom::SeaPenTemplateId::kArt,
            base::flat_map<mojom::SeaPenTemplateChip,
                           mojom::SeaPenTemplateOption>(
                {{mojom::SeaPenTemplateChip::kArtFeature,
                  mojom::SeaPenTemplateOption::kArtFeatureBeach},
                 {mojom::SeaPenTemplateChip::kArtMovement,
                  mojom::SeaPenTemplateOption::kArtMovementAbstract}}),
            mojom::SeaPenUserVisibleQuery::New("test template query text",
                                               "test template query title")));

    base::test::TestFuture<bool> save_sea_pen_image_future;
    sea_pen_wallpaper_manager()->SaveSeaPenImage(
        GetTestAccountId(), {CreateJpgBytes(), 111u}, sea_pen_query_ptr,
        save_sea_pen_image_future.GetCallback());
    ASSERT_TRUE(save_sea_pen_image_future.Get());
  }

  // Set the image as user wallpaper.
  test_wallpaper_controller()->SetSeaPenWallpaper(
      GetTestAccountId(), 111u, /*preview_mode=*/false, base::DoNothing());

  SetWallpaperObserver();
  test_wallpaper_observer()->WaitForAttributionChange();

  ash::personalization_app::mojom::CurrentAttribution* current_attr =
      current_attribution();
  EXPECT_EQ("111", current_attr->key);
  std::vector<std::string> expected_attr{
      "test template query text",
      l10n_util::GetStringUTF8(IDS_SEA_PEN_POWERED_BY_GOOGLE_AI)};
  EXPECT_EQ(expected_attr, current_attr->attribution);
}

TEST_F(PersonalizationAppWallpaperProviderImplTest, MissingSeaPenAttribution) {
  // Write a jpg with no metadata.
  const base::FilePath jpg_path = sea_pen_wallpaper_manager_session_delegate()
                                      ->GetStorageDirectory(GetTestAccountId())
                                      .Append("111")
                                      .AddExtension(".jpg");
  ASSERT_TRUE(base::CreateDirectory(jpg_path.DirName()));
  ASSERT_TRUE(base::WriteFile(jpg_path, CreateJpgBytes()));

  test_wallpaper_controller()->SetSeaPenWallpaper(
      GetTestAccountId(), 111u, /*preview_mode=*/false, base::DoNothing());

  SetWallpaperObserver();
  test_wallpaper_observer()->WaitForAttributionChange();

  ash::personalization_app::mojom::CurrentAttribution* current_attr =
      current_attribution();
  EXPECT_EQ("111", current_attr->key);
  EXPECT_EQ(std::vector<std::string>(), current_attr->attribution);
}

TEST_F(PersonalizationAppWallpaperProviderImplTest, SetCurrentWallpaperLayout) {
  auto* ctrl = test_wallpaper_controller();

  EXPECT_EQ(ctrl->update_current_wallpaper_layout_count(), 0);
  EXPECT_EQ(ctrl->update_current_wallpaper_layout_layout(), std::nullopt);

  auto layout = ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER;
  wallpaper_provider_remote()->SetCurrentWallpaperLayout(layout);
  wallpaper_provider_remote().FlushForTesting();

  EXPECT_EQ(ctrl->update_current_wallpaper_layout_count(), 1);
  EXPECT_EQ(ctrl->update_current_wallpaper_layout_layout(), layout);
}

TEST_F(PersonalizationAppWallpaperProviderImplTest,
       CallsMaybeStartHatsTimerOnDestruction) {
  // Insert a wallpaper image info to indicate that the user opened the
  // wallpaper app and requested wallpapers.
  AddWallpaperImage(GetDefaultImageInfo());

  EXPECT_CALL(*MockPersonalizationAppManager(),
              MaybeStartHatsTimer(HatsSurveyType::kWallpaper))
      .Times(1);

  ResetWallpaperProvider();
}

TEST_F(PersonalizationAppWallpaperProviderImplTest,
       DoesNotCallMaybeStartHatsTimerIfNoWallpaperFetched) {
  EXPECT_CALL(*MockPersonalizationAppManager(),
              MaybeStartHatsTimer(HatsSurveyType::kWallpaper))
      .Times(0);

  ResetWallpaperProvider();
}

TEST_F(PersonalizationAppWallpaperProviderImplTest, GetWallpaperAsJpegBytes) {
  auto image = CreateSolidImageSkia(/*width=*/1, /*height=*/1, SK_ColorRED);
  test_wallpaper_controller()->ShowWallpaperImage(image);
  scoped_refptr<base::RefCountedMemory> expected_jpeg_bytes =
      gfx::Image(image).As1xPNGBytes();

  // Jpeg bytes of the preview image.
  base::RunLoop loop;
  test_wallpaper_controller()->LoadPreviewImage(base::BindLambdaForTesting(
      [quit = loop.QuitClosure(), &expected_jpeg_bytes](
          scoped_refptr<base::RefCountedMemory> preview_bytes) {
        EXPECT_TRUE(preview_bytes->Equals(expected_jpeg_bytes));
        std::move(quit).Run();
      }));
  loop.Run();
}

TEST_F(PersonalizationAppWallpaperProviderImplTest, SetDailyRefreshBanned) {
  EXPECT_EQ(test_wallpaper_controller()->GetDailyRefreshCollectionId(
                GetTestAccountId()),
            "");

  const std::string collection_id = "collection_id";
  test_wallpaper_controller()->set_can_set_user_wallpaper(false);
  mojo::test::BadMessageObserver bad_message_observer;
  wallpaper_provider_remote()->SetDailyRefreshCollectionId(
      collection_id, base::BindLambdaForTesting(
                         [](bool success) { NOTREACHED_IN_MIGRATION(); }));
  EXPECT_EQ("Invalid request to set wallpaper",
            bad_message_observer.WaitForBadMessage());
}

TEST_F(PersonalizationAppWallpaperProviderImplTest,
       ShouldShowTimeOfDayWallpaperDialog) {
  test_wallpaper_controller()->ClearCounts();
  base::test::ScopedFeatureList features;
  features.InitWithFeatures({features::kFeatureManagementTimeOfDayWallpaper},
                            {});

  auto image_info = GetDefaultImageInfo();
  image_info.collection_id =
      wallpaper_constants::kTimeOfDayWallpaperCollectionId;
  std::vector<ash::OnlineWallpaperVariant> variants;
  variants.emplace_back(image_info.asset_id, image_info.image_url,
                        backdrop::Image::IMAGE_TYPE_UNKNOWN);

  AddWallpaperImage(image_info);

  base::test::TestFuture<bool> should_show_dialog_future;
  wallpaper_provider_remote()->ShouldShowTimeOfDayWallpaperDialog(
      should_show_dialog_future.GetCallback());
  // Expects to return true before time of day wallpaper is set.
  EXPECT_TRUE(should_show_dialog_future.Take());

  base::test::TestFuture<bool> success_future;
  wallpaper_provider_remote()->SelectWallpaper(image_info.asset_id,
                                               /*preview_mode=*/false,
                                               success_future.GetCallback());
  EXPECT_TRUE(success_future.Take());

  EXPECT_EQ(1, test_wallpaper_controller()->set_online_wallpaper_count());
  EXPECT_TRUE(
      test_wallpaper_controller()->wallpaper_info().value().MatchesSelection(
          ash::WallpaperInfo(
              {GetTestAccountId(),
               wallpaper_constants::kTimeOfDayWallpaperCollectionId,
               ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
               /*preview_mode=*/false, /*from_user=*/true,
               /*daily_refresh_enabled=*/false, image_info.unit_id, variants},
              variants.front())));

  wallpaper_provider_remote()->ShouldShowTimeOfDayWallpaperDialog(
      should_show_dialog_future.GetCallback());
  // Expects to return false after time of day wallpaper is set.
  EXPECT_FALSE(should_show_dialog_future.Take());
}

class PersonalizationAppWallpaperProviderImplGooglePhotosTest
    : public PersonalizationAppWallpaperProviderImplTest {
 protected:
  // Mocks an attempt to fetch the Google Photos enterprise setting from the
  // server. A successful attempt, which happens when the Google Photos
  // wallpaper integration is enabled, will enable wallpaper selection and
  // other Google Photos data fetches to go through.
  void FetchGooglePhotosEnabled(size_t num_fetches = 1) {
    using ash::personalization_app::mojom::GooglePhotosEnablementState;

    // Mock a fetcher for the enablement state query.
    auto* const google_photos_enabled_fetcher = static_cast<::testing::NiceMock<
        wallpaper_handlers::MockGooglePhotosEnabledFetcher>*>(
        delegate()->GetOrCreateGooglePhotosEnabledFetcher());

    EXPECT_CALL(*google_photos_enabled_fetcher, AddRequestAndStartIfNecessary)
        .Times(num_fetches);

    for (size_t i = 0; i < num_fetches; ++i) {
      base::test::TestFuture<GooglePhotosEnablementState> future;
      wallpaper_provider_remote()->FetchGooglePhotosEnabled(
          future.GetCallback());
      EXPECT_EQ(GooglePhotosEnablementState::kEnabled, future.Take());
    }
  }

  void AddToAlbumIdMap(const std::string& album_id,
                       const std::string& dedup_key) {
    std::set<std::string> dedup_keys;
    dedup_keys.insert(dedup_key);
    delegate()->album_id_dedup_key_map_.insert({album_id, dedup_keys});
  }

  // The number of times to start each idempotent API query.
  static constexpr size_t kNumFetches = 2;
  // Resume token value used across several tests.
  const std::string kResumeToken = "token";
};

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest, FetchAlbums) {
  // Mock a fetcher for the albums query.
  auto* const google_photos_albums_fetcher = static_cast<
      ::testing::NiceMock<wallpaper_handlers::MockGooglePhotosAlbumsFetcher>*>(
      delegate()->GetOrCreateGooglePhotosAlbumsFetcher());

  auto* const google_photos_shared_albums_fetcher =
      static_cast<::testing::NiceMock<
          wallpaper_handlers::MockGooglePhotosSharedAlbumsFetcher>*>(
          delegate()->GetOrCreateGooglePhotosSharedAlbumsFetcher());

  // Simulate the client making multiple requests for the same information to
  // test that all callbacks for that query are called.
  EXPECT_CALL(*google_photos_albums_fetcher,
              AddRequestAndStartIfNecessary(std::make_optional(kResumeToken),
                                            ::testing::_))
      .Times(kNumFetches);

  EXPECT_CALL(*google_photos_shared_albums_fetcher,
              AddRequestAndStartIfNecessary(std::make_optional(kResumeToken),
                                            ::testing::_))
      .Times(kNumFetches);

  // Test fetching Google Photos albums after fetching the enterprise setting.
  // Requests should be made if and only if the Google Photos wallpaper
  // integration is enabled.
  FetchGooglePhotosEnabled();
  for (size_t i = 0; i < kNumFetches; ++i) {
    base::test::TestFuture<
        ash::personalization_app::mojom::FetchGooglePhotosAlbumsResponsePtr>
        albums_response;
    wallpaper_provider_remote()->FetchGooglePhotosAlbums(
        kResumeToken, albums_response.GetCallback());
    EXPECT_TRUE(albums_response.Take()->albums.has_value());

    base::test::TestFuture<
        ash::personalization_app::mojom::FetchGooglePhotosAlbumsResponsePtr>
        shared_albums_response;
    wallpaper_provider_remote()->FetchGooglePhotosSharedAlbums(
        kResumeToken, shared_albums_response.GetCallback());
    EXPECT_TRUE(shared_albums_response.Take()->albums.has_value());
  }
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       FetchAlbumsBeforeEnabled) {
  // Mock a fetcher for the albums query.
  auto* const google_photos_albums_fetcher = static_cast<
      ::testing::NiceMock<wallpaper_handlers::MockGooglePhotosAlbumsFetcher>*>(
      delegate()->GetOrCreateGooglePhotosAlbumsFetcher());

  // The albums fetcher should never be called.
  EXPECT_CALL(*google_photos_albums_fetcher, AddRequestAndStartIfNecessary)
      .Times(0);

  mojo::test::BadMessageObserver bad_message_observer;
  // Test fetching Google Photos albums before fetching the enterprise enabled
  // setting. No requests should be made.
  wallpaper_provider_remote()->FetchGooglePhotosAlbums(
      kResumeToken, base::BindLambdaForTesting(
                        [](mojom::FetchGooglePhotosAlbumsResponsePtr response) {
                          NOTREACHED_IN_MIGRATION();
                        }));
  EXPECT_EQ(
      "Cannot call `FetchGooglePhotosAlbums()` without confirming that the "
      "Google Photos enterprise setting is enabled.",
      bad_message_observer.WaitForBadMessage());
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest, FetchEnabled) {
  // Simulate the client making multiple requests for the same information to
  // test that all callbacks for that query are called.
  FetchGooglePhotosEnabled(kNumFetches);
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest, FetchPhotos) {
  // Mock a fetcher for the photos query.
  auto* const google_photos_photos_fetcher = static_cast<
      ::testing::NiceMock<wallpaper_handlers::MockGooglePhotosPhotosFetcher>*>(
      delegate()->GetOrCreateGooglePhotosPhotosFetcher());

  // Simulate the client making multiple requests for the same information to
  // test that all callbacks for that query are called.
  const std::string item_id = "itemId";
  const std::string album_id = "albumId";
  EXPECT_CALL(*google_photos_photos_fetcher,
              AddRequestAndStartIfNecessary(
                  std::make_optional(item_id), std::make_optional(album_id),
                  std::make_optional(kResumeToken), false, ::testing::_))
      .Times(kNumFetches);

  // Test fetching Google Photos photos after fetching the enterprise setting.
  // Requests should be made if and only if the Google Photos wallpaper
  // integration is enabled.
  FetchGooglePhotosEnabled();
  for (size_t i = 0; i < kNumFetches; ++i) {
    base::test::TestFuture<
        ash::personalization_app::mojom::FetchGooglePhotosPhotosResponsePtr>
        photos_response;
    wallpaper_provider_remote()->FetchGooglePhotosPhotos(
        item_id, album_id, kResumeToken, photos_response.GetCallback());
    EXPECT_TRUE(photos_response.Take()->photos.has_value());
  }
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       FetchPhotosBeforeEnabled) {
  auto* const google_photos_photos_fetcher = static_cast<
      ::testing::NiceMock<wallpaper_handlers::MockGooglePhotosPhotosFetcher>*>(
      delegate()->GetOrCreateGooglePhotosPhotosFetcher());

  const std::string item_id = "itemId";
  const std::string album_id = "albumId";

  EXPECT_CALL(*google_photos_photos_fetcher, AddRequestAndStartIfNecessary)
      .Times(0);

  mojo::test::BadMessageObserver bad_message_observer;
  // Test fetching Google Photos photos before fetching the enterprise setting.
  // No requests should be made.
  wallpaper_provider_remote()->FetchGooglePhotosPhotos(
      item_id, album_id, kResumeToken,
      base::BindLambdaForTesting(
          [](mojom::FetchGooglePhotosPhotosResponsePtr response) {
            NOTREACHED_IN_MIGRATION();
          }));
  EXPECT_EQ(
      "Cannot call `FetchGooglePhotosPhotos()` without confirming that the "
      "Google Photos enterprise setting is enabled.",
      bad_message_observer.WaitForBadMessage());
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       SelectGooglePhotosPhoto) {
  test_wallpaper_controller()->ClearCounts();
  const std::string photo_id = "OmnisVirLupus";

  // Test selecting a wallpaper after fetching the enterprise setting.
  FetchGooglePhotosEnabled();
  base::test::TestFuture<bool> success_future;
  wallpaper_provider_remote()->SelectGooglePhotosPhoto(
      photo_id, ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
      /*preview_mode=*/false, success_future.GetCallback());
  EXPECT_TRUE(success_future.Take());

  EXPECT_EQ(1,
            test_wallpaper_controller()->set_google_photos_wallpaper_count());
  EXPECT_TRUE(
      test_wallpaper_controller()
          ->wallpaper_info()
          .value_or(ash::WallpaperInfo())
          .MatchesSelection(ash::WallpaperInfo(
              {GetTestAccountId(), photo_id, /*daily_refresh_enabled=*/false,
               ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
               /*preview_mode=*/false, "dedup_key"})));
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       SelectGooglePhotosPhotoBeforeEnabled) {
  ASSERT_FALSE(test_wallpaper_controller()->wallpaper_info().has_value());

  mojo::test::BadMessageObserver bad_message_observer;
  // Test selecting a wallpaper before fetching the enterprise setting.
  wallpaper_provider_remote()->SelectGooglePhotosPhoto(
      "OmnisVirLupus", ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
      /*preview_mode=*/false, base::BindLambdaForTesting([](bool success) {
        NOTREACHED_IN_MIGRATION();
      }));
  EXPECT_EQ(
      "Cannot call `SelectGooglePhotosPhoto()` without confirming that the "
      "Google Photos enterprise setting is enabled.",
      bad_message_observer.WaitForBadMessage());

  EXPECT_EQ(0,
            test_wallpaper_controller()->set_google_photos_wallpaper_count());
  EXPECT_FALSE(test_wallpaper_controller()->wallpaper_info().has_value());
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       SelectGooglePhotosPhotoBanned) {
  test_wallpaper_controller()->set_can_set_user_wallpaper(false);
  mojo::test::BadMessageObserver bad_message_observer;
  wallpaper_provider_remote()->SelectGooglePhotosPhoto(
      "OmnisVirLupus", ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
      /*preview_mode=*/false, base::BindLambdaForTesting([](bool success) {
        NOTREACHED_IN_MIGRATION();
      }));
  EXPECT_EQ(
      "Cannot call `SelectGooglePhotosPhoto()` without confirming that the "
      "Google Photos enterprise setting is enabled.",
      bad_message_observer.WaitForBadMessage());
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       SelectGooglePhotosAlbumWithoutEnterprise) {
  // Test selecting an album before fetching the enterprise setting.
  mojo::test::BadMessageObserver bad_message_observer;
  wallpaper_provider_remote()->SelectGooglePhotosAlbum(
      "OmnisVirLupus", base::BindLambdaForTesting(
                           [](bool success) { NOTREACHED_IN_MIGRATION(); }));
  EXPECT_EQ(
      "Rejected attempt to set Google Photos wallpaper while disabled via "
      "enterprise setting.",
      bad_message_observer.WaitForBadMessage());
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       SelectGooglePhotosAlbum) {
  test_wallpaper_controller()->ClearCounts();
  EXPECT_EQ(test_wallpaper_controller()->GetGooglePhotosDailyRefreshAlbumId(
                GetTestAccountId()),
            "");
  const std::string album_id = "OmnisVirLupus";

  // Test selecting an album after fetching the enterprise setting.
  FetchGooglePhotosEnabled();
  base::test::TestFuture<bool> success_future;
  wallpaper_provider_remote()->SelectGooglePhotosAlbum(
      album_id, success_future.GetCallback());
  EXPECT_TRUE(success_future.Take());
  EXPECT_EQ(test_wallpaper_controller()->GetGooglePhotosDailyRefreshAlbumId(
                GetTestAccountId()),
            album_id);
  EXPECT_EQ(
      test_wallpaper_controller()->get_update_daily_refresh_wallpaper_count(),
      1);
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       SelectGooglePhotosAlbumBanned) {
  test_wallpaper_controller()->set_can_set_user_wallpaper(false);
  FetchGooglePhotosEnabled();
  mojo::test::BadMessageObserver bad_message_observer;
  wallpaper_provider_remote()->SelectGooglePhotosAlbum(
      "OmnisVirLupus", base::BindLambdaForTesting(
                           [](bool success) { NOTREACHED_IN_MIGRATION(); }));
  EXPECT_EQ("Invalid request to select google photos album",
            bad_message_observer.WaitForBadMessage());
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       SelectGooglePhotosAlbum_WithoutForcingRefresh) {
  FetchGooglePhotosEnabled();
  test_wallpaper_controller()->ClearCounts();
  const std::string album_id = "OmnisVirLupus";
  const std::string photo_id = "DummyPhotoId";

  // Test selecting a photo.
  {
    base::test::TestFuture<bool> success_future;
    wallpaper_provider_remote()->SelectGooglePhotosPhoto(
        photo_id, ash::WallpaperLayout::WALLPAPER_LAYOUT_CENTER_CROPPED,
        /*preview_mode=*/false, success_future.GetCallback());
    EXPECT_TRUE(success_future.Take());
  }

  // Add the same photo to a google photos album, and select that album as daily
  // refresh source.
  {
    AddToAlbumIdMap(album_id, photo_id);
    test_wallpaper_controller()->add_dedup_key_to_wallpaper_info(photo_id);
    base::test::TestFuture<bool> success_future;
    wallpaper_provider_remote()->SelectGooglePhotosAlbum(
        album_id, success_future.GetCallback());
    EXPECT_TRUE(success_future.Take());
    // Still equal to 0 since no need to update - already selected an image from
    // the album.
    EXPECT_EQ(
        test_wallpaper_controller()->get_update_daily_refresh_wallpaper_count(),
        0);
  }
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       UnselectGooglePhotosAlbum) {
  const std::string album_id = "OmnisVirLupus";
  const std::vector<std::string> photo_ids = {"id_zero", "id_one"};
  // Two photos in this album.
  for (const auto& photo_id : photo_ids) {
    AddToAlbumIdMap(album_id, photo_id);
  }

  FetchGooglePhotosEnabled();
  {
    // Select the album.
    base::test::TestFuture<bool> success_future;
    wallpaper_provider_remote()->SelectGooglePhotosAlbum(
        album_id, success_future.GetCallback());
    EXPECT_TRUE(success_future.Take());
    EXPECT_EQ(1, test_wallpaper_controller()
                     ->get_update_daily_refresh_wallpaper_count());
  }

  test_wallpaper_controller()->ClearCounts();
  {
    // Unselect the album.
    base::test::TestFuture<bool> success_future;
    wallpaper_provider_remote()->SelectGooglePhotosAlbum(
        std::string(), success_future.GetCallback());
    EXPECT_TRUE(success_future.Take());
    EXPECT_EQ(0, test_wallpaper_controller()
                     ->get_update_daily_refresh_wallpaper_count());
  }
}

TEST_F(PersonalizationAppWallpaperProviderImplGooglePhotosTest,
       UpdateDailyRefresh) {
  const std::string album_id = "OmnisVirLupus";
  const std::vector<std::string> photo_ids = {"id_zero", "id_one"};
  // Two photos in this album.
  for (const auto& photo_id : photo_ids) {
    AddToAlbumIdMap(album_id, photo_id);
  }

  FetchGooglePhotosEnabled();
  {
    base::test::TestFuture<bool> success_future;
    wallpaper_provider_remote()->SelectGooglePhotosAlbum(
        album_id, success_future.GetCallback());
    EXPECT_TRUE(success_future.Take());

    EXPECT_EQ(1, test_wallpaper_controller()
                     ->get_update_daily_refresh_wallpaper_count());
  }

  {
    base::test::TestFuture<bool> success_future;
    wallpaper_provider_remote()->UpdateDailyRefreshWallpaper(
        success_future.GetCallback());
    EXPECT_TRUE(success_future.Take());
    EXPECT_EQ(2, test_wallpaper_controller()
                     ->get_update_daily_refresh_wallpaper_count());
  }
}

}  // namespace ash::personalization_app