chromium/chromeos/ash/components/osauth/impl/auth_hub_attempt_handler_unittest.cc

// Copyright 2023 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/osauth/impl/auth_hub_attempt_handler.h"

#include <memory>

#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/osauth/impl/auth_hub_common.h"
#include "chromeos/ash/components/osauth/impl/auth_parts_impl.h"
#include "chromeos/ash/components/osauth/public/auth_factor_engine.h"
#include "chromeos/ash/components/osauth/public/auth_factor_status_consumer.h"
#include "chromeos/ash/components/osauth/public/common_types.h"
#include "chromeos/ash/components/osauth/test_support/mock_auth_factor_engine.h"
#include "chromeos/ash/components/osauth/test_support/mock_auth_factor_status_consumer.h"
#include "components/account_id/account_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

constexpr AshAuthFactor kFactor = AshAuthFactor::kGaiaPassword;
constexpr AshAuthFactor kSpecialFactor = AshAuthFactor::kLegacyPin;

using base::test::RunOnceCallback;
using testing::_;
using testing::AnyNumber;
using testing::ByMove;
using testing::Eq;
using testing::Invoke;
using testing::Mock;
using testing::Return;
using testing::SaveArg;
using testing::StrictMock;

class MockAttemptHandlerOwner : public AuthHubAttemptHandler::Owner {
 public:
  MockAttemptHandlerOwner() = default;
  ~MockAttemptHandlerOwner() override = default;

  MOCK_METHOD(void,
              UpdateFactorUiCache,
              (const AuthAttemptVector&, AuthFactorsSet),
              (override));
  MOCK_METHOD(void,
              OnAuthenticationSuccess,
              (const AuthAttemptVector&, AshAuthFactor factor),
              (override));
  MOCK_METHOD(void,
              OnFactorAttemptFailed,
              (const AuthAttemptVector&, AshAuthFactor),
              (override));
};

class AuthHubAttemptHandlerTest : public ::testing::Test {
 protected:
  AuthHubAttemptHandlerTest() {
    parts_ = AuthPartsImpl::CreateTestInstance();
    attempt_ = AuthAttemptVector{AccountId::FromUserEmail("[email protected]"),
                                 AuthPurpose::kLogin};
  }

  ~AuthHubAttemptHandlerTest() override = default;

  void AddAvailableFactor(AshAuthFactor factor) {
    engines_[factor] = std::make_unique<StrictMock<MockAuthFactorEngine>>();
    auto* engine = engines_[factor].get();
    engines_map_[factor] = engine;
    EXPECT_CALL(*engine, SetUsageAllowed(_))
        .Times(AnyNumber())
        .WillRepeatedly(
            Invoke([&, factor](AuthFactorEngine::UsageAllowed usage) {
              engine_usage_[factor] = usage;
            }));
  }

  void InitializeConsumer() {
    EXPECT_CALL(status_consumer_, InitializeUi(_, _))
        .WillOnce(
            Invoke([&](AuthFactorsSet factors, AuthHubConnector* connector) {
              for (auto factor : factors) {
                factors_state_[factor] = AuthFactorState::kCheckingForPresence;
              }
            }));
    handler_->SetConsumer(&status_consumer_);
  }

  void InitializeWithTwoFactors() {
    AddAvailableFactor(kFactor);
    AddAvailableFactor(kSpecialFactor);
    handler_ = std::make_unique<AuthHubAttemptHandler>(
        &owner_, attempt_, engines_map_,
        AuthFactorsSet{kFactor, kSpecialFactor});
    InitializeConsumer();
  }

  void ExpectFactorStateUpdate() {
    EXPECT_CALL(status_consumer_, OnFactorStatusesChanged(_))
        .WillOnce(Invoke([&](FactorsStatusMap update) {
          for (const auto& factor : update) {
            factors_state_[factor.first] = factor.second;
          }
        }))
        .RetiresOnSaturation();
  }

  void ExpectFactorListChange() {
    EXPECT_CALL(status_consumer_, OnFactorListChanged(_))
        .WillOnce(
            Invoke([&](FactorsStatusMap update) { factors_state_ = update; }))
        .RetiresOnSaturation();
  }

  void MarkFactorNotRestricted(AshAuthFactor factor) {
    auto* engine = engines_[factor].get();
    EXPECT_CALL(*engine, IsDisabledByPolicy())
        .Times(AnyNumber())
        .WillRepeatedly(Return(false));
    EXPECT_CALL(*engine, IsLockedOut())
        .Times(AnyNumber())
        .WillRepeatedly(Return(false));
    EXPECT_CALL(*engine, IsFactorSpecificRestricted())
        .Times(AnyNumber())
        .WillRepeatedly(Return(false));
  }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  AuthAttemptVector attempt_;
  std::unique_ptr<AuthPartsImpl> parts_;
  StrictMock<MockAttemptHandlerOwner> owner_;
  std::unique_ptr<AuthHubAttemptHandler> handler_;
  AuthEnginesMap engines_map_;
  base::flat_map<AshAuthFactor, std::unique_ptr<MockAuthFactorEngine>> engines_;
  base::flat_map<AshAuthFactor, AuthFactorEngine::UsageAllowed> engine_usage_;
  base::flat_map<AshAuthFactor, AuthFactorState> factors_state_;
  StrictMock<MockAuthFactorStatusConsumer> status_consumer_;
};

TEST_F(AuthHubAttemptHandlerTest, UpdateCacheIfFactorAbsent) {
  InitializeWithTwoFactors();
  MarkFactorNotRestricted(kFactor);
  ExpectFactorListChange();
  AuthFactorsSet factor_cache;
  EXPECT_CALL(owner_, UpdateFactorUiCache(Eq(attempt_), _))
      .WillOnce(SaveArg<1>(&factor_cache));

  handler_->OnFactorsChecked(AuthFactorsSet{kFactor}, AuthFactorsSet{});

  EXPECT_EQ(factors_state_.size(), 1u);
  EXPECT_EQ(factors_state_[kFactor], AuthFactorState::kFactorReady);

  EXPECT_EQ(factor_cache, AuthFactorsSet{kFactor});
}

TEST_F(AuthHubAttemptHandlerTest, NoCacheUpdateIfFactorFailed) {
  InitializeWithTwoFactors();
  MarkFactorNotRestricted(kFactor);
  ExpectFactorStateUpdate();
  handler_->OnFactorsChecked(AuthFactorsSet{kFactor},
                             AuthFactorsSet{kSpecialFactor});
  EXPECT_EQ(factors_state_.size(), 2u);
  EXPECT_EQ(factors_state_[kFactor], AuthFactorState::kFactorReady);
  EXPECT_EQ(factors_state_[kSpecialFactor], AuthFactorState::kEngineError);
}

TEST_F(AuthHubAttemptHandlerTest, UpdateCacheIfFactorAdded) {
  AddAvailableFactor(kFactor);
  MarkFactorNotRestricted(kFactor);
  AddAvailableFactor(kSpecialFactor);
  MarkFactorNotRestricted(kSpecialFactor);
  handler_ = std::make_unique<AuthHubAttemptHandler>(
      &owner_, attempt_, engines_map_, AuthFactorsSet{kFactor});
  InitializeConsumer();

  ExpectFactorListChange();

  AuthFactorsSet factor_cache;
  EXPECT_CALL(owner_, UpdateFactorUiCache(Eq(attempt_), _))
      .WillOnce(SaveArg<1>(&factor_cache));

  handler_->OnFactorsChecked(AuthFactorsSet{kFactor, kSpecialFactor},
                             AuthFactorsSet{});

  EXPECT_EQ(factors_state_.size(), 2u);
  EXPECT_EQ(factors_state_[kFactor], AuthFactorState::kFactorReady);
  EXPECT_EQ(factors_state_[kSpecialFactor], AuthFactorState::kFactorReady);

  EXPECT_EQ(factor_cache, AuthFactorsSet({kFactor, kSpecialFactor}));
}

// TODO (b/271248265): add tests for initial status determination;
// TODO (b/271248265): add tests for status priorities;
// TODO (b/271248265): add tests for status updates;
// TODO (b/271248265): add tests for auth attempts;

}  // namespace ash