chromium/ash/quick_pair/repository/fast_pair/device_metadata_fetcher_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 "ash/quick_pair/repository/fast_pair/device_metadata_fetcher.h"

#include <memory>
#include <optional>

#include "ash/quick_pair/common/account_key_failure.h"
#include "ash/quick_pair/common/device.h"
#include "ash/quick_pair/common/fast_pair/fast_pair_http_result.h"
#include "ash/quick_pair/common/pair_failure.h"
#include "ash/quick_pair/common/protocol.h"
#include "ash/quick_pair/proto/fastpair.pb.h"
#include "ash/quick_pair/repository/mock_http_fetcher.h"
#include "base/base64.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

constexpr char kValidResponseEncoded[] =
    "Cr0HCKy4kAMYASJ4aHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL3dLM3YzcVI1d3"
    "pHMk5JbXBuS1UyYlpfblFpdjh4elJoVDFadWRhT0NhSzlOVzRVS3lZNWtvYlNrSHlxeUJZTzVO"
    "M1h3Um84XzRERkdGcHEtUjNWbW5nKgpQaXhlbCBCdWRzMrQCaW50ZW50OiNJbnRlbnQ7YWN0aW"
    "9uPWNvbS5nb29nbGUuYW5kcm9pZC5nbXMubmVhcmJ5LmRpc2NvdmVyeSUzQUFDVElPTl9NQUdJ"
    "Q19QQUlSO3BhY2thZ2U9Y29tLmdvb2dsZS5hbmRyb2lkLmdtcztjb21wb25lbnQ9Y29tLmdvb2"
    "dsZS5hbmRyb2lkLmdtcy8ubmVhcmJ5LmRpc2NvdmVyeS5zZXJ2aWNlLkRpc2NvdmVyeVNlcnZp"
    "Y2U7Uy5jb20uZ29vZ2xlLmFuZHJvaWQuZ21zLm5lYXJieS5kaXNjb3ZlcnklM0FFWFRSQV9DT0"
    "1QQU5JT05fQVBQPWNvbS5nb29nbGUuYW5kcm9pZC5hcHBzLndlYXJhYmxlcy5tYWVzdHJvLmNv"
    "bXBhbmlvbjtlbmRFmpkZP0pCEkAqaamccP9akybjAVzzvMeRhVAJjHOrKnyKet1/"
    "L9H3JlQQdpFD2w1lclPx5B9I2+mjvU9IHsW2Xlsn6z0+HgdTUgIIA1oMCNye8e4FEIDe/"
    "OIBaAd67QIKeGh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9POUo4V0dSQzg5Q08"
    "1OEFTamFGbWxjbVM1NWYtMWJJZjJvUWFXVzFyS2REMDZtRlpTS1E3RWlNT3JKSWpjRUtRR0RTL"
    "UlwUmN2TndhUGhvWXA1YVFuZxJ2aHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tL1l"
    "YeWk4Vy1VQkRKYklySU40cnhUQXljR2dSb2lXTWNuUFpsVnJRMU1MTHl1WjJEbVBIdGJzRDIzY"
    "m1leTdJbXlHZWpSbWR2YWZIS0tzbXV6Zm5mVBp5aHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnR"
    "lbnQuY29tL2hXeWdJM2liTjRIV0QtRmp3dlNuV1BIbkdzMUFNMTVLclh2VWx6Tmo2SlJDMmpNM"
    "jZuTFZjY0tYNDFRZEtmOHE3aFN2eVJMYjZMcVJCRDJWVTlQWkNQY4ABAZoBBkdvb2dsZaoBF1B"
    "yZXN0byBFVlQgQWxtb3N0IEJsYWNrsAECugEAGpEBiVBORw0KGgoAAAANSUhEUgAAAAQAAAAEC"
    "AYAAACp8Z5+"
    "AAAABHNCSVQICAgIfAhkiAAAAEhJREFUCJkFwTENgDAQQNGfYIAEXdjARTVUARI6UBMsnU9ADd"
    "zM0Hze2wBKKfda65lzvqhna83eu+qOemWmESEA6jHG+NQK8AOtZCpIT/"
    "9elAAAAABJRU5ErkJggiKjBRInVGFwIHRvIHBhaXIuIEVhcmJ1ZHMgd2lsbCBiZSB0aWVkIHRv"
    "ICVzGhxUYXAgdG8gcGFpciB3aXRoIHRoaXMgZGV2aWNlIhNUYXAgdG8gZmluaXNoIHNldHVwKi"
    "5UYXAgdG8gdXBkYXRlIGRldmljZSBzZXR0aW5ncyBhbmQgZmluaXNoIHNldHVwMj5UYXAgdG8g"
    "ZG93bmxvYWQgZGV2aWNlIGFwcCBvbiBHb29nbGUgUGxheSBhbmQgc2VlIGFsbCBmZWF0dXJlcz"
    "oRVW5hYmxlIHRvIGNvbm5lY3RCIlRyeSBtYW51YWxseSBwYWlyaW5nIHRvIHRoZSBkZXZpY2VK"
    "KCVzIHdpbGwgYXBwZWFyIG9uIGRldmljZXMgbGlua2VkIHdpdGggJXNSIVlvdXIgZGV2aWNlIG"
    "lzIHJlYWR5IHRvIGJlIHNldCB1cFpERG93bmxvYWQgdGhlIGRldmljZSBhcHAgb24gR29vZ2xl"
    "IFBsYXkgdG8gc2VlIGFsbCBhdmFpbGFibGUgZmVhdHVyZXNiGENvbm5lY3QgJXMgdG8gdGhpcy"
    "BwaG9uZWo6U2F2ZSBkZXZpY2UgdG8gJXMgZm9yIGZhc3RlciBwYWlyaW5nIHRvIHlvdXIgb3Ro"
    "ZXIgZGV2aWNlc3IcVGhpcyB3aWxsIHRha2UgYSBmZXcgbW9tZW50c3o3VHJ5IG1hbnVhbGx5IH"
    "BhaXJpbmcgdG8gdGhlIGRldmljZSBieSBnb2luZyB0byBTZXR0aW5nc7IBN0dldCB0aGUgaGFu"
    "ZHMtZnJlZSBoZWxwIG9uIHRoZSBnbyBmcm9tIEdvb2dsZSBBc3Npc3RhbnS6ASNUYXAgdG8gc2"
    "V0IHVwIHlvdXIgR29vZ2xlIEFzc2lzdGFudA==";
constexpr char kEmptyResponse[] = "{}";
constexpr char kInvalidResponse[] = "<html>404 error</html>";
constexpr char kHexDeviceId[] = "07B";
const int kDeviceId = 123;
const char kDeviceMetadataFetchResult[] =
    "Bluetooth.ChromeOS.FastPair.DeviceMetadataFetcher.Result";
const char kDeviceMetadataFetchNetError[] =
    "Bluetooth.ChromeOS.FastPair.DeviceMetadataFetcher.Get.NetError";
const char kDeviceMetadataFetchHttpResponseError[] =
    "Bluetooth.ChromeOS.FastPair.DeviceMetadataFetcher.Get.HttpResponseError";

}  // namespace

namespace ash {
namespace quick_pair {

class DeviceMetadataFetcherTest : public testing::Test {
 public:
  void SetUp() override {
    auto mock_http_fetcher = std::make_unique<MockHttpFetcher>();
    mock_http_fetcher_ = mock_http_fetcher.get();
    device_metadata_fetcher_ =
        std::make_unique<DeviceMetadataFetcher>(std::move(mock_http_fetcher));
  }

 protected:
  base::test::TaskEnvironment task_environment;
  base::HistogramTester histogram_tester_;
  std::unique_ptr<DeviceMetadataFetcher> device_metadata_fetcher_;
  raw_ptr<MockHttpFetcher> mock_http_fetcher_;
  data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
};

TEST_F(DeviceMetadataFetcherTest, ValidResponse) {
  EXPECT_CALL(*mock_http_fetcher_, ExecuteGetRequest)
      .WillOnce([](const GURL& url, FetchCompleteCallback callback) {
        ASSERT_EQ(0u,
                  url.spec().find(
                      "https://nearbydevices-pa.googleapis.com/v1/device/123"));
        std::string decoded;
        base::Base64Decode(kValidResponseEncoded, &decoded);
        std::move(callback).Run(
            std::make_unique<std::string>(decoded),
            std::make_unique<FastPairHttpResult>(net::Error::OK, nullptr));
      });

  base::MockCallback<GetObservedDeviceCallback> callback;
  EXPECT_CALL(callback, Run)
      .WillOnce([](std::optional<nearby::fastpair::GetObservedDeviceResponse>
                       response,
                   bool has_retryable_error) {
        ASSERT_EQ("Pixel Buds", response->device().name());
        ASSERT_EQ(
            "https://lh3.googleusercontent.com/"
            "wK3v3qR5wzG2NImpnKU2bZ_"
            "nQiv8xzRhT1ZudaOCaK9NW4UKyY5kobSkHyqyBYO5N3XwRo8_4DFGFpq-R3Vmng",
            response->device().image_url());
        ASSERT_EQ(0.6f, response->device().trigger_distance());
        ASSERT_FALSE(
            response->device().anti_spoofing_key_pair().public_key().empty());
        ASSERT_FALSE(
            response->device().true_wireless_images().left_bud_url().empty());
        ASSERT_FALSE(
            response->device().true_wireless_images().right_bud_url().empty());
        ASSERT_FALSE(
            response->device().true_wireless_images().case_url().empty());
      });

  device_metadata_fetcher_->LookupHexDeviceId(kHexDeviceId, callback.Get());
  task_environment.RunUntilIdle();
}

TEST_F(DeviceMetadataFetcherTest, InvalidResponse) {
  EXPECT_CALL(*mock_http_fetcher_, ExecuteGetRequest)
      .WillOnce([](const GURL& url, FetchCompleteCallback callback) {
        ASSERT_EQ(0u,
                  url.spec().find(
                      "https://nearbydevices-pa.googleapis.com/v1/device/123"));
        std::move(callback).Run(std::make_unique<std::string>(kInvalidResponse),
                                nullptr);
      });

  base::MockCallback<GetObservedDeviceCallback> callback;
  EXPECT_CALL(callback, Run)
      .WillOnce(
          [](std::optional<nearby::fastpair::GetObservedDeviceResponse>
                 response,
             bool has_retryable_error) { ASSERT_EQ(std::nullopt, response); });

  device_metadata_fetcher_->LookupDeviceId(kDeviceId, callback.Get());
  task_environment.RunUntilIdle();
}

TEST_F(DeviceMetadataFetcherTest, EmptyResponse) {
  EXPECT_CALL(*mock_http_fetcher_, ExecuteGetRequest)
      .WillOnce([](const GURL& url, FetchCompleteCallback callback) {
        ASSERT_EQ(0u,
                  url.spec().find(
                      "https://nearbydevices-pa.googleapis.com/v1/device/123"));
        std::move(callback).Run(std::make_unique<std::string>(kEmptyResponse),
                                nullptr);
      });

  base::MockCallback<GetObservedDeviceCallback> callback;
  EXPECT_CALL(callback, Run)
      .WillOnce(
          [](std::optional<nearby::fastpair::GetObservedDeviceResponse>
                 response,
             bool has_retryable_error) { ASSERT_EQ(std::nullopt, response); });

  device_metadata_fetcher_->LookupDeviceId(kDeviceId, callback.Get());
  task_environment.RunUntilIdle();
}

TEST_F(DeviceMetadataFetcherTest, NoResponse) {
  EXPECT_CALL(*mock_http_fetcher_, ExecuteGetRequest)
      .WillOnce([](const GURL& url, FetchCompleteCallback callback) {
        ASSERT_EQ(0u,
                  url.spec().find(
                      "https://nearbydevices-pa.googleapis.com/v1/device/123"));
        std::move(callback).Run(nullptr, nullptr);
      });

  base::MockCallback<GetObservedDeviceCallback> callback;
  EXPECT_CALL(callback, Run)
      .WillOnce(
          [](std::optional<nearby::fastpair::GetObservedDeviceResponse>
                 response,
             bool has_retryable_error) { ASSERT_EQ(std::nullopt, response); });

  device_metadata_fetcher_->LookupDeviceId(kDeviceId, callback.Get());
  task_environment.RunUntilIdle();
}

TEST_F(DeviceMetadataFetcherTest, RecordNetError) {
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchResult, 0);
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchNetError, 0);
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchHttpResponseError, 0);

  EXPECT_CALL(*mock_http_fetcher_, ExecuteGetRequest)
      .WillOnce([](const GURL& url, FetchCompleteCallback callback) {
        ASSERT_EQ(0u,
                  url.spec().find(
                      "https://nearbydevices-pa.googleapis.com/v1/device/123"));
        std::unique_ptr<FastPairHttpResult> http_result =
            std::make_unique<FastPairHttpResult>(
                /*net_error=*/net::ERR_CERT_COMMON_NAME_INVALID,
                /*head=*/network::CreateURLResponseHead(
                    net::HttpStatusCode::HTTP_NOT_FOUND)
                    .get());
        std::move(callback).Run(std::make_unique<std::string>(kInvalidResponse),
                                std::move(http_result));
      });

  base::MockCallback<GetObservedDeviceCallback> callback;
  EXPECT_CALL(callback, Run)
      .WillOnce(
          [](std::optional<nearby::fastpair::GetObservedDeviceResponse>
                 response,
             bool has_retryable_error) { ASSERT_EQ(std::nullopt, response); });

  device_metadata_fetcher_->LookupDeviceId(kDeviceId, callback.Get());
  task_environment.RunUntilIdle();

  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchResult, 1);
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchNetError, 1);
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchHttpResponseError, 0);
}

TEST_F(DeviceMetadataFetcherTest, RecordHttpError) {
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchResult, 0);
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchNetError, 0);
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchHttpResponseError, 0);

  EXPECT_CALL(*mock_http_fetcher_, ExecuteGetRequest)
      .WillOnce([](const GURL& url, FetchCompleteCallback callback) {
        ASSERT_EQ(0u,
                  url.spec().find(
                      "https://nearbydevices-pa.googleapis.com/v1/device/123"));
        std::unique_ptr<FastPairHttpResult> http_result =
            std::make_unique<FastPairHttpResult>(
                /*net_error=*/net::OK,
                /*head=*/network::CreateURLResponseHead(
                    net::HttpStatusCode::HTTP_NOT_FOUND)
                    .get());
        std::move(callback).Run(std::make_unique<std::string>(kInvalidResponse),
                                std::move(http_result));
      });

  base::MockCallback<GetObservedDeviceCallback> callback;
  EXPECT_CALL(callback, Run)
      .WillOnce(
          [](std::optional<nearby::fastpair::GetObservedDeviceResponse>
                 response,
             bool has_retryable_error) { ASSERT_EQ(std::nullopt, response); });

  device_metadata_fetcher_->LookupDeviceId(kDeviceId, callback.Get());
  task_environment.RunUntilIdle();

  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchResult, 1);
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchNetError, 0);
  histogram_tester_.ExpectTotalCount(kDeviceMetadataFetchHttpResponseError, 1);
}

}  // namespace quick_pair
}  // namespace ash