| // 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 "chrome/browser/chromeos/policy/remote_commands/device_command_screenshot_job.h" |
| |
| #include <map> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/test/ash_test_base.h" |
| #include "base/json/json_writer.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/test/test_mock_time_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "components/policy/proto/device_management_backend.pb.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/gfx/codec/png_codec.h" |
| |
| namespace policy { |
| |
| namespace em = enterprise_management; |
| |
| namespace { |
| |
| // String constant identifying the result field in the result payload. |
| const char* const kResultFieldName = "result"; |
| |
| const char* const kMockUploadUrl = "http://example.com/upload"; |
| |
| const RemoteCommandJob::UniqueIDType kUniqueID = 123456789; |
| |
| // String constant identifying the upload url field in the command payload. |
| const char* const kUploadUrlFieldName = "fileUploadUrl"; |
| |
| em::RemoteCommand GenerateScreenshotCommandProto( |
| RemoteCommandJob::UniqueIDType unique_id, |
| base::TimeDelta age_of_command, |
| const std::string upload_url) { |
| em::RemoteCommand command_proto; |
| command_proto.set_type( |
| enterprise_management::RemoteCommand_Type_DEVICE_SCREENSHOT); |
| command_proto.set_command_id(unique_id); |
| command_proto.set_age_of_command(age_of_command.InMilliseconds()); |
| std::string payload; |
| base::DictionaryValue root_dict; |
| root_dict.SetString(kUploadUrlFieldName, upload_url); |
| base::JSONWriter::Write(root_dict, &payload); |
| command_proto.set_payload(payload); |
| return command_proto; |
| } |
| |
| class MockUploadJob : public policy::UploadJob { |
| public: |
| // If |error_code| is a null pointer OnSuccess() will be invoked when the |
| // Start() method is called, otherwise OnFailure() will be invoked with the |
| // respective |error_code|. |
| MockUploadJob(const GURL& upload_url, |
| UploadJob::Delegate* delegate, |
| std::unique_ptr<UploadJob::ErrorCode> error_code); |
| ~MockUploadJob() override; |
| |
| // policy::UploadJob: |
| void AddDataSegment(const std::string& name, |
| const std::string& filename, |
| const std::map<std::string, std::string>& header_entries, |
| std::unique_ptr<std::string> data) override; |
| void Start() override; |
| |
| const GURL& GetUploadUrl() const; |
| |
| protected: |
| const GURL upload_url_; |
| UploadJob::Delegate* delegate_; |
| std::unique_ptr<UploadJob::ErrorCode> error_code_; |
| bool add_datasegment_succeeds_; |
| }; |
| |
| MockUploadJob::MockUploadJob(const GURL& upload_url, |
| UploadJob::Delegate* delegate, |
| std::unique_ptr<UploadJob::ErrorCode> error_code) |
| : upload_url_(upload_url), |
| delegate_(delegate), |
| error_code_(std::move(error_code)) {} |
| |
| MockUploadJob::~MockUploadJob() { |
| } |
| |
| void MockUploadJob::AddDataSegment( |
| const std::string& name, |
| const std::string& filename, |
| const std::map<std::string, std::string>& header_entries, |
| std::unique_ptr<std::string> data) {} |
| |
| void MockUploadJob::Start() { |
| DCHECK(delegate_); |
| EXPECT_EQ(kMockUploadUrl, upload_url_.spec()); |
| if (error_code_) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&UploadJob::Delegate::OnFailure, |
| base::Unretained(delegate_), *error_code_)); |
| return; |
| } |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&UploadJob::Delegate::OnSuccess, |
| base::Unretained(delegate_))); |
| } |
| |
| scoped_refptr<base::RefCountedBytes> GenerateTestPNG(const int& width, |
| const int& height) { |
| const SkColor background_color = SK_ColorBLUE; |
| SkBitmap bmp; |
| bmp.allocN32Pixels(width, height); |
| for (int y = 0; y < height; ++y) { |
| for (int x = 0; x < width; ++x) |
| *bmp.getAddr32(x, y) = background_color; |
| } |
| scoped_refptr<base::RefCountedBytes> png_bytes(new base::RefCountedBytes()); |
| gfx::PNGCodec::ColorFormat color_format = gfx::PNGCodec::FORMAT_RGBA; |
| if (!gfx::PNGCodec::Encode( |
| reinterpret_cast<const unsigned char*>(bmp.getPixels()), color_format, |
| gfx::Size(bmp.width(), bmp.height()), |
| static_cast<int>(bmp.rowBytes()), false, |
| std::vector<gfx::PNGCodec::Comment>(), &png_bytes->data())) { |
| LOG(ERROR) << "Failed to encode image"; |
| } |
| return png_bytes; |
| } |
| |
| class MockScreenshotDelegate : public DeviceCommandScreenshotJob::Delegate { |
| public: |
| MockScreenshotDelegate( |
| std::unique_ptr<UploadJob::ErrorCode> upload_job_error_code, |
| bool screenshot_allowed); |
| ~MockScreenshotDelegate() override; |
| |
| bool IsScreenshotAllowed() override; |
| void TakeSnapshot( |
| gfx::NativeWindow window, |
| const gfx::Rect& source_rect, |
| const ui::GrabWindowSnapshotAsyncPNGCallback& callback) override; |
| std::unique_ptr<UploadJob> CreateUploadJob(const GURL&, |
| UploadJob::Delegate*) override; |
| |
| private: |
| std::unique_ptr<UploadJob::ErrorCode> upload_job_error_code_; |
| bool screenshot_allowed_; |
| }; |
| |
| MockScreenshotDelegate::MockScreenshotDelegate( |
| std::unique_ptr<UploadJob::ErrorCode> upload_job_error_code, |
| bool screenshot_allowed) |
| : upload_job_error_code_(std::move(upload_job_error_code)), |
| screenshot_allowed_(screenshot_allowed) {} |
| |
| MockScreenshotDelegate::~MockScreenshotDelegate() { |
| } |
| |
| bool MockScreenshotDelegate::IsScreenshotAllowed() { |
| return screenshot_allowed_; |
| } |
| |
| void MockScreenshotDelegate::TakeSnapshot( |
| gfx::NativeWindow window, |
| const gfx::Rect& source_rect, |
| const ui::GrabWindowSnapshotAsyncPNGCallback& callback) { |
| const int width = source_rect.width(); |
| const int height = source_rect.height(); |
| scoped_refptr<base::RefCountedBytes> test_png = |
| GenerateTestPNG(width, height); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(callback, test_png)); |
| } |
| |
| std::unique_ptr<UploadJob> MockScreenshotDelegate::CreateUploadJob( |
| const GURL& upload_url, |
| UploadJob::Delegate* delegate) { |
| return std::make_unique<MockUploadJob>(upload_url, delegate, |
| std::move(upload_job_error_code_)); |
| } |
| |
| } // namespace |
| |
| class DeviceCommandScreenshotTest : public ash::AshTestBase { |
| public: |
| void VerifyResults(RemoteCommandJob* job, |
| RemoteCommandJob::Status expected_status, |
| std::string expected_payload); |
| |
| protected: |
| DeviceCommandScreenshotTest(); |
| |
| // ash::AshTestBase: |
| void SetUp() override; |
| |
| void InitializeScreenshotJob(RemoteCommandJob* job, |
| RemoteCommandJob::UniqueIDType unique_id, |
| base::TimeTicks issued_time, |
| const std::string& upload_url); |
| |
| std::string CreatePayloadFromResultCode( |
| DeviceCommandScreenshotJob::ResultCode result_code); |
| |
| base::RunLoop run_loop_; |
| base::TimeTicks test_start_time_; |
| |
| private: |
| scoped_refptr<base::TestMockTimeTaskRunner> task_runner_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeviceCommandScreenshotTest); |
| }; |
| |
| DeviceCommandScreenshotTest::DeviceCommandScreenshotTest() |
| : task_runner_(new base::TestMockTimeTaskRunner()) { |
| } |
| |
| void DeviceCommandScreenshotTest::SetUp() { |
| ash::AshTestBase::SetUp(); |
| test_start_time_ = base::TimeTicks::Now(); |
| } |
| |
| void DeviceCommandScreenshotTest::InitializeScreenshotJob( |
| RemoteCommandJob* job, |
| RemoteCommandJob::UniqueIDType unique_id, |
| base::TimeTicks issued_time, |
| const std::string& upload_url) { |
| EXPECT_TRUE(job->Init( |
| base::TimeTicks::Now(), |
| GenerateScreenshotCommandProto( |
| unique_id, base::TimeTicks::Now() - issued_time, upload_url))); |
| EXPECT_EQ(unique_id, job->unique_id()); |
| EXPECT_EQ(RemoteCommandJob::NOT_STARTED, job->status()); |
| } |
| |
| std::string DeviceCommandScreenshotTest::CreatePayloadFromResultCode( |
| DeviceCommandScreenshotJob::ResultCode result_code) { |
| std::string payload; |
| base::DictionaryValue root_dict; |
| if (result_code != DeviceCommandScreenshotJob::SUCCESS) |
| root_dict.Set(kResultFieldName, std::make_unique<base::Value>(result_code)); |
| base::JSONWriter::Write(root_dict, &payload); |
| return payload; |
| } |
| |
| void DeviceCommandScreenshotTest::VerifyResults( |
| RemoteCommandJob* job, |
| RemoteCommandJob::Status expected_status, |
| std::string expected_payload) { |
| EXPECT_EQ(expected_status, job->status()); |
| if (job->status() == RemoteCommandJob::SUCCEEDED) { |
| std::unique_ptr<std::string> payload = job->GetResultPayload(); |
| EXPECT_TRUE(payload); |
| EXPECT_EQ(expected_payload, *payload); |
| } |
| run_loop_.Quit(); |
| } |
| |
| TEST_F(DeviceCommandScreenshotTest, Success) { |
| std::unique_ptr<RemoteCommandJob> job(new DeviceCommandScreenshotJob( |
| std::make_unique<MockScreenshotDelegate>(nullptr, true))); |
| InitializeScreenshotJob(job.get(), kUniqueID, test_start_time_, |
| kMockUploadUrl); |
| bool success = job->Run( |
| base::TimeTicks::Now(), |
| base::Bind( |
| &DeviceCommandScreenshotTest::VerifyResults, base::Unretained(this), |
| base::Unretained(job.get()), RemoteCommandJob::SUCCEEDED, |
| CreatePayloadFromResultCode(DeviceCommandScreenshotJob::SUCCESS))); |
| EXPECT_TRUE(success); |
| run_loop_.Run(); |
| } |
| |
| TEST_F(DeviceCommandScreenshotTest, FailureUserInput) { |
| std::unique_ptr<RemoteCommandJob> job(new DeviceCommandScreenshotJob( |
| std::make_unique<MockScreenshotDelegate>(nullptr, false))); |
| InitializeScreenshotJob(job.get(), kUniqueID, test_start_time_, |
| kMockUploadUrl); |
| bool success = |
| job->Run(base::TimeTicks::Now(), |
| base::Bind(&DeviceCommandScreenshotTest::VerifyResults, |
| base::Unretained(this), base::Unretained(job.get()), |
| RemoteCommandJob::FAILED, |
| CreatePayloadFromResultCode( |
| DeviceCommandScreenshotJob::FAILURE_USER_INPUT))); |
| EXPECT_TRUE(success); |
| run_loop_.Run(); |
| } |
| |
| TEST_F(DeviceCommandScreenshotTest, Failure) { |
| using ErrorCode = UploadJob::ErrorCode; |
| std::unique_ptr<ErrorCode> error_code( |
| new ErrorCode(UploadJob::AUTHENTICATION_ERROR)); |
| std::unique_ptr<RemoteCommandJob> job(new DeviceCommandScreenshotJob( |
| std::make_unique<MockScreenshotDelegate>(std::move(error_code), true))); |
| InitializeScreenshotJob(job.get(), kUniqueID, test_start_time_, |
| kMockUploadUrl); |
| bool success = job->Run( |
| base::TimeTicks::Now(), |
| base::Bind(&DeviceCommandScreenshotTest::VerifyResults, |
| base::Unretained(this), base::Unretained(job.get()), |
| RemoteCommandJob::FAILED, |
| CreatePayloadFromResultCode( |
| DeviceCommandScreenshotJob::FAILURE_AUTHENTICATION))); |
| EXPECT_TRUE(success); |
| run_loop_.Run(); |
| } |
| |
| } // namespace policy |