0
Files
src/printing/printing_context_chromeos_unittest.cc
Bryan Cain 034c5dc124 Directly construct IPP requests for print job management
Instead of relying on libcups functions to do it for us. This allows us
to bypass the awkward abstraction of cups_option_t, where all attributes
had to be encoded as strings, and send collection-type attributes like
"client-info" without relying on the undocumented internal format used
by libcups.

Bug: b:267349303
Test: send print jobs with various options to ippeveprinter
Test: print something using an actual printer
Change-Id: I7ec030c28e33dbf9a68cfbb14185c585b3a797b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4209858
Reviewed-by: Benjamin Gordon <bmgordon@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Sparik Hayrapetyan <ust@google.com>
Auto-Submit: Bryan Cain <bryancain@chromium.org>
Commit-Queue: Bryan Cain <bryancain@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1103416}
2023-02-09 19:24:54 +00:00

344 lines
13 KiB
C++

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "printing/printing_context_chromeos.h"
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "printing/backend/cups_ipp_constants.h"
#include "printing/backend/mock_cups_printer.h"
#include "printing/mojom/print.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace printing {
namespace {
using ::testing::_;
using ::testing::ByMove;
using ::testing::DoAll;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
constexpr char kPrinterName[] = "printer";
constexpr char16_t kPrinterName16[] = u"printer";
constexpr char kUsername[] = "test user";
constexpr char kDocumentName[] = "document name";
constexpr char16_t kDocumentName16[] = u"document name";
class MockCupsConnection : public CupsConnection {
public:
MOCK_METHOD1(GetDests, bool(std::vector<std::unique_ptr<CupsPrinter>>&));
MOCK_METHOD2(GetJobs,
bool(const std::vector<std::string>& printer_ids,
std::vector<QueueStatus>* jobs));
MOCK_METHOD2(GetPrinterStatus,
bool(const std::string& printer_id,
PrinterStatus* printer_status));
MOCK_CONST_METHOD0(server_name, std::string());
MOCK_CONST_METHOD0(last_error, int());
MOCK_CONST_METHOD0(last_error_message, std::string());
MOCK_METHOD1(GetPrinter,
std::unique_ptr<CupsPrinter>(const std::string& printer_name));
};
class TestPrintSettings : public PrintSettings {
public:
TestPrintSettings() { set_duplex_mode(mojom::DuplexMode::kSimplex); }
};
class PrintingContextTest : public testing::Test,
public PrintingContext::Delegate {
public:
void SetDefaultSettings(bool send_user_info, const std::string& uri) {
auto unique_connection = std::make_unique<MockCupsConnection>();
auto* connection = unique_connection.get();
auto unique_printer = std::make_unique<NiceMock<MockCupsPrinter>>();
printer_ = unique_printer.get();
EXPECT_CALL(*printer_, GetUri()).WillRepeatedly(Return(uri));
EXPECT_CALL(*connection, GetPrinter(kPrinterName))
.WillOnce(Return(ByMove(std::move(unique_printer))));
printing_context_ = PrintingContextChromeos::CreateForTesting(
this, std::move(unique_connection));
auto settings = std::make_unique<PrintSettings>();
settings->set_device_name(kPrinterName16);
settings->set_send_user_info(send_user_info);
settings->set_duplex_mode(mojom::DuplexMode::kLongEdge);
settings->set_username(kUsername);
printing_context_->UpdatePrintSettingsFromPOD(std::move(settings));
}
ipp_attribute_t* GetAttribute(ipp_t* attributes,
const char* attr_name) const {
DCHECK(attr_name);
ipp_attribute_t* ret = nullptr;
for (ipp_attribute_t* attr = ippFirstAttribute(attributes); attr;
attr = ippNextAttribute(attributes)) {
const char* name = ippGetName(attr);
if (name && !strcmp(attr_name, name)) {
EXPECT_EQ(nullptr, ret)
<< "Multiple attributes with name " << attr_name << " found.";
ret = attr;
}
}
EXPECT_TRUE(ret);
return ret;
}
void TestStringOptionValue(const char* attr_name,
const char* expected_value) const {
auto attributes = SettingsToIPPOptions(settings_);
auto* attr = GetAttribute(attributes.get(), attr_name);
EXPECT_STREQ(expected_value, ippGetString(attr, 0, nullptr));
}
void TestIntegerOptionValue(const char* attr_name, int expected_value) const {
auto attributes = SettingsToIPPOptions(settings_);
auto* attr = GetAttribute(attributes.get(), attr_name);
EXPECT_EQ(expected_value, ippGetInteger(attr, 0));
}
void TestOctetStringOptionValue(const char* attr_name,
base::span<const char> expected_value) const {
auto attributes = SettingsToIPPOptions(settings_);
auto* attr = GetAttribute(attributes.get(), attr_name);
int length;
void* value = ippGetOctetString(attr, 0, &length);
ASSERT_EQ(expected_value.size(), static_cast<size_t>(length));
ASSERT_TRUE(value);
EXPECT_EQ(0, memcmp(expected_value.data(), value, expected_value.size()));
}
void TestResolutionOptionValue(const char* attr_name,
int expected_x_res,
int expected_y_res) const {
auto attributes = SettingsToIPPOptions(settings_);
auto* attr = GetAttribute(attributes.get(), attr_name);
ipp_res_t unit;
int y_res;
int x_res = ippGetResolution(attr, 0, &y_res, &unit);
EXPECT_EQ(unit, IPP_RES_PER_INCH);
EXPECT_EQ(expected_x_res, x_res);
EXPECT_EQ(expected_y_res, y_res);
}
bool HasAttribute(const char* attr_name) const {
auto attributes = SettingsToIPPOptions(settings_);
return !!ippFindAttribute(attributes.get(), attr_name, IPP_TAG_ZERO);
}
int GetAttrValueCount(const char* attr_name) const {
auto attributes = SettingsToIPPOptions(settings_);
auto* attr = GetAttribute(attributes.get(), attr_name);
return ippGetCount(attr);
}
TestPrintSettings settings_;
// PrintingContext::Delegate methods.
gfx::NativeView GetParentView() override { return nullptr; }
std::string GetAppLocale() override { return std::string(); }
std::unique_ptr<PrintingContextChromeos> printing_context_;
raw_ptr<MockCupsPrinter> printer_;
};
TEST_F(PrintingContextTest, SettingsToIPPOptions_Color) {
settings_.set_color(mojom::ColorModel::kGray);
TestStringOptionValue(kIppColor, "monochrome");
settings_.set_color(mojom::ColorModel::kColor);
TestStringOptionValue(kIppColor, "color");
}
TEST_F(PrintingContextTest, SettingsToIPPOptions_Duplex) {
settings_.set_duplex_mode(mojom::DuplexMode::kSimplex);
TestStringOptionValue(kIppDuplex, "one-sided");
settings_.set_duplex_mode(mojom::DuplexMode::kLongEdge);
TestStringOptionValue(kIppDuplex, "two-sided-long-edge");
settings_.set_duplex_mode(mojom::DuplexMode::kShortEdge);
TestStringOptionValue(kIppDuplex, "two-sided-short-edge");
}
TEST_F(PrintingContextTest, SettingsToIPPOptions_Media) {
TestStringOptionValue(kIppMedia, "");
settings_.set_requested_media(
{gfx::Size(297000, 420000), "iso_a3_297x420mm"});
TestStringOptionValue(kIppMedia, "iso_a3_297x420mm");
}
TEST_F(PrintingContextTest, SettingsToIPPOptions_Copies) {
settings_.set_copies(3);
TestIntegerOptionValue(kIppCopies, 3);
}
TEST_F(PrintingContextTest, SettingsToIPPOptions_Collate) {
TestStringOptionValue(kIppCollate, "separate-documents-uncollated-copies");
settings_.set_collate(true);
TestStringOptionValue(kIppCollate, "separate-documents-collated-copies");
}
TEST_F(PrintingContextTest, SettingsToIPPOptions_Pin) {
EXPECT_FALSE(HasAttribute(kIppPin));
settings_.set_pin_value("1234");
TestOctetStringOptionValue(kIppPin, base::make_span("1234", 4u));
}
TEST_F(PrintingContextTest, SettingsToIPPOptions_Resolution) {
EXPECT_FALSE(HasAttribute(kIppResolution));
settings_.set_dpi_xy(0, 300);
EXPECT_FALSE(HasAttribute(kIppResolution));
settings_.set_dpi_xy(300, 0);
EXPECT_FALSE(HasAttribute(kIppResolution));
settings_.set_dpi(600);
TestResolutionOptionValue(kIppResolution, 600, 600);
settings_.set_dpi_xy(600, 1200);
TestResolutionOptionValue(kIppResolution, 600, 1200);
}
TEST_F(PrintingContextTest, SettingsToIPPOptions_SendUserInfo_Secure) {
ipp_status_t status = ipp_status_t::IPP_STATUS_OK;
std::u16string document_name = kDocumentName16;
SetDefaultSettings(/*send_user_info=*/true, "ipps://test-uri");
std::string create_job_document_name;
std::string create_job_username;
std::string start_document_document_name;
std::string start_document_username;
EXPECT_CALL(*printer_, CreateJob)
.WillOnce(DoAll(SetArgPointee<0>(/*job_id=*/1),
SaveArg<1>(&create_job_document_name),
SaveArg<2>(&create_job_username), Return(status)));
EXPECT_CALL(*printer_, StartDocument)
.WillOnce(DoAll(SaveArg<1>(&start_document_document_name),
SaveArg<3>(&start_document_username), Return(true)));
printing_context_->NewDocument(document_name);
EXPECT_EQ(create_job_document_name, kDocumentName);
EXPECT_EQ(start_document_document_name, kDocumentName);
EXPECT_EQ(create_job_username, kUsername);
EXPECT_EQ(start_document_username, kUsername);
}
TEST_F(PrintingContextTest, SettingsToIPPOptions_SendUserInfo_Insecure) {
ipp_status_t status = ipp_status_t::IPP_STATUS_OK;
std::u16string document_name = kDocumentName16;
std::string default_username = "chronos";
std::string default_document_name = "-";
SetDefaultSettings(/*send_user_info=*/true, "ipp://test-uri");
std::string create_job_document_name;
std::string create_job_username;
std::string start_document_document_name;
std::string start_document_username;
EXPECT_CALL(*printer_, CreateJob)
.WillOnce(DoAll(SetArgPointee<0>(/*job_id=*/1),
SaveArg<1>(&create_job_document_name),
SaveArg<2>(&create_job_username), Return(status)));
EXPECT_CALL(*printer_, StartDocument)
.WillOnce(DoAll(SaveArg<1>(&start_document_document_name),
SaveArg<3>(&start_document_username), Return(true)));
printing_context_->NewDocument(document_name);
EXPECT_EQ(create_job_document_name, default_document_name);
EXPECT_EQ(start_document_document_name, default_document_name);
EXPECT_EQ(create_job_username, default_username);
EXPECT_EQ(start_document_username, default_username);
}
TEST_F(PrintingContextTest, SettingsToIPPOptions_DoNotSendUserInfo) {
ipp_status_t status = ipp_status_t::IPP_STATUS_OK;
std::u16string document_name = kDocumentName16;
SetDefaultSettings(/*send_user_info=*/false, "ipps://test-uri");
std::string create_job_document_name;
std::string create_job_username;
std::string start_document_document_name;
std::string start_document_username;
EXPECT_CALL(*printer_, CreateJob)
.WillOnce(DoAll(SetArgPointee<0>(/*job_id=*/1),
SaveArg<1>(&create_job_document_name),
SaveArg<2>(&create_job_username), Return(status)));
EXPECT_CALL(*printer_, StartDocument)
.WillOnce(DoAll(SaveArg<1>(&start_document_document_name),
SaveArg<3>(&start_document_username), Return(true)));
printing_context_->NewDocument(document_name);
EXPECT_EQ(create_job_document_name, "");
EXPECT_EQ(start_document_document_name, "");
EXPECT_EQ(create_job_username, "");
EXPECT_EQ(start_document_username, "");
}
TEST_F(PrintingContextTest, SettingsToIPPOptionsClientInfo) {
mojom::IppClientInfo client_info(
mojom::IppClientInfo::ClientType::kOperatingSystem, "a-", "B_", "1.",
"a.1-B_");
settings_.set_client_infos({client_info});
auto attributes = SettingsToIPPOptions(settings_);
auto* attr = ippFindAttribute(attributes.get(), kIppClientInfo,
IPP_TAG_BEGIN_COLLECTION);
auto* client_info_collection = ippGetCollection(attr, 0);
attr = ippFindAttribute(client_info_collection, kIppClientName, IPP_TAG_NAME);
EXPECT_STREQ("a-", ippGetString(attr, 0, nullptr));
attr = ippFindAttribute(client_info_collection, kIppClientType, IPP_TAG_ENUM);
EXPECT_EQ(4, ippGetInteger(attr, 0));
attr =
ippFindAttribute(client_info_collection, kIppClientPatches, IPP_TAG_TEXT);
EXPECT_STREQ("B_", ippGetString(attr, 0, nullptr));
attr = ippFindAttribute(client_info_collection, kIppClientStringVersion,
IPP_TAG_TEXT);
EXPECT_STREQ("1.", ippGetString(attr, 0, nullptr));
attr = ippFindAttribute(client_info_collection, kIppClientVersion,
IPP_TAG_STRING);
int length;
void* version = ippGetOctetString(attr, 0, &length);
ASSERT_TRUE(version);
EXPECT_EQ(6, length);
EXPECT_EQ(0, memcmp("a.1-B_", version, 6));
}
TEST_F(PrintingContextTest, SettingsToIPPOptionsClientInfoSomeValid) {
mojom::IppClientInfo valid_client_info(
mojom::IppClientInfo::ClientType::kOperatingSystem, "aB.1-_", "aB.1-_",
"aB.1-_", "aB.1-_");
mojom::IppClientInfo invalid_client_info(
mojom::IppClientInfo::ClientType::kOperatingSystem, "{}", "aB.1-_",
"aB.1-_", "aB.1-_");
settings_.set_client_infos(
{valid_client_info, invalid_client_info, valid_client_info});
// Check that the invalid item is skipped in the client-info collection.
EXPECT_EQ(GetAttrValueCount(kIppClientInfo), 2);
}
TEST_F(PrintingContextTest, SettingsToIPPOptionsClientInfoEmpty) {
settings_.set_client_infos({});
EXPECT_FALSE(HasAttribute(kIppClientInfo));
mojom::IppClientInfo invalid_client_info(
mojom::IppClientInfo::ClientType::kOther, "$", " ", "{}", absl::nullopt);
settings_.set_client_infos({invalid_client_info});
EXPECT_FALSE(HasAttribute(kIppClientInfo));
}
} // namespace
} // namespace printing