chromium/chrome/browser/ash/crosapi/extension_printer_service_ash_browsertest.cc

// Copyright 2024 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/crosapi/extension_printer_service_ash.h"

#include <memory>
#include <utility>

#include "base/logging.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/repeating_test_future.h"
#include "base/test/test_future.h"
#include "base/test/values_test_util.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/crosapi/mojom/extension_printer.mojom.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace crosapi {

namespace {

using ::testing::_;
using ::testing::Mock;

ExtensionPrinterServiceAsh* ExtensionPrinterService() {
  return CrosapiManager::Get()->crosapi_ash()->extension_printer_service_ash();
}

base::Value::List CreateTestPrintersSet1() {
  return base::test::ParseJsonList(R"(
    [ {
      "description": "A virtual printer for testing",
      "extensionId": "jbljdigmdjodgkcllikhggoepmmffba1",
      "extensionName": "Test Printer Provider",
      "id": "jbljdigmdjodgkcllikhggoepmmffba1:test-printer-01",
      "name": "Test Printer 01"
    }, {
      "description": "A virtual printer for testing",
      "extensionId": "jbljdigmdjodgkcllikhggoepmmffba1",
      "extensionName": "Test Printer Provider",
      "id": "jbljdigmdjodgkcllikhggoepmmffba1:test-printer-02",
      "name": "Test Printer 02"
    } ]
  )");
}

base::Value::List CreateTestPrintersSet2() {
  return base::test::ParseJsonList(R"(
    [ {
      "description": "A virtual printer for testing",
      "extensionId": "jbljdigmdjodgkcllikhggoepmmffba2",
      "extensionName": "Test Printer Provider",
      "id": "jbljdigmdjodgkcllikhggoepmmffba2:test-printer-03",
      "name": "Test Printer 03"
    }]
  )");
}

base::Value::Dict CreateTestCapability() {
  return base::test::ParseJsonDict(R"(
    {
      "version": "1.0",
      "printer": {
        "supported_content_type": [
          {"content_type": "application/pdf"}
        ]
      }
    })");
}

class MockExtensionPrinterServiceProvider
    : public crosapi::mojom::ExtensionPrinterServiceProvider {
 public:
  MOCK_METHOD(void,
              DispatchGetPrintersRequest,
              (const ::base::UnguessableToken& request_id),
              (override));
  MOCK_METHOD(void, DispatchResetRequest, (), (override));
  MOCK_METHOD(void,
              DispatchStartGetCapability,
              (const std::string& destination_id,
               DispatchStartGetCapabilityCallback callback),
              (override));
  MOCK_METHOD(void,
              DispatchStartPrint,
              (const ::std::u16string& job_title,
               ::base::Value::Dict settings,
               ::scoped_refptr<::base::RefCountedMemory> print_data,
               DispatchStartPrintCallback callback),
              (override));
  MOCK_METHOD(void,
              DispatchStartGrantPrinterAccess,
              (const std::string& printer_id,
               DispatchStartGrantPrinterAccessCallback callback),
              (override));

  mojo::Receiver<mojom::ExtensionPrinterServiceProvider> receiver_{this};
};

}  // namespace

class ExtensionPrinterServiceAshBrowserTest : public InProcessBrowserTest {
 public:
  ExtensionPrinterServiceAshBrowserTest() = default;

  void VerifyProvider() {
    ExtensionPrinterServiceAsh* service = ExtensionPrinterService();
    EXPECT_TRUE(service->HasProviderForTesting());
  }

 protected:
  MockExtensionPrinterServiceProvider& mock_provider() {
    return mock_provider_;
  }
  void FlushForTesting() {
    extension_printer_service_remote_.FlushForTesting();
  }

 private:
  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    ExtensionPrinterService()->BindReceiver(
        extension_printer_service_remote_.BindNewPipeAndPassReceiver());

    extension_printer_service_remote_->RegisterServiceProvider(
        mock_provider_.receiver_.BindNewPipeAndPassRemote());
    extension_printer_service_remote_.FlushForTesting();
  }

  MockExtensionPrinterServiceProvider mock_provider_;
  mojo::Remote<mojom::ExtensionPrinterService>
      extension_printer_service_remote_;
};

// Verifies that a service provider is registered.
IN_PROC_BROWSER_TEST_F(ExtensionPrinterServiceAshBrowserTest,
                       RegisterServiceProvider) {
  VerifyProvider();
}

// Verifies that StartGetPrinters can receive printers from multiple extensions.
IN_PROC_BROWSER_TEST_F(ExtensionPrinterServiceAshBrowserTest,
                       StartGetPrinters) {
  base::HistogramTester histogram_tester;

  constexpr char kNumberOfPrintersMetricName[] =
      "Printing.LacrosExtensions.FromAsh.NumberOfPrinters";

  EXPECT_CALL(mock_provider(), DispatchGetPrintersRequest(_))
      .WillOnce([](const ::base::UnguessableToken& requestId) {
        ExtensionPrinterServiceAsh* service = ExtensionPrinterService();
        // Simulates reporting printers from extension 1.
        service->PrintersAdded(requestId, CreateTestPrintersSet1(), false);
        // Simulates reporting printers from extension 2.
        service->PrintersAdded(requestId, CreateTestPrintersSet2(), false);
        // Simulates reporting printers is done.
        service->PrintersAdded(requestId, base::Value::List(), true);
      });

  base::test::RepeatingTestFuture<base::Value::List> printers_added_future;
  base::test::TestFuture<void> done_future;

  ExtensionPrinterService()->StartGetPrinters(
      printers_added_future.GetCallback(), done_future.GetCallback());

  // Verifies the first set of printers from extension 1.
  const base::Value::List& printers_set1 = printers_added_future.Take();
  EXPECT_EQ(printers_set1.size(), 2u);

  const base::Value::Dict& printer1 = printers_set1[0].GetDict();
  base::ExpectDictStringValue("A virtual printer for testing", printer1,
                              "description");
  base::ExpectDictStringValue("jbljdigmdjodgkcllikhggoepmmffba1", printer1,
                              "extensionId");
  base::ExpectDictStringValue("Test Printer Provider", printer1,
                              "extensionName");
  base::ExpectDictStringValue(
      "jbljdigmdjodgkcllikhggoepmmffba1:test-printer-01", printer1, "id");
  base::ExpectDictStringValue("Test Printer 01", printer1, "name");

  const base::Value::Dict& printer2 = printers_set1[1].GetDict();
  base::ExpectDictStringValue("A virtual printer for testing", printer2,
                              "description");
  base::ExpectDictStringValue("jbljdigmdjodgkcllikhggoepmmffba1", printer2,
                              "extensionId");
  base::ExpectDictStringValue("Test Printer Provider", printer2,
                              "extensionName");
  base::ExpectDictStringValue(
      "jbljdigmdjodgkcllikhggoepmmffba1:test-printer-02", printer2, "id");
  base::ExpectDictStringValue("Test Printer 02", printer2, "name");

  // Verifies the second set of printers from extension 2.
  const base::Value::List& printers_set2 = printers_added_future.Take();
  EXPECT_EQ(printers_set2.size(), 1u);

  const base::Value::Dict& printer3 = printers_set2[0].GetDict();
  base::ExpectDictStringValue("A virtual printer for testing", printer3,
                              "description");
  base::ExpectDictStringValue("jbljdigmdjodgkcllikhggoepmmffba2", printer3,
                              "extensionId");
  base::ExpectDictStringValue("Test Printer Provider", printer3,
                              "extensionName");
  base::ExpectDictStringValue(
      "jbljdigmdjodgkcllikhggoepmmffba2:test-printer-03", printer3, "id");
  base::ExpectDictStringValue("Test Printer 03", printer3, "name");

  // Verifies that the GetPrintersDoneCallback is invoked when no more printers
  // will be reported.
  EXPECT_TRUE(done_future.Wait());
  // The histogram is recorded once with a value of 3 (number of printers).
  histogram_tester.ExpectUniqueSample(kNumberOfPrintersMetricName, 3, 1);
}

// Verifies that StartGetCapability can receive capability.
IN_PROC_BROWSER_TEST_F(ExtensionPrinterServiceAshBrowserTest, Reset) {
  // Captures the request_id.
  base::UnguessableToken captured_request_id;
  // Simulates that a get printers request has been created but reporting
  // printers is not done yet, i.e., the service provider has not called
  // PrintersAdded.
  EXPECT_CALL(mock_provider(), DispatchGetPrintersRequest(_))
      .WillOnce(
          [&captured_request_id](const ::base::UnguessableToken& request_id) {
            captured_request_id = request_id;
          });
  // Verifies that the downstream's Reset has been called.
  EXPECT_CALL(mock_provider(), DispatchResetRequest()).Times(1);

  EXPECT_FALSE(ExtensionPrinterService()->HasAnyPendingGetPrintersRequests());

  // Starts a Get printers request.
  base::test::RepeatingTestFuture<base::Value::List> printers_added_future;
  base::test::TestFuture<void> done_future;
  ExtensionPrinterService()->StartGetPrinters(
      printers_added_future.GetCallback(), done_future.GetCallback());
  FlushForTesting();

  // A pending request with |captured_request_id| has been created.
  EXPECT_TRUE(ExtensionPrinterService()->HasPendingGetPrintersRequestForTesting(
      captured_request_id));

  ExtensionPrinterService()->Reset();
  // The pending request with |captured_request_id| has been cleared.
  EXPECT_FALSE(
      ExtensionPrinterService()->HasPendingGetPrintersRequestForTesting(
          captured_request_id));
  // And there are none pending requests with other ids.
  EXPECT_FALSE(ExtensionPrinterService()->HasAnyPendingGetPrintersRequests());
}

// Verifies that StartGetCapability can receive capability.
IN_PROC_BROWSER_TEST_F(ExtensionPrinterServiceAshBrowserTest,
                       StartGetCapability) {
  EXPECT_CALL(mock_provider(), DispatchStartGetCapability(_, _))
      .WillOnce([](const std::string& destination_id,
                   base::OnceCallback<void(::base::Value::Dict)> callback) {
        std::move(callback).Run(CreateTestCapability());
      });

  base::test::TestFuture<base::Value::Dict> get_capability_future;

  ExtensionPrinterService()->StartGetCapability(
      "jbljdigmdjodgkcllikhggoepmmffba1:test-printer-02",
      get_capability_future.GetCallback());

  const base::Value::Dict& capability = get_capability_future.Take();
  base::ExpectDictStringValue("1.0", capability, "version");

  const base::Value::List* supportedContentTypes =
      capability.FindListByDottedPath("printer.supported_content_type");
  ASSERT_TRUE(supportedContentTypes);
  EXPECT_EQ(supportedContentTypes->size(), 1u);

  const base::Value& contentType1 = (*supportedContentTypes)[0];
  EXPECT_TRUE(contentType1.is_dict());
  base::ExpectDictStringValue("application/pdf", contentType1.GetDict(),
                              "content_type");
}

// Verifies that StartPrint is dispatched correctly.
IN_PROC_BROWSER_TEST_F(ExtensionPrinterServiceAshBrowserTest, StartPrint) {
  // Test data for the print job.
  const std::u16string job_title = u"Test Print Job";
  base::Value::Dict settings = base::test::ParseJsonDict(R"(
    {
      "copies": 2,
      "color": "color"
    }
  )");
  scoped_refptr<base::RefCountedMemory> print_data =
      base::MakeRefCounted<base::RefCountedString>("Test print data");

  // Captures the arguments passed to DispatchStartPrint.
  std::u16string captured_job_title;
  base::Value::Dict captured_settings;
  scoped_refptr<base::RefCountedMemory> captured_print_data;

  // Sets up the expectation for DispatchStartPrint.
  EXPECT_CALL(mock_provider(), DispatchStartPrint(_, _, _, _))
      .WillOnce(
          [&](const std::u16string& job_title, base::Value::Dict settings,
              scoped_refptr<base::RefCountedMemory> print_data,
              MockExtensionPrinterServiceProvider::DispatchStartPrintCallback
                  callback) {
            // Capture the arguments.
            captured_job_title = job_title;
            captured_settings = std::move(settings);
            captured_print_data = print_data;

            // Simulate a successful print job.
            std::move(callback).Run(crosapi::mojom::StartPrintStatus::KOk);
          });

  // Calls the StartPrint method.
  base::test::TestFuture<crosapi::mojom::StartPrintStatus> print_future;
  ExtensionPrinterService()->StartPrint(job_title, std::move(settings),
                                        print_data, print_future.GetCallback());
  FlushForTesting();

  // Verifies the result of the print job.
  EXPECT_EQ(print_future.Get(), crosapi::mojom::StartPrintStatus::KOk);
  // Asserts the captured data matches the input.
  EXPECT_EQ(captured_job_title, job_title);
  EXPECT_EQ(captured_settings,
            base::test::ParseJsonDict(R"({"copies": 2, "color": "color"})"));
  EXPECT_TRUE(captured_print_data->Equals(print_data));
}

// Verifies that StartGrantPrinterAccess is dispatched correctly.
IN_PROC_BROWSER_TEST_F(ExtensionPrinterServiceAshBrowserTest,
                       StartGrantPrinterAccess) {
  const std::string test_printer_id = "test_printer_id_123";
  base::Value::Dict expected_printer_info = base::test::ParseJsonDict(R"(
    {
      "printerId": "test_printer_id_123",
      "name": "Test Printer"
    }
  )");

  EXPECT_CALL(mock_provider(),
              DispatchStartGrantPrinterAccess(test_printer_id, _))
      .WillOnce([&expected_printer_info](
                    const std::string& printer_id,
                    MockExtensionPrinterServiceProvider::
                        DispatchStartGrantPrinterAccessCallback callback) {
        // Calls the callback with the simulated printer info.
        std::move(callback).Run(expected_printer_info.Clone());
      });

  base::test::TestFuture<base::Value::Dict> grant_access_future;
  ExtensionPrinterService()->StartGrantPrinterAccess(
      test_printer_id, grant_access_future.GetCallback());

  const base::Value::Dict& printer_info = grant_access_future.Get();
  EXPECT_EQ(printer_info, expected_printer_info);
}

}  // namespace crosapi