chromium/chromeos/ash/services/libassistant/notification_delegate_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 "base/memory/raw_ref.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/services/libassistant/public/cpp/assistant_notification.h"
#include "chromeos/ash/services/libassistant/public/mojom/notification_delegate.mojom-forward.h"
#include "chromeos/ash/services/libassistant/test_support/libassistant_service_tester.h"
#include "chromeos/assistant/internal/action/cros_action_module.h"
#include "chromeos/assistant/internal/libassistant/shared_headers.h"
#include "chromeos/assistant/internal/proto/shared/proto/v2/delegate/event_handler_interface.pb.h"
#include "testing/gmock/include/gmock/gmock.h"

namespace ash::libassistant {

namespace {

class NotificationDelegateMock : public mojom::NotificationDelegate {
 public:
  NotificationDelegateMock() = default;
  NotificationDelegateMock(const NotificationDelegateMock&) = delete;
  NotificationDelegateMock& operator=(const NotificationDelegateMock&) = delete;
  ~NotificationDelegateMock() override = default;

  // mojom::NotificationDelegate implementation:
  MOCK_METHOD(void,
              AddOrUpdateNotification,
              (assistant::AssistantNotification notification));
  MOCK_METHOD(void, RemoveAllNotifications, (bool from_server));
  MOCK_METHOD(void,
              RemoveNotificationByGroupingKey,
              (const std::string& grouping_key, bool from_server));

  void Bind(
      mojo::PendingReceiver<mojom::NotificationDelegate> pending_receiver) {
    receiver_.Bind(std::move(pending_receiver));
  }

  void FlushForTesting() { receiver_.FlushForTesting(); }

 private:
  mojo::Receiver<mojom::NotificationDelegate> receiver_{this};
};

// Helper class to fire interaction response handlers for tests.
// TODO(meilinw): move it to a separate class since it is used both here and
// by |ConversationObserverTest|.
class CrosActionModuleHelper {
 public:
  explicit CrosActionModuleHelper(
      chromeos::assistant::action::CrosActionModule* action_module)
      : action_module_(*action_module) {}
  CrosActionModuleHelper(const CrosActionModuleHelper&) = delete;
  CrosActionModuleHelper& operator=(const CrosActionModuleHelper&) = delete;
  ~CrosActionModuleHelper() = default;

  void ShowNotification(
      const chromeos::assistant::action::Notification& notification) {
    for (auto* observer : action_observers())
      observer->OnShowNotification(notification);
  }

 private:
  const std::vector<chromeos::assistant::action::AssistantActionObserver*>&
  action_observers() {
    return action_module_->GetActionObserversForTesting();
  }

  const raw_ref<const chromeos::assistant::action::CrosActionModule>
      action_module_;
};

}  // namespace

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

  void SetUp() override {
    delegate_mock_.Bind(
        service_tester_.GetNotificationDelegatePendingReceiver());
    service_tester_.Start();
    action_module_helper_ = std::make_unique<CrosActionModuleHelper>(
        static_cast<chromeos::assistant::action::CrosActionModule*>(
            service_tester_.service()
                .conversation_controller()
                .action_module()));

    service_tester_.service()
        .conversation_controller()
        .OnAssistantClientRunning(&service_tester_.assistant_client());
  }

  NotificationDelegateMock& delegate_mock() { return delegate_mock_; }

  CrosActionModuleHelper& action_module_helper() {
    return *action_module_helper_.get();
  }

  void OnNotificationRemoved(const std::string& grouping_id) {
    ::assistant::api::OnDeviceStateEventRequest request;
    auto* notification_removed =
        request.mutable_event()->mutable_on_notification_removed();
    notification_removed->set_grouping_id(grouping_id);

    service_tester_.service().conversation_controller().OnGrpcMessageForTesting(
        std::move(request));
  }

 private:
  base::test::SingleThreadTaskEnvironment environment_;
  ::testing::StrictMock<NotificationDelegateMock> delegate_mock_;
  LibassistantServiceTester service_tester_;
  std::unique_ptr<CrosActionModuleHelper> action_module_helper_;
};

TEST_F(NotificationDelegateTest, ShouldInvokeAddOrUpdateNotification) {
  chromeos::assistant::action::Notification input_notification{
      /*title=*/"title",
      /*text=*/"text",
      /*action_url=*/"https://action-url/",
      /*notification_id=*/"notification_id",
      /*consistency_token=*/"consistency_token",
      /*opaque_token=*/"opaque_token",
      /*grouping_key=*/"grouping_key",
      /*obfuscated_gaia_id=*/"obfuscated_gaia_id",
      /*expiry_timestamp_ms=*/100,
  };

  EXPECT_CALL(delegate_mock(), AddOrUpdateNotification)
      .WillOnce(testing::Invoke(
          [&](const assistant::AssistantNotification& output_notification) {
            EXPECT_EQ("title", output_notification.title);
            EXPECT_EQ("text", output_notification.message);
            EXPECT_EQ("notification_id", output_notification.server_id);
            EXPECT_EQ("notification_id", output_notification.client_id);
            EXPECT_EQ("consistency_token",
                      output_notification.consistency_token);
            EXPECT_EQ("opaque_token", output_notification.opaque_token);
            EXPECT_EQ("grouping_key", output_notification.grouping_key);
            EXPECT_EQ("obfuscated_gaia_id",
                      output_notification.obfuscated_gaia_id);
            EXPECT_EQ(base::Time::FromMillisecondsSinceUnixEpoch(100),
                      output_notification.expiry_time);
            EXPECT_EQ(true, output_notification.from_server);
          }));

  action_module_helper().ShowNotification(input_notification);
  delegate_mock().FlushForTesting();
}

TEST_F(NotificationDelegateTest, ShouldInvokeRemoveAllNotifications) {
  EXPECT_CALL(delegate_mock(), RemoveAllNotifications(/*from_server=*/true));

  // Pass in empty |grouping_key| should trigger all notifications being
  // removed.
  OnNotificationRemoved(/*grouping_key=*/"");
  delegate_mock().FlushForTesting();
}

TEST_F(NotificationDelegateTest, ShouldInvokeRemoveNotificationByGroupingKey) {
  const std::string grouping_id = "grouping-id";
  EXPECT_CALL(delegate_mock(), RemoveNotificationByGroupingKey(
                                   /*id=*/grouping_id, /*from_server=*/true));

  // Pass in non-empty |grouping_key| will trigger specific group of
  // notifications being removed.
  OnNotificationRemoved(grouping_id);
  delegate_mock().FlushForTesting();
}

}  // namespace ash::libassistant