blob: d6e44ba802b36148ec2dde8952fbf4768ff87719 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/policy/core/common/remote_commands/remote_commands_service.h"
#include <stddef.h>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/containers/queue.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/tick_clock.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/remote_commands/remote_command_job.h"
#include "components/policy/core/common/remote_commands/remote_commands_factory.h"
#include "components/policy/core/common/remote_commands/remote_commands_queue.h"
#include "components/policy/core/common/remote_commands/test_remote_command_job.h"
#include "components/policy/core/common/remote_commands/testing_remote_commands_server.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "net/url_request/url_request_context_getter.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ReturnNew;
namespace policy {
namespace {
namespace em = enterprise_management;
const char kDMToken[] = "dmtoken";
const char kTestPayload[] = "_testing_payload_";
const int kTestCommandExecutionTimeInSeconds = 1;
const int kTestClientServerCommunicationDelayInSeconds = 3;
void ExpectSucceededJob(const std::string& expected_payload,
const em::RemoteCommandResult& command_result) {
EXPECT_EQ(em::RemoteCommandResult_ResultType_RESULT_SUCCESS,
command_result.result());
EXPECT_EQ(expected_payload, command_result.payload());
}
} // namespace
// Mocked RemoteCommand factory to allow us to build test commands.
class MockTestRemoteCommandFactory : public RemoteCommandsFactory {
public:
MockTestRemoteCommandFactory() {
ON_CALL(*this, BuildTestCommand())
.WillByDefault(ReturnNew<TestRemoteCommandJob>(
true,
base::TimeDelta::FromSeconds(kTestCommandExecutionTimeInSeconds)));
}
MOCK_METHOD0(BuildTestCommand, TestRemoteCommandJob*());
private:
// RemoteCommandJobsFactory:
std::unique_ptr<RemoteCommandJob> BuildJobForType(
em::RemoteCommand_Type type) override {
if (type != em::RemoteCommand_Type_COMMAND_ECHO_TEST) {
ADD_FAILURE();
return nullptr;
}
return base::WrapUnique<RemoteCommandJob>(BuildTestCommand());
}
DISALLOW_COPY_AND_ASSIGN(MockTestRemoteCommandFactory);
};
// A mocked CloudPolicyClient to interact with a TestingRemoteCommandsServer.
class TestingCloudPolicyClientForRemoteCommands : public CloudPolicyClient {
public:
explicit TestingCloudPolicyClientForRemoteCommands(
TestingRemoteCommandsServer* server)
: CloudPolicyClient(std::string() /* machine_id */,
std::string() /* machine_model */,
std::string() /* brand_code */,
nullptr /* service */,
nullptr /* request_context */,
nullptr /* url_loader_factory */,
nullptr /* signing_service */,
CloudPolicyClient::DeviceDMTokenCallback()),
server_(server) {
dm_token_ = kDMToken;
}
~TestingCloudPolicyClientForRemoteCommands() override {
EXPECT_TRUE(expected_fetch_commands_calls_.empty());
}
// Expect a FetchRemoteCommands() call with |expected_command_results|
// commands results sent and |expected_fetched_commands| commands fetched.
// |commands_fetched_callback| will be executed after the fetch is processed.
void ExpectFetchCommands(size_t expected_command_results,
size_t expected_fetched_commands,
const base::Closure& commands_fetched_callback) {
expected_fetch_commands_calls_.push(FetchCallExpectation(
expected_command_results, expected_fetched_commands,
commands_fetched_callback));
}
private:
// Expectations for a single FetchRemoteCommands() call.
struct FetchCallExpectation {
FetchCallExpectation(size_t expected_command_results,
size_t expected_fetched_commands,
const base::Closure& commands_fetched_callback)
: expected_command_results(expected_command_results),
expected_fetched_commands(expected_fetched_commands),
commands_fetched_callback(commands_fetched_callback) {}
virtual ~FetchCallExpectation() {}
const size_t expected_command_results;
const size_t expected_fetched_commands;
const base::Closure commands_fetched_callback;
};
void FetchRemoteCommands(
std::unique_ptr<RemoteCommandJob::UniqueIDType> last_command_id,
const std::vector<em::RemoteCommandResult>& command_results,
const RemoteCommandCallback& callback) override {
ASSERT_FALSE(expected_fetch_commands_calls_.empty());
const FetchCallExpectation fetch_call_expectation =
expected_fetch_commands_calls_.front();
expected_fetch_commands_calls_.pop();
// Simulate delay from client to DMServer.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&TestingCloudPolicyClientForRemoteCommands::DoFetchRemoteCommands,
base::Unretained(this), std::move(last_command_id), command_results,
callback, fetch_call_expectation),
base::TimeDelta::FromSeconds(
kTestClientServerCommunicationDelayInSeconds));
}
void DoFetchRemoteCommands(
std::unique_ptr<RemoteCommandJob::UniqueIDType> last_command_id,
const std::vector<em::RemoteCommandResult>& command_results,
const RemoteCommandCallback& callback,
const FetchCallExpectation& fetch_call_expectation) {
const std::vector<em::RemoteCommand> fetched_commands =
server_->FetchCommands(std::move(last_command_id), command_results);
EXPECT_EQ(fetch_call_expectation.expected_command_results,
command_results.size());
EXPECT_EQ(fetch_call_expectation.expected_fetched_commands,
fetched_commands.size());
if (!fetch_call_expectation.commands_fetched_callback.is_null())
fetch_call_expectation.commands_fetched_callback.Run();
// Simulate delay from DMServer back to client.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::Bind(callback, DM_STATUS_SUCCESS, fetched_commands),
base::TimeDelta::FromSeconds(
kTestClientServerCommunicationDelayInSeconds));
}
base::queue<FetchCallExpectation> expected_fetch_commands_calls_;
TestingRemoteCommandsServer* server_;
DISALLOW_COPY_AND_ASSIGN(TestingCloudPolicyClientForRemoteCommands);
};
// Base class for unit tests regarding remote commands service.
class RemoteCommandsServiceTest : public testing::Test {
protected:
RemoteCommandsServiceTest() = default;
void SetUp() override {
server_.reset(new TestingRemoteCommandsServer());
server_->SetClock(mock_task_runner_->GetMockTickClock());
cloud_policy_client_.reset(
new TestingCloudPolicyClientForRemoteCommands(server_.get()));
}
void TearDown() override {
remote_commands_service_.reset();
cloud_policy_client_.reset();
server_.reset();
}
void StartService(std::unique_ptr<RemoteCommandsFactory> factory) {
remote_commands_service_.reset(new RemoteCommandsService(
std::move(factory), cloud_policy_client_.get()));
remote_commands_service_->SetClockForTesting(
mock_task_runner_->GetMockTickClock());
}
void FlushAllTasks() { mock_task_runner_->FastForwardUntilNoTasksRemain(); }
std::unique_ptr<TestingRemoteCommandsServer> server_;
std::unique_ptr<TestingCloudPolicyClientForRemoteCommands>
cloud_policy_client_;
std::unique_ptr<RemoteCommandsService> remote_commands_service_;
private:
const scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_ =
base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::TestMockTimeTaskRunner::Type::kBoundToThread);
DISALLOW_COPY_AND_ASSIGN(RemoteCommandsServiceTest);
};
// Tests that no command will be fetched if no commands is issued.
TEST_F(RemoteCommandsServiceTest, NoCommands) {
std::unique_ptr<MockTestRemoteCommandFactory> factory(
new MockTestRemoteCommandFactory());
EXPECT_CALL(*factory, BuildTestCommand()).Times(0);
StartService(std::move(factory));
// A fetch requst should get nothing from server.
cloud_policy_client_->ExpectFetchCommands(0u, 0u, base::Closure());
EXPECT_TRUE(remote_commands_service_->FetchRemoteCommands());
FlushAllTasks();
}
// Tests that existing commands issued before service started will be fetched.
TEST_F(RemoteCommandsServiceTest, ExistingCommand) {
std::unique_ptr<MockTestRemoteCommandFactory> factory(
new MockTestRemoteCommandFactory());
EXPECT_CALL(*factory, BuildTestCommand()).Times(1);
{
base::RunLoop run_loop;
// Issue a command before service started.
server_->IssueCommand(em::RemoteCommand_Type_COMMAND_ECHO_TEST,
kTestPayload,
base::Bind(&ExpectSucceededJob, kTestPayload), false);
// Start the service, run until the command is fetched.
cloud_policy_client_->ExpectFetchCommands(0u, 1u, run_loop.QuitClosure());
StartService(std::move(factory));
EXPECT_TRUE(remote_commands_service_->FetchRemoteCommands());
run_loop.Run();
}
// And run again so that the result can be reported.
cloud_policy_client_->ExpectFetchCommands(1u, 0u, base::Closure());
FlushAllTasks();
EXPECT_EQ(0u, server_->NumberOfCommandsPendingResult());
}
// Tests that commands issued after service started will be fetched.
TEST_F(RemoteCommandsServiceTest, NewCommand) {
std::unique_ptr<MockTestRemoteCommandFactory> factory(
new MockTestRemoteCommandFactory());
EXPECT_CALL(*factory, BuildTestCommand()).Times(1);
StartService(std::move(factory));
// Set up expectations on fetch commands calls. The first request will fetch
// one command, and the second will fetch none but provide result for the
// previous command instead.
cloud_policy_client_->ExpectFetchCommands(0u, 1u, base::Closure());
cloud_policy_client_->ExpectFetchCommands(1u, 0u, base::Closure());
// Issue a command and manually start a command fetch.
server_->IssueCommand(em::RemoteCommand_Type_COMMAND_ECHO_TEST, kTestPayload,
base::Bind(&ExpectSucceededJob, kTestPayload), false);
EXPECT_TRUE(remote_commands_service_->FetchRemoteCommands());
FlushAllTasks();
EXPECT_EQ(0u, server_->NumberOfCommandsPendingResult());
}
// Tests that commands issued after service started will be fetched, even if
// the command is issued when a fetch request is ongoing.
TEST_F(RemoteCommandsServiceTest, NewCommandFollwingFetch) {
std::unique_ptr<MockTestRemoteCommandFactory> factory(
new MockTestRemoteCommandFactory());
EXPECT_CALL(*factory, BuildTestCommand()).Times(1);
StartService(std::move(factory));
{
base::RunLoop run_loop;
// Add a command which will be issued after first fetch.
server_->IssueCommand(em::RemoteCommand_Type_COMMAND_ECHO_TEST,
kTestPayload,
base::Bind(&ExpectSucceededJob, kTestPayload), true);
cloud_policy_client_->ExpectFetchCommands(0u, 0u, run_loop.QuitClosure());
// Attempts to fetch commands.
EXPECT_TRUE(remote_commands_service_->FetchRemoteCommands());
// There should be no issued command at this point.
EXPECT_EQ(0u, server_->NumberOfCommandsPendingResult());
// The command fetch should be in progress.
EXPECT_TRUE(remote_commands_service_->IsCommandFetchInProgressForTesting());
// And a following up fetch request should be enqueued.
EXPECT_FALSE(remote_commands_service_->FetchRemoteCommands());
// Run until first fetch request is completed.
run_loop.Run();
}
// The command should be issued now. Note that this command was actually
// issued before the first fetch request completes in previous run loop.
EXPECT_EQ(1u, server_->NumberOfCommandsPendingResult());
cloud_policy_client_->ExpectFetchCommands(0u, 1u, base::Closure());
cloud_policy_client_->ExpectFetchCommands(1u, 0u, base::Closure());
// No further fetch request is made, but the new issued command should be
// fetched and executed.
FlushAllTasks();
EXPECT_EQ(0u, server_->NumberOfCommandsPendingResult());
}
} // namespace policy