chromium/chromeos/components/kcer/token_key_finder_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/components/kcer/token_key_finder.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/timer/timer.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace kcer::internal {
namespace {

class KcerTokenKeyFinderTest : public ::testing::Test {
 protected:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
};

// Test that TokenKeyFinder can collect one "key found" result which is returned
// synchronously.
TEST_F(KcerTokenKeyFinderTest, OneKeyFoundResultSync) {
  base::test::TestFuture<base::expected<std::optional<Token>, Error>>
      result_waiter;

  auto finder = TokenKeyFinder::Create(
      /*results_to_receive=*/1, result_waiter.GetCallback());

  auto finder_callback = finder->GetCallback(Token::kDevice);

  finder.reset();  // It should be ok to delete the original ref-pointer now.

  EXPECT_FALSE(result_waiter.IsReady());

  std::move(finder_callback).Run(/*key_exists=*/true);

  EXPECT_TRUE(result_waiter.IsReady());
  ASSERT_TRUE(result_waiter.Get().has_value());
  EXPECT_EQ(result_waiter.Get().value(), Token::kDevice);
}

// Test that TokenKeyFinder can collect one "key found" result which is returned
// asynchronously.
TEST_F(KcerTokenKeyFinderTest, OneKeyFoundResultAsync) {
  base::test::TestFuture<base::expected<std::optional<Token>, Error>>
      result_waiter;

  auto finder = TokenKeyFinder::Create(
      /*results_to_receive=*/1, result_waiter.GetCallback());

  base::OneShotTimer fake_token;  // Runs a callback after a delay.
  fake_token.Start(FROM_HERE, base::Seconds(10),
                   base::BindOnce(finder->GetCallback(Token::kDevice),
                                  /*key_exists=*/true));

  finder.reset();  // It should be ok to delete the original ref-pointer now.

  EXPECT_FALSE(result_waiter.IsReady());

  task_environment_.FastForwardBy(base::Seconds(5));
  EXPECT_FALSE(result_waiter.IsReady());
  task_environment_.FastForwardBy(base::Seconds(6));

  EXPECT_TRUE(result_waiter.IsReady());
  ASSERT_TRUE(result_waiter.Get().has_value());
  EXPECT_EQ(result_waiter.Get().value(), Token::kDevice);
}

// Test that TokenKeyFinder can collect one "key not found" result.
TEST_F(KcerTokenKeyFinderTest, OneKeyNotFoundResult) {
  base::test::TestFuture<base::expected<std::optional<Token>, Error>>
      result_waiter;

  auto finder = TokenKeyFinder::Create(
      /*results_to_receive=*/1, result_waiter.GetCallback());

  base::OneShotTimer fake_token;  // Runs a callback after a delay.
  fake_token.Start(FROM_HERE, base::Seconds(10),
                   base::BindOnce(finder->GetCallback(Token::kDevice),
                                  /*key_exists=*/false));

  finder.reset();  // It should be ok to delete the original ref-pointer now.

  EXPECT_FALSE(result_waiter.IsReady());

  task_environment_.FastForwardBy(base::Seconds(11));

  EXPECT_TRUE(result_waiter.IsReady());
  ASSERT_TRUE(result_waiter.Get().has_value());
  EXPECT_FALSE(result_waiter.Get().value().has_value());
}

// Test that TokenKeyFinder can collect one failure result.
TEST_F(KcerTokenKeyFinderTest, OneFailureResult) {
  base::test::TestFuture<base::expected<std::optional<Token>, Error>>
      result_waiter;

  auto finder = TokenKeyFinder::Create(
      /*results_to_receive=*/1, result_waiter.GetCallback());

  base::OneShotTimer fake_token;  // Runs a callback after a delay.
  fake_token.Start(FROM_HERE, base::Seconds(10),
                   base::BindOnce(finder->GetCallback(Token::kUser),
                                  /*key_exists=*/base::unexpected(
                                      Error::kTokenInitializationFailed)));

  finder.reset();  // It should be ok to delete the original ref-pointer now.

  EXPECT_FALSE(result_waiter.IsReady());

  task_environment_.FastForwardBy(base::Seconds(11));

  EXPECT_TRUE(result_waiter.IsReady());
  ASSERT_FALSE(result_waiter.Get().has_value());
  EXPECT_EQ(result_waiter.Get().error(), Error::kTokenInitializationFailed);
}

// Test that TokenKeyFinder indicates when a key is found when it's found on
// both tokens (not supposed to happen, but still a theoretically possible edge
// case).
TEST_F(KcerTokenKeyFinderTest, TwoKeysFound) {
  base::test::TestFuture<base::expected<std::optional<Token>, Error>>
      result_waiter;

  auto finder = TokenKeyFinder::Create(
      /*results_to_receive=*/2, result_waiter.GetCallback());

  base::OneShotTimer fake_token_1;  // Runs a callback after a delay.
  fake_token_1.Start(FROM_HERE, base::Seconds(3),
                     base::BindOnce(finder->GetCallback(Token::kUser),
                                    /*key_exists=*/true));

  base::OneShotTimer fake_token_2;  // Runs a callback after a delay.
  fake_token_2.Start(FROM_HERE, base::Seconds(5),
                     base::BindOnce(finder->GetCallback(Token::kDevice),
                                    /*key_exists=*/true));

  finder.reset();  // It should be ok to delete the original ref-pointer now.

  EXPECT_FALSE(result_waiter.IsReady());

  task_environment_.FastForwardBy(base::Seconds(6));

  EXPECT_TRUE(result_waiter.IsReady());
  ASSERT_TRUE(result_waiter.Get().has_value());
  EXPECT_TRUE(result_waiter.Get().value().has_value());
}

// Test that TokenKeyFinder indicates that the key was not found when it was not
// found on both tokens.
TEST_F(KcerTokenKeyFinderTest, TwoTokensKeyNotFound) {
  base::test::TestFuture<base::expected<std::optional<Token>, Error>>
      result_waiter;

  auto finder = TokenKeyFinder::Create(
      /*results_to_receive=*/2, result_waiter.GetCallback());

  base::OneShotTimer fake_token_1;  // Runs a callback after a delay.
  fake_token_1.Start(FROM_HERE, base::Seconds(3),
                     base::BindOnce(finder->GetCallback(Token::kUser),
                                    /*key_exists=*/false));

  base::OneShotTimer fake_token_2;  // Runs a callback after a delay.
  fake_token_2.Start(FROM_HERE, base::Seconds(5),
                     base::BindOnce(finder->GetCallback(Token::kDevice),
                                    /*key_exists=*/false));

  finder.reset();  // It should be ok to delete the original ref-pointer now.

  EXPECT_FALSE(result_waiter.IsReady());

  task_environment_.FastForwardBy(base::Seconds(6));

  EXPECT_TRUE(result_waiter.IsReady());
  ASSERT_TRUE(result_waiter.Get().has_value());
  EXPECT_FALSE(result_waiter.Get().value().has_value());
}

// Test that TokenKeyFinder indicates that the key was found when it was found
// on one of the tokens.
TEST_F(KcerTokenKeyFinderTest, TwoTokensOneKeyFound) {
  base::test::TestFuture<base::expected<std::optional<Token>, Error>>
      result_waiter;

  auto finder = TokenKeyFinder::Create(
      /*results_to_receive=*/2, result_waiter.GetCallback());

  base::OneShotTimer fake_token_1;  // Runs a callback after a delay.
  fake_token_1.Start(FROM_HERE, base::Seconds(10),
                     base::BindOnce(finder->GetCallback(Token::kUser),
                                    /*key_exists=*/false));

  base::OneShotTimer fake_token_2;  // Runs a callback after a delay.
  fake_token_2.Start(FROM_HERE, base::Seconds(20),
                     base::BindOnce(finder->GetCallback(Token::kDevice),
                                    /*key_exists=*/true));

  finder.reset();  // It should be ok to delete the original ref-pointer now.

  EXPECT_FALSE(result_waiter.IsReady());

  task_environment_.FastForwardBy(base::Seconds(15));
  EXPECT_FALSE(result_waiter.IsReady());
  task_environment_.FastForwardBy(base::Seconds(7));

  EXPECT_TRUE(result_waiter.IsReady());
  ASSERT_TRUE(result_waiter.Get().has_value());
  EXPECT_EQ(result_waiter.Get().value(), Token::kDevice);
}

// Test that TokenKeyFinder returns an error when the key is not found on one
// token and another one returned an error.
TEST_F(KcerTokenKeyFinderTest, OneErrorOneNotFound) {
  base::test::TestFuture<base::expected<std::optional<Token>, Error>>
      result_waiter;

  auto finder = TokenKeyFinder::Create(
      /*results_to_receive=*/2, result_waiter.GetCallback());

  base::OneShotTimer fake_token_1;  // Runs a callback after a delay.
  fake_token_1.Start(FROM_HERE, base::Seconds(3),
                     base::BindOnce(finder->GetCallback(Token::kUser),
                                    /*key_exists=*/base::unexpected(
                                        Error::kTokenInitializationFailed)));

  base::OneShotTimer fake_token_2;  // Runs a callback after a delay.
  fake_token_2.Start(FROM_HERE, base::Seconds(4),
                     base::BindOnce(finder->GetCallback(Token::kDevice),
                                    /*key_exists=*/false));

  finder.reset();  // It should be ok to delete the original ref-pointer now.

  EXPECT_FALSE(result_waiter.IsReady());

  task_environment_.FastForwardBy(base::Seconds(7));

  EXPECT_TRUE(result_waiter.IsReady());
  ASSERT_FALSE(result_waiter.Get().has_value());
  EXPECT_EQ(result_waiter.Get().error(), Error::kTokenInitializationFailed);
}

}  // namespace
}  // namespace kcer::internal