chromium/chrome/browser/ash/android_sms/connection_manager_unittest.cc

// Copyright 2018 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/ash/android_sms/connection_manager.h"

#include <utility>

#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/ash/android_sms/android_sms_urls.h"
#include "chrome/browser/ash/android_sms/fake_android_sms_app_manager.h"
#include "chrome/browser/ash/android_sms/fake_connection_establisher.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/fake_service_worker_context.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace android_sms {

namespace {

const int64_t kDummyVersionId = 123l;
const int64_t kDummyVersionId2 = 456l;

GURL GetAndroidMessagesURLOld() {
  // For this test, consider the staging server to be the "old" URL.
  return GetAndroidMessagesURL(false /* use_install_url */,
                               PwaDomain::kStaging);
}

}  // namespace

class ConnectionManagerTest : public testing::Test {
 protected:
  class TestServiceWorkerProvider
      : public ConnectionManager::ServiceWorkerProvider {
   public:
    TestServiceWorkerProvider(
        content::FakeServiceWorkerContext* new_url_service_worker,
        content::FakeServiceWorkerContext* old_url_service_worker)
        : new_url_service_worker_(new_url_service_worker),
          old_url_service_worker_(old_url_service_worker) {}

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

    ~TestServiceWorkerProvider() override = default;

   private:
    // ConnectionManager::ServiceWorkerProvider:
    content::ServiceWorkerContext* Get(const GURL& url,
                                       Profile* profile) override {
      if (url == GetAndroidMessagesURL())
        return new_url_service_worker_;

      if (url == GetAndroidMessagesURLOld())
        return old_url_service_worker_;

      NOTREACHED_IN_MIGRATION();
      return nullptr;
    }

    raw_ptr<content::FakeServiceWorkerContext> new_url_service_worker_;
    raw_ptr<content::FakeServiceWorkerContext> old_url_service_worker_;
  };

  enum class PwaState { kEnabledWithNewUrl, kEnabledWithOldUrl, kDisabled };

  ConnectionManagerTest() = default;

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

  ~ConnectionManagerTest() override = default;

  void SetUp() override {
    fake_new_service_worker_context_ =
        std::make_unique<content::FakeServiceWorkerContext>();
    fake_old_service_worker_context_ =
        std::make_unique<content::FakeServiceWorkerContext>();
    fake_android_sms_app_manager_ =
        std::make_unique<FakeAndroidSmsAppManager>();
    fake_multidevice_setup_client_ =
        std::make_unique<multidevice_setup::FakeMultiDeviceSetupClient>();
  }

  void SetupConnectionManager(PwaState initial_pwa_state) {
    SetPwaState(initial_pwa_state);

    auto fake_connection_establisher =
        std::make_unique<FakeConnectionEstablisher>();
    fake_connection_establisher_ = fake_connection_establisher.get();

    auto test_service_worker_provider =
        std::make_unique<TestServiceWorkerProvider>(
            fake_new_service_worker_context_.get(),
            fake_old_service_worker_context_.get());
    test_service_worker_provider_ = test_service_worker_provider.get();

    connection_manager_ = base::WrapUnique(
        new ConnectionManager(std::move(fake_connection_establisher), &profile_,
                              fake_android_sms_app_manager_.get(),
                              fake_multidevice_setup_client_.get(),
                              std::move(test_service_worker_provider)));
  }

  void VerifyEstablishConnectionCalls(
      size_t expected_count,
      bool is_last_call_expected_to_be_new_url = true) {
    const auto& establish_connection_calls =
        fake_connection_establisher_->establish_connection_calls();
    EXPECT_EQ(expected_count, establish_connection_calls.size());

    if (expected_count == 0u)
      return;

    if (is_last_call_expected_to_be_new_url) {
      EXPECT_EQ(GetAndroidMessagesURL(),
                std::get<0>(establish_connection_calls.back()));
      EXPECT_EQ(fake_new_service_worker_context_.get(),
                std::get<2>(establish_connection_calls.back()));
    } else {
      EXPECT_EQ(GetAndroidMessagesURLOld(),
                std::get<0>(establish_connection_calls.back()));
      EXPECT_EQ(fake_old_service_worker_context_.get(),
                std::get<2>(establish_connection_calls.back()));
    }
  }

  void VerifyTearDownConnectionCalls(
      size_t expected_count,
      bool is_last_call_expected_to_be_new_url = true) {
    const auto& tear_down_connection_calls =
        fake_connection_establisher_->tear_down_connection_calls();
    EXPECT_EQ(expected_count, tear_down_connection_calls.size());

    if (expected_count == 0u)
      return;

    if (is_last_call_expected_to_be_new_url) {
      EXPECT_EQ(GetAndroidMessagesURL(),
                std::get<0>(tear_down_connection_calls.back()));
      EXPECT_EQ(fake_new_service_worker_context_.get(),
                std::get<1>(tear_down_connection_calls.back()));
    } else {
      EXPECT_EQ(GetAndroidMessagesURLOld(),
                std::get<0>(tear_down_connection_calls.back()));
      EXPECT_EQ(fake_old_service_worker_context_.get(),
                std::get<1>(tear_down_connection_calls.back()));
    }
  }

  void SetPwaState(PwaState pwa_state) {
    if (pwa_state == PwaState::kDisabled) {
      fake_android_sms_app_manager_->SetInstalledAppUrl(std::nullopt);
      fake_multidevice_setup_client_->SetFeatureState(
          multidevice_setup::mojom::Feature::kMessages,
          multidevice_setup::mojom::FeatureState::kDisabledByUser);
      return;
    }

    fake_android_sms_app_manager_->SetInstalledAppUrl(
        pwa_state == PwaState::kEnabledWithNewUrl ? GetAndroidMessagesURL()
                                                  : GetAndroidMessagesURLOld());
    fake_multidevice_setup_client_->SetFeatureState(
        multidevice_setup::mojom::Feature::kMessages,
        multidevice_setup::mojom::FeatureState::kEnabledByUser);
  }

  content::FakeServiceWorkerContext* fake_new_service_worker_context() const {
    return fake_new_service_worker_context_.get();
  }

  content::FakeServiceWorkerContext* fake_old_service_worker_context() const {
    return fake_old_service_worker_context_.get();
  }

 private:
  content::BrowserTaskEnvironment task_environment_;

  TestingProfile profile_;
  std::unique_ptr<content::FakeServiceWorkerContext>
      fake_new_service_worker_context_;
  std::unique_ptr<content::FakeServiceWorkerContext>
      fake_old_service_worker_context_;
  std::unique_ptr<multidevice_setup::FakeMultiDeviceSetupClient>
      fake_multidevice_setup_client_;
  std::unique_ptr<FakeAndroidSmsAppManager> fake_android_sms_app_manager_;
  raw_ptr<FakeConnectionEstablisher, DanglingUntriaged>
      fake_connection_establisher_;
  raw_ptr<TestServiceWorkerProvider, DanglingUntriaged>
      test_service_worker_provider_;

  std::unique_ptr<ConnectionManager> connection_manager_;
};

TEST_F(ConnectionManagerTest, ConnectOnActivate) {
  SetupConnectionManager(PwaState::kEnabledWithNewUrl);
  fake_new_service_worker_context()->NotifyObserversOnVersionActivated(
      kDummyVersionId, GetAndroidMessagesURL());

  // Verify that connection establishing is attempted on startup and
  // when a new version is activated.
  // startup + activate.
  VerifyEstablishConnectionCalls(2u /* expected_count */);
}

TEST_F(ConnectionManagerTest, ConnectOnNoControllees) {
  SetupConnectionManager(PwaState::kEnabledWithNewUrl);
  // Notify Activation so that Connection manager is tracking the version ID.
  fake_new_service_worker_context()->NotifyObserversOnVersionActivated(
      kDummyVersionId, GetAndroidMessagesURL());

  // Verify that connection establishing is attempted when there are no
  // controllees.
  fake_new_service_worker_context()->NotifyObserversOnNoControllees(
      kDummyVersionId, GetAndroidMessagesURL());
  // startup + activate + no controllees.
  VerifyEstablishConnectionCalls(3u /* expected_count */);
}

TEST_F(ConnectionManagerTest, IgnoreRedundantVersion) {
  SetupConnectionManager(PwaState::kEnabledWithNewUrl);
  fake_new_service_worker_context()->NotifyObserversOnVersionActivated(
      kDummyVersionId, GetAndroidMessagesURL());

  // Notify that current active version is now redundant.
  fake_new_service_worker_context()->NotifyObserversOnVersionRedundant(
      kDummyVersionId, GetAndroidMessagesURL());

  // Verify that no connection establishing is attempted when there are no
  // controllees for the redundant version.
  fake_new_service_worker_context()->NotifyObserversOnNoControllees(
      kDummyVersionId, GetAndroidMessagesURL());
  // startup + activate only.
  VerifyEstablishConnectionCalls(2u /* expected_count */);
}

TEST_F(ConnectionManagerTest, ConnectOnNoControlleesWithNoActive) {
  SetupConnectionManager(PwaState::kEnabledWithNewUrl);
  // Verify that connection establishing is attempted when there are no
  // controllees for a version ID even if the activate notification was missed.
  fake_new_service_worker_context()->NotifyObserversOnNoControllees(
      kDummyVersionId, GetAndroidMessagesURL());
  // startup + no controllees.
  VerifyEstablishConnectionCalls(2u /* expected_count */);
}

TEST_F(ConnectionManagerTest, IgnoreOnNoControlleesInvalidId) {
  SetupConnectionManager(PwaState::kEnabledWithNewUrl);
  fake_new_service_worker_context()->NotifyObserversOnVersionActivated(
      kDummyVersionId, GetAndroidMessagesURL());

  // Verify that OnNoControllees with different version id is ignored.
  fake_new_service_worker_context()->NotifyObserversOnNoControllees(
      kDummyVersionId2, GetAndroidMessagesURL());
  // startup + activate only.
  VerifyEstablishConnectionCalls(2u /* expected_count */);
}

TEST_F(ConnectionManagerTest, InvalidScope) {
  SetupConnectionManager(PwaState::kEnabledWithNewUrl);
  GURL invalid_scope("https://example.com");
  // Verify that OnVersionActivated and OnNoControllees with invalid scope
  // are ignored
  fake_new_service_worker_context()->NotifyObserversOnVersionActivated(
      kDummyVersionId, invalid_scope);
  fake_new_service_worker_context()->NotifyObserversOnNoControllees(
      kDummyVersionId, invalid_scope);
  // startup only, ignore others.
  VerifyEstablishConnectionCalls(1u /* expected_count */);

  // Verify that OnVersionRedundant with invalid scope is ignored
  fake_new_service_worker_context()->NotifyObserversOnVersionActivated(
      kDummyVersionId, GetAndroidMessagesURL());
  fake_new_service_worker_context()->NotifyObserversOnVersionRedundant(
      kDummyVersionId, invalid_scope);
  fake_new_service_worker_context()->NotifyObserversOnNoControllees(
      kDummyVersionId, GetAndroidMessagesURL());
  // startup + activate + no controllees.
  VerifyEstablishConnectionCalls(3u /* expected_count */);
}

TEST_F(ConnectionManagerTest, FeatureStateInitDisabled) {
  // Verify that connection is not established on initialization
  // if the feature is not enabled.
  SetupConnectionManager(PwaState::kDisabled);
  VerifyEstablishConnectionCalls(0u /* expected_count */);

  SetPwaState(PwaState::kEnabledWithNewUrl);
  VerifyEstablishConnectionCalls(1u /* expected_count */);
}

TEST_F(ConnectionManagerTest, FeatureStateChange) {
  SetupConnectionManager(PwaState::kEnabledWithNewUrl);
  fake_new_service_worker_context()->NotifyObserversOnVersionActivated(
      kDummyVersionId, GetAndroidMessagesURL());

  // Verify that disabling feature stops the service worker.
  SetPwaState(PwaState::kDisabled);
  VerifyEstablishConnectionCalls(2u /* expected_count */);
  VerifyTearDownConnectionCalls(1u /* expected_count */);

  // Verify that subsequent service worker events do not trigger connection.
  fake_new_service_worker_context()->NotifyObserversOnNoControllees(
      kDummyVersionId, GetAndroidMessagesURL());
  VerifyEstablishConnectionCalls(2u /* expected_count */);

  // Verify that enabling feature establishes connection again.
  SetPwaState(PwaState::kEnabledWithNewUrl);
  VerifyEstablishConnectionCalls(3u /* expected_count */);

  // Verify that connection is established if the version id changes.
  fake_new_service_worker_context()->NotifyObserversOnNoControllees(
      kDummyVersionId2, GetAndroidMessagesURL());
  VerifyEstablishConnectionCalls(4u /* expected_count */);
}

TEST_F(ConnectionManagerTest, AppUrlMigration) {
  SetupConnectionManager(PwaState::kEnabledWithOldUrl);
  fake_old_service_worker_context()->NotifyObserversOnVersionActivated(
      kDummyVersionId, GetAndroidMessagesURLOld());
  VerifyEstablishConnectionCalls(
      2u /* expected_count */, false /* is_last_call_expected_to_be_new_url */);

  // Switch to the new URL.
  SetPwaState(PwaState::kEnabledWithNewUrl);

  // The ServiceWorker for the old URL should have stopped.
  VerifyTearDownConnectionCalls(
      1u /* expected_count*/, false /* is_last_call_expected_to_be_new_url */);

  // A connection to the new URL should have occurred.
  VerifyEstablishConnectionCalls(
      3u /* expected_count */, true /* is_last_call_expected_to_be_new_url */);

  fake_new_service_worker_context()->NotifyObserversOnVersionActivated(
      kDummyVersionId, GetAndroidMessagesURL());
  VerifyEstablishConnectionCalls(
      4u /* expected_count */, true /* is_last_call_expected_to_be_new_url */);
}

}  // namespace android_sms
}  // namespace ash