chromium/chromeos/ash/components/boca/babelorca/oauth_token_fetcher_unittest.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/boca/babelorca/oauth_token_fetcher.h"

#include <optional>

#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chromeos/ash/components/boca/babelorca/token_data_wrapper.h"
#include "chromeos/ash/components/boca/babelorca/token_fetcher.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::babelorca {
namespace {

constexpr char kOAuthToken[] = "test-token";
constexpr base::Time kExpirationTime =
    base::Time::FromDeltaSinceWindowsEpoch(base::Days(50000));
constexpr char kIdToken[] = "id-test-token";
constexpr base::TimeDelta kRetryInitialBackoff = base::Milliseconds(500);

class OAuthTokenFetcherTest : public testing::Test {
 protected:
  // testing::Test:
  void SetUp() override {
    account_info_ = identity_test_env_.MakeAccountAvailable("[email protected]");
    identity_test_env_.SetPrimaryAccount(account_info_.email,
                                         signin::ConsentLevel::kSignin);
  }

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  signin::IdentityTestEnvironment identity_test_env_;
  AccountInfo account_info_;
};

TEST_F(OAuthTokenFetcherTest, SuccessfulOAuthTokenFetch) {
  base::test::TestFuture<std::optional<TokenDataWrapper>> fetch_future;
  OAuthTokenFetcher oauth_token_fetcher(identity_test_env_.identity_manager());

  oauth_token_fetcher.FetchToken(fetch_future.GetCallback());
  identity_test_env_
      .WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
          kOAuthToken, kExpirationTime, kIdToken,
          {GaiaConstants::kTachyonOAuthScope});

  auto token_data = fetch_future.Get();
  ASSERT_TRUE(token_data.has_value());
  EXPECT_THAT(token_data->token, testing::StrEq(kOAuthToken));
  EXPECT_EQ(token_data->expiration_time, kExpirationTime);
}

TEST_F(OAuthTokenFetcherTest, FailedOAuthTokenFetch) {
  base::test::TestFuture<std::optional<TokenDataWrapper>> fetch_future;
  OAuthTokenFetcher oauth_token_fetcher(identity_test_env_.identity_manager());

  oauth_token_fetcher.FetchToken(fetch_future.GetCallback());
  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      account_info_.account_id,
      GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR));

  auto token_data = fetch_future.Get();
  EXPECT_FALSE(token_data.has_value());
}

TEST_F(OAuthTokenFetcherTest, OverlappingTokenFetchShouldFail) {
  base::test::TestFuture<std::optional<TokenDataWrapper>> first_fetch_future;
  base::test::TestFuture<std::optional<TokenDataWrapper>> overlap_fetch_future;
  OAuthTokenFetcher oauth_token_fetcher(identity_test_env_.identity_manager());

  oauth_token_fetcher.FetchToken(first_fetch_future.GetCallback());
  oauth_token_fetcher.FetchToken(overlap_fetch_future.GetCallback());
  identity_test_env_
      .WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
          kOAuthToken, kExpirationTime, kIdToken,
          {GaiaConstants::kTachyonOAuthScope});

  EXPECT_TRUE(first_fetch_future.Get().has_value());
  EXPECT_FALSE(overlap_fetch_future.Get().has_value());
}

TEST_F(OAuthTokenFetcherTest, SuccessiveTokenFetchShouldProceed) {
  base::test::TestFuture<std::optional<TokenDataWrapper>> first_fetch_future;
  base::test::TestFuture<std::optional<TokenDataWrapper>> second_fetch_future;
  OAuthTokenFetcher oauth_token_fetcher(identity_test_env_.identity_manager());

  oauth_token_fetcher.FetchToken(first_fetch_future.GetCallback());
  identity_test_env_
      .WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
          kOAuthToken, kExpirationTime, kIdToken,
          {GaiaConstants::kTachyonOAuthScope});
  oauth_token_fetcher.FetchToken(second_fetch_future.GetCallback());
  identity_test_env_
      .WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
          kOAuthToken, kExpirationTime, kIdToken,
          {GaiaConstants::kTachyonOAuthScope});

  EXPECT_TRUE(first_fetch_future.Get().has_value());
  EXPECT_TRUE(second_fetch_future.Get().has_value());
}

TEST_F(OAuthTokenFetcherTest, RetryOnRetriableError) {
  base::test::TestFuture<std::optional<TokenDataWrapper>> fetch_future;
  OAuthTokenFetcher oauth_token_fetcher(identity_test_env_.identity_manager());

  oauth_token_fetcher.FetchToken(fetch_future.GetCallback());
  // Failure.
  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      account_info_.account_id,
      GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
  // First retry success.
  task_environment_.FastForwardBy(kRetryInitialBackoff);
  identity_test_env_
      .WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
          kOAuthToken, kExpirationTime, kIdToken,
          {GaiaConstants::kTachyonOAuthScope});

  EXPECT_TRUE(fetch_future.Get().has_value());
}

TEST_F(OAuthTokenFetcherTest, RespondOnLastSuccessfulRetry) {
  base::test::TestFuture<std::optional<TokenDataWrapper>> fetch_future;
  OAuthTokenFetcher oauth_token_fetcher(identity_test_env_.identity_manager());

  oauth_token_fetcher.FetchToken(fetch_future.GetCallback());
  // Failure.
  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      account_info_.account_id,
      GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
  task_environment_.FastForwardBy(kRetryInitialBackoff);
  // First retry failure.
  task_environment_.FastForwardBy(kRetryInitialBackoff);
  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      account_info_.account_id,
      GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED));
  // Second retry success.
  task_environment_.FastForwardBy(kRetryInitialBackoff * 2);
  identity_test_env_
      .WaitForAccessTokenRequestIfNecessaryAndRespondWithTokenForScopes(
          kOAuthToken, kExpirationTime, kIdToken,
          {GaiaConstants::kTachyonOAuthScope});

  EXPECT_TRUE(fetch_future.Get().has_value());
}

TEST_F(OAuthTokenFetcherTest, RespondAfterMaxRetries) {
  base::test::TestFuture<std::optional<TokenDataWrapper>> fetch_future;
  OAuthTokenFetcher oauth_token_fetcher(identity_test_env_.identity_manager());

  oauth_token_fetcher.FetchToken(fetch_future.GetCallback());
  // Failure.
  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      account_info_.account_id,
      GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
  // First retry failure.
  task_environment_.FastForwardBy(kRetryInitialBackoff);
  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      account_info_.account_id,
      GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED));
  // Second retry failure.
  task_environment_.FastForwardBy(kRetryInitialBackoff * 2);
  identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
      account_info_.account_id,
      GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));

  EXPECT_FALSE(fetch_future.Get().has_value());
}

}  // namespace
}  // namespace ash::babelorca