chromium/chrome/browser/extensions/api/printing/printing_apitest.cc

// 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