chromium/chrome/browser/ash/crosapi/document_scan_ash_type_converters_unittest.cc

// 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/crosapi/document_scan_ash_type_converters.h"

#include "base/containers/contains.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace mojo {
namespace {

using ::testing::ElementsAre;
using ::testing::UnorderedElementsAre;

TEST(DocumentScanAshTypeConvertersTest, OperationResult) {
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_UNKNOWN),
            crosapi::mojom::ScannerOperationResult::kUnknown);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_SUCCESS),
            crosapi::mojom::ScannerOperationResult::kSuccess);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_UNSUPPORTED),
            crosapi::mojom::ScannerOperationResult::kUnsupported);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_CANCELLED),
            crosapi::mojom::ScannerOperationResult::kCancelled);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_DEVICE_BUSY),
            crosapi::mojom::ScannerOperationResult::kDeviceBusy);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_INVALID),
            crosapi::mojom::ScannerOperationResult::kInvalid);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_WRONG_TYPE),
            crosapi::mojom::ScannerOperationResult::kWrongType);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_EOF),
            crosapi::mojom::ScannerOperationResult::kEndOfData);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_ADF_JAMMED),
            crosapi::mojom::ScannerOperationResult::kAdfJammed);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_ADF_EMPTY),
            crosapi::mojom::ScannerOperationResult::kAdfEmpty);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_COVER_OPEN),
            crosapi::mojom::ScannerOperationResult::kCoverOpen);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_IO_ERROR),
            crosapi::mojom::ScannerOperationResult::kIoError);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_ACCESS_DENIED),
            crosapi::mojom::ScannerOperationResult::kAccessDenied);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_NO_MEMORY),
            crosapi::mojom::ScannerOperationResult::kNoMemory);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_UNREACHABLE),
            crosapi::mojom::ScannerOperationResult::kDeviceUnreachable);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_MISSING),
            crosapi::mojom::ScannerOperationResult::kDeviceMissing);
  EXPECT_EQ(ConvertTo<crosapi::mojom::ScannerOperationResult>(
                lorgnette::OPERATION_RESULT_INTERNAL_ERROR),
            crosapi::mojom::ScannerOperationResult::kInternalError);
}

TEST(DocumentScanAshTypeConvertersTest, OptionType) {
  EXPECT_EQ(ConvertForTesting(lorgnette::TYPE_UNKNOWN),
            crosapi::mojom::OptionType::kUnknown);
  EXPECT_EQ(ConvertForTesting(lorgnette::TYPE_BOOL),
            crosapi::mojom::OptionType::kBool);
  EXPECT_EQ(ConvertForTesting(lorgnette::TYPE_INT),
            crosapi::mojom::OptionType::kInt);
  EXPECT_EQ(ConvertForTesting(lorgnette::TYPE_FIXED),
            crosapi::mojom::OptionType::kFixed);
  EXPECT_EQ(ConvertForTesting(lorgnette::TYPE_STRING),
            crosapi::mojom::OptionType::kString);
  EXPECT_EQ(ConvertForTesting(lorgnette::TYPE_BUTTON),
            crosapi::mojom::OptionType::kButton);
  EXPECT_EQ(ConvertForTesting(lorgnette::TYPE_GROUP),
            crosapi::mojom::OptionType::kGroup);
}

TEST(DocumentScanAshTypeConvertersTest, OptionUnit) {
  EXPECT_EQ(ConvertForTesting(lorgnette::UNIT_NONE),
            crosapi::mojom::OptionUnit::kUnitless);
  EXPECT_EQ(ConvertForTesting(lorgnette::UNIT_PIXEL),
            crosapi::mojom::OptionUnit::kPixel);
  EXPECT_EQ(ConvertForTesting(lorgnette::UNIT_BIT),
            crosapi::mojom::OptionUnit::kBit);
  EXPECT_EQ(ConvertForTesting(lorgnette::UNIT_MM),
            crosapi::mojom::OptionUnit::kMm);
  EXPECT_EQ(ConvertForTesting(lorgnette::UNIT_DPI),
            crosapi::mojom::OptionUnit::kDpi);
  EXPECT_EQ(ConvertForTesting(lorgnette::UNIT_PERCENT),
            crosapi::mojom::OptionUnit::kPercent);
  EXPECT_EQ(ConvertForTesting(lorgnette::UNIT_MICROSECOND),
            crosapi::mojom::OptionUnit::kMicrosecond);
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraintType) {
  EXPECT_EQ(ConvertForTesting(lorgnette::OptionConstraint::CONSTRAINT_NONE),
            crosapi::mojom::OptionConstraintType::kNone);
  EXPECT_EQ(
      ConvertForTesting(lorgnette::OptionConstraint::CONSTRAINT_INT_RANGE),
      crosapi::mojom::OptionConstraintType::kIntRange);
  EXPECT_EQ(
      ConvertForTesting(lorgnette::OptionConstraint::CONSTRAINT_FIXED_RANGE),
      crosapi::mojom::OptionConstraintType::kFixedRange);
  EXPECT_EQ(ConvertForTesting(lorgnette::OptionConstraint::CONSTRAINT_INT_LIST),
            crosapi::mojom::OptionConstraintType::kIntList);
  EXPECT_EQ(
      ConvertForTesting(lorgnette::OptionConstraint::CONSTRAINT_FIXED_LIST),
      crosapi::mojom::OptionConstraintType::kFixedList);
  EXPECT_EQ(
      ConvertForTesting(lorgnette::OptionConstraint::CONSTRAINT_STRING_LIST),
      crosapi::mojom::OptionConstraintType::kStringList);
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraint_TypeNone) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(lorgnette::OptionConstraint::CONSTRAINT_NONE);
  input.mutable_int_range()->set_min(42);
  input.mutable_fixed_range()->set_min(42);
  input.add_valid_int(1);
  input.add_valid_fixed(2.0);
  input.add_valid_string("string");
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kNone);
  EXPECT_TRUE(output->restriction.is_null());
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraint_IntRangeWithoutRange) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(lorgnette::OptionConstraint::CONSTRAINT_INT_RANGE);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kIntRange);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_int_range());
  EXPECT_EQ(output->restriction->get_int_range()->min, 0);
  EXPECT_EQ(output->restriction->get_int_range()->max, 0);
  EXPECT_EQ(output->restriction->get_int_range()->quant, 0);
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraint_IntRangeWithRange) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(lorgnette::OptionConstraint::CONSTRAINT_INT_RANGE);
  input.mutable_int_range()->set_min(1);
  input.mutable_int_range()->set_max(10);
  input.mutable_int_range()->set_quant(2);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kIntRange);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_int_range());
  EXPECT_EQ(output->restriction->get_int_range()->min, 1);
  EXPECT_EQ(output->restriction->get_int_range()->max, 10);
  EXPECT_EQ(output->restriction->get_int_range()->quant, 2);
}

TEST(DocumentScanAshTypeConvertersTest,
     OptionConstraint_FixedRangeWithoutRange) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(
      lorgnette::OptionConstraint::CONSTRAINT_FIXED_RANGE);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kFixedRange);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_fixed_range());
  EXPECT_EQ(output->restriction->get_fixed_range()->min, 0.0);
  EXPECT_EQ(output->restriction->get_fixed_range()->max, 0.0);
  EXPECT_EQ(output->restriction->get_fixed_range()->quant, 0.0);
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraint_FixedRangeWithRange) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(
      lorgnette::OptionConstraint::CONSTRAINT_FIXED_RANGE);
  input.mutable_fixed_range()->set_min(1.0);
  input.mutable_fixed_range()->set_max(10.0);
  input.mutable_fixed_range()->set_quant(0.5);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kFixedRange);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_fixed_range());
  EXPECT_EQ(output->restriction->get_fixed_range()->min, 1.0);
  EXPECT_EQ(output->restriction->get_fixed_range()->max, 10.0);
  EXPECT_EQ(output->restriction->get_fixed_range()->quant, 0.5);
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraint_IntListWithoutList) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(lorgnette::OptionConstraint::CONSTRAINT_INT_LIST);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kIntList);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_valid_int());
  EXPECT_TRUE(output->restriction->get_valid_int().empty());
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraint_IntListWithList) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(lorgnette::OptionConstraint::CONSTRAINT_INT_LIST);
  input.add_valid_int(42);
  input.add_valid_int(43);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kIntList);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_valid_int());
  EXPECT_THAT(output->restriction->get_valid_int(), ElementsAre(42, 43));
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraint_FixedListWithoutList) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(lorgnette::OptionConstraint::CONSTRAINT_FIXED_LIST);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kFixedList);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_valid_fixed());
  EXPECT_TRUE(output->restriction->get_valid_fixed().empty());
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraint_FixedListWithList) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(lorgnette::OptionConstraint::CONSTRAINT_FIXED_LIST);
  input.add_valid_fixed(0.25);
  input.add_valid_fixed(0.5);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kFixedList);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_valid_fixed());
  EXPECT_THAT(output->restriction->get_valid_fixed(), ElementsAre(0.25, 0.5));
}

TEST(DocumentScanAshTypeConvertersTest,
     OptionConstraint_StringListWithoutList) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(
      lorgnette::OptionConstraint::CONSTRAINT_STRING_LIST);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kStringList);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_valid_string());
  EXPECT_TRUE(output->restriction->get_valid_string().empty());
}

TEST(DocumentScanAshTypeConvertersTest, OptionConstraint_StringListWithList) {
  lorgnette::OptionConstraint input;
  input.set_constraint_type(
      lorgnette::OptionConstraint::CONSTRAINT_STRING_LIST);
  input.add_valid_string("1");
  input.add_valid_string("2");
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionConstraintType::kStringList);
  ASSERT_FALSE(output->restriction.is_null());
  ASSERT_TRUE(output->restriction->is_valid_string());
  EXPECT_THAT(output->restriction->get_valid_string(), ElementsAre("1", "2"));
}

// Test all the fields that don't require complicated logic so they can be
// omitted from the subsequent tests.
TEST(DocumentScanAshTypeConvertersTest, ScannerOption_BasicFields) {
  lorgnette::ScannerOption input;
  input.set_name("option-name");
  input.set_title("Option title");
  input.set_description("Option Description\nLine 2");
  input.set_option_type(lorgnette::OptionType::TYPE_BUTTON);
  input.set_unit(lorgnette::OptionUnit::UNIT_NONE);
  input.set_detectable(true);
  input.set_auto_settable(true);
  input.set_emulated(true);
  input.set_active(true);
  input.set_advanced(true);

  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->name, "option-name");
  EXPECT_EQ(output->title, "Option title");
  EXPECT_EQ(output->description, "Option Description\nLine 2");
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kButton);
  EXPECT_EQ(output->unit, crosapi::mojom::OptionUnit::kUnitless);
  EXPECT_TRUE(output->value.is_null());
  EXPECT_TRUE(output->constraint.is_null());
  EXPECT_TRUE(output->isDetectable);
  EXPECT_EQ(output->configurability,
            crosapi::mojom::OptionConfigurability::kNotConfigurable);
  EXPECT_TRUE(output->isAutoSettable);
  EXPECT_TRUE(output->isEmulated);
  EXPECT_TRUE(output->isActive);
  EXPECT_TRUE(output->isAdvanced);
  EXPECT_FALSE(output->isInternal);
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_ConfigurabilitySoftware) {
  lorgnette::ScannerOption input;
  input.set_sw_settable(true);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->configurability,
            crosapi::mojom::OptionConfigurability::kSoftwareConfigurable);
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_ConfigurabilityHardware) {
  lorgnette::ScannerOption input;
  input.set_hw_settable(true);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->configurability,
            crosapi::mojom::OptionConfigurability::kHardwareConfigurable);
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_BoolWithoutBoolValue) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_BOOL);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kBool);
  EXPECT_TRUE(output->value.is_null());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_BoolWithBoolValue) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_BOOL);
  input.set_bool_value(true);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kBool);
  ASSERT_FALSE(output->value.is_null());
  ASSERT_TRUE(output->value->is_bool_value());
  EXPECT_EQ(output->value->get_bool_value(), true);
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_IntWithoutIntValue) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_INT);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kInt);
  EXPECT_TRUE(output->value.is_null());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_IntWithOneIntValue) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_INT);
  input.mutable_int_value()->add_value(1);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kInt);
  ASSERT_FALSE(output->value.is_null());
  ASSERT_TRUE(output->value->is_int_value());
  EXPECT_EQ(output->value->get_int_value(), 1);
}

TEST(DocumentScanAshTypeConvertersTest,
     ScannerOption_IntWithMultipleIntValues) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_INT);
  input.mutable_int_value()->add_value(5);
  input.mutable_int_value()->add_value(4);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kInt);
  ASSERT_FALSE(output->value.is_null());
  ASSERT_TRUE(output->value->is_int_list());
  EXPECT_THAT(output->value->get_int_list(), ElementsAre(5, 4));
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_FixedWithoutFixedValue) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_FIXED);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kFixed);
  EXPECT_TRUE(output->value.is_null());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_FixedWithOneFixedValue) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_FIXED);
  input.mutable_fixed_value()->add_value(3.25);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kFixed);
  ASSERT_FALSE(output->value.is_null());
  ASSERT_TRUE(output->value->is_fixed_value());
  EXPECT_EQ(output->value->get_fixed_value(), 3.25);
}

TEST(DocumentScanAshTypeConvertersTest,
     ScannerOption_FixedWithMultipleFixedValues) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_FIXED);
  input.mutable_fixed_value()->add_value(5.0);
  input.mutable_fixed_value()->add_value(4.5);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kFixed);
  ASSERT_FALSE(output->value.is_null());
  ASSERT_TRUE(output->value->is_fixed_list());
  EXPECT_THAT(output->value->get_fixed_list(), ElementsAre(5.0, 4.5));
}

TEST(DocumentScanAshTypeConvertersTest,
     ScannerOption_StringWithoutStringValue) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_STRING);
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kString);
  EXPECT_TRUE(output->value.is_null());
}

TEST(DocumentScanAshTypeConvertersTest,
     ScannerOption_StringWithEmptyStringValue) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_STRING);
  input.set_string_value("");
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kString);
  ASSERT_FALSE(output->value.is_null());
  ASSERT_TRUE(output->value->is_string_value());
  EXPECT_EQ(output->value->get_string_value(), "");
}

TEST(DocumentScanAshTypeConvertersTest,
     ScannerOption_StringWithNonEmptyStringValue) {
  lorgnette::ScannerOption input;
  input.set_option_type(lorgnette::TYPE_STRING);
  input.set_string_value("string");
  auto output = ConvertForTesting(input);
  EXPECT_EQ(output->type, crosapi::mojom::OptionType::kString);
  ASSERT_FALSE(output->value.is_null());
  ASSERT_TRUE(output->value->is_string_value());
  EXPECT_EQ(output->value->get_string_value(), "string");
}

// Verifies that a constraint is copied in.  Details of the OptionConstraint
// conversion are covered in specific tests above.  The case of a missing
// constraint is covered in the BasicFields test.
TEST(DocumentScanAshTypeConvertersTest, ScannerOption_NonEmptyConstraint) {
  lorgnette::ScannerOption input;
  input.mutable_constraint()->set_constraint_type(
      lorgnette::OptionConstraint::CONSTRAINT_STRING_LIST);
  input.mutable_constraint()->add_valid_string("string");
  auto output = ConvertForTesting(input);
  ASSERT_FALSE(output->constraint.is_null());
  EXPECT_EQ(output->constraint->type,
            crosapi::mojom::OptionConstraintType::kStringList);
  ASSERT_FALSE(output->constraint->restriction.is_null());
  ASSERT_TRUE(output->constraint->restriction->is_valid_string());
  EXPECT_THAT(output->constraint->restriction->get_valid_string(),
              ElementsAre("string"));
}

TEST(DocumentScanAshTypeConvertersTest,
     GetScannerListResponse_EmptyObjectSucceeds) {
  lorgnette::ListScannersResponse input;
  auto output = crosapi::mojom::GetScannerListResponse::From(input);
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kUnknown);
  EXPECT_EQ(output->scanners.size(), 0U);
}

TEST(DocumentScanAshTypeConvertersTest, GetScannerListResponse_UsbScanner) {
  lorgnette::ListScannersResponse input;
  input.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
  lorgnette::ScannerInfo* scanner = input.add_scanners();
  scanner->set_name("backend:usb:18d1:505e");
  scanner->set_manufacturer("GoogleTest");
  scanner->set_model("USB Scanner");
  scanner->set_display_name("GoogleTest USB Scanner (USB)");
  scanner->set_device_uuid("13245-67890");
  scanner->set_connection_type(lorgnette::CONNECTION_USB);
  scanner->set_secure(true);
  scanner->add_image_format("image/png");
  scanner->add_image_format("image/jpeg");
  scanner->set_protocol_type("backend");

  auto output = crosapi::mojom::GetScannerListResponse::From(input);
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kSuccess);
  ASSERT_EQ(output->scanners.size(), 1U);
  crosapi::mojom::ScannerInfoPtr& scanner_out = output->scanners[0];
  EXPECT_EQ(scanner_out->id, "backend:usb:18d1:505e");
  EXPECT_EQ(scanner_out->manufacturer, "GoogleTest");
  EXPECT_EQ(scanner_out->model, "USB Scanner");
  EXPECT_EQ(scanner_out->display_name, "GoogleTest USB Scanner (USB)");
  EXPECT_EQ(scanner_out->device_uuid, "13245-67890");
  EXPECT_EQ(scanner_out->connection_type,
            crosapi::mojom::ScannerInfo_ConnectionType::kUsb);
  EXPECT_TRUE(scanner_out->secure);
  EXPECT_THAT(scanner_out->image_formats,
              UnorderedElementsAre("image/png", "image/jpeg"));
  EXPECT_EQ(scanner_out->protocol_type, "backend");
}

TEST(DocumentScanAshTypeConvertersTest, GetScannerListResponse_NetworkScanner) {
  lorgnette::ListScannersResponse input;
  input.set_result(lorgnette::OPERATION_RESULT_NO_MEMORY);
  lorgnette::ScannerInfo* scanner = input.add_scanners();
  scanner->set_name("backend:net:127.0.0.1");
  scanner->set_manufacturer("GoogleTest");
  scanner->set_model("Network Scanner");
  scanner->set_display_name("GoogleTest Network Scanner");
  scanner->set_device_uuid("13245-67890");
  scanner->set_connection_type(lorgnette::CONNECTION_NETWORK);
  scanner->set_secure(false);
  scanner->add_image_format("image/png");
  scanner->add_image_format("image/jpeg");
  scanner->set_protocol_type("backend");

  auto output = crosapi::mojom::GetScannerListResponse::From(input);
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kNoMemory);
  ASSERT_EQ(output->scanners.size(), 1U);
  crosapi::mojom::ScannerInfoPtr& scanner_out = output->scanners[0];
  EXPECT_EQ(scanner_out->id, "backend:net:127.0.0.1");
  EXPECT_EQ(scanner_out->manufacturer, "GoogleTest");
  EXPECT_EQ(scanner_out->model, "Network Scanner");
  EXPECT_EQ(scanner_out->display_name, "GoogleTest Network Scanner");
  EXPECT_EQ(scanner_out->device_uuid, "13245-67890");
  EXPECT_EQ(scanner_out->connection_type,
            crosapi::mojom::ScannerInfo_ConnectionType::kNetwork);
  EXPECT_FALSE(scanner_out->secure);
  EXPECT_THAT(scanner_out->image_formats,
              UnorderedElementsAre("image/png", "image/jpeg"));
  EXPECT_EQ(scanner_out->protocol_type, "backend");
}

TEST(DocumentScanAshTypeConvertersTest,
     OpenScannerResponse_EmptyObjectSucceeds) {
  lorgnette::OpenScannerResponse input;
  auto output = crosapi::mojom::OpenScannerResponse::From(input);
  EXPECT_TRUE(output->scanner_id.empty());
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kUnknown);
  EXPECT_FALSE(output->scanner_handle.has_value());
  EXPECT_FALSE(output->options.has_value());
}

// Test that all provided options are copied into the mojom result.  Ensuring
// that option details are converted correctly is covered by specific
// ScannerOption tests above.
TEST(DocumentScanAshTypeConvertersTest,
     OpenScannerResponse_AllOptionsIncluded) {
  lorgnette::OpenScannerResponse input;
  input.mutable_scanner_id()->set_connection_string("scanner:id");
  input.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
  input.mutable_config()->mutable_scanner()->set_token("12345");
  (*input.mutable_config()->mutable_options())["option1-name"] = {};
  (*input.mutable_config()->mutable_options())["option2-name"] = {};
  auto output = crosapi::mojom::OpenScannerResponse::From(input);
  EXPECT_EQ(output->scanner_id, "scanner:id");
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kSuccess);
  ASSERT_TRUE(output->scanner_handle.has_value());
  EXPECT_EQ(output->scanner_handle.value(), "12345");
  ASSERT_TRUE(output->options.has_value());
  EXPECT_TRUE(base::Contains(output->options.value(), "option1-name"));
  EXPECT_TRUE(base::Contains(output->options.value(), "option2-name"));
}

TEST(DocumentScanAshTypeConvertersTest,
     CloseScannerResponse_EmptyObjectSucceeds) {
  lorgnette::CloseScannerResponse input;
  auto output = crosapi::mojom::CloseScannerResponse::From(input);
  EXPECT_TRUE(output->scanner_handle.empty());
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kUnknown);
}

TEST(DocumentScanAshTypeConvertersTest, CloseScannerResponse_NonEmptyResponse) {
  lorgnette::CloseScannerResponse input;
  input.mutable_scanner()->set_token("55555");
  input.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
  auto output = crosapi::mojom::CloseScannerResponse::From(input);
  EXPECT_EQ(output->scanner_handle, "55555");
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kSuccess);
}

TEST(DocumentScanAshTypeConvertersTest, StartPreparedScanResponse_EmptyObject) {
  lorgnette::StartPreparedScanResponse input;
  auto output = crosapi::mojom::StartPreparedScanResponse::From(input);
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kUnknown);
  EXPECT_TRUE(output->scanner_handle.empty());
  EXPECT_FALSE(output->job_handle.has_value());
}

TEST(DocumentScanAshTypeConvertersTest, StartPreparedScanResponse_Success) {
  lorgnette::StartPreparedScanResponse input;
  input.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
  input.mutable_scanner()->set_token("scanner-handle");
  input.mutable_job_handle()->set_token("job-handle");

  auto output = crosapi::mojom::StartPreparedScanResponse::From(input);
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kSuccess);
  EXPECT_EQ(output->scanner_handle, "scanner-handle");
  ASSERT_TRUE(output->job_handle.has_value());
  EXPECT_EQ(output->job_handle.value(), "job-handle");
}

TEST(DocumentScanAshTypeConvertersTest, ReadScanDataResponse_EmptyObject) {
  lorgnette::ReadScanDataResponse input;
  auto output = crosapi::mojom::ReadScanDataResponse::From(input);
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kUnknown);
  EXPECT_TRUE(output->job_handle.empty());
  EXPECT_FALSE(output->data.has_value());
}

TEST(DocumentScanAshTypeConvertersTest, ReadScanDataResponse_Success) {
  lorgnette::ReadScanDataResponse input;
  input.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
  input.mutable_job_handle()->set_token("job-handle");
  input.set_data("data");
  input.set_estimated_completion(23);

  auto output = crosapi::mojom::ReadScanDataResponse::From(input);
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kSuccess);
  EXPECT_EQ(output->job_handle, "job-handle");
  ASSERT_TRUE(output->data.has_value());
  EXPECT_THAT(output->data.value(), ElementsAre('d', 'a', 't', 'a'));
  EXPECT_EQ(output->estimated_completion, 23U);
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_EmptyObject) {
  auto input = crosapi::mojom::OptionSetting::New();
  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_TRUE(output->name().empty());
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_UNKNOWN);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_SuccessBool) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kBool;
  input->value = crosapi::mojom::OptionValue::NewBoolValue(true);

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_BOOL);
  EXPECT_TRUE(output->has_bool_value());
  EXPECT_TRUE(output->bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_SuccessInt) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kInt;
  input->value = crosapi::mojom::OptionValue::NewIntValue(10);

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_INT);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_TRUE(output->has_int_value());
  EXPECT_THAT(output->int_value().value(), ElementsAre(10));
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_SuccessIntList) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kInt;
  input->value = crosapi::mojom::OptionValue::NewIntList({10, 20});

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_INT);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_TRUE(output->has_int_value());
  EXPECT_THAT(output->int_value().value(), ElementsAre(10, 20));
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_SuccessFixed) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kFixed;
  input->value = crosapi::mojom::OptionValue::NewFixedValue(10.5);

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_FIXED);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_TRUE(output->has_fixed_value());
  EXPECT_THAT(output->fixed_value().value(), ElementsAre(10.5));
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_SuccessFixedList) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kFixed;
  input->value = crosapi::mojom::OptionValue::NewFixedList({10.4, 20.6});

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_FIXED);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_TRUE(output->has_fixed_value());
  EXPECT_THAT(output->fixed_value().value(), ElementsAre(10.4, 20.6));
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_SuccessString) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kString;
  input->value = crosapi::mojom::OptionValue::NewStringValue("option-value");

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_STRING);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_TRUE(output->has_string_value());
  EXPECT_EQ(output->string_value(), "option-value");
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_BoolNoValue) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kBool;
  // Don't set a value for this option.

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_BOOL);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_BoolWrongValue) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kBool;
  // Set an incorrect value for this option.
  input->value = crosapi::mojom::OptionValue::NewStringValue("bad-value");

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  EXPECT_FALSE(output.has_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_IntNoValue) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kInt;
  // Don't set a value for this option.

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_INT);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_IntWrongValue) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kInt;
  // Set an incorrect value for this option.
  input->value = crosapi::mojom::OptionValue::NewStringValue("bad-value");

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  EXPECT_FALSE(output.has_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_FixedNoValue) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kFixed;
  // Don't set a value for this option.

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_FIXED);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_FixedWrongValue) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kFixed;
  // Set an incorrect value for this option.
  input->value = crosapi::mojom::OptionValue::NewStringValue("bad-value");

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  EXPECT_FALSE(output.has_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_StringNoValue) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kString;
  // Don't set a value for this option.

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_STRING);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_StringWrongValue) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kString;
  // Set an incorrect value for this option.
  input->value = crosapi::mojom::OptionValue::NewBoolValue(false);

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  EXPECT_FALSE(output.has_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_SuccessUnknown) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kUnknown;
  // Even if a value is set on the input, it won't be set on the output since
  // the type is unknown.
  input->value = crosapi::mojom::OptionValue::NewStringValue("not-used");

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_UNKNOWN);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_SuccessButton) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kButton;
  // Even if a value is set on the input, it won't be set on the output since
  // the type is button.
  input->value = crosapi::mojom::OptionValue::NewIntValue(10);

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_BUTTON);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, ScannerOption_SuccessGroup) {
  auto input = crosapi::mojom::OptionSetting::New();
  input->name = "option-name";
  input->type = crosapi::mojom::OptionType::kGroup;
  // Even if a value is set on the input, it won't be set on the output since
  // the type is group.
  input->value = crosapi::mojom::OptionValue::NewFixedList({10.4, 20.6});

  auto output = input.To<std::optional<lorgnette::ScannerOption>>();

  ASSERT_TRUE(output.has_value());
  EXPECT_EQ(output->name(), "option-name");
  EXPECT_EQ(output->option_type(), lorgnette::TYPE_GROUP);
  EXPECT_FALSE(output->has_bool_value());
  EXPECT_FALSE(output->has_int_value());
  EXPECT_FALSE(output->has_fixed_value());
  EXPECT_FALSE(output->has_string_value());
}

TEST(DocumentScanAshTypeConvertersTest, SetOptionsResponse_EmptyObject) {
  lorgnette::SetOptionsResponse input;
  auto output = crosapi::mojom::SetOptionsResponse::From(input);
  EXPECT_TRUE(output->scanner_handle.empty());
  EXPECT_TRUE(output->results.empty());
  ASSERT_TRUE(output->options.has_value());
  EXPECT_TRUE(output->options->empty());
}

TEST(DocumentScanAshTypeConvertersTest, SetOptionsResponse_Success) {
  lorgnette::SetOptionsResponse input;
  input.mutable_scanner()->set_token("scanner-handle");
  // For this input, we can just use a single status since other tests will test
  // this exhaustively.  This test just needs to make sure the status is passed
  // back.
  (*input.mutable_results())["result"] = lorgnette::OPERATION_RESULT_SUCCESS;
  // Likewise, we just need a single option with a single attribute populated.
  // This doesn't need to exhaustively test ScannerOption - there are other
  // tests that will do that.
  lorgnette::ScannerConfig config;
  lorgnette::ScannerOption option;
  option.set_name("option-name");
  (*config.mutable_options())["option-name"] = std::move(option);
  *input.mutable_config() = config;

  auto output = crosapi::mojom::SetOptionsResponse::From(input);
  EXPECT_EQ(output->scanner_handle, "scanner-handle");
  ASSERT_EQ(output->results.size(), 1U);
  EXPECT_EQ(output->results[0]->name, "result");
  EXPECT_EQ(output->results[0]->result,
            crosapi::mojom::ScannerOperationResult::kSuccess);
  ASSERT_TRUE(output->options.has_value());
  auto value = output->options.value().find("option-name");
  ASSERT_TRUE(value != output->options.value().end());
  EXPECT_EQ(value->second->name, "option-name");
}

TEST(DocumentScanAshTypeConvertersTest, GetOptionGroupsResponse_EmptyObject) {
  lorgnette::GetCurrentConfigResponse input;
  auto output = crosapi::mojom::GetOptionGroupsResponse::From(input);
  EXPECT_TRUE(output->scanner_handle.empty());
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kUnknown);
  EXPECT_FALSE(output->groups.has_value());
}

TEST(DocumentScanAshTypeConvertersTest, GetOptionGroupsResponse_Success) {
  lorgnette::GetCurrentConfigResponse input;
  input.mutable_scanner()->set_token("scanner-handle");
  input.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
  lorgnette::ScannerConfig* config = input.mutable_config();
  lorgnette::OptionGroup* group1 = config->add_option_groups();
  group1->set_title("group1");
  group1->add_members("group1-val1");
  group1->add_members("group1-val2");
  lorgnette::OptionGroup* group2 = config->add_option_groups();
  group2->set_title("group2");
  group2->add_members("group2-val1");

  auto output = crosapi::mojom::GetOptionGroupsResponse::From(input);
  EXPECT_EQ(output->scanner_handle, "scanner-handle");
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kSuccess);
  EXPECT_TRUE(output->groups.has_value());
  ASSERT_EQ(output->groups.value().size(), 2U);
  const auto& actual1 = output->groups.value()[0];
  EXPECT_EQ(actual1->title, "group1");
  EXPECT_THAT(actual1->members, ElementsAre("group1-val1", "group1-val2"));
  const auto& actual2 = output->groups.value()[1];
  EXPECT_EQ(actual2->title, "group2");
  EXPECT_THAT(actual2->members, ElementsAre("group2-val1"));
}

TEST(DocumentScanAshTypeConvertersTest, CancelScanResponse_EmptyObject) {
  lorgnette::CancelScanResponse input;
  auto output = crosapi::mojom::CancelScanResponse::From(input);
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kUnknown);
  EXPECT_TRUE(output->job_handle.empty());
}

TEST(DocumentScanAshTypeConvertersTest, CancelScanResponse_Success) {
  lorgnette::CancelScanResponse input;
  input.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
  input.mutable_job_handle()->set_token("job-handle");

  auto output = crosapi::mojom::CancelScanResponse::From(input);
  EXPECT_EQ(output->result, crosapi::mojom::ScannerOperationResult::kSuccess);
  EXPECT_EQ(output->job_handle, "job-handle");
}

}  // namespace
}  // namespace mojo