910e62b5创建于 1月15日历史提交
// Copyright 2016 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/backend/cups_ipp_helper.h"

#include <cups/cups.h>

#include <map>
#include <memory>
#include <string_view>
#include <utility>

#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "printing/backend/cups_ipp_constants.h"
#include "printing/backend/mock_cups_printer.h"
#include "printing/backend/print_backend_utils.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 {

using ::testing::Pointwise;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
using ::testing::UnorderedPointwise;

// Matches the name field to a string.
MATCHER(AdvancedCapabilityName, "") {
  *result_listener << "Expected: " << std::get<1>(arg)
                   << " vs Actual: " << std::get<0>(arg).name;
  return std::get<0>(arg).name == std::get<1>(arg);
}

// Compares an ipp_t* db entry from a media-col-database to a media_info
// object.
MATCHER(EqualsMediaColEntry, "") {
  return MediaColDbEntryEquals(std::get<0>(arg), std::get<1>(arg));
}

class MockCupsPrinterWithMarginsAndAttributes : public MockCupsPrinter {
 public:
  // name and value of IPP attribute; needed to fetch localized display name
  using LocalizationKey = std::pair<std::string_view, std::string_view>;

  MockCupsPrinterWithMarginsAndAttributes() = default;
  ~MockCupsPrinterWithMarginsAndAttributes() override = default;

  // CupsOptionProvider:
  ipp_attribute_t* GetSupportedOptionValues(
      const char* option_name) const override {
    const auto attr = supported_attributes_.find(option_name);
    return attr != supported_attributes_.end() ? attr->second : nullptr;
  }

  // CupsOptionProvider:
  std::vector<std::string_view> GetSupportedOptionValueStrings(
      const char* option_name) const override {
    ipp_attribute_t* attr = GetSupportedOptionValues(option_name);
    if (!attr)
      return std::vector<std::string_view>();

    std::vector<std::string_view> strings;
    const int size = ippGetCount(attr);
    strings.reserve(size);
    for (int i = 0; i < size; ++i) {
      const char* const value = ippGetString(attr, i, nullptr);
      if (!value) {
        continue;
      }
      strings.push_back(value);
    }

    return strings;
  }

  // CupsOptionProvider:
  ipp_attribute_t* GetDefaultOptionValue(
      const char* option_name) const override {
    const auto attr = default_attributes_.find(option_name);
    return attr != default_attributes_.end() ? attr->second : nullptr;
  }

  // CupsOptionProvider:
  ipp_attribute_t* GetMediaColDatabase() const override {
    return media_col_database_;
  }

  // CupsOptionProvider:
  bool CheckOptionSupported(const char* name,
                            const char* value) const override {
    NOTREACHED();
  }

  const char* GetLocalizedOptionValueName(const char* option_name,
                                          const char* value) const override {
    LocalizationKey key = {option_name, value};
    auto localized_name = localized_strings_.find(key);
    if (localized_name == localized_strings_.end()) {
      return nullptr;
    }
    return localized_name->second.c_str();
  }

  void SetSupportedOptions(std::string_view name, ipp_attribute_t* attribute) {
    supported_attributes_[name] = attribute;
  }

  void SetOptionDefault(std::string_view name, ipp_attribute_t* attribute) {
    default_attributes_[name] = attribute;
  }

  void SetLocalizedOptionValueNames(
      std::map<LocalizationKey, std::string> strings) {
    localized_strings_ = std::move(strings);
  }

  void SetMediaColDatabase(ipp_attribute_t* attribute) {
    media_col_database_ = attribute;
  }

 private:
  std::map<std::string_view, raw_ptr<ipp_attribute_t, CtnExperimental>>
      supported_attributes_;
  std::map<std::string_view, raw_ptr<ipp_attribute_t, CtnExperimental>>
      default_attributes_;
  std::map<LocalizationKey, std::string> localized_strings_;
  raw_ptr<ipp_attribute_t, DanglingUntriaged> media_col_database_;
};

class PrintBackendCupsIppHelperTest : public ::testing::Test {
 protected:
  void SetUp() override {
    ipp_ = ippNew();
    printer_ = std::make_unique<MockCupsPrinterWithMarginsAndAttributes>();
  }

  void TearDown() override {
    ippDelete(ipp_);
    printer_.reset();
  }

  raw_ptr<ipp_t, DanglingUntriaged> ipp_;
  std::unique_ptr<MockCupsPrinterWithMarginsAndAttributes> printer_;
};

ipp_attribute_t* MakeInteger(ipp_t* ipp, int value) {
  return ippAddInteger(ipp, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "TEST_DATA",
                       value);
}

ipp_attribute_t* MakeIntCollection(ipp_t* ipp, const std::vector<int>& values) {
  return ippAddIntegers(ipp, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "TEST_DATA",
                        values.size(), values.data());
}

ipp_attribute_t* MakeRange(ipp_t* ipp, int lower_bound, int upper_bound) {
  return ippAddRange(ipp, IPP_TAG_PRINTER, "TEST_DATA", lower_bound,
                     upper_bound);
}

ipp_attribute_t* MakeString(ipp_t* ipp, const char* value) {
  return ippAddString(ipp, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "TEST_DATA",
                      nullptr, value);
}

ipp_attribute_t* MakeStringCollection(ipp_t* ipp,
                                      const std::vector<const char*>& strings) {
  return ippAddStrings(ipp, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "TEST_DATA",
                       strings.size(), nullptr, strings.data());
}

struct media_info {
  int width;
  int height;
  int bottom_margin;
  int left_margin;
  int right_margin;
  int top_margin;
  std::map<const char*, const char*> keyword_attrs;
  bool is_width_range;
  int width_max;
  bool is_height_range;
  int height_max;
};

// Return true if `db_entry` matches the data specified in `info`
// (`keyword_attrs` are not checked).
bool MediaColDbEntryEquals(ipp_t* db_entry, media_info info) {
  if (!db_entry) {
    return false;
  }
  ipp_t* media_size = ippGetCollection(
      ippFindAttribute(db_entry, kIppMediaSize, IPP_TAG_BEGIN_COLLECTION), 0);
  if (!media_size) {
    return false;
  }

  ipp_attribute_t* bottom_attr =
      ippFindAttribute(db_entry, kIppMediaBottomMargin, IPP_TAG_INTEGER);
  ipp_attribute_t* left_attr =
      ippFindAttribute(db_entry, kIppMediaLeftMargin, IPP_TAG_INTEGER);
  ipp_attribute_t* right_attr =
      ippFindAttribute(db_entry, kIppMediaRightMargin, IPP_TAG_INTEGER);
  ipp_attribute_t* top_attr =
      ippFindAttribute(db_entry, kIppMediaTopMargin, IPP_TAG_INTEGER);
  if (!bottom_attr || !left_attr || !right_attr || !top_attr) {
    return false;
  }
  if (ippGetInteger(bottom_attr, 0) != info.bottom_margin ||
      ippGetInteger(left_attr, 0) != info.left_margin ||
      ippGetInteger(right_attr, 0) != info.right_margin ||
      ippGetInteger(top_attr, 0) != info.top_margin) {
    return false;
  }

  if (info.is_width_range) {
    ipp_attribute_t* width_range_attr =
        ippFindAttribute(media_size, kIppXDimension, IPP_TAG_RANGE);
    if (!width_range_attr) {
      return false;
    }
    int max_width = 0;
    int width = ippGetRange(width_range_attr, 0, &max_width);
    if (width != info.width || max_width != info.width_max) {
      return false;
    }
  } else {
    ipp_attribute_t* width_attr =
        ippFindAttribute(media_size, kIppXDimension, IPP_TAG_INTEGER);
    if (!width_attr) {
      return false;
    }
    int width = ippGetInteger(width_attr, 0);
    if (width != info.width) {
      return false;
    }
  }

  if (info.is_height_range) {
    ipp_attribute_t* height_range_attr =
        ippFindAttribute(media_size, kIppYDimension, IPP_TAG_RANGE);
    if (!height_range_attr) {
      return false;
    }
    int max_height = 0;
    int height = ippGetRange(height_range_attr, 0, &max_height);
    if (height != info.height || max_height != info.height_max) {
      return false;
    }
  } else {
    ipp_attribute_t* height_attr =
        ippFindAttribute(media_size, kIppYDimension, IPP_TAG_INTEGER);
    if (!height_attr) {
      return false;
    }
    int height = ippGetInteger(height_attr, 0);
    if (height != info.height) {
      return false;
    }
  }

  return true;
}

// Returns a vector with pointers to all the entries in `media_col_db`.
// `media_col_db` maintains ownership of the returned values.
std::vector<ipp_t*> GetMediaColEntries(ipp_attribute_t* media_col_db) {
  if (!media_col_db) {
    return std::vector<ipp_t*>();
  }

  std::vector<ipp_t*> retval;
  for (int i = 0; i < ippGetCount(media_col_db); i++) {
    retval.push_back(ippGetCollection(media_col_db, i));
  }

  return retval;
}

ScopedIppPtr MakeMediaCol(const media_info& info) {
  ScopedIppPtr media_col = WrapIpp(ippNew());
  ScopedIppPtr media_size = WrapIpp(ippNew());

  if (info.is_width_range) {
    ippAddRange(media_size.get(), IPP_TAG_ZERO, "x-dimension", info.width,
                info.width_max);
  } else {
    ippAddInteger(media_size.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
                  "x-dimension", info.width);
  }
  if (info.is_height_range) {
    ippAddRange(media_size.get(), IPP_TAG_ZERO, "y-dimension", info.height,
                info.height_max);
  } else {
    ippAddInteger(media_size.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
                  "y-dimension", info.height);
  }

  ippAddCollection(media_col.get(), IPP_TAG_ZERO, "media-size",
                   media_size.get());

  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
                "media-bottom-margin", info.bottom_margin);
  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
                "media-left-margin", info.left_margin);
  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
                "media-right-margin", info.right_margin);
  ippAddInteger(media_col.get(), IPP_TAG_ZERO, IPP_TAG_INTEGER,
                "media-top-margin", info.top_margin);

  for (auto& it : info.keyword_attrs) {
    ippAddString(media_col.get(), IPP_TAG_ZERO, IPP_TAG_KEYWORD, it.first,
                 nullptr, it.second);
  }

  return media_col;
}

ipp_attribute_t* MakeMediaColDefault(ipp_t* ipp, const media_info& info) {
  ScopedIppPtr media_col = MakeMediaCol(info);
  return ippAddCollection(ipp, IPP_TAG_ZERO, "TEST_DATA", media_col.get());
}

ipp_attribute_t* MakeMediaColDatabase(ipp_t* ipp,
                                      const std::vector<media_info>& media) {
  std::vector<ScopedIppPtr> collections;
  std::vector<const ipp_t*> raw_collections;

  for (auto info : media) {
    ScopedIppPtr entry = MakeMediaCol(info);
    raw_collections.emplace_back(entry.get());
    collections.emplace_back(std::move(entry));
  }

  return ippAddCollections(ipp, IPP_TAG_PRINTER, kIppMediaColDatabase,
                           raw_collections.size(), raw_collections.data());
}

TEST_F(PrintBackendCupsIppHelperTest, DefaultPaper) {
  EXPECT_EQ(PrinterSemanticCapsAndDefaults::Paper(), DefaultPaper(*printer_));
  printer_->SetOptionDefault(
      "media-col",
      MakeMediaColDefault(ipp_, {21000, 29700, 10, 10, 10, 10, {}}));
  PrinterSemanticCapsAndDefaults::Paper default_paper = DefaultPaper(*printer_);
  EXPECT_EQ(default_paper.size_um().width(), 210000);
  EXPECT_EQ(default_paper.size_um().height(), 297000);
}

TEST_F(PrintBackendCupsIppHelperTest, CopiesCapable) {
  printer_->SetSupportedOptions("copies", MakeRange(ipp_, 1, 2));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(2, caps.copies_max);
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(PrintBackendCupsIppHelperTest, PaperMargins) {
  // There is one borderless variant of the paper. Thus, the total margins
  // stored will be papers.size() - 1 as borderless margins are not stored
  // as a separate entry.
  base::test::ScopedFeatureList scoped_enable;
  scoped_enable.InitAndEnableFeature(features::kApiPrintingMarginsAndScale);
  static const std::array<PaperMargins, 3> kExpectedMarginsUm = {
      PaperMargins{2960, 3150, 2960, 3150},
      PaperMargins{3900, 100, 6350, 200},
      PaperMargins{1000, 1000, 1000, 1000},
  };

  printer_->SetMediaColDatabase(
      MakeMediaColDatabase(ipp_, {
                                     {21000, 29700, 0, 0, 0, 0, {}},
                                     {21000, 29700, 296, 315, 315, 296, {}},
                                     {21590, 35560, 635, 20, 10, 390, {}},
                                     {18200, 25700, 100, 100, 100, 100, {}},
                                 }));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);
  EXPECT_EQ(kExpectedMarginsUm.size(), caps.papers.size());

  std::vector<PaperMargins> supported_margins_um;
  for (const auto& paper : caps.papers) {
    ASSERT_TRUE(paper.supported_margins_um().has_value());
    supported_margins_um.emplace_back(paper.supported_margins_um().value());
  }
  EXPECT_THAT(supported_margins_um,
              UnorderedElementsAreArray(kExpectedMarginsUm));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

TEST_F(PrintBackendCupsIppHelperTest, CopiesNotCapable) {
  // copies missing, no setup
  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(1, caps.copies_max);
}

TEST_F(PrintBackendCupsIppHelperTest, ColorPrinter) {
  printer_->SetSupportedOptions(
      "print-color-mode", MakeStringCollection(ipp_, {"color", "monochrome"}));
  printer_->SetOptionDefault("print-color-mode", MakeString(ipp_, "color"));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_TRUE(caps.color_changeable);
  EXPECT_TRUE(caps.color_default);
}

TEST_F(PrintBackendCupsIppHelperTest, BWPrinter) {
  printer_->SetSupportedOptions("print-color-mode",
                                MakeStringCollection(ipp_, {"monochrome"}));
  printer_->SetOptionDefault("print-color-mode",
                             MakeString(ipp_, "monochrome"));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_FALSE(caps.color_changeable);
  EXPECT_FALSE(caps.color_default);
}

TEST_F(PrintBackendCupsIppHelperTest, DuplexSupported) {
  printer_->SetSupportedOptions(
      "sides",
      MakeStringCollection(ipp_, {"two-sided-long-edge", "one-sided"}));
  printer_->SetOptionDefault("sides", MakeString(ipp_, "one-sided"));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_THAT(caps.duplex_modes,
              UnorderedElementsAre(mojom::DuplexMode::kSimplex,
                                   mojom::DuplexMode::kLongEdge));
  EXPECT_EQ(mojom::DuplexMode::kSimplex, caps.duplex_default);
}

TEST_F(PrintBackendCupsIppHelperTest, DuplexNotSupported) {
  printer_->SetSupportedOptions("sides",
                                MakeStringCollection(ipp_, {"one-sided"}));
  printer_->SetOptionDefault("sides", MakeString(ipp_, "one-sided"));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_THAT(caps.duplex_modes,
              UnorderedElementsAre(mojom::DuplexMode::kSimplex));
  EXPECT_EQ(mojom::DuplexMode::kSimplex, caps.duplex_default);
}

TEST_F(PrintBackendCupsIppHelperTest, MediaTypes) {
  printer_->SetSupportedOptions(
      "media-type",
      MakeStringCollection(
          ipp_, {"stationery", "custom-1", "photographic-glossy", "custom-2"}));
  printer_->SetOptionDefault("media-type", MakeString(ipp_, "stationery"));

  // set mock display names that would be read from the printer's strings file
  // (printer-strings-uri)
  printer_->SetLocalizedOptionValueNames({
      {{"media-type", "stationery"}, "Plain Paper"},
      {{"media-type", "custom-2"}, "Custom Two"},
  });

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(caps.default_media_type.vendor_id, "stationery");
  ASSERT_EQ(caps.media_types.size(), 4U);
  EXPECT_EQ(caps.media_types[0].vendor_id, "stationery");
  EXPECT_EQ(caps.media_types[0].display_name, "Plain Paper");
  EXPECT_EQ(caps.media_types[1].vendor_id, "custom-1");
  EXPECT_EQ(caps.media_types[1].display_name, "custom-1");
  EXPECT_EQ(caps.media_types[2].vendor_id, "photographic-glossy");
  EXPECT_EQ(caps.media_types[2].display_name, "photographic-glossy");
  EXPECT_EQ(caps.media_types[3].vendor_id, "custom-2");
  EXPECT_EQ(caps.media_types[3].display_name, "Custom Two");
}

TEST_F(PrintBackendCupsIppHelperTest, DefaultMediaTypeNotSupported) {
  printer_->SetSupportedOptions(
      "media-type",
      MakeStringCollection(ipp_, {"stationery", "photographic-glossy"}));
  printer_->SetOptionDefault("media-type",
                             MakeString(ipp_, "not-actually-supported"));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(caps.default_media_type.vendor_id, "stationery");
  ASSERT_EQ(caps.media_types.size(), 2U);
  EXPECT_EQ(caps.media_types[0].vendor_id, "stationery");
  EXPECT_EQ(caps.media_types[1].vendor_id, "photographic-glossy");
}

TEST_F(PrintBackendCupsIppHelperTest, A4PaperSupported) {
  printer_->SetMediaColDatabase(
      MakeMediaColDatabase(ipp_, {{21000, 29700, 10, 10, 10, 10, {}}}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(1U, caps.papers.size());

  PrinterSemanticCapsAndDefaults::Paper paper = caps.papers[0];
  EXPECT_EQ(210000, paper.size_um().width());
  EXPECT_EQ(297000, paper.size_um().height());
}

#if BUILDFLAG(IS_MAC)
TEST_F(PrintBackendCupsIppHelperTest, NearA4PaperDetected) {
  printer_->SetMediaColDatabase(
      MakeMediaColDatabase(ipp_, {{20990, 29704, 10, 10, 10, 10, {}}}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(1U, caps.papers.size());

  PrinterSemanticCapsAndDefaults::Paper paper = caps.papers[0];
  EXPECT_EQ(210000, paper.size_um().width());
  EXPECT_EQ(297000, paper.size_um().height());
}

TEST_F(PrintBackendCupsIppHelperTest, NonStandardPaperUnchanged) {
  printer_->SetMediaColDatabase(
      MakeMediaColDatabase(ipp_, {{20800, 29500, 10, 10, 10, 10, {}}}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(1U, caps.papers.size());

  PrinterSemanticCapsAndDefaults::Paper paper = caps.papers[0];
  EXPECT_EQ(208000, paper.size_um().width());
  EXPECT_EQ(295000, paper.size_um().height());
}
#endif  // BUILDFLAG(IS_MAC)

TEST_F(PrintBackendCupsIppHelperTest, CustomPaperSupported) {
  printer_->SetMediaColDatabase(MakeMediaColDatabase(
      ipp_, {{8000, 2540, 10, 10, 10, 10, {}, false, 0, true, 254000}}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(1U, caps.papers.size());

  PrinterSemanticCapsAndDefaults::Paper paper = caps.papers[0];
  EXPECT_EQ(80000, paper.size_um().width());
  EXPECT_EQ(25400, paper.size_um().height());
  EXPECT_EQ(2540000, paper.max_height_um());
}

TEST_F(PrintBackendCupsIppHelperTest, CustomPaperWithZeroMinHeight) {
  printer_->SetMediaColDatabase(MakeMediaColDatabase(
      ipp_, {{8000, 0, 0, 0, 0, 0, {}, false, 8000, true, 254000}}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(1U, caps.papers.size());

  // Zero-height paper is not allowed.  However, this paper will get used but
  // the height will get changed into some small, non-zero value.
  PrinterSemanticCapsAndDefaults::Paper paper = caps.papers[0];
  EXPECT_EQ(80000, paper.size_um().width());
  EXPECT_EQ(2540000, paper.max_height_um());
  EXPECT_TRUE(paper.size_um().height() > 0);
}

TEST_F(PrintBackendCupsIppHelperTest, CustomPaperWithInvalidHeight) {
  // Max height is less than min height, which is not allowed.
  printer_->SetMediaColDatabase(MakeMediaColDatabase(
      ipp_, {{2000, 2540, 0, 0, 0, 0, {}, true, 8000, true, 1000}}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(0U, caps.papers.size());
}

TEST_F(PrintBackendCupsIppHelperTest, CustomPaperWithInvalidWidth) {
  // Varible-width pages are not supported.
  printer_->SetMediaColDatabase(MakeMediaColDatabase(
      ipp_, {{2000, 2540, 0, 0, 0, 0, {}, true, 8000, true, 254000}}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(0U, caps.papers.size());
}

TEST_F(PrintBackendCupsIppHelperTest, LegalPaperDefault) {
  // na_legal_8.5x14in
  printer_->SetOptionDefault(
      "media-col",
      MakeMediaColDefault(ipp_, {21590, 35560, 10, 10, 10, 10, {}}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);
  EXPECT_EQ(215900, caps.default_paper.size_um().width());
  EXPECT_EQ(355600, caps.default_paper.size_um().height());
}

// Tests that CapsAndDefaultsFromPrinter() does not propagate papers with
// invalid sizes or margins to the Chromium print backend.
TEST_F(PrintBackendCupsIppHelperTest, OmitPapersWithInvalidSizes) {
  printer_->SetMediaColDatabase(
      MakeMediaColDatabase(ipp_, {
                                     {18200, 25700, 100, 100, 100, 100, {}},
                                     {0, 29700, 100, 100, 100, 100, {}},
                                     {-1, 29700, 100, 100, 100, 100, {}},
                                     {21000, 0, 100, 100, 100, 100, {}},
                                     {21000, -1, 100, 100, 100, 100, {}},
                                     {21000, 29700, -1, 100, 100, 100, {}},
                                     {21000, 29700, 100, -1, 100, 100, {}},
                                     {21000, 29700, 100, 100, -1, 100, {}},
                                     {21000, 29700, 100, 100, 100, -1, {}},
                                     {21000, 29700, 100, 10500, 10500, 100, {}},
                                     {21000, 29700, 14850, 100, 100, 14850, {}},
                                     {17600, 25000, 100, 100, 100, 100, {}},
                                 }));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  // The printer reports that it supports four media sizes, two of which
  // are invalid (``invalidsize'' and the empty vendor ID). The
  // preceding call to CapsAndDefaultsFromPrinter() will have dropped
  // these invalid sizes.
  ASSERT_EQ(2U, caps.papers.size());
  for (const auto& paper : caps.papers) {
    EXPECT_NE(21000, paper.size_um().width());
    EXPECT_NE(29700, paper.size_um().height());
  }
}

// Tests that CapsAndDefaultsFromPrinter() will propagate custom size ranges
// from the  the media-col-database to the Chromium print backend.
TEST_F(PrintBackendCupsIppHelperTest, IncludePapersWithSizeRanges) {
  printer_->SetMediaColDatabase(MakeMediaColDatabase(
      ipp_, {
                {11430, 26352, 100, 100, 100, 100, {}},
                {8000, 2540, 100, 100, 100, 100, {}, false, 0, true, 2540000},
                {20320, 25400, 100, 100, 100, 100, {}},
                {100000, 141400, 100, 100, 100, 100, {}},
            }));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  // The printer reports that it supports four media sizes, one of which
  // contains a custom range.  All four of these should be supported.
  ASSERT_EQ(4U, caps.papers.size());
}

// Tests that when the media-col-database contains both bordered and borderless
// versions of a size, CapsAndDefaultsFromPrinter() takes the bordered version
// and marks it as having a borderless variant.
TEST_F(PrintBackendCupsIppHelperTest, HandleBorderlessVariants) {
  PrinterSemanticCapsAndDefaults caps;

  printer_->SetMediaColDatabase(
      MakeMediaColDatabase(ipp_, {
                                     {21000, 29700, 100, 100, 100, 100, {}},
                                     {21000, 29700, 0, 0, 0, 0, {}},
                                 }));
  CapsAndDefaultsFromPrinter(*printer_, &caps);
  ASSERT_EQ(1U, caps.papers.size());
  EXPECT_NE(gfx::Rect(0, 0, 210000, 297000),
            caps.papers[0].printable_area_um());
  EXPECT_TRUE(caps.papers[0].has_borderless_variant());

  printer_->SetMediaColDatabase(
      MakeMediaColDatabase(ipp_, {
                                     {21000, 29700, 0, 0, 0, 0, {}},
                                     {21000, 29700, 100, 100, 100, 100, {}},
                                 }));
  CapsAndDefaultsFromPrinter(*printer_, &caps);
  ASSERT_EQ(1U, caps.papers.size());
  EXPECT_NE(gfx::Rect(0, 0, 210000, 297000),
            caps.papers[0].printable_area_um());
  EXPECT_TRUE(caps.papers[0].has_borderless_variant());

  // If the only available version of a size is borderless, go ahead and use it.
  // Not sure if any actual printers do this, but it's allowed by the IPP spec.
  printer_->SetMediaColDatabase(
      MakeMediaColDatabase(ipp_, {
                                     {21000, 29700, 0, 0, 0, 0, {}},
                                 }));
  CapsAndDefaultsFromPrinter(*printer_, &caps);
  ASSERT_EQ(1U, caps.papers.size());
  EXPECT_EQ(gfx::Rect(0, 0, 210000, 297000),
            caps.papers[0].printable_area_um());
  EXPECT_FALSE(caps.papers[0].has_borderless_variant());

  // If the only available versions of a size are bordered, there shouldn't be
  // a borderless variant.
  printer_->SetMediaColDatabase(
      MakeMediaColDatabase(ipp_, {
                                     {21000, 29700, 100, 100, 100, 100, {}},
                                     {21000, 29700, 200, 200, 200, 200, {}},
                                 }));
  CapsAndDefaultsFromPrinter(*printer_, &caps);
  ASSERT_EQ(1U, caps.papers.size());
  EXPECT_EQ(gfx::Rect(1000, 1000, 210000 - 1000 - 1000, 297000 - 1000 - 1000),
            caps.papers[0].printable_area_um());
  EXPECT_FALSE(caps.papers[0].has_borderless_variant());
}

// At the time of this writing, there are no media-source or media-type
// attributes in the media-col-database that cupsd gives us. However, according
// to the IPP spec, each paper size *should* have a separate variant for each
// supported combination of size and type. So make sure behavior doesn't change
// and we don't create duplicate paper sizes when/if CUPS improves in the
// future.
TEST_F(PrintBackendCupsIppHelperTest, NoDuplicateSizes) {
  printer_->SetMediaColDatabase(MakeMediaColDatabase(
      ipp_,
      {
          {21000,
           29700,
           300,
           300,
           300,
           300,
           {{"media-type", "stationery"}, {"media-source", "main"}}},
          {21000,
           29700,
           300,
           300,
           300,
           300,
           {{"media-type", "stationery"}, {"media-source", "main"}}},
          {21000,
           29700,
           500,
           500,
           500,
           500,
           {{"media-type", "stationery"}, {"media-source", "main"}}},
          {21000,
           29700,
           300,
           300,
           300,
           300,
           {{"media-type", "photographic"}, {"media-source", "main"}}},
          {21000,
           29700,
           0,
           0,
           0,
           0,
           {{"media-type", "photographic"}, {"media-source", "main"}}},
          {21000,
           29700,
           300,
           300,
           300,
           300,
           {{"media-type", "photographic-high-gloss"},
            {"media-source", "main"}}},
          {21000,
           29700,
           0,
           0,
           0,
           0,
           {{"media-type", "photographic-high-gloss"},
            {"media-source", "main"}}},
          {21000,
           29700,
           300,
           300,
           300,
           300,
           {{"media-type", "photographic-glossy"}, {"media-source", "main"}}},
          {21000,
           29700,
           0,
           0,
           0,
           0,
           {{"media-type", "photographic-glossy"}, {"media-source", "main"}}},
          {21000,
           29700,
           300,
           300,
           300,
           300,
           {{"media-type", "photographic-semi-gloss"},
            {"media-source", "main"}}},
          {21000,
           29700,
           0,
           0,
           0,
           0,
           {{"media-type", "photographic-semi-gloss"},
            {"media-source", "main"}}},
          {21000,
           29700,
           300,
           300,
           300,
           300,
           {{"media-type", "photographic-matte"}, {"media-source", "main"}}},
          {21000,
           29700,
           0,
           0,
           0,
           0,
           {{"media-type", "photographic-matte"}, {"media-source", "main"}}},
      }));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(1U, caps.papers.size());
}

TEST_F(PrintBackendCupsIppHelperTest, FilterMediaColSizesNominal) {
  // Create db with no variable-size entries.  All 4 should be retained after
  // filtering.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(ipp.get(), {
                                      {5800, 20000},
                                      {5800, 200000},
                                      {8000, 20000},
                                      {8000, 200000},
                                  });

  FilterMediaColSizes(ipp);

  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);

  ASSERT_TRUE(media_col_db);
  EXPECT_THAT(
      GetMediaColEntries(media_col_db),
      UnorderedPointwise(EqualsMediaColEntry(),
                         {media_info{5800, 20000}, media_info{5800, 200000},
                          media_info{8000, 20000}, media_info{8000, 200000}}));
}

TEST_F(PrintBackendCupsIppHelperTest,
       FilterMediaColSizesVariableWidthAndHeight) {
  // This db has one variable-sized entry and 2 fixed widths, so the
  // variable-sized entry should get replaced by two variable-sized height
  // entries with the fixed widths.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(
      ipp.get(), {
                     {5800, 20000},
                     {5800, 200000},
                     {8000, 20000},
                     {8000, 200000},
                     {2540, 2540, 0, 0, 0, 0, {}, true, 8000, true, 200000},
                 });

  FilterMediaColSizes(ipp);
  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
  ASSERT_TRUE(media_col_db);
  EXPECT_THAT(
      GetMediaColEntries(media_col_db),
      UnorderedPointwise(
          EqualsMediaColEntry(),
          {media_info{5800, 20000}, media_info{5800, 200000},
           media_info{8000, 20000}, media_info{8000, 200000},
           media_info{5800, 2540, 0, 0, 0, 0, {}, false, 0, true, 200000},
           media_info{8000, 2540, 0, 0, 0, 0, {}, false, 0, true, 200000}}));
}

TEST_F(PrintBackendCupsIppHelperTest, FilterMediaColSizesVariableWidth) {
  // Variable width entries (with fixed height) will get filtered out.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(ipp.get(),
                       {
                           {5800, 20000},
                           {5800, 200000},
                           {8000, 20000},
                           {8000, 200000},
                           {7000, 20000, 0, 0, 0, 0, {}, true, 8000, false, 0},
                       });

  FilterMediaColSizes(ipp);
  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
  ASSERT_TRUE(media_col_db);
  EXPECT_THAT(
      GetMediaColEntries(media_col_db),
      UnorderedPointwise(EqualsMediaColEntry(),
                         {media_info{5800, 20000}, media_info{5800, 200000},
                          media_info{8000, 20000}, media_info{8000, 200000}}));
}

TEST_F(PrintBackendCupsIppHelperTest, FilterMediaColSizesVariableHeight) {
  // Entry with fixed width and variable height should get retained after
  // filtering.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(ipp.get(),
                       {
                           {5800, 20000},
                           {5800, 200000},
                           {8000, 20000},
                           {8000, 200000},
                           {8000, 2540, 0, 0, 0, 0, {}, false, 0, true, 200000},
                       });

  FilterMediaColSizes(ipp);
  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
  ASSERT_TRUE(media_col_db);
  EXPECT_THAT(
      GetMediaColEntries(media_col_db),
      UnorderedPointwise(
          EqualsMediaColEntry(),
          {media_info{5800, 20000}, media_info{5800, 200000},
           media_info{8000, 20000}, media_info{8000, 200000},
           media_info{8000, 2540, 0, 0, 0, 0, {}, false, 0, true, 200000}}));
}

TEST_F(PrintBackendCupsIppHelperTest, FilterMediaColSizesSameVariableHeight) {
  // Entry with a variable height with the min and max equal to each other just
  // ends up being a fixed height entry.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(ipp.get(),
                       {
                           {8000, 20000, 0, 0, 0, 0, {}, false, 0, true, 20000},
                       });

  FilterMediaColSizes(ipp);
  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
  ASSERT_TRUE(media_col_db);
  EXPECT_THAT(
      GetMediaColEntries(media_col_db),
      UnorderedPointwise(EqualsMediaColEntry(), {media_info{8000, 20000}}));
}

TEST_F(PrintBackendCupsIppHelperTest, FilterMediaColSizesSingleVariableEntry) {
  // Since there are no fixed widths, this will get filtered out.  Consequently,
  // the media-col-database entry won't even exist.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(
      ipp.get(), {
                     {2540, 2540, 0, 0, 0, 0, {}, true, 8000, true, 200000},
                 });

  FilterMediaColSizes(ipp);
  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
  ASSERT_FALSE(media_col_db);
}

TEST_F(PrintBackendCupsIppHelperTest, FilterMediaColSizesInvalidWidth) {
  // This db has one variable-sized entry which has a minimum width that only
  // matches one of the fixed width sizes, so only one variable entry will be
  // retained.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(
      ipp.get(), {
                     {5800, 20000},
                     {5800, 200000},
                     {8000, 20000},
                     {8000, 200000},
                     {7000, 2540, 0, 0, 0, 0, {}, true, 8000, true, 200000},
                 });

  FilterMediaColSizes(ipp);
  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
  ASSERT_TRUE(media_col_db);
  EXPECT_THAT(
      GetMediaColEntries(media_col_db),
      UnorderedPointwise(
          EqualsMediaColEntry(),
          {media_info{5800, 20000}, media_info{5800, 200000},
           media_info{8000, 20000}, media_info{8000, 200000},
           media_info{8000, 2540, 0, 0, 0, 0, {}, false, 0, true, 200000}}));
}

TEST_F(PrintBackendCupsIppHelperTest,
       FilterMediaColSizesMultipleVariableEntries) {
  // Multiple variable entries - non-overlapping.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(
      ipp.get(), {
                     {5800, 20000},
                     {5800, 200000},
                     {8000, 20000},
                     {8000, 200000},
                     {5000, 2540, 0, 0, 0, 0, {}, true, 6000, true, 100000},
                     {7000, 2540, 0, 0, 0, 0, {}, true, 8000, true, 200000},
                 });

  FilterMediaColSizes(ipp);
  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
  ASSERT_TRUE(media_col_db);
  EXPECT_THAT(
      GetMediaColEntries(media_col_db),
      UnorderedPointwise(
          EqualsMediaColEntry(),
          {media_info{5800, 20000}, media_info{5800, 200000},
           media_info{8000, 20000}, media_info{8000, 200000},
           media_info{5800, 2540, 0, 0, 0, 0, {}, false, 0, true, 100000},
           media_info{8000, 2540, 0, 0, 0, 0, {}, false, 0, true, 200000}}));
}

TEST_F(PrintBackendCupsIppHelperTest,
       FilterMediaColSizesMultipleVariableEntriesOverlap) {
  // Multiple variable entries - overlapping.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(
      ipp.get(), {
                     {5800, 20000},
                     {5800, 200000},
                     {8000, 20000},
                     {8000, 200000},
                     {2540, 4000, 0, 0, 0, 0, {}, true, 8000, true, 100000},
                     {2540, 5000, 0, 0, 0, 0, {}, true, 8000, true, 200000},
                 });

  FilterMediaColSizes(ipp);
  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
  ASSERT_TRUE(media_col_db);
  EXPECT_THAT(
      GetMediaColEntries(media_col_db),
      UnorderedPointwise(
          EqualsMediaColEntry(),
          {media_info{5800, 20000}, media_info{5800, 200000},
           media_info{8000, 20000}, media_info{8000, 200000},
           media_info{5800, 4000, 0, 0, 0, 0, {}, false, 0, true, 100000},
           media_info{8000, 4000, 0, 0, 0, 0, {}, false, 0, true, 100000},
           media_info{5800, 5000, 0, 0, 0, 0, {}, false, 0, true, 200000},
           media_info{8000, 5000, 0, 0, 0, 0, {}, false, 0, true, 200000}}));
}

TEST_F(PrintBackendCupsIppHelperTest,
       FilterMediaColSizesMultipleVariableEntriesOverlapOutsideRange) {
  // Multiple variable entries - overlapping.  The second variable-length entry
  // will get rejected because there is no fixed width in bounds.
  ScopedIppPtr ipp = WrapIpp(ippNew());
  MakeMediaColDatabase(
      ipp.get(), {
                     {5800, 20000},
                     {2540, 4000, 0, 0, 0, 0, {}, true, 8000, true, 100000},
                     {2540, 5000, 0, 0, 0, 0, {}, true, 3000, true, 200000},
                 });

  FilterMediaColSizes(ipp);
  ipp_attribute_t* media_col_db = ippFindAttribute(
      ipp.get(), kIppMediaColDatabase, IPP_TAG_BEGIN_COLLECTION);
  ASSERT_TRUE(media_col_db);
  EXPECT_THAT(
      GetMediaColEntries(media_col_db),
      UnorderedPointwise(
          EqualsMediaColEntry(),
          {media_info{5800, 20000},
           media_info{5800, 4000, 0, 0, 0, 0, {}, false, 0, true, 100000}}));
}

TEST_F(PrintBackendCupsIppHelperTest,
       OverrideUnavailableCanonDefaultMediaType) {
  printer_->SetSupportedOptions(
      "media-type",
      MakeStringCollection(ipp_, {"com.canon.unavailable", "stationery"}));
  printer_->SetOptionDefault("media-type",
                             MakeString(ipp_, "com.canon.unavailable"));

  printer_->SetLocalizedOptionValueNames({
      {{"media-type", "com.canon.unavailable"}, "Unavailable Media"},
      {{"media-type", "stationery"}, "Plain Paper"},
  });

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);
  EXPECT_EQ(caps.default_media_type.vendor_id, "stationery");
}

TEST_F(PrintBackendCupsIppHelperTest,
       OverrideUnavailableCanonDefaultMediaTypeStationeryUnavailable) {
  printer_->SetSupportedOptions(
      "media-type",
      MakeStringCollection(ipp_, {"com.canon.unavailable", "not.stationery"}));
  printer_->SetOptionDefault("media-type",
                             MakeString(ipp_, "com.canon.unavailable"));

  printer_->SetLocalizedOptionValueNames({
      {{"media-type", "com.canon.unavailable"}, "Unavailable Media"},
      {{"media-type", "not.stationery"}, "Not Plain Paper"},
  });

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);
  EXPECT_EQ(caps.default_media_type.vendor_id, "com.canon.unavailable");
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(PrintBackendCupsIppHelperTest, PinSupported) {
  printer_->SetSupportedOptions("job-password", MakeInteger(ipp_, 4));
  printer_->SetSupportedOptions("job-password-encryption",
                                MakeStringCollection(ipp_, {"none"}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_TRUE(caps.pin_supported);
}

TEST_F(PrintBackendCupsIppHelperTest, PinNotSupported) {
  // Pin support missing, no setup.
  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_FALSE(caps.pin_supported);
}

TEST_F(PrintBackendCupsIppHelperTest, PinEncryptionNotSupported) {
  printer_->SetSupportedOptions("job-password", MakeInteger(ipp_, 4));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_FALSE(caps.pin_supported);
}

TEST_F(PrintBackendCupsIppHelperTest, PinTooShort) {
  printer_->SetSupportedOptions("job-password", MakeInteger(ipp_, 3));
  printer_->SetSupportedOptions("job-password-encryption",
                                MakeStringCollection(ipp_, {"none"}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_FALSE(caps.pin_supported);
}

TEST_F(PrintBackendCupsIppHelperTest, AdvancedCaps) {
  base::HistogramTester histograms;

  printer_->SetSupportedOptions(
      "job-creation-attributes",
      MakeStringCollection(
          ipp_, {"copies", "confirmation-sheet-print", "finishings",
                 "job-message-to-operator", "output-bin", "print-quality"}));
  printer_->SetSupportedOptions("finishings",
                                MakeIntCollection(ipp_, {3, 7, 10}));
  printer_->SetSupportedOptions(
      "output-bin", MakeStringCollection(ipp_, {"face-down", "face-up"}));
  printer_->SetSupportedOptions("print-quality",
                                MakeIntCollection(ipp_, {3, 4, 5}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(6u, caps.advanced_capabilities.size());
  EXPECT_EQ("confirmation-sheet-print", caps.advanced_capabilities[0].name);
  EXPECT_EQ(AdvancedCapability::Type::kBoolean,
            caps.advanced_capabilities[0].type);
  EXPECT_EQ("finishings/7", caps.advanced_capabilities[1].name);
  EXPECT_EQ(AdvancedCapability::Type::kBoolean,
            caps.advanced_capabilities[1].type);
  EXPECT_EQ("finishings/10", caps.advanced_capabilities[2].name);
  EXPECT_EQ(AdvancedCapability::Type::kBoolean,
            caps.advanced_capabilities[2].type);
  EXPECT_EQ("job-message-to-operator", caps.advanced_capabilities[3].name);
  EXPECT_EQ(AdvancedCapability::Type::kString,
            caps.advanced_capabilities[3].type);
  EXPECT_EQ("output-bin", caps.advanced_capabilities[4].name);
  EXPECT_EQ(AdvancedCapability::Type::kString,
            caps.advanced_capabilities[4].type);
  EXPECT_EQ(2u, caps.advanced_capabilities[4].values.size());
  EXPECT_EQ("print-quality", caps.advanced_capabilities[5].name);
  EXPECT_EQ(AdvancedCapability::Type::kString,
            caps.advanced_capabilities[5].type);
  EXPECT_EQ(3u, caps.advanced_capabilities[5].values.size());
  histograms.ExpectUniqueSample("Printing.CUPS.IppAttributesCount", 5, 1);
}

TEST_F(PrintBackendCupsIppHelperTest, MediaSource) {
  printer_->SetSupportedOptions(
      "media-source",
      MakeStringCollection(ipp_, {"top", "main", "auto", "tray-3", "tray-4"}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  ASSERT_EQ(1u, caps.advanced_capabilities.size());
  const AdvancedCapability& cap = caps.advanced_capabilities[0];
  EXPECT_EQ("media-source", cap.name);
  EXPECT_EQ(AdvancedCapability::Type::kString, cap.type);
  EXPECT_THAT(cap.values,
              Pointwise(AdvancedCapabilityName(),
                        {"top", "main", "auto", "tray-3", "tray-4"}));
}

// Test print-scaling values are correctly stored.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes) {
  base::test::ScopedFeatureList scoped_enable;
  scoped_enable.InitAndEnableFeature(features::kApiPrintingMarginsAndScale);
  printer_->SetSupportedOptions(
      "print-scaling",
      MakeStringCollection(ipp_, {"auto", "auto-fit", "fill", "fit", "none"}));
  printer_->SetOptionDefault("print-scaling", MakeString(ipp_, "fit"));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(caps.print_scaling_type_default, mojom::PrintScalingType::kFit);
  EXPECT_THAT(
      caps.print_scaling_types,
      UnorderedElementsAreArray(
          {mojom::PrintScalingType::kAuto, mojom::PrintScalingType::kAutoFit,
           mojom::PrintScalingType::kFill, mojom::PrintScalingType::kFit,
           mojom::PrintScalingType::kNone}));
}

TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_CorrectMapping) {
  base::test::ScopedFeatureList scoped_enable;
  scoped_enable.InitAndEnableFeature(features::kApiPrintingMarginsAndScale);
  struct ScalingTypeToString {
    std::string str;
    mojom::PrintScalingType type;
  };
  constexpr std::array<ScalingTypeToString, 5> kScalingTypes{
      ScalingTypeToString{"auto", mojom::PrintScalingType::kAuto},
      ScalingTypeToString{"auto-fit", mojom::PrintScalingType::kAutoFit},
      ScalingTypeToString{"fill", mojom::PrintScalingType::kFill},
      ScalingTypeToString{"fit", mojom::PrintScalingType::kFit},
      ScalingTypeToString{"none", mojom::PrintScalingType::kNone}};

  for (const auto& value : kScalingTypes) {
    printer_->SetSupportedOptions(
        "print-scaling", MakeStringCollection(ipp_, {value.str.c_str()}));
    PrinterSemanticCapsAndDefaults caps;
    CapsAndDefaultsFromPrinter(*printer_, &caps);

    EXPECT_EQ(caps.print_scaling_type_default, value.type);
    EXPECT_THAT(caps.print_scaling_types,
                UnorderedElementsAreArray({value.type}));
  }
}

// Test first value from supported values is used as default if default is
// missing.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_NoDefault) {
  base::test::ScopedFeatureList scoped_enable;
  scoped_enable.InitAndEnableFeature(features::kApiPrintingMarginsAndScale);
  printer_->SetSupportedOptions(
      "print-scaling",
      MakeStringCollection(ipp_, {"auto-fit", "fill", "none"}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(caps.print_scaling_type_default, mojom::PrintScalingType::kAutoFit);
  EXPECT_THAT(caps.print_scaling_types,
              UnorderedElementsAreArray({mojom::PrintScalingType::kAutoFit,
                                         mojom::PrintScalingType::kFill,
                                         mojom::PrintScalingType::kNone}));
}

// Test first value from supported values is used as default if default is
// unknown.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_DefaultUnknown) {
  base::test::ScopedFeatureList scoped_enable;
  scoped_enable.InitAndEnableFeature(features::kApiPrintingMarginsAndScale);
  printer_->SetSupportedOptions(
      "print-scaling", MakeStringCollection(ipp_, {"fit", "fill", "none"}));
  printer_->SetOptionDefault("print-scaling", MakeString(ipp_, "value-1"));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(caps.print_scaling_type_default, mojom::PrintScalingType::kFit);
  EXPECT_THAT(caps.print_scaling_types,
              UnorderedElementsAreArray({mojom::PrintScalingType::kFit,
                                         mojom::PrintScalingType::kFill,
                                         mojom::PrintScalingType::kNone}));
}

// Test no values are stored when there are no supported values.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_NoValues) {
  base::test::ScopedFeatureList scoped_enable;
  scoped_enable.InitAndEnableFeature(features::kApiPrintingMarginsAndScale);
  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(caps.print_scaling_type_default,
            mojom::PrintScalingType::kUnknownPrintScalingType);
  EXPECT_TRUE(caps.print_scaling_types.empty());
}

// Test no values are stored despite printer saying it has one default value
// while supported values are empty.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_NoValues2) {
  base::test::ScopedFeatureList scoped_enable;
  scoped_enable.InitAndEnableFeature(features::kApiPrintingMarginsAndScale);
  printer_->SetOptionDefault("print-scaling", MakeString(ipp_, "auto"));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(caps.print_scaling_type_default,
            mojom::PrintScalingType::kUnknownPrintScalingType);
  EXPECT_TRUE(caps.print_scaling_types.empty());
}

// Test unknown values are not stored and default is correctly set as first
// value of known print-scaling types.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_UnknownValues) {
  base::test::ScopedFeatureList scoped_enable;
  scoped_enable.InitAndEnableFeature(features::kApiPrintingMarginsAndScale);
  printer_->SetSupportedOptions(
      "print-scaling",
      MakeStringCollection(ipp_, {"value-1", "value-2", "auto", "none"}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(caps.print_scaling_type_default, mojom::PrintScalingType::kAuto);
  EXPECT_THAT(caps.print_scaling_types,
              UnorderedElementsAreArray({mojom::PrintScalingType::kAuto,
                                         mojom::PrintScalingType::kNone}));
}

// Test default is not set if only unknown values are supported.
TEST_F(PrintBackendCupsIppHelperTest, PrintScalingTypes_UnknownValues2) {
  base::test::ScopedFeatureList scoped_enable;
  scoped_enable.InitAndEnableFeature(features::kApiPrintingMarginsAndScale);
  printer_->SetSupportedOptions(
      "print-scaling",
      MakeStringCollection(ipp_, {"value-1", "value-2", "value-3", "value-4"}));

  PrinterSemanticCapsAndDefaults caps;
  CapsAndDefaultsFromPrinter(*printer_, &caps);

  EXPECT_EQ(caps.print_scaling_type_default,
            mojom::PrintScalingType::kUnknownPrintScalingType);
  EXPECT_TRUE(caps.print_scaling_types.empty());
}

#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace printing