// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/scanning/lorgnette_scanner_manager.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/protobuf_matchers.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/scanning/lorgnette_scanner_manager_util.h"
#include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h"
#include "chrome/browser/ash/scanning/zeroconf_scanner_detector_utils.h"
#include "chrome/browser/local_discovery/service_discovery_client.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
#include "chromeos/ash/components/dbus/lorgnette/lorgnette_service.pb.h"
#include "chromeos/ash/components/dbus/lorgnette_manager/fake_lorgnette_manager_client.h"
#include "chromeos/ash/components/dbus/lorgnette_manager/lorgnette_manager_client.h"
#include "chromeos/ash/components/scanning/scanner.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "net/base/ip_address.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace {
using ::base::test::EqualsProto;
using local_discovery::ServiceDescription;
using ::testing::ElementsAreArray;
using ::testing::UnorderedElementsAreArray;
using LocalScannerFilter = LorgnetteScannerManager::LocalScannerFilter;
using SecureScannerFilter = LorgnetteScannerManager::SecureScannerFilter;
// Test device names for different types of lorgnette scanners.
constexpr char kLorgnetteNetworkIpDeviceName[] = "test:MX3100_192.168.0.3";
constexpr char kLorgnetteNetworkUrlDeviceName[] =
"http://testscanner.domain.org";
constexpr char kLorgnetteUsbDeviceName[] = "test:04A91752_94370B";
constexpr char kLorgnetteNetworkEpsonDeviceName[] = "epson2:net:192.168.0.3";
// A scanner name that does not correspond to a known scanner.
constexpr char kUnknownScannerName[] = "Unknown Scanner";
// Model which contains the manufacturer.
constexpr char kModelContainingManufacturer[] = "TEST Model X";
MATCHER_P(EquivalentToListScannersResponse,
info,
"Non-ephemeral fields match against the matcher's argument.") {
if (arg.result() != info.result()) {
*result_listener << "Expected result: "
<< OperationResult_Name(info.result())
<< ", actual result: "
<< OperationResult_Name(arg.result());
return false;
}
if (arg.scanners_size() != info.scanners_size()) {
*result_listener << "Expected number of scanners: " << info.scanners_size()
<< ", actual number of scanners: " << arg.scanners_size();
return false;
}
for (int i = 0; i < arg.scanners_size(); i++) {
std::string expected_serialized;
lorgnette::ScannerInfo clean_info = info.scanners(i);
clean_info.set_name("name");
clean_info.set_device_uuid("device_uuid");
if (!clean_info.SerializeToString(&expected_serialized)) {
*result_listener << "Expected ScannerInfo fails to serialize";
return false;
}
std::string actual_serialized;
lorgnette::ScannerInfo clean_arg = arg.scanners(i);
clean_arg.set_name("name");
clean_arg.set_device_uuid("device_uuid");
if (!clean_arg.SerializeToString(&actual_serialized)) {
*result_listener << "Actual ScannerInfo fails to serialize";
return false;
}
if (expected_serialized != actual_serialized) {
*result_listener << "Provided ScannerInfo at " << i
<< " did not match the expected ScannerInfo"
<< "\n Expected: " << expected_serialized
<< "\n Provided: " << actual_serialized;
return false;
}
}
return true;
}
lorgnette::ScannerInfo ScannerInfoFromScanner(const Scanner& scanner) {
lorgnette::ScannerInfo info;
info.set_manufacturer(scanner.manufacturer);
info.set_model(scanner.model);
info.set_display_name(scanner.display_name);
info.set_type("multi-function peripheral");
info.set_device_uuid(scanner.uuid);
*info.add_image_format() = "image/jpeg";
*info.add_image_format() = "image/png";
// Set the connection string to the first one available from |scanner|.
for (const auto& [protocol, names] : scanner.device_names) {
if (!names.empty()) {
info.set_name(names.begin()->device_name);
switch (protocol) {
case ScanProtocol::kEscls:
info.set_secure(true);
[[fallthrough]];
case ScanProtocol::kEscl:
case ScanProtocol::kLegacyNetwork:
info.set_connection_type(lorgnette::CONNECTION_NETWORK);
break;
case ScanProtocol::kLegacyUsb:
info.set_connection_type(lorgnette::CONNECTION_USB);
info.set_secure(true);
break;
case ScanProtocol::kUnknown:
// Set nothing.
break;
}
break;
}
}
info.set_protocol_type(ProtocolTypeForScanner(info));
return info;
}
// Returns a ScannerInfo object with the given |name| and |model|, if provided.
lorgnette::ScannerInfo CreateLorgnetteScanner(
std::string name,
const std::string& model = "MX3100") {
lorgnette::ScannerInfo scanner;
scanner.set_name(name);
scanner.set_manufacturer("Test");
scanner.set_model(model);
scanner.set_display_name("Flatbed Test " + model);
scanner.set_type("Flatbed");
return scanner;
}
// Returns a ListScannersResponse containing a single ScannerInfo object created
// with the given |name| and |model|, if provided.
lorgnette::ListScannersResponse CreateListScannersResponse(
std::string name,
const std::string& model = "MX3100") {
lorgnette::ScannerInfo scanner = CreateLorgnetteScanner(name, model);
lorgnette::ListScannersResponse response;
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
*response.add_scanners() = std::move(scanner);
return response;
}
// Returns a zeroconf Scanner with the device name marked as |usable|.
Scanner CreateZeroconfScanner(bool usable = true, const std::string uuid = "") {
return CreateSaneScanner("Test MX3100",
ZeroconfScannerDetector::kEsclsServiceType, "Test",
"MX3100", uuid, /*rs=*/"", /*pdl=*/{},
net::IPAddress(192, 168, 0, 3), 5, usable)
.value();
}
// Returns a zeroconf Scanner with the Epsonds backend and the device name
// marked as |usable|
Scanner CreateNonEsclEpsonZeroconfScanner(bool usable = true) {
return CreateSaneScanner("EPSON TEST",
ZeroconfScannerDetector::kGenericScannerServiceType,
"EPSON", "TEST", /*uuid=*/"", /*rs=*/"", /*pdl=*/{},
net::IPAddress(192, 168, 0, 3), 5, usable)
.value();
}
// Returns a zeroconf Scanner with an Epson name but ESCLs Service marked as
// |usable|.
Scanner CreateEsclEpsonZeroconfScanner(bool usable = true) {
return CreateSaneScanner("EPSON TEST",
ZeroconfScannerDetector::kEsclsServiceType, "EPSON",
"TEST", /*uuid=*/"", /*rs=*/"", /*pdl=*/{},
net::IPAddress(192, 168, 0, 3), 5, usable)
.value();
}
// Returns a zeroconf Scanner with |scanner_name| and device name marked
// |usable|.
Scanner CreateScannerCustomName(const std::string& scanner_name,
bool usable = true) {
return CreateSaneScanner(
scanner_name, ZeroconfScannerDetector::kEsclsServiceType,
"Manufacturer", "Model", /*uuid=*/"", /*rs=*/"",
/*pdl=*/{}, net::IPAddress(192, 168, 0, 3), 5, usable)
.value();
}
class FakeZeroconfScannerDetector final : public ZeroconfScannerDetector {
public:
FakeZeroconfScannerDetector() = default;
~FakeZeroconfScannerDetector() override = default;
void RegisterScannersDetectedCallback(
OnScannersDetectedCallback callback) override {
on_scanners_detected_callback_ = std::move(callback);
}
std::vector<Scanner> GetScanners() override {
std::vector<Scanner> scanners;
for (const auto& entry : scanners_)
scanners.push_back(entry.second);
return scanners;
}
void OnDeviceChanged(const std::string& service_type,
bool added,
const ServiceDescription& service_description) override {
}
void OnDeviceRemoved(const std::string& service_type,
const std::string& service_name) override {}
void OnDeviceCacheFlushed(const std::string& service_type) override {}
void OnPermissionRejected() override {}
// Used to trigger on_scanners_detected_callback_ after adding the given
// |scanners| to the detected scanners.
void AddDetections(const std::vector<Scanner>& scanners) {
for (const auto& scanner : scanners) {
scanners_[scanner.display_name] = scanner;
}
on_scanners_detected_callback_.Run(GetScanners());
}
// Used to trigger on_scanners_detected_callback_ after removing the given
// |scanners| from the detected scanners.
void RemoveDetections(const std::vector<Scanner>& scanners) {
for (const auto& scanner : scanners)
scanners_.erase(scanner.display_name);
on_scanners_detected_callback_.Run(GetScanners());
}
private:
base::flat_map<std::string, Scanner> scanners_;
OnScannersDetectedCallback on_scanners_detected_callback_;
};
} // namespace
class LorgnetteScannerManagerTest : public testing::Test {
public:
LorgnetteScannerManagerTest() = default;
~LorgnetteScannerManagerTest() override = default;
void SetUp() override {
// Set up a test account / user / profile
constexpr char kEmail[] = "test@test";
const AccountId account_id = AccountId::FromUserEmail(kEmail);
auto fake_user_manager = std::make_unique<ash::FakeChromeUserManager>();
profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
EXPECT_TRUE(profile_manager_->SetUp());
TestingProfile* testing_profile =
profile_manager_->CreateTestingProfile(kEmail,
/*is_main_profile=*/true);
fake_user_manager->AddUserWithAffiliationAndTypeAndProfile(
account_id, false, user_manager::UserType::kRegular, testing_profile);
fake_user_manager->LoginUser(account_id);
fake_user_manager->SwitchActiveUser(account_id);
user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
std::move(fake_user_manager));
run_loop_ = std::make_unique<base::RunLoop>();
DlcserviceClient::InitializeFake();
LorgnetteManagerClient::InitializeFake();
auto fake_zeroconf_scanner_detector =
std::make_unique<FakeZeroconfScannerDetector>();
fake_zeroconf_scanner_detector_ = fake_zeroconf_scanner_detector.get();
lorgnette_scanner_manager_ = LorgnetteScannerManager::Create(
std::move(fake_zeroconf_scanner_detector), testing_profile);
// Set empty but successful capabilities response by default.
lorgnette::ScannerCapabilities capabilities;
GetLorgnetteManagerClient()->SetScannerCapabilitiesResponse(capabilities);
}
void TearDown() override {
lorgnette_scanner_manager_.reset();
LorgnetteManagerClient::Shutdown();
DlcserviceClient::Shutdown();
run_loop_.reset();
user_manager_.reset();
profile_manager_.reset();
}
// Returns a FakeLorgnetteManagerClient with an empty but successful
// GetCapabilities response by default.
FakeLorgnetteManagerClient* GetLorgnetteManagerClient() {
return static_cast<FakeLorgnetteManagerClient*>(
LorgnetteManagerClient::Get());
}
// Calls LorgnetteScannerManager::GetScannerNames() and binds a callback to
// process the result.
void GetScannerNames() {
lorgnette_scanner_manager_->GetScannerNames(
base::BindOnce(&LorgnetteScannerManagerTest::GetScannerNamesCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::GetScannerInfoList() and binds a callback to
// process the result.
void GetScannerInfoList(LocalScannerFilter local_only,
SecureScannerFilter secure_only) {
lorgnette_scanner_manager_->GetScannerInfoList(
"client-id", local_only, secure_only,
base::BindOnce(&LorgnetteScannerManagerTest::GetScannerInfoListCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::GetScannerCapabilities() and binds a
// callback to process the result.
void GetScannerCapabilities(const std::string& scanner_name) {
lorgnette_scanner_manager_->GetScannerCapabilities(
scanner_name,
base::BindOnce(
&LorgnetteScannerManagerTest::GetScannerCapabilitiesCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::OpenScanner() and binds a callback to
// process the result.
void OpenScanner(const lorgnette::OpenScannerRequest& request) {
lorgnette_scanner_manager_->OpenScanner(
request,
base::BindOnce(&LorgnetteScannerManagerTest::OpenScannerCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::CloseScanner() and binds a callback to
// process the result.
void CloseScanner() {
lorgnette_scanner_manager_->CloseScanner(
lorgnette::CloseScannerRequest(),
base::BindOnce(&LorgnetteScannerManagerTest::CloseScannerCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::SetOptions() and binds a callback to
// process the result.
void SetOptions() {
lorgnette_scanner_manager_->SetOptions(
lorgnette::SetOptionsRequest(),
base::BindOnce(&LorgnetteScannerManagerTest::SetOptionsCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::GetCurrentConfig() and binds a callback to
// process the result.
void GetCurrentConfig() {
lorgnette_scanner_manager_->GetCurrentConfig(
lorgnette::GetCurrentConfigRequest(),
base::BindOnce(&LorgnetteScannerManagerTest::GetCurrentConfigCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::StartPreparedScan() and binds a callback to
// process the result.
void StartPreparedScan() {
lorgnette_scanner_manager_->StartPreparedScan(
lorgnette::StartPreparedScanRequest(),
base::BindOnce(&LorgnetteScannerManagerTest::StartPreparedScanCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::ReadScanData() and binds a callback to
// process the result.
void ReadScanData() {
lorgnette_scanner_manager_->ReadScanData(
lorgnette::ReadScanDataRequest(),
base::BindOnce(&LorgnetteScannerManagerTest::ReadScanDataCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::IsRotateAlternate() and returns result.
bool GetRotateAlternate(const std::string& scanner_name,
const std::string& source_name) {
return lorgnette_scanner_manager_->IsRotateAlternate(scanner_name,
source_name);
}
// Calls LorgnetteScannerManager::Scan() and binds a callback to process the
// result.
void Scan(const std::string& scanner_name,
const lorgnette::ScanSettings& settings) {
lorgnette_scanner_manager_->Scan(
scanner_name, settings, base::NullCallback(),
base::BindRepeating(&LorgnetteScannerManagerTest::PageCallback,
base::Unretained(this)),
base::BindOnce(&LorgnetteScannerManagerTest::ScanCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::CancelScan() and binds a callback to process
// the result.
void CancelScan() {
lorgnette_scanner_manager_->CancelScan(
base::BindOnce(&LorgnetteScannerManagerTest::CancelScanCallback,
base::Unretained(this)));
}
// Calls LorgnetteScannerManager::CancelScan() with a CancelScanRequest and
// binds a callback to process the result.
void CancelScanJob() {
lorgnette_scanner_manager_->CancelScan(
lorgnette::CancelScanRequest(),
base::BindOnce(&LorgnetteScannerManagerTest::CancelScanJobCallback,
base::Unretained(this)));
}
// Runs all tasks until the ThreadPool's non-delayed queues are empty.
void CompleteTasks() { task_environment_.RunUntilIdle(); }
// Runs run_loop_ until a callback calls Quit().
void WaitForResult() {
run_loop_->Run();
run_loop_ = std::make_unique<base::RunLoop>();
}
FakeZeroconfScannerDetector* fake_zeroconf_scanner_detector() {
return fake_zeroconf_scanner_detector_;
}
const std::vector<std::string>& scanner_names() const {
return scanner_names_;
}
std::optional<lorgnette::ListScannersResponse> list_scanners_response()
const {
return list_scanners_response_;
}
std::optional<lorgnette::ScannerCapabilities> scanner_capabilities() const {
return scanner_capabilities_;
}
std::optional<lorgnette::OpenScannerResponse> open_scanner_response() const {
return open_scanner_response_;
}
std::optional<lorgnette::CloseScannerResponse> close_scanner_response()
const {
return close_scanner_response_;
}
std::optional<lorgnette::SetOptionsResponse> set_options_response() const {
return set_options_response_;
}
std::optional<lorgnette::GetCurrentConfigResponse>
get_current_config_response() const {
return get_current_config_response_;
}
std::optional<lorgnette::StartPreparedScanResponse>
start_prepared_scan_response() const {
return start_prepared_scan_response_;
}
std::optional<lorgnette::ReadScanDataResponse> read_scan_data_response()
const {
return read_scan_data_response_;
}
std::vector<std::string> scan_data() const { return scan_data_; }
lorgnette::ScanFailureMode failure_mode() const { return failure_mode_; }
bool cancel_scan_success() const { return cancel_scan_success_; }
std::optional<lorgnette::CancelScanResponse> cancel_scan_response() const {
return cancel_scan_response_;
}
private:
// Handles the result of calling LorgnetteScannerManager::GetScannerNames().
void GetScannerNamesCallback(std::vector<std::string> scanner_names) {
scanner_names_ = scanner_names;
run_loop_->Quit();
}
// Handles the result of calling
// LorgnetteScannerManager::GetScannerInfoList().
void GetScannerInfoListCallback(
const std::optional<lorgnette::ListScannersResponse>& response) {
list_scanners_response_ = response;
run_loop_->Quit();
}
void GetScannerCapabilitiesCallback(
const std::optional<lorgnette::ScannerCapabilities>&
scanner_capabilities) {
scanner_capabilities_ = scanner_capabilities;
run_loop_->Quit();
}
void OpenScannerCallback(
const std::optional<lorgnette::OpenScannerResponse>& response) {
open_scanner_response_ = response;
run_loop_->Quit();
}
void CloseScannerCallback(
const std::optional<lorgnette::CloseScannerResponse>& response) {
close_scanner_response_ = response;
run_loop_->Quit();
}
void SetOptionsCallback(
const std::optional<lorgnette::SetOptionsResponse>& response) {
set_options_response_ = response;
run_loop_->Quit();
}
void GetCurrentConfigCallback(
const std::optional<lorgnette::GetCurrentConfigResponse>& response) {
get_current_config_response_ = response;
run_loop_->Quit();
}
void StartPreparedScanCallback(
const std::optional<lorgnette::StartPreparedScanResponse>& response) {
start_prepared_scan_response_ = response;
run_loop_->Quit();
}
void ReadScanDataCallback(
const std::optional<lorgnette::ReadScanDataResponse>& response) {
read_scan_data_response_ = response;
run_loop_->Quit();
}
// Handles receiving a page from LorgnetteScannerManager::Scan().
void PageCallback(std::string page_data, uint32_t /*page_number*/) {
scan_data_.push_back(page_data);
}
// Handles completion of LorgnetteScannerManager::Scan().
void ScanCallback(lorgnette::ScanFailureMode failure_mode) {
failure_mode_ = failure_mode;
run_loop_->Quit();
}
// Handles completion of LorgnetteScannerManager::CancelScan().
void CancelScanCallback(bool success) {
cancel_scan_success_ = success;
run_loop_->Quit();
}
// Handles completion of LorgnetteScannerManager::CancelScan() when called
// with a CancelScanRequest.
void CancelScanJobCallback(
const std::optional<lorgnette::CancelScanResponse>& response) {
cancel_scan_response_ = response;
run_loop_->Quit();
}
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<user_manager::ScopedUserManager> user_manager_;
std::unique_ptr<TestingProfileManager> profile_manager_;
std::unique_ptr<base::RunLoop> run_loop_;
raw_ptr<FakeZeroconfScannerDetector, DanglingUntriaged>
fake_zeroconf_scanner_detector_;
std::unique_ptr<LorgnetteScannerManager> lorgnette_scanner_manager_;
std::vector<std::string> scanner_names_;
std::optional<lorgnette::ListScannersResponse> list_scanners_response_;
std::optional<lorgnette::ScannerCapabilities> scanner_capabilities_;
std::optional<lorgnette::OpenScannerResponse> open_scanner_response_;
std::optional<lorgnette::CloseScannerResponse> close_scanner_response_;
std::optional<lorgnette::SetOptionsResponse> set_options_response_;
std::optional<lorgnette::GetCurrentConfigResponse>
get_current_config_response_;
std::optional<lorgnette::StartPreparedScanResponse>
start_prepared_scan_response_;
std::optional<lorgnette::ReadScanDataResponse> read_scan_data_response_;
lorgnette::ScanFailureMode failure_mode_ =
lorgnette::SCAN_FAILURE_MODE_NO_FAILURE;
bool cancel_scan_success_ = false;
std::optional<lorgnette::CancelScanResponse> cancel_scan_response_;
std::vector<std::string> scan_data_;
};
// Test that no scanner names are returned when no scanners have been detected.
TEST_F(LorgnetteScannerManagerTest, NoScanners) {
GetScannerNames();
WaitForResult();
EXPECT_TRUE(scanner_names().empty());
}
// Test that no scanner names are returned when scanners are detected, but none
// return capabilities.
TEST_F(LorgnetteScannerManagerTest, NoScannersWithNoCap) {
GetLorgnetteManagerClient()->SetListScannersResponse(
CreateListScannersResponse(kLorgnetteNetworkIpDeviceName));
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
auto epson_scanner = CreateNonEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({epson_scanner});
GetLorgnetteManagerClient()->SetScannerCapabilitiesResponse(std::nullopt);
GetScannerNames();
WaitForResult();
EXPECT_TRUE(scanner_names().empty());
}
// Test that the name of a detected zeroconf scanner can be retrieved.
TEST_F(LorgnetteScannerManagerTest, ZeroconfScanner) {
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_THAT(scanner_names(), ElementsAreArray({scanner.display_name}));
}
// Test that the name of a detected Epson zeroconf scanner can be retrieved.
TEST_F(LorgnetteScannerManagerTest, NonEsclEpsonZeroconfScanner) {
auto scanner = CreateNonEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_THAT(scanner_names(), ElementsAreArray({scanner.display_name}));
}
// Test that the name of a detected ESCL Epson zeroconf scanner can be
// retrieved.
TEST_F(LorgnetteScannerManagerTest, EsclEpsonZeroconfScanner) {
auto scanner = CreateEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_THAT(scanner_names(), ElementsAreArray({scanner.display_name}));
}
// Test that the name of a detected non-ESCL zeroconf scanner does not generate
// a scanner if it is not an Epson.
TEST_F(LorgnetteScannerManagerTest, NonEsclNonEpsonZeroconfScanner) {
std::optional<Scanner> scanner = CreateSaneScanner(
"Test MX3100", ZeroconfScannerDetector::kGenericScannerServiceType,
/*manufacturer=*/"", /*model=*/"", /*uuid=*/"", /*rs=*/"", /*pdl=*/{},
net::IPAddress(192, 168, 0, 3), 5, true);
EXPECT_FALSE(scanner.has_value());
}
// Test that the name of a detected lorgnette scanner can be retrieved.
TEST_F(LorgnetteScannerManagerTest, LorgnetteScanner) {
lorgnette::ListScannersResponse response =
CreateListScannersResponse(kLorgnetteNetworkIpDeviceName);
GetLorgnetteManagerClient()->SetListScannersResponse(response);
GetScannerNames();
WaitForResult();
const auto& scanner = response.scanners()[0];
std::string scanner_name = scanner.manufacturer() + " " + scanner.model();
EXPECT_THAT(scanner_names(), ElementsAreArray({scanner_name}));
}
// Test that two detected scanners with the same IP address are deduplicated and
// reported with single scanner name.
TEST_F(LorgnetteScannerManagerTest, DeduplicateScanner) {
GetLorgnetteManagerClient()->SetListScannersResponse(
CreateListScannersResponse(kLorgnetteNetworkIpDeviceName));
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
auto epson_scanner = CreateNonEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({epson_scanner});
auto escl_epson_scanner = CreateEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({escl_epson_scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_THAT(scanner_names(), ElementsAreArray({epson_scanner.display_name,
scanner.display_name}));
}
// Test that two detected zeroconf Epson scanners and a lorgnette Epson scanner
// with the same IP address are deduplicated and reported with single scanner
// name.
TEST_F(LorgnetteScannerManagerTest, DeduplicateLorgnetteEpsonScanner) {
GetLorgnetteManagerClient()->SetListScannersResponse(
CreateListScannersResponse(kLorgnetteNetworkEpsonDeviceName,
"EPSON Test2"));
auto epson_scanner = CreateNonEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({epson_scanner});
auto escl_epson_scanner = CreateEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({escl_epson_scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_THAT(scanner_names(), ElementsAreArray({epson_scanner.display_name}));
}
// Test that two detected scanners with the same IP address are deduplicated and
// reported with single scanner name, while USB of the same model is reported
// separately.
TEST_F(LorgnetteScannerManagerTest, DeduplicateNetPlusUsbScanner) {
lorgnette::ListScannersResponse response;
lorgnette::ScannerInfo info =
CreateLorgnetteScanner(kLorgnetteUsbDeviceName, "MX3100");
*response.add_scanners() = std::move(info);
info = CreateLorgnetteScanner(kLorgnetteNetworkIpDeviceName, "MX3100");
*response.add_scanners() = std::move(info);
GetLorgnetteManagerClient()->SetListScannersResponse(response);
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_THAT(scanner_names(),
ElementsAreArray(
{scanner.display_name, scanner.display_name + " (USB)"}));
}
// Test that a lorgnette scanner with a URL in the name gets reported as a
// network scanner instead of a USB scanner (i.e. USB is not in the returned
// scanner name).
TEST_F(LorgnetteScannerManagerTest, LorgnetteScannerWithUrl) {
lorgnette::ListScannersResponse response =
CreateListScannersResponse(kLorgnetteNetworkUrlDeviceName);
GetLorgnetteManagerClient()->SetListScannersResponse(response);
GetScannerNames();
WaitForResult();
auto& scanner = response.scanners()[0];
std::string scanner_name = scanner.manufacturer() + " " + scanner.model();
EXPECT_THAT(scanner_names(), ElementsAreArray({scanner_name}));
}
// Test that detecting a lorgnette USB scanner results in a scanner name ending
// with "(USB)."
TEST_F(LorgnetteScannerManagerTest, LorgnetteUSBScanner) {
lorgnette::ListScannersResponse response =
CreateListScannersResponse(kLorgnetteUsbDeviceName);
GetLorgnetteManagerClient()->SetListScannersResponse(response);
GetScannerNames();
WaitForResult();
auto& scanner = response.scanners()[0];
std::string scanner_name =
scanner.manufacturer() + " " + scanner.model() + " (USB)";
EXPECT_THAT(scanner_names(), ElementsAreArray({scanner_name}));
}
// Test that a lorgnette scanner whose model includes the manufacturer doesn't
// duplicate the manufacturer in the display name.
TEST_F(LorgnetteScannerManagerTest, LorgnetteScannerNoDuplicatedManufacturer) {
lorgnette::ListScannersResponse response = CreateListScannersResponse(
kLorgnetteNetworkIpDeviceName, kModelContainingManufacturer);
GetLorgnetteManagerClient()->SetListScannersResponse(response);
GetScannerNames();
WaitForResult();
const auto& scanner = response.scanners()[0];
EXPECT_THAT(scanner_names(), ElementsAreArray({scanner.model()}));
}
// Test that two lorgnette scanners with the same manufacturer and model are
// given unique names.
TEST_F(LorgnetteScannerManagerTest, UniqueScannerNames) {
lorgnette::ListScannersResponse response =
CreateListScannersResponse(kLorgnetteNetworkIpDeviceName);
lorgnette::ScannerInfo scanner =
CreateLorgnetteScanner(kLorgnetteNetworkIpDeviceName);
*response.add_scanners() = std::move(scanner);
GetLorgnetteManagerClient()->SetListScannersResponse(response);
GetScannerNames();
WaitForResult();
ASSERT_EQ(scanner_names().size(), 2u);
EXPECT_NE(scanner_names()[0], scanner_names()[1]);
}
// Test that removing a detected scanner removes it from the list of available
// scanners.
TEST_F(LorgnetteScannerManagerTest, RemoveScanner) {
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_THAT(scanner_names(), ElementsAreArray({scanner.display_name}));
fake_zeroconf_scanner_detector()->RemoveDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_TRUE(scanner_names().empty());
}
// Test GetScannerInfoList when std::nullopt response is returned.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListNull) {
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_EQ(list_scanners_response().value().scanners_size(), 0);
}
// Test GetScannerInfoList when empty response is returned.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListEmpty) {
GetLorgnetteManagerClient()->SetListScannersResponse(
lorgnette::ListScannersResponse());
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_EQ(list_scanners_response().value().scanners_size(), 0);
}
// Test that a detected lorgnette scanner can be retrieved.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListLorgnette) {
lorgnette::ListScannersResponse response =
CreateListScannersResponse(kLorgnetteNetworkIpDeviceName);
GetLorgnetteManagerClient()->SetListScannersResponse(response);
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_EQ(list_scanners_response()->result(),
lorgnette::OPERATION_RESULT_SUCCESS);
ASSERT_EQ(list_scanners_response().value().scanners_size(), 1);
// UUID should have been populated.
EXPECT_FALSE(
list_scanners_response().value().scanners(0).device_uuid().empty());
// Name should have been replaced by a token.
EXPECT_NE(list_scanners_response().value().scanners(0).name(),
response.scanners(0).name());
// Remaining fields should match |response|.
EXPECT_THAT(list_scanners_response().value(),
EquivalentToListScannersResponse(response));
}
// Test that a detected zeroconf scanner can be retrieved.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListZeroconf) {
auto expected_scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({expected_scanner});
// Response has an unknown result because lorgnette isn't called, but it
// contains the one zeroconf scanner anyway.
lorgnette::ListScannersResponse expected_response;
expected_response.set_result(lorgnette::OPERATION_RESULT_UNKNOWN);
*expected_response.add_scanners() = ScannerInfoFromScanner(expected_scanner);
CompleteTasks();
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
ASSERT_EQ(list_scanners_response().value().scanners_size(), 1);
EXPECT_THAT(list_scanners_response().value(),
EquivalentToListScannersResponse(expected_response));
}
// Test that a non-escl zeroconf scanner can be retrieved.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListNonEsclZeroconf) {
auto zeroconf_scanner = CreateZeroconfScanner();
auto non_escl_scanner = CreateNonEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections(
{zeroconf_scanner, non_escl_scanner});
CompleteTasks();
// When the scanner list is retrieved and it contains non-escl network
// scanners, those are verified by attempting to open the scanner. Provide an
// open response to our fake client.
lorgnette::ScannerConfig config;
config.mutable_scanner()->set_token("scanner-token");
lorgnette::OpenScannerResponse response;
response.mutable_scanner_id()->set_connection_string("connection-string");
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
*response.mutable_config() = std::move(config);
GetLorgnetteManagerClient()->SetOpenScannerResponse(response);
// Response has an unknown result because lorgnette isn't called, but it
// contains the zeroconf scanners anyway.
lorgnette::ListScannersResponse expected_response;
expected_response.set_result(lorgnette::OPERATION_RESULT_UNKNOWN);
*expected_response.add_scanners() = ScannerInfoFromScanner(zeroconf_scanner);
*expected_response.add_scanners() = ScannerInfoFromScanner(non_escl_scanner);
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
ASSERT_EQ(list_scanners_response().value().scanners_size(), 2);
EXPECT_THAT(list_scanners_response().value(),
EquivalentToListScannersResponse(expected_response));
}
// Test that a non-escl zeroconf scanner can be retrieved when it's busy.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListNonEsclZeroconfBusy) {
auto non_escl_scanner = CreateNonEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({non_escl_scanner});
CompleteTasks();
// When the scanner list is retrieved and it contains non-escl network
// scanners, those are verified by attempting to open the scanner. Provide an
// open response to our fake client.
lorgnette::ScannerConfig config;
config.mutable_scanner()->set_token("scanner-token");
lorgnette::OpenScannerResponse response;
response.mutable_scanner_id()->set_connection_string("connection-string");
response.set_result(lorgnette::OPERATION_RESULT_DEVICE_BUSY);
*response.mutable_config() = std::move(config);
GetLorgnetteManagerClient()->SetOpenScannerResponse(response);
// Response has an unknown result because lorgnette isn't called, but it
// contains the zeroconf scanner anyway.
lorgnette::ListScannersResponse expected_response;
expected_response.set_result(lorgnette::OPERATION_RESULT_UNKNOWN);
*expected_response.add_scanners() = ScannerInfoFromScanner(non_escl_scanner);
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
ASSERT_EQ(list_scanners_response().value().scanners_size(), 1);
EXPECT_THAT(list_scanners_response().value(),
EquivalentToListScannersResponse(expected_response));
}
// Test that a non-escl zeroconf scanner is not returned if it's unreachable.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListNonEsclZeroconfDead) {
auto non_escl_scanner = CreateNonEsclEpsonZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({non_escl_scanner});
CompleteTasks();
// When the scanner list is retrieved and it contains non-escl network
// scanners, those are verified by attempting to open the scanner. Provide an
// open response to our fake client.
lorgnette::ScannerConfig config;
config.mutable_scanner()->set_token("scanner-token");
lorgnette::OpenScannerResponse response;
response.mutable_scanner_id()->set_connection_string("connection-string");
response.set_result(lorgnette::OPERATION_RESULT_INVALID);
*response.mutable_config() = std::move(config);
GetLorgnetteManagerClient()->SetOpenScannerResponse(response);
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_EQ(list_scanners_response().value().scanners_size(), 0);
}
// Test that unusable zeroconf scanner is not retrieved.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListZeroconfUnusable) {
auto scanner = CreateZeroconfScanner(false);
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_EQ(list_scanners_response().value().scanners_size(), 0);
}
// Test that multiple zeroconf scanners have the same UUID.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListZeroconfSameUuid) {
auto scanner = CreateZeroconfScanner(true, "12345-67890");
ASSERT_EQ(scanner.device_names.size(), 1u);
// Adding a second device name to the same Scanner should cause both
// ScannerInfo objects to have the same UUID.
scanner.device_names.begin()->second.insert(
ScannerDeviceName("airscan:escl:Test MX3100:http://192.168.0.3:5/"));
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
ASSERT_EQ(list_scanners_response().value().scanners_size(), 2);
EXPECT_EQ(list_scanners_response().value().scanners(0).device_uuid(),
"12345-67890");
EXPECT_EQ(list_scanners_response().value().scanners(1).device_uuid(),
"12345-67890");
}
// Test that multiple zeroconf scanners have the same UUID even when the
// zeroconf detection does not populate a UUID.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListZeroconfAbsentUuid) {
auto scanner = CreateZeroconfScanner();
ASSERT_EQ(scanner.device_names.size(), 1u);
// Adding a second device name to the same Scanner should cause both
// ScannerInfo objects to have the same UUID.
scanner.device_names.begin()->second.insert(
ScannerDeviceName("airscan:escl:Test MX3100:http://192.168.0.3:5/"));
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
ASSERT_EQ(list_scanners_response().value().scanners_size(), 2);
// When a UUID is not detected in the zeroconf detector these two objects
// should still have the same UUID.
EXPECT_FALSE(
list_scanners_response().value().scanners(0).device_uuid().empty());
EXPECT_EQ(list_scanners_response().value().scanners(0).device_uuid(),
list_scanners_response().value().scanners(1).device_uuid());
}
// Test that detected zeroconf and lorgnette scanners can be retrieved. These
// scanners should have different UUIDs since they represent different physical
// hardware.
TEST_F(LorgnetteScannerManagerTest,
GetScannerInfoListUniqueZeroconfAndLorgnette) {
lorgnette::ListScannersResponse response =
CreateListScannersResponse(kLorgnetteNetworkUrlDeviceName, "Model 2");
GetLorgnetteManagerClient()->SetListScannersResponse(response);
fake_zeroconf_scanner_detector()->AddDetections({CreateZeroconfScanner()});
CompleteTasks();
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_EQ(list_scanners_response()->result(),
lorgnette::OPERATION_RESULT_SUCCESS);
ASSERT_EQ(list_scanners_response().value().scanners_size(), 2);
EXPECT_FALSE(
list_scanners_response().value().scanners(0).device_uuid().empty());
EXPECT_FALSE(
list_scanners_response().value().scanners(1).device_uuid().empty());
EXPECT_NE(list_scanners_response().value().scanners(0).device_uuid(),
list_scanners_response().value().scanners(1).device_uuid());
}
// Test scanners are filtered correctly.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListLocalOnlyFilter) {
lorgnette::ListScannersResponse response;
lorgnette::ScannerInfo info = CreateLorgnetteScanner(kLorgnetteUsbDeviceName);
info.set_connection_type(lorgnette::CONNECTION_USB);
info.set_secure(false);
*response.add_scanners() = std::move(info);
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
// This scanner should get filtered out because it's a network scanner.
GetLorgnetteManagerClient()->SetListScannersResponse(response);
fake_zeroconf_scanner_detector()->AddDetections({CreateZeroconfScanner()});
CompleteTasks();
GetScannerInfoList(LocalScannerFilter::kLocalScannersOnly,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
ASSERT_EQ(list_scanners_response().value().scanners_size(), 1);
EXPECT_THAT(list_scanners_response().value(),
EquivalentToListScannersResponse(response));
}
// Test that scanners are filtered correctly.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListSecureOnlyFilter) {
// This scanner should get filtered out because it's not secure.
lorgnette::ListScannersResponse response;
lorgnette::ScannerInfo info = CreateLorgnetteScanner(kLorgnetteUsbDeviceName);
info.set_connection_type(lorgnette::CONNECTION_USB);
info.set_secure(false);
*response.add_scanners() = std::move(info);
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
// This scanner will be returned because it uses a secure connection protocol.
auto expected_scanner = CreateZeroconfScanner();
lorgnette::ListScannersResponse expected_response;
expected_response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
*expected_response.add_scanners() = ScannerInfoFromScanner(expected_scanner);
GetLorgnetteManagerClient()->SetListScannersResponse(response);
fake_zeroconf_scanner_detector()->AddDetections({expected_scanner});
CompleteTasks();
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kSecureScannersOnly);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_EQ(list_scanners_response()->result(),
lorgnette::OPERATION_RESULT_SUCCESS);
ASSERT_EQ(list_scanners_response().value().scanners_size(), 1);
EXPECT_THAT(list_scanners_response().value(),
EquivalentToListScannersResponse(expected_response));
}
// Test that scanners are filtered correctly.
TEST_F(LorgnetteScannerManagerTest,
GetScannerInfoListLocalAndSecureOnlyFilter) {
// This scanner should get filtered out because it's not secure.
lorgnette::ListScannersResponse response;
lorgnette::ScannerInfo info = CreateLorgnetteScanner(kLorgnetteUsbDeviceName);
info.set_connection_type(lorgnette::CONNECTION_USB);
info.set_secure(false);
*response.add_scanners() = std::move(info);
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
GetLorgnetteManagerClient()->SetListScannersResponse(response);
// This scanner should get filtered out because it's a network scanner.
fake_zeroconf_scanner_detector()->AddDetections({CreateZeroconfScanner()});
CompleteTasks();
GetScannerInfoList(LocalScannerFilter::kLocalScannersOnly,
SecureScannerFilter::kSecureScannersOnly);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_EQ(list_scanners_response()->result(),
lorgnette::OPERATION_RESULT_SUCCESS);
ASSERT_EQ(list_scanners_response().value().scanners_size(), 0);
}
// Test that generated tokens carry through multiple discovery requests.
TEST_F(LorgnetteScannerManagerTest, GetScannerInfoListTokensAcrossSessions) {
lorgnette::ListScannersResponse response;
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
// Two scanners with different names.
lorgnette::ScannerInfo info = CreateLorgnetteScanner(kLorgnetteUsbDeviceName);
info.set_connection_type(lorgnette::CONNECTION_USB);
info.set_secure(true);
info.set_device_uuid("1234-5678-90");
*response.add_scanners() = info;
info.set_name(info.name() + "2");
info.set_device_uuid("1234-5678-91");
*response.add_scanners() = std::move(info);
// First request picks up both scanners.
GetLorgnetteManagerClient()->SetListScannersResponse(response);
GetScannerInfoList(LocalScannerFilter::kLocalScannersOnly,
SecureScannerFilter::kSecureScannersOnly);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_THAT(list_scanners_response().value(),
EquivalentToListScannersResponse(response));
std::string remove_token =
list_scanners_response().value().scanners(0).name();
std::string keep_token = list_scanners_response().value().scanners(1).name();
// Change the first scanner's UUID. This should create a new token and
// invalidate the original. The second scanner preserves its token because it
// is unchanged.
response.mutable_scanners(0)->set_device_uuid("9876-5432-10");
GetLorgnetteManagerClient()->SetListScannersResponse(response);
GetScannerInfoList(LocalScannerFilter::kLocalScannersOnly,
SecureScannerFilter::kSecureScannersOnly);
WaitForResult();
ASSERT_TRUE(list_scanners_response());
EXPECT_THAT(list_scanners_response().value(),
EquivalentToListScannersResponse(response));
EXPECT_NE(list_scanners_response().value().scanners(0).name(), remove_token);
EXPECT_EQ(list_scanners_response().value().scanners(1).name(), keep_token);
}
// Test that getting capabilities fails when GetScannerNames() has never been
// called.
TEST_F(LorgnetteScannerManagerTest, GetCapsNoScanner) {
GetScannerCapabilities(kUnknownScannerName);
WaitForResult();
EXPECT_FALSE(scanner_capabilities());
}
// Test that getting capabilities fails when the scanner name does not
// correspond to a known scanner.
TEST_F(LorgnetteScannerManagerTest, GetCapsUnknownScanner) {
fake_zeroconf_scanner_detector()->AddDetections({CreateZeroconfScanner()});
CompleteTasks();
GetScannerNames();
WaitForResult();
GetScannerCapabilities(kUnknownScannerName);
WaitForResult();
EXPECT_FALSE(scanner_capabilities());
}
// Test that getting capabilities fails when there is no usable device name.
TEST_F(LorgnetteScannerManagerTest, GetCapsNoUsableDeviceName) {
auto scanner = CreateZeroconfScanner(/*usable=*/false);
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
GetScannerCapabilities(scanner.display_name);
WaitForResult();
EXPECT_FALSE(scanner_capabilities());
}
// Test that failing to get capabilities from lorgnette returns no capabilities.
TEST_F(LorgnetteScannerManagerTest, GetCapsFail) {
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
GetLorgnetteManagerClient()->SetScannerCapabilitiesResponse(std::nullopt);
GetScannerCapabilities(scanner.display_name);
WaitForResult();
EXPECT_FALSE(scanner_capabilities());
}
// Test that getting capabilities succeeds with a valid scanner name.
TEST_F(LorgnetteScannerManagerTest, GetCaps) {
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
lorgnette::ScannerCapabilities capabilities;
capabilities.add_resolutions(300);
capabilities.add_color_modes(lorgnette::MODE_COLOR);
GetLorgnetteManagerClient()->SetScannerCapabilitiesResponse(capabilities);
GetScannerNames();
WaitForResult();
GetScannerCapabilities(scanner.display_name);
WaitForResult();
ASSERT_TRUE(scanner_capabilities());
const auto caps = scanner_capabilities().value();
ASSERT_EQ(caps.resolutions_size(), 1);
EXPECT_EQ(caps.resolutions()[0], 300u);
EXPECT_EQ(caps.sources_size(), 0);
ASSERT_EQ(caps.color_modes_size(), 1);
EXPECT_EQ(caps.color_modes()[0], lorgnette::MODE_COLOR);
}
// Test opening a scanner without calling GetScannerInfoList first.
TEST_F(LorgnetteScannerManagerTest, OpenScanner_UnknownClientFails) {
lorgnette::ScannerId scanner_id;
scanner_id.set_connection_string("connection-string");
lorgnette::OpenScannerRequest request;
request.set_client_id("client-id");
*request.mutable_scanner_id() = scanner_id;
lorgnette::OpenScannerResponse response;
*response.mutable_scanner_id() = std::move(scanner_id);
response.set_result(lorgnette::OPERATION_RESULT_INVALID);
OpenScanner(request);
WaitForResult();
ASSERT_TRUE(open_scanner_response());
EXPECT_THAT(open_scanner_response().value(), EqualsProto(response));
}
// Test opening a scanner with a token that wasn't previously returned.
TEST_F(LorgnetteScannerManagerTest, OpenScanner_UnknownTokenFails) {
// Create a mapping for this client, but without any valid tokens.
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
lorgnette::ScannerId scanner_id;
scanner_id.set_connection_string("connection-string");
lorgnette::OpenScannerRequest request;
request.set_client_id("client-id");
*request.mutable_scanner_id() = scanner_id;
lorgnette::OpenScannerResponse response;
*response.mutable_scanner_id() = std::move(scanner_id);
response.set_result(lorgnette::OPERATION_RESULT_INVALID);
OpenScanner(request);
WaitForResult();
ASSERT_TRUE(open_scanner_response());
EXPECT_THAT(open_scanner_response().value(), EqualsProto(response));
}
// Test opening a scanner with a token that is no longer valid.
TEST_F(LorgnetteScannerManagerTest, OpenScanner_ExpiredTokenFails) {
lorgnette::ListScannersResponse list_response =
CreateListScannersResponse(kLorgnetteNetworkIpDeviceName);
GetLorgnetteManagerClient()->SetListScannersResponse(list_response);
// Create a mapping for this client with one valid token.
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response().has_value());
ASSERT_EQ(list_scanners_response().value().scanners_size(), 1);
// Refer to the valid token.
lorgnette::ScannerId scanner_id;
scanner_id.set_connection_string(
list_scanners_response().value().scanners(0).name());
// The token is no longer valid after getting back no scanners.
GetLorgnetteManagerClient()->SetListScannersResponse({});
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response().has_value());
EXPECT_EQ(list_scanners_response().value().scanners_size(), 0);
lorgnette::OpenScannerRequest request;
request.set_client_id("client-id");
*request.mutable_scanner_id() = scanner_id;
lorgnette::OpenScannerResponse expected_response;
*expected_response.mutable_scanner_id() = std::move(scanner_id);
expected_response.set_result(lorgnette::OPERATION_RESULT_MISSING);
OpenScanner(request);
WaitForResult();
ASSERT_TRUE(open_scanner_response());
EXPECT_THAT(open_scanner_response().value(), EqualsProto(expected_response));
}
// Test opening a scanner with a valid token.
TEST_F(LorgnetteScannerManagerTest, OpenScanner_ValidTokenSucceeds) {
lorgnette::ListScannersResponse list_response =
CreateListScannersResponse(kLorgnetteNetworkIpDeviceName);
GetLorgnetteManagerClient()->SetListScannersResponse(list_response);
// Create a mapping for this client with one valid token.
GetScannerInfoList(LocalScannerFilter::kIncludeNetworkScanners,
SecureScannerFilter::kIncludeUnsecureScanners);
WaitForResult();
ASSERT_TRUE(list_scanners_response().has_value());
ASSERT_EQ(list_scanners_response().value().scanners_size(), 1);
// Refer to the valid token.
lorgnette::ScannerId scanner_id;
scanner_id.set_connection_string(
list_scanners_response().value().scanners(0).name());
lorgnette::OpenScannerRequest request;
request.set_client_id("client-id");
*request.mutable_scanner_id() = scanner_id;
lorgnette::ScannerHandle handle;
handle.set_token("scanner-token");
lorgnette::ScannerConfig config;
*config.mutable_scanner() = std::move(handle);
lorgnette::OpenScannerResponse expected_response;
*expected_response.mutable_scanner_id() = std::move(scanner_id);
expected_response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
*expected_response.mutable_config() = std::move(config);
lorgnette::OpenScannerResponse lorgnette_response = expected_response;
lorgnette_response.mutable_scanner_id()->set_connection_string(
kLorgnetteNetworkIpDeviceName);
GetLorgnetteManagerClient()->SetOpenScannerResponse(lorgnette_response);
OpenScanner(request);
WaitForResult();
ASSERT_TRUE(open_scanner_response());
EXPECT_THAT(open_scanner_response().value(), EqualsProto(expected_response));
}
// Test closing a scanner.
TEST_F(LorgnetteScannerManagerTest, CloseScanner) {
lorgnette::ScannerHandle handle;
handle.set_token("scanner-token");
lorgnette::CloseScannerResponse response;
*response.mutable_scanner() = std::move(handle);
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
GetLorgnetteManagerClient()->SetCloseScannerResponse(response);
CloseScanner();
WaitForResult();
ASSERT_TRUE(close_scanner_response());
EXPECT_THAT(response, EqualsProto(close_scanner_response().value()));
}
// Test setting the options for a scanner.
TEST_F(LorgnetteScannerManagerTest, SetOptions) {
constexpr std::string kScannerToken = "scanner-token";
lorgnette::SetOptionsResponse response;
response.mutable_scanner()->set_token(kScannerToken);
response.mutable_config()->mutable_scanner()->set_token(kScannerToken);
GetLorgnetteManagerClient()->SetSetOptionsResponse(response);
SetOptions();
WaitForResult();
ASSERT_TRUE(set_options_response());
EXPECT_THAT(response, EqualsProto(set_options_response().value()));
}
// Test getting the config for a scanner.
TEST_F(LorgnetteScannerManagerTest, GetCurrentConfig) {
constexpr std::string kScannerToken = "scanner-token";
lorgnette::GetCurrentConfigResponse response;
response.mutable_scanner()->set_token(kScannerToken);
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
response.mutable_config()->mutable_scanner()->set_token(kScannerToken);
GetLorgnetteManagerClient()->SetGetCurrentConfigResponse(response);
GetCurrentConfig();
WaitForResult();
ASSERT_TRUE(get_current_config_response());
EXPECT_THAT(response, EqualsProto(get_current_config_response().value()));
}
// Test starting a prepared scan.
TEST_F(LorgnetteScannerManagerTest, StartPreparedScan) {
lorgnette::ScannerHandle handle;
handle.set_token("scanner-token");
lorgnette::JobHandle job_handle;
job_handle.set_token("job-handle-token");
lorgnette::StartPreparedScanResponse response;
*response.mutable_scanner() = std::move(handle);
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
*response.mutable_job_handle() = std::move(job_handle);
GetLorgnetteManagerClient()->SetStartPreparedScanResponse(response);
StartPreparedScan();
WaitForResult();
ASSERT_TRUE(start_prepared_scan_response());
EXPECT_THAT(response, EqualsProto(start_prepared_scan_response().value()));
}
// Test reading scan data.
TEST_F(LorgnetteScannerManagerTest, ReadScanData) {
lorgnette::JobHandle handle;
handle.set_token("job-token");
lorgnette::ReadScanDataResponse response;
*response.mutable_job_handle() = std::move(handle);
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
response.set_data("test-data");
response.set_estimated_completion(25);
GetLorgnetteManagerClient()->SetReadScanDataResponse(response);
ReadScanData();
WaitForResult();
ASSERT_TRUE(read_scan_data_response());
EXPECT_THAT(response, EqualsProto(read_scan_data_response().value()));
}
// Test that scanning fails when GetScannerNames() has never been called.
TEST_F(LorgnetteScannerManagerTest, NoScannersNames) {
lorgnette::ScanSettings settings;
Scan(kUnknownScannerName, settings);
WaitForResult();
EXPECT_EQ(scan_data().size(), 0u);
EXPECT_EQ(failure_mode(), lorgnette::SCAN_FAILURE_MODE_UNKNOWN);
}
// Test that scanning fails when the scanner name does not correspond to a known
// scanner.
TEST_F(LorgnetteScannerManagerTest, UnknownScannerName) {
fake_zeroconf_scanner_detector()->AddDetections({CreateZeroconfScanner()});
CompleteTasks();
GetScannerNames();
WaitForResult();
lorgnette::ScanSettings settings;
Scan(kUnknownScannerName, settings);
WaitForResult();
EXPECT_EQ(scan_data().size(), 0u);
EXPECT_EQ(failure_mode(), lorgnette::SCAN_FAILURE_MODE_UNKNOWN);
}
// Test that scanning fails when there is no usable device name.
TEST_F(LorgnetteScannerManagerTest, NoUsableDeviceName) {
auto scanner = CreateZeroconfScanner(/*usable=*/false);
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
lorgnette::ScanSettings settings;
Scan(scanner.display_name, settings);
WaitForResult();
EXPECT_EQ(scan_data().size(), 0u);
EXPECT_EQ(failure_mode(), lorgnette::SCAN_FAILURE_MODE_UNKNOWN);
}
// Test that images aren't rotated when scanner isn't an Epson scanner.
TEST_F(LorgnetteScannerManagerTest, ScanNotRotatedNonEpson) {
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_FALSE(GetRotateAlternate(scanner.display_name, "ADF Duplex"));
}
// Test that images aren't rotated when scanner is a non-rotating Epson scanner.
TEST_F(LorgnetteScannerManagerTest, ScanNotRotatedEpsonException) {
auto scanner = CreateScannerCustomName("Epson WF-C579Ra");
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_FALSE(GetRotateAlternate(scanner.display_name, "ADF Duplex"));
}
// Test that images aren't rotated when scan request is non-ADF.
TEST_F(LorgnetteScannerManagerTest, ScanNotRotatedNonADF) {
auto scanner = CreateScannerCustomName("Epson XP-7100");
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_FALSE(GetRotateAlternate(scanner.display_name, "Flatbed"));
}
// Test that scanned images are rotated when scanner setup requires it.
TEST_F(LorgnetteScannerManagerTest, ScanRotated) {
auto scanner = CreateScannerCustomName("Epson XP-7100");
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
EXPECT_TRUE(GetRotateAlternate(scanner.display_name, "ADF Duplex"));
}
// Test that scanning succeeds with a valid scanner name.
TEST_F(LorgnetteScannerManagerTest, ScanOnePage) {
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
std::vector<std::string> pages = {"TestScanData"};
GetLorgnetteManagerClient()->SetScanResponse(pages);
lorgnette::ScanSettings settings;
Scan(scanner.display_name, settings);
WaitForResult();
ASSERT_EQ(scan_data().size(), 1u);
EXPECT_EQ(scan_data()[0], "TestScanData");
EXPECT_EQ(failure_mode(), lorgnette::SCAN_FAILURE_MODE_NO_FAILURE);
}
TEST_F(LorgnetteScannerManagerTest, ScanMultiplePages) {
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
std::vector<std::string> pages = {"TestPageOne", "TestPageTwo",
"TestPageThree"};
GetLorgnetteManagerClient()->SetScanResponse(pages);
lorgnette::ScanSettings settings;
Scan(scanner.display_name, settings);
WaitForResult();
ASSERT_EQ(scan_data().size(), 3u);
EXPECT_EQ(scan_data()[0], "TestPageOne");
EXPECT_EQ(scan_data()[1], "TestPageTwo");
EXPECT_EQ(scan_data()[2], "TestPageThree");
EXPECT_EQ(failure_mode(), lorgnette::SCAN_FAILURE_MODE_NO_FAILURE);
}
// Test that requesting to cancel the current scan job returns the success
// result.
TEST_F(LorgnetteScannerManagerTest, CancelScan) {
auto scanner = CreateZeroconfScanner();
fake_zeroconf_scanner_detector()->AddDetections({scanner});
CompleteTasks();
GetScannerNames();
WaitForResult();
CancelScan();
WaitForResult();
EXPECT_TRUE(cancel_scan_success());
}
// Test canceling a scan by JobHandle.
TEST_F(LorgnetteScannerManagerTest, CancelScanByJobHandle) {
lorgnette::JobHandle job_handle;
job_handle.set_token("job-handle-token");
lorgnette::CancelScanResponse response;
response.set_success(true);
response.set_result(lorgnette::OPERATION_RESULT_SUCCESS);
*response.mutable_job_handle() = std::move(job_handle);
GetLorgnetteManagerClient()->SetCancelScanResponse(response);
CancelScanJob();
WaitForResult();
ASSERT_TRUE(cancel_scan_response());
EXPECT_THAT(response, EqualsProto(cancel_scan_response().value()));
}
} // namespace ash