// 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 "chrome/browser/ash/printing/history/print_job_reporting_service.h"
#include <string_view>
#include "base/functional/bind.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/printing/history/print_job_info.pb.h"
#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/account_id/account_id.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/reporting/client/mock_report_queue.h"
#include "components/reporting/client/report_queue.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/protobuf/src/google/protobuf/message_lite.h"
namespace ash {
namespace {
namespace print = printing::proto;
namespace em = ::enterprise_management;
using ::testing::_;
using ::testing::DoAll;
using ::testing::IsNull;
using ::testing::Matcher;
using ::testing::MatcherInterface;
using ::testing::MatchResultListener;
using ::testing::Not;
using ::testing::SaveArg;
using ::testing::SaveArgPointee;
em::PrintJobEvent CreateJobEvent(
const std::string id,
const std::string title,
::reporting::error::Code status,
em::PrintJobEvent::PrintSettings::ColorMode color,
em::PrintJobEvent::PrintSettings::DuplexMode duplex) {
em::PrintJobEvent event;
// Print job configuration
auto* job_config = event.mutable_job_configuration();
job_config->set_id(id);
job_config->set_title(title);
job_config->set_status(status);
base::Time time;
EXPECT_TRUE(base::Time::FromUTCString("14 Feb 2021 10:00", &time));
job_config->set_creation_timestamp_ms(time.InMillisecondsSinceUnixEpoch());
EXPECT_TRUE(base::Time::FromUTCString("14 Feb 2021 10:30", &time));
job_config->set_completion_timestamp_ms(time.InMillisecondsSinceUnixEpoch());
job_config->set_number_of_pages(10);
// Print settings
auto* settings = job_config->mutable_settings();
settings->set_color(color);
settings->set_duplex(duplex);
settings->mutable_media_size()->set_height(297000);
settings->mutable_media_size()->set_width(420000);
settings->mutable_media_size()->set_vendor_id("iso_a3_297x420mm");
settings->set_copies(1);
// Printer
event.mutable_printer()->set_uri("ipp://192.168.1.5:631");
event.mutable_printer()->set_name("name1");
event.mutable_printer()->set_id(id);
// User
event.set_user_type(em::PrintJobEvent_UserType_KIOSK);
return event;
}
print::PrintJobInfo CreateJobInfo(const std::string id,
const std::string title,
print::PrintJobInfo::PrintJobStatus status,
print::PrintSettings::ColorMode color,
print::PrintSettings::DuplexMode duplex) {
print::PrintJobInfo info;
// Print job configuration
info.set_id(id);
info.set_title(title);
info.set_status(status);
base::Time time;
EXPECT_TRUE(base::Time::FromUTCString("14 Feb 2021 10:00", &time));
info.set_creation_time(time.InMillisecondsSinceUnixEpoch());
info.set_creation_time(time.InMillisecondsSinceUnixEpoch());
EXPECT_TRUE(base::Time::FromUTCString("14 Feb 2021 10:30", &time));
info.set_completion_time(time.InMillisecondsSinceUnixEpoch());
info.set_number_of_pages(10);
// Print settings
auto* settings = info.mutable_settings();
settings->set_color(color);
settings->set_duplex(duplex);
settings->mutable_media_size()->set_height(297000);
settings->mutable_media_size()->set_width(420000);
settings->mutable_media_size()->set_vendor_id("iso_a3_297x420mm");
settings->set_copies(1);
// Printer
info.mutable_printer()->set_name("name1");
info.mutable_printer()->set_uri("ipp://192.168.1.5:631");
info.mutable_printer()->set_source(print::Printer_PrinterSource_POLICY);
info.mutable_printer()->set_id(id);
return info;
}
em::PrintJobEvent JobEvent1() {
return CreateJobEvent("id1", "title1", ::reporting::error::OK,
em::PrintJobEvent::PrintSettings::BLACK_AND_WHITE,
em::PrintJobEvent::PrintSettings::ONE_SIDED);
}
print::PrintJobInfo JobInfo1() {
return CreateJobInfo("id1", "title1", print::PrintJobInfo::PRINTED,
print::PrintSettings::BLACK_AND_WHITE,
print::PrintSettings::ONE_SIDED);
}
em::PrintJobEvent JobEvent2() {
return CreateJobEvent("id2", "title2", ::reporting::error::CANCELLED,
em::PrintJobEvent::PrintSettings::COLOR,
em::PrintJobEvent::PrintSettings::TWO_SIDED_LONG_EDGE);
}
print::PrintJobInfo JobInfo2() {
return CreateJobInfo("id2", "title2", print::PrintJobInfo::CANCELED,
print::PrintSettings::COLOR,
print::PrintSettings::TWO_SIDED_LONG_EDGE);
}
class PrintJobEventMatcher : public MatcherInterface<const em::PrintJobEvent&> {
public:
explicit PrintJobEventMatcher(const em::PrintJobEvent& event)
: id_(event.job_configuration().id()),
title_(event.job_configuration().title()),
status_(static_cast<::reporting::error::Code>(
event.job_configuration().status())),
creation_timestamp_ms_(
event.job_configuration().creation_timestamp_ms()),
completion_timestamp_ms_(
event.job_configuration().completion_timestamp_ms()),
pages_(event.job_configuration().number_of_pages()),
color_(event.job_configuration().settings().color()),
duplex_(event.job_configuration().settings().duplex()),
media_size_(event.job_configuration().settings().media_size()),
copies_(event.job_configuration().settings().copies()),
printer_uri_(event.printer().uri()),
printer_name_(event.printer().name()),
user_type_(event.user_type()) {}
bool MatchAndExplain(const em::PrintJobEvent& event,
MatchResultListener* listener) const {
// Print job configuration
bool id_equal = event.job_configuration().id() == id_;
if (!id_equal) {
*listener << " |id| is " << event.job_configuration().id();
}
bool title_equal = event.job_configuration().title() == title_;
if (!title_equal) {
*listener << " |title| is " << event.job_configuration().title();
}
bool status_equal = event.job_configuration().status() == status_;
if (!status_equal) {
*listener << " |status| is " << event.job_configuration().status();
}
bool creation_timestamp_ms_equal =
event.job_configuration().creation_timestamp_ms() ==
creation_timestamp_ms_;
if (!creation_timestamp_ms_equal) {
*listener << " |creation timestamp_ms| is "
<< event.job_configuration().creation_timestamp_ms();
}
bool completion_timestamp_ms_equal =
event.job_configuration().completion_timestamp_ms() ==
completion_timestamp_ms_;
if (!completion_timestamp_ms_equal) {
*listener << " |completion timestamp_ms| is "
<< event.job_configuration().completion_timestamp_ms();
}
bool pages_equal = event.job_configuration().number_of_pages() == pages_;
if (!pages_equal) {
*listener << " |pages| is "
<< event.job_configuration().number_of_pages();
}
// Print settings
bool color_equal = event.job_configuration().settings().color() == color_;
if (!color_equal) {
*listener << " |color| is "
<< event.job_configuration().settings().color();
}
bool duplex_equal =
event.job_configuration().settings().duplex() == duplex_;
if (!duplex_equal) {
*listener << " |duplex| is "
<< event.job_configuration().settings().duplex();
}
auto& media_size = event.job_configuration().settings().media_size();
bool media_equal = media_size.width() == media_size_.width() &&
media_size.height() == media_size_.height() &&
media_size.vendor_id() == media_size_.vendor_id();
if (!media_equal) {
*listener << " |media| is " << media_size.width() << "x"
<< media_size.height() << " and " << media_size.vendor_id();
}
bool copies_equal =
event.job_configuration().settings().copies() == copies_;
if (!copies_equal) {
*listener << " |copies| is "
<< event.job_configuration().settings().copies();
}
// Printer
bool printer_uri_equal = event.printer().uri() == printer_uri_;
if (!printer_uri_equal) {
*listener << " |printer uri| is " << event.printer().uri();
}
bool printer_name_equal = event.printer().name() == printer_name_;
if (!printer_name_equal) {
*listener << " |printer name| is " << event.printer().name();
}
bool printer_id_equal = event.printer().id() == id_;
if (!printer_id_equal) {
*listener << " |printer id| is " << event.printer().id();
}
// User
bool user_type_equal = event.user_type() == user_type_;
if (!user_type_equal) {
*listener << " |user type| is " << event.user_type();
}
return id_equal && title_equal && status_equal &&
creation_timestamp_ms_equal && completion_timestamp_ms_equal &&
pages_equal && color_equal && duplex_equal && media_equal &&
copies_equal && printer_uri_equal && printer_name_equal &&
printer_id_equal && user_type_equal;
}
void DescribeTo(::std::ostream* os) const {}
private:
std::string id_;
std::string title_;
::reporting::error::Code status_;
int64_t creation_timestamp_ms_;
int64_t completion_timestamp_ms_;
int pages_;
em::PrintJobEvent_PrintSettings_ColorMode color_;
em::PrintJobEvent_PrintSettings_DuplexMode duplex_;
em::PrintJobEvent_PrintSettings_MediaSize media_size_;
int32_t copies_;
std::string printer_uri_;
std::string printer_name_;
em::PrintJobEvent_UserType user_type_;
};
Matcher<const em::PrintJobEvent&> IsPrintJobEvent(
const em::PrintJobEvent& event) {
return Matcher<const em::PrintJobEvent&>(new PrintJobEventMatcher(event));
}
} // namespace
class PrintJobReportingServiceTest : public ::testing::Test {
public:
void SetUp() override {
auto user_manager = std::make_unique<FakeChromeUserManager>();
AccountId account_id(AccountId::FromUserEmail("[email protected]"));
user_manager->AddKioskAppUser(account_id);
user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
std::move(user_manager));
}
void ChangeReportingSetting(bool should_report) {
testing_settings_.device_settings()->SetBoolean(kReportDevicePrintJobs,
should_report);
}
// Creates a new report queue that can be used by the PrintJobReportingService
// with the enqueue operation stubbed
std::unique_ptr<::reporting::ReportQueue, base::OnTaskRunnerDeleter>
CreateReportQueue() {
auto report_queue = std::unique_ptr<::reporting::MockReportQueue,
base::OnTaskRunnerDeleter>(
new ::reporting::MockReportQueue(),
base::OnTaskRunnerDeleter(
base::ThreadPool::CreateSequencedTaskRunner({})));
EXPECT_CALL(*report_queue, AddRecord)
.WillRepeatedly(
[this](std::string_view record, ::reporting::Priority priority,
::reporting::ReportQueue::EnqueueCallback callback) {
em::PrintJobEvent event;
event.ParseFromString(std::string(record));
events_.push_back(event);
priorities_.push_back(priority);
});
return std::move(report_queue);
}
protected:
content::BrowserTaskEnvironment task_environment_;
ScopedTestingCrosSettings testing_settings_;
std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_;
std::unique_ptr<PrintJobReportingService> print_job_reporting_service_;
std::vector<em::PrintJobEvent> events_;
std::vector<::reporting::Priority> priorities_;
};
TEST_F(PrintJobReportingServiceTest, ShouldReportPolicyDisabled) {
print_job_reporting_service_ =
PrintJobReportingService::CreateForTest(CreateReportQueue());
ChangeReportingSetting(false);
print_job_reporting_service_->OnPrintJobFinished(JobInfo1());
print_job_reporting_service_->OnPrintJobFinished(JobInfo2());
EXPECT_TRUE(events_.empty());
EXPECT_TRUE(priorities_.empty());
}
TEST_F(PrintJobReportingServiceTest, Enqueue) {
print_job_reporting_service_ =
PrintJobReportingService::CreateForTest(CreateReportQueue());
ChangeReportingSetting(true);
print_job_reporting_service_->OnPrintJobFinished(JobInfo1());
print_job_reporting_service_->OnPrintJobFinished(JobInfo2());
ASSERT_EQ(events_.size(), 2u);
EXPECT_THAT(events_[0], IsPrintJobEvent(JobEvent1()));
EXPECT_EQ(priorities_[0], ::reporting::Priority::SLOW_BATCH);
EXPECT_THAT(events_[1], IsPrintJobEvent(JobEvent2()));
EXPECT_EQ(priorities_[1], ::reporting::Priority::SLOW_BATCH);
}
TEST_F(PrintJobReportingServiceTest, ShouldReportPolicyInitiallyEnabled) {
ChangeReportingSetting(true);
// Create the reporting service after setting the policy to true.
print_job_reporting_service_ =
PrintJobReportingService::CreateForTest(CreateReportQueue());
print_job_reporting_service_->OnPrintJobFinished(JobInfo1());
ASSERT_EQ(events_.size(), 1u);
EXPECT_THAT(events_[0], IsPrintJobEvent(JobEvent1()));
EXPECT_EQ(priorities_[0], ::reporting::Priority::SLOW_BATCH);
}
TEST_F(PrintJobReportingServiceTest, ShouldReportPolicyInitiallyDisabled) {
ChangeReportingSetting(false);
// Create the reporting service after setting the policy to false.
print_job_reporting_service_ =
PrintJobReportingService::CreateForTest(CreateReportQueue());
print_job_reporting_service_->OnPrintJobFinished(JobInfo1());
EXPECT_TRUE(events_.empty());
EXPECT_TRUE(priorities_.empty());
}
} // namespace ash