blob: 0aaefcd38630cfd6a49eb2a35f146ba3a4db6df7 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/reporting/client/report_queue_provider.h"
#include <memory>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "components/reporting/client/mock_report_queue.h"
#include "components/reporting/client/mock_report_queue_provider.h"
#include "components/reporting/client/report_queue.h"
#include "components/reporting/client/report_queue_configuration.h"
#include "components/reporting/client/report_queue_provider_test_helper.h"
#include "components/reporting/util/statusor.h"
#include "components/reporting/util/test_support_callbacks.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::StrEq;
using ::testing::WithArg;
namespace reporting {
namespace {
constexpr char kTestMessage[] = "TEST MESSAGE";
class ReportQueueProviderTest : public ::testing::Test {
protected:
void SetUp() override {
helper_ = std::make_unique<test::ReportQueueProviderTestHelper>();
}
void TearDown() override { helper_.reset(); }
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<test::ReportQueueProviderTestHelper> helper_;
const Destination destination_ = Destination::UPLOAD_EVENTS;
};
// Asynchronously creates ReportingQueue and enqueue message.
// Returns result with callback.
void CreateQueuePostData(
std::string data,
Priority priority,
std::unique_ptr<ReportQueueConfiguration> config,
scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner,
ReportQueue::EnqueueCallback done_cb) {
base::OnceCallback<void(StatusOr<std::unique_ptr<ReportQueue>>)> queue_cb =
base::BindOnce(
[](std::string data, Priority priority,
ReportQueue::EnqueueCallback done_cb,
StatusOr<std::unique_ptr<ReportQueue>> report_queue_result) {
// Bail out if queue failed to create.
if (!report_queue_result.has_value()) {
std::move(done_cb).Run(report_queue_result.error());
return;
}
// Queue created successfully, enqueue the message on a random
// thread and verify.
EXPECT_CALL(*static_cast<MockReportQueue*>(
report_queue_result.value().get()),
AddRecord(StrEq(data), Eq(priority), _))
.WillOnce(WithArg<2>([](ReportQueue::EnqueueCallback cb) {
std::move(cb).Run(Status::StatusOK());
}));
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](std::unique_ptr<ReportQueue> queue, std::string data,
Priority priority,
ReportQueue::EnqueueCallback done_cb) {
queue->Enqueue(data, priority, std::move(done_cb));
},
std::move(report_queue_result.value()), data, priority,
std::move(done_cb)));
},
std::string(data), priority, std::move(done_cb));
ReportQueueProvider::CreateQueue(
std::move(config),
// Verification callback needs to be serialized, because EXPECT_... do not
// support multithreading.
base::BindPostTask(sequenced_task_runner, std::move(queue_cb)));
}
// Asynchronously creates ReportingQueue and enqueue message.
// Returns result with callback.
void CreateSpeculativeQueuePostData(
std::string data,
Priority priority,
std::unique_ptr<ReportQueueConfiguration> config,
scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner,
ReportQueue::EnqueueCallback done_cb) {
auto report_queue_result =
ReportQueueProvider::CreateSpeculativeQueue(std::move(config));
// Bail out if queue failed to create.
if (!report_queue_result.has_value()) {
std::move(done_cb).Run(report_queue_result.error());
return;
}
// Queue created successfully, enqueue the message on a random thread and
// verify.
EXPECT_CALL(*static_cast<MockReportQueue*>(report_queue_result.value().get()),
AddRecord(StrEq(data), Eq(priority), _))
.WillOnce(WithArg<2>([](ReportQueue::EnqueueCallback cb) {
std::move(cb).Run(Status::StatusOK());
}));
// Enqueue on a random thread again.
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](std::unique_ptr<ReportQueue, base::OnTaskRunnerDeleter> queue,
std::string data, Priority priority,
ReportQueue::EnqueueCallback done_cb) {
queue->Enqueue(data, priority, std::move(done_cb));
},
std::move(report_queue_result.value()), data, priority,
// Verification callback needs to be serialized, because EXPECT_... do
// not support multithreading.
base::BindPostTask(sequenced_task_runner, std::move(done_cb))));
}
TEST_F(ReportQueueProviderTest, CreateAndGetQueue) {
// Create configuration.
auto config_result =
ReportQueueConfiguration::Create(
{.event_type = EventType::kDevice, .destination = destination_})
.Build();
ASSERT_TRUE(config_result.has_value());
EXPECT_CALL(*helper_->mock_provider(), OnInitCompletedMock()).Times(1);
helper_->mock_provider()->ExpectCreateNewQueueAndReturnNewMockQueue(1);
// Use it to asynchronously create ReportingQueue and then asynchronously
// send the message.
test::TestEvent<Status> e;
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&CreateQueuePostData, kTestMessage, FAST_BATCH,
std::move(config_result.value()),
base::SequencedTaskRunner::GetCurrentDefault(), e.cb()));
const auto res = e.result();
EXPECT_OK(res) << res;
}
TEST_F(ReportQueueProviderTest, CreateMultipleQueues) {
static constexpr std::array<std::pair<Priority, Destination>, 9> send_as{
std::make_pair(FAST_BATCH, HEARTBEAT_EVENTS),
std::make_pair(SLOW_BATCH, CRD_EVENTS),
std::make_pair(IMMEDIATE, LOGIN_LOGOUT_EVENTS),
std::make_pair(SLOW_BATCH, HEARTBEAT_EVENTS),
std::make_pair(FAST_BATCH, CRD_EVENTS),
std::make_pair(IMMEDIATE, LOGIN_LOGOUT_EVENTS),
std::make_pair(FAST_BATCH, HEARTBEAT_EVENTS),
std::make_pair(IMMEDIATE, CRD_EVENTS),
std::make_pair(SLOW_BATCH, LOGIN_LOGOUT_EVENTS),
};
test::TestCallbackAutoWaiter waiter;
waiter.Attach(send_as.size() + 1);
// Expect only one InitCompleted callback.
EXPECT_CALL(*helper_->mock_provider(), OnInitCompletedMock())
.WillOnce(Invoke(&waiter, &test::TestCallbackAutoWaiter::Signal));
// ... even though we create multiple queues.
helper_->mock_provider()->ExpectCreateNewQueueAndReturnNewMockQueue(
send_as.size());
for (const auto& s : send_as) {
// Create configuration.
auto config_result =
ReportQueueConfiguration::Create(
{.event_type = EventType::kDevice, .destination = s.second})
.Build();
ASSERT_TRUE(config_result.has_value());
// Compose the message.
std::string message = std::string(kTestMessage)
.append(" priority=")
.append(Priority_Name(s.first))
.append(" destination=")
.append(Destination_Name(s.second));
// Asynchronously create ReportingQueue and then asynchronously send the
// message.
auto done_cb = base::BindOnce(
[](test::TestCallbackWaiter* waiter, Status status) {
EXPECT_OK(status) << status;
waiter->Signal();
},
&waiter);
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&CreateQueuePostData, std::move(message),
/*priority=*/s.first, std::move(config_result.value()),
base::SequencedTaskRunner::GetCurrentDefault(),
std::move(done_cb)));
}
waiter.Signal(); // Release the waiter
}
TEST_F(ReportQueueProviderTest, CreateMultipleSpeculativeQueues) {
static constexpr std::array<std::pair<Priority, Destination>, 9> send_as{
std::make_pair(FAST_BATCH, HEARTBEAT_EVENTS),
std::make_pair(SLOW_BATCH, CRD_EVENTS),
std::make_pair(IMMEDIATE, LOGIN_LOGOUT_EVENTS),
std::make_pair(SLOW_BATCH, HEARTBEAT_EVENTS),
std::make_pair(FAST_BATCH, CRD_EVENTS),
std::make_pair(IMMEDIATE, LOGIN_LOGOUT_EVENTS),
std::make_pair(FAST_BATCH, HEARTBEAT_EVENTS),
std::make_pair(IMMEDIATE, CRD_EVENTS),
std::make_pair(SLOW_BATCH, LOGIN_LOGOUT_EVENTS),
};
test::TestCallbackAutoWaiter waiter;
waiter.Attach(send_as.size() + 1);
// Expect only one InitCompleted callback.
EXPECT_CALL(*helper_->mock_provider(), OnInitCompletedMock())
.WillOnce(Invoke(&waiter, &test::TestCallbackAutoWaiter::Signal));
// ... even though we create multiple queues.
helper_->mock_provider()
->ExpectCreateNewSpeculativeQueueAndReturnNewMockQueue(send_as.size());
for (const auto& s : send_as) {
// Create configuration.
auto config_result =
ReportQueueConfiguration::Create(
{.event_type = EventType::kDevice, .destination = s.second})
.Build();
ASSERT_TRUE(config_result.has_value());
// Compose the message.
std::string message = std::string(kTestMessage)
.append(" priority=")
.append(Priority_Name(s.first))
.append(" destination=")
.append(Destination_Name(s.second));
// Create SpeculativeReportingQueue and then asynchronously send the
// message.
auto done_cb = base::BindOnce(
[](test::TestCallbackWaiter* waiter, Status status) {
EXPECT_OK(status) << status;
waiter->Signal();
},
&waiter);
CreateSpeculativeQueuePostData(
std::move(message),
/*priority=*/s.first, std::move(config_result.value()),
base::SequencedTaskRunner::GetCurrentDefault(), std::move(done_cb));
}
waiter.Signal(); // Release the waiter
}
TEST_F(ReportQueueProviderTest,
CreateReportQueueWithEncryptedReportingPipelineDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(kEncryptedReportingPipeline);
// Create configuration
auto config_result =
ReportQueueConfiguration::Create(
{.event_type = EventType::kDevice, .destination = destination_})
.Build();
ASSERT_TRUE(config_result.has_value());
test::TestEvent<ReportQueueProvider::CreateReportQueueResponse> event;
ReportQueueProvider::CreateQueue(std::move(config_result.value()),
event.cb());
const auto result = event.result();
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code(), error::FAILED_PRECONDITION);
}
TEST_F(ReportQueueProviderTest,
CreateSpeculativeReportQueueWithEncryptedReportingPipelineDisabled) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(kEncryptedReportingPipeline);
// Create configuration
auto config_result =
ReportQueueConfiguration::Create(
{.event_type = EventType::kDevice, .destination = destination_})
.Build();
ASSERT_TRUE(config_result.has_value());
const auto result = ReportQueueProvider::CreateSpeculativeQueue(
std::move(config_result.value()));
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error().code(), error::FAILED_PRECONDITION);
}
} // namespace
} // namespace reporting