chromium/chromeos/ash/components/phonehub/notification_processor_unittest.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/phonehub/notification_processor.h"
#include <string>

#include "ash/constants/ash_features.h"
#include "base/memory/ptr_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/phonehub/fake_notification_manager.h"
#include "chromeos/ash/components/phonehub/phone_model_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_unittest_util.h"

namespace ash {
namespace phonehub {

namespace {

constexpr int64_t kNotificationIdA = 1;
constexpr int64_t kNotificationIdB = 2;
constexpr int64_t kNotificationIdC = 3;

constexpr int64_t kInlineReplyIdA = 3;
constexpr int64_t kInlineReplyIdB = 4;

constexpr int64_t kOpenableActionId = -2;
constexpr int64_t kAnswerActionId = 1;
constexpr int64_t kDeclineActionId = 2;
constexpr int64_t kHangupActionId = 3;

const char kIconDataA[] = "icon_a";
const char kIconDataB[] = "icon_b";

const char kSharedImageA[] = "shared_image_a";
const char kSharedImageB[] = "shared_image_b";

const char kContactImageA[] = "contact_image_a";
const char kContactImageB[] = "contact_image_b";

// Garbage color for the purpose of verification in these tests.
const SkColor kIconColor = SkColorSetRGB(0x12, 0x34, 0x56);

SkBitmap TestBitmap() {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(1, 1);
  return bitmap;
}

gfx::Image TestImage() {
  gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(TestBitmap());
  image_skia.MakeThreadSafe();
  return gfx::Image(image_skia);
}

}  // namespace

class NotificationProcessorTest : public testing::Test {
 public:
  friend class NotificationProcessor;

  NotificationProcessorTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{features::kEcheSWA,
                              features::kPhoneHubCallNotification},
        /*disabled_features=*/{});
  }
  NotificationProcessorTest(const NotificationProcessorTest&) = delete;
  NotificationProcessorTest& operator=(const NotificationProcessorTest&) =
      delete;
  ~NotificationProcessorTest() override = default;

  class FakeImageDecoderDelegate
      : public NotificationProcessor::ImageDecoderDelegate {
   public:
    using DecodeImageCallback = NotificationProcessor::DecodeImageCallback;

    FakeImageDecoderDelegate() = default;
    ~FakeImageDecoderDelegate() override = default;

    void PerformImageDecode(
        const std::string& data,
        DecodeImageCallback single_image_decoded_closure) override {
      decode_image_callbacks_.push(std::move(single_image_decoded_closure));
    }

    size_t NumberOfDecodeImageCallbacks() {
      return decode_image_callbacks_.size();
    }

    void RunNextCallback(SkBitmap bitmap) {
      std::move(decode_image_callbacks_.front()).Run(bitmap);
      decode_image_callbacks_.pop();
    }

    void RunAllCallbacks() {
      while (!decode_image_callbacks_.empty())
        RunNextCallback(TestBitmap());
    }

    void RunAllCallbacksWithEmpty() {
      while (!decode_image_callbacks_.empty())
        RunNextCallback(SkBitmap());
    }

    std::queue<DecodeImageCallback> decode_image_callbacks_;
  };

  // testing::Test:
  void SetUp() override {
    fake_notification_manager_ = std::make_unique<FakeNotificationManager>();

    notification_processor_ = base::WrapUnique(new NotificationProcessor(
        fake_notification_manager_.get(),
        std::make_unique<FakeImageDecoderDelegate>()));
  }

  FakeImageDecoderDelegate* image_decoder_delegate() {
    return static_cast<FakeImageDecoderDelegate*>(
        notification_processor_->delegate_.get());
  }

  NotificationProcessor* notification_processor() {
    return notification_processor_.get();
  }

  FakeNotificationManager* fake_notification_manager() {
    return fake_notification_manager_.get();
  }

  size_t NumPendingRequests() {
    return notification_processor()->pending_notification_requests_.size();
  }

  proto::Notification CreateNewInlineReplyableOpenableNotification(
      int64_t notification_id,
      int64_t inline_reply_id,
      Notification::InteractionBehavior behavior) {
    return CreateNewInlineReplyableNotification(
        notification_id, inline_reply_id,
        /* icon= */ std::string(),
        /* shared_image= */ std::string(),
        /* contact_image= */ std::string(), behavior);
  }

  proto::Notification CreateNewInlineReplyableNotification(
      int64_t notification_id,
      int64_t inline_reply_id,
      std::string icon = std::string(),
      std::string shared_image = std::string(),
      std::string contact_image = std::string(),
      Notification::InteractionBehavior behavior =
          Notification::InteractionBehavior::kNone,
      proto::AppStreamabilityStatus app_streamability_status =
          proto::AppStreamabilityStatus::STREAMABLE) {
    auto origin_app = std::make_unique<proto::App>();
    origin_app->set_icon(icon);
    origin_app->set_app_streamability_status(app_streamability_status);

    proto::Notification notification;
    notification.set_id(notification_id);
    notification.set_allocated_origin_app(origin_app.release());
    notification.set_contact_image(contact_image);
    notification.set_shared_image(shared_image);
    notification.set_category(
        proto::Notification::Category::Notification_Category_CONVERSATION);

    notification.add_actions();
    proto::Action* mutable_action = notification.mutable_actions(0);
    mutable_action->set_id(inline_reply_id);
    mutable_action->set_type(proto::Action_InputType::Action_InputType_TEXT);

    if (behavior == Notification::InteractionBehavior::kOpenable) {
      notification.add_actions();
      proto::Action* open_action = notification.mutable_actions(1);
      open_action->set_id(kOpenableActionId);
      open_action->set_type(proto::Action_InputType::Action_InputType_OPEN);
    }
    return notification;
  }

  proto::Notification CreateNewInlineReplyableMonochromeIconNotification(
      int64_t notification_id,
      int64_t inline_reply_id,
      std::optional<SkColor> icon_color = std::nullopt,
      std::string icon = std::string()) {
    proto::Notification notification = CreateNewInlineReplyableNotification(
        notification_id, inline_reply_id, icon);
    proto::App* origin_app = notification.mutable_origin_app();
    origin_app->set_monochrome_icon_mask(icon);
    if (icon_color != std::nullopt) {
      auto color_rgb = std::make_unique<proto::ColorRgb>();
      color_rgb->set_red(SkColorGetR(*icon_color));
      color_rgb->set_green(SkColorGetG(*icon_color));
      color_rgb->set_blue(SkColorGetB(*icon_color));
      origin_app->set_allocated_monochrome_icon_color(color_rgb.release());
    }
    return notification;
  }

  proto::Notification CreateNonTextTypeNotification(
      int64_t notification_id,
      int64_t inline_reply_id,
      std::string icon = std::string()) {
    auto origin_app = std::make_unique<proto::App>();
    origin_app->set_icon(icon);

    proto::Notification notification;
    notification.set_id(notification_id);
    notification.set_allocated_origin_app(origin_app.release());

    notification.add_actions();
    proto::Action* open_action = notification.mutable_actions(0);
    open_action->set_id(kOpenableActionId);
    open_action->set_type(proto::Action_InputType::Action_InputType_OPEN);

    return notification;
  }

  proto::Notification CreateIncomingCallNotification(int64_t notification_id) {
    auto origin_app = std::make_unique<proto::App>();
    origin_app->set_icon(std::string());

    proto::Notification notification;
    notification.set_id(notification_id);
    notification.set_allocated_origin_app(origin_app.release());
    notification.set_contact_image(std::string());
    notification.set_shared_image(std::string());
    notification.set_category(
        proto::Notification::Category::Notification_Category_INCOMING_CALL);

    notification.add_actions();
    proto::Action* answer_action = notification.mutable_actions(0);
    answer_action->set_id(kAnswerActionId);
    answer_action->set_call_action(
        proto::Action_CallAction::Action_CallAction_ANSWER);

    notification.add_actions();
    proto::Action* decline_action = notification.mutable_actions(1);
    decline_action->set_id(kDeclineActionId);
    decline_action->set_call_action(
        proto::Action_CallAction::Action_CallAction_DECLINE);

    return notification;
  }

  proto::Notification CreateOngoingCallNotification(int64_t notification_id) {
    auto origin_app = std::make_unique<proto::App>();
    origin_app->set_icon(std::string());

    proto::Notification notification;
    notification.set_id(notification_id);
    notification.set_allocated_origin_app(origin_app.release());
    notification.set_contact_image(std::string());
    notification.set_shared_image(std::string());
    notification.set_category(
        proto::Notification::Category::Notification_Category_ONGOING_CALL);

    notification.add_actions();
    proto::Action* hangup_action = notification.mutable_actions(0);
    hangup_action->set_id(kHangupActionId);
    hangup_action->set_call_action(
        proto::Action_CallAction::Action_CallAction_HANGUP);

    notification.add_actions();
    proto::Action* open_action = notification.mutable_actions(1);
    open_action->set_id(kOpenableActionId);
    open_action->set_type(proto::Action_InputType::Action_InputType_OPEN);

    return notification;
  }

 private:
  std::unique_ptr<FakeNotificationManager> fake_notification_manager_;
  std::unique_ptr<NotificationProcessor> notification_processor_;

  base::test::ScopedFeatureList scoped_feature_list_;
  base::test::SingleThreadTaskEnvironment task_environment_;
};

TEST_F(NotificationProcessorTest, FailedToDecodeImage) {
  std::vector<proto::Notification> first_set_of_notifications;

  // The icon should be an empty image as the decoder failed to decode the
  // image to be used as both the color_icon and monochrome_icon_mask.
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunNextCallback(SkBitmap());
  image_decoder_delegate()->RunNextCallback(SkBitmap());

  const Notification* notification =
      fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_TRUE(notification->app_metadata().color_icon.IsEmpty());
  EXPECT_TRUE(notification->app_metadata().monochrome_icon_mask.has_value());
  EXPECT_FALSE(notification->shared_image().has_value());
  EXPECT_FALSE(notification->contact_image().has_value());
}

TEST_F(NotificationProcessorTest, ShouldSkipDecodeImageIfNotAvailable) {
  std::vector<proto::Notification> first_set_of_notifications;

  first_set_of_notifications.emplace_back(CreateNonTextTypeNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA));
  notification_processor()->AddNotifications(first_set_of_notifications);

  EXPECT_EQ(0u, image_decoder_delegate()->NumberOfDecodeImageCallbacks());
  image_decoder_delegate()->RunAllCallbacks();

  EXPECT_EQ(0u, fake_notification_manager()->num_notifications());
}

TEST_F(NotificationProcessorTest, MonochromeIconFieldsPopulatedCorrectly) {
  std::vector<proto::Notification> first_set_of_notifications;

  // Legacy notifications don't supply color and should not be filled in.
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  const Notification* notification =
      fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().color_icon,
                                        TestImage()));
  EXPECT_FALSE(notification->app_metadata().icon_color.has_value());

  // Monochrome notifications without a color should not have icon_color filled.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(
      CreateNewInlineReplyableMonochromeIconNotification(
          kNotificationIdA, kInlineReplyIdA, std::nullopt, kIconDataA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  notification = fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_TRUE(notification->app_metadata().icon_is_monochrome);
  EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().color_icon,
                                        TestImage()));
  EXPECT_FALSE(notification->app_metadata().icon_color.has_value());

  // Monochrome notifications with a color should have icon_color filled.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(
      CreateNewInlineReplyableMonochromeIconNotification(
          kNotificationIdA, kInlineReplyIdA, kIconColor, kIconDataA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  notification = fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_TRUE(notification->app_metadata().icon_is_monochrome);
  EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().color_icon,
                                        TestImage()));
  EXPECT_TRUE(notification->app_metadata().icon_color.has_value());
  EXPECT_TRUE(*notification->app_metadata().icon_color == kIconColor);

  // Monochrome notifications without an icon should not fill in color.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(
      CreateNewInlineReplyableMonochromeIconNotification(
          kNotificationIdA, kInlineReplyIdA, kIconColor,
          /*icon=*/std::string()));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacksWithEmpty();

  notification = fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_TRUE(notification->app_metadata().icon_is_monochrome);
  EXPECT_TRUE(notification->app_metadata().color_icon.IsEmpty());
  EXPECT_FALSE(notification->app_metadata().icon_color.has_value());
}

TEST_F(NotificationProcessorTest, ImageFieldPopulatedCorrectly) {
  std::vector<proto::Notification> first_set_of_notifications;

  // The icon should be populated. The shared and contact image should be null.
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  const Notification* notification =
      fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().color_icon,
                                        TestImage()));
  EXPECT_FALSE(notification->shared_image().has_value());
  EXPECT_FALSE(notification->contact_image().has_value());

  // The icon and shared image should be populated. The contact image should be
  // null.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  notification = fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().color_icon,
                                        TestImage()));
  EXPECT_TRUE(
      gfx::test::AreImagesEqual(*notification->shared_image(), TestImage()));
  EXPECT_FALSE(notification->contact_image().has_value());

  // The icon and contact image should be populated. The shared image should be
  // null.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA, std::string(),
      kContactImageA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  notification = fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().color_icon,
                                        TestImage()));
  EXPECT_FALSE(notification->shared_image().has_value());
  EXPECT_TRUE(
      gfx::test::AreImagesEqual(*notification->contact_image(), TestImage()));

  // All images should be should be populated.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA,
      kContactImageA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();
  EXPECT_TRUE(gfx::test::AreImagesEqual(notification->app_metadata().color_icon,
                                        TestImage()));
  EXPECT_TRUE(
      gfx::test::AreImagesEqual(*notification->shared_image(), TestImage()));
  EXPECT_TRUE(
      gfx::test::AreImagesEqual(*notification->contact_image(), TestImage()));
}

TEST_F(NotificationProcessorTest, StreamabilityStatus) {
  std::vector<proto::Notification> first_set_of_notifications;

  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA, std::string(),
      std::string(), Notification::InteractionBehavior::kNone,
      proto::AppStreamabilityStatus::BLOCK_LISTED));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  const Notification* notification =
      fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_EQ(proto::AppStreamabilityStatus::BLOCK_LISTED,
            notification->app_metadata().app_streamability_status);
}

TEST_F(NotificationProcessorTest, AddRemoveClearWithoutRace) {
  // Add 2 notifications with all images populated.
  std::vector<proto::Notification> first_set_of_notifications;
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA,
      kContactImageA));
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdB, kInlineReplyIdB, kIconDataB, kSharedImageB,
      kContactImageB));

  notification_processor()->AddNotifications(first_set_of_notifications);

  // 8 image decode callbacks will occur for kIconDataA, data for icons twice
  // for each notification, kSharedImageA, kContactImageA, kIconDataB,
  // kSharedImageB, and kContactImageB.
  EXPECT_EQ(8u, image_decoder_delegate()->NumberOfDecodeImageCallbacks());
  image_decoder_delegate()->RunAllCallbacks();

  EXPECT_EQ(2u, fake_notification_manager()->num_notifications());
  EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdA));
  EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdB));

  // Remove notification with id kNotificationIdA.
  base::flat_set<int64_t> ids_of_notifications_to_remove;
  ids_of_notifications_to_remove.emplace(kNotificationIdA);
  notification_processor()->RemoveNotifications(ids_of_notifications_to_remove);
  EXPECT_EQ(1u, fake_notification_manager()->num_notifications());
  EXPECT_FALSE(fake_notification_manager()->GetNotification(kNotificationIdA));
  EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdB));

  // Clear all notifications.
  notification_processor()->ClearNotificationsAndPendingUpdates();
  EXPECT_EQ(0u, fake_notification_manager()->num_notifications());
}

TEST_F(NotificationProcessorTest, AddRemoveWithRace) {
  // Add 2 notifications.
  std::vector<proto::Notification> first_set_of_notifications;
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA));
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdB, kInlineReplyIdB, kIconDataB));

  notification_processor()->AddNotifications(first_set_of_notifications);

  // One pending requests because |first_set_of_notifications| processing
  // occurred immediately.
  EXPECT_EQ(1u, NumPendingRequests());

  // Remove notification with id kNotificationIdA while
  // |first_set_of_notifications| is still being processed.
  base::flat_set<int64_t> ids_of_notifications_to_remove;
  ids_of_notifications_to_remove.emplace(kNotificationIdA);
  notification_processor()->RemoveNotifications(ids_of_notifications_to_remove);

  // Pending delete request, first in the queue.
  EXPECT_EQ(2u, NumPendingRequests());
  EXPECT_EQ(0u, fake_notification_manager()->num_notifications());

  // Add a set of notifications such that only one image needs to be decoded,
  // when neither the first set has completed processing more the remove request
  // has been fully processed.
  std::vector<proto::Notification> second_set_of_notifications;
  second_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA));
  notification_processor()->AddNotifications(second_set_of_notifications);

  // Pending add request, second in the queue.
  EXPECT_EQ(3u, NumPendingRequests());
  EXPECT_EQ(0u, fake_notification_manager()->num_notifications());

  // 5 image decode callbacks will occur (two icons, two monochrome icons, and a
  // shared image). When the last image decode callback is finished running,
  // which in this case is original icon2, it will cause the next notification
  // edit request to be executed.
  EXPECT_EQ(5u, image_decoder_delegate()->NumberOfDecodeImageCallbacks());
  EXPECT_EQ(3u, NumPendingRequests());
  image_decoder_delegate()->RunNextCallback(TestBitmap());

  EXPECT_EQ(4u, image_decoder_delegate()->NumberOfDecodeImageCallbacks());
  EXPECT_EQ(3u, NumPendingRequests());
  image_decoder_delegate()->RunNextCallback(TestBitmap());

  EXPECT_EQ(3u, image_decoder_delegate()->NumberOfDecodeImageCallbacks());
  EXPECT_EQ(3u, NumPendingRequests());

  // The scheduled remove callback will occur, then subsequently the add
  // notification with 1 image.
  image_decoder_delegate()->RunNextCallback(TestBitmap());
  image_decoder_delegate()->RunNextCallback(TestBitmap());
  image_decoder_delegate()->RunNextCallback(TestBitmap());
  EXPECT_EQ(1u, NumPendingRequests());
  EXPECT_EQ(2u, image_decoder_delegate()->NumberOfDecodeImageCallbacks());

  EXPECT_EQ(1u, fake_notification_manager()->num_notifications());
  EXPECT_FALSE(fake_notification_manager()->GetNotification(kNotificationIdA));
  EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdB));

  // 1 image decode callback will occur.
  image_decoder_delegate()->RunAllCallbacks();
  EXPECT_EQ(0u, NumPendingRequests());
  EXPECT_EQ(2u, fake_notification_manager()->num_notifications());
  EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdA));
  EXPECT_TRUE(fake_notification_manager()->GetNotification(kNotificationIdB));
}

TEST_F(NotificationProcessorTest, AddClearAllWithRace) {
  std::vector<proto::Notification> first_set_of_notifications;
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdA, kInlineReplyIdA, kIconDataA, kSharedImageA));
  first_set_of_notifications.emplace_back(CreateNewInlineReplyableNotification(
      kNotificationIdB, kInlineReplyIdB, kIconDataA));

  notification_processor()->AddNotifications(first_set_of_notifications);

  // Clearing Notifications will invalidate all callbacks in process and
  // immediately clear all pointers.
  notification_processor()->ClearNotificationsAndPendingUpdates();
  EXPECT_EQ(0u, fake_notification_manager()->num_notifications());
  image_decoder_delegate()->RunAllCallbacks();
  EXPECT_EQ(0u, NumPendingRequests());
  EXPECT_EQ(0u, fake_notification_manager()->num_notifications());
  EXPECT_FALSE(fake_notification_manager()->GetNotification(kNotificationIdA));
  EXPECT_FALSE(fake_notification_manager()->GetNotification(kNotificationIdB));
}

TEST_F(NotificationProcessorTest, InteractionBehaviorPopulatedCorrectly) {
  std::vector<proto::Notification> first_set_of_notifications;

  // The notification should be openable if a OPEN action is specified.
  first_set_of_notifications.emplace_back(
      CreateNewInlineReplyableOpenableNotification(
          kNotificationIdA, kInlineReplyIdA,
          Notification::InteractionBehavior::kOpenable));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  const Notification* notification =
      fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_EQ(Notification::InteractionBehavior::kOpenable,
            notification->interaction_behavior());
  EXPECT_EQ(Notification::Category::kConversation, notification->category());

  // The notification should not specify interaction behaviors if none are
  // available.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(
      CreateNewInlineReplyableNotification(kNotificationIdA, kInlineReplyIdA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  notification = fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_EQ(Notification::InteractionBehavior::kNone,
            notification->interaction_behavior());
  EXPECT_EQ(Notification::Category::kConversation, notification->category());

  // The notification has IncomingCall category if answer action is
  // found.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(
      CreateIncomingCallNotification(kNotificationIdA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  notification = fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_EQ(Notification::Category::kIncomingCall, notification->category());

  // The notification has OngoingCall category if hangup action is
  // found.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(
      CreateOngoingCallNotification(kNotificationIdC));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  notification = fake_notification_manager()->GetNotification(kNotificationIdC);
  EXPECT_EQ(Notification::Category::kOngoingCall, notification->category());
}

TEST_F(NotificationProcessorTest, ActionIdMapPopulatedCorrectly) {
  std::vector<proto::Notification> first_set_of_notifications;

  // The inline reply notification should have reply action id.
  first_set_of_notifications.emplace_back(
      CreateNewInlineReplyableNotification(kNotificationIdA, kInlineReplyIdA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  const Notification* notification =
      fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_EQ(1u, notification->action_id_map().size());
  EXPECT_EQ(kInlineReplyIdA, notification->action_id_map().at(
                                 Notification::ActionType::kInlineReply));

  // The incoming call notification should have answer and decline action ids.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(
      CreateIncomingCallNotification(kNotificationIdA));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  notification = fake_notification_manager()->GetNotification(kNotificationIdA);
  EXPECT_EQ(2u, notification->action_id_map().size());
  EXPECT_EQ(kAnswerActionId, notification->action_id_map().at(
                                 Notification::ActionType::kAnswer));
  EXPECT_EQ(kDeclineActionId, notification->action_id_map().at(
                                  Notification::ActionType::kDecline));

  // The ongoing call notification should have hangup action id.
  first_set_of_notifications.clear();
  first_set_of_notifications.emplace_back(
      CreateOngoingCallNotification(kNotificationIdC));
  notification_processor()->AddNotifications(first_set_of_notifications);
  image_decoder_delegate()->RunAllCallbacks();

  notification = fake_notification_manager()->GetNotification(kNotificationIdC);
  EXPECT_EQ(1u, notification->action_id_map().size());
  EXPECT_EQ(kHangupActionId, notification->action_id_map().at(
                                 Notification::ActionType::kHangup));
}

}  // namespace phonehub
}  // namespace ash