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