chromium/chrome/browser/ash/printing/usb_printer_detector_unittest.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 "chrome/browser/ash/printing/usb_printer_detector.h"

#include <utility>
#include <vector>

#include "ash/public/cpp/session/session_controller.h"
#include "ash/public/cpp/session/session_observer.h"
#include "ash/public/cpp/test/mock_session_controller.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/device/public/cpp/test/fake_usb_device_info.h"
#include "services/device/public/cpp/test/fake_usb_device_manager.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "services/device/public/mojom/usb_manager.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {
namespace {

constexpr uint8_t kPrinterInterfaceClass = 7;
constexpr uint8_t kPrinterInterfaceSubclass = 1;
constexpr uint8_t kPrinterIppUsbProtocol = 4;

constexpr uint8_t kPrinterUsbProtocol = 0xff;
constexpr uint16_t kUsbVenderId = 0x02ad;
constexpr uint16_t kUsbProductId = 0x138c;
constexpr uint8_t kUsbDeviceClass = 0x09;

scoped_refptr<device::FakeUsbDeviceInfo> CreateFakeUsbPrinter(
    bool is_ipp_supported) {
  std::vector<device::mojom::UsbConfigurationInfoPtr> configs;
  configs.push_back(device::mojom::UsbConfigurationInfo::New());
  configs[0]->interfaces.push_back(device::mojom::UsbInterfaceInfo::New());

  auto alternate = device::mojom::UsbAlternateInterfaceInfo::New();
  alternate->class_code = kPrinterInterfaceClass;
  alternate->subclass_code = kPrinterInterfaceSubclass;
  alternate->protocol_code =
      is_ipp_supported ? kPrinterIppUsbProtocol : kPrinterUsbProtocol;

  configs[0]->interfaces[0]->alternates.push_back(std::move(alternate));
  return base::MakeRefCounted<device::FakeUsbDeviceInfo>(
      kUsbVenderId, kUsbProductId, kUsbDeviceClass, std::move(configs));
}

class UsbPrinterDetectorTest : public testing::Test {
 protected:
  class FakePrinterDetectorClient {
   public:
    FakePrinterDetectorClient() = default;

    void WaitForPrinters() {
      base::RunLoop run_loop;
      done_callback_ = run_loop.QuitClosure();
      run_loop.Run();
    }

    void OnPrintersFound(
        const std::vector<PrinterDetector::DetectedPrinter>& printers) {
      if (done_callback_)
        std::move(done_callback_).Run();
    }

   private:
    base::OnceClosure done_callback_;
  };

  UsbPrinterDetectorTest() {
    ON_CALL(this->session_controller_, IsScreenLocked).WillByDefault([this]() {
      return this->screen_locked_;
    });
    ON_CALL(this->session_controller_, AddObserver)
        .WillByDefault([this](ash::SessionObserver* observer) {
          this->observer_ = observer;
        });
    ON_CALL(this->session_controller_, RemoveObserver)
        .WillByDefault([this](ash::SessionObserver* observer) {
          this->observer_ = nullptr;
        });

    mojo::PendingRemote<device::mojom::UsbDeviceManager> manager;
    usb_manager_.AddReceiver(manager.InitWithNewPipeAndPassReceiver());

    detector_ = UsbPrinterDetector::CreateForTesting(std::move(manager),
                                                     &session_controller_);
    detector_->RegisterPrintersFoundCallback(
        base::BindRepeating(&FakePrinterDetectorClient::OnPrintersFound,
                            base::Unretained(&detector_client_)));

    fake_non_printer_ = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
        kUsbVenderId, kUsbProductId);
    fake_printer_ = CreateFakeUsbPrinter(false);
    fake_printer_ipp_ = CreateFakeUsbPrinter(true);

    base::RunLoop().RunUntilIdle();
  }

  base::test::TaskEnvironment task_environment_;
  bool screen_locked_ = false;
  raw_ptr<ash::SessionObserver> observer_ = nullptr;
  testing::NiceMock<ash::MockSessionController> session_controller_;
  std::unique_ptr<UsbPrinterDetector> detector_;
  FakePrinterDetectorClient detector_client_;
  device::FakeUsbDeviceManager usb_manager_;
  scoped_refptr<device::FakeUsbDeviceInfo> fake_non_printer_;
  scoped_refptr<device::FakeUsbDeviceInfo> fake_printer_;
  scoped_refptr<device::FakeUsbDeviceInfo> fake_printer_ipp_;
};

// Test GetPrinters().
TEST_F(UsbPrinterDetectorTest, GetPrinters) {
  EXPECT_EQ(0u, detector_->GetPrinters().size());

  usb_manager_.AddDevice(fake_non_printer_);
  usb_manager_.AddDevice(fake_printer_);
  usb_manager_.AddDevice(fake_printer_ipp_);

  detector_client_.WaitForPrinters();
  EXPECT_EQ(1u, detector_->GetPrinters().size());

  detector_client_.WaitForPrinters();
  EXPECT_EQ(2u, detector_->GetPrinters().size());
}

// Test OnPrintersFound callback on deviceAdded/Removed.
TEST_F(UsbPrinterDetectorTest, OnPrintersFoundCallback) {
  EXPECT_EQ(0u, detector_->GetPrinters().size());

  usb_manager_.AddDevice(fake_non_printer_);
  usb_manager_.AddDevice(fake_printer_);
  usb_manager_.AddDevice(fake_printer_ipp_);

  detector_client_.WaitForPrinters();
  EXPECT_EQ(1u, detector_->GetPrinters().size());

  detector_client_.WaitForPrinters();
  EXPECT_EQ(2u, detector_->GetPrinters().size());

  usb_manager_.RemoveDevice(fake_non_printer_);
  usb_manager_.RemoveDevice(fake_printer_);
  usb_manager_.RemoveDevice(fake_printer_ipp_);

  detector_client_.WaitForPrinters();
  EXPECT_EQ(1u, detector_->GetPrinters().size());

  detector_client_.WaitForPrinters();
  EXPECT_EQ(0u, detector_->GetPrinters().size());
}

// New printers are not announced as long as the screen is locked.
TEST_F(UsbPrinterDetectorTest, NewPrintersHoldWhenTheScreenIsLocked) {
  screen_locked_ = true;

  usb_manager_.AddDevice(fake_printer_);
  usb_manager_.AddDevice(fake_printer_ipp_);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0u, detector_->GetPrinters().size());

  ASSERT_NE(observer_, nullptr);
  observer_->OnLockStateChanged(screen_locked_);
  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(0u, detector_->GetPrinters().size());

  screen_locked_ = false;
  observer_->OnLockStateChanged(screen_locked_);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(2u, detector_->GetPrinters().size());
}

}  // namespace
}  // namespace ash