blob: 88fa7b13b5c6f59568721cbe1b870adba5fac49e [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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 <string>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/webui/scanning/mojom/scanning.mojom-test-utils.h"
#include "ash/webui/scanning/mojom/scanning.mojom.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.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/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/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/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/abseil-cpp/absl/types/optional.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
namespace ash {
using holding_space::ScopedTestMountPoint;
namespace {
namespace mojo_ipc = scanning::mojom;
// 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[] = "user@email.com";
// 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"},
// Temporarily set searchable pdfs to follow png pipeline while
// implementing.
{mojo_ipc::FileType::kSearchablePdf, "png"}};
// Returns a DocumentSource object.
lorgnette::DocumentSource CreateLorgnetteDocumentSource() {
lorgnette::DocumentSource source;
source.set_type(lorgnette::SOURCE_PLATEN);
source.set_name(kDocumentSourceName);
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();
caps.add_color_modes(lorgnette::MODE_COLOR);
caps.add_resolutions(kFirstResolution);
caps.add_resolutions(kSecondResolution);
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;
}
// Returns single FilePath to mimic saved PDF format scan.
base::FilePath CreateSavedPdfScanPath(const base::FilePath& dir,
const base::Time::Exploded& scan_time) {
return dir.Append(base::StringPrintf("scan_%02d%02d%02d-%02d%02d%02d.pdf",
scan_time.year, scan_time.month,
scan_time.day_of_month, scan_time.hour,
scan_time.minute, scan_time.second));
}
// Returns a vector of FilePaths to mimic saved scans.
std::vector<base::FilePath> CreateSavedScanPaths(
const base::FilePath& dir,
const base::Time::Exploded& 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_%02d%02d%02d-%02d%02d%02d_%d.%s", scan_time.year,
scan_time.month, scan_time.day_of_month, scan_time.hour,
scan_time.minute, scan_time.second, i,
typeAndExtension->second.c_str())));
}
}
return file_paths;
}
// Returns a manually generated PNG image.
std::string CreatePng() {
SkBitmap bitmap;
bitmap.allocN32Pixels(100, 100);
bitmap.eraseARGB(255, 0, 255, 0);
std::vector<unsigned char> bytes;
gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &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::ScanSettings settings;
settings.scan_to_path = scan_to_path;
settings.file_type = file_type;
settings.source_name = source;
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) override {
page_complete_ = true;
}
void OnScanComplete(
mojo_ipc::ScanResult 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;
}
// 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_ == mojo_ipc::ScanResult::kSuccess;
}
// Returns true if the cancel scan request completed successfully.
bool cancel_scan_success() const { return cancel_scan_success_; }
// Returns the result of the scan job.
mojo_ipc::ScanResult scan_result() const { return 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;
mojo_ipc::ScanResult scan_result_ = mojo_ipc::ScanResult::kUnknownError;
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_)) {
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() {
std::vector<mojo_ipc::ScannerPtr> scanners;
mojo_ipc::ScanServiceAsyncWaiter(scan_service_remote_.get())
.GetScanners(&scanners);
return scanners;
}
// 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) {
mojo_ipc::ScannerCapabilitiesPtr caps =
mojo_ipc::ScannerCapabilities::New();
mojo_ipc::ScanServiceAsyncWaiter(scan_service_remote_.get())
.GetScannerCapabilities(scanner_id, &caps);
return caps;
}
// 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) {
bool success;
mojo_ipc::ScanServiceAsyncWaiter(scan_service_remote_.get())
.StartScan(scanner_id, std::move(settings),
fake_scan_job_observer_.GenerateRemote(), &success);
task_environment_.RunUntilIdle();
return success;
}
// Performs a cancel scan request.
void CancelScan() {
scan_service_remote_->CancelScan();
task_environment_.RunUntilIdle();
}
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_;
TestingProfile* const profile_;
std::unique_ptr<ScopedTestMountPoint> scanned_files_mount_;
std::unique_ptr<TestSessionController> session_controller_;
ash::FakeChromeUserManager* const user_manager_;
user_manager::ScopedUserManager user_manager_owner_;
std::unique_ptr<ScanService> scan_service_;
private:
mojo::Remote<mojo_ipc::ScanService> scan_service_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());
EXPECT_TRUE(caps->color_modes.empty());
EXPECT_TRUE(caps->resolutions.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(
absl::nullopt);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
auto caps = GetScannerCapabilities(scanners[0]->id);
EXPECT_TRUE(caps->sources.empty());
EXPECT_TRUE(caps->color_modes.empty());
EXPECT_TRUE(caps->resolutions.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->color_modes.size(), 1u);
EXPECT_EQ(caps->color_modes[0], mojo_ipc::ColorMode::kColor);
ASSERT_EQ(caps->resolutions.size(), 2u);
EXPECT_EQ(caps->resolutions[0], kFirstResolution);
EXPECT_EQ(caps->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 = {CreatePng(), CreatePng(),
CreatePng()};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
base::Time::Exploded scan_time;
// Since we're using mock time, this is deterministic.
base::Time::Now().LocalExplode(&scan_time);
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);
if (type == mojo_ipc::FileType::kSearchablePdf &&
!base::FeatureList::IsEnabled(
chromeos::features::kScanAppSearchablePdf)) {
continue;
}
const std::vector<base::FilePath> saved_scan_paths = CreateSavedScanPaths(
scanned_files_mount_->GetRootPath(), scan_time, 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(mojo_ipc::ScanResult::kSuccess,
fake_scan_job_observer_.scan_result());
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 = {CreatePng(), CreatePng(),
CreatePng()};
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
base::Time::Exploded scan_time;
// Since we're using mock time, this is deterministic.
base::Time::Now().LocalExplode(&scan_time);
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(), scan_time);
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(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(mojo_ipc::ScanResult::kDeviceBusy,
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(mojo_ipc::ScanResult::kDeviceBusy,
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);
base::Time::Exploded scan_time;
// Since we're using mock time, this is deterministic.
base::Time::Now().LocalExplode(&scan_time);
const std::vector<base::FilePath> saved_scan_paths =
CreateSavedScanPaths(scanned_files_mount_->GetRootPath(), scan_time,
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(mojo_ipc::ScanResult::kSuccess,
fake_scan_job_observer_.scan_result());
EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
}
// 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);
base::Time::Exploded scan_time;
// Since we're using mock time, this is deterministic.
base::Time::Now().LocalExplode(&scan_time);
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(), scan_time,
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(mojo_ipc::ScanResult::kSuccess,
fake_scan_job_observer_.scan_result());
EXPECT_EQ(saved_scan_paths, fake_scan_job_observer_.scanned_file_paths());
// 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(mojo_ipc::ScanResult::kDeviceBusy,
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 = {CreatePng(), CreatePng(),
CreatePng()};
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
base::Time::Exploded scan_time;
// Since we're using mock time, this is deterministic.
base::Time::Now().LocalExplode(&scan_time);
// 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());
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);
if (type == mojo_ipc::FileType::kSearchablePdf &&
!base::FeatureList::IsEnabled(
chromeos::features::kScanAppSearchablePdf)) {
continue;
}
fake_lorgnette_scanner_manager_.SetScanResponse(scan_data);
const std::vector<base::FilePath> saved_scan_paths = CreateSavedScanPaths(
scanned_files_mount_->GetRootPath(), scan_time, 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(mojo_ipc::ScanResult::kSuccess,
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_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(mojo_ipc::ScanResult::kDeviceBusy,
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());
}
}
} // namespace ash