chromium/chrome/browser/ash/account_manager/account_apps_availability_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 "chrome/browser/ash/account_manager/account_apps_availability.h"

#include <memory>
#include <utility>

#include "ash/constants/ash_features.h"
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "components/account_manager_core/account.h"
#include "components/account_manager_core/account_manager_facade.h"
#include "components/account_manager_core/mock_account_manager_facade.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user_manager_base.h"
#include "google_apis/gaia/oauth2_access_token_fetcher.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;
using testing::Contains;
using testing::InSequence;

using Checkpoint = ::testing::MockFunction<void(int step)>;

namespace ash {

namespace {

constexpr char kPrimaryAccountEmail[] = "[email protected]";
constexpr char kSecondaryAccount1Email[] = "[email protected]";
constexpr char kSecondaryAccount2Email[] = "[email protected]";

account_manager::Account CreateAccount(const std::string& email,
                                       const std::string& gaia_id) {
  account_manager::AccountKey key(gaia_id,
                                  ::account_manager::AccountType::kGaia);
  return {key, email};
}

class MockObserver : public AccountAppsAvailability::Observer {
 public:
  MockObserver() = default;
  ~MockObserver() override = default;

  MOCK_METHOD(void,
              OnAccountAvailableInArc,
              (const account_manager::Account&),
              (override));
  MOCK_METHOD(void,
              OnAccountUnavailableInArc,
              (const account_manager::Account&),
              (override));
};

base::flat_set<account_manager::Account> GetAccountsAvailableInArcSync(
    AccountAppsAvailability* availability) {
  base::test::TestFuture<const base::flat_set<account_manager::Account>&>
      future;
  availability->GetAccountsAvailableInArc(future.GetCallback());
  return future.Get();
}

MATCHER_P(AccountEqual, other, "") {
  return arg.key == other.key && arg.raw_email == other.raw_email;
}

}  // namespace

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

 protected:
  AccountAppsAvailabilityTest() = default;
  ~AccountAppsAvailabilityTest() override = default;

  void SetUp() override {
    identity_test_env()->EnableRemovalOfExtendedAccountInfo();
    pref_service_ = std::make_unique<TestingPrefServiceSimple>();
    AccountAppsAvailability::RegisterPrefs(pref_service_->registry());

    fake_user_manager_.Reset(std::make_unique<user_manager::FakeUserManager>());

    primary_account_ = identity_test_env()->MakePrimaryAccountAvailable(
        kPrimaryAccountEmail, signin::ConsentLevel::kSignin);
    LoginUserSession();
  }

  void TearDown() override { pref_service_.reset(); }

  std::unique_ptr<AccountAppsAvailability> CreateAccountAppsAvailability() {
    return std::make_unique<AccountAppsAvailability>(
        GetAccountManagerFacade(identity_test_env()->identity_manager()),
        identity_test_env()->identity_manager(), pref_service_.get());
  }

  signin::IdentityTestEnvironment* identity_test_env() {
    return &identity_test_env_;
  }

  AccountInfo* primary_account_info() { return &primary_account_; }

 private:
  void LoginUserSession() {
    auto account_id = AccountId::FromUserEmailGaiaId(primary_account_.email,
                                                     primary_account_.gaia);
    auto* user = fake_user_manager_->AddUser(account_id);
    fake_user_manager_->UserLoggedIn(account_id, user->username_hash(), false,
                                     false);
  }

  base::test::SingleThreadTaskEnvironment task_environment_;
  signin::IdentityTestEnvironment identity_test_env_;
  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
  AccountInfo primary_account_;
  user_manager::TypedScopedUserManager<user_manager::FakeUserManager>
      fake_user_manager_;
};

TEST_F(AccountAppsAvailabilityTest, InitializationPrefIsPersistedOnDisk) {
  base::HistogramTester tester;
  auto account_apps_availability = CreateAccountAppsAvailability();
  EXPECT_FALSE(account_apps_availability->IsInitialized());
  // Wait for `GetAccounts` call to finish.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(account_apps_availability->IsInitialized());
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(
      0u,
      tester.GetAllSamples(AccountAppsAvailability::kNumAccountsInArcMetricName)
          .size());
  EXPECT_EQ(0u,
            tester
                .GetAllSamples(
                    AccountAppsAvailability::kPercentAccountsInArcMetricName)
                .size());
  account_apps_availability.reset();

  account_apps_availability = CreateAccountAppsAvailability();
  EXPECT_TRUE(account_apps_availability->IsInitialized());
  // Wait for `GetAccounts` call to finish.
  base::RunLoop().RunUntilIdle();
  tester.ExpectUniqueSample(
      AccountAppsAvailability::kNumAccountsInArcMetricName,
      /*sample=*/1, /*expected_bucket_count=*/1);
  tester.ExpectUniqueSample(
      AccountAppsAvailability::kPercentAccountsInArcMetricName,
      /*sample=*/100, /*expected_bucket_count=*/1);
}

TEST_F(AccountAppsAvailabilityTest, CallsBeforeInitialization) {
  const AccountInfo secondary_account_info =
      identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email);
  const account_manager::Account primary_account =
      CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia);
  const account_manager::Account secondary_account =
      CreateAccount(kSecondaryAccount1Email, secondary_account_info.gaia);

  auto account_apps_availability = CreateAccountAppsAvailability();
  EXPECT_FALSE(account_apps_availability->IsInitialized());
  // Make secondary account unavailable in ARC.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account,
                                                        false);

  base::test::TestFuture<const base::flat_set<account_manager::Account>&>
      future;
  // Wait for initialization and for `GetAccountsAvailableInArc` call
  // completion.
  account_apps_availability->GetAccountsAvailableInArc(future.GetCallback());
  base::flat_set<account_manager::Account> result = future.Get();
  EXPECT_TRUE(account_apps_availability->IsInitialized());

  // Only primary account is available, secondary account was removed.
  EXPECT_EQ(result.size(), 1u);
  EXPECT_THAT(result, Contains(AccountEqual(primary_account)));
}

TEST_F(AccountAppsAvailabilityTest, GetAccountsAvailableInArc) {
  const AccountInfo secondary_account_info =
      identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email);
  const account_manager::Account primary_account =
      CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia);
  const account_manager::Account secondary_account =
      CreateAccount(kSecondaryAccount1Email, secondary_account_info.gaia);

  auto account_apps_availability = CreateAccountAppsAvailability();
  // Wait for initialization to finish.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(account_apps_availability->IsInitialized());
  // All accounts are available after initialization:
  auto accounts =
      GetAccountsAvailableInArcSync(account_apps_availability.get());
  EXPECT_EQ(accounts.size(), 2u);
  EXPECT_THAT(accounts, Contains(AccountEqual(primary_account)));
  EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account)));

  // Remove an account from ARC.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account,
                                                        false);
  auto accounts_1 =
      GetAccountsAvailableInArcSync(account_apps_availability.get());
  EXPECT_EQ(accounts_1.size(), 1u);
  EXPECT_THAT(accounts_1, Contains(AccountEqual(primary_account)));
}

TEST_F(AccountAppsAvailabilityTest, SetIsAccountAvailableInArc) {
  const AccountInfo secondary_account_1_info =
      identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email);
  const account_manager::Account primary_account =
      CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia);
  const account_manager::Account secondary_account_1 =
      CreateAccount(kSecondaryAccount1Email, secondary_account_1_info.gaia);

  auto account_apps_availability = CreateAccountAppsAvailability();
  // Wait for initialization to finish.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(account_apps_availability->IsInitialized());
  // All accounts are available after initialization:
  {
    auto accounts =
        GetAccountsAvailableInArcSync(account_apps_availability.get());
    EXPECT_EQ(accounts.size(), 2u);
    EXPECT_THAT(accounts, Contains(AccountEqual(primary_account)));
    EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account_1)));
  }

  // Remove an account from ARC.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1,
                                                        false);
  {
    auto accounts =
        GetAccountsAvailableInArcSync(account_apps_availability.get());
    EXPECT_EQ(accounts.size(), 1u);
    EXPECT_THAT(accounts, Contains(AccountEqual(primary_account)));
  }

  const AccountInfo secondary_account_2_info =
      identity_test_env()->MakeAccountAvailable(kSecondaryAccount2Email);
  const account_manager::Account secondary_account_2 =
      CreateAccount(kSecondaryAccount2Email, secondary_account_2_info.gaia);
  // Add the account to ARC.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_2,
                                                        true);
  {
    auto accounts =
        GetAccountsAvailableInArcSync(account_apps_availability.get());
    EXPECT_EQ(accounts.size(), 2u);
    EXPECT_THAT(accounts, Contains(AccountEqual(primary_account)));
    EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account_2)));
  }

  // Remove the first account again.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1,
                                                        false);
  // Add the second account again.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_2,
                                                        true);
  {
    auto accounts =
        GetAccountsAvailableInArcSync(account_apps_availability.get());
    EXPECT_EQ(accounts.size(), 2u);
    EXPECT_THAT(accounts, Contains(AccountEqual(primary_account)));
    EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account_2)));
  }

  // Add the first account back.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1,
                                                        true);
  // Remove the second account.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_2,
                                                        false);
  {
    auto accounts =
        GetAccountsAvailableInArcSync(account_apps_availability.get());
    EXPECT_EQ(accounts.size(), 2u);
    EXPECT_THAT(accounts, Contains(AccountEqual(primary_account)));
    EXPECT_THAT(accounts, Contains(AccountEqual(secondary_account_1)));
  }
}

TEST_F(AccountAppsAvailabilityTest, ObserversAreCalledWhenAvailabilityChanges) {
  const AccountInfo secondary_account_1_info =
      identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email);
  const account_manager::Account primary_account =
      CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia);
  const account_manager::Account secondary_account_1 =
      CreateAccount(kSecondaryAccount1Email, secondary_account_1_info.gaia);

  auto account_apps_availability = CreateAccountAppsAvailability();
  // Wait for initialization to finish.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(account_apps_availability->IsInitialized());

  MockObserver mock_observer;
  base::ScopedObservation<AccountAppsAvailability,
                          AccountAppsAvailability::Observer>
      observation{&mock_observer};
  observation.Observe(account_apps_availability.get());

  Checkpoint checkpoint;
  {
    InSequence s;

    EXPECT_CALL(mock_observer,
                OnAccountUnavailableInArc(AccountEqual(secondary_account_1)))
        .Times(1);
    EXPECT_CALL(checkpoint, Call(1));
    EXPECT_CALL(mock_observer,
                OnAccountAvailableInArc(AccountEqual(secondary_account_1)))
        .Times(1);
  }

  // [Account is available in ARC] Remove an account from ARC - observer is
  // called.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1,
                                                        false);

  checkpoint.Call(1);

  // [Account is NOT available in ARC] Add an account to ARC - observer is
  // called.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1,
                                                        true);
}

TEST_F(AccountAppsAvailabilityTest,
       ObserversAreNotCalledWhenAvailabilityDoesntChange) {
  const AccountInfo secondary_account_1_info =
      identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email);
  const account_manager::Account primary_account =
      CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia);
  const account_manager::Account secondary_account_1 =
      CreateAccount(kSecondaryAccount1Email, secondary_account_1_info.gaia);

  auto account_apps_availability = CreateAccountAppsAvailability();
  // Wait for initialization to finish.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(account_apps_availability->IsInitialized());

  MockObserver mock_observer;
  base::ScopedObservation<AccountAppsAvailability,
                          AccountAppsAvailability::Observer>
      observation{&mock_observer};
  observation.Observe(account_apps_availability.get());

  Checkpoint checkpoint;
  {
    InSequence s;

    EXPECT_CALL(mock_observer, OnAccountAvailableInArc(_)).Times(0);
    EXPECT_CALL(mock_observer, OnAccountUnavailableInArc(_)).Times(0);
    EXPECT_CALL(checkpoint, Call(1));
    EXPECT_CALL(mock_observer, OnAccountAvailableInArc(_)).Times(0);
    EXPECT_CALL(mock_observer, OnAccountUnavailableInArc(_)).Times(0);
  }

  // [Account is available in ARC] Add the same account again - observer is not
  // called.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1,
                                                        true);
  checkpoint.Call(1);

  const AccountInfo secondary_account_2_info =
      identity_test_env()->MakeAccountAvailable(kSecondaryAccount2Email);
  const account_manager::Account secondary_account_2 =
      CreateAccount(kSecondaryAccount2Email, secondary_account_2_info.gaia);

  // [Account is NOT available in ARC] Account is removed from ARC - observer is
  // not called.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_2,
                                                        false);
}

TEST_F(AccountAppsAvailabilityTest,
       ObserversAreCalledWhenAvailableAccountIsChanged) {
  const AccountInfo secondary_account_1_info =
      identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email);
  const account_manager::Account primary_account =
      CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia);
  const account_manager::Account secondary_account_1 =
      CreateAccount(kSecondaryAccount1Email, secondary_account_1_info.gaia);

  auto account_apps_availability = CreateAccountAppsAvailability();
  // Wait for initialization to finish.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(account_apps_availability->IsInitialized());

  MockObserver mock_observer;
  base::ScopedObservation<AccountAppsAvailability,
                          AccountAppsAvailability::Observer>
      observation{&mock_observer};
  observation.Observe(account_apps_availability.get());

  Checkpoint checkpoint;
  {
    InSequence s;

    EXPECT_CALL(mock_observer,
                OnAccountAvailableInArc(AccountEqual(secondary_account_1)))
        .Times(1);
    EXPECT_CALL(checkpoint, Call(1));
    EXPECT_CALL(mock_observer,
                OnAccountUnavailableInArc(AccountEqual(secondary_account_1)))
        .Times(1);
  }

  // [Account is available in ARC] Account is upserted - observer is called.
  identity_test_env()->SetRefreshTokenForAccount(
      secondary_account_1_info.account_id);
  // Wait for async calls to finish.
  base::RunLoop().RunUntilIdle();
  checkpoint.Call(1);

  // [Account is available in ARC] Account is removed - observer is
  // called.
  identity_test_env()->RemoveRefreshTokenForAccount(
      secondary_account_1_info.account_id);
  // Wait for async calls to finish.
  base::RunLoop().RunUntilIdle();
}

TEST_F(AccountAppsAvailabilityTest,
       ObserversAreNotCalledWhenUnavailableAccountIsChanged) {
  const AccountInfo secondary_account_1_info =
      identity_test_env()->MakeAccountAvailable(kSecondaryAccount1Email);
  const account_manager::Account primary_account =
      CreateAccount(kPrimaryAccountEmail, primary_account_info()->gaia);
  const account_manager::Account secondary_account_1 =
      CreateAccount(kSecondaryAccount1Email, secondary_account_1_info.gaia);

  auto account_apps_availability = CreateAccountAppsAvailability();
  // Wait for initialization to finish.
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(account_apps_availability->IsInitialized());

  MockObserver mock_observer;
  base::ScopedObservation<AccountAppsAvailability,
                          AccountAppsAvailability::Observer>
      observation{&mock_observer};
  observation.Observe(account_apps_availability.get());

  Checkpoint checkpoint;
  {
    InSequence s;

    EXPECT_CALL(mock_observer,
                OnAccountUnavailableInArc(AccountEqual(secondary_account_1)))
        .Times(1);
    EXPECT_CALL(checkpoint, Call(1));
    EXPECT_CALL(mock_observer, OnAccountAvailableInArc(_)).Times(0);
    EXPECT_CALL(mock_observer, OnAccountUnavailableInArc(_)).Times(0);
    EXPECT_CALL(checkpoint, Call(2));
    EXPECT_CALL(mock_observer, OnAccountAvailableInArc(_)).Times(0);
    EXPECT_CALL(mock_observer, OnAccountUnavailableInArc(_)).Times(0);
  }

  // Remove an account from ARC.
  account_apps_availability->SetIsAccountAvailableInArc(secondary_account_1,
                                                        false);
  checkpoint.Call(1);

  // [Account is NOT available in ARC] Account is upserted - observer is not
  // called.
  identity_test_env()->SetRefreshTokenForAccount(
      secondary_account_1_info.account_id);
  // Wait for async calls to finish.
  base::RunLoop().RunUntilIdle();
  checkpoint.Call(2);

  // [Account is NOT available in ARC] Account is removed - observer is not
  // called.
  identity_test_env()->RemoveRefreshTokenForAccount(
      secondary_account_1_info.account_id);
  // Wait for async calls to finish.
  base::RunLoop().RunUntilIdle();
}

}  // namespace ash