blob: 4f0ccda0cc8977df2157017d1ff0f789ac7c8b2f [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 "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