// Copyright 2019 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/notifications/scheduler/internal/scheduled_notification_manager.h"

#include <utility>
#include <vector>

#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/task_environment.h"
#include "base/uuid.h"
#include "chrome/browser/notifications/scheduler/internal/collection_store.h"
#include "chrome/browser/notifications/scheduler/internal/icon_store.h"
#include "chrome/browser/notifications/scheduler/internal/scheduler_config.h"
#include "chrome/browser/notifications/scheduler/public/notification_entry.h"
#include "chrome/browser/notifications/scheduler/public/notification_params.h"
#include "chrome/browser/notifications/scheduler/public/notification_scheduler_constant.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;
using Entries = std::vector<std::unique_ptr<notifications::NotificationEntry>>;

namespace notifications {
namespace {

const char kGuid[] = "test_guid_1234";
const char kNonExistentGuid[] = "guid_non_existent";
const char16_t kTitle[] = u"test_title";
const char kSmallIconUuid[] = "test_small_icon_uuid";
const char kLargeIconUuid[] = "test_large_icon_uuid";

NotificationEntry CreateNotificationEntry(SchedulerClientType type) {
  NotificationEntry entry(type,
                          base::Uuid::GenerateRandomV4().AsLowercaseString());
  entry.schedule_params.deliver_time_start = base::Time::Now() + base::Days(1);
  entry.schedule_params.deliver_time_end = base::Time::Now() + base::Days(2);
  return entry;
}

// Verifies notification entry is same as expected.
void VerifyNotificationEntry(const NotificationEntry* entry,
                             const NotificationEntry* expected) {
  if (expected == nullptr) {
    EXPECT_FALSE(entry);
    return;
  }

  EXPECT_TRUE(entry);
  EXPECT_EQ(*entry, *expected);
  const auto& entry_icons = entry->notification_data.icons;
  const auto& expected_icons = expected->notification_data.icons;
  for (const auto& icon : entry_icons) {
    auto icon_type = icon.first;
    EXPECT_TRUE(expected_icons.contains(icon_type));
    EXPECT_EQ(entry_icons.at(icon_type).bitmap.width(),
              expected_icons.at(icon_type).bitmap.width());
    EXPECT_EQ(entry_icons.at(icon_type).bitmap.height(),
              expected_icons.at(icon_type).bitmap.height());
  }
}

IconStore::IconTypeBundleMap CreateIcons() {
  IconStore::IconTypeBundleMap result;
  SkBitmap large_icon;
  large_icon.allocN32Pixels(12, 13);
  SkBitmap small_icon;
  large_icon.allocN32Pixels(3, 4);
  result.emplace(IconType::kLargeIcon, IconBundle(std::move(large_icon)));
  result.emplace(IconType::kSmallIcon, IconBundle(std::move(small_icon)));
  return result;
}

class MockNotificationStore : public CollectionStore<NotificationEntry> {
 public:
  MockNotificationStore() = default;
  MockNotificationStore(const MockNotificationStore&) = delete;
  MockNotificationStore& operator=(const MockNotificationStore&) = delete;

  MOCK_METHOD1(InitAndLoad,
               void(CollectionStore<NotificationEntry>::LoadCallback));
  MOCK_METHOD3(Add,
               void(const std::string&,
                    const NotificationEntry&,
                    base::OnceCallback<void(bool)>));
  MOCK_METHOD3(Update,
               void(const std::string&,
                    const NotificationEntry&,
                    base::OnceCallback<void(bool)>));
  MOCK_METHOD2(Delete,
               void(const std::string&, base::OnceCallback<void(bool)>));
};

class MockIconStore : public IconStore {
 public:
  MockIconStore() = default;
  MockIconStore(const MockIconStore&) = delete;
  MockIconStore& operator=(const MockIconStore&) = delete;

  MOCK_METHOD1(InitAndLoadKeys, void(IconStore::InitAndLoadKeysCallback));
  MOCK_METHOD2(LoadIcons,
               void(const std::vector<std::string>&,
                    IconStore::LoadIconsCallback));
  MOCK_METHOD2(AddIcons,
               void(IconStore::IconTypeBundleMap, IconStore::AddCallback));
  MOCK_METHOD2(DeleteIcons,
               void(const std::vector<std::string>&,
                    IconStore::UpdateCallback));
};

class ScheduledNotificationManagerTest : public testing::Test {
 public:
  ScheduledNotificationManagerTest()
      : notification_store_(nullptr), icon_store_(nullptr) {}
  ScheduledNotificationManagerTest(const ScheduledNotificationManagerTest&) =
      delete;
  ScheduledNotificationManagerTest& operator=(
      const ScheduledNotificationManagerTest&) = delete;
  ~ScheduledNotificationManagerTest() override = default;

  void SetUp() override {
    auto notification_store = std::make_unique<MockNotificationStore>();
    auto icon_store = std::make_unique<MockIconStore>();
    notification_store_ = notification_store.get();
    icon_store_ = icon_store.get();
    config_.notification_expiration = base::Days(1);
    manager_ = ScheduledNotificationManager::Create(
        std::move(notification_store), std::move(icon_store),
        {SchedulerClientType::kTest1, SchedulerClientType::kTest2}, config_);
  }

 protected:
  ScheduledNotificationManager* manager() { return manager_.get(); }
  MockNotificationStore* notification_store() { return notification_store_; }
  MockIconStore* icon_store() { return icon_store_; }
  const SchedulerConfig& config() const { return config_; }

  // Initializes the manager with predefined data in the store.
  void InitWithData(std::vector<NotificationEntry> data) {
    Entries entries;
    auto icon_keys = std::make_unique<std::vector<std::string>>();
    for (auto it = data.begin(); it != data.end(); ++it) {
      for (const auto& pair : it->icons_uuid) {
        icon_keys->emplace_back(pair.second);
      }
      auto entry_ptr = std::make_unique<NotificationEntry>(it->type, it->guid);
      *(entry_ptr.get()) = *it;
      entries.emplace_back(std::move(entry_ptr));
    }

    // Initialize the store and call the callback.
    EXPECT_CALL(*notification_store(), InitAndLoad(_))
        .WillOnce([&entries](base::OnceCallback<void(bool, Entries)> cb) {
          std::move(cb).Run(true, std::move(entries));
        });
    EXPECT_CALL(*icon_store(), InitAndLoadKeys(_))
        .WillOnce(
            [&icon_keys](
                base::OnceCallback<void(bool, IconStore::LoadedIconKeys)> cb) {
              std::move(cb).Run(true, std::move(icon_keys));
            });
    EXPECT_CALL(*icon_store(), DeleteIcons(_, _)).RetiresOnSaturation();

    base::RunLoop loop;
    manager()->Init(base::BindOnce(
        [](base::RepeatingClosure closure, bool success) {
          EXPECT_TRUE(success);
          std::move(closure).Run();
        },
        loop.QuitClosure()));
    loop.Run();
  }

  // Schedules a notification and wait until the callback is called.
  void ScheduleNotification(std::unique_ptr<NotificationParams> params,
                            bool expected_success) {
    base::RunLoop loop;
    auto schedule_callback = base::BindOnce(
        [](base::RepeatingClosure quit_closure, bool expected_success,
           bool success) {
          EXPECT_EQ(success, expected_success);
          quit_closure.Run();
        },
        loop.QuitClosure(), expected_success);
    manager()->ScheduleNotification(std::move(params),
                                    std::move(schedule_callback));
    loop.Run();
  }

  void DisplayNotification(const std::string& guid,
                           const NotificationEntry* expected_entry) {
    base::RunLoop loop;
    auto display_callback = base::BindOnce(
        [](base::RepeatingClosure quit_closure,
           const NotificationEntry* expected_entry,
           std::unique_ptr<NotificationEntry> entry) {
          VerifyNotificationEntry(entry.get(), expected_entry);
          quit_closure.Run();
        },
        loop.QuitClosure(), expected_entry);
    manager()->DisplayNotification(guid, std::move(display_callback));
    loop.Run();
  }

 private:
  base::test::TaskEnvironment task_environment_;
  raw_ptr<MockNotificationStore, DanglingUntriaged> notification_store_;
  raw_ptr<MockIconStore, DanglingUntriaged> icon_store_;
  std::vector<SchedulerClientType> clients_;
  std::unique_ptr<ScheduledNotificationManager> manager_;
  SchedulerConfig config_;
};

// Verify that error is received when notification database failed to
// initialize.
TEST_F(ScheduledNotificationManagerTest, NotificationDbInitFailed) {
  EXPECT_CALL(*notification_store(), InitAndLoad(_))
      .WillOnce([](base::OnceCallback<void(bool, Entries)> callback) {
        std::move(callback).Run(false, Entries());
      });

  EXPECT_CALL(*icon_store(), InitAndLoadKeys(_))
      .WillOnce(
          [](base::OnceCallback<void(bool, IconStore::LoadedIconKeys)> cb) {
            std::move(cb).Run(true, nullptr);
          });
  base::RunLoop loop;
  manager()->Init(base::BindOnce(
      [](base::RepeatingClosure closure, bool success) {
        // Expected to receive error.
        EXPECT_FALSE(success);
        std::move(closure).Run();
      },
      loop.QuitClosure()));
  loop.Run();
}

// Verify that error is received when icon database failed to initialize.
TEST_F(ScheduledNotificationManagerTest, IconDbInitFailed) {
  ON_CALL(*notification_store(), InitAndLoad(_))
      .WillByDefault([](base::OnceCallback<void(bool, Entries)> callback) {
        std::move(callback).Run(true, Entries());
      });

  EXPECT_CALL(*icon_store(), InitAndLoadKeys(_))
      .WillOnce(
          [](base::OnceCallback<void(bool, IconStore::LoadedIconKeys)> cb) {
            std::move(cb).Run(false, nullptr);
          });
  base::RunLoop loop;
  manager()->Init(base::BindOnce(
      [](base::RepeatingClosure closure, bool success) {
        // Expected to receive error.
        EXPECT_FALSE(success);
        std::move(closure).Run();
      },
      loop.QuitClosure()));
  loop.Run();
}

// Verify that residual expired icons are deleted when icon database is
// initialized.
TEST_F(ScheduledNotificationManagerTest, IconDbInitAndLoadKeys) {
  EXPECT_CALL(*notification_store(), InitAndLoad(_))
      .WillOnce([](base::OnceCallback<void(bool, Entries)> callback) {
        std::move(callback).Run(true, Entries());
      });

  auto icon_keys = std::make_unique<std::vector<std::string>>();
  auto* icon_keys_ptr = icon_keys.get();
  icon_keys->emplace_back(kSmallIconUuid);
  icon_keys->emplace_back(kLargeIconUuid);
  EXPECT_CALL(*icon_store(), InitAndLoadKeys(_))
      .WillOnce(
          [&icon_keys](
              base::OnceCallback<void(bool, IconStore::LoadedIconKeys)> cb) {
            std::move(cb).Run(true, std::move(icon_keys));
          });
  EXPECT_CALL(*icon_store(), DeleteIcons(*icon_keys_ptr, _));
  base::RunLoop loop;
  manager()->Init(base::BindOnce(
      [](base::RepeatingClosure closure, bool success) {
        EXPECT_TRUE(success);
        std::move(closure).Run();
      },
      loop.QuitClosure()));
  loop.Run();
}

// Test to schedule a notification.
TEST_F(ScheduledNotificationManagerTest, ScheduleNotification) {
  InitWithData(std::vector<NotificationEntry>());
  NotificationData notification_data;
  notification_data.title = kTitle;
  ScheduleParams schedule_params;
  schedule_params.priority = ScheduleParams::Priority::kLow;
  auto params = std::make_unique<NotificationParams>(
      SchedulerClientType::kTest1, notification_data, schedule_params);
  params->schedule_params.deliver_time_start = base::Time::Now();
  params->schedule_params.deliver_time_end = base::Time::Now() + base::Days(1);

  params->enable_ihnr_buttons = true;
  std::string guid = params->guid;
  EXPECT_FALSE(guid.empty());

  // Verify call contract.
  EXPECT_CALL(*icon_store(), AddIcons(_, _))
      .WillOnce([](IconStore::IconTypeBundleMap icons,
                   IconStore::AddCallback callback) {
        std::move(callback).Run(IconStore::IconTypeUuidMap{}, true);
      });
  EXPECT_CALL(*notification_store(), Add(guid, _, _))
      .WillOnce([guid](const std::string&, const NotificationEntry&,
                       base::OnceCallback<void(bool)> cb) {
        std::move(cb).Run(true);
      });
  ScheduleNotification(std::move(params), true /*expected_success*/);

  // Verify in-memory data.
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 1u);
  const NotificationEntry* entry = *(notifications.begin()->second.begin());
  EXPECT_EQ(entry->guid, guid);
  EXPECT_NE(entry->create_time, base::Time());

  // TODO(xingliu): change these to compare with operator==.
  EXPECT_EQ(entry->notification_data.title, kTitle);
  EXPECT_EQ(entry->schedule_params.priority, ScheduleParams::Priority::kLow);

  // Verify that |enable_ihnr_buttons| will add the helpful/unhelpful buttons.
  auto buttons = entry->notification_data.buttons;
  EXPECT_EQ(buttons.size(), 2u);
  EXPECT_EQ(buttons[0].id, notifications::kDefaultHelpfulButtonId);
  EXPECT_EQ(buttons[0].type, ActionButtonType::kHelpful);
  EXPECT_EQ(buttons[1].id, notifications::kDefaultUnhelpfulButtonId);
  EXPECT_EQ(buttons[1].type, ActionButtonType::kUnhelpful);
}

// Verifies that schedules a notification with unregistered client will fail.
TEST_F(ScheduledNotificationManagerTest, ScheduleInvalidNotification) {
  InitWithData(std::vector<NotificationEntry>());
  NotificationData notification_data;
  notification_data.title = kTitle;
  ScheduleParams schedule_params;
  // Client type kTest3 is not registered.
  auto params = std::make_unique<NotificationParams>(
      SchedulerClientType::kTest3, notification_data, schedule_params);

  EXPECT_CALL(*icon_store(), AddIcons(_, _)).Times(0);
  EXPECT_CALL(*notification_store(), Add(_, _, _)).Times(0);
  ScheduleNotification(std::move(params), false /*expected_success*/);
}

// Verifies that schedules a notification with duplicate guid will fail.
TEST_F(ScheduledNotificationManagerTest, ScheduleNotificationDuplicateGuid) {
  auto entry = CreateNotificationEntry(SchedulerClientType::kTest1);
  entry.guid = kGuid;
  InitWithData(std::vector<NotificationEntry>({entry}));

  NotificationData notification_data;
  notification_data.title = kTitle;
  ScheduleParams schedule_params;
  auto params = std::make_unique<NotificationParams>(
      SchedulerClientType::kTest1, notification_data, schedule_params);
  params->schedule_params.deliver_time_start = base::Time::Now();
  params->schedule_params.deliver_time_end = base::Time::Now() + base::Days(1);
  // Duplicate guid.
  params->guid = kGuid;

  EXPECT_CALL(*icon_store(), AddIcons(_, _)).Times(0);
  EXPECT_CALL(*notification_store(), Add(_, _, _)).Times(0);
  ScheduleNotification(std::move(params), false /*expected_success*/);
}

// Test to schedule a notification without guid, we will auto generated one.
TEST_F(ScheduledNotificationManagerTest, ScheduleNotificationEmptyGuid) {
  InitWithData(std::vector<NotificationEntry>());
  auto params = std::make_unique<NotificationParams>(
      SchedulerClientType::kTest1, NotificationData(), ScheduleParams());
  params->schedule_params.deliver_time_start = base::Time::Now();
  params->schedule_params.deliver_time_end = base::Time::Now() + base::Days(1);

  // Verify call contract.
  EXPECT_CALL(*icon_store(), AddIcons(_, _))
      .WillOnce([](IconStore::IconTypeBundleMap icons,
                   IconStore::AddCallback callback) {
        std::move(callback).Run(IconStore::IconTypeUuidMap{}, true);
      });
  EXPECT_CALL(*notification_store(), Add(_, _, _))
      .WillOnce(
          [&](const std::string&, const NotificationEntry&,
              base::OnceCallback<void(bool)> cb) { std::move(cb).Run(true); });

  ScheduleNotification(std::move(params), true /*expected_success*/);

  // Verify in-memory data.
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 1u);
  const NotificationEntry* entry = *(notifications.begin()->second.begin());
  EXPECT_NE(entry->guid, std::string());
  EXPECT_NE(entry->create_time, base::Time());
}

// Test to display a notification.
TEST_F(ScheduledNotificationManagerTest, DisplayNotification) {
  auto entry = CreateNotificationEntry(SchedulerClientType::kTest1);
  entry.guid = kGuid;
  InitWithData(std::vector<NotificationEntry>({entry}));

  EXPECT_CALL(*icon_store(), LoadIcons(_, _))
      .WillOnce([](std::vector<std::string> keys,
                   IconStore::LoadIconsCallback callback) {
        std::move(callback).Run(true, IconStore::LoadedIconsMap());
      });
  EXPECT_CALL(*notification_store(), Delete(kGuid, _));
  EXPECT_CALL(*icon_store(), DeleteIcons(_, _));

  DisplayNotification(kGuid, &entry);

  // Before display, the notification is removed from the store.
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_TRUE(notifications.empty());
}

// Test to display non-existing notification.
TEST_F(ScheduledNotificationManagerTest, DisplayNotificationWithoutEntry) {
  auto entry = CreateNotificationEntry(SchedulerClientType::kTest1);
  entry.guid = kGuid;
  InitWithData(std::vector<NotificationEntry>({entry}));

  EXPECT_CALL(*icon_store(), LoadIcons(_, _)).Times(0);
  EXPECT_CALL(*notification_store(), Delete(_, _)).Times(0);
  EXPECT_CALL(*icon_store(), DeleteIcons(_, _)).Times(0);

  DisplayNotification(kNonExistentGuid, nullptr);

  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(*notifications.at(SchedulerClientType::kTest1).back(), entry);
}

// Verify GetAllNotifications API, the notification should be sorted based on
// creation timestamp.
TEST_F(ScheduledNotificationManagerTest, GetAllNotifications) {
  // Ordering: entry1.create_time < entry0.create_time < entry2.create_time.
  auto now = base::Time::Now();
  auto entry0 = CreateNotificationEntry(SchedulerClientType::kTest1);
  entry0.create_time = now;
  auto entry1 = CreateNotificationEntry(SchedulerClientType::kTest1);
  entry1.create_time = now - base::Minutes(1);
  auto entry2 = CreateNotificationEntry(SchedulerClientType::kTest1);
  entry2.create_time = now + base::Minutes(1);

  InitWithData(std::vector<NotificationEntry>({entry0, entry1, entry2}));
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 1u);
  EXPECT_EQ(notifications.begin()->second.size(), 3u);

  // Entries returned by GetAllNotifications() should be sorted by creation
  // timestamp.
  const NotificationEntry* output0 = notifications.begin()->second.front();
  const NotificationEntry* output2 = notifications.begin()->second.back();
  EXPECT_EQ(output0->create_time, entry1.create_time);
  EXPECT_EQ(output2->create_time, entry2.create_time);
}

// Verifies GetNotifications() can return the correct values.
TEST_F(ScheduledNotificationManagerTest, GetNotifications) {
  auto entry = CreateNotificationEntry(SchedulerClientType::kTest1);
  InitWithData(std::vector<NotificationEntry>({entry}));
  std::vector<raw_ptr<const NotificationEntry>> entries;
  manager()->GetNotifications(SchedulerClientType::kTest2, &entries);
  EXPECT_TRUE(entries.empty());

  manager()->GetNotifications(SchedulerClientType::kTest1, &entries);
  EXPECT_EQ(entries.size(), 1u);
  EXPECT_EQ(*entries.back(), entry);

  manager()->GetNotifications(SchedulerClientType::kTest2, &entries);
  EXPECT_TRUE(entries.empty());
}

// Verify DeleteNotifications API, all notifications with given
// SchedulerClientType should be deleted.
TEST_F(ScheduledNotificationManagerTest, DeleteNotifications) {
  // Type1: entry0
  // Type2: entry1, entry2
  auto entry0 = CreateNotificationEntry(SchedulerClientType::kTest1);
  auto entry1 = CreateNotificationEntry(SchedulerClientType::kTest2);
  auto entry2 = CreateNotificationEntry(SchedulerClientType::kTest2);
  InitWithData(std::vector<NotificationEntry>({entry0, entry1, entry2}));
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 2u);

  EXPECT_CALL(*notification_store(), Delete(_, _))
      .Times(2)
      .RetiresOnSaturation();
  EXPECT_CALL(*icon_store(), DeleteIcons(_, _)).Times(2).RetiresOnSaturation();
  manager()->DeleteNotifications(SchedulerClientType::kTest2);
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 1u);

  // Ensure deleting non-existing key will not crash, and store will not call
  // Delete.
  EXPECT_CALL(*notification_store(), Delete(_, _))
      .Times(0)
      .RetiresOnSaturation();
  manager()->DeleteNotifications(SchedulerClientType::kTest2);
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 1u);

  EXPECT_CALL(*notification_store(), Delete(_, _)).RetiresOnSaturation();
  EXPECT_CALL(*icon_store(), DeleteIcons(_, _)).RetiresOnSaturation();
  manager()->DeleteNotifications(SchedulerClientType::kTest1);
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 0u);

  // Unregistered client.
  EXPECT_CALL(*notification_store(), Delete(_, _)).Times(0);
  manager()->DeleteNotifications(SchedulerClientType::kTest3);
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 0u);
}

// Verifies that unused notifications will be deleted during initialization.
TEST_F(ScheduledNotificationManagerTest, PruneNotifications) {
  // Type1: entry0
  // Type2: entry1(invalid), entry2(expired), entry3
  // Type3: entry4(unregistered client)
  auto now = base::Time::Now();
  auto entry0 = CreateNotificationEntry(SchedulerClientType::kTest1);
  entry0.create_time = now - base::Hours(12);
  auto entry1 = CreateNotificationEntry(SchedulerClientType::kTest2);
  entry1.create_time = now - base::Hours(14);
  entry1.schedule_params.deliver_time_start = base::Time::Now() - base::Days(2);
  entry1.schedule_params.deliver_time_end = base::Time::Now() - base::Days(1);
  auto entry2 = CreateNotificationEntry(SchedulerClientType::kTest2);
  entry2.create_time = now - base::Hours(24);
  auto entry3 = CreateNotificationEntry(SchedulerClientType::kTest2);
  entry3.create_time = now - base::Hours(23);
  auto entry4 = CreateNotificationEntry(SchedulerClientType::kTest3);

  EXPECT_CALL(*notification_store(), Delete(_, _)).Times(3);
  EXPECT_CALL(*icon_store(), DeleteIcons(_, _)).Times(3).RetiresOnSaturation();
  InitWithData(
      std::vector<NotificationEntry>({entry0, entry1, entry2, entry3, entry4}));
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 2u);
  EXPECT_EQ(notifications[SchedulerClientType::kTest1].size(), 1u);
  EXPECT_EQ(notifications[SchedulerClientType::kTest2].size(), 1u);
}

// Test to schedule a notification with two icons in notification data.
TEST_F(ScheduledNotificationManagerTest, ScheduleNotificationWithIcons) {
  InitWithData(std::vector<NotificationEntry>());
  NotificationData notification_data;
  notification_data.icons = CreateIcons();
  ScheduleParams schedule_params;
  auto params = std::make_unique<NotificationParams>(
      SchedulerClientType::kTest1, notification_data, schedule_params);
  params->schedule_params.deliver_time_start = base::Time::Now();
  params->schedule_params.deliver_time_end = base::Time::Now() + base::Days(1);

  std::string guid = params->guid;
  EXPECT_FALSE(guid.empty());

  // Verify call contract.
  EXPECT_CALL(*icon_store(), AddIcons(_, _))
      .WillOnce([](IconStore::IconTypeBundleMap icons,
                   IconStore::AddCallback callback) {
        IconStore::IconTypeUuidMap icons_uuid_map;
        for (const auto& pair : icons) {
          if (pair.first == IconType::kLargeIcon)
            icons_uuid_map.emplace(pair.first, kLargeIconUuid);
          else if (pair.first == IconType::kSmallIcon)
            icons_uuid_map.emplace(pair.first, kSmallIconUuid);
        }
        std::move(callback).Run(std::move(icons_uuid_map), true);
      });

  EXPECT_CALL(*notification_store(), Add(guid, _, _))
      .WillOnce([guid](const std::string&, const NotificationEntry&,
                       base::OnceCallback<void(bool)> cb) {
        std::move(cb).Run(true);
      });
  ScheduleNotification(std::move(params), true /*expected_success*/);

  // Verify in-memory data.
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_EQ(notifications.size(), 1u);
  const NotificationEntry* entry = *(notifications.begin()->second.begin());
  EXPECT_EQ(entry->guid, guid);
  EXPECT_NE(entry->create_time, base::Time());
  EXPECT_EQ(entry->icons_uuid.size(), 2u);
  EXPECT_EQ(entry->icons_uuid.at(IconType::kLargeIcon), kLargeIconUuid);
  EXPECT_EQ(entry->icons_uuid.at(IconType::kSmallIcon), kSmallIconUuid);
}

// Test to schedule a notification failed on saving icons.
TEST_F(ScheduledNotificationManagerTest, ScheduleNotificationWithIconsFailed) {
  InitWithData(std::vector<NotificationEntry>());
  NotificationData notification_data;
  notification_data.icons = CreateIcons();
  ScheduleParams schedule_params;
  auto params = std::make_unique<NotificationParams>(
      SchedulerClientType::kTest1, notification_data, schedule_params);
  params->schedule_params.deliver_time_start = base::Time::Now();
  params->schedule_params.deliver_time_end = base::Time::Now() + base::Days(1);

  // Verify call contract.
  EXPECT_CALL(*icon_store(), AddIcons(_, _))
      .WillOnce([](IconStore::IconTypeBundleMap icons,
                   IconStore::AddCallback callback) {
        IconStore::IconTypeUuidMap icons_uuid_map;
        for (const auto& pair : icons) {
          if (pair.first == IconType::kLargeIcon)
            icons_uuid_map.emplace(pair.first, kLargeIconUuid);
          else if (pair.first == IconType::kSmallIcon)
            icons_uuid_map.emplace(pair.first, kSmallIconUuid);
        }
        std::move(callback).Run(std::move(icons_uuid_map), false);
      });

  ScheduleNotification(std::move(params), false /*expected_success*/);

  // Verify in-memory data.
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_TRUE(notifications.empty());
}

// Verifies the case that failed to add to the notification store.
TEST_F(ScheduledNotificationManagerTest, ScheduleAddNotificationFailed) {
  InitWithData(std::vector<NotificationEntry>());
  NotificationData notification_data;
  notification_data.icons = CreateIcons();
  ScheduleParams schedule_params;
  auto params = std::make_unique<NotificationParams>(
      SchedulerClientType::kTest1, notification_data, schedule_params);
  params->schedule_params.deliver_time_start = base::Time::Now();
  params->schedule_params.deliver_time_end = base::Time::Now() + base::Days(1);

  // Succeeded to add icons.
  EXPECT_CALL(*icon_store(), AddIcons(_, _))
      .WillOnce([](IconStore::IconTypeBundleMap icons,
                   IconStore::AddCallback callback) {
        IconStore::IconTypeUuidMap icons_uuid_map;
        for (const auto& pair : icons) {
          if (pair.first == IconType::kLargeIcon)
            icons_uuid_map.emplace(pair.first, kLargeIconUuid);
          else if (pair.first == IconType::kSmallIcon)
            icons_uuid_map.emplace(pair.first, kSmallIconUuid);
        }
        std::move(callback).Run(std::move(icons_uuid_map), true);
      });

  std::vector<std::string> icons_to_delete{kSmallIconUuid, kLargeIconUuid};
  EXPECT_CALL(*icon_store(), DeleteIcons(icons_to_delete, _));

  // Failed to add notifications.
  EXPECT_CALL(*notification_store(), Add(_, _, _))
      .WillOnce(
          [](const std::string&, const NotificationEntry&,
             base::OnceCallback<void(bool)> cb) { std::move(cb).Run(false); });

  ScheduleNotification(std::move(params), false /*expected_success*/);

  // Verify in-memory data.
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_TRUE(notifications.empty());
}

// Test to display a notification with icons.
TEST_F(ScheduledNotificationManagerTest, DisplayNotificationWithIcons) {
  auto entry = CreateNotificationEntry(SchedulerClientType::kTest1);
  entry.guid = kGuid;
  entry.icons_uuid[IconType::kLargeIcon] = kLargeIconUuid;
  entry.icons_uuid[IconType::kSmallIcon] = kSmallIconUuid;
  InitWithData(std::vector<NotificationEntry>({entry}));

  auto icons = CreateIcons();
  EXPECT_CALL(*icon_store(), LoadIcons(_, _))
      .WillOnce([&icons](std::vector<std::string> keys,
                         IconStore::LoadIconsCallback callback) {
        IconStore::LoadedIconsMap result;
        result.emplace(kSmallIconUuid, icons.at(IconType::kSmallIcon));
        result.emplace(kLargeIconUuid, icons.at(IconType::kLargeIcon));
        std::move(callback).Run(true, std::move(result));
      });
  EXPECT_CALL(*notification_store(), Delete(kGuid, _));
  EXPECT_CALL(*icon_store(), DeleteIcons(_, _));

  auto expected_entry(entry);
  expected_entry.notification_data.icons = icons;
  DisplayNotification(kGuid, &expected_entry);
  // Verify in-memory data.
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_TRUE(notifications.empty());
}

// Test to display a notification with icons loaded failed.
TEST_F(ScheduledNotificationManagerTest, DisplayNotificationWithIconsFailed) {
  auto entry = CreateNotificationEntry(SchedulerClientType::kTest1);
  entry.guid = kGuid;
  entry.icons_uuid[IconType::kLargeIcon] = kLargeIconUuid;
  entry.icons_uuid[IconType::kSmallIcon] = kSmallIconUuid;
  InitWithData(std::vector<NotificationEntry>({entry}));

  EXPECT_CALL(*icon_store(), LoadIcons(_, _))
      .WillOnce([](std::vector<std::string> keys,
                   IconStore::LoadIconsCallback callback) {
        std::move(callback).Run(false, IconStore::LoadedIconsMap{});
      });
  EXPECT_CALL(*notification_store(), Delete(kGuid, _));
  EXPECT_CALL(*icon_store(), DeleteIcons(_, _));
  DisplayNotification(kGuid, nullptr /*expected_entry*/);

  // Verify in-memory data.
  ScheduledNotificationManager::Notifications notifications;
  manager()->GetAllNotifications(&notifications);
  EXPECT_TRUE(notifications.empty());
}

}  // namespace
}  // namespace notifications
