chromium/chromeos/ash/components/phonehub/notification_manager_impl_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_manager_impl.h"

#include <memory>
#include <optional>

#include "base/containers/flat_map.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/ash/components/phonehub/fake_message_sender.h"
#include "chromeos/ash/components/phonehub/fake_user_action_recorder.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace phonehub {

namespace {

using multidevice_setup::mojom::Feature;
using multidevice_setup::mojom::FeatureState;

const char16_t kAppName[] = u"Test App";
const char kPackageName[] = "com.google.testapp";
const int64_t kUserId = 1;

const char16_t kTitle[] = u"Test notification";
const char16_t kTextContent[] = u"This is a test notification";

enum class NotificationState { kAdded, kUpdated, kRemoved };

Notification CreateNotification(int64_t id) {
  return phonehub::Notification(
      id,
      phonehub::Notification::AppMetadata(
          kAppName, kPackageName,
          /*color_icon=*/gfx::Image(), /*monochrome_icon_mask=*/std::nullopt,
          /*icon_color=*/std::nullopt,
          /*icon_is_monochrome=*/true, kUserId,
          proto::AppStreamabilityStatus::STREAMABLE),
      base::Time::Now(), Notification::Importance::kDefault,
      Notification::Category::kConversation,
      {{Notification::ActionType::kInlineReply, /*action_id=*/0}},
      Notification::InteractionBehavior::kNone, kTitle, kTextContent);
}

class FakeObserver : public NotificationManager::Observer {
 public:
  FakeObserver() = default;
  ~FakeObserver() override = default;

  std::optional<NotificationState> GetState(int64_t notification_id) const {
    const auto it = id_to_state_map_.find(notification_id);
    if (it == id_to_state_map_.end())
      return std::nullopt;
    return it->second;
  }

 private:
  // NotificationManager::Observer:
  void OnNotificationsAdded(
      const base::flat_set<int64_t>& notification_ids) override {
    for (int64_t id : notification_ids)
      id_to_state_map_[id] = NotificationState::kAdded;
  }

  void OnNotificationsUpdated(
      const base::flat_set<int64_t>& notification_ids) override {
    for (int64_t id : notification_ids)
      id_to_state_map_[id] = NotificationState::kUpdated;
  }

  void OnNotificationsRemoved(
      const base::flat_set<int64_t>& notification_ids) override {
    for (int64_t id : notification_ids)
      id_to_state_map_[id] = NotificationState::kRemoved;
  }

  base::flat_map<int64_t, NotificationState> id_to_state_map_;
};

}  // namespace

class NotificationManagerImplTest : public testing::Test {
 protected:
  NotificationManagerImplTest() = default;
  NotificationManagerImplTest(const NotificationManagerImplTest&) = delete;
  NotificationManagerImplTest& operator=(const NotificationManagerImplTest&) =
      delete;
  ~NotificationManagerImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    manager_ = std::make_unique<NotificationManagerImpl>(
        &fake_message_sender_, &fake_user_action_recorder_,
        &fake_multidevice_setup_client_);
    manager_->AddObserver(&fake_observer_);
  }

  void TearDown() override { manager_->RemoveObserver(&fake_observer_); }

  NotificationManager& manager() { return *manager_; }
  FakeMessageSender& fake_message_sender() { return fake_message_sender_; }

  void SetNotificationsInternal(
      const base::flat_set<Notification>& notifications) {
    manager_->SetNotificationsInternal(notifications);
  }

  void ClearNotificationsInternal() { manager_->ClearNotificationsInternal(); }

  size_t GetNumNotifications() {
    return manager_->id_to_notification_map_.size();
  }

  std::optional<NotificationState> GetNotificationState(
      int64_t notification_id) {
    return fake_observer_.GetState(notification_id);
  }

  void SetNotificationFeatureStatus(FeatureState feature_state) {
    fake_multidevice_setup_client_.SetFeatureState(
        Feature::kPhoneHubNotifications, feature_state);
  }

  FakeUserActionRecorder fake_user_action_recorder_;

 private:
  FakeObserver fake_observer_;

  FakeMessageSender fake_message_sender_;
  multidevice_setup::FakeMultiDeviceSetupClient fake_multidevice_setup_client_;

  std::unique_ptr<NotificationManager> manager_;
};

TEST_F(NotificationManagerImplTest, SetAndClearNotificationsInternal) {
  EXPECT_EQ(0u, GetNumNotifications());
  const int64_t expected_id1 = 0;
  const int64_t expected_id2 = 1;

  SetNotificationsInternal(base::flat_set<Notification>{
      CreateNotification(expected_id1), CreateNotification(expected_id2)});
  EXPECT_EQ(2u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id1));
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id2));

  ClearNotificationsInternal();
  EXPECT_EQ(0u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kRemoved, GetNotificationState(expected_id1));
  EXPECT_EQ(NotificationState::kRemoved, GetNotificationState(expected_id2));
}

TEST_F(NotificationManagerImplTest, GetNotification) {
  EXPECT_EQ(0u, GetNumNotifications());
  const int64_t expected_id1 = 0;

  SetNotificationsInternal(
      base::flat_set<Notification>{CreateNotification(expected_id1)});
  EXPECT_EQ(1u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id1));

  // Call GetNotification() on an existent notification id. Expect a non-null
  // pointer.
  EXPECT_TRUE(manager().GetNotification(expected_id1));

  // Call GetNotification() on a non-existent notification id. Expect a null
  // pointer.
  EXPECT_FALSE(manager().GetNotification(/*notification_id=*/4));

  // Remove |expected_id1| and expect that GetNotification() returns a null
  // pointer.
  manager().DismissNotification(expected_id1);
  EXPECT_EQ(1u, fake_message_sender().GetDismissNotificationRequestCallCount());
  EXPECT_EQ(expected_id1,
            fake_message_sender().GetRecentDismissNotificationRequest());
  EXPECT_FALSE(manager().GetNotification(expected_id1));
}

TEST_F(NotificationManagerImplTest, DismissNotifications) {
  EXPECT_EQ(0u, GetNumNotifications());
  const int64_t expected_id1 = 0;
  const int64_t expected_id2 = 1;

  SetNotificationsInternal(base::flat_set<Notification>{
      CreateNotification(expected_id1), CreateNotification(expected_id2)});
  EXPECT_EQ(2u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id1));
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id2));

  manager().DismissNotification(expected_id2);
  EXPECT_EQ(1u, fake_user_action_recorder_.num_notification_dismissals());
  EXPECT_EQ(1u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id1));
  EXPECT_EQ(NotificationState::kRemoved, GetNotificationState(expected_id2));
  EXPECT_EQ(1u, fake_message_sender().GetDismissNotificationRequestCallCount());
  EXPECT_EQ(expected_id2,
            fake_message_sender().GetRecentDismissNotificationRequest());

  // Dismiss the same notification again, verify nothing happens.
  manager().DismissNotification(expected_id2);
  EXPECT_EQ(1u, fake_user_action_recorder_.num_notification_dismissals());
  EXPECT_EQ(1u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id1));
  EXPECT_EQ(NotificationState::kRemoved, GetNotificationState(expected_id2));
  EXPECT_EQ(1u, fake_message_sender().GetDismissNotificationRequestCallCount());
  EXPECT_EQ(expected_id2,
            fake_message_sender().GetRecentDismissNotificationRequest());
}

TEST_F(NotificationManagerImplTest, UpdatedNotification) {
  EXPECT_EQ(0u, GetNumNotifications());
  const int64_t expected_id1 = 0;
  const int64_t expected_id2 = 1;

  SetNotificationsInternal(base::flat_set<Notification>{
      CreateNotification(expected_id1), CreateNotification(expected_id2)});
  EXPECT_EQ(2u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id1));
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id2));

  // Simulate updating a notification.
  SetNotificationsInternal(
      base::flat_set<Notification>{CreateNotification(expected_id1)});
  EXPECT_EQ(2u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kUpdated, GetNotificationState(expected_id1));
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id2));
}

TEST_F(NotificationManagerImplTest, SendInlineReply) {
  EXPECT_EQ(0u, GetNumNotifications());
  const int64_t expected_id1 = 0;

  SetNotificationsInternal(
      base::flat_set<Notification>{CreateNotification(expected_id1)});
  EXPECT_EQ(1u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id1));

  // Simulate sending an inline reply to a notification.
  const std::u16string& expected_reply(u"test reply");
  manager().SendInlineReply(expected_id1, expected_reply);
  EXPECT_EQ(1u, fake_user_action_recorder_.num_notification_replies());
  EXPECT_EQ(1u, GetNumNotifications());
  EXPECT_EQ(NotificationState::kAdded, GetNotificationState(expected_id1));
  EXPECT_EQ(1u,
            fake_message_sender().GetNotificationInlineReplyRequestCallCount());
  std::pair<int64_t, std::u16string> pair =
      fake_message_sender().GetRecentNotificationInlineReplyRequest();
  EXPECT_EQ(expected_id1, pair.first);
  EXPECT_EQ(expected_reply, pair.second);

  // Simulate sending an inline reply to a non-existent notification. Expect
  // that no new reply calls were called and that the most recent reply is the
  // same as the previous inline reply call.
  manager().SendInlineReply(/*notification_id=*/5, /*reply=*/std::u16string());
  EXPECT_EQ(1u, fake_user_action_recorder_.num_notification_replies());
  EXPECT_EQ(1u,
            fake_message_sender().GetNotificationInlineReplyRequestCallCount());
  pair = fake_message_sender().GetRecentNotificationInlineReplyRequest();
  EXPECT_EQ(expected_id1, pair.first);
  EXPECT_EQ(expected_reply, pair.second);
}

TEST_F(NotificationManagerImplTest, ClearNotificationsOnFeatureStatusChanged) {
  // Simulate enabling notification feature state.
  SetNotificationFeatureStatus(FeatureState::kEnabledByUser);

  // Set an internal notification.
  SetNotificationsInternal(
      base::flat_set<Notification>{CreateNotification(/*notification_id=*/1)});
  EXPECT_EQ(1u, GetNumNotifications());

  // Change notification feature state to disabled, expect internal
  // notifications to be cleared.
  SetNotificationFeatureStatus(FeatureState::kDisabledByUser);
  EXPECT_EQ(0u, GetNumNotifications());
}
}  // namespace phonehub
}  // namespace ash