blob: 9d13ad3d66cd2669237f1d1d805726b7bc2921a4 [file] [log] [blame]
// 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/ui/webui/print_preview/print_preview_handler_chromeos.h"
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "chrome/browser/ash/crosapi/test_crosapi_dependency_registry.h"
#include "chrome/browser/printing/print_test_utils.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_handler.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h"
#include "chrome/browser/ui/webui/print_preview/print_preview_utils.h"
#include "chrome/browser/ui/webui/print_preview/printer_handler.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/chromeos/printing/fake_local_printer_chromeos.h"
#include "chromeos/crosapi/mojom/local_printer.mojom.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_web_ui.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/idle_service_ash.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#endif
namespace printing {
namespace {
std::vector<crosapi::mojom::LocalDestinationInfoPtr>
ConvertToLocalDestinationInfo(std::vector<std::string> printer_ids) {
std::vector<crosapi::mojom::LocalDestinationInfoPtr> local_printers;
for (const auto& printer_id : printer_ids) {
crosapi::mojom::LocalDestinationInfoPtr local_printer =
crosapi::mojom::LocalDestinationInfo::New();
local_printer->id = printer_id;
local_printers.push_back(std::move(local_printer));
}
return local_printers;
}
} // namespace
const char kSelectedPrintServerId[] = "selected-print-server-id";
const char kSelectedPrintServerName[] = "Print Server Name";
class TestLocalPrinter : public FakeLocalPrinter {
public:
TestLocalPrinter() = default;
TestLocalPrinter(const TestLocalPrinter&) = delete;
TestLocalPrinter& operator=(const TestLocalPrinter&) = delete;
~TestLocalPrinter() override { EXPECT_FALSE(print_server_ids_); }
std::vector<std::string> TakePrintServerIds() {
std::vector<std::string> print_server_ids = std::move(*print_server_ids_);
print_server_ids_.reset();
return print_server_ids;
}
// crosapi::mojom::LocalPrinter:
void ChoosePrintServers(const std::vector<std::string>& print_server_ids,
ChoosePrintServersCallback callback) override {
EXPECT_FALSE(print_server_ids_);
print_server_ids_ = print_server_ids;
}
void AddPrintServerObserver(
mojo::PendingRemote<crosapi::mojom::PrintServerObserver> remote,
AddPrintServerObserverCallback callback) override {
EXPECT_FALSE(remote_);
EXPECT_TRUE(remote);
remote_ =
mojo::Remote<crosapi::mojom::PrintServerObserver>(std::move(remote));
std::move(callback).Run();
}
void GetPrintServersConfig(GetPrintServersConfigCallback callback) override {
ASSERT_TRUE(config_);
std::move(callback).Run(std::move(config_));
config_ = nullptr;
}
void AddLocalPrintersObserver(
mojo::PendingRemote<crosapi::mojom::LocalPrintersObserver> remote,
AddLocalPrintersObserverCallback callback) override {
std::move(callback).Run(std::move(local_printers_));
}
void SetLocalPrinters(std::vector<std::string> printer_ids) {
local_printers_ = ConvertToLocalDestinationInfo(printer_ids);
}
private:
friend class PrintPreviewHandlerChromeOSTest;
std::vector<crosapi::mojom::LocalDestinationInfoPtr> local_printers_;
mojo::Remote<crosapi::mojom::PrintServerObserver> remote_;
std::optional<std::vector<std::string>> print_server_ids_;
crosapi::mojom::PrintServersConfigPtr config_;
};
class FakePrintPreviewUI : public PrintPreviewUI {
public:
FakePrintPreviewUI(content::WebUI* web_ui,
std::unique_ptr<PrintPreviewHandler> handler)
: PrintPreviewUI(web_ui, std::move(handler)) {}
FakePrintPreviewUI(const FakePrintPreviewUI&) = delete;
FakePrintPreviewUI& operator=(const FakePrintPreviewUI&) = delete;
~FakePrintPreviewUI() override = default;
private:
};
struct PrinterInfo {
std::string id;
bool is_default;
base::Value::Dict basic_info;
base::Value::Dict capabilities;
};
class TestPrinterHandlerChromeOS : public PrinterHandler {
public:
explicit TestPrinterHandlerChromeOS(
const std::vector<PrinterInfo>& printers) {
SetPrinters(printers);
}
TestPrinterHandlerChromeOS(const TestPrinterHandlerChromeOS&) = delete;
TestPrinterHandlerChromeOS& operator=(const TestPrinterHandlerChromeOS&) =
delete;
~TestPrinterHandlerChromeOS() override = default;
void Reset() override {}
void GetDefaultPrinter(DefaultPrinterCallback cb) override {
std::move(cb).Run(default_printer_);
}
void StartGetPrinters(AddedPrintersCallback added_printers_callback,
GetPrintersDoneCallback done_callback) override {
if (!printers_.empty()) {
added_printers_callback.Run(printers_.Clone());
}
std::move(done_callback).Run();
}
void StartGetCapability(const std::string& destination_id,
GetCapabilityCallback callback) override {
std::move(callback).Run(printer_capabilities_[destination_id].Clone());
}
void StartGrantPrinterAccess(const std::string& printer_id,
GetPrinterInfoCallback callback) override {}
void StartPrint(const std::u16string& job_title,
base::Value::Dict settings,
scoped_refptr<base::RefCountedMemory> print_data,
PrintCallback callback) override {
std::move(callback).Run(base::Value());
}
void SetPrinters(const std::vector<PrinterInfo>& printers) {
printers_.clear();
for (const auto& printer : printers) {
if (printer.is_default) {
default_printer_ = printer.id;
}
printers_.Append(printer.basic_info.Clone());
printer_capabilities_[printer.id] = printer.capabilities.Clone();
}
}
private:
std::string default_printer_;
base::Value::List printers_;
std::map<std::string, base::Value::Dict> printer_capabilities_;
};
class TestPrintPreviewHandlerChromeOS : public PrintPreviewHandlerChromeOS {
public:
explicit TestPrintPreviewHandlerChromeOS(
std::unique_ptr<PrinterHandler> printer_handler)
: test_printer_handler_(std::move(printer_handler)) {}
PrinterHandler* GetPrinterHandler(mojom::PrinterType printer_type) override {
return test_printer_handler_.get();
}
private:
std::unique_ptr<PrinterHandler> test_printer_handler_;
};
PrinterInfo GetSimplePrinterInfo(const std::string& name, bool is_default) {
PrinterInfo simple_printer;
simple_printer.id = name;
simple_printer.is_default = is_default;
simple_printer.basic_info.Set("printer_name", simple_printer.id);
simple_printer.basic_info.Set("printer_description", "Printer for test");
simple_printer.basic_info.Set("printer_status", 1);
base::Value::Dict cdd;
simple_printer.capabilities.Set("printer", simple_printer.basic_info.Clone());
simple_printer.capabilities.Set("capabilities", cdd.Clone());
return simple_printer;
}
class PrintPreviewHandlerChromeOSTest : public testing::Test {
public:
PrintPreviewHandlerChromeOSTest() = default;
PrintPreviewHandlerChromeOSTest(const PrintPreviewHandlerChromeOSTest&) =
delete;
PrintPreviewHandlerChromeOSTest& operator=(
const PrintPreviewHandlerChromeOSTest&) = delete;
~PrintPreviewHandlerChromeOSTest() override = default;
void SetUp() override {
#if BUILDFLAG(IS_CHROMEOS_ASH)
ASSERT_TRUE(testing_profile_manager_.SetUp());
crosapi::IdleServiceAsh::DisableForTesting();
ash::LoginState::Initialize();
manager_ = crosapi::CreateCrosapiManagerWithTestRegistry();
#endif
preview_web_contents_ = content::WebContents::Create(
content::WebContents::CreateParams(&profile_));
web_ui_ = std::make_unique<content::TestWebUI>();
web_ui_->set_web_contents(preview_web_contents_.get());
// Create printer handler.
printers_.push_back(
GetSimplePrinterInfo(test::kPrinterName, /*is_default=*/true));
auto printer_handler =
std::make_unique<TestPrinterHandlerChromeOS>(printers_);
printer_handler_ = printer_handler.get();
auto preview_handler = std::make_unique<TestPrintPreviewHandlerChromeOS>(
std::move(printer_handler));
preview_handler->SetInitiatorForTesting(preview_web_contents_.get());
handler_ = preview_handler.get();
local_printer_ = std::make_unique<TestLocalPrinter>();
handler_->local_printer_ = local_printer_.get();
web_ui()->AddMessageHandler(std::move(preview_handler));
handler_->AllowJavascriptForTesting();
auto preview_ui = std::make_unique<FakePrintPreviewUI>(
web_ui(), std::make_unique<PrintPreviewHandler>());
web_ui()->SetController(std::move(preview_ui));
}
void TearDown() override {
#if BUILDFLAG(IS_CHROMEOS_ASH)
manager_.reset();
ash::LoginState::Shutdown();
#endif
}
void DisableAshChrome() {
local_printer_ = nullptr;
handler_->local_printer_ = nullptr;
}
void AssertWebUIEventFired(const content::TestWebUI::CallData& data,
const std::string& event_id) {
EXPECT_EQ("cr.webUIListenerCallback", data.function_name());
ASSERT_TRUE(data.arg1()->is_string());
EXPECT_EQ(event_id, data.arg1()->GetString());
}
content::TestWebUI* web_ui() { return web_ui_.get(); }
void ChangePrintServersConfig(crosapi::mojom::PrintServersConfigPtr config) {
EXPECT_TRUE(local_printer_->remote_);
local_printer_->config_ = config.Clone();
// Call the callback directly instead of through the mojo remote
// so that it is synchronous.
handler_->OnPrintServersChanged(std::move(config));
}
std::vector<std::string> TakePrintServerIds() {
return local_printer_->TakePrintServerIds();
}
void ChangeServerPrinters() { handler_->OnServerPrintersChanged(); }
TestPrinterHandlerChromeOS* printer_handler() { return printer_handler_; }
std::vector<PrinterInfo>& printers() { return printers_; }
void SetLocalPrinters(std::vector<std::string> printer_ids) {
local_printer_->SetLocalPrinters(printer_ids);
}
void FireOnLocalPrintersUpdated(std::vector<std::string> printer_ids) {
handler_->OnLocalPrintersUpdated(
ConvertToLocalDestinationInfo(printer_ids));
}
#if BUILDFLAG(IS_CHROMEOS_LACROS)
int LocalPrinterVersion() {
return handler_->GetLocalPrinterVersionForTesting();
}
#endif
private:
content::BrowserTaskEnvironment task_environment_;
#if BUILDFLAG(IS_CHROMEOS_ASH)
TestingProfileManager testing_profile_manager_{
TestingBrowserProcess::GetGlobal()};
std::unique_ptr<crosapi::CrosapiManager> manager_;
#endif
TestingProfile profile_;
std::unique_ptr<TestLocalPrinter> local_printer_;
std::unique_ptr<content::WebContents> preview_web_contents_;
std::unique_ptr<content::TestWebUI> web_ui_;
raw_ptr<PrintPreviewHandlerChromeOS> handler_;
raw_ptr<TestPrinterHandlerChromeOS> printer_handler_;
std::vector<PrinterInfo> printers_;
};
TEST_F(PrintPreviewHandlerChromeOSTest, ChoosePrintServersNoAsh) {
DisableAshChrome();
base::Value::List selected_args;
base::Value::List selected_ids_js;
selected_ids_js.Append(kSelectedPrintServerId);
selected_args.Append(std::move(selected_ids_js));
web_ui()->HandleReceivedMessage("choosePrintServers", selected_args);
AssertWebUIEventFired(*web_ui()->call_data().back(),
"server-printers-loading");
EXPECT_EQ(web_ui()->call_data().back()->arg2()->GetBool(), true);
}
TEST_F(PrintPreviewHandlerChromeOSTest, GetPrintServersConfigNoAsh) {
DisableAshChrome();
base::Value::List args;
args.Append("callback_id");
web_ui()->HandleReceivedMessage("getPrintServersConfig", args);
EXPECT_EQ("cr.webUIResponse", web_ui()->call_data().back()->function_name());
EXPECT_EQ(base::Value("callback_id"), *web_ui()->call_data().back()->arg1());
EXPECT_EQ(base::Value(true), *web_ui()->call_data().back()->arg2());
EXPECT_EQ(base::Value(), *web_ui()->call_data().back()->arg3());
}
TEST_F(PrintPreviewHandlerChromeOSTest, ChoosePrintServers) {
base::Value::List selected_args;
base::Value::List selected_ids_js;
selected_ids_js.Append(kSelectedPrintServerId);
selected_args.Append(std::move(selected_ids_js));
base::Value::List none_selected_args;
base::Value::List none_selected_js;
none_selected_args.Append(std::move(none_selected_js));
web_ui()->HandleReceivedMessage("choosePrintServers", selected_args);
EXPECT_THAT(TakePrintServerIds(),
testing::ElementsAre(std::string(kSelectedPrintServerId)));
web_ui()->HandleReceivedMessage("choosePrintServers", none_selected_args);
EXPECT_THAT(TakePrintServerIds(), testing::IsEmpty());
AssertWebUIEventFired(*web_ui()->call_data().back(),
"server-printers-loading");
EXPECT_EQ(web_ui()->call_data().back()->arg2()->GetBool(), true);
}
TEST_F(PrintPreviewHandlerChromeOSTest, OnPrintServersChanged) {
std::vector<crosapi::mojom::PrintServerPtr> servers;
servers.push_back(crosapi::mojom::PrintServer::New(
kSelectedPrintServerId, GURL("http://print-server.com"),
kSelectedPrintServerName));
crosapi::mojom::PrintServersConfigPtr config =
crosapi::mojom::PrintServersConfig::New();
config->print_servers = std::move(servers);
config->fetching_mode = ash::ServerPrintersFetchingMode::kStandard;
ChangePrintServersConfig(std::move(config));
auto* call_data = web_ui()->call_data().back().get();
AssertWebUIEventFired(*call_data, "print-servers-config-changed");
const base::Value::List* printer_list =
call_data->arg2()->GetDict().FindList("printServers");
bool is_single_server_fetching_mode =
call_data->arg2()
->GetDict()
.FindBool("isSingleServerFetchingMode")
.value();
ASSERT_EQ(printer_list->size(), 1u);
const base::Value::Dict& first_printer = printer_list->front().GetDict();
EXPECT_EQ(*first_printer.FindString("id"), kSelectedPrintServerId);
EXPECT_EQ(*first_printer.FindString("name"), kSelectedPrintServerName);
EXPECT_EQ(is_single_server_fetching_mode, false);
base::Value::List args;
args.Append("callback_id");
web_ui()->HandleReceivedMessage("getPrintServersConfig", args);
const base::Value kExpectedConfig = base::test::ParseJson(R"({
"isSingleServerFetchingMode": false,
"printServers": [ {
"id": "selected-print-server-id",
"name": "Print Server Name" } ]
})");
EXPECT_EQ("cr.webUIResponse", web_ui()->call_data().back()->function_name());
EXPECT_EQ(base::Value("callback_id"), *web_ui()->call_data().back()->arg1());
EXPECT_EQ(base::Value(true), *web_ui()->call_data().back()->arg2());
EXPECT_EQ(kExpectedConfig, *web_ui()->call_data().back()->arg3());
}
TEST_F(PrintPreviewHandlerChromeOSTest, OnServerPrintersUpdated) {
ChangeServerPrinters();
AssertWebUIEventFired(*web_ui()->call_data().back(),
"server-printers-loading");
EXPECT_EQ(web_ui()->call_data().back()->arg2()->GetBool(), false);
}
TEST_F(PrintPreviewHandlerChromeOSTest, HandlePrinterSetup) {
base::Value::Dict media_1;
media_1.Set("width_microns", 100);
media_1.Set("height_microns", 200);
base::Value::Dict media_2;
media_2.Set("width_microns", 300);
media_2.Set("is_continuous_feed", true);
// After filtering, the expected media will just have the discrete media.
base::Value::List expected_media;
expected_media.Append(media_1.Clone());
base::Value::List option_list;
option_list.Append(std::move(media_1));
option_list.Append(std::move(media_2));
base::Value::Dict media_size;
media_size.Set("option", std::move(option_list));
base::Value::Dict printer;
printer.Set("media_size", std::move(media_size));
base::Value::Dict cdd;
cdd.Set("printer", std::move(printer));
ASSERT_EQ(1u, printers().size());
printers()[0].capabilities.Set(kSettingCapabilities, std::move(cdd));
printer_handler()->SetPrinters(printers());
base::Value::List args;
args.Append("callback_id");
args.Append(test::kPrinterName);
web_ui()->HandleReceivedMessage("setupPrinter", args);
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
EXPECT_EQ("cr.webUIResponse", data.function_name());
ASSERT_TRUE(data.arg1()->is_string());
EXPECT_EQ("callback_id", data.arg1()->GetString());
ASSERT_TRUE(data.arg2()->is_bool());
EXPECT_TRUE(data.arg2()->GetBool());
ASSERT_TRUE(data.arg3()->is_dict());
const base::Value::Dict* cdd_result =
data.arg3()->GetDict().FindDict(kSettingCapabilities);
ASSERT_TRUE(cdd_result);
const base::Value::List* options = GetMediaSizeOptionsFromCdd(*cdd_result);
ASSERT_TRUE(options);
EXPECT_EQ(expected_media, *options);
}
// Verify 'getShowManagePrinters' can be called.
TEST_F(PrintPreviewHandlerChromeOSTest, HandleGetCanShowManagePrinters) {
const std::string callback_id = "callback-id";
base::Value::List args;
args.Append(callback_id);
web_ui()->HandleReceivedMessage("getShowManagePrinters", args);
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
EXPECT_EQ("cr.webUIResponse", data.function_name());
ASSERT_TRUE(data.arg1()->is_string());
EXPECT_EQ(callback_id, data.arg1()->GetString());
ASSERT_TRUE(data.arg2()->is_bool());
ASSERT_TRUE(data.arg2()->GetBool());
}
// Verify 'observeLocalPrinters' can be called.
TEST_F(PrintPreviewHandlerChromeOSTest, HandleObserveLocalPrinters) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (int{crosapi::mojom::LocalPrinter::MethodMinVersions::
kAddLocalPrintersObserverMinVersion} > LocalPrinterVersion()) {
LOG(ERROR) << "Local printer version incompatible";
return;
}
#endif
const std::vector<std::string> printers{"Printer1", "Printer2", "Printer3"};
SetLocalPrinters(printers);
const std::string callback_id = "callback-id";
base::Value::List args;
args.Append(callback_id);
web_ui()->HandleReceivedMessage("observeLocalPrinters", args);
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
EXPECT_EQ("cr.webUIResponse", data.function_name());
ASSERT_TRUE(data.arg1()->is_string());
EXPECT_EQ(callback_id, data.arg1()->GetString());
// True if ResolveJavascriptCallback and false if RejectJavascriptCallback
// is called by the handler.
EXPECT_TRUE(data.arg2()->GetBool());
EXPECT_EQ(printers.size(), data.arg3()->GetList().size());
}
// Verify 'local-printers-updated' is fired when the observer is triggered.
TEST_F(PrintPreviewHandlerChromeOSTest, FireLocalPrintersUpdated) {
const std::vector<std::string> printers{"Printer1", "Printer2", "Printer3"};
FireOnLocalPrintersUpdated(printers);
const content::TestWebUI::CallData& data = *web_ui()->call_data().back();
AssertWebUIEventFired(data, "local-printers-updated");
EXPECT_EQ(printers.size(), data.arg2()->GetList().size());
}
} // namespace printing