chromium/chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.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/wallpaper_handlers/mock_wallpaper_handlers.h"

#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/wallpaper/wallpaper_constants.h"
#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
#include "ash/webui/personalization_app/proto/backdrop_wallpaper.pb.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/ash/wallpaper_handlers/wallpaper_handlers.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace wallpaper_handlers {

namespace {

constexpr char kDataUrlPrefix[] = "data:image/png;base64,";
constexpr uint64_t kTimeOfDayStartingAssetId = 88;
constexpr int kMaxImageNum = 5;
constexpr char kDarklightCollectionId[] = "dark_light_collection";

// Images used in test must have a unique `asset_id` for Personalization App to
// function correctly. Make sure that the fake `collection_id` values used in
// browser tests map to unique `asset_id` values.
int GetStartingAssetId(const std::string& collection_id) {
  if (collection_id ==
      ash::wallpaper_constants::kTimeOfDayWallpaperCollectionId) {
    return kTimeOfDayStartingAssetId;
  } else if (collection_id == "fake_collection_id_0") {
    return 20;
  } else if (collection_id == "fake_collection_id_1") {
    return 30;
  } else if (collection_id == "fake_collection_id_2") {
    return 40;
  } else if (collection_id == kDarklightCollectionId) {
    return 50;
  } else {
    return 100;
  }
}

backdrop::Image_ImageType GetImageType(int asset_id) {
  switch (asset_id) {
    case kTimeOfDayStartingAssetId:
      return backdrop::Image_ImageType_IMAGE_TYPE_MORNING_MODE;
    case kTimeOfDayStartingAssetId + 1:
      return backdrop::Image_ImageType_IMAGE_TYPE_LIGHT_MODE;
    case kTimeOfDayStartingAssetId + 2:
      return backdrop::Image_ImageType_IMAGE_TYPE_LATE_AFTERNOON_MODE;
    case kTimeOfDayStartingAssetId + 3:
      return backdrop::Image_ImageType_IMAGE_TYPE_DARK_MODE;
    case kTimeOfDayStartingAssetId + 4:
      return backdrop::Image_ImageType_IMAGE_TYPE_PREVIEW_MODE;
    default:
      return backdrop::Image_ImageType_IMAGE_TYPE_UNKNOWN;
  }
}

backdrop::Collection GenerateFakeBackdropCollection(int number) {
  backdrop::Collection collection;
  collection.set_collection_id(
      base::StringPrintf("fake_collection_id_%i", number));
  collection.set_collection_name(
      base::StringPrintf("Test Collection %i", number));
  backdrop::Image* image = collection.add_preview();
  // Needs a data url so that it loads.
  image->set_image_url(kDataUrlPrefix);
  return collection;
}

backdrop::Image GenerateFakeBackdropImage(const std::string& collection_id,
                                          int asset_id) {
  backdrop::Image image;
  image.set_asset_id(asset_id);
  image.set_image_url(kDataUrlPrefix + base::NumberToString(asset_id));
  for (auto line = 0; line < 2; line++) {
    image.add_attribution()->set_text(
        base::StringPrintf("fake_attribution_%s_asset_id_%i_line_%i",
                           collection_id.c_str(), asset_id, line));
  }
  if (collection_id ==
      ash::wallpaper_constants::kTimeOfDayWallpaperCollectionId) {
    image.set_unit_id(MockBackdropImageInfoFetcher::kTimeOfDayUnitId);
  } else {
    image.set_unit_id(asset_id);
  }
  image.set_image_type(GetImageType(asset_id));
  return image;
}

std::vector<backdrop::Image> GenerateDarkLightBackdropImagePair(
    const std::string& collection_id,
    int asset_id) {
  std::vector<backdrop::Image> pairs;
  const int unit_id = asset_id;
  {
    // Generates Light variant.
    backdrop::Image image;
    image.set_asset_id(asset_id);
    image.set_image_url(kDataUrlPrefix + base::NumberToString(asset_id));
    for (auto line = 0; line < 2; line++) {
      image.add_attribution()->set_text(
          base::StringPrintf("fake_attribution_%s_asset_id_%i_line_%i",
                             collection_id.c_str(), asset_id, line));
    }
    image.set_unit_id(unit_id);
    image.set_image_type(backdrop::Image_ImageType_IMAGE_TYPE_LIGHT_MODE);
    pairs.push_back(image);
  }
  {
    // Generates Dark variant.
    backdrop::Image image;
    const auto dark_asset_id = asset_id + 1;
    image.set_asset_id(dark_asset_id);
    image.set_image_url(kDataUrlPrefix + base::NumberToString(dark_asset_id));
    for (auto line = 0; line < 2; line++) {
      image.add_attribution()->set_text(
          base::StringPrintf("fake_attribution_%s_asset_id_%i_line_%i",
                             collection_id.c_str(), dark_asset_id, line));
    }
    image.set_unit_id(unit_id);
    image.set_image_type(backdrop::Image_ImageType_IMAGE_TYPE_DARK_MODE);
    pairs.push_back(image);
  }
  return pairs;
}

ash::personalization_app::mojom::GooglePhotosPhotoPtr
CreateFakeGooglePhotosPhoto(const std::string& id) {
  auto photo = ash::personalization_app::mojom::GooglePhotosPhoto::New();
  photo->id = id;
  photo->name = id;
  photo->dedup_key = id;
  photo->url = GURL(kDataUrlPrefix + id);
  return photo;
}

ash::personalization_app::mojom::FetchGooglePhotosPhotosResponsePtr
CreateFakeGooglePhotosPhotosResponse(
    const std::optional<std::string>& item_id) {
  auto response =
      ash::personalization_app::mojom::FetchGooglePhotosPhotosResponse::New();
  std::vector<ash::personalization_app::mojom::GooglePhotosPhotoPtr> photos;
  if (item_id.has_value()) {
    // Request for a specific photo with matching id.
    photos.push_back(CreateFakeGooglePhotosPhoto(item_id.value()));
  } else {
    // Request for list of photos.
    for (auto i = 0; i < 3; i++) {
      auto photo = ash::personalization_app::mojom::GooglePhotosPhoto::New();
      std::string id = base::StringPrintf("fake_google_photos_photo_id_%i", i);
      photo->id = id;
      photo->name = id;
      photo->dedup_key = id;
      photo->url = GURL(kDataUrlPrefix + base::NumberToString(i));
      photos.push_back(std::move(photo));
    }
  }

  response->photos = std::move(photos);
  response->resume_token = std::nullopt;
  return response;
}

ash::personalization_app::mojom::FetchGooglePhotosAlbumsResponsePtr
CreateFakeGooglePhotosSharedAlbumsResponse() {
  std::vector<ash::personalization_app::mojom::GooglePhotosAlbumPtr> result;
  for (int i = 0; i < 3; i++) {
    auto album = ash::personalization_app::mojom::GooglePhotosAlbum::New();
    std::string id =
        base::StringPrintf("fake_google_photos_shared_album_id_%i", i);
    album->id = id;
    album->is_shared = true;
    // Shared albums always have `photo_count == 0` due to technical debt on
    // server side.
    album->photo_count = 0;
    album->preview = GURL(kDataUrlPrefix + base::NumberToString(i));
    album->timestamp = base::Time::Now();
    album->title = id;
    result.push_back(std::move(album));
  }
  return ash::personalization_app::mojom::FetchGooglePhotosAlbumsResponse::New(
      std::move(result), std::nullopt);
}

}  // namespace

MockBackdropCollectionInfoFetcher::MockBackdropCollectionInfoFetcher() {
  ON_CALL(*this, Start).WillByDefault([](OnCollectionsInfoFetched callback) {
    std::vector<backdrop::Collection> collections;
    {
      if (ash::features::IsTimeOfDayWallpaperEnabled()) {
        // Generate a fake time of day collection.
        backdrop::Collection time_of_day_collection;
        time_of_day_collection.set_collection_id(
            ash::wallpaper_constants::kTimeOfDayWallpaperCollectionId);
        time_of_day_collection.set_collection_name("Dawn to dark");
        time_of_day_collection.set_description_content(
            "Dawn to dark collection description");
        backdrop::Image* image = time_of_day_collection.add_preview();
        // Needs a data url so that it loads.
        image->set_image_url(kDataUrlPrefix);
        collections.push_back(std::move(time_of_day_collection));
      }
    }
    {
      // Generate a dark light collection.
      backdrop::Collection dark_light_collection;
      dark_light_collection.set_collection_id(kDarklightCollectionId);
      dark_light_collection.set_collection_name("Dark Light collection");
      dark_light_collection.set_description_content(
          "Dark Light collection description");
      backdrop::Image* image = dark_light_collection.add_preview();
      // Needs a data url so that it loads.
      image->set_image_url(kDataUrlPrefix);
      collections.push_back(std::move(dark_light_collection));
    }
    for (auto i = 0; i < 3; i++) {
      collections.push_back(GenerateFakeBackdropCollection(i));
    }
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), /*success=*/true, collections));
  });
}

MockBackdropCollectionInfoFetcher::~MockBackdropCollectionInfoFetcher() =
    default;

MockBackdropImageInfoFetcher::MockBackdropImageInfoFetcher(
    const std::string& collection_id)
    : BackdropImageInfoFetcher(collection_id), collection_id_(collection_id) {
  ON_CALL(*this, Start)
      .WillByDefault([&collection_id =
                          collection_id_](OnImagesInfoFetched callback) {
        std::vector<backdrop::Image> images;
        const auto starting_asset_id = GetStartingAssetId(collection_id);
        if (collection_id == kDarklightCollectionId) {
          for (auto asset_id = starting_asset_id;
               asset_id < starting_asset_id + kMaxImageNum * 2; asset_id += 2) {
            std::vector<backdrop::Image> pairs =
                GenerateDarkLightBackdropImagePair(collection_id, asset_id);
            images.push_back(pairs[0]);
            images.push_back(pairs[1]);
          }
        } else {
          for (auto asset_id = starting_asset_id;
               asset_id < starting_asset_id + kMaxImageNum; asset_id++) {
            images.push_back(
                GenerateFakeBackdropImage(collection_id, asset_id));
          }
        }
        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
            FROM_HERE, base::BindOnce(std::move(callback), /*success=*/true,
                                      collection_id, images));
      });
}

MockBackdropImageInfoFetcher::~MockBackdropImageInfoFetcher() = default;

MockBackdropSurpriseMeImageFetcher::MockBackdropSurpriseMeImageFetcher(
    const std::string& collection_id)
    : BackdropSurpriseMeImageFetcher(collection_id, /*resume_token=*/""),
      collection_id_(collection_id) {
  ON_CALL(*this, Start)
      .WillByDefault([&collection_id = collection_id_,
                      &id_incrementer =
                          id_incrementer_](OnSurpriseMeImageFetched callback) {
        const auto starting_asset_id = GetStartingAssetId(collection_id);
        if (collection_id == kDarklightCollectionId) {
          id_incrementer = (id_incrementer + 2) % (2 * kMaxImageNum);
          const auto asset_id = starting_asset_id + id_incrementer;
          std::vector<backdrop::Image> pairs =
              GenerateDarkLightBackdropImagePair(collection_id, asset_id);
          base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE, base::BindOnce(std::move(callback), /*success=*/true,
                                        pairs[0], /*new_resume_token=*/""));
        } else {
          id_incrementer = (id_incrementer + 1) % kMaxImageNum;
          const auto asset_id = starting_asset_id + id_incrementer;
          base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE,
              base::BindOnce(std::move(callback), /*success=*/true,
                             GenerateFakeBackdropImage(collection_id, asset_id),
                             /*new_resume_token=*/""));
        }
      });
}

MockBackdropSurpriseMeImageFetcher::~MockBackdropSurpriseMeImageFetcher() =
    default;

MockGooglePhotosAlbumsFetcher::MockGooglePhotosAlbumsFetcher(Profile* profile)
    : GooglePhotosAlbumsFetcher(profile) {
  using ash::personalization_app::mojom::FetchGooglePhotosAlbumsResponse;
  using ash::personalization_app::mojom::GooglePhotosAlbumPtr;

  ON_CALL(*this, AddRequestAndStartIfNecessary)
      .WillByDefault(
          [](const std::optional<std::string>& resume_token,
             base::OnceCallback<void(GooglePhotosAlbumsCbkArgs)> callback) {
            auto response = FetchGooglePhotosAlbumsResponse::New(
                std::vector<GooglePhotosAlbumPtr>(), std::nullopt);
            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
                FROM_HERE,
                base::BindOnce(std::move(callback), std::move(response)));
          });

  ON_CALL(*this, ParseResponse)
      .WillByDefault([this](const base::Value::Dict* response) {
        return GooglePhotosAlbumsFetcher::ParseResponse(response);
      });
}

MockGooglePhotosAlbumsFetcher::~MockGooglePhotosAlbumsFetcher() = default;

std::optional<size_t> MockGooglePhotosAlbumsFetcher::GetResultCount(
    const GooglePhotosAlbumsCbkArgs& result) {
  return GooglePhotosAlbumsFetcher::GetResultCount(result);
}

MockGooglePhotosSharedAlbumsFetcher::MockGooglePhotosSharedAlbumsFetcher(
    Profile* profile)
    : GooglePhotosSharedAlbumsFetcher(profile) {
  using ash::personalization_app::mojom::FetchGooglePhotosAlbumsResponse;
  using ash::personalization_app::mojom::GooglePhotosAlbumPtr;

  ON_CALL(*this, AddRequestAndStartIfNecessary)
      .WillByDefault(
          [](const std::optional<std::string>& resume_token,
             base::OnceCallback<void(ash::personalization_app::mojom::
                                         FetchGooglePhotosAlbumsResponsePtr)>
                 callback) {
            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
                FROM_HERE,
                base::BindOnce(std::move(callback),
                               CreateFakeGooglePhotosSharedAlbumsResponse()));
          });

  ON_CALL(*this, ParseResponse)
      .WillByDefault([this](const base::Value::Dict* response) {
        return GooglePhotosSharedAlbumsFetcher::ParseResponse(response);
      });
}

MockGooglePhotosSharedAlbumsFetcher::~MockGooglePhotosSharedAlbumsFetcher() =
    default;

std::optional<size_t> MockGooglePhotosSharedAlbumsFetcher::GetResultCount(
    const GooglePhotosAlbumsCbkArgs& result) {
  return GooglePhotosSharedAlbumsFetcher::GetResultCount(result);
}

MockGooglePhotosEnabledFetcher::MockGooglePhotosEnabledFetcher(Profile* profile)
    : GooglePhotosEnabledFetcher(profile) {
  ON_CALL(*this, AddRequestAndStartIfNecessary)
      .WillByDefault(
          [](base::OnceCallback<void(GooglePhotosEnablementState)> callback) {
            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
                FROM_HERE,
                base::BindOnce(std::move(callback),
                               GooglePhotosEnablementState::kEnabled));
          });

  ON_CALL(*this, ParseResponse)
      .WillByDefault([this](const base::Value::Dict* response) {
        return GooglePhotosEnabledFetcher::ParseResponse(response);
      });
}

MockGooglePhotosEnabledFetcher::~MockGooglePhotosEnabledFetcher() = default;

std::optional<size_t> MockGooglePhotosEnabledFetcher::GetResultCount(
    const GooglePhotosEnablementState& result) {
  return GooglePhotosEnabledFetcher::GetResultCount(result);
}

MockGooglePhotosPhotosFetcher::MockGooglePhotosPhotosFetcher(Profile* profile)
    : GooglePhotosPhotosFetcher(profile) {
  using ash::personalization_app::mojom::FetchGooglePhotosPhotosResponse;
  using ash::personalization_app::mojom::GooglePhotosPhotoPtr;

  ON_CALL(*this, AddRequestAndStartIfNecessary)
      .WillByDefault(
          [](const std::optional<std::string>& item_id,
             const std::optional<std::string>& album_id,
             const std::optional<std::string>& resume_token, bool shuffle,
             base::OnceCallback<void(GooglePhotosPhotosCbkArgs)> callback) {
            base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
                FROM_HERE,
                base::BindOnce(std::move(callback),
                               CreateFakeGooglePhotosPhotosResponse(item_id)));
          });

  ON_CALL(*this, ParseResponse)
      .WillByDefault([this](const base::Value::Dict* response) {
        return GooglePhotosPhotosFetcher::ParseResponse(response);
      });
}

MockGooglePhotosPhotosFetcher::~MockGooglePhotosPhotosFetcher() = default;

std::optional<size_t> MockGooglePhotosPhotosFetcher::GetResultCount(
    const GooglePhotosPhotosCbkArgs& result) {
  return GooglePhotosPhotosFetcher::GetResultCount(result);
}

}  // namespace wallpaper_handlers