chromium/chrome/browser/ash/android_sms/android_sms_app_manager_impl_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/android_sms_app_manager_impl.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_simple_task_runner.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/ash/android_sms/android_sms_urls.h"
#include "chrome/browser/ash/android_sms/fake_android_sms_app_setup_controller.h"
#include "chrome/test/base/testing_profile.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace android_sms {

const char kNewAppId[] = "newAppId";
const char kOldAppId[] = "oldAppId";

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

class TestObserver : public AndroidSmsAppManager::Observer {
 public:
  TestObserver() = default;

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

  ~TestObserver() override = default;

  size_t num_installed_app_url_changed_events() {
    return num_installed_app_url_changed_events_;
  }

 private:
  // AndroidSmsAppManager::Observer:
  void OnInstalledAppUrlChanged() override {
    ++num_installed_app_url_changed_events_;
  }

  size_t num_installed_app_url_changed_events_ = 0u;
};

class AndroidSmsAppManagerImplTest : public testing::Test {
 public:
  AndroidSmsAppManagerImplTest(const AndroidSmsAppManagerImplTest&) = delete;
  AndroidSmsAppManagerImplTest& operator=(const AndroidSmsAppManagerImplTest&) =
      delete;

 protected:
  class TestPwaDelegate : public AndroidSmsAppManagerImpl::PwaDelegate {
   public:
    TestPwaDelegate() = default;

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

    ~TestPwaDelegate() override = default;

    const std::vector<std::string>& opened_app_ids() const {
      return opened_app_ids_;
    }

    const std::vector<std::pair<std::string, std::string>>&
    transfer_item_attribute_params() {
      return transfer_item_attribute_params_;
    }

    // AndroidSmsAppManagerImpl::PwaDelegate:
    void OpenApp(Profile*, const std::string& app_id) override {
      opened_app_ids_.push_back(app_id);
    }

    bool TransferItemAttributes(
        const std::string& from_app_id,
        const std::string& to_app_id,
        app_list::AppListSyncableService* app_list_syncable_service) override {
      transfer_item_attribute_params_.emplace_back(from_app_id, to_app_id);
      return true;
    }

    bool IsAppRegistryReady(Profile* profile) override { return true; }

    void ExecuteOnAppRegistryReady(Profile* profile,
                                   base::OnceClosure task) override {
      test_task_runner_->PostTask(FROM_HERE, std::move(task));
    }

    void RunPendingTasks() { test_task_runner_->RunUntilIdle(); }

   private:
    std::vector<std::string> opened_app_ids_;
    std::vector<std::pair<std::string, std::string>>
        transfer_item_attribute_params_;
    scoped_refptr<base::TestSimpleTaskRunner> test_task_runner_ =
        base::MakeRefCounted<base::TestSimpleTaskRunner>();
  };

  AndroidSmsAppManagerImplTest() = default;
  ~AndroidSmsAppManagerImplTest() override = default;

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

    test_pref_service_ =
        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
    auto test_pwa_delegate = std::make_unique<TestPwaDelegate>();
    test_pwa_delegate_ = test_pwa_delegate.get();
    android_sms_app_manager_ = std::make_unique<AndroidSmsAppManagerImpl>(
        &profile_, fake_android_sms_app_setup_controller_.get(),
        test_pref_service_.get(), nullptr /* app_list_syncable_service */,
        std::move(test_pwa_delegate));

    test_observer_ = std::make_unique<TestObserver>();
    android_sms_app_manager_->AddObserver(test_observer_.get());
  }

  void TearDown() override {
    android_sms_app_manager_->RemoveObserver(test_observer_.get());
  }

  void CompleteAsyncInitialization() { test_pwa_delegate_->RunPendingTasks(); }

  TestPwaDelegate* test_pwa_delegate() { return test_pwa_delegate_; }

  TestObserver* test_observer() { return test_observer_.get(); }

  FakeAndroidSmsAppSetupController* fake_android_sms_app_setup_controller() {
    return fake_android_sms_app_setup_controller_.get();
  }

  AndroidSmsAppManager* android_sms_app_manager() {
    return android_sms_app_manager_.get();
  }

  sync_preferences::TestingPrefServiceSyncable* test_pref_service() {
    return test_pref_service_.get();
  }

 private:
  content::BrowserTaskEnvironment task_environment_;

  TestingProfile profile_;
  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
      test_pref_service_;
  std::unique_ptr<FakeAndroidSmsAppSetupController>
      fake_android_sms_app_setup_controller_;

  raw_ptr<TestPwaDelegate, DanglingUntriaged> test_pwa_delegate_;
  std::unique_ptr<TestObserver> test_observer_;

  std::unique_ptr<AndroidSmsAppManagerImpl> android_sms_app_manager_;
};

TEST_F(AndroidSmsAppManagerImplTest, TestSetUpMessages_NoPreviousApp_Fails) {
  CompleteAsyncInitialization();

  android_sms_app_manager()->SetUpAndroidSmsApp();
  fake_android_sms_app_setup_controller()->CompletePendingSetUpAppRequest(
      GetAndroidMessagesURL() /* expected_app_url */,
      GetAndroidMessagesURL(
          true /* use_install_url */) /* expected_install_url */,
      std::nullopt /* id_for_app */);

  // Verify that no installed app exists and no observers were notified.
  EXPECT_FALSE(fake_android_sms_app_setup_controller()->GetAppMetadataAtUrl(
      GetAndroidMessagesURL(true /* use_install_url */)));
  EXPECT_FALSE(android_sms_app_manager()->GetCurrentAppUrl());
  EXPECT_EQ(0u, test_observer()->num_installed_app_url_changed_events());
}

TEST_F(AndroidSmsAppManagerImplTest,
       TestSetUpMessages_ThenTearDown_NoPreviousApp) {
  CompleteAsyncInitialization();
  const GURL install_url = GetAndroidMessagesURL(true /* use_install_url */);

  android_sms_app_manager()->SetUpAndroidSmsApp();
  fake_android_sms_app_setup_controller()->CompletePendingSetUpAppRequest(
      GetAndroidMessagesURL() /* expected_app_url */,
      install_url /* expected_install_url */, kNewAppId);

  // Verify that the app was installed and observers were notified.
  EXPECT_EQ(kNewAppId, fake_android_sms_app_setup_controller()
                           ->GetAppMetadataAtUrl(GetAndroidMessagesURL(
                               true /* use_install_url */))
                           ->pwa);
  EXPECT_TRUE(fake_android_sms_app_setup_controller()
                  ->GetAppMetadataAtUrl(install_url)
                  ->is_cookie_present);
  EXPECT_EQ(GetAndroidMessagesURL(),
            *android_sms_app_manager()->GetCurrentAppUrl());
  EXPECT_EQ(1u, test_observer()->num_installed_app_url_changed_events());

  // Now, tear down the app, which should remove the DefaultToPersist cookie.
  android_sms_app_manager()->TearDownAndroidSmsApp();
  fake_android_sms_app_setup_controller()->CompletePendingDeleteCookieRequest(
      GetAndroidMessagesURL() /* expected_app_url */,
      GetAndroidMessagesURL(
          true /* use_install_url */) /* expected_install_url */);
  EXPECT_FALSE(fake_android_sms_app_setup_controller()
                   ->GetAppMetadataAtUrl(
                       GetAndroidMessagesURL(true /* use_install_url */))
                   ->is_cookie_present);
}

TEST_F(AndroidSmsAppManagerImplTest, TestSetUpMessagesAndLaunch_NoPreviousApp) {
  CompleteAsyncInitialization();
  const GURL install_url = GetAndroidMessagesURL(true /* use_install_url */);

  android_sms_app_manager()->SetUpAndLaunchAndroidSmsApp();
  fake_android_sms_app_setup_controller()->CompletePendingSetUpAppRequest(
      GetAndroidMessagesURL() /* expected_app_url */,
      install_url /* expected_install_url */, kNewAppId);

  // Verify that the app was installed and observers were notified.
  EXPECT_EQ(kNewAppId, fake_android_sms_app_setup_controller()
                           ->GetAppMetadataAtUrl(GetAndroidMessagesURL(
                               true /* use_install_url */))
                           ->pwa);
  EXPECT_TRUE(fake_android_sms_app_setup_controller()
                  ->GetAppMetadataAtUrl(install_url)
                  ->is_cookie_present);
  EXPECT_EQ(GetAndroidMessagesURL(),
            *android_sms_app_manager()->GetCurrentAppUrl());
  EXPECT_EQ(1u, test_observer()->num_installed_app_url_changed_events());

  // The app should have been launched.
  EXPECT_EQ(kNewAppId, test_pwa_delegate()->opened_app_ids()[0]);
}

TEST_F(AndroidSmsAppManagerImplTest,
       TestSetUpMessages_PreviousAppExists_Fails) {
  // Before completing initialization, install the old app.
  fake_android_sms_app_setup_controller()->SetAppAtUrl(
      GetAndroidMessagesURLOld(true /* use_install_url */), kOldAppId);
  CompleteAsyncInitialization();

  // This should trigger the new app to be installed; fail this installation.
  // This simulates a situation which could occur if the user signs in with the
  // flag enabled but is offline and thus unable to install the new PWA.
  fake_android_sms_app_setup_controller()->CompletePendingSetUpAppRequest(
      GetAndroidMessagesURL() /* expected_app_url */,
      GetAndroidMessagesURL(
          true /* use_install_url */) /* expected_install_url */,
      std::nullopt /* id_for_app */);

  // Verify that the new app was not installed and no observers were notified.
  EXPECT_FALSE(fake_android_sms_app_setup_controller()->GetAppMetadataAtUrl(
      GetAndroidMessagesURL(true /* use_install_url */)));
  EXPECT_EQ(0u, test_observer()->num_installed_app_url_changed_events());

  // The old app should still be true.
  EXPECT_EQ(GetAndroidMessagesURLOld(),
            *android_sms_app_manager()->GetCurrentAppUrl());
  EXPECT_EQ(kOldAppId, fake_android_sms_app_setup_controller()
                           ->GetAppMetadataAtUrl(GetAndroidMessagesURLOld(
                               true /* use_install_url */))
                           ->pwa);
  EXPECT_TRUE(fake_android_sms_app_setup_controller()
                  ->GetAppMetadataAtUrl(
                      GetAndroidMessagesURLOld(true /* use_install_url */))
                  ->is_cookie_present);
}

TEST_F(AndroidSmsAppManagerImplTest,
       TestSetUpMessages_ThenTearDown_PreviousAppExists) {
  // Before completing initialization, install the old app.
  fake_android_sms_app_setup_controller()->SetAppAtUrl(
      GetAndroidMessagesURLOld(true /* use_install_url */), kOldAppId);
  CompleteAsyncInitialization();

  // This should trigger the new app to be installed.
  const GURL install_url = GetAndroidMessagesURL(true /* use_install_url */);
  fake_android_sms_app_setup_controller()->CompletePendingSetUpAppRequest(
      GetAndroidMessagesURL() /* expected_app_url */,
      install_url /* expected_install_url */, kNewAppId /* id_for_app */);

  // Verify that the app was installed and attributes were transferred. By this
  // point, observers should not have been notified yet since the old app was
  // not yet installed.
  EXPECT_EQ(kNewAppId, fake_android_sms_app_setup_controller()
                           ->GetAppMetadataAtUrl(GetAndroidMessagesURL(
                               true /* use_install_url */))
                           ->pwa);
  EXPECT_TRUE(fake_android_sms_app_setup_controller()
                  ->GetAppMetadataAtUrl(
                      GetAndroidMessagesURL(true /* use_install_url */))
                  ->is_cookie_present);
  EXPECT_EQ(GetAndroidMessagesURL(),
            *android_sms_app_manager()->GetCurrentAppUrl());
  EXPECT_EQ(std::make_pair(std::string(kOldAppId), std::string(kNewAppId)),
            test_pwa_delegate()->transfer_item_attribute_params()[0]);
  EXPECT_EQ(0u, test_observer()->num_installed_app_url_changed_events());

  // Now, complete uninstallation of the old app; this should trigger observers
  // to be notified.
  fake_android_sms_app_setup_controller()->CompleteRemoveAppRequest(
      GetAndroidMessagesURLOld() /* expected_app_url */,
      GetAndroidMessagesURLOld(
          true /* use_install_url */) /* expected_install_url */,
      GetAndroidMessagesURL() /* expected_migrated_to_app_url */,
      true /* success */);
  EXPECT_EQ(1u, test_observer()->num_installed_app_url_changed_events());
}

TEST_F(AndroidSmsAppManagerImplTest, TestGetCurrentAppUrl) {
  // Enable staging flag and install both prod and staging apps.
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kUseMessagesStagingUrl);
  fake_android_sms_app_setup_controller()->SetAppAtUrl(
      GetAndroidMessagesURL(true /* use_install_url */, PwaDomain::kProdGoogle),
      kOldAppId);
  fake_android_sms_app_setup_controller()->SetAppAtUrl(
      GetAndroidMessagesURL(true /* use_install_url */, PwaDomain::kStaging),
      kNewAppId);
  CompleteAsyncInitialization();

  // When both apps are installed GetCurrentAppUrl should always
  // return the preferred app.
  EXPECT_EQ(
      GetAndroidMessagesURL(false /* use_install_url */, PwaDomain::kStaging),
      android_sms_app_manager()->GetCurrentAppUrl());
}

}  // namespace android_sms
}  // namespace ash