chromium/chrome/browser/push_notification/push_notification_service_desktop_impl_unittest.cc

// Copyright 2023 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/push_notification/push_notification_service_desktop_impl.h"

#include <memory>

#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "chrome/browser/push_notification/prefs/push_notification_prefs.h"
#include "chrome/browser/push_notification/server_client/fake_push_notification_server_client.h"
#include "chrome/browser/push_notification/server_client/push_notification_server_client_desktop_impl.h"
#include "chromeos/ash/components/nearby/common/scheduling/fake_nearby_scheduler_factory.h"
#include "components/gcm_driver/fake_gcm_driver.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/push_notification/fake_push_notification_client.h"
#include "components/push_notification/push_notification_constants.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

const char kPushNotificationAppId[] = "com.google.chrome.push_notification";
const char kSenderIdFCMToken[] = "sharing_fcm_token";
const char kSharingSenderID[] = "745476177629";
const char kTestMessage[] = "This is a test message";
const char kTestRepresentativeTargetId[] = "0123456789";
const char kTotalTokenRetrievalTime[] =
    "PushNotification.ChromeOS.GCM.Token.RetrievalTime";
const char kTotalSuccessfulRegistrationResponseTime[] =
    "PushNotification.ChromeOS.MultiLoginUpdateApi.ResponseTime.Success";
const char kTotalFailedRegistrationResponseTime[] =
    "PushNotification.ChromeOS.MultiLoginUpdateApi.ResponseTime.Failure";
const char kGcmTokenRetrievalResult[] =
    "PushNotification.ChromeOS.GCM.Token.RetrievalResult";
const char kServiceRegistrationResult[] =
    "PushNotification.ChromeOS.Registration.Result";

class FakeInstanceID : public instance_id::InstanceID {
 public:
  explicit FakeInstanceID(gcm::FakeGCMDriver* gcm_driver)
      : InstanceID(kPushNotificationAppId, gcm_driver) {}
  ~FakeInstanceID() override = default;

  void GetID(GetIDCallback callback) override { NOTIMPLEMENTED(); }

  void GetCreationTime(GetCreationTimeCallback callback) override {
    NOTIMPLEMENTED();
  }

  void GetToken(const std::string& authorized_entity,
                const std::string& scope,
                base::TimeDelta time_to_live,
                std::set<Flags> flags,
                GetTokenCallback callback) override {
    if (authorized_entity == kSharingSenderID) {
      std::move(callback).Run(kSenderIdFCMToken, result_);
    } else {
      std::move(callback).Run(fcm_token_, result_);
    }
  }

  void ValidateToken(const std::string& authorized_entity,
                     const std::string& scope,
                     const std::string& token,
                     ValidateTokenCallback callback) override {
    NOTIMPLEMENTED();
  }

  void DeleteToken(const std::string& authorized_entity,
                   const std::string& scope,
                   DeleteTokenCallback callback) override {
    std::move(callback).Run(result_);
  }

  void DeleteTokenImpl(const std::string& authorized_entity,
                       const std::string& scope,
                       DeleteTokenCallback callback) override {
    NOTIMPLEMENTED();
  }

  void DeleteIDImpl(DeleteIDCallback callback) override { NOTIMPLEMENTED(); }

  void SetFCMResult(InstanceID::Result result) { result_ = result; }

  void SetFCMToken(std::string fcm_token) { fcm_token_ = std::move(fcm_token); }

 private:
  InstanceID::Result result_;
  std::string fcm_token_;
};

class FakeInstanceIDDriver : public instance_id::InstanceIDDriver {
 public:
  FakeInstanceIDDriver() : InstanceIDDriver(nullptr) {}

  FakeInstanceIDDriver(const FakeInstanceIDDriver&) = delete;
  FakeInstanceIDDriver& operator=(const FakeInstanceIDDriver&) = delete;

  ~FakeInstanceIDDriver() override = default;

  instance_id::InstanceID* GetInstanceID(const std::string& app_id) override {
    return fake_instance_id_;
  }

  void SetFakeInstanceID(raw_ptr<FakeInstanceID> fake_instance_id) {
    fake_instance_id_ = std::move(fake_instance_id);
  }

 private:
  raw_ptr<FakeInstanceID> fake_instance_id_;
};

}  // namespace

namespace push_notification {

class PushNotificationServiceDesktopImplTest : public testing::Test {
 public:
  PushNotificationServiceDesktopImplTest() {
    shared_factory_ =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            base::BindOnce([]() -> network::mojom::URLLoaderFactory* {
              ADD_FAILURE() << "Did not expect this to actually be used";
              return nullptr;
            }));
  }

  ~PushNotificationServiceDesktopImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    RegisterPushNotificationPrefs(pref_service_.registry());
    ash::nearby::NearbySchedulerFactory::SetFactoryForTesting(
        &scheduler_factory_);
    PushNotificationServerClientDesktopImpl::Factory::SetFactoryForTesting(
        &fake_client_factory_);
    fake_gcm_driver_ = std::make_unique<gcm::FakeGCMDriver>();
    fake_instance_id_ =
        std::make_unique<FakeInstanceID>(fake_gcm_driver_.get());
    fake_instance_id_driver_.SetFakeInstanceID(fake_instance_id_.get());
    identity_test_env_ = std::make_unique<signin::IdentityTestEnvironment>(
        &test_url_loader_factory_, nullptr, nullptr);

    push_notification_service_ =
        std::make_unique<PushNotificationServiceDesktopImpl>(
            &pref_service_, &fake_instance_id_driver_,
            identity_test_env_->identity_manager(),
            base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
                &test_url_loader_factory_));
    histogram_tester_.ExpectTotalCount(kTotalTokenRetrievalTime, 0);
    histogram_tester_.ExpectTotalCount(kTotalSuccessfulRegistrationResponseTime,
                                       0);
    histogram_tester_.ExpectTotalCount(kTotalFailedRegistrationResponseTime, 0);
    histogram_tester_.ExpectBucketCount(kGcmTokenRetrievalResult,
                                        /*bucket: failure=*/0, 0);
    histogram_tester_.ExpectBucketCount(kGcmTokenRetrievalResult,
                                        /*bucket: success=*/1, 0);
    histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                        /*bucket: failure=*/0, 0);
    histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                        /*bucket: success=*/1, 0);
  }

  void TearDown() override {
    push_notification_service_.reset();
    identity_test_env_.reset();
    fake_gcm_driver_.reset();
  }

  push_notification::proto::NotificationsMultiLoginUpdateResponse
  CreateResponseProto() {
    push_notification::proto::NotificationsMultiLoginUpdateResponse
        response_proto;
    push_notification::proto::NotificationsMultiLoginUpdateResponse::
        RegistrationResult* registration_result =
            response_proto.add_registration_results();
    push_notification::proto::StatusProto* status =
        registration_result->mutable_status();
    status->set_code(0);
    status->set_message("OK");
    push_notification::proto::Target* target =
        registration_result->mutable_target();
    target->set_representative_target_id(kTestRepresentativeTargetId);
    return response_proto;
  }

  void CheckForSuccessfulRegistration() {
    EXPECT_TRUE(fake_client_factory_.fake_server_client()
                    ->HasRegisterWithPushNotificationServiceCallback());
    EXPECT_TRUE(fake_client_factory_.fake_server_client()->HasErrorCallback());
    fake_client_factory_.fake_server_client()
        ->InvokeRegisterWithPushNotificationServiceSuccessCallback(
            CreateResponseProto());
    EXPECT_TRUE(push_notification_service_->IsServiceInitialized());
    EXPECT_EQ(kTestRepresentativeTargetId,
              pref_service_.GetString(
                  prefs::kPushNotificationRepresentativeTargetIdPrefName));
    histogram_tester_.ExpectTotalCount(kTotalSuccessfulRegistrationResponseTime,
                                       1);
  }

  void CheckForFailedRegistration(
      PushNotificationDesktopApiCallFlow::PushNotificationApiCallFlowError
          error) {
    EXPECT_TRUE(fake_client_factory_.fake_server_client()
                    ->HasRegisterWithPushNotificationServiceCallback());
    EXPECT_TRUE(fake_client_factory_.fake_server_client()->HasErrorCallback());
    fake_client_factory_.fake_server_client()
        ->InvokeRegisterWithPushNotificationServiceErrorCallback(error);
  }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  base::HistogramTester histogram_tester_;
  TestingPrefServiceSimple pref_service_;
  std::unique_ptr<PushNotificationServiceDesktopImpl>
      push_notification_service_;
  std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
  scoped_refptr<network::SharedURLLoaderFactory> shared_factory_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  std::unique_ptr<FakeInstanceID> fake_instance_id_;
  std::unique_ptr<gcm::FakeGCMDriver> fake_gcm_driver_;
  FakeInstanceIDDriver fake_instance_id_driver_;
  FakePushNotificationServerClient::Factory fake_client_factory_;
  ash::nearby::FakeNearbySchedulerFactory scheduler_factory_;
};

TEST_F(PushNotificationServiceDesktopImplTest, StartService) {
  fake_instance_id_->SetFCMResult(instance_id::InstanceID::Result::SUCCESS);
  fake_instance_id_->SetFCMToken(kSenderIdFCMToken);
  ash::nearby::FakeNearbyScheduler* registration_scheduler =
      scheduler_factory_.pref_name_to_on_demand_instance()
          .find(
              prefs::
                  kPushNotificationRegistrationAttemptBackoffSchedulerPrefName)
          ->second.fake_scheduler;
  registration_scheduler->InvokeRequestCallback();
  EXPECT_EQ(std::string(), fake_client_factory_.fake_server_client()
                               ->GetRequestProto()
                               .target()
                               .representative_target_id());
  histogram_tester_.ExpectTotalCount(kTotalTokenRetrievalTime, 1);
  CheckForSuccessfulRegistration();
  histogram_tester_.ExpectTotalCount(kTotalFailedRegistrationResponseTime, 0);
  histogram_tester_.ExpectBucketCount(kGcmTokenRetrievalResult,
                                      /*bucket: success=*/1, 1);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: failure=*/0, 0);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: success=*/1, 1);
}

TEST_F(PushNotificationServiceDesktopImplTest, StartServiceWithPref) {
  fake_instance_id_->SetFCMResult(instance_id::InstanceID::Result::SUCCESS);
  fake_instance_id_->SetFCMToken(kSenderIdFCMToken);
  pref_service_.SetString(
      prefs::kPushNotificationRepresentativeTargetIdPrefName,
      kTestRepresentativeTargetId);
  ash::nearby::FakeNearbyScheduler* registration_scheduler =
      scheduler_factory_.pref_name_to_on_demand_instance()
          .find(
              prefs::
                  kPushNotificationRegistrationAttemptBackoffSchedulerPrefName)
          ->second.fake_scheduler;
  registration_scheduler->InvokeRequestCallback();
  EXPECT_EQ(kTestRepresentativeTargetId,
            fake_client_factory_.fake_server_client()
                ->GetRequestProto()
                .target()
                .representative_target_id());
  histogram_tester_.ExpectTotalCount(kTotalTokenRetrievalTime, 1);
  CheckForSuccessfulRegistration();
  histogram_tester_.ExpectTotalCount(kTotalFailedRegistrationResponseTime, 0);
  histogram_tester_.ExpectBucketCount(kGcmTokenRetrievalResult,
                                      /*bucket: success=*/1, 1);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: failure=*/0, 0);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: success=*/1, 1);
}

TEST_F(PushNotificationServiceDesktopImplTest, StartServiceWithPrefStoreReset) {
  fake_instance_id_->SetFCMResult(instance_id::InstanceID::Result::SUCCESS);
  fake_instance_id_->SetFCMToken(kSenderIdFCMToken);
  pref_service_.SetString(
      prefs::kPushNotificationRepresentativeTargetIdPrefName,
      kTestRepresentativeTargetId);
  ash::nearby::FakeNearbyScheduler* registration_scheduler =
      scheduler_factory_.pref_name_to_on_demand_instance()
          .find(
              prefs::
                  kPushNotificationRegistrationAttemptBackoffSchedulerPrefName)
          ->second.fake_scheduler;
  registration_scheduler->InvokeRequestCallback();
  EXPECT_EQ(kTestRepresentativeTargetId,
            fake_client_factory_.fake_server_client()
                ->GetRequestProto()
                .target()
                .representative_target_id());
  histogram_tester_.ExpectTotalCount(kTotalTokenRetrievalTime, 1);
  CheckForSuccessfulRegistration();
  histogram_tester_.ExpectTotalCount(kTotalFailedRegistrationResponseTime, 0);
  histogram_tester_.ExpectBucketCount(kGcmTokenRetrievalResult,
                                      /*bucket: success=*/1, 1);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: failure=*/0, 0);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: success=*/1, 1);
  push_notification_service_->OnStoreReset();
  EXPECT_EQ(std::string(),
            pref_service_.GetString(
                prefs::kPushNotificationRepresentativeTargetIdPrefName));
}

TEST_F(PushNotificationServiceDesktopImplTest, StartServiceTokenFailure) {
  fake_instance_id_->SetFCMResult(
      instance_id::InstanceID::Result::SERVER_ERROR);

  ash::nearby::FakeNearbyScheduler* registration_scheduler =
      scheduler_factory_.pref_name_to_on_demand_instance()
          .find(
              prefs::
                  kPushNotificationRegistrationAttemptBackoffSchedulerPrefName)
          ->second.fake_scheduler;
  registration_scheduler->InvokeRequestCallback();

  histogram_tester_.ExpectTotalCount(kTotalTokenRetrievalTime, 0);
  EXPECT_FALSE(fake_client_factory_.fake_server_client());
  EXPECT_FALSE(push_notification_service_->IsServiceInitialized());
  histogram_tester_.ExpectTotalCount(kTotalFailedRegistrationResponseTime, 0);
  histogram_tester_.ExpectBucketCount(kGcmTokenRetrievalResult,
                                      /*bucket: failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: failure=*/0, 0);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: success=*/1, 0);
}

TEST_F(PushNotificationServiceDesktopImplTest,
       StartServiceAuthenticationFailure) {
  fake_instance_id_->SetFCMResult(instance_id::InstanceID::Result::SUCCESS);
  fake_instance_id_->SetFCMToken(kSenderIdFCMToken);

  ash::nearby::FakeNearbyScheduler* registration_scheduler =
      scheduler_factory_.pref_name_to_on_demand_instance()
          .find(
              prefs::
                  kPushNotificationRegistrationAttemptBackoffSchedulerPrefName)
          ->second.fake_scheduler;
  registration_scheduler->InvokeRequestCallback();

  histogram_tester_.ExpectTotalCount(kTotalTokenRetrievalTime, 1);
  CheckForFailedRegistration(
      PushNotificationDesktopApiCallFlow::PushNotificationApiCallFlowError::
          kAuthenticationError);
  histogram_tester_.ExpectBucketCount(kGcmTokenRetrievalResult,
                                      /*bucket: success=*/1, 1);
  histogram_tester_.ExpectTotalCount(kTotalFailedRegistrationResponseTime, 1);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: failure=*/0, 1);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: success=*/1, 0);
}

TEST_F(PushNotificationServiceDesktopImplTest,
       StartServiceAuthenticationFailureRetrySuccess) {
  fake_instance_id_->SetFCMResult(instance_id::InstanceID::Result::SUCCESS);
  fake_instance_id_->SetFCMToken(kSenderIdFCMToken);

  ash::nearby::FakeNearbyScheduler* registration_scheduler =
      scheduler_factory_.pref_name_to_on_demand_instance()
          .find(
              prefs::
                  kPushNotificationRegistrationAttemptBackoffSchedulerPrefName)
          ->second.fake_scheduler;
  registration_scheduler->InvokeRequestCallback();

  histogram_tester_.ExpectTotalCount(kTotalTokenRetrievalTime, 1);
  CheckForFailedRegistration(
      PushNotificationDesktopApiCallFlow::PushNotificationApiCallFlowError::
          kAuthenticationError);
  histogram_tester_.ExpectTotalCount(kTotalFailedRegistrationResponseTime, 1);

  registration_scheduler->InvokeRequestCallback();

  histogram_tester_.ExpectTotalCount(kTotalTokenRetrievalTime, 2);
  CheckForFailedRegistration(
      PushNotificationDesktopApiCallFlow::PushNotificationApiCallFlowError::
          kAuthenticationError);
  histogram_tester_.ExpectTotalCount(kTotalFailedRegistrationResponseTime, 2);

  registration_scheduler->InvokeRequestCallback();

  histogram_tester_.ExpectTotalCount(kTotalTokenRetrievalTime, 3);
  CheckForSuccessfulRegistration();
  histogram_tester_.ExpectTotalCount(kTotalFailedRegistrationResponseTime, 2);
  histogram_tester_.ExpectBucketCount(kGcmTokenRetrievalResult,
                                      /*bucket: success=*/1, 3);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: failure=*/0, 2);
  histogram_tester_.ExpectBucketCount(kServiceRegistrationResult,
                                      /*bucket: success=*/1, 1);
}

TEST_F(PushNotificationServiceDesktopImplTest, OnMessageRecieved) {
  // No need to invoke the scheduler here because we aren't performing any
  // actions that require initialization to be complete.

  auto* client_manager =
      push_notification_service_->GetPushNotificationClientManager();
  auto fake_push_notification_client =
      std::make_unique<FakePushNotificationClient>(
          push_notification::ClientId::kNearbyPresence);
  client_manager->AddPushNotificationClient(
      fake_push_notification_client.get());
  gcm::IncomingMessage message;
  message.data.insert_or_assign(kNotificationTypeIdKey,
                                push_notification::kNearbyPresenceClientId);
  message.data.insert_or_assign(kNotificationPayloadKey, kTestMessage);

  push_notification_service_->OnMessage(kPushNotificationAppId,
                                        std::move(message));
  EXPECT_EQ(
      kTestMessage,
      fake_push_notification_client->GetMostRecentMessageDataRecieved().at(
          kNotificationPayloadKey));
}

}  // namespace push_notification