// 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/scan_service.h"
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "ash/webui/scanning/mojom/scanning.mojom.h"
#include "ash/webui/scanning/scanning_uma.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/i18n/time_formatting.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/scanning/fake_lorgnette_scanner_manager.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h"
#include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h"
#include "chrome/browser/ui/ash/holding_space/scoped_test_mount_point.h"
#include "chrome/browser/ui/ash/session/test_session_controller.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/lorgnette/lorgnette_service.pb.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 "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"
namespace ash {
using holding_space::ScopedTestMountPoint;
namespace {
namespace mojo_ipc = scanning::mojom;
using ProtoScanFailureMode = lorgnette::ScanFailureMode;
// Path to the user's "My files" folder.
constexpr char kMyFilesPath[] = "/home/chronos/user/MyFiles";
// Scanner names used for tests.
constexpr char kFirstTestScannerName[] = "Test Scanner 1";
constexpr char16_t kFirstTestScannerName16[] = u"Test Scanner 1";
constexpr char kSecondTestScannerName[] = "Test Scanner 2";
constexpr char16_t kSecondTestScannerName16[] = u"Test Scanner 2";
constexpr char kEpsonTestName[] =
"airscan:escl:EPSON XP-7100 Series:http://100.107.108.190:443/eSCL/";
// Document source name used for tests.
constexpr char kDocumentSourceName[] = "Flatbed";
constexpr char kAdfSourceName[] = "ADF Duplex";
// Resolutions used for tests.
constexpr uint32_t kFirstResolution = 75;
constexpr uint32_t kSecondResolution = 300;
// Email used for test profile.
constexpr char kUserEmail[] = "[email protected]";
// Translation from file type to saved file extension.
const std::map<mojo_ipc::FileType, std::string> kFileTypes = {
{mojo_ipc::FileType::kJpg, "jpg"},
{mojo_ipc::FileType::kPdf, "pdf"},
{mojo_ipc::FileType::kPng, "png"}};
// Returns a DocumentSource object.
lorgnette::DocumentSource CreateLorgnetteDocumentSource() {
lorgnette::DocumentSource source;
source.set_type(lorgnette::SOURCE_PLATEN);
source.set_name(kDocumentSourceName);
source.add_color_modes(lorgnette::MODE_COLOR);
source.add_resolutions(kFirstResolution);
source.add_resolutions(kSecondResolution);
return source;
}
// Returns an ADF Duplex DocumentSource object.
lorgnette::DocumentSource CreateAdfDuplexDocumentSource() {
lorgnette::DocumentSource source;
source.set_type(lorgnette::SOURCE_ADF_DUPLEX);
source.set_name(kAdfSourceName);
return source;
}
// Returns a ScannerCapabilities object.
lorgnette::ScannerCapabilities CreateLorgnetteScannerCapabilities() {
lorgnette::ScannerCapabilities caps;
*caps.add_sources() = CreateLorgnetteDocumentSource();
return caps;
}
// Returns a ScannerCapabilities object used for testing a scanner
// that flips alternate pages..
lorgnette::ScannerCapabilities CreateEpsonScannerCapabilities() {
lorgnette::ScannerCapabilities caps;
*caps.add_sources() = CreateAdfDuplexDocumentSource();
caps.add_color_modes(lorgnette::MODE_COLOR);
caps.add_resolutions(kFirstResolution);
caps.add_resolutions(kSecondResolution);
return caps;
}
std::string GetTimestamp(const base::Time& scan_time) {
return base::UnlocalizedTimeFormatWithPattern(scan_time, "yyMMdd-HHmmss");
}
// Returns single FilePath to mimic saved PDF format scan.
base::FilePath CreateSavedPdfScanPath(const base::FilePath& dir,
const base::Time& scan_time) {
return dir.Append(
base::StringPrintf("scan_%s.pdf", GetTimestamp(scan_time).c_str()));
}
// Returns a vector of FilePaths to mimic saved scans.
std::vector<base::FilePath> CreateSavedScanPaths(
const base::FilePath& dir,
const base::Time& scan_time,
const mojo_ipc::FileType& file_type,
int num_pages_to_scan) {
const auto typeAndExtension = kFileTypes.find(file_type);
EXPECT_NE(typeAndExtension, kFileTypes.cend());
std::vector<base::FilePath> file_paths;
if (file_type == mojo_ipc::FileType::kPdf) {
file_paths.reserve(1);
file_paths.push_back(CreateSavedPdfScanPath(dir, scan_time));
} else {
file_paths.reserve(num_pages_to_scan);
for (int i = 1; i <= num_pages_to_scan; i++) {
file_paths.push_back(dir.Append(
base::StringPrintf("scan_%s_%d.%s", GetTimestamp(scan_time).c_str(),
i, typeAndExtension->second.c_str())));
}
}
return file_paths;
}
// Returns a manually generated JPEG image. |alpha| is used to make them unique.
std::string CreateJpeg(const int alpha = 255) {
SkBitmap bitmap;
bitmap.allocN32Pixels(100, 100);
bitmap.eraseARGB(alpha, 0, 0, 255);
std::vector<unsigned char> bytes;
CHECK(gfx::JPEGCodec::Encode(bitmap, 90, &bytes));
return std::string(bytes.begin(), bytes.end());
}
// Returns scan settings with the given path and file type.
mojo_ipc::ScanSettings CreateScanSettings(
const base::FilePath& scan_to_path,
const mojo_ipc::FileType& file_type,
const std::string& source = "",
mojo_ipc::ColorMode color_mode = mojo_ipc::ColorMode::kColor,
mojo_ipc::PageSize page_size = mojo_ipc::PageSize::kIsoA3,
uint32_t resolution = kFirstResolution) {
mojo_ipc::ScanSettings settings;
settings.scan_to_path = scan_to_path;
settings.file_type = file_type;
settings.source_name = source;
settings.page_size = page_size;
settings.color_mode = color_mode;
settings.resolution_dpi = resolution;
return settings;
}
// Returns a profile manager set up to generate testing profiles.
std::unique_ptr<TestingProfileManager> CreateTestingProfileManager() {
auto profile_manager = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
EXPECT_TRUE(profile_manager->SetUp());
return profile_manager;
}
} // namespace
class FakeScanJobObserver : public mojo_ipc::ScanJobObserver {
public:
FakeScanJobObserver() = default;
~FakeScanJobObserver() override = default;
FakeScanJobObserver(const FakeScanJobObserver&) = delete;
FakeScanJobObserver& operator=(const FakeScanJobObserver&) = delete;
// mojo_ipc::ScanJobObserver:
void OnPageProgress(uint32_t page_number,
uint32_t progress_percent) override {
progress_ = progress_percent;
}
void OnPageComplete(const std::vector<uint8_t>& page_data,
const uint32_t new_page_index) override {
page_complete_ = true;
new_page_index_ = new_page_index;
}
void OnScanComplete(
ProtoScanFailureMode result,
const std::vector<base::FilePath>& scanned_file_paths) override {
scan_result_ = result;
scanned_file_paths_ = scanned_file_paths;
}
void OnCancelComplete(bool success) override {
cancel_scan_success_ = success;
}
void OnMultiPageScanFail(ProtoScanFailureMode result) override {
multi_page_scan_result_ = result;
}
// Creates a pending remote that can be passed in calls to
// ScanService::StartScan().
mojo::PendingRemote<mojo_ipc::ScanJobObserver> GenerateRemote() {
if (receiver_.is_bound())
receiver_.reset();
mojo::PendingRemote<mojo_ipc::ScanJobObserver> remote;
receiver_.Bind(remote.InitWithNewPipeAndPassReceiver());
return remote;
}
// Returns true if the scan completed successfully.
bool scan_success() const {
return progress_ == 100 && page_complete_ &&
scan_result_ == ProtoScanFailureMode::SCAN_FAILURE_MODE_NO_FAILURE;
}
// Returns true if the cancel scan request completed successfully.
bool cancel_scan_success() const { return cancel_scan_success_; }
uint32_t new_page_index() const { return new_page_index_; }
// Returns the result of the scan job.
ProtoScanFailureMode scan_result() const { return scan_result_; }
// Returns the result of the multi-page scan job.
ProtoScanFailureMode multi_page_scan_result() const {
return multi_page_scan_result_;
}
// Returns file paths of the saved scan files.
std::vector<base::FilePath> scanned_file_paths() const {
return scanned_file_paths_;
}
private:
uint32_t progress_ = 0;
bool page_complete_ = false;
uint32_t new_page_index_ = UINT32_MAX;
ProtoScanFailureMode scan_result_ =
ProtoScanFailureMode::SCAN_FAILURE_MODE_UNKNOWN;
ProtoScanFailureMode multi_page_scan_result_ =
ProtoScanFailureMode::SCAN_FAILURE_MODE_UNKNOWN;
bool cancel_scan_success_ = false;
std::vector<base::FilePath> scanned_file_paths_;
mojo::Receiver<mojo_ipc::ScanJobObserver> receiver_{this};
};
class ScanServiceTest : public testing::Test {
public:
ScanServiceTest()
: profile_manager_(CreateTestingProfileManager()),
profile_(profile_manager_->CreateTestingProfile(kUserEmail)),
scanned_files_mount_(
ScopedTestMountPoint::CreateAndMountDownloads(profile_)),
session_controller_(std::make_unique<TestSessionController>()),
user_manager_(new ash::FakeChromeUserManager),
user_manager_owner_(base::WrapUnique(user_manager_.get())) {
DCHECK(scanned_files_mount_->IsValid());
const AccountId account_id(AccountId::FromUserEmail(kUserEmail));
user_manager_->AddUser(account_id);
user_manager_->LoginUser(account_id);
SetupScanService(scanned_files_mount_->GetRootPath(),
base::FilePath("/google/drive"));
}
void SetupScanService(base::FilePath my_files_path,
base::FilePath google_drive_path) {
scan_service_ = std::make_unique<ScanService>(
&fake_lorgnette_scanner_manager_, my_files_path, google_drive_path,
profile_);
if (scan_service_remote_.is_bound()) {
scan_service_remote_.reset();
}
scan_service_->BindInterface(
scan_service_remote_.BindNewPipeAndPassReceiver());
}
// Gets scanners by calling ScanService::GetScanners() via the mojo::Remote.
std::vector<mojo_ipc::ScannerPtr> GetScanners() {
base::test::TestFuture<std::vector<mojo_ipc::ScannerPtr>> future;
scan_service_remote_->GetScanners(future.GetCallback());
return future.Take();
}
// Gets scanner capabilities for the scanner identified by |scanner_id| by
// calling ScanService::GetScannerCapabilities() via the mojo::Remote.
mojo_ipc::ScannerCapabilitiesPtr GetScannerCapabilities(
const base::UnguessableToken& scanner_id) {
base::test::TestFuture<mojo_ipc::ScannerCapabilitiesPtr> future;
scan_service_remote_->GetScannerCapabilities(scanner_id,
future.GetCallback());
return future.Take();
}
// Starts a scan with the scanner identified by |scanner_id| with the given
// |settings| by calling ScanService::StartScan() via the mojo::Remote.
bool StartScan(const base::UnguessableToken& scanner_id,
mojo_ipc::ScanSettingsPtr settings) {
base::test::TestFuture<bool> future;
scan_service_remote_->StartScan(scanner_id, std::move(settings),
fake_scan_job_observer_.GenerateRemote(),
future.GetCallback());
bool success = future.Take();
task_environment_.RunUntilIdle();
return success;
}
// Starts a multi-page scan with the scanner identified by |scanner_id| with
// the given |settings| by calling ScanService::StartMultiPageScan() via the
// mojo::Remote. Binds the returned MultiPageScanController
// mojo::PendingRemote.
bool StartMultiPageScan(const base::UnguessableToken& scanner_id,
mojo_ipc::ScanSettingsPtr settings) {
base::test::TestFuture<
mojo::PendingRemote<mojo_ipc::MultiPageScanController>>
future;
scan_service_remote_->StartMultiPageScan(
scanner_id, std::move(settings),
fake_scan_job_observer_.GenerateRemote(), future.GetCallback());
auto pending_remote = future.Take();
if (!pending_remote.is_valid())
return false;
multi_page_scan_controller_remote_.Bind(std::move(pending_remote));
task_environment_.RunUntilIdle();
return true;
}
void ResetMultiPageScanControllerRemote() {
multi_page_scan_controller_remote_.reset();
}
bool ScanNextPage(const base::UnguessableToken& scanner_id,
mojo_ipc::ScanSettingsPtr settings) {
base::test::TestFuture<bool> future;
multi_page_scan_controller_remote_->ScanNextPage(
scanner_id, std::move(settings), future.GetCallback());
bool success = future.Take();
task_environment_.RunUntilIdle();
return success;
}
// Performs a cancel scan request.
void CancelScan() {
scan_service_remote_->CancelScan();
task_environment_.RunUntilIdle();
}
void CompleteMultiPageScan() {
multi_page_scan_controller_remote_->CompleteMultiPageScan();
task_environment_.RunUntilIdle();
}
void RemovePage(const uint32_t page_index) {
multi_page_scan_controller_remote_->RemovePage(page_index);
task_environment_.RunUntilIdle();
}
bool RescanPage(const base::UnguessableToken& scanner_id,
mojo_ipc::ScanSettingsPtr settings,
const uint32_t page_index) {
base::test::TestFuture<bool> future;
multi_page_scan_controller_remote_->RescanPage(
scanner_id, std::move(settings), page_index, future.GetCallback());
bool success = future.Take();
task_environment_.RunUntilIdle();
return success;
}
protected:
// A `BrowserTaskEnvironment` allows the test to create a `TestingProfile`.
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
FakeLorgnetteScannerManager fake_lorgnette_scanner_manager_;
FakeScanJobObserver fake_scan_job_observer_;
std::unique_ptr<TestingProfileManager> profile_manager_;
const raw_ptr<TestingProfile> profile_;
std::unique_ptr<ScopedTestMountPoint> scanned_files_mount_;
std::unique_ptr<TestSessionController> session_controller_;
const raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> user_manager_;
user_manager::ScopedUserManager user_manager_owner_;
std::unique_ptr<ScanService> scan_service_;
private:
mojo::Remote<mojo_ipc::ScanService> scan_service_remote_;
mojo::Remote<mojo_ipc::MultiPageScanController>
multi_page_scan_controller_remote_;
};
// Test that no scanners are returned when there are no scanner names.
TEST_F(ScanServiceTest, NoScannerNames) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse({});
auto scanners = GetScanners();
EXPECT_TRUE(scanners.empty());
}
// Test that a scanner is returned with the correct display name.
TEST_F(ScanServiceTest, GetScanners) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
EXPECT_EQ(scanners[0]->display_name, kFirstTestScannerName16);
}
// Test that two returned scanners have unique IDs.
TEST_F(ScanServiceTest, UniqueScannerIds) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName, kSecondTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 2u);
EXPECT_EQ(scanners[0]->display_name, kFirstTestScannerName16);
EXPECT_EQ(scanners[1]->display_name, kSecondTestScannerName16);
EXPECT_NE(scanners[0]->id, scanners[1]->id);
}
// Test that the number of detected scanners is recorded.
TEST_F(ScanServiceTest, RecordNumDetectedScanners) {
base::HistogramTester histogram_tester;
histogram_tester.ExpectTotalCount("Scanning.NumDetectedScanners", 0);
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName, kSecondTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 2u);
histogram_tester.ExpectUniqueSample("Scanning.NumDetectedScanners", 2, 1);
}
// Test that attempting to get capabilities with a scanner ID that doesn't
// correspond to a scanner results in obtaining no capabilities.
TEST_F(ScanServiceTest, BadScannerId) {
auto caps = GetScannerCapabilities(base::UnguessableToken::Create());
EXPECT_TRUE(caps->sources.empty());
}
// Test that failing to obtain capabilities from the LorgnetteScannerManager
// results in obtaining no capabilities.
TEST_F(ScanServiceTest, NoCapabilities) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
fake_lorgnette_scanner_manager_.SetGetScannerCapabilitiesResponse(
std::nullopt);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
auto caps = GetScannerCapabilities(scanners[0]->id);
EXPECT_TRUE(caps->sources.empty());
}
// Test that scanner capabilities can be obtained successfully.
TEST_F(ScanServiceTest, GetScannerCapabilities) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
fake_lorgnette_scanner_manager_.SetGetScannerCapabilitiesResponse(
CreateLorgnetteScannerCapabilities());
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
auto caps = GetScannerCapabilities(scanners[0]->id);
ASSERT_EQ(caps->sources.size(), 1u);
EXPECT_EQ(caps->sources[0]->type, mojo_ipc::SourceType::kFlatbed);
EXPECT_EQ(caps->sources[0]->name, kDocumentSourceName);
ASSERT_EQ(caps->sources[0]->color_modes.size(), 1u);
EXPECT_EQ(caps->sources[0]->color_modes[0], mojo_ipc::ColorMode::kColor);
ASSERT_EQ(caps->sources[0]->resolutions.size(), 2u);
EXPECT_EQ(caps->sources[0]->resolutions[0], kFirstResolution);
EXPECT_EQ(caps->sources[0]->resolutions[1], kSecondResolution);
}
// Test that attempting to scan with a scanner ID that doesn't correspond to a
// scanner results in a failed scan.
TEST_F(ScanServiceTest, ScanWithBadScannerId) {
EXPECT_FALSE(StartScan(base::UnguessableToken::Create(),
mojo_ipc::ScanSettings::New()));
}
// Test that attempting to scan with an unsupported file path fails.
// Specifically, use a file path with directory navigation (e.g. "..") to verify
// it can't be used to save scanned images to an unsupported path.
TEST_F(ScanServiceTest, ScanWithUnsupportedFilePath) {
const base::FilePath my_files_path(kMyFilesPath);
SetupScanService(my_files_path, base::FilePath("/google/drive"));
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
const std::vector<std::string> scan_data = {"TestData"};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
const mojo_ipc::ScanSettings settings = CreateScanSettings(
my_files_path.Append("../../../var/log"), mojo_ipc::FileType::kPng);
EXPECT_FALSE(StartScan(scanners[0]->id, settings.Clone()));
}
// Test that a scan can be performed successfully.
TEST_F(ScanServiceTest, Scan) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
const std::vector<std::string> scan_data = {CreateJpeg(), CreateJpeg(),
CreateJpeg()};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
const auto now = base::Time::Now();
base::HistogramTester histogram_tester;
int num_single_file_scans = 0u;
int num_multi_file_scans = 0u;
for (int type_num = static_cast<int>(mojo_ipc::FileType::kMinValue);
type_num <= static_cast<int>(mojo_ipc::FileType::kMaxValue);
++type_num) {
auto type = static_cast<mojo_ipc::FileType>(type_num);
const std::vector<base::FilePath> saved_scan_paths = CreateSavedScanPaths(
scanned_files_mount_->GetRootPath(), now, type, scan_data.size());
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_FALSE(base::PathExists(saved_scan_path));
mojo_ipc::ScanSettings settings =
CreateScanSettings(scanned_files_mount_->GetRootPath(), type);
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_TRUE(base::PathExists(saved_scan_path));
EXPECT_TRUE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_NO_FAILURE,
fake_scan_job_observer_.scan_result());
EXPECT_EQ(scan_data.size() - 1, fake_scan_job_observer_.new_page_index());
EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
// Verify that the histograms have been updated correctly.
histogram_tester.ExpectBucketCount(
"Scanning.NumFilesCreated", saved_scan_paths.size(),
type == mojo_ipc::FileType::kPdf ? ++num_single_file_scans
: ++num_multi_file_scans);
histogram_tester.ExpectUniqueSample(
"Scanning.NumPagesScanned", scan_data.size(),
num_single_file_scans + num_multi_file_scans);
}
}
// Test that an Epson ADF Duplex scan, which produces flipped pages, completes
// successfully.
TEST_F(ScanServiceTest, RotateEpsonADF) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse({kEpsonTestName});
fake_lorgnette_scanner_manager_.SetGetScannerCapabilitiesResponse(
CreateEpsonScannerCapabilities());
const std::vector<std::string> scan_data = {CreateJpeg(), CreateJpeg(),
CreateJpeg()};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
mojo_ipc::ScanSettings settings =
CreateScanSettings(scanned_files_mount_->GetRootPath(),
mojo_ipc::FileType::kPdf, "ADF Duplex");
const base::FilePath saved_scan_path = CreateSavedPdfScanPath(
scanned_files_mount_->GetRootPath(), base::Time::Now());
EXPECT_FALSE(base::PathExists(saved_scan_path));
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
EXPECT_TRUE(base::PathExists(saved_scan_path));
EXPECT_TRUE(fake_scan_job_observer_.scan_success());
const std::vector<base::FilePath> scanned_file_paths =
fake_scan_job_observer_.scanned_file_paths();
EXPECT_EQ(1u, scanned_file_paths.size());
EXPECT_EQ(scan_data.size() - 1, fake_scan_job_observer_.new_page_index());
EXPECT_EQ(saved_scan_path, scanned_file_paths.front());
}
// Test that when a scan fails, the scan job is marked as failed.
TEST_F(ScanServiceTest, ScanFails) {
// Skip setting the scan data in FakeLorgnetteScannerManager so the scan will
// fail.
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
const mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPng);
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
EXPECT_FALSE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_DEVICE_BUSY,
fake_scan_job_observer_.scan_result());
EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
}
// Tests that a new scan job can succeed after the previous scan failed.
TEST_F(ScanServiceTest, ScanAfterFailedScan) {
// Skip setting the scan data in FakeLorgnetteScannerManager so the scan will
// fail.
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
const mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPng);
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
EXPECT_FALSE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_DEVICE_BUSY,
fake_scan_job_observer_.scan_result());
EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
// Set scan data so next scan is successful.
const std::vector<std::string> scan_data = {"TestData1", "TestData2",
"TestData3"};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
const std::vector<base::FilePath> saved_scan_paths = CreateSavedScanPaths(
scanned_files_mount_->GetRootPath(), base::Time::Now(),
mojo_ipc::FileType::kPng, scan_data.size());
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_FALSE(base::PathExists(saved_scan_path));
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_TRUE(base::PathExists(saved_scan_path));
EXPECT_TRUE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_NO_FAILURE,
fake_scan_job_observer_.scan_result());
EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
EXPECT_EQ(scan_data.size() - 1, fake_scan_job_observer_.new_page_index());
}
// Tests that a failed scan does not retain values from the previous successful
// scan.
TEST_F(ScanServiceTest, FailedScanAfterSuccessfulScan) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
const std::vector<std::string> scan_data = {"TestData1", "TestData2",
"TestData3"};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
const mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPng);
const std::vector<base::FilePath> saved_scan_paths = CreateSavedScanPaths(
scanned_files_mount_->GetRootPath(), base::Time::Now(),
mojo_ipc::FileType::kPng, scan_data.size());
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_FALSE(base::PathExists(saved_scan_path));
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_TRUE(base::PathExists(saved_scan_path));
EXPECT_TRUE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_NO_FAILURE,
fake_scan_job_observer_.scan_result());
EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
EXPECT_EQ(scan_data.size() - 1, fake_scan_job_observer_.new_page_index());
// Remove the scan data from FakeLorgnetteScannerManager so the scan will
// fail.
fake_lorgnette_scanner_manager_.SetScanResponse({});
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
EXPECT_FALSE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_DEVICE_BUSY,
fake_scan_job_observer_.scan_result());
EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
}
// Test that canceling sends an update to the observer OnCancelComplete().
TEST_F(ScanServiceTest, CancelScanBeforeScanCompletes) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
const std::vector<std::string> scan_data = {"TestData"};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
const mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPng);
StartScan(scanners[0]->id, settings.Clone());
CancelScan();
EXPECT_TRUE(fake_scan_job_observer_.cancel_scan_success());
}
// Test that a multi-page image scan creates a holding space item.
TEST_F(ScanServiceTest, HoldingSpaceScan) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
const std::vector<std::string> scan_data = {CreateJpeg(), CreateJpeg(),
CreateJpeg()};
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
// Verify that the holding space starts out empty.
HoldingSpaceKeyedService* holding_space_keyed_service =
HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(profile_);
ASSERT_TRUE(holding_space_keyed_service);
const HoldingSpaceModel* holding_space_model =
holding_space_keyed_service->model_for_testing();
ASSERT_TRUE(holding_space_model);
size_t num_items_in_holding_space = 0u;
ASSERT_EQ(num_items_in_holding_space, holding_space_model->items().size());
const auto now = base::Time::Now();
for (int type_num = static_cast<int>(mojo_ipc::FileType::kMinValue);
type_num <= static_cast<int>(mojo_ipc::FileType::kMaxValue);
++type_num) {
auto type = static_cast<mojo_ipc::FileType>(type_num);
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
const std::vector<base::FilePath> saved_scan_paths = CreateSavedScanPaths(
scanned_files_mount_->GetRootPath(), now, type, scan_data.size());
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_FALSE(base::PathExists(saved_scan_path));
mojo_ipc::ScanSettings settings =
CreateScanSettings(scanned_files_mount_->GetRootPath(), type);
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
EXPECT_TRUE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_NO_FAILURE,
fake_scan_job_observer_.scan_result());
EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
// Verify that the all pages of each scan are added to the holding space.
EXPECT_EQ(num_items_in_holding_space + saved_scan_paths.size(),
holding_space_model->items().size());
for (const auto& saved_scan_path : saved_scan_paths) {
EXPECT_TRUE(base::PathExists(saved_scan_path));
HoldingSpaceItem* scanned_item =
holding_space_model->items()[num_items_in_holding_space++].get();
EXPECT_EQ(scanned_item->type(), HoldingSpaceItem::Type::kScan);
EXPECT_EQ(scanned_item->file().file_path, saved_scan_path);
}
// Remove the scan data from FakeLorgnetteScannerManager so the scan will
// fail.
fake_lorgnette_scanner_manager_.SetScanResponse({});
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
EXPECT_FALSE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_DEVICE_BUSY,
fake_scan_job_observer_.scan_result());
EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
// Verify that no item is added to the holding space when a scan fails.
EXPECT_EQ(num_items_in_holding_space, holding_space_model->items().size());
}
}
// Test that a multi-page scan can be performed successfully.
TEST_F(ScanServiceTest, MultiPageScan) {
base::HistogramTester histogram_tester;
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
const std::vector<std::string> scan_data = {CreateJpeg()};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
const std::vector<base::FilePath> saved_scan_paths = CreateSavedScanPaths(
scanned_files_mount_->GetRootPath(), base::Time::Now(),
mojo_ipc::FileType::kPdf, scan_data.size());
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_FALSE(base::PathExists(saved_scan_path));
mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPdf);
uint32_t new_page_index = 0;
// Scan the first page without completing the scan.
EXPECT_TRUE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_FALSE(base::PathExists(saved_scan_path));
EXPECT_FALSE(fake_scan_job_observer_.scan_success());
EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
// Scan the second page without completing the scan.
EXPECT_TRUE(ScanNextPage(scanners[0]->id, settings.Clone()));
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_FALSE(base::PathExists(saved_scan_path));
EXPECT_FALSE(fake_scan_job_observer_.scan_success());
EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
// Complete the multi-page scan expecting 2 pages to be scanned and a single
// PDF to be created.
CompleteMultiPageScan();
for (const auto& saved_scan_path : saved_scan_paths)
EXPECT_TRUE(base::PathExists(saved_scan_path));
EXPECT_TRUE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
histogram_tester.ExpectUniqueSample("Scanning.NumPagesScanned", 2, 1);
histogram_tester.ExpectUniqueSample("Scanning.MultiPageScan.NumPagesScanned",
2, 1);
histogram_tester.ExpectUniqueSample("Scanning.MultiPageScan.PageScanResult",
scanning::ScanJobFailureReason::kSuccess,
2);
}
// Test that when a multi-page scan fails, the scan job is marked as failed.
TEST_F(ScanServiceTest, MultiPageScanFails) {
base::HistogramTester histogram_tester;
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
const std::vector<std::string> scan_data = {CreateJpeg()};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPdf);
// The first scan should pass with no failure.
EXPECT_TRUE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
EXPECT_FALSE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_UNKNOWN,
fake_scan_job_observer_.multi_page_scan_result());
EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
EXPECT_EQ(0u, fake_scan_job_observer_.new_page_index());
// Set scan data to empty vector in FakeLorgnetteScannerManager so the next
// scan will fail.
fake_lorgnette_scanner_manager_.SetScanResponse({});
EXPECT_TRUE(ScanNextPage(scanners[0]->id, settings.Clone()));
EXPECT_FALSE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_DEVICE_BUSY,
fake_scan_job_observer_.multi_page_scan_result());
EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
histogram_tester.ExpectBucketCount("Scanning.MultiPageScan.PageScanResult",
scanning::ScanJobFailureReason::kSuccess,
1);
histogram_tester.ExpectBucketCount(
"Scanning.MultiPageScan.PageScanResult",
scanning::ScanJobFailureReason::kDeviceBusy, 1);
}
// Test that attempting to start a second multi-page scan while another
// multi-page scan session is going will fail.
TEST_F(ScanServiceTest, StartingAnotherMultiPageScan) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
const std::vector<std::string> scan_data = {CreateJpeg()};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPdf);
// The first scan should pass with no failure.
EXPECT_TRUE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
EXPECT_FALSE(fake_scan_job_observer_.scan_success());
EXPECT_EQ(ProtoScanFailureMode::SCAN_FAILURE_MODE_UNKNOWN,
fake_scan_job_observer_.multi_page_scan_result());
EXPECT_TRUE(fake_scan_job_observer_.scanned_file_paths().empty());
EXPECT_EQ(0u, fake_scan_job_observer_.new_page_index());
// The second attempt should fail.
EXPECT_FALSE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
}
// Test that a page can be removed from a multi-page scan with two scanned
// images.
TEST_F(ScanServiceTest, MultiPageScanRemoveWithTwoPages) {
base::HistogramTester histogram_tester;
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPdf);
uint32_t new_page_index = 0;
const std::string first_scanned_image = CreateJpeg(/*alpha=*/1);
const std::vector<std::string> first_scan_data = {first_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(first_scan_data);
EXPECT_TRUE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
const std::string second_scanned_image = CreateJpeg(/*alpha=*/2);
const std::vector<std::string> second_scan_data = {second_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(second_scan_data);
EXPECT_TRUE(ScanNextPage(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
// Delete the first page.
RemovePage(0);
CompleteMultiPageScan();
const std::vector<std::string> scanned_images =
scan_service_->GetScannedImagesForTesting();
EXPECT_EQ(1u, scanned_images.size());
EXPECT_EQ(second_scanned_image, scanned_images[0]);
// Expect 1 record of the Scanning.NumPagesScanned metric in the 1 pages
// scanned bucket.
histogram_tester.ExpectUniqueSample("Scanning.NumPagesScanned", 1, 1);
histogram_tester.ExpectUniqueSample("Scanning.MultiPageScan.NumPagesScanned",
1, 1);
histogram_tester.ExpectUniqueSample(
"Scanning.MultiPageScan.ToolbarAction",
scanning::ScanMultiPageToolbarAction::kRemovePage, 1);
}
// Test that a page can be removed from a multi-page scan with three scanned
// images.
TEST_F(ScanServiceTest, MultiPageScanRemoveWithThreePages) {
base::HistogramTester histogram_tester;
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPdf);
uint32_t new_page_index = 0;
const std::string first_scanned_image = CreateJpeg(/*alpha=*/1);
const std::vector<std::string> first_scan_data = {first_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(first_scan_data);
EXPECT_TRUE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
const std::string second_scanned_image = CreateJpeg(/*alpha=*/2);
const std::vector<std::string> second_scan_data = {second_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(second_scan_data);
EXPECT_TRUE(ScanNextPage(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
const std::string third_scanned_image = CreateJpeg(/*alpha=*/3);
const std::vector<std::string> third_scan_data = {third_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(third_scan_data);
EXPECT_TRUE(ScanNextPage(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
// Delete the second page.
RemovePage(1);
CompleteMultiPageScan();
const std::vector<std::string> scanned_images =
scan_service_->GetScannedImagesForTesting();
EXPECT_EQ(2u, scanned_images.size());
EXPECT_EQ(first_scanned_image, scanned_images[0]);
EXPECT_EQ(third_scanned_image, scanned_images[1]);
// Expect 1 record of the Scanning.NumPagesScanned metric in the 2 pages
// scanned bucket.
histogram_tester.ExpectUniqueSample("Scanning.NumPagesScanned", 2, 1);
histogram_tester.ExpectUniqueSample("Scanning.MultiPageScan.NumPagesScanned",
2, 1);
histogram_tester.ExpectUniqueSample(
"Scanning.MultiPageScan.ToolbarAction",
scanning::ScanMultiPageToolbarAction::kRemovePage, 1);
}
// Test that if there's only one page available, the page is removed and the
// multi-page scan session is reset and a new session can be started.
TEST_F(ScanServiceTest, MultiPageScanRemoveLastPage) {
base::HistogramTester histogram_tester;
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
const std::vector<std::string> scan_data = {CreateJpeg()};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPdf);
uint32_t new_page_index = 0;
EXPECT_TRUE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
// Removing the page should reset the multi-page scan session.
RemovePage(0);
--new_page_index;
// Start a new scan and complete it with 1 page.
ResetMultiPageScanControllerRemote();
EXPECT_TRUE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
CompleteMultiPageScan();
const std::vector<std::string> scanned_images =
scan_service_->GetScannedImagesForTesting();
EXPECT_EQ(1u, scanned_images.size());
// Expect 1 record of the Scanning.NumPagesScanned metric in the 1 page
// scanned bucket.
histogram_tester.ExpectUniqueSample("Scanning.NumPagesScanned", 1, 1);
histogram_tester.ExpectUniqueSample("Scanning.MultiPageScan.NumPagesScanned",
1, 1);
histogram_tester.ExpectUniqueSample(
"Scanning.MultiPageScan.ToolbarAction",
scanning::ScanMultiPageToolbarAction::kRemovePage, 1);
}
// Test that a page can be rescanned and replaced from a multi-page scan with
// one scanned image.
TEST_F(ScanServiceTest, MultiPageScanRescanWithOnePage) {
base::HistogramTester histogram_tester;
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPdf);
uint32_t new_page_index = 0;
const std::string first_scanned_image = CreateJpeg(/*alpha=*/1);
const std::vector<std::string> first_scan_data = {first_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(first_scan_data);
EXPECT_TRUE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
// Rescan the page.
const std::string rescanned_scanned_image = CreateJpeg(/*alpha=*/2);
const std::vector<std::string> rescanned_scan_data = {
rescanned_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(rescanned_scan_data);
EXPECT_TRUE(RescanPage(scanners[0]->id, settings.Clone(), /*page_index=*/0));
EXPECT_EQ(0u, fake_scan_job_observer_.new_page_index());
CompleteMultiPageScan();
const std::vector<std::string> scanned_images =
scan_service_->GetScannedImagesForTesting();
EXPECT_EQ(1u, scanned_images.size());
EXPECT_EQ(rescanned_scanned_image, scanned_images[0]);
// Expect 1 record of the Scanning.NumPagesScanned metric in the 1 pages
// scanned bucket.
histogram_tester.ExpectUniqueSample("Scanning.NumPagesScanned", 1, 1);
histogram_tester.ExpectUniqueSample("Scanning.MultiPageScan.NumPagesScanned",
1, 1);
histogram_tester.ExpectUniqueSample(
"Scanning.MultiPageScan.ToolbarAction",
scanning::ScanMultiPageToolbarAction::kRescanPage, 1);
}
// Test that a page can be rescanned and replaced from a multi-page scan with
// three scanned images.
TEST_F(ScanServiceTest, MultiPageScanRescanWithThreePages) {
base::HistogramTester histogram_tester;
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPdf);
uint32_t new_page_index = 0;
const std::string first_scanned_image = CreateJpeg(/*alpha=*/1);
const std::vector<std::string> first_scan_data = {first_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(first_scan_data);
EXPECT_TRUE(StartMultiPageScan(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
const std::string second_scanned_image = CreateJpeg(/*alpha=*/2);
const std::vector<std::string> second_scan_data = {second_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(second_scan_data);
EXPECT_TRUE(ScanNextPage(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
const std::string third_scanned_image = CreateJpeg(/*alpha=*/3);
const std::vector<std::string> third_scan_data = {third_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(third_scan_data);
EXPECT_TRUE(ScanNextPage(scanners[0]->id, settings.Clone()));
EXPECT_EQ(new_page_index++, fake_scan_job_observer_.new_page_index());
// Rescan the second page.
const std::string rescanned_scanned_image = CreateJpeg(/*alpha=*/4);
const std::vector<std::string> rescanned_scan_data = {
rescanned_scanned_image};
fake_lorgnette_scanner_manager_.SetScanResponse(rescanned_scan_data);
EXPECT_TRUE(RescanPage(scanners[0]->id, settings.Clone(), /*page_index=*/1));
EXPECT_EQ(1u, fake_scan_job_observer_.new_page_index());
CompleteMultiPageScan();
const std::vector<std::string> scanned_images =
scan_service_->GetScannedImagesForTesting();
EXPECT_EQ(3u, scanned_images.size());
EXPECT_EQ(first_scanned_image, scanned_images[0]);
EXPECT_EQ(rescanned_scanned_image, scanned_images[1]);
EXPECT_EQ(third_scanned_image, scanned_images[2]);
// Expect 1 record of the Scanning.NumPagesScanned metric in the 3 pages
// scanned bucket.
histogram_tester.ExpectUniqueSample("Scanning.NumPagesScanned", 3, 1);
histogram_tester.ExpectUniqueSample("Scanning.MultiPageScan.NumPagesScanned",
3, 1);
histogram_tester.ExpectUniqueSample(
"Scanning.MultiPageScan.ToolbarAction",
scanning::ScanMultiPageToolbarAction::kRescanPage, 1);
}
TEST_F(ScanServiceTest, ResetReceiverOnBindInterface) {
// This test simulates a user refreshing the WebUI page. The receiver should
// be reset before binding the new receiver. Otherwise we would get a DCHECK
// error from mojo::Receiver
mojo::Remote<scanning::mojom::ScanService> remote;
scan_service_->BindInterface(remote.BindNewPipeAndPassReceiver());
base::RunLoop().RunUntilIdle();
remote.reset();
scan_service_->BindInterface(remote.BindNewPipeAndPassReceiver());
base::RunLoop().RunUntilIdle();
}
// TODO(b:307385730): Parameterize this test once more settings combinations
// are added.
TEST_F(ScanServiceTest, ScanDataSettings) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
// Settings correspond to "flatbed_jpeg_color_letter_300_dpi"
// which sets the `alpha` used in the generated JPEG images to
// 1.
mojo_ipc::ScanSettings settings = CreateScanSettings(
scanned_files_mount_->GetRootPath(), mojo_ipc::FileType::kPdf, "flatbed",
mojo_ipc::ColorMode::kColor, mojo_ipc::PageSize::kNaLetter,
kSecondResolution);
EXPECT_TRUE(StartScan(scanners[0]->id, settings.Clone()));
EXPECT_EQ(1u, scan_service_->GetScannedImagesForTesting().size());
EXPECT_EQ(CreateJpeg(/*alpha=*/1),
scan_service_->GetScannedImagesForTesting()[0]);
}
} // namespace ash