// 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 "content/browser/bluetooth/web_bluetooth_pairing_manager_impl.h"

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/notreached.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "content/browser/bluetooth/web_bluetooth_pairing_manager.h"
#include "content/browser/bluetooth/web_bluetooth_pairing_manager_delegate.h"
#include "content/public/browser/bluetooth_delegate.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

using ::base::test::SingleThreadTaskEnvironment;
using ::base::test::TestFuture;
using ::blink::WebBluetoothDeviceId;
using ::blink::mojom::WebBluetoothResult;
using ::blink::mojom::WebBluetoothService;
using ::blink::mojom::WebBluetoothWriteType;
using ::device::BluetoothDevice;
using ::device::MockBluetoothDevice;
using ::testing::Return;
using PairPromptResult = BluetoothDelegate::PairPromptResult;

/**
 * A collection of related Bluetooth test data.
 */
struct TestData {
  const std::string characteristic_instance_id;
  const std::string descriptor_instance_id;
  const WebBluetoothDeviceId device_id;
  const std::string device_address;
  const std::string device_name;
  const std::string pincode;
};

// Test instance data that are valid and are the ID's of the fake objects
// managed by these tests.
const TestData kValidTestData = {
    .characteristic_instance_id = "valid-test-characteristic-id",
    .descriptor_instance_id = "valid-test-descriptor-id",
    .device_id = WebBluetoothDeviceId("000000000000000000000A=="),
    .device_address = "00:11:22:33:44:55",
    .device_name = "test device",
    .pincode = "123456",
};

// All valid data, but does not represent any object managed by these tests.
const TestData kValidNonTestData = {
    .characteristic_instance_id = "valid-non-test-characteristic-id",
    .descriptor_instance_id = "valid-non-test-descriptor-id",
    .device_id = WebBluetoothDeviceId("111111111111111111111A=="),
    .device_address = "66:77:88:99:00:11",
    .device_name = "non test device",
    .pincode = "789012",
};

const TestData kInvalidTestData;

}  // namespace

class BluetoothPairingManagerTest : public testing::Test,
                                    public WebBluetoothPairingManagerDelegate {
 public:
  enum class AuthBehavior {
    kSucceedFirst,   // Successfully authenticate on first pair attempt.
    kSucceedSecond,  // Successfully authenticate on second pair attempt.
    kFailAll,        // Fail authentication on all pair attempts.
    kSuspend,        // Suspend in-progress authentication.
    kFirstSuspend,   // First auth is suspended, following will succeed.
    kUnspecified,    // Initial (error) behavior.
  };

  BluetoothPairingManagerTest()
      : characteristic_value_{1, 2, 3, 5},
        descriptor_value_{4, 8, 15, 16, 23, 42},
        pairing_manager_(
            std::make_unique<WebBluetoothPairingManagerImpl>(this)) {}
  ~BluetoothPairingManagerTest() override = default;

  void SetUp() override {}

  WebBluetoothDeviceId GetCharacteristicDeviceID(
      const std::string& characteristic_instance_id) override {
    if (characteristic_instance_id == kValidTestData.characteristic_instance_id)
      return kValidTestData.device_id;
    return kInvalidTestData.device_id;
  }

  WebBluetoothDeviceId GetDescriptorDeviceId(
      const std::string& descriptor_instance_id) override {
    if (descriptor_instance_id == kValidTestData.descriptor_instance_id)
      return kValidTestData.device_id;
    return kInvalidTestData.device_id;
  }

  WebBluetoothDeviceId GetWebBluetoothDeviceId(
      const std::string& device_address) override {
    if (device_address == kValidTestData.device_address)
      return kValidTestData.device_id;
    return kInvalidTestData.device_id;
  }

  void PairDevice(const WebBluetoothDeviceId& device_id,
                  BluetoothDevice::PairingDelegate* pairing_delegate,
                  BluetoothDevice::ConnectCallback callback) override {
    ASSERT_TRUE(device_id.IsValid());
    EXPECT_EQ(device_id, kValidTestData.device_id);
    num_pair_attempts_++;

    switch (auth_behavior_) {
      case AuthBehavior::kSucceedFirst:
        EXPECT_EQ(1, num_pair_attempts_);
        device_paired_ = true;
        std::move(callback).Run(/*error_code=*/std::nullopt);
        break;
      case AuthBehavior::kSucceedSecond:
        switch (num_pair_attempts_) {
          case 1:
            std::move(callback).Run(BluetoothDevice::ERROR_AUTH_REJECTED);
            break;
          case 2:
            device_paired_ = true;
            std::move(callback).Run(/*error_code=*/std::nullopt);
            break;
          default:
            NOTREACHED();
        }
        break;
      case AuthBehavior::kFailAll:
        std::move(callback).Run(BluetoothDevice::ERROR_AUTH_REJECTED);
        break;
      case AuthBehavior::kSuspend:
        EXPECT_TRUE(pair_callback_.is_null());
        pair_callback_ = std::move(callback);
        break;
      case AuthBehavior::kFirstSuspend:
        if (num_pair_attempts_ == 1) {
          EXPECT_TRUE(pair_callback_.is_null());
          pair_callback_ = std::move(callback);
        } else {
          device_paired_ = true;
          std::move(callback).Run(/*error_code=*/std::nullopt);
        }
        break;
      case AuthBehavior::kUnspecified:
        NOTREACHED() << "Test must set auth behavior";
    }
  }

  void CancelPairing(const WebBluetoothDeviceId& device_id) override {
    ASSERT_TRUE(device_id.IsValid());
    EXPECT_EQ(device_id, kValidTestData.device_id);
    std::move(pair_callback_).Run(BluetoothDevice::ERROR_AUTH_CANCELED);
  }

  void SetPinCode(const blink::WebBluetoothDeviceId& device_id,
                  const std::string& pincode) override {
    ASSERT_TRUE(device_id.IsValid());
    EXPECT_EQ(device_id, kValidTestData.device_id);
    std::move(pair_callback_).Run(/*error_code=*/std::nullopt);
  }

  void PairConfirmed(const blink::WebBluetoothDeviceId& device_id) override {
    ASSERT_TRUE(device_id.IsValid());
    EXPECT_EQ(device_id, kValidTestData.device_id);
    std::move(pair_callback_).Run(/*error_code=*/std::nullopt);
  }

  void ResumeSuspendedPairingWithSuccess() {
    device_paired_ = true;
    EXPECT_FALSE(pair_callback_.is_null());
    std::move(pair_callback_).Run(/*error_code=*/std::nullopt);
  }

  void RemoteCharacteristicReadValue(
      const std::string& characteristic_instance_id,
      WebBluetoothService::RemoteCharacteristicReadValueCallback callback)
      override {
    if (characteristic_instance_id !=
        kValidTestData.characteristic_instance_id) {
      std::move(callback).Run(WebBluetoothResult::CHARACTERISTIC_NOT_FOUND,
                              characteristic_value_);
      return;
    }
    if (device_paired_) {
      std::move(callback).Run(WebBluetoothResult::SUCCESS,
                              characteristic_value_);
      return;
    }

    std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED,
                            /*value=*/{});
  }

  void RemoteDescriptorReadValue(
      const std::string& descriptor_instance_id,
      WebBluetoothService::RemoteDescriptorReadValueCallback callback)
      override {
    if (descriptor_instance_id != kValidTestData.descriptor_instance_id) {
      std::move(callback).Run(WebBluetoothResult::DESCRIPTOR_NOT_FOUND,
                              descriptor_value_);
      return;
    }
    if (device_paired_) {
      std::move(callback).Run(WebBluetoothResult::SUCCESS, descriptor_value_);
      return;
    }

    std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED,
                            /*value=*/{});
  }

  void RemoteCharacteristicWriteValue(
      const std::string& characteristic_instance_id,
      base::span<const uint8_t> value,
      WebBluetoothWriteType write_type,
      WebBluetoothService::RemoteCharacteristicWriteValueCallback callback)
      override {
    if (characteristic_instance_id !=
        kValidTestData.characteristic_instance_id) {
      std::move(callback).Run(WebBluetoothResult::CHARACTERISTIC_NOT_FOUND);
      return;
    }
    if (device_paired_) {
      characteristic_value_ = std::vector<uint8_t>(value.begin(), value.end());
      std::move(callback).Run(WebBluetoothResult::SUCCESS);
      return;
    }

    std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED);
  }

  void RemoteDescriptorWriteValue(
      const std::string& descriptor_instance_id,
      base::span<const uint8_t> value,
      WebBluetoothService::RemoteDescriptorWriteValueCallback callback)
      override {
    if (descriptor_instance_id != kValidTestData.descriptor_instance_id) {
      std::move(callback).Run(WebBluetoothResult::DESCRIPTOR_NOT_FOUND);
      return;
    }
    if (device_paired_) {
      descriptor_value_ = std::vector<uint8_t>(value.begin(), value.end());
      std::move(callback).Run(WebBluetoothResult::SUCCESS);
      return;
    }

    std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED);
  }

  void RemoteCharacteristicStartNotificationsInternal(
      const std::string& characteristic_instance_id,
      mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient>
          client,
      blink::mojom::WebBluetoothService::
          RemoteCharacteristicStartNotificationsCallback callback) override {
    if (characteristic_instance_id !=
        kValidTestData.characteristic_instance_id) {
      std::move(callback).Run(WebBluetoothResult::CHARACTERISTIC_NOT_FOUND);
      return;
    }
    if (device_paired_) {
      std::move(callback).Run(WebBluetoothResult::SUCCESS);
      return;
    }

    std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED);
  }

  void PromptForBluetoothPairing(
      const std::u16string& device_identifier,
      BluetoothDelegate::PairPromptCallback callback,
      BluetoothDelegate::PairingKind pairing_kind,
      const std::optional<std::u16string>& pin) override {
    std::move(callback).Run(prompt_result_);
  }

  void SetAuthBehavior(AuthBehavior auth_behavior) {
    auth_behavior_ = auth_behavior;
  }

  int num_pair_attempts() const { return num_pair_attempts_; }

  void SetPromptResult(PairPromptResult result) {
    prompt_result_ = result;
  }

  const std::vector<uint8_t>& characteristic_value() const {
    return characteristic_value_;
  }

  const std::vector<uint8_t>& descriptor_value() const {
    return descriptor_value_;
  }

  void DeletePairingManager() { pairing_manager_.reset(); }

  WebBluetoothPairingManagerImpl* pairing_manager() {
    return pairing_manager_.get();
  }

 protected:
  BluetoothDevice::ConnectCallback pair_callback_;

 private:
  std::vector<uint8_t> characteristic_value_;
  std::vector<uint8_t> descriptor_value_;
  int num_pair_attempts_ = 0;
  bool device_paired_ = false;
  AuthBehavior auth_behavior_ = AuthBehavior::kUnspecified;
  PairPromptResult prompt_result_;
  std::unique_ptr<WebBluetoothPairingManagerImpl> pairing_manager_;
  SingleThreadTaskEnvironment single_threaded_task_environment_;
};

TEST_F(BluetoothPairingManagerTest, ReadSuccessfulAuthFirstSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedFirst);

  const std::vector<uint8_t> expected_value = characteristic_value();
  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicReadValue(
      kValidTestData.characteristic_instance_id,
      base::BindLambdaForTesting(
          [&loop, &expected_value](WebBluetoothResult result,
                                   base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
            EXPECT_EQ(value, expected_value)
                << "Incorrect characteristic value";
            loop.Quit();
          }));

  loop.Run();
  EXPECT_EQ(1, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, ReadSuccessfulAuthSecondSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedSecond);

  const std::vector<uint8_t> expected_value = characteristic_value();
  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicReadValue(
      kValidTestData.characteristic_instance_id,
      base::BindLambdaForTesting(
          [&loop, &expected_value](WebBluetoothResult result,
                                   base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
            EXPECT_EQ(value, expected_value)
                << "Incorrect characteristic value";
            loop.Quit();
          }));

  loop.Run();
  EXPECT_EQ(2, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, ReadFailAllAuthsFail) {
  SetAuthBehavior(AuthBehavior::kFailAll);

  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicReadValue(
      kValidTestData.characteristic_instance_id,
      base::BindLambdaForTesting(
          [&loop](WebBluetoothResult result, base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result);
            EXPECT_TRUE(value.empty());
            loop.Quit();
          }));

  loop.Run();
  EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts,
            num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, ReadInvalidCharacteristicId) {
  SetAuthBehavior(AuthBehavior::kFailAll);

  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicReadValue(
      kValidNonTestData.characteristic_instance_id,
      base::BindLambdaForTesting(
          [&loop](WebBluetoothResult result, base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_UNKNOWN_ERROR, result);
            EXPECT_TRUE(value.empty());
            loop.Quit();
          }));

  loop.Run();
  EXPECT_EQ(0, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, ReadCharacteristicDeleteDelegate) {
  SetAuthBehavior(AuthBehavior::kSuspend);

  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicReadValue(
      kValidTestData.characteristic_instance_id,
      base::BindLambdaForTesting(
          [&loop](WebBluetoothResult result, base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result);
            EXPECT_TRUE(value.empty());
            loop.Quit();
          }));

  // Deleting the pairing manager will cancel all pending device pairing
  // operations. Test this is true.
  DeletePairingManager();
  loop.Run();
  EXPECT_EQ(1, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, ReadCharacteristicDoublePair) {
  SetAuthBehavior(AuthBehavior::kFirstSuspend);

  int callback_count = 0;
  base::RunLoop loop;
  const std::vector<uint8_t> expected_value = characteristic_value();
  pairing_manager()->PairForCharacteristicReadValue(
      kValidTestData.characteristic_instance_id,
      base::BindLambdaForTesting(
          [&loop, &callback_count, &expected_value](
              WebBluetoothResult result, base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
            EXPECT_EQ(value, expected_value);
            if (++callback_count == 2)
              loop.Quit();
          }));

  // Now try to pair for reading a second time. This should fail due to an
  // in-progress pairing.
  pairing_manager()->PairForCharacteristicReadValue(
      kValidTestData.characteristic_instance_id,
      base::BindLambdaForTesting(
          [&loop, &callback_count](WebBluetoothResult result,
                                   base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result);
            if (++callback_count == 2)
              loop.Quit();
          }));

  ResumeSuspendedPairingWithSuccess();
  loop.Run();

  EXPECT_EQ(1, num_pair_attempts()) << "Only the first operation should pair";
}

TEST_F(BluetoothPairingManagerTest,
       WriteCharacteristicSuccessfulAuthFirstSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedFirst);

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  EXPECT_NE(kWriteValue, characteristic_value());

  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicWriteValue(
      kValidTestData.characteristic_instance_id, kWriteValue,
      WebBluetoothWriteType::kWriteWithResponse,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(1, num_pair_attempts());
  EXPECT_EQ(kWriteValue, characteristic_value());
}

TEST_F(BluetoothPairingManagerTest,
       WriteCharacteristicSuccessfulAuthSecondSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedSecond);

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  EXPECT_NE(kWriteValue, characteristic_value());

  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicWriteValue(
      kValidTestData.characteristic_instance_id, kWriteValue,
      WebBluetoothWriteType::kWriteWithResponse,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(2, num_pair_attempts());
  EXPECT_EQ(kWriteValue, characteristic_value());
}

TEST_F(BluetoothPairingManagerTest, WriteCharacteristicFailAllAuthsFail) {
  SetAuthBehavior(AuthBehavior::kFailAll);

  const std::vector<uint8_t> default_test_characteristic_value =
      characteristic_value();

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  EXPECT_NE(kWriteValue, default_test_characteristic_value);

  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicWriteValue(
      kValidTestData.characteristic_instance_id, kWriteValue,
      WebBluetoothWriteType::kWriteWithResponse,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts,
            num_pair_attempts());
  EXPECT_EQ(default_test_characteristic_value, characteristic_value());
}

TEST_F(BluetoothPairingManagerTest, WriteCharacteristicInvalidID) {
  SetAuthBehavior(AuthBehavior::kFailAll);

  const std::vector<uint8_t> default_test_characteristic_value =
      characteristic_value();

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  EXPECT_NE(kWriteValue, default_test_characteristic_value);

  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicWriteValue(
      kValidNonTestData.characteristic_instance_id, kWriteValue,
      WebBluetoothWriteType::kWriteWithResponse,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::CONNECT_UNKNOWN_ERROR, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(0, num_pair_attempts());
  EXPECT_EQ(default_test_characteristic_value, characteristic_value());
}

TEST_F(BluetoothPairingManagerTest, WriteCharacteristicDeleteDelegate) {
  SetAuthBehavior(AuthBehavior::kSuspend);

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicWriteValue(
      kValidTestData.characteristic_instance_id, kWriteValue,
      WebBluetoothWriteType::kWriteWithResponse,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result);
        loop.Quit();
      }));

  // Verify that deleting the pairing manager will cancel all pending device
  // pairing.
  DeletePairingManager();
  loop.Run();
  EXPECT_EQ(1, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, WriteCharacteristicDoublePair) {
  SetAuthBehavior(AuthBehavior::kFirstSuspend);

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  int callback_count = 0;
  base::RunLoop loop;
  pairing_manager()->PairForCharacteristicWriteValue(
      kValidTestData.characteristic_instance_id, kWriteValue,
      WebBluetoothWriteType::kWriteWithResponse,
      base::BindLambdaForTesting(
          [&loop, &callback_count](WebBluetoothResult result) {
            EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
            if (++callback_count == 2)
              loop.Quit();
          }));

  // Now try to pair for writing a second time. This should fail due to an
  // in-progress pairing.
  pairing_manager()->PairForCharacteristicWriteValue(
      kValidTestData.characteristic_instance_id, kWriteValue,
      WebBluetoothWriteType::kWriteWithResponse,
      base::BindLambdaForTesting(
          [&loop, &callback_count](WebBluetoothResult result) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result);
            if (++callback_count == 2)
              loop.Quit();
          }));

  ResumeSuspendedPairingWithSuccess();
  loop.Run();

  EXPECT_EQ(1, num_pair_attempts()) << "Only the first operation should pair";
}

TEST_F(BluetoothPairingManagerTest, DescriptorReadSuccessfulAuthFirstSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedFirst);

  const std::vector<uint8_t> expected_value = descriptor_value();
  base::RunLoop loop;
  pairing_manager()->PairForDescriptorReadValue(
      kValidTestData.descriptor_instance_id,
      base::BindLambdaForTesting(
          [&loop, &expected_value](WebBluetoothResult result,
                                   base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
            EXPECT_EQ(value, expected_value) << "Incorrect descriptor value";
            loop.Quit();
          }));

  loop.Run();
  EXPECT_EQ(1, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, DescriptorReadSuccessfulAuthSecondSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedSecond);

  const std::vector<uint8_t> expected_value = descriptor_value();
  base::RunLoop loop;
  pairing_manager()->PairForDescriptorReadValue(
      kValidTestData.descriptor_instance_id,
      base::BindLambdaForTesting(
          [&loop, &expected_value](WebBluetoothResult result,
                                   base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
            EXPECT_EQ(value, expected_value) << "Incorrect descriptor value";
            loop.Quit();
          }));

  loop.Run();
  EXPECT_EQ(2, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, DescriptorReadFailAllAuthsFail) {
  SetAuthBehavior(AuthBehavior::kFailAll);

  base::RunLoop loop;
  pairing_manager()->PairForDescriptorReadValue(
      kValidTestData.descriptor_instance_id,
      base::BindLambdaForTesting(
          [&loop](WebBluetoothResult result, base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result);
            EXPECT_TRUE(value.empty());
            loop.Quit();
          }));

  loop.Run();
  EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts,
            num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, DescriptorReadInvalidDescriptorId) {
  SetAuthBehavior(AuthBehavior::kFailAll);

  base::RunLoop loop;
  pairing_manager()->PairForDescriptorReadValue(
      kValidNonTestData.descriptor_instance_id,
      base::BindLambdaForTesting(
          [&loop](WebBluetoothResult result, base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_UNKNOWN_ERROR, result);
            loop.Quit();
          }));

  loop.Run();
  EXPECT_EQ(0, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, ReadDescriptorDeleteDelegate) {
  SetAuthBehavior(AuthBehavior::kSuspend);

  base::RunLoop loop;
  pairing_manager()->PairForDescriptorReadValue(
      kValidTestData.descriptor_instance_id,
      base::BindLambdaForTesting(
          [&loop](WebBluetoothResult result, base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result);
            loop.Quit();
          }));

  // Verify that deleting the pairing manager will cancel all pending device
  // pairing.
  DeletePairingManager();
  loop.Run();
  EXPECT_EQ(1, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, ReadDescriptorDoublePair) {
  SetAuthBehavior(AuthBehavior::kFirstSuspend);

  const std::vector<uint8_t> expected_value = descriptor_value();
  int callback_count = 0;
  base::RunLoop loop;
  pairing_manager()->PairForDescriptorReadValue(
      kValidTestData.descriptor_instance_id,
      base::BindLambdaForTesting(
          [&loop, &callback_count, &expected_value](
              WebBluetoothResult result, base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
            EXPECT_EQ(value, expected_value) << "Incorrect descriptor value";
            if (++callback_count == 2)
              loop.Quit();
          }));

  // Now try to pair for reading a second time. This should fail due to an
  // in-progress pairing.
  pairing_manager()->PairForDescriptorReadValue(
      kValidTestData.descriptor_instance_id,
      base::BindLambdaForTesting(
          [&loop, &callback_count](WebBluetoothResult result,
                                   base::span<const uint8_t> value) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result);
            EXPECT_TRUE(value.empty());
            if (++callback_count == 2)
              loop.Quit();
          }));

  ResumeSuspendedPairingWithSuccess();
  loop.Run();

  EXPECT_EQ(1, num_pair_attempts()) << "Only the first operation should pair";
}

TEST_F(BluetoothPairingManagerTest, WriteDescriptorSuccessfulAuthFirstSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedFirst);

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  EXPECT_NE(kWriteValue, descriptor_value());

  base::RunLoop loop;
  pairing_manager()->PairForDescriptorWriteValue(
      kValidTestData.descriptor_instance_id, kWriteValue,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(1, num_pair_attempts());
  EXPECT_EQ(kWriteValue, descriptor_value());
}

TEST_F(BluetoothPairingManagerTest,
       WriteDescriptorSuccessfulAuthSecondSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedSecond);

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  EXPECT_NE(kWriteValue, descriptor_value());

  base::RunLoop loop;
  pairing_manager()->PairForDescriptorWriteValue(
      kValidTestData.descriptor_instance_id, kWriteValue,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(2, num_pair_attempts());
  EXPECT_EQ(kWriteValue, descriptor_value());
}

TEST_F(BluetoothPairingManagerTest, WriteDescriptorFailAllAuthsFail) {
  SetAuthBehavior(AuthBehavior::kFailAll);

  const std::vector<uint8_t> default_test_descriptor_value = descriptor_value();

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  EXPECT_NE(kWriteValue, default_test_descriptor_value);

  base::RunLoop loop;
  pairing_manager()->PairForDescriptorWriteValue(
      kValidTestData.descriptor_instance_id, kWriteValue,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts,
            num_pair_attempts());
  EXPECT_EQ(default_test_descriptor_value, descriptor_value());
}

TEST_F(BluetoothPairingManagerTest, WriteDescriptorInvalidID) {
  SetAuthBehavior(AuthBehavior::kFailAll);

  const std::vector<uint8_t> default_test_descriptor_value = descriptor_value();

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  EXPECT_NE(kWriteValue, default_test_descriptor_value);

  base::RunLoop loop;
  pairing_manager()->PairForDescriptorWriteValue(
      kValidNonTestData.descriptor_instance_id, kWriteValue,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::CONNECT_UNKNOWN_ERROR, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(0, num_pair_attempts());
  EXPECT_EQ(default_test_descriptor_value, descriptor_value());
}

TEST_F(BluetoothPairingManagerTest, WriteDescriptorDeleteDelegate) {
  SetAuthBehavior(AuthBehavior::kSuspend);

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  base::RunLoop loop;
  pairing_manager()->PairForDescriptorWriteValue(
      kValidTestData.descriptor_instance_id, kWriteValue,
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result);
        loop.Quit();
      }));

  // Verify that deleting the pairing manager will cancel all pending device
  // pairing.
  DeletePairingManager();
  loop.Run();
  EXPECT_EQ(1, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, WriteDescriptorDoublePair) {
  SetAuthBehavior(AuthBehavior::kFirstSuspend);

  const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11};
  int callback_count = 0;
  base::RunLoop loop;
  pairing_manager()->PairForDescriptorWriteValue(
      kValidTestData.descriptor_instance_id, kWriteValue,
      base::BindLambdaForTesting(
          [&loop, &callback_count](WebBluetoothResult result) {
            EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
            if (++callback_count == 2)
              loop.Quit();
          }));

  // Now try to pair for writing a second time. This should fail due to an
  // in-progress pairing.
  pairing_manager()->PairForDescriptorWriteValue(
      kValidTestData.descriptor_instance_id, kWriteValue,
      base::BindLambdaForTesting(
          [&loop, &callback_count](WebBluetoothResult result) {
            EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result);
            if (++callback_count == 2)
              loop.Quit();
          }));

  ResumeSuspendedPairingWithSuccess();
  loop.Run();

  EXPECT_EQ(1, num_pair_attempts()) << "Only the first operation should pair";
}

TEST_F(BluetoothPairingManagerTest, CredentialPromptPINSuccess) {
  BluetoothDelegate::PairPromptResult result;
  result.result_code = BluetoothDelegate::PairPromptStatus::kSuccess;
  result.pin = kValidTestData.pincode;
  SetPromptResult(result);

  MockBluetoothDevice device(/*adapter=*/nullptr,
                             /*bluetooth_class=*/0,
                             kValidTestData.device_name.c_str(),
                             kValidTestData.device_address,
                             /*initially_paired=*/false,
                             /*connected=*/true);

  EXPECT_CALL(device, GetAddress());
  EXPECT_CALL(device, GetNameForDisplay());

  base::RunLoop run_loop;
  pair_callback_ = base::BindLambdaForTesting(
      [&run_loop](std::optional<BluetoothDevice::ConnectErrorCode> error_code) {
        EXPECT_FALSE(error_code);
        run_loop.Quit();
      });
  pairing_manager()->RequestPinCode(&device);
  run_loop.Run();
}

TEST_F(BluetoothPairingManagerTest, CredentialPromptPINCancelled) {
  PairPromptResult result;
  result.result_code = BluetoothDelegate::PairPromptStatus::kCancelled;
  SetPromptResult(result);

  MockBluetoothDevice device(/*adapter=*/nullptr,
                             /*bluetooth_class=*/0,
                             kValidTestData.device_name.c_str(),
                             kValidTestData.device_address,
                             /*initially_paired=*/false,
                             /*connected=*/true);

  EXPECT_CALL(device, GetAddress());
  EXPECT_CALL(device, GetNameForDisplay());

  base::RunLoop run_loop;
  pair_callback_ = base::BindLambdaForTesting(
      [&run_loop](std::optional<BluetoothDevice::ConnectErrorCode> error_code) {
        EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, *error_code);
        run_loop.Quit();
      });
  pairing_manager()->RequestPinCode(&device);
  run_loop.Run();
}

TEST_F(BluetoothPairingManagerTest, CredentialPromptPasskeyCancelled) {
  MockBluetoothDevice device(/*adapter=*/nullptr,
                             /*bluetooth_class=*/0,
                             kValidTestData.device_name.c_str(),
                             kValidTestData.device_address,
                             /*initially_paired=*/false,
                             /*connected=*/true);

  EXPECT_CALL(device, CancelPairing());

  // Passkey not supported. Verify pairing cancelled.
  pairing_manager()->RequestPasskey(&device);
}

TEST_F(BluetoothPairingManagerTest, PairConfirmPromptSuccess) {
  PairPromptResult result;
  result.result_code = BluetoothDelegate::PairPromptStatus::kSuccess;
  SetPromptResult(result);

  MockBluetoothDevice device(/*adapter=*/nullptr,
                             /*bluetooth_class=*/0,
                             kValidTestData.device_name.c_str(),
                             kValidTestData.device_address,
                             /*initially_paired=*/false,
                             /*connected=*/true);

  EXPECT_CALL(device, GetAddress());
  EXPECT_CALL(device, GetNameForDisplay());

  TestFuture<std::optional<BluetoothDevice::ConnectErrorCode>> future;
  pair_callback_ = future.GetCallback();
  pairing_manager()->AuthorizePairing(&device);
  EXPECT_FALSE(future.Get());
}

TEST_F(BluetoothPairingManagerTest, PairConfirmPromptCancelled) {
  PairPromptResult result;
  result.result_code = BluetoothDelegate::PairPromptStatus::kCancelled;
  SetPromptResult(result);

  MockBluetoothDevice device(/*adapter=*/nullptr,
                             /*bluetooth_class=*/0,
                             kValidTestData.device_name.c_str(),
                             kValidTestData.device_address,
                             /*initially_paired=*/false,
                             /*connected=*/true);

  EXPECT_CALL(device, GetAddress());
  EXPECT_CALL(device, GetNameForDisplay());

  TestFuture<std::optional<BluetoothDevice::ConnectErrorCode>> future;
  pair_callback_ = future.GetCallback();
  pairing_manager()->AuthorizePairing(&device);
  EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, future.Get());
}

TEST_F(BluetoothPairingManagerTest, PairConfirmPinPromptSuccess) {
  PairPromptResult result;
  result.result_code = BluetoothDelegate::PairPromptStatus::kSuccess;
  SetPromptResult(result);

  MockBluetoothDevice device(/*adapter=*/nullptr,
                             /*bluetooth_class=*/0,
                             kValidTestData.device_name.c_str(),
                             kValidTestData.device_address,
                             /*initially_paired=*/false,
                             /*connected=*/true);

  EXPECT_CALL(device, GetAddress());
  EXPECT_CALL(device, GetNameForDisplay());

  TestFuture<std::optional<BluetoothDevice::ConnectErrorCode>> future;
  pair_callback_ = future.GetCallback();
  pairing_manager()->ConfirmPasskey(&device, 123456);
  EXPECT_FALSE(future.Get());
}

TEST_F(BluetoothPairingManagerTest, PairConfirmPinPromptCancelled) {
  PairPromptResult result;
  result.result_code = BluetoothDelegate::PairPromptStatus::kCancelled;
  SetPromptResult(result);

  MockBluetoothDevice device(/*adapter=*/nullptr,
                             /*bluetooth_class=*/0,
                             kValidTestData.device_name.c_str(),
                             kValidTestData.device_address,
                             /*initially_paired=*/false,
                             /*connected=*/true);

  EXPECT_CALL(device, GetAddress());
  EXPECT_CALL(device, GetNameForDisplay());

  TestFuture<std::optional<BluetoothDevice::ConnectErrorCode>> future;
  pair_callback_ = future.GetCallback();
  pairing_manager()->ConfirmPasskey(&device, 123456);
  EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, future.Get());
}

TEST_F(BluetoothPairingManagerTest, StartNotificationsAllAuthsSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedFirst);

  base::RunLoop loop;
  mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> client;
  pairing_manager()->PairForCharacteristicStartNotifications(
      kValidTestData.characteristic_instance_id, std::move(client),
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(1, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, StartNotificationsAuthSecondSuccess) {
  SetAuthBehavior(AuthBehavior::kSucceedSecond);

  base::RunLoop loop;
  mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> client;
  pairing_manager()->PairForCharacteristicStartNotifications(
      kValidTestData.characteristic_instance_id, std::move(client),
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::SUCCESS, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(2, num_pair_attempts());
}

TEST_F(BluetoothPairingManagerTest, StartNotificationsAllAuthsFail) {
  SetAuthBehavior(AuthBehavior::kFailAll);

  base::RunLoop loop;
  mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> client;
  pairing_manager()->PairForCharacteristicStartNotifications(
      kValidTestData.characteristic_instance_id, std::move(client),
      base::BindLambdaForTesting([&loop](WebBluetoothResult result) {
        EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result);
        loop.Quit();
      }));

  loop.Run();
  EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts,
            num_pair_attempts());
}

}  // namespace content
