// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_BROWSER_USB_USB_TEST_UTILS_H_
#define CONTENT_BROWSER_USB_USB_TEST_UTILS_H_

#include <memory>
#include <vector>

#include "base/containers/span.h"
#include "base/observer_list.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/usb_chooser.h"
#include "content/public/browser/usb_delegate.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "services/device/public/mojom/usb_enumeration_options.mojom-forward.h"
#include "services/device/public/mojom/usb_manager.mojom-forward.h"
#include "services/device/public/mojom/usb_manager_client.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/mojom/usb/web_usb_service.mojom.h"
#include "url/origin.h"

namespace content {

class RenderFrameHost;

// A mock UsbDeviceManagerClient implementation that can be used to listen for
// USB device connection events.
class MockDeviceManagerClient : public device::mojom::UsbDeviceManagerClient {
 public:
  MockDeviceManagerClient();
  MockDeviceManagerClient(MockDeviceManagerClient&) = delete;
  MockDeviceManagerClient& operator=(MockDeviceManagerClient&) = delete;
  ~MockDeviceManagerClient() override;

  void Bind(
      mojo::PendingAssociatedReceiver<device::mojom::UsbDeviceManagerClient>
          receiver) {
    receiver_.Bind(std::move(receiver));
  }

  mojo::PendingAssociatedRemote<device::mojom::UsbDeviceManagerClient>
  CreateInterfacePtrAndBind() {
    auto client = receiver_.BindNewEndpointAndPassRemote();
    receiver_.set_disconnect_handler(base::BindOnce(
        &MockDeviceManagerClient::OnConnectionError, base::Unretained(this)));
    return client;
  }

  MOCK_METHOD(void, OnDeviceAdded, (device::mojom::UsbDeviceInfoPtr));
  MOCK_METHOD(void, OnDeviceRemoved, (device::mojom::UsbDeviceInfoPtr));

  MOCK_METHOD(void, ConnectionError, ());
  void OnConnectionError() {
    receiver_.reset();
    ConnectionError();
  }

 private:
  mojo::AssociatedReceiver<device::mojom::UsbDeviceManagerClient> receiver_{
      this};
};

// A UsbDelegate implementation that can be mocked for tests.
class MockUsbDelegate : public UsbDelegate {
 public:
  MockUsbDelegate();
  MockUsbDelegate(MockUsbDelegate&) = delete;
  MockUsbDelegate& operator=(MockUsbDelegate&) = delete;
  ~MockUsbDelegate() override;

  // Simulates opening the USB device chooser dialog and selecting an item. The
  // chooser automatically selects the item returned by RunChooserInternal,
  // which may be mocked. Returns `nullptr`. `options` is ignored.
  std::unique_ptr<UsbChooser> RunChooser(
      RenderFrameHost& frame,
      blink::mojom::WebUsbRequestDeviceOptionsPtr options,
      blink::mojom::WebUsbService::GetPermissionCallback callback) override;

  void AddObserver(BrowserContext* browser_context,
                   Observer* observer) override;
  void RemoveObserver(BrowserContext* browser_context,
                      Observer* observer) override;

  // Simulate events from tests.
  void OnDeviceAdded(const device::mojom::UsbDeviceInfo& device);
  void OnDeviceRemoved(const device::mojom::UsbDeviceInfo& device);
  void OnPermissionRevoked(const url::Origin& origin);
  void OnDeviceManagerConnectionError();

  MOCK_METHOD0(RunChooserInternal, device::mojom::UsbDeviceInfoPtr());
  MOCK_METHOD4(AdjustProtectedInterfaceClasses,
               void(BrowserContext*,
                    const url::Origin&,
                    RenderFrameHost*,
                    std::vector<uint8_t>&));
  MOCK_METHOD1(PageMayUseUsb, bool(Page&));
  MOCK_METHOD2(CanRequestDevicePermission,
               bool(BrowserContext*, const url::Origin&));
  MOCK_METHOD3(RevokeDevicePermissionWebInitiated,
               void(BrowserContext*,
                    const url::Origin&,
                    const device::mojom::UsbDeviceInfo&));
  MOCK_METHOD2(GetDeviceInfo,
               const device::mojom::UsbDeviceInfo*(BrowserContext*,
                                                   const std::string& guid));
  MOCK_METHOD4(HasDevicePermission,
               bool(BrowserContext*,
                    RenderFrameHost*,
                    const url::Origin&,
                    const device::mojom::UsbDeviceInfo&));
  MOCK_METHOD2(GetDevices,
               void(BrowserContext*,
                    blink::mojom::WebUsbService::GetDevicesCallback));
  MOCK_METHOD5(GetDevice,
               void(BrowserContext*,
                    const std::string&,
                    base::span<const uint8_t>,
                    mojo::PendingReceiver<device::mojom::UsbDevice>,
                    mojo::PendingRemote<device::mojom::UsbDeviceClient>));
  MOCK_METHOD2(SetDeviceManagerForTesting,
               void(RenderFrameHost&,
                    mojo::PendingRemote<device::mojom::UsbDeviceManager>
                        device_manager));
  MOCK_METHOD1(IsServiceWorkerAllowedForOrigin, bool(const url::Origin&));
  MOCK_METHOD2(IncrementConnectionCount,
               void(BrowserContext*, const url::Origin&));
  MOCK_METHOD2(DecrementConnectionCount,
               void(BrowserContext*, const url::Origin&));

  const base::ObserverList<Observer>& observer_list() { return observer_list_; }

  void SetAssertBrowserContext(bool assert_browser_context);

 private:
  base::ObserverList<UsbDelegate::Observer> observer_list_;
  bool assert_browser_context_ = false;
};

template <typename SuperClass>
class UsbTestContentBrowserClientBase : public SuperClass {
 public:
  MockUsbDelegate& delegate() { return delegate_; }

  // ContentBrowserClient:
  UsbDelegate* GetUsbDelegate() override { return &delegate_; }

 private:
  testing::NiceMock<MockUsbDelegate> delegate_;
};

using UsbTestContentBrowserClient =
    UsbTestContentBrowserClientBase<ContentBrowserClient>;

}  // namespace content

#endif  // CONTENT_BROWSER_USB_USB_TEST_UTILS_H_
