// 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