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