chromium/ash/quick_pair/repository/fast_pair/device_image_store_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 "ash/quick_pair/repository/fast_pair/device_image_store.h"

#include <optional>

#include "ash/quick_pair/proto/fastpair.pb.h"
#include "ash/quick_pair/proto/fastpair_data.pb.h"
#include "ash/quick_pair/repository/fast_pair/device_metadata.h"
#include "ash/quick_pair/repository/fast_pair/mock_fast_pair_image_decoder.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/functional/callback_helpers.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "chromeos/ash/services/bluetooth_config/public/cpp/device_image_info.h"
#include "components/prefs/pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/gfx/image/image_unittest_util.h"

namespace ash::quick_pair {

namespace {

using ::base::test::RunOnceCallback;
using bluetooth_config::DeviceImageInfo;
using ::testing::_;

constexpr char kTestModelId[] = "ABC123";
constexpr char kTestLeftBudUrl[] = "left_bud";
constexpr char kTestRightBudUrl[] = "right_bud";
constexpr char kTestCaseUrl[] = "case";

}  // namespace

class DeviceImageStoreTest : public AshTestBase {
 public:
  void SetUp() override {
    AshTestBase::SetUp();

    nearby::fastpair::TrueWirelessHeadsetImages true_wireless_images;
    true_wireless_images.set_left_bud_url(kTestLeftBudUrl);
    true_wireless_images.set_right_bud_url(kTestRightBudUrl);
    true_wireless_images.set_case_url(kTestCaseUrl);

    nearby::fastpair::Device device;
    *device.mutable_true_wireless_images() = true_wireless_images;

    nearby::fastpair::GetObservedDeviceResponse response;
    *response.mutable_device() = device;

    test_image_ = gfx::test::CreateImage(100, 100);
    device_metadata_ = std::make_unique<DeviceMetadata>(response, test_image_);

    mock_decoder_ = std::make_unique<MockFastPairImageDecoder>();
    // On call to DecodeImage, run the third argument callback with test_image_.
    ON_CALL(*mock_decoder_, DecodeImageFromUrl(_, _, _))
        .WillByDefault(base::test::RunOnceCallbackRepeatedly<2>(test_image_));

    device_image_store_ =
        std::make_unique<DeviceImageStore>(mock_decoder_.get());
  }

 protected:
  std::unique_ptr<DeviceMetadata> device_metadata_;
  gfx::Image test_image_;
  std::unique_ptr<MockFastPairImageDecoder> mock_decoder_;
  std::unique_ptr<DeviceImageStore> device_image_store_;
};

TEST_F(DeviceImageStoreTest, FetchDeviceImagesValidDefaultOnly) {
  base::MockCallback<DeviceImageStore::FetchDeviceImagesCallback> callback;
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kDefault,
                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kLeftBud,
                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kRightBud,
                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kCase,
                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
      .Times(1);

  // Only include the default image in the metadata.
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata default_only_metadata =
      DeviceMetadata(std::move(response), test_image_);
  device_image_store_->FetchDeviceImages(kTestModelId, &default_only_metadata,
                                         callback.Get());
}

TEST_F(DeviceImageStoreTest, FetchDeviceImagesInvalidDefaultOnly) {
  base::MockCallback<DeviceImageStore::FetchDeviceImagesCallback> callback;
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kDefault,
                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kLeftBud,
                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kRightBud,
                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kCase,
                         DeviceImageStore::FetchDeviceImagesResult::kSkipped)))
      .Times(1);

  // Include only an empty default image in the metadata.
  nearby::fastpair::GetObservedDeviceResponse response;
  DeviceMetadata empty_image_metadata =
      DeviceMetadata(std::move(response), gfx::Image());
  device_image_store_->FetchDeviceImages(kTestModelId, &empty_image_metadata,
                                         callback.Get());
}

TEST_F(DeviceImageStoreTest, FetchDeviceImagesValidTrueWireless) {
  base::MockCallback<DeviceImageStore::FetchDeviceImagesCallback> callback;
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kDefault,
                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kLeftBud,
                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kRightBud,
                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kCase,
                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
      .Times(1);

  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
                                         callback.Get());
}

TEST_F(DeviceImageStoreTest, FetchDeviceImagesInvalidTrueWireless) {
  base::MockCallback<DeviceImageStore::FetchDeviceImagesCallback> callback;
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kDefault,
                         DeviceImageStore::FetchDeviceImagesResult::kSuccess)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kLeftBud,
                         DeviceImageStore::FetchDeviceImagesResult::kFailure)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kRightBud,
                         DeviceImageStore::FetchDeviceImagesResult::kFailure)))
      .Times(1);
  EXPECT_CALL(
      callback,
      Run(std::make_pair(DeviceImageStore::DeviceImageType::kCase,
                         DeviceImageStore::FetchDeviceImagesResult::kFailure)))
      .Times(1);

  // Simulate an error during download/decode by returning an empty image.
  ON_CALL(*mock_decoder_, DecodeImageFromUrl(_, _, _))
      .WillByDefault(base::test::RunOnceCallbackRepeatedly<2>(gfx::Image()));
  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
                                         callback.Get());
}

TEST_F(DeviceImageStoreTest, PersistDeviceImagesValid) {
  // First, save the device images to memory.
  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
                                         base::DoNothing());
  EXPECT_TRUE(device_image_store_->PersistDeviceImages(kTestModelId));

  // Validate that the images are persisted to prefs.
  PrefService* local_state = Shell::Get()->local_state();
  const base::Value::Dict& device_image_store_dict =
      local_state->GetDict(DeviceImageStore::kDeviceImageStorePref);
  const base::Value::Dict* images_dict =
      device_image_store_dict.FindDict(kTestModelId);
  EXPECT_TRUE(images_dict);
  const std::string* persisted_image = images_dict->FindString("Default");
  std::string expected_encoded_image =
      webui::GetBitmapDataUrl(test_image_.AsBitmap());
  EXPECT_EQ(*persisted_image, expected_encoded_image);
}

TEST_F(DeviceImageStoreTest, PersistDeviceImagesInvalidModelId) {
  // Don't save the device images to memory.
  EXPECT_FALSE(device_image_store_->PersistDeviceImages(kTestModelId));
}

TEST_F(DeviceImageStoreTest, EvictDeviceImagesValid) {
  // First, persist the device images to disk.
  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
                                         base::DoNothing());
  EXPECT_TRUE(device_image_store_->PersistDeviceImages(kTestModelId));
  EXPECT_TRUE(device_image_store_->EvictDeviceImages(kTestModelId));

  // Validate that the images are evicted from prefs.
  PrefService* local_state = Shell::Get()->local_state();
  const base::Value::Dict& device_image_store_dict =
      local_state->GetDict(DeviceImageStore::kDeviceImageStorePref);
  const base::Value* images_dict = device_image_store_dict.Find(kTestModelId);
  EXPECT_FALSE(images_dict);
}

TEST_F(DeviceImageStoreTest, EvictDeviceImagesInvalidModelId) {
  // Don't persist the device images to disk.
  EXPECT_FALSE(device_image_store_->EvictDeviceImages(kTestModelId));
}

TEST_F(DeviceImageStoreTest, EvictDeviceImagesInvalidDoubleFree) {
  // First, persist the device images to disk.
  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
                                         base::DoNothing());
  EXPECT_TRUE(device_image_store_->PersistDeviceImages(kTestModelId));
  EXPECT_TRUE(device_image_store_->EvictDeviceImages(kTestModelId));

  // The second evict should fail.
  EXPECT_FALSE(device_image_store_->EvictDeviceImages(kTestModelId));
}

TEST_F(DeviceImageStoreTest, GetImagesForDeviceModelValid) {
  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
                                         base::DoNothing());

  std::optional<DeviceImageInfo> images =
      device_image_store_->GetImagesForDeviceModel(kTestModelId);
  EXPECT_TRUE(images);

  const std::string default_image = images->default_image();
  EXPECT_FALSE(default_image.empty());
  EXPECT_EQ(default_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));

  const std::string left_bud_image = images->left_bud_image();
  EXPECT_FALSE(left_bud_image.empty());
  EXPECT_EQ(left_bud_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));

  const std::string right_bud_image = images->right_bud_image();
  EXPECT_FALSE(right_bud_image.empty());
  EXPECT_EQ(right_bud_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));

  const std::string case_image = images->case_image();
  EXPECT_FALSE(case_image.empty());
  EXPECT_EQ(case_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
}

TEST_F(DeviceImageStoreTest, GetImagesForDeviceModelInvalidUninitialized) {
  // Don't initialize the dictionary with any results.
  std::optional<DeviceImageInfo> images =
      device_image_store_->GetImagesForDeviceModel(kTestModelId);
  EXPECT_FALSE(images);
}

TEST_F(DeviceImageStoreTest, GetImagesForDeviceModelInvalidNotAdded) {
  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
                                         base::DoNothing());
  // Look for a model ID that wasn't added.
  std::optional<DeviceImageInfo> images =
      device_image_store_->GetImagesForDeviceModel("DEF456");
  EXPECT_FALSE(images);
}

TEST_F(DeviceImageStoreTest, LoadPersistedImagesFromPrefs) {
  // First, persist the device images to disk.
  device_image_store_->FetchDeviceImages(kTestModelId, device_metadata_.get(),
                                         base::DoNothing());
  device_image_store_->PersistDeviceImages(kTestModelId);

  // A new/restarted DeviceImageStore instance should load persisted images
  // from prefs.
  DeviceImageStore new_device_image_store(mock_decoder_.get());
  std::optional<DeviceImageInfo> images =
      new_device_image_store.GetImagesForDeviceModel(kTestModelId);
  EXPECT_TRUE(images);

  const std::string default_image = images->default_image();
  EXPECT_FALSE(default_image.empty());
  EXPECT_EQ(default_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));

  const std::string left_bud_image = images->left_bud_image();
  EXPECT_FALSE(left_bud_image.empty());
  EXPECT_EQ(left_bud_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));

  const std::string right_bud_image = images->right_bud_image();
  EXPECT_FALSE(right_bud_image.empty());
  EXPECT_EQ(right_bud_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));

  const std::string case_image = images->case_image();
  EXPECT_FALSE(case_image.empty());
  EXPECT_EQ(case_image, webui::GetBitmapDataUrl(test_image_.AsBitmap()));
}

}  // namespace ash::quick_pair