// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/signin/internal/identity_manager/oauth2_token_service_delegate_android.h"

#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::InSequence;
using ::testing::Return;
using ::testing::Sequence;
using ::testing::StrictMock;

namespace signin {

namespace {
const std::vector<CoreAccountId> kEmptyVector;
class OAuth2TokenServiceDelegateAndroidForTest
    : public OAuth2TokenServiceDelegateAndroid {
 public:
  OAuth2TokenServiceDelegateAndroidForTest(
      AccountTrackerService* account_tracker_service)
      : OAuth2TokenServiceDelegateAndroid(account_tracker_service) {}
  MOCK_METHOD1(SetAccounts, void(const std::vector<CoreAccountId>&));
};

class TestObserver : public OAuth2TokenServiceObserver {
 public:
  MOCK_METHOD1(OnRefreshTokenAvailable, void(const CoreAccountId&));
  MOCK_METHOD1(OnRefreshTokenRevoked, void(const CoreAccountId&));
  MOCK_METHOD0(OnRefreshTokensLoaded, void());
};
}  // namespace

class OAuth2TokenServiceDelegateAndroidTest : public testing::Test {
 public:
  OAuth2TokenServiceDelegateAndroidTest() {}
  ~OAuth2TokenServiceDelegateAndroidTest() override = default;

 protected:
  void SetUp() override {
    testing::Test::SetUp();
    AccountTrackerService::RegisterPrefs(pref_service_.registry());
    account_tracker_service_.Initialize(&pref_service_, base::FilePath());
    OAuth2TokenServiceDelegateAndroid::
        set_disable_interaction_with_system_accounts();
    delegate_ = std::make_unique<OAuth2TokenServiceDelegateAndroidForTest>(
        &account_tracker_service_);
    delegate_->AddObserver(&observer_);
    CreateAndSeedAccounts();
  }

  void TearDown() override {
    delegate_->RemoveObserver(&observer_);
    testing::Test::TearDown();
  }

  AccountInfo CreateAccountInfo(const std::string& gaia_id,
                                const std::string& email) {
    AccountInfo account_info;

    account_info.gaia = gaia_id;
    account_info.email = email;
    account_info.full_name = "fullname";
    account_info.given_name = "givenname";
    account_info.hosted_domain = "example.com";
    account_info.locale = "en";
    account_info.picture_url = "https://example.com";
    account_info.is_child_account = false;
    account_info.account_id = account_tracker_service_.PickAccountIdForAccount(
        account_info.gaia, account_info.email);

    DCHECK(account_info.IsValid());

    return account_info;
  }

  void CreateAndSeedAccounts() {
    account1_ = CreateAccountInfo("gaia-id-user-1", "user-1@example.com");
    account2_ = CreateAccountInfo("gaia-id-user-2", "user-2@example.com");
    // SeedAccountInfo is required for
    // OAuth2TokenServiceDelegateAndrod::MapAccountNameToAccountId
    account_tracker_service_.SeedAccountInfo(account1_);
    account_tracker_service_.SeedAccountInfo(account2_);
  }

  AccountTrackerService account_tracker_service_;
  sync_preferences::TestingPrefServiceSyncable pref_service_;
  std::unique_ptr<OAuth2TokenServiceDelegateAndroidForTest> delegate_;
  StrictMock<TestObserver> observer_;

  AccountInfo account1_;
  AccountInfo account2_;
};

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith0SystemAccount0AccountAndNotSignedIn) {
  EXPECT_CALL(*delegate_, SetAccounts(kEmptyVector)).WillOnce(Return());
  // No observer call expected
  delegate_->UpdateAccountList(std::string(), {}, {});
  EXPECT_TRUE(account_tracker_service_.GetAccounts().empty());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith1SystemAccount0AccountAndNotSignedIn) {
  EXPECT_CALL(*delegate_, SetAccounts(kEmptyVector)).WillOnce(Return());
  // No observer call expected
  delegate_->UpdateAccountList(std::string(), {}, {account1_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>{account1_},
            account_tracker_service_.GetAccounts());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith1SystemAccount1AccountAndNotSignedIn) {
  Sequence seq;
  EXPECT_CALL(*delegate_, SetAccounts(kEmptyVector))
      .InSequence(seq)
      .WillOnce(Return());
  // Stored account from |GetAccounts| must fire a revoked event
  EXPECT_CALL(observer_, OnRefreshTokenRevoked(account1_.account_id))
      .InSequence(seq)
      .WillOnce(Return());

  delegate_->UpdateAccountList(std::string(), {account1_.account_id},
                               {account1_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>{account1_},
            account_tracker_service_.GetAccounts());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith1SystemAccount0AccountAndSignedIn) {
  Sequence seq;
  EXPECT_CALL(*delegate_,
              SetAccounts(std::vector<CoreAccountId>({account1_.account_id})))
      .InSequence(seq)
      .WillOnce(Return());
  EXPECT_CALL(observer_, OnRefreshTokenAvailable(account1_.account_id))
      .InSequence(seq)
      .WillOnce(Return());

  delegate_->UpdateAccountList(account1_.account_id, {},
                               {account1_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>{account1_},
            account_tracker_service_.GetAccounts());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith1SystemAccount1AccountAndSignedIn) {
  Sequence seq;
  EXPECT_CALL(*delegate_,
              SetAccounts(std::vector<CoreAccountId>({account1_.account_id})))
      .InSequence(seq)
      .WillOnce(Return());
  EXPECT_CALL(observer_, OnRefreshTokenAvailable(account1_.account_id))
      .InSequence(seq)
      .WillOnce(Return());

  delegate_->UpdateAccountList(account1_.account_id, {account1_.account_id},
                               {account1_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>{account1_},
            account_tracker_service_.GetAccounts());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith1SystemAccount1AccountDifferentAndSignedIn) {
  Sequence seq;
  EXPECT_CALL(*delegate_,
              SetAccounts(std::vector<CoreAccountId>({account1_.account_id})))
      .InSequence(seq)
      .WillOnce(Return());
  // Previously stored account is removed, new account is available
  EXPECT_CALL(observer_, OnRefreshTokenAvailable(account1_.account_id))
      .InSequence(seq)
      .WillOnce(Return());
  EXPECT_CALL(observer_, OnRefreshTokenRevoked(account2_.account_id))
      .InSequence(seq)
      .WillOnce(Return());

  delegate_->UpdateAccountList(account1_.account_id, {account2_.account_id},
                               {account1_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>{account1_},
            account_tracker_service_.GetAccounts());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith0SystemAccount1AccountSignedIn) {
  Sequence seq;
  EXPECT_CALL(*delegate_, SetAccounts(kEmptyVector))
      .InSequence(seq)
      .WillOnce(Return());
  EXPECT_CALL(observer_, OnRefreshTokenRevoked(account1_.account_id))
      .InSequence(seq)
      .WillOnce(Return());

  delegate_->UpdateAccountList(account1_.account_id, {account1_.account_id},
                               {});
  EXPECT_TRUE(account_tracker_service_.GetAccounts().empty());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith1SystemAccount0AccountAndSignedInDifferent) {
  EXPECT_CALL(*delegate_, SetAccounts(kEmptyVector)).WillOnce(Return());

  delegate_->UpdateAccountList(account2_.account_id, {},
                               {account1_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>{account1_},
            account_tracker_service_.GetAccounts());
}

// Test Getsysaccounts return a user != from signed user while GetAccounts not
// empty
TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith1SystemAccount1AccountAndSignedInDifferent) {
  Sequence seq;
  EXPECT_CALL(*delegate_, SetAccounts(kEmptyVector))
      .InSequence(seq)
      .WillOnce(Return());
  EXPECT_CALL(observer_, OnRefreshTokenRevoked(account1_.account_id))
      .InSequence(seq)
      .WillOnce(Return());

  delegate_->UpdateAccountList(account2_.account_id, {account1_.account_id},
                               {account1_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>{account1_},
            account_tracker_service_.GetAccounts());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith2SystemAccount0AccountAndSignedIn) {
  Sequence seq;
  EXPECT_CALL(*delegate_, SetAccounts(std::vector<CoreAccountId>(
                              {account1_.account_id, account2_.account_id})))
      .InSequence(seq)
      .WillOnce(Return());
  // OnRefreshTokenAvailable fired, signed in account should go first.
  EXPECT_CALL(observer_, OnRefreshTokenAvailable(account2_.account_id))
      .InSequence(seq)
      .WillOnce(Return());
  EXPECT_CALL(observer_, OnRefreshTokenAvailable(account1_.account_id))
      .InSequence(seq)
      .WillOnce(Return());

  delegate_->UpdateAccountList(account2_.account_id, {},
                               {account1_.account_id, account2_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>({account1_, account2_}),
            account_tracker_service_.GetAccounts());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith2SystemAccount1AccountAndSignedIn) {
  Sequence seq;
  EXPECT_CALL(*delegate_, SetAccounts(std::vector<CoreAccountId>(
                              {account1_.account_id, account2_.account_id})))
      .InSequence(seq)
      .WillOnce(Return());
  // OnRefreshTokenAvailable fired, signed in account should go first.
  EXPECT_CALL(observer_, OnRefreshTokenAvailable(account1_.account_id))
      .InSequence(seq)
      .WillOnce(Return());
  EXPECT_CALL(observer_, OnRefreshTokenAvailable(account2_.account_id))
      .InSequence(seq)
      .WillOnce(Return());
  delegate_->UpdateAccountList(account1_.account_id, {account2_.account_id},
                               {account1_.account_id, account2_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>({account1_, account2_}),
            account_tracker_service_.GetAccounts());
}

TEST_F(OAuth2TokenServiceDelegateAndroidTest,
       UpdateAccountListWith1SystemAccount2AccountAndSignedIn) {
  Sequence seq;
  EXPECT_CALL(*delegate_,
              SetAccounts(std::vector<CoreAccountId>({account1_.account_id})))
      .InSequence(seq)
      .WillOnce(Return());
  // OnRefreshTokenAvailable fired, signed in account should go first.
  EXPECT_CALL(observer_, OnRefreshTokenAvailable(account1_.account_id))
      .InSequence(seq)
      .WillOnce(Return());
  EXPECT_CALL(observer_, OnRefreshTokenRevoked(account2_.account_id))
      .InSequence(seq)
      .WillOnce(Return());

  delegate_->UpdateAccountList(account1_.account_id,
                               {account1_.account_id, account2_.account_id},
                               {account1_.account_id});
  EXPECT_EQ(std::vector<AccountInfo>({account1_}),
            account_tracker_service_.GetAccounts());
}

}  // namespace signin
