// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "chrome/browser/extensions/api/printing/print_job_submitter.h"
#include "chrome/browser/extensions/api/printing/printing_test_utils.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/printing/local_printer_utils_chromeos.h"
#include "chrome/browser/ui/browser.h"
#include "content/public/test/browser_test.h"
#include "extensions/test/test_extension_dir.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "base/test/gmock_callback_support.h"
#include "chrome/browser/extensions/api/printing/fake_print_job_controller.h"
#include "chrome/browser/extensions/api/printing/printing_api_handler.h"
#include "chrome/test/chromeos/printing/mock_local_printer_chromeos.h"
#include "chromeos/crosapi/mojom/local_printer.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "chromeos/printing/printer_configuration.h"
#endif
namespace extensions {
namespace {
constexpr char kId[] = "id";
constexpr char kName[] = "name";
#if BUILDFLAG(IS_CHROMEOS_LACROS)
using testing::_;
using testing::DoAll;
using testing::InSequence;
using testing::NiceMock;
using testing::Return;
using testing::WithArg;
using testing::WithArgs;
using testing::WithoutArgs;
#endif
} // namespace
class PrintingApiTestBase : public ExtensionApiTest,
public testing::WithParamInterface<ExtensionType> {
public:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
PrintJobSubmitter::SkipConfirmationDialogForTesting();
}
protected:
ExtensionType GetExtensionType() const { return GetParam(); }
void RunTest(const char* html_test_page) {
auto dir = CreatePrintingExtension(GetExtensionType());
auto run_options = GetExtensionType() == ExtensionType::kChromeApp
? RunOptions{.custom_arg = html_test_page,
.launch_as_platform_app = true}
: RunOptions({.extension_url = html_test_page});
ASSERT_TRUE(RunExtensionTest(dir->UnpackedPath(), run_options, {}));
}
};
#if BUILDFLAG(IS_CHROMEOS_ASH)
class PrintingApiTest : public PrintingApiTestBase {
public:
void PreRunTestOnMainThread() override {
PrintingApiTestBase::PreRunTestOnMainThread();
helper_->Init(browser()->profile());
}
void TearDownOnMainThread() override {
helper_.reset();
PrintingApiTestBase::TearDownOnMainThread();
}
void SetUpInProcessBrowserTestFixture() override {
PrintingApiTestBase::SetUpInProcessBrowserTestFixture();
helper_ = std::make_unique<PrintingTestHelper>();
}
protected:
void AddPrinterWithSemanticCaps(
const std::string& printer_id,
const std::string& printer_display_name,
std::unique_ptr<printing::PrinterSemanticCapsAndDefaults> caps) {
helper_->AddAvailablePrinter(printer_id, printer_display_name,
std::move(caps));
}
private:
std::unique_ptr<PrintingTestHelper> helper_;
};
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
class PrintingApiTest : public PrintingApiTestBase {
public:
void SetUpOnMainThread() override {
PrintingApiTestBase::SetUpOnMainThread();
printing_infra_helper_ =
std::make_unique<PrintingBackendInfrastructureHelper>();
}
void CreatedBrowserMainParts(
content::BrowserMainParts* browser_main_parts) override {
PrintingApiTestBase::CreatedBrowserMainParts(browser_main_parts);
chromeos::LacrosService::Get()->InjectRemoteForTesting(
local_printer_receiver_.BindNewPipeAndPassRemote());
// When PrintingAPIHandler is initiated, it attempts to bind the observer
// for print jobs.
EXPECT_CALL(local_printer(), AddPrintJobObserver(_, _, _))
.WillOnce(WithArgs<0, 2>(
[&](mojo::PendingRemote<crosapi::mojom::PrintJobObserver> remote,
MockLocalPrinter::AddPrintJobObserverCallback callback) {
observer_remote_.Bind(std::move(remote));
std::move(callback).Run();
}));
}
protected:
NiceMock<MockLocalPrinter>& local_printer() { return local_printer_; }
crosapi::mojom::PrintJobObserver* observer_remote() {
return observer_remote_.get();
}
PrintingBackendInfrastructureHelper& printing_infra_helper() {
return *printing_infra_helper_;
}
private:
NiceMock<MockLocalPrinter> local_printer_;
mojo::Receiver<crosapi::mojom::LocalPrinter> local_printer_receiver_{
&local_printer_};
mojo::Remote<crosapi::mojom::PrintJobObserver> observer_remote_;
std::unique_ptr<PrintingBackendInfrastructureHelper> printing_infra_helper_;
};
#endif
using PrintingPromiseApiTest = PrintingApiTest;
IN_PROC_BROWSER_TEST_P(PrintingApiTest, GetPrinters) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
AddPrinterWithSemanticCaps(kId, kName, ConstructPrinterCapabilities());
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
EXPECT_CALL(local_printer(), GetPrinters(_))
.WillOnce(base::test::RunOnceCallback<0>(
ConstructGetPrintersResponse(kId, kName)));
#endif
RunTest("get_printers.html");
}
IN_PROC_BROWSER_TEST_P(PrintingApiTest, GetPrinterInfo) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
AddPrinterWithSemanticCaps(kId, kName, ConstructPrinterCapabilities());
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
EXPECT_CALL(local_printer(), GetCapability(kId, _))
.WillOnce(base::test::RunOnceCallback<1>(
printing::PrinterWithCapabilitiesToMojom(
chromeos::Printer(kId), *ConstructPrinterCapabilities())));
#endif
RunTest("get_printer_info.html");
}
// Verifies that:
// a) PrintingHooksDelegate substitutes corresponding Blob UUID and DCHECK
// doesn't fail.
// b) Whole API arguments handling pipeline works correctly.
// We use fake version of PrintJobController because we don't have a mock
// version of PrintingContext which is required to handle sending print job to
// the printer.
IN_PROC_BROWSER_TEST_P(PrintingApiTest, SubmitJob) {
ASSERT_TRUE(StartEmbeddedTestServer());
#if BUILDFLAG(IS_CHROMEOS_ASH)
AddPrinterWithSemanticCaps(kId, kName, ConstructPrinterCapabilities());
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
InSequence s;
EXPECT_CALL(local_printer(), GetCapability(kId, _))
.WillOnce(base::test::RunOnceCallback<1>(
printing::PrinterWithCapabilitiesToMojom(
chromeos::Printer(kId), *ConstructPrinterCapabilities())));
// Acknowledge print job creation so that the mojo callback doesn't hang.
EXPECT_CALL(local_printer(), CreatePrintJob(_, _))
.WillOnce(base::test::RunOnceCallback<1>());
printing_infra_helper()
.test_printing_context_factory()
.SetPrinterNameForSubsequentContexts(kId);
#endif
RunTest("submit_job.html");
}
// As above, but tests using promise based API calls.
IN_PROC_BROWSER_TEST_P(PrintingPromiseApiTest, SubmitJob) {
ASSERT_TRUE(StartEmbeddedTestServer());
#if BUILDFLAG(IS_CHROMEOS_ASH)
AddPrinterWithSemanticCaps(kId, kName, ConstructPrinterCapabilities());
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
InSequence s;
EXPECT_CALL(local_printer(), GetCapability(kId, _))
.WillOnce(base::test::RunOnceCallback<1>(
printing::PrinterWithCapabilitiesToMojom(
chromeos::Printer(kId), *ConstructPrinterCapabilities())));
// Acknowledge print job creation so that the mojo callback doesn't hang.
EXPECT_CALL(local_printer(), CreatePrintJob(_, _))
.WillOnce(base::test::RunOnceCallback<1>());
printing_infra_helper()
.test_printing_context_factory()
.SetPrinterNameForSubsequentContexts(kId);
#endif
RunTest("submit_job_promise.html");
}
// Verifies that:
// a) Cancel job request works smoothly.
// b) OnJobStatusChanged() events are dispatched correctly.
IN_PROC_BROWSER_TEST_P(PrintingApiTest, CancelJob) {
ASSERT_TRUE(StartEmbeddedTestServer());
#if BUILDFLAG(IS_CHROMEOS_ASH)
AddPrinterWithSemanticCaps(kId, kName, ConstructPrinterCapabilities());
#elif BUILDFLAG(IS_CHROMEOS_LACROS)
InSequence s;
EXPECT_CALL(local_printer(), GetCapability(kId, _))
.WillOnce(base::test::RunOnceCallback<1>(
printing::PrinterWithCapabilitiesToMojom(
chromeos::Printer(kId), *ConstructPrinterCapabilities())));
std::optional<uint32_t> job_id;
// Pretends to acknowledge the incoming Lacros print job creation request and
// responds with PrintJobStatus::kStarted event.
// The callback is ignored by the implementation -- for this reason the
// invocation order doesn't really matter here (however, dropping it would
// yield a mojo error).
EXPECT_CALL(local_printer(), CreatePrintJob(_, _))
.WillOnce(DoAll(WithArg<0>([&](const auto& job) {
job_id = job->job_id;
auto update = crosapi::mojom::PrintJobUpdate::New();
update->status =
crosapi::mojom::PrintJobStatus::kStarted;
observer_remote()->OnPrintJobUpdate(kId, *job_id,
std::move(update));
}),
base::test::RunOnceCallback<1>()));
// Pretends to acknowledge the incoming Lacros print job cancelation request
// and responds with PrintJobStatus::kCancelled event.
// The callback is ignored by the implementation -- for this reason the
// invocation order doesn't really matter here (however, dropping it would
// yield a mojo error).
EXPECT_CALL(local_printer(), CancelPrintJob(_, _, _))
.WillOnce(DoAll(WithoutArgs([&] {
// Thanks to InSequence defined in the beginning of the
// test, it's guaranteed that `job_id` will be set
// before we get here.
ASSERT_TRUE(job_id);
auto update = crosapi::mojom::PrintJobUpdate::New();
update->status =
crosapi::mojom::PrintJobStatus::kCancelled;
observer_remote()->OnPrintJobUpdate(kId, *job_id,
std::move(update));
}),
base::test::RunOnceCallback<2>(/*canceled=*/true)));
printing_infra_helper()
.test_printing_context_factory()
.SetPrinterNameForSubsequentContexts(kId);
#endif
RunTest("cancel_job.html");
}
INSTANTIATE_TEST_SUITE_P(/**/,
PrintingApiTest,
testing::Values(ExtensionType::kChromeApp,
ExtensionType::kExtensionMV2,
ExtensionType::kExtensionMV3));
// We only run the promise based tests for MV3 extensions as promise based API
// calls are only exposed to MV3.
INSTANTIATE_TEST_SUITE_P(/**/,
PrintingPromiseApiTest,
testing::Values(ExtensionType::kExtensionMV3));
} // namespace extensions