| // 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/compiler_specific.h" | 
 | #include "base/feature_list.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/strings/string_split.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/test/scoped_feature_list.h" | 
 | #include "printing/backend/cups_ipp_constants.h" | 
 | #include "printing/backend/mock_cups_printer.h" | 
 | #include "printing/mojom/print.mojom.h" | 
 | #include "printing/printing_features.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"; | 
 |  | 
 | constexpr gfx::Size kDefaultPaperSize = {215900, 279400}; | 
 | constexpr char kDefaultPaperName[] = "some_vendor_id"; | 
 |  | 
 | 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::TestWithParam<bool>, | 
 |                             public PrintingContext::Delegate { | 
 |  public: | 
 |   PrintingContextTest() { | 
 |     if (GetParam()) { | 
 |       feature_list_.InitAndEnableFeature(features::kApiPrintingMarginsAndScale); | 
 |     } else { | 
 |       feature_list_.InitAndDisableFeature( | 
 |           features::kApiPrintingMarginsAndScale); | 
 |     } | 
 |   } | 
 |   ~PrintingContextTest() override = default; | 
 |  | 
 |   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, PrintingContext::OutOfProcessBehavior::kDisabled, | 
 |         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)); | 
 |     settings_.set_requested_media({kDefaultPaperSize, kDefaultPaperName}); | 
 |   } | 
 |  | 
 |   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 && UNSAFE_TODO(!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_, printable_area_); | 
 |     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_, printable_area_); | 
 |     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_, printable_area_); | 
 |     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, UNSAFE_TODO(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_, printable_area_); | 
 |     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); | 
 |   } | 
 |  | 
 |   void TestMediaColValue(const gfx::Size& expected_size, | 
 |                          int expected_bottom_margin, | 
 |                          int expected_left_margin, | 
 |                          int expected_right_margin, | 
 |                          int expected_top_margin) { | 
 |     auto attributes = SettingsToIPPOptions(settings_, printable_area_); | 
 |     ipp_t* media_col = | 
 |         ippGetCollection(GetAttribute(attributes.get(), kIppMediaCol), 0); | 
 |     ipp_t* media_size = | 
 |         ippGetCollection(GetAttribute(media_col, kIppMediaSize), 0); | 
 |  | 
 |     int width = ippGetInteger(GetAttribute(media_size, kIppXDimension), 0); | 
 |     int height = ippGetInteger(GetAttribute(media_size, kIppYDimension), 0); | 
 |     EXPECT_EQ(expected_size.width(), width); | 
 |     EXPECT_EQ(expected_size.height(), height); | 
 |  | 
 |     int bottom = | 
 |         ippGetInteger(GetAttribute(media_col, kIppMediaBottomMargin), 0); | 
 |     int left = ippGetInteger(GetAttribute(media_col, kIppMediaLeftMargin), 0); | 
 |     int right = ippGetInteger(GetAttribute(media_col, kIppMediaRightMargin), 0); | 
 |     int top = ippGetInteger(GetAttribute(media_col, kIppMediaTopMargin), 0); | 
 |  | 
 |     EXPECT_EQ(expected_bottom_margin, bottom); | 
 |     EXPECT_EQ(expected_left_margin, left); | 
 |     EXPECT_EQ(expected_right_margin, right); | 
 |     EXPECT_EQ(expected_top_margin, top); | 
 |   } | 
 |  | 
 |   bool HasAttribute(const char* attr_name) const { | 
 |     auto attributes = SettingsToIPPOptions(settings_, printable_area_); | 
 |     return !!ippFindAttribute(attributes.get(), attr_name, IPP_TAG_ZERO); | 
 |   } | 
 |  | 
 |   int GetAttrValueCount(const char* attr_name) const { | 
 |     auto attributes = SettingsToIPPOptions(settings_, printable_area_); | 
 |     auto* attr = GetAttribute(attributes.get(), attr_name); | 
 |     return ippGetCount(attr); | 
 |   } | 
 |  | 
 |   bool IsPrintingMarginsAndScaleEnabled() const { return GetParam(); } | 
 |  | 
 |   TestPrintSettings settings_; | 
 |   gfx::Rect printable_area_; | 
 |  | 
 |   // PrintingContext::Delegate methods. | 
 |   gfx::NativeView GetParentView() override { return gfx::NativeView(); } | 
 |   std::string GetAppLocale() override { return std::string(); } | 
 |  | 
 |   base::test::ScopedFeatureList feature_list_; | 
 |   std::unique_ptr<PrintingContextChromeos> printing_context_; | 
 |   raw_ptr<MockCupsPrinter> printer_; | 
 | }; | 
 |  | 
 | TEST_P(PrintingContextTest, SettingsToIPPOptions_Color) { | 
 |   settings_.set_color(mojom::ColorModel::kGray); | 
 |   TestStringOptionValue(kIppColor, "monochrome"); | 
 |   settings_.set_color(mojom::ColorModel::kColor); | 
 |   TestStringOptionValue(kIppColor, "color"); | 
 | } | 
 |  | 
 | TEST_P(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_P(PrintingContextTest, SettingsToIPPOptions_MediaCol) { | 
 |   settings_.set_requested_media( | 
 |       {gfx::Size(297000, 420000), "iso_a3_297x420mm"}); | 
 |   printable_area_ = | 
 |       gfx::Rect(2000, 1000, 297000 - (2000 + 3000), 420000 - (1000 + 4000)); | 
 |   TestMediaColValue(gfx::Size(29700, 42000), 100, 200, 300, 400); | 
 | } | 
 |  | 
 | TEST_P(PrintingContextTest, SettingsToIPPOptions_MediaColCustomMargins) { | 
 |   settings_.set_requested_media( | 
 |       {gfx::Size(297000, 420000), "iso_a3_297x420mm"}); | 
 |   settings_.SetCustomMargins({0, 0, 50, 30, 40, 60}); | 
 |   ASSERT_EQ(settings_.margin_type(), mojom::MarginType::kCustomMargins); | 
 |   printable_area_ = | 
 |       gfx::Rect(2000, 1000, 297000 - (2000 + 3000), 420000 - (1000 + 4000)); | 
 |   // If custom margins are set, they mustn't be used for print job regardless | 
 |   // of the feature flag. | 
 |   TestMediaColValue(gfx::Size(29700, 42000), 100, 200, 300, 400); | 
 |  | 
 |   // If precomputed margins for backend are set, they must only be used if the | 
 |   // feature is enabled. | 
 |   { | 
 |     settings_.SetCustomMarginsForBackend({0, 0, 50, 30, 40, 60}); | 
 |     ASSERT_EQ(settings_.margin_type(), | 
 |               mojom::MarginType::kPrecomputedMarginsForBackend); | 
 |     if (IsPrintingMarginsAndScaleEnabled()) { | 
 |       TestMediaColValue(gfx::Size(29700, 42000), 6, 5, 3, 4); | 
 |     } else { | 
 |       TestMediaColValue(gfx::Size(29700, 42000), 100, 200, 300, 400); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | // All margins should be zero regardless of the feature flag. | 
 | TEST_P(PrintingContextTest, SettingsToIPPOptions_MediaColZeroMargins) { | 
 |   settings_.set_requested_media( | 
 |       {gfx::Size(297000, 420000), "iso_a3_297x420mm"}); | 
 |   printable_area_ = gfx::Rect(0, 0, 297000, 420000); | 
 |  | 
 |   // Set custom margins to zero | 
 |   settings_.SetCustomMargins({0, 0, 0, 0, 0, 0}); | 
 |   settings_.set_borderless(true); | 
 |   ASSERT_EQ(settings_.margin_type(), mojom::MarginType::kCustomMargins); | 
 |   TestMediaColValue(gfx::Size(29700, 42000), 0, 0, 0, 0); | 
 |  | 
 |   // Set precomputed margins for backend as zero. | 
 |   settings_.SetCustomMarginsForBackend({0, 0, 0, 0, 0, 0}); | 
 |   ASSERT_EQ(settings_.margin_type(), | 
 |             mojom::MarginType::kPrecomputedMarginsForBackend); | 
 |   TestMediaColValue(gfx::Size(29700, 42000), 0, 0, 0, 0); | 
 |  | 
 |   // Set margins type to `kNoMargins`. | 
 |   settings_.set_margin_type(mojom::MarginType::kNoMargins); | 
 |   TestMediaColValue(gfx::Size(29700, 42000), 0, 0, 0, 0); | 
 | } | 
 |  | 
 | TEST_P(PrintingContextTest, SettingsToIPPOptionsMediaColLandscape) { | 
 |   settings_.set_requested_media( | 
 |       {gfx::Size(148000, 200000), "om_200030x148170um_200x148mm"}); | 
 |   // Use margins (LBRT) of 500, 700, 200, and 1000. | 
 |   printable_area_ = | 
 |       gfx::Rect(500, 700, 148000 - (500 + 200), 200000 - (700 + 1000)); | 
 |   // The requested media and printable area is in portrait mode (height larger | 
 |   // than width).  Since the vendor ID has a width larger than the height, the | 
 |   // expected media should get swapped.  When swapped, the margins (LBRT) should | 
 |   // be 1000, 500, 700, and 200. | 
 |   TestMediaColValue(gfx::Size(20000, 14800), 50, 100, 70, 20); | 
 | } | 
 |  | 
 | TEST_P(PrintingContextTest, SettingsToIPPOptions_Copies) { | 
 |   settings_.set_copies(3); | 
 |   TestIntegerOptionValue(kIppCopies, 3); | 
 | } | 
 |  | 
 | TEST_P(PrintingContextTest, SettingsToIPPOptions_Collate) { | 
 |   TestStringOptionValue(kIppCollate, "separate-documents-uncollated-copies"); | 
 |   settings_.set_collate(true); | 
 |   TestStringOptionValue(kIppCollate, "separate-documents-collated-copies"); | 
 | } | 
 |  | 
 | TEST_P(PrintingContextTest, SettingsToIPPOptions_Pin) { | 
 |   EXPECT_FALSE(HasAttribute(kIppPin)); | 
 |   settings_.set_pin_value("1234"); | 
 |   TestOctetStringOptionValue(kIppPin, base::span_from_cstring("1234")); | 
 | } | 
 |  | 
 | TEST_P(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_P(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_P(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_P(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_P(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_, printable_area_); | 
 |   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, UNSAFE_TODO(memcmp("a.1-B_", version, 6))); | 
 | } | 
 |  | 
 | TEST_P(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_P(PrintingContextTest, SettingsToIPPOptionsClientInfoEmpty) { | 
 |   settings_.set_client_infos({}); | 
 |   EXPECT_FALSE(HasAttribute(kIppClientInfo)); | 
 |  | 
 |   mojom::IppClientInfo invalid_client_info( | 
 |       mojom::IppClientInfo::ClientType::kOther, "$", " ", "{}", std::nullopt); | 
 |  | 
 |   settings_.set_client_infos({invalid_client_info}); | 
 |   EXPECT_FALSE(HasAttribute(kIppClientInfo)); | 
 | } | 
 |  | 
 | TEST_P(PrintingContextTest, SettingsToIPPOptionsPrintScaling) { | 
 |   // Define test cases for print scaling | 
 |   struct PrintScalingTestCase { | 
 |     mojom::PrintScalingType scaling_type; | 
 |     const char* expected_value; | 
 |   } constexpr kTestCases[] = { | 
 |       {mojom::PrintScalingType::kUnknownPrintScalingType, nullptr}, | 
 |       {mojom::PrintScalingType::kAuto, "auto"}, | 
 |       {mojom::PrintScalingType::kAutoFit, "auto-fit"}, | 
 |       {mojom::PrintScalingType::kFill, "fill"}, | 
 |       {mojom::PrintScalingType::kFit, "fit"}, | 
 |       {mojom::PrintScalingType::kNone, "none"}, | 
 |   }; | 
 |  | 
 |   for (const auto& test_case : kTestCases) { | 
 |     settings_.set_print_scaling(test_case.scaling_type); | 
 |     if (test_case.scaling_type == | 
 |         mojom::PrintScalingType::kUnknownPrintScalingType) { | 
 |       EXPECT_FALSE(HasAttribute(kIppPrintScaling)); | 
 |     } else { | 
 |       TestStringOptionValue(kIppPrintScaling, test_case.expected_value); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(PrintingContextTest, | 
 |                          PrintingContextTest, | 
 |                          testing::Bool()); | 
 |  | 
 | }  // namespace | 
 |  | 
 | }  // namespace printing |