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

// Copyright 2021 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/local_printer_ash.h"

#include <algorithm>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/crosapi/test_local_printer_ash.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/printing/cups_printers_manager_factory.h"
#include "chrome/browser/ash/printing/fake_cups_printers_manager.h"
#include "chrome/browser/ash/printing/ipp_client_info_calculator.h"
#include "chrome/browser/ash/printing/oauth2/authorization_zones_manager_factory.h"
#include "chrome/browser/ash/printing/oauth2/mock_authorization_zones_manager.h"
#include "chrome/browser/ash/printing/oauth2/status_code.h"
#include "chrome/browser/printing/local_printer_utils_chromeos.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/printing/printer_capabilities.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/crosapi/mojom/local_printer.mojom-forward.h"
#include "chromeos/crosapi/mojom/local_printer.mojom.h"
#include "chromeos/printing/cups_printer_status.h"
#include "chromeos/printing/ppd_provider.h"
#include "chromeos/printing/printer_configuration.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/testing_pref_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/browser_task_environment.h"
#include "printing/backend/print_backend.h"
#include "printing/backend/printing_restrictions.h"
#include "printing/backend/test_print_backend.h"
#include "printing/buildflags/buildflags.h"
#include "printing/mojom/print.mojom-forward.h"
#include "printing/mojom/print.mojom.h"
#include "printing/printing_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"

#if BUILDFLAG(ENABLE_OOP_PRINTING)
#include "chrome/browser/printing/print_backend_service_manager.h"
#include "chrome/browser/printing/print_backend_service_test_impl.h"
#else
#include "base/notreached.h"
#endif

using ::chromeos::Printer;
using ::chromeos::PrinterClass;
using testing::ByMove;
using testing::NiceMock;
using testing::Return;

namespace printing {

namespace {

using LocalPrintersCallback = base::OnceCallback<void(
    std::vector<crosapi::mojom::LocalDestinationInfoPtr>)>;
using crosapi::mojom::GetOAuthAccessTokenResultPtr;
using printing::mojom::IppClientInfoPtr;

constexpr char kPrinterUri[] = "http://localhost:80";
const AccountId kAffiliatedUserAccountId =
    AccountId::FromUserEmail("[email protected]");
const AccountId kUnaffiliatedUserAccountId =
    AccountId::FromUserEmail("[email protected]");

// Used as a callback to `GetPrinters()` in tests.
// Records list returned by `GetPrinters()`.
void RecordPrinterList(
    std::vector<crosapi::mojom::LocalDestinationInfoPtr>& printers_out,
    std::vector<crosapi::mojom::LocalDestinationInfoPtr> printers) {
  printers_out = std::move(printers);
}

void RecordGetCapability(
    crosapi::mojom::CapabilitiesResponsePtr& capabilities_out,
    crosapi::mojom::CapabilitiesResponsePtr capability) {
  capabilities_out = std::move(capability);
}

void RecordGetEulaUrl(GURL& fetched_eula_url, const GURL& eula_url) {
  fetched_eula_url = eula_url;
}

Printer CreateTestPrinter(const std::string& id,
                          const std::string& name,
                          const std::string& description) {
  Printer printer;
  printer.SetUri(kPrinterUri);
  printer.set_id(id);
  printer.set_display_name(name);
  printer.set_description(description);
  return printer;
}

Printer CreateTestPrinterWithPpdReference(const std::string& id,
                                          const std::string& name,
                                          const std::string& description,
                                          Printer::PpdReference ref) {
  Printer printer = CreateTestPrinter(id, name, description);
  Printer::PpdReference* mutable_ppd_reference =
      printer.mutable_ppd_reference();
  *mutable_ppd_reference = ref;
  return printer;
}

Printer CreateEnterprisePrinter(const std::string& id,
                                const std::string& name,
                                const std::string& description) {
  Printer printer = CreateTestPrinter(id, name, description);
  printer.set_source(Printer::SRC_POLICY);
  return printer;
}

}  // namespace

// Fake `PpdProvider` backend. This fake `PpdProvider` is used to fake fetching
// the PPD EULA license of a destination. If `effective_make_and_model` is
// empty, it will return with NOT_FOUND and an empty string. Otherwise, it will
// return SUCCESS with `effective_make_and_model` as the PPD license.
class FakePpdProvider : public chromeos::PpdProvider {
 public:
  FakePpdProvider() = default;

  void ResolvePpdLicense(std::string_view effective_make_and_model,
                         ResolvePpdLicenseCallback cb) override {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(cb),
                       effective_make_and_model.empty() ? PpdProvider::NOT_FOUND
                                                        : PpdProvider::SUCCESS,
                       std::string(effective_make_and_model)));
  }

  // These methods are not used by `CupsPrintersManager`.
  void ResolvePpd(const Printer::PpdReference& reference,
                  ResolvePpdCallback cb) override {}
  void ResolvePpdReference(const chromeos::PrinterSearchData& search_data,
                           ResolvePpdReferenceCallback cb) override {}
  void ResolveManufacturers(ResolveManufacturersCallback cb) override {}
  void ResolvePrinters(const std::string& manufacturer,
                       ResolvePrintersCallback cb) override {}
  void ReverseLookup(const std::string& effective_make_and_model,
                     ReverseLookupCallback cb) override {}

 private:
  ~FakePpdProvider() override = default;
};

class FakeLocalPrintersObserver : public crosapi::mojom::LocalPrintersObserver {
 public:
  FakeLocalPrintersObserver() = default;
  ~FakeLocalPrintersObserver() override = default;

  mojo::PendingRemote<crosapi::mojom::LocalPrintersObserver> GenerateRemote() {
    mojo::PendingRemote<crosapi::mojom::LocalPrintersObserver> remote;
    receiver_.Bind(remote.InitWithNewPipeAndPassReceiver());
    return remote;
  }

  // crosapi::mojom::LocalPrintersObserver:
  void OnLocalPrintersUpdated(
      std::vector<crosapi::mojom::LocalDestinationInfoPtr> printers) override {
    for (const auto& printer : printers) {
      crosapi::mojom::LocalDestinationInfoPtr local_printer =
          crosapi::mojom::LocalDestinationInfo::New();
      local_printer->id = printer->id;
      local_printers_.push_back(std::move(local_printer));
    }

    if (local_printers_callback_) {
      std::move(local_printers_callback_).Run(mojo::Clone(local_printers_));
    }
  }

  void GetLocalPrinters(LocalPrintersCallback callback) {
    local_printers_callback_ = std::move(callback);
  }

 private:
  LocalPrintersCallback local_printers_callback_;
  std::vector<crosapi::mojom::LocalDestinationInfoPtr> local_printers_;
  mojo::Receiver<crosapi::mojom::LocalPrintersObserver> receiver_{this};
};

class TestLocalPrinterAshWithPrinterConfigurer : public TestLocalPrinterAsh {
 public:
  TestLocalPrinterAshWithPrinterConfigurer(
      Profile* profile,
      scoped_refptr<chromeos::PpdProvider> ppd_provider,
      ash::FakeCupsPrintersManager* manager)
      : TestLocalPrinterAsh(profile, ppd_provider), manager_(manager) {}
  TestLocalPrinterAshWithPrinterConfigurer(
      const TestLocalPrinterAshWithPrinterConfigurer&) = delete;
  TestLocalPrinterAshWithPrinterConfigurer& operator=(
      const TestLocalPrinterAshWithPrinterConfigurer&) = delete;
  ~TestLocalPrinterAshWithPrinterConfigurer() override = default;

 private:
  const raw_ptr<ash::FakeCupsPrintersManager> manager_;
};

// Base testing class for `LocalPrinterAsh`.  Contains the base
// logic to allow for using either a local task runner or a service to make
// print backend calls, and to possibly enable fallback when using a service.
// Tests to trigger those different paths can be done by overloading
// `UseService()` and `SupportFallback()`.
class LocalPrinterAshTestBase : public testing::Test {
 public:
  const std::vector<PrinterSemanticCapsAndDefaults::Paper> kPapers = {
      {"bar", "vendor", gfx::Size(600, 600), gfx::Rect(0, 0, 600, 600)}};

  LocalPrinterAshTestBase() = default;
  LocalPrinterAshTestBase(const LocalPrinterAshTestBase&) = delete;
  LocalPrinterAshTestBase& operator=(const LocalPrinterAshTestBase&) = delete;
  ~LocalPrinterAshTestBase() override = default;

  TestPrintBackend* sandboxed_print_backend() {
    return sandboxed_test_backend_.get();
  }
  TestPrintBackend* unsandboxed_print_backend() {
    return unsandboxed_test_backend_.get();
  }

  void SetUsername(const std::string& username) {
    fake_user_manager_->SaveUserDisplayEmail(
        AccountId::FromUserEmail(TestingProfile::kDefaultProfileUserName),
        username);
  }

  // Indicate if calls to print backend should be made using a service instead
  // of a local task runner.
  virtual bool UseService() = 0;

  // Indicate if fallback support for access-denied errors should be included
  // when using a service for print backend calls.
  virtual bool SupportFallback() = 0;

  virtual std::vector<base::test::FeatureRefAndParams> FeaturesToEnable() {
    return {};
  }

  sync_preferences::TestingPrefServiceSyncable* GetPrefs() {
    return profile_.GetTestingPrefService();
  }

  void SetUp() override {
    fake_user_manager_->AddUser(
        AccountId::FromUserEmail(TestingProfile::kDefaultProfileUserName));

    std::vector<base::test::FeatureRefAndParams> features_to_enable =
        FeaturesToEnable();
    std::vector<base::test::FeatureRef> features_to_disable;
#if BUILDFLAG(ENABLE_OOP_PRINTING)
    // Choose between running with local test runner or via a service.
    if (UseService()) {
      features_to_enable.emplace_back(base::test::FeatureRefAndParams(
          features::kEnableOopPrintDrivers,
          {{ features::kEnableOopPrintDriversSandbox.name,
             "true" }}));
    } else {
      features_to_disable.push_back(features::kEnableOopPrintDrivers);
    }
#endif
    feature_list_.InitWithFeaturesAndParameters(features_to_enable,
                                                features_to_disable);

    sandboxed_test_backend_ = base::MakeRefCounted<TestPrintBackend>();
    ppd_provider_ = base::MakeRefCounted<FakePpdProvider>();
    ash::CupsPrintersManagerFactory::GetInstance()->SetTestingFactoryAndUse(
        &profile_,
        base::BindLambdaForTesting([this](content::BrowserContext* context)
                                       -> std::unique_ptr<KeyedService> {
          auto printers_manager =
              std::make_unique<ash::FakeCupsPrintersManager>();
          printers_manager_ = printers_manager.get();
          return printers_manager;
        }));
    local_printer_ash_ =
        std::make_unique<TestLocalPrinterAshWithPrinterConfigurer>(
            &profile_, ppd_provider_, printers_manager_);

    if (UseService()) {
#if BUILDFLAG(ENABLE_OOP_PRINTING)
      sandboxed_print_backend_service_ =
          PrintBackendServiceTestImpl::LaunchForTesting(sandboxed_test_remote_,
                                                        sandboxed_test_backend_,
                                                        /*sandboxed=*/true);

      if (SupportFallback()) {
        unsandboxed_test_backend_ = base::MakeRefCounted<TestPrintBackend>();

        unsandboxed_print_backend_service_ =
            PrintBackendServiceTestImpl::LaunchForTesting(
                unsandboxed_test_remote_, unsandboxed_test_backend_,
                /*sandboxed=*/false);
      }

      // Client registration is normally covered by `PrintPreviewUI`, so mimic
      // that here.
      service_manager_client_id_ =
          PrintBackendServiceManager::GetInstance().RegisterQueryClient();
#else
      NOTREACHED_IN_MIGRATION();
#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)
    } else {
      // Use of task runners will call `PrintBackend::CreateInstance()`, which
      // needs a test backend registered for it to use.
      PrintBackend::SetPrintBackendForTesting(sandboxed_test_backend_.get());
    }
  }

  void TearDown() override {
#if BUILDFLAG(ENABLE_OOP_PRINTING)
    if (UseService()) {
      PrintBackendServiceManager::GetInstance().UnregisterClient(
          service_manager_client_id_);
    }

    PrintBackendServiceManager::ResetForTesting();
#endif
  }

 protected:
  void AddPrinter(const std::string& id,
                  const std::string& display_name,
                  const std::string& description,
                  bool is_default,
                  bool requires_elevated_permissions) {
    auto caps = std::make_unique<PrinterSemanticCapsAndDefaults>();
    caps->papers = kPapers;
    auto basic_info = std::make_unique<PrinterBasicInfo>(
        id, display_name, description, /*printer_status=*/0, is_default,
        PrinterBasicInfoOptions{});

#if BUILDFLAG(ENABLE_OOP_PRINTING)
    if (SupportFallback()) {
      // Need to populate same values into a second print backend.
      // For fallback they will always be treated as valid.
      auto caps_unsandboxed =
          std::make_unique<PrinterSemanticCapsAndDefaults>(*caps);
      auto basic_info_unsandboxed =
          std::make_unique<PrinterBasicInfo>(*basic_info);
      unsandboxed_print_backend()->AddValidPrinter(
          id, std::move(caps_unsandboxed), std::move(basic_info_unsandboxed));
    }
#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)

    if (requires_elevated_permissions) {
      sandboxed_print_backend()->AddAccessDeniedPrinter(id);
    } else {
      sandboxed_print_backend()->AddValidPrinter(id, std::move(caps),
                                                 std::move(basic_info));
    }
  }

#if BUILDFLAG(ENABLE_OOP_PRINTING)
  void SetTerminateServiceOnNextInteraction() {
    if (SupportFallback()) {
      unsandboxed_print_backend_service_
          ->SetTerminateReceiverOnNextInteraction();
    }

    sandboxed_print_backend_service_->SetTerminateReceiverOnNextInteraction();
  }
#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)

  void RunUntilIdle() { task_environment_.RunUntilIdle(); }

  ash::FakeCupsPrintersManager& printers_manager() {
    DCHECK(printers_manager_);
    return *printers_manager_;
  }

  crosapi::LocalPrinterAsh* local_printer_ash() {
    return local_printer_ash_.get();
  }

  base::test::ScopedFeatureList& feature_list() { return feature_list_; }

  ash::FakeChromeUserManager* fake_user_manager() {
    return fake_user_manager_.Get();
  }

 protected:
  FakeLocalPrintersObserver fake_local_printers_observer_;

 private:
  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
      fake_user_manager_{std::make_unique<ash::FakeChromeUserManager>()};

  // Must outlive `profile_`.
  content::BrowserTaskEnvironment task_environment_;

  // Must outlive `printers_manager_`.
  TestingProfile profile_;
  scoped_refptr<TestPrintBackend> sandboxed_test_backend_;
  scoped_refptr<TestPrintBackend> unsandboxed_test_backend_;
  raw_ptr<ash::FakeCupsPrintersManager> printers_manager_ = nullptr;
  scoped_refptr<FakePpdProvider> ppd_provider_;
  std::unique_ptr<crosapi::LocalPrinterAsh> local_printer_ash_;
  base::test::ScopedFeatureList feature_list_;

#if BUILDFLAG(ENABLE_OOP_PRINTING)
  // Support for testing via a service instead of with a local task runner.
  mojo::Remote<mojom::PrintBackendService> sandboxed_test_remote_;
  mojo::Remote<mojom::PrintBackendService> unsandboxed_test_remote_;
  std::unique_ptr<PrintBackendServiceTestImpl> sandboxed_print_backend_service_;
  std::unique_ptr<PrintBackendServiceTestImpl>
      unsandboxed_print_backend_service_;
  PrintBackendServiceManager::ClientId service_manager_client_id_;
#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)
};

// Testing class to cover `LocalPrinterAsh` handling using a local
// task runner.
class LocalPrinterAshTest : public LocalPrinterAshTestBase {
 public:
  LocalPrinterAshTest() = default;
  LocalPrinterAshTest(const LocalPrinterAshTest&) = delete;
  LocalPrinterAshTest& operator=(const LocalPrinterAshTest&) = delete;
  ~LocalPrinterAshTest() override = default;

  bool UseService() override { return false; }
  bool SupportFallback() override { return false; }
};

// Testing class to cover `LocalPrinterAsh` handling using either a
// local task runner or a service.  Makes no attempt to cover fallback when
// using a service, which is handled separately by
// `LocalPrinterAshServiceTest`.
class LocalPrinterAshProcessScopeTest
    : public LocalPrinterAshTestBase,
      public testing::WithParamInterface<bool> {
 public:
  LocalPrinterAshProcessScopeTest() = default;
  LocalPrinterAshProcessScopeTest(const LocalPrinterAshProcessScopeTest&) =
      delete;
  LocalPrinterAshProcessScopeTest& operator=(
      const LocalPrinterAshProcessScopeTest&) = delete;
  ~LocalPrinterAshProcessScopeTest() override = default;

  bool UseService() override { return GetParam(); }
  bool SupportFallback() override { return false; }
};

#if BUILDFLAG(ENABLE_OOP_PRINTING)

// Testing class to cover `LocalPrinterAsh` handling using only a
// service.  This can check different behavior for whether fallback is enabled,
// Mojom data validation conditions, or service termination.
class LocalPrinterAshServiceTest : public LocalPrinterAshTestBase {
 public:
  LocalPrinterAshServiceTest() = default;
  LocalPrinterAshServiceTest(const LocalPrinterAshServiceTest&) = delete;
  LocalPrinterAshServiceTest& operator=(const LocalPrinterAshServiceTest&) =
      delete;
  ~LocalPrinterAshServiceTest() override = default;

  bool UseService() override { return true; }
  bool SupportFallback() override { return true; }
};

INSTANTIATE_TEST_SUITE_P(All, LocalPrinterAshProcessScopeTest, testing::Bool());

#else

// Without OOP printing we only test local test runner configuration.
INSTANTIATE_TEST_SUITE_P(/*no prefix */,
                         LocalPrinterAshProcessScopeTest,
                         testing::Values(false));

#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)

TEST_F(LocalPrinterAshTest, GetStatus) {
  chromeos::CupsPrinterStatus printer1("printer1");
  printer1.AddStatusReason(crosapi::mojom::StatusReason::Reason::kPaperJam,
                           crosapi::mojom::StatusReason::Severity::kError);
  printers_manager().SetPrinterStatus(printer1);
  crosapi::mojom::PrinterStatusPtr printer_status;
  local_printer_ash()->GetStatus(
      "printer1",
      base::BindLambdaForTesting([&](crosapi::mojom::PrinterStatusPtr status) {
        printer_status = std::move(status);
      }));
  auto expected_status = crosapi::mojom::PrinterStatus::New();
  expected_status->printer_id = "printer1";
  expected_status->timestamp = printer1.GetTimestamp();
  expected_status->status_reasons.push_back(crosapi::mojom::StatusReason::New(
      crosapi::mojom::StatusReason::Reason::kPaperJam,
      crosapi::mojom::StatusReason::Severity::kError));
  EXPECT_EQ(expected_status, printer_status);
}

TEST_F(LocalPrinterAshTest, GetPrinters) {
  Printer saved_printer =
      CreateTestPrinter("printer1", "saved", "description1");
  Printer enterprise_printer =
      CreateEnterprisePrinter("printer2", "enterprise", "description2");
  Printer automatic_printer =
      CreateTestPrinter("printer3", "automatic", "description3");

  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);
  printers_manager().AddPrinter(enterprise_printer, PrinterClass::kEnterprise);
  printers_manager().AddPrinter(automatic_printer, PrinterClass::kAutomatic);

  std::vector<crosapi::mojom::LocalDestinationInfoPtr> printers;
  local_printer_ash()->GetPrinters(
      base::BindOnce(&RecordPrinterList, std::ref(printers)));

  ASSERT_EQ(3u, printers.size());

  EXPECT_EQ("printer1", printers[0]->id);
  EXPECT_EQ("saved", printers[0]->name);
  EXPECT_EQ("description1", printers[0]->description);
  EXPECT_FALSE(printers[0]->configured_via_policy);
  ASSERT_TRUE(printers[0]->uri);
  EXPECT_EQ(kPrinterUri, *printers[0]->uri);

  EXPECT_EQ("printer2", printers[1]->id);
  EXPECT_EQ("enterprise", printers[1]->name);
  EXPECT_EQ("description2", printers[1]->description);
  EXPECT_TRUE(printers[1]->configured_via_policy);
  ASSERT_TRUE(printers[1]->uri);
  EXPECT_EQ(kPrinterUri, *printers[1]->uri);

  EXPECT_EQ("printer3", printers[2]->id);
  EXPECT_EQ("automatic", printers[2]->name);
  EXPECT_EQ("description3", printers[2]->description);
  EXPECT_FALSE(printers[2]->configured_via_policy);
  ASSERT_TRUE(printers[2]->uri);
  EXPECT_EQ(kPrinterUri, *printers[2]->uri);
}

// Tests that fetching capabilities for non-installed printers is successful
// depending on its autoconf compatibility.
TEST_F(LocalPrinterAshTest, GetCapabilityForNonInstalledPrinters) {
  feature_list().Reset();

  const std::string autoconf_printer_id = "printer1";
  Printer autoconf_printer =
      CreateTestPrinter(autoconf_printer_id, "discovered", "description1");
  const std::string non_autoconf_printer_id = "printer2";
  Printer non_autoconf_printer =
      CreateTestPrinter(non_autoconf_printer_id, "discovered", "description2");

  printers_manager().AddPrinter(autoconf_printer, PrinterClass::kDiscovered);
  printers_manager().AddPrinter(non_autoconf_printer,
                                PrinterClass::kDiscovered);
  printers_manager().MarkPrinterAsNotAutoconfigurable(non_autoconf_printer_id);

  // Add printer capabilities to `test_backend_`.
  AddPrinter(autoconf_printer_id, "discovered", "description1",
             /*is_default=*/true,
             /*requires_elevated_permissions=*/false);
  AddPrinter(non_autoconf_printer_id, "discovered", "description2",
             /*is_default=*/true,
             /*requires_elevated_permissions=*/false);

  // Try to fetch capabilities for both printers but only the autoconf printer
  // should succeed.
  crosapi::mojom::CapabilitiesResponsePtr autoconf_fetched_caps;
  local_printer_ash()->GetCapability(
      autoconf_printer_id,
      base::BindOnce(&RecordGetCapability, std::ref(autoconf_fetched_caps)));
  crosapi::mojom::CapabilitiesResponsePtr non_autoconf_fetched_caps;
  local_printer_ash()->GetCapability(
      non_autoconf_printer_id,
      base::BindOnce(&RecordGetCapability,
                     std::ref(non_autoconf_fetched_caps)));
  RunUntilIdle();

  EXPECT_TRUE(autoconf_fetched_caps);
  EXPECT_FALSE(non_autoconf_fetched_caps);
}

// Tests that fetching capabilities for an existing installed printer is
// successful.
TEST_P(LocalPrinterAshProcessScopeTest, GetCapabilityValidPrinter) {
  Printer saved_printer =
      CreateTestPrinter("printer1", "saved", "description1");
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);
  printers_manager().MarkInstalled(saved_printer.id());

  // Add printer capabilities to `test_backend_`.
  AddPrinter("printer1", "saved", "description1", /*is_default=*/true,
             /*requires_elevated_permissions=*/false);

  crosapi::mojom::CapabilitiesResponsePtr fetched_caps;
  local_printer_ash()->GetCapability(
      "printer1", base::BindOnce(&RecordGetCapability, std::ref(fetched_caps)));

  RunUntilIdle();

  ASSERT_TRUE(fetched_caps);
  EXPECT_FALSE(fetched_caps->has_secure_protocol);
  ASSERT_TRUE(fetched_caps->basic_info);
  EXPECT_EQ("printer1", fetched_caps->basic_info->id);
  EXPECT_EQ("saved", fetched_caps->basic_info->name);
  EXPECT_EQ("description1", fetched_caps->basic_info->description);
  EXPECT_FALSE(fetched_caps->basic_info->configured_via_policy);
  ASSERT_TRUE(fetched_caps->basic_info->uri);
  EXPECT_EQ(kPrinterUri, *fetched_caps->basic_info->uri);

  ASSERT_TRUE(fetched_caps->capabilities);
  EXPECT_EQ(kPapers, fetched_caps->capabilities->papers);
}

// Test that printers which have not yet been installed are installed with
// `SetUpPrinter` before their capabilities are fetched.
TEST_P(LocalPrinterAshProcessScopeTest, GetCapabilityPrinterNotInstalled) {
  Printer discovered_printer =
      CreateTestPrinter("printer1", "discovered", "description1");
  // NOTE: The printer `discovered_printer` is not installed.
  printers_manager().AddPrinter(discovered_printer, PrinterClass::kDiscovered);

  // Add printer capabilities to `test_backend_`.
  AddPrinter("printer1", "discovered", "description1", /*is_default=*/true,
             /*requires_elevated_permissions=*/false);

  crosapi::mojom::CapabilitiesResponsePtr fetched_caps;

  EXPECT_FALSE(printers_manager().IsPrinterInstalled(discovered_printer));

  // Install printer and fetch capabilities.
  local_printer_ash()->GetCapability(
      "printer1", base::BindOnce(&RecordGetCapability, std::ref(fetched_caps)));

  RunUntilIdle();

  EXPECT_TRUE(printers_manager().IsPrinterInstalled(discovered_printer));
  ASSERT_TRUE(fetched_caps);
  ASSERT_TRUE(fetched_caps->basic_info);
  EXPECT_EQ("printer1", fetched_caps->basic_info->id);
  ASSERT_TRUE(fetched_caps->capabilities);
  EXPECT_EQ(kPapers, fetched_caps->capabilities->papers);
}

// In this test we expect the `GetCapability` to bail early because the
// provided printer can't be found in the `CupsPrintersManager`.
TEST_P(LocalPrinterAshProcessScopeTest, GetCapabilityInvalidPrinter) {
  auto fetched_caps = crosapi::mojom::CapabilitiesResponse::New();
  local_printer_ash()->GetCapability(
      "invalid printer",
      base::BindOnce(&RecordGetCapability, std::ref(fetched_caps)));

  RunUntilIdle();

  EXPECT_FALSE(fetched_caps);
}

// Tests that no capabilities are returned if a printer is unreachable from
// CUPS. We simulate this behavior by not calling AddPrinter(), which registers
// a printer with the test backend.
TEST_P(LocalPrinterAshProcessScopeTest, GetCapabilityUnreachablePrinter) {
  Printer saved_printer =
      CreateTestPrinter("printer1", "saved", "description1");
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);
  printers_manager().MarkInstalled(saved_printer.id());

  crosapi::mojom::CapabilitiesResponsePtr fetched_caps;
  local_printer_ash()->GetCapability(
      /*destination_id=*/"printer1",
      base::BindOnce(&RecordGetCapability, std::ref(fetched_caps)));

  RunUntilIdle();

  ASSERT_TRUE(fetched_caps);
  ASSERT_TRUE(fetched_caps->basic_info);
  EXPECT_EQ("printer1", fetched_caps->basic_info->id);
  EXPECT_FALSE(fetched_caps->capabilities);
}

#if BUILDFLAG(ENABLE_OOP_PRINTING)

// Tests that fetching capabilities fails if the print backend service
// terminates early, such as it would from a crash.
TEST_F(LocalPrinterAshServiceTest, GetCapabilityTerminatedService) {
  Printer saved_printer =
      CreateTestPrinter("printer1", "saved", "description1");
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);
  printers_manager().MarkInstalled(saved_printer.id());

  // Add printer capabilities to `test_backend_`.
  AddPrinter("printer1", "saved", "description1", /*is_default=*/true,
             /*requires_elevated_permissions=*/false);

  // Set up for service to terminate on next use.
  SetTerminateServiceOnNextInteraction();

  crosapi::mojom::CapabilitiesResponsePtr fetched_caps;
  local_printer_ash()->GetCapability(
      /*destination_id=*/"printer1",
      base::BindOnce(&RecordGetCapability, std::ref(fetched_caps)));

  RunUntilIdle();

  ASSERT_TRUE(fetched_caps);
  ASSERT_TRUE(fetched_caps->basic_info);
  EXPECT_EQ("printer1", fetched_caps->basic_info->id);
  EXPECT_FALSE(fetched_caps->capabilities);
}

#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)

// Test that installed printers to which the user does not have permission to
// access will receive a dictionary for the capabilities but will not have any
// settings in that.
TEST_P(LocalPrinterAshProcessScopeTest, GetCapabilityAccessDenied) {
  Printer saved_printer =
      CreateTestPrinter("printer1", "saved", "description1");
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);
  printers_manager().MarkInstalled(saved_printer.id());

  // Add printer capabilities to `test_backend_`.
  AddPrinter("printer1", "saved", "description1", /*is_default=*/true,
             /*requires_elevated_permissions=*/true);

  crosapi::mojom::CapabilitiesResponsePtr fetched_caps;
  local_printer_ash()->GetCapability(
      "printer1", base::BindOnce(&RecordGetCapability, std::ref(fetched_caps)));

  RunUntilIdle();

  ASSERT_TRUE(fetched_caps);
  ASSERT_TRUE(fetched_caps->basic_info);
  EXPECT_EQ("printer1", fetched_caps->basic_info->id);
  EXPECT_FALSE(fetched_caps->capabilities);
}

#if BUILDFLAG(ENABLE_OOP_PRINTING)

TEST_F(LocalPrinterAshServiceTest, GetCapabilityElevatedPermissionsSucceeds) {
  Printer saved_printer =
      CreateTestPrinter("printer1", "saved", "description1");
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);
  printers_manager().MarkInstalled(saved_printer.id());

  // Add printer capabilities to `test_backend_`.
  AddPrinter("printer1", "saved", "description1", /*is_default=*/true,
             /*requires_elevated_permissions=*/true);

  // Note that printer does not initially show as requiring elevated privileges.
  EXPECT_FALSE(PrintBackendServiceManager::GetInstance()
                   .PrinterDriverFoundToRequireElevatedPrivilege("printer1"));

  crosapi::mojom::CapabilitiesResponsePtr fetched_caps;
  local_printer_ash()->GetCapability(
      "printer1", base::BindOnce(&RecordGetCapability, std::ref(fetched_caps)));

  RunUntilIdle();

  // Verify that this printer now shows up as requiring elevated privileges.
  EXPECT_TRUE(PrintBackendServiceManager::GetInstance()
                  .PrinterDriverFoundToRequireElevatedPrivilege("printer1"));

  // Getting capabilities should succeed when fallback is supported.
  ASSERT_TRUE(fetched_caps);
  ASSERT_TRUE(fetched_caps->basic_info);
  EXPECT_EQ("printer1", fetched_caps->basic_info->id);
  ASSERT_TRUE(fetched_caps->capabilities);
  EXPECT_EQ(kPapers, fetched_caps->capabilities->papers);
}

#endif  // BUILDFLAG(ENABLE_OOP_PRINTING)

// Test that fetching a PPD license will return a license if the printer has one
// available.
TEST_F(LocalPrinterAshTest, FetchValidEulaUrl) {
  Printer::PpdReference ref;
  ref.effective_make_and_model = "expected_make_model";

  // Printers with a `PpdReference` will return a license
  Printer saved_printer = CreateTestPrinterWithPpdReference(
      "printer1", "saved", "description1", ref);
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);
  printers_manager().MarkInstalled(saved_printer.id());

  GURL fetched_eula_url;
  local_printer_ash()->GetEulaUrl(
      "printer1",
      base::BindOnce(&RecordGetEulaUrl, std::ref(fetched_eula_url)));

  RunUntilIdle();

  EXPECT_EQ(fetched_eula_url, GURL("chrome://os-credits/#expected_make_model"));
}

// Test that a printer with no PPD license will return an empty string.
TEST_F(LocalPrinterAshTest, FetchNotFoundEulaUrl) {
  // A printer without a `PpdReference` will simulate a PPD without a license.
  Printer saved_printer =
      CreateTestPrinter("printer1", "saved", "description1");
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);
  printers_manager().MarkInstalled(saved_printer.id());

  GURL fetched_eula_url;
  local_printer_ash()->GetEulaUrl(
      "printer1",
      base::BindOnce(&RecordGetEulaUrl, std::ref(fetched_eula_url)));

  RunUntilIdle();

  EXPECT_TRUE(fetched_eula_url.is_empty());
}

// Test that fetching a PPD license will exit early if the printer is not found
// in `CupsPrintersManager`.
TEST_F(LocalPrinterAshTest, FetchEulaUrlOnNonExistantPrinter) {
  Printer saved_printer =
      CreateTestPrinter("printer1", "saved", "description1");

  GURL fetched_eula_url;
  local_printer_ash()->GetEulaUrl(
      "printer1",
      base::BindOnce(&RecordGetEulaUrl, std::ref(fetched_eula_url)));

  RunUntilIdle();

  EXPECT_TRUE(fetched_eula_url.is_empty());
}

TEST_F(LocalPrinterAshTest, GetPolicies_Unset) {
  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));
  EXPECT_EQ(crosapi::mojom::Policies::New(), policies);
}

TEST_F(LocalPrinterAshTest, GetPolicies_PaperSize) {
  auto* prefs = GetPrefs();
  base::Value::Dict paper_size;
  paper_size.Set(kPaperSizeName, "iso_a4_210x297mm");
  prefs->Set("printing.paper_size_default", base::Value(std::move(paper_size)));

  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));

  ASSERT_TRUE(policies);
  EXPECT_EQ(gfx::Size(210000, 297000), policies->paper_size_default);
}

TEST_F(LocalPrinterAshTest, GetPolicies_BackgroundGraphics) {
  auto* prefs = GetPrefs();
  // crosapi::mojom::Policies::BackgroundGraphicsModeRestriction::kDisabled
  prefs->SetInteger(prefs::kPrintingAllowedBackgroundGraphicsModes, 2);
  // crosapi::mojom::Policies::BackgroundGraphicsModeRestriction::kEnabled
  prefs->SetInteger(prefs::kPrintingBackgroundGraphicsDefault, 1);

  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));

  ASSERT_TRUE(policies);
  EXPECT_EQ(
      crosapi::mojom::Policies::BackgroundGraphicsModeRestriction::kDisabled,
      policies->allowed_background_graphics_modes);
  EXPECT_EQ(
      crosapi::mojom::Policies::BackgroundGraphicsModeRestriction::kEnabled,
      policies->background_graphics_default);
}

TEST_F(LocalPrinterAshTest, GetPolicies_MaxSheetsAllowed) {
  auto* prefs = GetPrefs();
  prefs->SetInteger(prefs::kPrintingMaxSheetsAllowed, 5);

  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));

  EXPECT_TRUE(policies->max_sheets_allowed_has_value);
  EXPECT_EQ(5u, policies->max_sheets_allowed);
}

// Zero sheets allowed is a valid policy.
TEST_F(LocalPrinterAshTest, GetPolicies_ZeroSheetsAllowed) {
  auto* prefs = GetPrefs();
  prefs->SetInteger(prefs::kPrintingMaxSheetsAllowed, 0);

  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));

  ASSERT_TRUE(policies);
  EXPECT_TRUE(policies->max_sheets_allowed_has_value);
  EXPECT_EQ(0u, policies->max_sheets_allowed);
}

// Negative sheets allowed is not a valid policy.
TEST_F(LocalPrinterAshTest, GetPolicies_NegativeMaxSheets) {
  auto* prefs = GetPrefs();
  prefs->SetInteger(prefs::kPrintingMaxSheetsAllowed, -1);

  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));

  ASSERT_TRUE(policies);
  EXPECT_FALSE(policies->max_sheets_allowed_has_value);
}

TEST_F(LocalPrinterAshTest, GetPolicies_PrintHeaderFooter_UnmanagedDisabled) {
  auto* prefs = GetPrefs();
  prefs->SetBoolean(prefs::kPrintHeaderFooter, false);
  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));
  ASSERT_TRUE(policies);
  EXPECT_EQ(crosapi::mojom::Policies::OptionalBool::kUnset,
            policies->print_header_footer_allowed);
  EXPECT_EQ(crosapi::mojom::Policies::OptionalBool::kFalse,
            policies->print_header_footer_default);
}

TEST_F(LocalPrinterAshTest, GetPolicies_PrintHeaderFooter_UnmanagedEnabled) {
  auto* prefs = GetPrefs();
  prefs->SetBoolean(prefs::kPrintHeaderFooter, true);
  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));
  ASSERT_TRUE(policies);
  EXPECT_EQ(crosapi::mojom::Policies::OptionalBool::kUnset,
            policies->print_header_footer_allowed);
  EXPECT_EQ(crosapi::mojom::Policies::OptionalBool::kTrue,
            policies->print_header_footer_default);
}

TEST_F(LocalPrinterAshTest, GetPolicies_PrintHeaderFooter_ManagedDisabled) {
  auto* prefs = GetPrefs();
  prefs->SetManagedPref(prefs::kPrintHeaderFooter,
                        std::make_unique<base::Value>(false));
  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));
  ASSERT_TRUE(policies);
  EXPECT_EQ(crosapi::mojom::Policies::OptionalBool::kFalse,
            policies->print_header_footer_allowed);
  EXPECT_EQ(crosapi::mojom::Policies::OptionalBool::kUnset,
            policies->print_header_footer_default);
}

TEST_F(LocalPrinterAshTest, GetPolicies_PrintHeaderFooter_ManagedEnabled) {
  auto* prefs = GetPrefs();
  prefs->SetManagedPref(prefs::kPrintHeaderFooter,
                        std::make_unique<base::Value>(true));
  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));
  ASSERT_TRUE(policies);
  EXPECT_EQ(crosapi::mojom::Policies::OptionalBool::kTrue,
            policies->print_header_footer_allowed);
  EXPECT_EQ(crosapi::mojom::Policies::OptionalBool::kUnset,
            policies->print_header_footer_default);
}

TEST_F(LocalPrinterAshTest, GetPolicies_Color) {
  const uint32_t expected_allowed_color_modes = static_cast<uint32_t>(
      static_cast<int32_t>(printing::mojom::ColorModeRestriction::kMonochrome) |
      static_cast<int32_t>(printing::mojom::ColorModeRestriction::kColor));
  auto* prefs = GetPrefs();
  prefs->SetInteger(prefs::kPrintingAllowedColorModes, 3);
  prefs->SetInteger(prefs::kPrintingColorDefault, 2);

  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));

  EXPECT_EQ(expected_allowed_color_modes, policies->allowed_color_modes);
  EXPECT_EQ(printing::mojom::ColorModeRestriction::kColor,
            policies->default_color_mode);
}

TEST_F(LocalPrinterAshTest, GetPolicies_Duplex) {
  const uint32_t expected_allowed_duplex_modes = static_cast<uint32_t>(
      static_cast<int32_t>(printing::mojom::DuplexModeRestriction::kSimplex) |
      static_cast<int32_t>(printing::mojom::DuplexModeRestriction::kDuplex));
  auto* prefs = GetPrefs();
  prefs->SetInteger(prefs::kPrintingAllowedDuplexModes, 7);
  prefs->SetInteger(prefs::kPrintingDuplexDefault, 1);

  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));

  EXPECT_EQ(expected_allowed_duplex_modes, policies->allowed_duplex_modes);
  EXPECT_EQ(printing::mojom::DuplexModeRestriction::kSimplex,
            policies->default_duplex_mode);
}

TEST_F(LocalPrinterAshTest, GetPolicies_Pin) {
  auto* prefs = GetPrefs();
  prefs->SetInteger(prefs::kPrintingAllowedPinModes, 1);
  prefs->SetInteger(prefs::kPrintingPinDefault, 2);

  crosapi::mojom::PoliciesPtr policies;
  local_printer_ash()->GetPolicies(base::BindLambdaForTesting(
      [&](crosapi::mojom::PoliciesPtr data) { policies = std::move(data); }));

  EXPECT_EQ(printing::mojom::PinModeRestriction::kPin,
            policies->allowed_pin_modes);
  EXPECT_EQ(printing::mojom::PinModeRestriction::kNoPin,
            policies->default_pin_mode);
}

TEST_F(LocalPrinterAshTest, GetUsernamePerPolicy_Allowed) {
  SetUsername("[email protected]");
  GetPrefs()->SetBoolean(prefs::kPrintingSendUsernameAndFilenameEnabled, true);
  std::optional<std::string> username;
  local_printer_ash()->GetUsernamePerPolicy(
      base::BindOnce(base::BindLambdaForTesting(
          [&](const std::optional<std::string>& uname) { username = uname; })));
  ASSERT_TRUE(username);
  EXPECT_EQ("[email protected]", *username);
}

TEST_F(LocalPrinterAshTest, GetUsernamePerPolicy_Denied) {
  SetUsername("[email protected]");
  GetPrefs()->SetBoolean(prefs::kPrintingSendUsernameAndFilenameEnabled, false);
  std::optional<std::string> username;
  local_printer_ash()->GetUsernamePerPolicy(
      base::BindOnce(base::BindLambdaForTesting(
          [&](const std::optional<std::string>& uname) { username = uname; })));
  EXPECT_EQ(std::nullopt, username);
}

// Verify the LocalPrintersObserver receives the full set of local printers
// when added or triggered.
TEST_F(LocalPrinterAshTest, LocalPrintersObserver) {
  Printer saved_printer =
      CreateTestPrinter("printer1", "saved", "description1");
  Printer enterprise_printer =
      CreateEnterprisePrinter("printer2", "enterprise", "description2");
  Printer automatic_printer =
      CreateTestPrinter("printer3", "automatic", "description3");

  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);
  printers_manager().AddPrinter(enterprise_printer, PrinterClass::kEnterprise);
  printers_manager().AddPrinter(automatic_printer, PrinterClass::kAutomatic);

  // Starting observation should return the 3 added printers.
  local_printer_ash()->AddLocalPrintersObserver(
      fake_local_printers_observer_.GenerateRemote(),
      base::BindLambdaForTesting(
          [&](std::vector<crosapi::mojom::LocalDestinationInfoPtr> printers) {
            EXPECT_EQ(3u, printers.size());
          }));

  base::test::TestFuture<std::vector<crosapi::mojom::LocalDestinationInfoPtr>>
      printers_future;
  fake_local_printers_observer_.GetLocalPrinters(printers_future.GetCallback());

  local_printer_ash()->OnLocalPrintersUpdated();
  EXPECT_EQ(3u, printers_future.Get().size());
}

TEST(LocalPrinterAsh, ConfigToMojom) {
  ash::PrintServersConfig config;
  config.fetching_mode = crosapi::mojom::PrintServersConfig::
      ServerPrintersFetchingMode::kSingleServerOnly;
  config.print_servers.emplace_back("id", GURL("http://localhost"), "name");
  crosapi::mojom::PrintServersConfigPtr mojom =
      crosapi::LocalPrinterAsh::ConfigToMojom(config);
  ASSERT_TRUE(mojom);
  EXPECT_EQ(crosapi::mojom::PrintServersConfig::ServerPrintersFetchingMode::
                kSingleServerOnly,
            mojom->fetching_mode);
  ASSERT_EQ(1u, mojom->print_servers.size());
  EXPECT_EQ("id", mojom->print_servers[0]->id);
  EXPECT_EQ(GURL("http://localhost"), mojom->print_servers[0]->url);
  EXPECT_EQ("name", mojom->print_servers[0]->name);
}

TEST(LocalPrinterAsh, PrinterToMojom) {
  Printer printer("id");
  printer.set_display_name("name");
  printer.set_description("description");
  chromeos::CupsPrinterStatus status("id");
  status.AddStatusReason(crosapi::mojom::StatusReason::Reason::kOutOfInk,
                         crosapi::mojom::StatusReason::Severity::kWarning);
  printer.set_printer_status(status);
  crosapi::mojom::LocalDestinationInfoPtr mojom =
      printing::PrinterToMojom(printer);
  ASSERT_TRUE(mojom);
  EXPECT_EQ("id", mojom->id);
  EXPECT_EQ("name", mojom->name);
  EXPECT_EQ("description", mojom->description);
  EXPECT_FALSE(mojom->configured_via_policy);

  EXPECT_EQ(printing::StatusToMojom(status), mojom->printer_status);
}

TEST(LocalPrinterAsh, PrinterToMojom_ConfiguredViaPolicy) {
  Printer printer("id");
  printer.set_source(Printer::SRC_POLICY);
  crosapi::mojom::LocalDestinationInfoPtr mojom =
      printing::PrinterToMojom(printer);
  ASSERT_TRUE(mojom);
  EXPECT_EQ("id", mojom->id);
  EXPECT_TRUE(mojom->configured_via_policy);
}

TEST(LocalPrinterAsh, StatusToMojom) {
  chromeos::CupsPrinterStatus status("id");
  status.AddStatusReason(crosapi::mojom::StatusReason::Reason::kOutOfInk,
                         crosapi::mojom::StatusReason::Severity::kWarning);
  crosapi::mojom::PrinterStatusPtr mojom = printing::StatusToMojom(status);
  ASSERT_TRUE(mojom);
  EXPECT_EQ("id", mojom->printer_id);
  EXPECT_EQ(status.GetTimestamp(), mojom->timestamp);
  ASSERT_EQ(1u, mojom->status_reasons.size());
  EXPECT_EQ(crosapi::mojom::StatusReason::Reason::kOutOfInk,
            mojom->status_reasons[0]->reason);
  EXPECT_EQ(crosapi::mojom::StatusReason::Severity::kWarning,
            mojom->status_reasons[0]->severity);
}

// Base testing class for `LocalPrinterAsh` with enabled OAuth2 feature.
class LocalPrinterAshWithOAuth2Test : public testing::Test {
 public:
  const std::vector<PrinterSemanticCapsAndDefaults::Paper> kPapers = {
      {"bar", "vendor", gfx::Size(600, 600), gfx::Rect(0, 0, 600, 600)}};

  LocalPrinterAshWithOAuth2Test() = default;
  LocalPrinterAshWithOAuth2Test(const LocalPrinterAshWithOAuth2Test&) = delete;
  LocalPrinterAshWithOAuth2Test& operator=(
      const LocalPrinterAshWithOAuth2Test&) = delete;
  ~LocalPrinterAshWithOAuth2Test() override = default;

  sync_preferences::TestingPrefServiceSyncable* GetPrefs() {
    return profile_.GetTestingPrefService();
  }

  void SetUp() override {
    ppd_provider_ = base::MakeRefCounted<FakePpdProvider>();
    ash::CupsPrintersManagerFactory::GetInstance()->SetTestingFactoryAndUse(
        &profile_,
        base::BindLambdaForTesting([this](content::BrowserContext* context)
                                       -> std::unique_ptr<KeyedService> {
          auto printers_manager =
              std::make_unique<ash::FakeCupsPrintersManager>();
          printers_manager_ = printers_manager.get();
          return printers_manager;
        }));
    ash::printing::oauth2::AuthorizationZonesManagerFactory::GetInstance()
        ->SetTestingFactoryAndUse(
            &profile_,
            base::BindLambdaForTesting([this](content::BrowserContext* context)
                                           -> std::unique_ptr<KeyedService> {
              auto auth_manager = std::make_unique<testing::StrictMock<
                  ash::printing::oauth2::MockAuthorizationZoneManager>>();
              auth_manager_ = auth_manager.get();
              return auth_manager;
            }));
    local_printer_ash_ =
        std::make_unique<TestLocalPrinterAsh>(&profile_, ppd_provider_);
  }

  GetOAuthAccessTokenResultPtr GetOAuthAccessToken(
      const std::string& printer_id) {
    base::test::TestFuture<GetOAuthAccessTokenResultPtr> future;
    local_printer_ash()->GetOAuthAccessToken("printer_id",
                                             future.GetCallback());
    return future.Take();
  }

 protected:
  ash::FakeCupsPrintersManager& printers_manager() {
    DCHECK(printers_manager_);
    return *printers_manager_;
  }

  ash::printing::oauth2::MockAuthorizationZoneManager& auth_manager() {
    DCHECK(auth_manager_);
    return *auth_manager_;
  }

  crosapi::LocalPrinterAsh* local_printer_ash() {
    return local_printer_ash_.get();
  }

 private:
  // Enables the OAuth2 feature.
  base::test::ScopedFeatureList feature_list_ =
      base::test::ScopedFeatureList(::ash::features::kEnableOAuthIpp);
  // Must outlive `profile_`.
  content::BrowserTaskEnvironment task_environment_;

  // Must outlive `printers_manager_`.
  TestingProfile profile_;
  raw_ptr<ash::FakeCupsPrintersManager> printers_manager_ = nullptr;
  raw_ptr<
      testing::StrictMock<ash::printing::oauth2::MockAuthorizationZoneManager>>
      auth_manager_ = nullptr;
  scoped_refptr<FakePpdProvider> ppd_provider_;
  std::unique_ptr<crosapi::LocalPrinterAsh> local_printer_ash_;
};

TEST_F(LocalPrinterAshWithOAuth2Test, GetOAuthAccessTokenUnknownPrinter) {
  const GetOAuthAccessTokenResultPtr result = GetOAuthAccessToken("printer_id");

  ASSERT_TRUE(result);
  EXPECT_TRUE(result->is_error());
}

TEST_F(LocalPrinterAshWithOAuth2Test, GetOAuthAccessTokenNonOAuthPrinter) {
  Printer saved_printer =
      CreateTestPrinter("printer_id", "saved", "description1");
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);

  chromeos::CupsPrinterStatus printer_status("printer_id");
  printers_manager().SetPrinterStatus(printer_status);

  const GetOAuthAccessTokenResultPtr result = GetOAuthAccessToken("printer_id");

  ASSERT_TRUE(result);
  EXPECT_TRUE(result->is_none());
}

TEST_F(LocalPrinterAshWithOAuth2Test, GetOAuthAccessTokenOAuthConnectionError) {
  Printer saved_printer =
      CreateTestPrinter("printer_id", "saved", "description1");
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);

  chromeos::CupsPrinterStatus printer_status("printer_id");
  printer_status.SetAuthenticationInfo({"https://server/url", "scope"});
  printers_manager().SetPrinterStatus(printer_status);

  EXPECT_CALL(auth_manager(), GetEndpointAccessToken(testing::_, testing::_,
                                                     "scope", testing::_))
      .WillOnce([](const GURL& auth_server, const chromeos::Uri& ipp_endpoint,
                   const std::string& scope,
                   ash::printing::oauth2::StatusCallback callback) {
        EXPECT_EQ(auth_server.spec(), "https://server/url");
        std::move(callback).Run(
            ash::printing::oauth2::StatusCode::kConnectionError,
            "error_message");
      });

  const GetOAuthAccessTokenResultPtr result = GetOAuthAccessToken("printer_id");

  ASSERT_TRUE(result);
  EXPECT_TRUE(result->is_error());
}

TEST_F(LocalPrinterAshWithOAuth2Test, GetOAuthAccessTokenSuccess) {
  Printer saved_printer =
      CreateTestPrinter("printer_id", "saved", "description1");
  printers_manager().AddPrinter(saved_printer, PrinterClass::kSaved);

  chromeos::CupsPrinterStatus printer_status("printer_id");
  printer_status.SetAuthenticationInfo({"https://server/url", "scope"});
  printers_manager().SetPrinterStatus(printer_status);

  EXPECT_CALL(auth_manager(), GetEndpointAccessToken(testing::_, testing::_,
                                                     "scope", testing::_))
      .WillOnce([](const GURL& auth_server, const chromeos::Uri& ipp_endpoint,
                   const std::string& scope,
                   ash::printing::oauth2::StatusCallback callback) {
        EXPECT_EQ(auth_server.spec(), "https://server/url");
        std::move(callback).Run(ash::printing::oauth2::StatusCode::kOK,
                                "access_token");
      });

  const GetOAuthAccessTokenResultPtr result = GetOAuthAccessToken("printer_id");

  ASSERT_TRUE(result);
  ASSERT_TRUE(result->is_token());
  EXPECT_EQ(result->get_token()->token, "access_token");
}

// LocalPrinterAsh implementation that overrides `GetIppClientInfoCalculator()`
// for testing.
class TestLocalPrinterAshWithClientInfoCalculator : public TestLocalPrinterAsh {
 public:
  TestLocalPrinterAshWithClientInfoCalculator(
      Profile* profile,
      scoped_refptr<chromeos::PpdProvider> ppd_provider,
      ash::printing::IppClientInfoCalculator* client_info_calculator)
      : TestLocalPrinterAsh(profile, ppd_provider),
        client_info_calculator_(client_info_calculator) {}
  TestLocalPrinterAshWithClientInfoCalculator(
      const TestLocalPrinterAshWithClientInfoCalculator&) = delete;
  TestLocalPrinterAshWithClientInfoCalculator& operator=(
      const TestLocalPrinterAshWithClientInfoCalculator&) = delete;
  ~TestLocalPrinterAshWithClientInfoCalculator() override = default;

  ash::printing::IppClientInfoCalculator* GetIppClientInfoCalculator()
      override {
    return client_info_calculator_;
  }

 private:
  raw_ptr<ash::printing::IppClientInfoCalculator> client_info_calculator_;
};

struct MockIppClientInfoCalculator : ash::printing::IppClientInfoCalculator {
  MOCK_METHOD(printing::mojom::IppClientInfoPtr, GetOsInfo, (), (const));
  MOCK_METHOD(printing::mojom::IppClientInfoPtr, GetDeviceInfo, (), (const));
};

//
class LocalPrinterAshWithIppClientInfoTest : public LocalPrinterAshTest {
 public:
  LocalPrinterAshWithIppClientInfoTest() = default;
  LocalPrinterAshWithIppClientInfoTest(
      const LocalPrinterAshWithIppClientInfoTest&) = delete;
  LocalPrinterAshWithIppClientInfoTest& operator=(
      const LocalPrinterAshWithIppClientInfoTest&) = delete;
  ~LocalPrinterAshWithIppClientInfoTest() override = default;

  void SetUp() override {
    fake_user_manager()->AddUserWithAffiliation(kAffiliatedUserAccountId,
                                                /*is_affiliated=*/true);
    fake_user_manager()->AddUserWithAffiliation(kUnaffiliatedUserAccountId,
                                                /*is_affiliated=*/false);
    SetActiveUser(kUnaffiliatedUserAccountId);
    auto ppd_provider = base::MakeRefCounted<FakePpdProvider>();
    ash::CupsPrintersManagerFactory::GetInstance()->SetTestingFactoryAndUse(
        &profile_,
        base::BindLambdaForTesting([this](content::BrowserContext* context)
                                       -> std::unique_ptr<KeyedService> {
          auto printers_manager =
              std::make_unique<ash::FakeCupsPrintersManager>();
          printers_manager_ = printers_manager.get();
          return printers_manager;
        }));
    local_printer_ash_ =
        std::make_unique<TestLocalPrinterAshWithClientInfoCalculator>(
            &profile_, ppd_provider, &client_info_calculator_);
  }

  void SetActiveUser(AccountId account_id) {
    fake_user_manager()->SwitchActiveUser(account_id);
  }

  std::vector<IppClientInfoPtr> GetIppClientInfo(
      const std::string& printer_id) {
    base::test::TestFuture<std::vector<IppClientInfoPtr>> future;
    local_printer_ash()->GetIppClientInfo(printer_id, future.GetCallback());
    return future.Take();
  }

 protected:
  ash::FakeCupsPrintersManager& printers_manager() {
    DCHECK(printers_manager_);
    return *printers_manager_;
  }

  crosapi::LocalPrinterAsh* local_printer_ash() {
    return local_printer_ash_.get();
  }

  MockIppClientInfoCalculator& client_info_calculator() {
    return client_info_calculator_;
  }

 private:
  // Must outlive `printers_manager_`.
  TestingProfile profile_;
  raw_ptr<ash::FakeCupsPrintersManager> printers_manager_ = nullptr;
  NiceMock<MockIppClientInfoCalculator> client_info_calculator_;
  std::unique_ptr<crosapi::LocalPrinterAsh> local_printer_ash_;
};

// Checks that `GetIppClientInfo()` returns an empty result if called with a
// `printer_id` that has no associated printer.
TEST_F(LocalPrinterAshWithIppClientInfoTest, GetIppClientInfoMissingPrinter) {
  const std::vector<IppClientInfoPtr> result = GetIppClientInfo("id");

  ASSERT_TRUE(result.empty());
}

// Checks that `GetIppClientInfo()` returns only OS info for a printer that uses
// an insecure protocol.
TEST_F(LocalPrinterAshWithIppClientInfoTest, GetIppClientInfoInsecurePrinter) {
  SetActiveUser(kAffiliatedUserAccountId);
  Printer printer = CreateTestPrinter("id", "name", "description");
  printer.SetUri("ipp://localhost");
  printer.set_source(Printer::Source::SRC_POLICY);
  printers_manager().AddPrinter(printer, PrinterClass::kSaved);

  const mojom::IppClientInfoPtr expected = mojom::IppClientInfo::New(
      mojom::IppClientInfo::ClientType::kOperatingSystem, "ChromeOS",
      std::nullopt, "1.2.3", std::nullopt);
  EXPECT_CALL(client_info_calculator(), GetOsInfo)
      .WillOnce(Return(ByMove(expected.Clone())));
  const std::vector<IppClientInfoPtr> result = GetIppClientInfo("id");

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0], expected);
}

// Checks that `GetIppClientInfo()` returns only OS info if the current user is
// unaffiliated.
TEST_F(LocalPrinterAshWithIppClientInfoTest, GetIppClientInfoUnaffiliatedUser) {
  SetActiveUser(kUnaffiliatedUserAccountId);
  Printer printer = CreateTestPrinter("id", "name", "description");
  printer.SetUri("ipps://localhost");
  printer.set_source(Printer::Source::SRC_POLICY);
  printers_manager().AddPrinter(printer, PrinterClass::kSaved);

  const mojom::IppClientInfoPtr expected = mojom::IppClientInfo::New(
      mojom::IppClientInfo::ClientType::kOperatingSystem, "ChromeOS",
      std::nullopt, "1.2.3", std::nullopt);
  EXPECT_CALL(client_info_calculator(), GetOsInfo)
      .WillOnce(Return(ByMove(expected.Clone())));
  const std::vector<IppClientInfoPtr> result = GetIppClientInfo("id");

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0], expected);
}

// Checks that `GetIppClientInfo()` returns only OS info for an unmanaged
// printer.
TEST_F(LocalPrinterAshWithIppClientInfoTest, GetIppClientInfoUnmanagedPrinter) {
  SetActiveUser(kAffiliatedUserAccountId);
  Printer printer = CreateTestPrinter("id", "name", "description");
  printer.SetUri("ipps://localhost");
  printer.set_source(Printer::Source::SRC_USER_PREFS);
  printers_manager().AddPrinter(printer, PrinterClass::kSaved);

  const mojom::IppClientInfoPtr expected = mojom::IppClientInfo::New(
      mojom::IppClientInfo::ClientType::kOperatingSystem, "ChromeOS",
      std::nullopt, "1.2.3", std::nullopt);
  EXPECT_CALL(client_info_calculator(), GetOsInfo)
      .WillOnce(Return(ByMove(expected.Clone())));
  const std::vector<IppClientInfoPtr> result = GetIppClientInfo("id");

  ASSERT_EQ(result.size(), 1u);
  EXPECT_EQ(result[0], expected);
}

// Checks that `GetIppClientInfo()` returns both OS info and device info for a
// managed printer that uses a secure protocol if the current user is
// affiliated.
TEST_F(LocalPrinterAshWithIppClientInfoTest,
       GetIppClientInfoSecureManagedPrinterAffiliatedUser) {
  SetActiveUser(kAffiliatedUserAccountId);
  Printer printer = CreateTestPrinter("id", "name", "description");
  printer.SetUri("ipps://localhost");
  printer.set_source(Printer::Source::SRC_POLICY);
  printers_manager().AddPrinter(printer, PrinterClass::kSaved);

  const mojom::IppClientInfo expected_os_info =
      mojom::IppClientInfo(mojom::IppClientInfo::ClientType::kOperatingSystem,
                           "ChromeOS", std::nullopt, "1.2.3", std::nullopt);
  const mojom::IppClientInfo expected_device_info =
      mojom::IppClientInfo(mojom::IppClientInfo::ClientType::kOther, "abc",
                           std::nullopt, "", std::nullopt);
  EXPECT_CALL(client_info_calculator(), GetOsInfo)
      .WillOnce(Return(ByMove(expected_os_info.Clone())));
  EXPECT_CALL(client_info_calculator(), GetDeviceInfo)
      .WillOnce(Return(ByMove(expected_device_info.Clone())));
  const std::vector<IppClientInfoPtr> result = GetIppClientInfo("id");

  EXPECT_THAT(result, testing::UnorderedElementsAre(
                          testing::Pointee(expected_os_info),
                          testing::Pointee(expected_device_info)));
}

}  // namespace printing