chromium/chromeos/components/kcer/token_results_merger_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_results_merger.h"

#include "base/containers/flat_map.h"
#include "base/memory/scoped_refptr.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 {

// "Move-only" is not required, but should be sufficient. Contains an `id` for
// comparison.
struct MoveOnlyType {
  // A convenience method for creating a vector of objects. The objects will
  // have ids 0, ..., `size`-1 .
  static std::vector<MoveOnlyType> CreateVector(size_t size) {
    return CreateVector(size, /*start_id=*/0);
  }
  // A convenience method for creating a vector of objects. The objects will
  // have ids `start_id`, ..., `start_id`+`size`-`1` .
  static std::vector<MoveOnlyType> CreateVector(size_t size, size_t start_id) {
    std::vector<MoveOnlyType> result;
    for (size_t i = 0; i < size; ++i) {
      result.emplace_back(start_id + i);
    }
    return result;
  }

  explicit MoveOnlyType(size_t object_id) : id(object_id) {}
  ~MoveOnlyType() = default;
  MoveOnlyType(MoveOnlyType&&) = default;
  MoveOnlyType& operator=(MoveOnlyType&&) = default;
  MoveOnlyType(const MoveOnlyType&) = delete;
  MoveOnlyType& operator=(const MoveOnlyType&) = delete;

  bool operator==(const MoveOnlyType& other) const { return (id == other.id); }

  size_t id;
};

namespace {

using Merger = TokenResultsMerger<MoveOnlyType>;

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

// Test that TokenResultsMerger can collect one success result which is returned
// synchronously.
TEST_F(KcerTokenResultsMergerTest, OneSuccessResultSync) {
  base::test::TestFuture<std::vector<MoveOnlyType>,
                         base::flat_map<Token, Error>>
      result_waiter;

  scoped_refptr<Merger> merger = Merger::Create(
      /*results_to_receive=*/1, result_waiter.GetCallback());

  auto merger_callback = merger->GetCallback(Token::kDevice);

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

  EXPECT_FALSE(result_waiter.IsReady());

  std::move(merger_callback).Run(MoveOnlyType::CreateVector(3));

  EXPECT_TRUE(result_waiter.IsReady());
  EXPECT_EQ(result_waiter.Get<0>(), MoveOnlyType::CreateVector(3));
  EXPECT_TRUE(result_waiter.Get<1>().empty());
}

// Test that TokenResultsMerger can collect one success result which is returned
// asynchronously.
TEST_F(KcerTokenResultsMergerTest, OneSuccessResultAsync) {
  base::test::TestFuture<std::vector<MoveOnlyType>,
                         base::flat_map<Token, Error>>
      result_waiter;

  scoped_refptr<Merger> merger = Merger::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(merger->GetCallback(Token::kDevice),
                                  MoveOnlyType::CreateVector(3)));

  merger.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());
  EXPECT_EQ(result_waiter.Get<0>(), MoveOnlyType::CreateVector(3));
  EXPECT_TRUE(result_waiter.Get<1>().empty());
}

// Test that TokenResultsMerger can collect one fail result.
TEST_F(KcerTokenResultsMergerTest, OneFailureResult) {
  base::test::TestFuture<std::vector<MoveOnlyType>,
                         base::flat_map<Token, Error>>
      result_waiter;

  scoped_refptr<Merger> merger = Merger::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(merger->GetCallback(Token::kDevice),
                                  base::unexpected(Error::kUnknownError)));

  merger.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());
  EXPECT_TRUE(result_waiter.Get<0>().empty());
  const base::flat_map<Token, Error>& error_map = result_waiter.Get<1>();
  ASSERT_TRUE(base::Contains(error_map, Token::kDevice));
  EXPECT_EQ(error_map.at(Token::kDevice), Error::kUnknownError);
}

// Test that TokenResultsMerger can collect two success results.
TEST_F(KcerTokenResultsMergerTest, TwoSuccessResults) {
  base::test::TestFuture<std::vector<MoveOnlyType>,
                         base::flat_map<Token, Error>>
      result_waiter;

  scoped_refptr<Merger> merger = Merger::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(merger->GetCallback(Token::kUser),
                                    MoveOnlyType::CreateVector(3, 0)));

  base::OneShotTimer fake_token_2;  // Runs a callback after a delay.
  fake_token_2.Start(FROM_HERE, base::Seconds(5),
                     base::BindOnce(merger->GetCallback(Token::kDevice),
                                    MoveOnlyType::CreateVector(4, 3)));

  merger.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());
  EXPECT_EQ(result_waiter.Get<0>(), MoveOnlyType::CreateVector(7));
  EXPECT_TRUE(result_waiter.Get<1>().empty());
}

// Test that TokenResultsMerger can collect two fail results.
TEST_F(KcerTokenResultsMergerTest, TwoFailureResults) {
  base::test::TestFuture<std::vector<MoveOnlyType>,
                         base::flat_map<Token, Error>>
      result_waiter;

  scoped_refptr<Merger> merger = Merger::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(5),
      base::BindOnce(merger->GetCallback(Token::kUser),
                     base::unexpected(Error::kTokenIsNotAvailable)));

  base::OneShotTimer fake_token_2;  // Runs a callback after a delay.
  fake_token_2.Start(FROM_HERE, base::Seconds(5),
                     base::BindOnce(merger->GetCallback(Token::kDevice),
                                    base::unexpected(Error::kUnknownError)));

  merger.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());
  EXPECT_TRUE(result_waiter.Get<0>().empty());
  const base::flat_map<Token, Error>& error_map = result_waiter.Get<1>();
  ASSERT_TRUE(base::Contains(error_map, Token::kDevice));
  ASSERT_TRUE(base::Contains(error_map, Token::kUser));
  EXPECT_EQ(error_map.at(Token::kDevice), Error::kUnknownError);
  EXPECT_EQ(error_map.at(Token::kUser), Error::kTokenIsNotAvailable);
}

// Test that TokenResultsMerger can collect one fail with one success results.
TEST_F(KcerTokenResultsMergerTest, OneFailOneSuccessResults) {
  base::test::TestFuture<std::vector<MoveOnlyType>,
                         base::flat_map<Token, Error>>
      result_waiter;

  scoped_refptr<Merger> merger = Merger::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(merger->GetCallback(Token::kUser),
                                    MoveOnlyType::CreateVector(3)));

  base::OneShotTimer fake_token_2;  // Runs a callback after a delay.
  fake_token_2.Start(FROM_HERE, base::Seconds(5),
                     base::BindOnce(merger->GetCallback(Token::kDevice),
                                    base::unexpected(Error::kUnknownError)));

  merger.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());
  EXPECT_EQ(result_waiter.Get<0>(), MoveOnlyType::CreateVector(3));
  const base::flat_map<Token, Error>& error_map = result_waiter.Get<1>();
  ASSERT_TRUE(base::Contains(error_map, Token::kDevice));
  EXPECT_EQ(error_map.at(Token::kDevice), Error::kUnknownError);
}

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