// Copyright 2023 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/usb_printer_util.h"

#include "services/device/public/mojom/usb_device.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace {

using device::mojom::UsbAlternateInterfaceInfo;
using device::mojom::UsbConfigurationInfo;
using device::mojom::UsbDeviceInfo;
using device::mojom::UsbInterfaceInfo;
using testing::ElementsAre;
using testing::Eq;
using testing::HasSubstr;
using testing::Optional;

TEST(UsbPrinterUtilTest, UsbDeviceToPrinterWithValidSerialNumber) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;
  device_info.serial_number = u"12345";

  PrinterDetector::DetectedPrinter entry;
  EXPECT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  EXPECT_THAT(entry.printer.uri().GetNormalized(), HasSubstr("?serial=12345"));
}

TEST(UsbPrinterUtilTest, UsbDeviceToPrinterWithNoSerialNumber) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;

  PrinterDetector::DetectedPrinter entry;
  EXPECT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  // If the device_info does not specify a serial number, the URI should get
  // created with the character '?'.
  EXPECT_THAT(entry.printer.uri().GetNormalized(), HasSubstr("?serial=?"));
}

TEST(UsbPrinterUtilTest, UsbDeviceToPrinterWithEmptySerialNumber) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;
  device_info.serial_number = u"";

  PrinterDetector::DetectedPrinter entry;
  EXPECT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  // If the device_info specifies an empty serial number, the URI should get
  // created with the character '?'.
  EXPECT_THAT(entry.printer.uri().GetNormalized(), HasSubstr("?serial=?"));
}

TEST(UsbPrinterUtilTest, UsbDeviceToPrinter_UsbDeviceId) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;

  PrinterDetector::DetectedPrinter entry;
  ASSERT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  EXPECT_THAT(entry.printer.usb_device_id(),
              chromeos::Printer::UsbDeviceId(1, 2));
}

TEST(UsbPrinterUtilTest, GuessEffectiveMakeAndModelDuplicatedManufacturer) {
  UsbDeviceInfo device_info;
  device_info.manufacturer_name = u"bixolon";
  device_info.product_name = u"bixolon abc-123";

  EXPECT_EQ(GuessEffectiveMakeAndModel(device_info), "bixolon abc-123");
}

TEST(UsbPrinterUtilTest, GuessEffectiveMakeAndModel) {
  UsbDeviceInfo device_info;
  device_info.manufacturer_name = u"bixolon";
  device_info.product_name = u"abc-123";

  EXPECT_EQ(GuessEffectiveMakeAndModel(device_info), "bixolon abc-123");
}

TEST(UsbPrinterUtilTest, MakeDisplayNameBothMissing) {
  std::string make_and_model = MakeDisplayName("", "");
  EXPECT_FALSE(make_and_model.empty());
}

TEST(UsbPrinterUtilTest, MakeDisplayNameBothPresent) {
  std::string make_and_model = MakeDisplayName("TheMake", "TheModel");
  EXPECT_NE(make_and_model.find("TheMake"), std::string::npos);
  EXPECT_NE(make_and_model.find("TheModel"), std::string::npos);
}

TEST(UsbPrinterUtilTest, MakeDisplayNameMakeOnly) {
  std::string make_and_model = MakeDisplayName("TheMake", "");
  EXPECT_NE(make_and_model.find("TheMake"), std::string::npos);
}

TEST(UsbPrinterUtilTest, MakeDisplayNameModelOnly) {
  std::string make_and_model = MakeDisplayName("", "TheModel");
  EXPECT_NE(make_and_model.find("TheModel"), std::string::npos);
}

TEST(UsbPrinterUtilTest, SearchDataNotUpdatedWithoutMake) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;
  device_info.serial_number = u"";
  device_info.manufacturer_name = u"make";
  device_info.product_name = u"model";

  PrinterDetector::DetectedPrinter entry;
  EXPECT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  entry.ppd_search_data.make_and_model.push_back("make model");

  chromeos::UsbPrinterId device_id;
  device_id.set_model("new-model");
  UpdateSearchDataFromDeviceId(device_id, &entry);

  EXPECT_EQ("make model", entry.printer.make_and_model());
  EXPECT_THAT(entry.ppd_search_data.make_and_model, ElementsAre("make model"));
}

TEST(UsbPrinterUtilTest, SearchDataNotUpdatedWithoutModel) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;
  device_info.serial_number = u"";
  device_info.manufacturer_name = u"make";
  device_info.product_name = u"model";

  PrinterDetector::DetectedPrinter entry;
  EXPECT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  entry.ppd_search_data.make_and_model.push_back("make model");

  chromeos::UsbPrinterId device_id;
  device_id.set_make("new-make");
  UpdateSearchDataFromDeviceId(device_id, &entry);

  EXPECT_EQ("make model", entry.printer.make_and_model());
  EXPECT_THAT(entry.ppd_search_data.make_and_model, ElementsAre("make model"));
}

TEST(UsbPrinterUtilTest, SearchDataNotUpdatedIfNamesMatch) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;
  device_info.serial_number = u"";
  device_info.manufacturer_name = u"make";
  device_info.product_name = u"model";

  PrinterDetector::DetectedPrinter entry;
  EXPECT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  entry.ppd_search_data.make_and_model.push_back("make model");

  chromeos::UsbPrinterId device_id;
  device_id.set_make("MAKE");
  device_id.set_model("MODEL");
  UpdateSearchDataFromDeviceId(device_id, &entry);

  EXPECT_EQ("make model", entry.printer.make_and_model());
  EXPECT_THAT(entry.ppd_search_data.make_and_model, ElementsAre("make model"));
}

TEST(UsbPrinterUtilTest, SearchDataReplacedForGenericDescriptors) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;
  device_info.serial_number = u"";
  device_info.manufacturer_name = u"epson";
  device_info.product_name = u"usb2.0 mfp(hi-speed)";

  PrinterDetector::DetectedPrinter entry;
  EXPECT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  entry.ppd_search_data.make_and_model.push_back("epson usb2.0 mfp(hi-speed)");

  chromeos::UsbPrinterId device_id;
  device_id.set_make("MAKE");
  device_id.set_model("MODEL");
  UpdateSearchDataFromDeviceId(device_id, &entry);

  EXPECT_EQ("MAKE MODEL", entry.printer.make_and_model());
  EXPECT_THAT(entry.ppd_search_data.make_and_model, ElementsAre("make model"));
}

TEST(UsbPrinterUtilTest, SearchDataReplacedForEmptyMakeModel) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;
  device_info.serial_number = u"";
  device_info.manufacturer_name = u"";
  device_info.product_name = u"";

  PrinterDetector::DetectedPrinter entry;
  EXPECT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  entry.ppd_search_data.make_and_model.push_back("");

  chromeos::UsbPrinterId device_id;
  device_id.set_make("MAKE");
  device_id.set_model("MODEL");
  UpdateSearchDataFromDeviceId(device_id, &entry);

  EXPECT_EQ("MAKE MODEL", entry.printer.make_and_model());
  EXPECT_THAT(entry.ppd_search_data.make_and_model, ElementsAre("make model"));
}

TEST(UsbPrinterUtilTest, SearchDataAugmentedForNonGenericDescriptors) {
  UsbDeviceInfo device_info;
  device_info.vendor_id = 1;
  device_info.product_id = 2;
  device_info.serial_number = u"";
  device_info.manufacturer_name = u"make";
  device_info.product_name = u"model";

  PrinterDetector::DetectedPrinter entry;
  EXPECT_TRUE(UsbDeviceToPrinter(device_info, &entry));
  entry.ppd_search_data.make_and_model.push_back("make model");

  chromeos::UsbPrinterId device_id;
  device_id.set_make("REAL-MAKE");
  device_id.set_model("REAL-MODEL");
  UpdateSearchDataFromDeviceId(device_id, &entry);

  EXPECT_EQ("make model", entry.printer.make_and_model());
  EXPECT_THAT(entry.ppd_search_data.make_and_model,
              ElementsAre("make model", "real-make real-model"));
}

TEST(UsbPrinterUtilTest, FindPrinterInterfaceTargetMissingConfig) {
  UsbDeviceInfo di;
  EXPECT_FALSE(internal::FindPrinterInterfaceTarget(di).has_value());
}

TEST(UsbPrinterUtilTest, FindPrinterInterfaceTargetMissingInterface) {
  UsbDeviceInfo di;
  di.configurations.emplace_back(UsbConfigurationInfo::New());

  EXPECT_FALSE(internal::FindPrinterInterfaceTarget(di).has_value());
}

TEST(UsbPrinterUtilTest, FindPrinterInterfaceTargetMissingAlternate) {
  UsbDeviceInfo di;
  auto& config = di.configurations.emplace_back(UsbConfigurationInfo::New());
  config->interfaces.emplace_back(UsbInterfaceInfo::New());

  EXPECT_FALSE(internal::FindPrinterInterfaceTarget(di).has_value());
}

TEST(UsbPrinterUtilTest, FindPrinterInterfaceTargetNonPrinterAlternate) {
  UsbDeviceInfo di;
  auto& config = di.configurations.emplace_back(UsbConfigurationInfo::New());
  auto& iface = config->interfaces.emplace_back(UsbInterfaceInfo::New());
  auto& alt = iface->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt->class_code = 0;

  EXPECT_FALSE(internal::FindPrinterInterfaceTarget(di).has_value());
}

TEST(UsbPrinterUtilTest, FindPrinterInterfaceTargetOnePrinterAlternate) {
  UsbDeviceInfo di;
  di.vendor_id = 0x1234;
  di.product_id = 0x5678;
  auto& config = di.configurations.emplace_back(UsbConfigurationInfo::New());
  auto& iface = config->interfaces.emplace_back(UsbInterfaceInfo::New());
  auto& alt = iface->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt->class_code = 7;
  alt->subclass_code = 1;

  // The first interface is a printer, so it should be returned.
  internal::PrinterInterfaceTarget expected = {.vidpid = "1234:5678",
                                               .config = 0,
                                               .interface = 0,
                                               .alternate = 0,
                                               .set_alternate = false};

  EXPECT_THAT(internal::FindPrinterInterfaceTarget(di), Optional(Eq(expected)));
}

TEST(UsbPrinterUtilTest, FindPrinterInterfaceTargetPrinterInterfaceSecond) {
  UsbDeviceInfo di;
  di.vendor_id = 0x1234;
  di.product_id = 0x5678;
  auto& config = di.configurations.emplace_back(UsbConfigurationInfo::New());
  auto& iface1 = config->interfaces.emplace_back(UsbInterfaceInfo::New());
  auto& alt1 = iface1->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt1->class_code = 255;
  alt1->subclass_code = 255;
  auto& iface2 = config->interfaces.emplace_back(UsbInterfaceInfo::New());
  auto& alt2 = iface2->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt2->class_code = 7;
  alt2->subclass_code = 1;

  // Skip the first non-printer interface and return the printer interface.
  internal::PrinterInterfaceTarget expected = {.vidpid = "1234:5678",
                                               .config = 0,
                                               .interface = 1,
                                               .alternate = 0,
                                               .set_alternate = false};

  EXPECT_THAT(internal::FindPrinterInterfaceTarget(di), Optional(Eq(expected)));
}

TEST(UsbPrinterUtilTest, FindPrinterInterfaceTargetPrinterSkipMultiAlternates) {
  UsbDeviceInfo di;
  di.vendor_id = 0x1234;
  di.product_id = 0x5678;
  auto& config = di.configurations.emplace_back(UsbConfigurationInfo::New());
  auto& iface1 = config->interfaces.emplace_back(UsbInterfaceInfo::New());
  auto& alt11 = iface1->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt11->class_code = 255;
  alt11->subclass_code = 255;
  auto& alt12 = iface1->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt12->class_code = 7;
  alt12->subclass_code = 1;
  auto& iface2 = config->interfaces.emplace_back(UsbInterfaceInfo::New());
  auto& alt2 = iface2->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt2->class_code = 7;
  alt2->subclass_code = 1;

  // Skip the first interface because its printer class isn't the first
  // alternate.
  internal::PrinterInterfaceTarget expected = {.vidpid = "1234:5678",
                                               .config = 0,
                                               .interface = 1,
                                               .alternate = 0,
                                               .set_alternate = false};

  EXPECT_THAT(internal::FindPrinterInterfaceTarget(di), Optional(Eq(expected)));
}

TEST(UsbPrinterUtilTest,
     FindPrinterInterfaceTargetPrinterForcedMultiAlternates) {
  UsbDeviceInfo di;
  di.vendor_id = 0x1234;
  di.product_id = 0x5678;
  auto& config = di.configurations.emplace_back(UsbConfigurationInfo::New());
  auto& iface1 = config->interfaces.emplace_back(UsbInterfaceInfo::New());
  auto& alt11 = iface1->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt11->class_code = 255;
  alt11->subclass_code = 255;
  auto& alt12 = iface1->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt12->class_code = 7;
  alt12->subclass_code = 1;
  auto& iface2 = config->interfaces.emplace_back(UsbInterfaceInfo::New());
  auto& alt2 = iface2->alternates.emplace_back(
      device::mojom::UsbAlternateInterfaceInfo::New());
  alt2->class_code = 255;
  alt2->subclass_code = 255;

  // Choose the first interface even though it has multiple alternates because
  // none of the other interfaces are printer class.
  internal::PrinterInterfaceTarget expected = {.vidpid = "1234:5678",
                                               .config = 0,
                                               .interface = 0,
                                               .alternate = 1,
                                               .set_alternate = true};

  EXPECT_THAT(internal::FindPrinterInterfaceTarget(di), Optional(Eq(expected)));
}

}  // namespace
}  // namespace ash
