chromium/chrome/browser/nearby_sharing/local_device_data/nearby_share_device_data_updater_impl_unittest.cc

// Copyright 2020 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/nearby_sharing/local_device_data/nearby_share_device_data_updater_impl.h"

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/containers/contains.h"
#include "base/no_destructor.h"
#include "base/test/task_environment.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "chrome/browser/nearby_sharing/client/fake_nearby_share_client.h"
#include "chromeos/ash/components/nearby/common/client/nearby_http_result.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/nearby/sharing/proto/device_rpc.pb.h"
#include "third_party/nearby/sharing/proto/rpc_resources.pb.h"

namespace {

const char kDeviceIdPrefix[] = "users/me/devices/";
const char kTestDeviceId[] = "test_device_id";
const char kTestContactEmail1[] = "[email protected]";
const char kTestContactEmail2[] = "[email protected]";
const char kTestCertificateId1[] = "cert_id_1";
const char kTestCertificateId2[] = "cert_id_2";
const char kTestPersonName[] = "person_name";
constexpr base::TimeDelta kTestTimeout = base::Minutes(123);

const std::vector<nearby::sharing::proto::Contact>& TestContactList() {
  static const base::NoDestructor<std::vector<nearby::sharing::proto::Contact>>
      list([] {
        nearby::sharing::proto::Contact contact1;
        contact1.mutable_identifier()->set_account_name(kTestContactEmail1);
        nearby::sharing::proto::Contact contact2;
        contact2.mutable_identifier()->set_account_name(kTestContactEmail2);
        return std::vector<nearby::sharing::proto::Contact>{contact1, contact2};
      }());
  return *list;
}

const std::vector<nearby::sharing::proto::PublicCertificate>&
TestCertificateList() {
  static const base::NoDestructor<
      std::vector<nearby::sharing::proto::PublicCertificate>>
      list([] {
        nearby::sharing::proto::PublicCertificate cert1;
        cert1.set_secret_id(kTestCertificateId1);
        nearby::sharing::proto::PublicCertificate cert2;
        cert2.set_secret_id(kTestCertificateId2);
        return std::vector<nearby::sharing::proto::PublicCertificate>{cert1,
                                                                      cert2};
      }());
  return *list;
}

const nearby::sharing::proto::UpdateDeviceResponse& TestResponse() {
  static const base::NoDestructor<nearby::sharing::proto::UpdateDeviceResponse>
      response([] {
        nearby::sharing::proto::UpdateDeviceResponse response;
        response.set_person_name(kTestPersonName);
        return response;
      }());
  return *response;
}

void VerifyRequest(
    const std::optional<std::vector<nearby::sharing::proto::Contact>>&
        expected_contacts,
    const std::optional<std::vector<nearby::sharing::proto::PublicCertificate>>&
        expected_certificates,
    const nearby::sharing::proto::UpdateDeviceRequest& request) {
  std::vector<std::string> field_mask{request.update_mask().paths().begin(),
                                      request.update_mask().paths().end()};

  EXPECT_EQ(static_cast<size_t>(expected_contacts.has_value()) +
                static_cast<size_t>(expected_certificates.has_value()),
            field_mask.size());

  EXPECT_EQ(expected_contacts.has_value(),
            base::Contains(field_mask, "contacts"));
  EXPECT_EQ(expected_certificates.has_value(),
            base::Contains(field_mask, "public_certificates"));

  EXPECT_EQ(std::string(kDeviceIdPrefix) + kTestDeviceId,
            request.device().name());

  if (expected_contacts) {
    ASSERT_EQ(static_cast<int>(expected_contacts->size()),
              request.device().contacts().size());
    for (size_t i = 0; i < expected_contacts->size(); ++i) {
      EXPECT_EQ((*expected_contacts)[i].SerializeAsString(),
                request.device().contacts()[i].SerializeAsString());
    }
  } else {
    EXPECT_TRUE(request.device().contacts().empty());
  }

  if (expected_certificates) {
    ASSERT_EQ(static_cast<int>(expected_certificates->size()),
              request.device().public_certificates().size());
    for (size_t i = 0; i < expected_certificates->size(); ++i) {
      EXPECT_EQ((*expected_certificates)[i].SerializeAsString(),
                request.device().public_certificates()[i].SerializeAsString());
    }
  } else {
    EXPECT_TRUE(request.device().public_certificates().empty());
  }
}

void VerifyResponse(
    const std::optional<nearby::sharing::proto::UpdateDeviceResponse>&
        expected_response,
    const std::optional<nearby::sharing::proto::UpdateDeviceResponse>&
        response) {
  if (expected_response) {
    ASSERT_TRUE(response);
    EXPECT_EQ(expected_response->SerializeAsString(),
              response->SerializeAsString());
  } else {
    EXPECT_FALSE(response);
  }
}

}  // namespace

class NearbyShareDeviceDataUpdaterImplTest : public ::testing::Test {
 protected:
  enum class UpdateDeviceRequestResult { kSuccess, kHttpFailure, kTimeout };

  NearbyShareDeviceDataUpdaterImplTest() = default;
  ~NearbyShareDeviceDataUpdaterImplTest() override = default;

  void SetUp() override {
    updater_ = NearbyShareDeviceDataUpdaterImpl::Factory::Create(
        kTestDeviceId, kTestTimeout, &fake_client_factory_);
  }

  void CallUpdateDeviceData(
      const std::optional<std::vector<nearby::sharing::proto::Contact>>&
          contacts,
      const std::optional<
          std::vector<nearby::sharing::proto::PublicCertificate>>&
          certificates) {
    updater_->UpdateDeviceData(
        contacts, certificates,
        base::BindOnce(&NearbyShareDeviceDataUpdaterImplTest::OnResult,
                       base::Unretained(this)));
  }

  void ProcessNextUpdateDeviceDataRequest(
      const std::optional<std::vector<nearby::sharing::proto::Contact>>&
          expected_contacts,
      const std::optional<
          std::vector<nearby::sharing::proto::PublicCertificate>>&
          expected_certificates,
      UpdateDeviceRequestResult result) {
    // Verify the next request.
    ASSERT_FALSE(fake_client_factory_.instances().empty());
    FakeNearbyShareClient* client = fake_client_factory_.instances().back();
    ASSERT_EQ(1u, client->update_device_requests().size());
    VerifyRequest(expected_contacts, expected_certificates,
                  client->update_device_requests()[0].request);

    // Send and verify the response.
    size_t num_responses = responses_.size();
    switch (result) {
      case UpdateDeviceRequestResult::kSuccess:
        std::move(client->update_device_requests()[0].callback)
            .Run(TestResponse());
        break;
      case UpdateDeviceRequestResult::kHttpFailure:
        std::move(client->update_device_requests()[0].error_callback)
            .Run(ash::nearby::NearbyHttpError::kBadRequest);
        break;
      case UpdateDeviceRequestResult::kTimeout:
        FastForward(kTestTimeout);
        break;
    }
    EXPECT_EQ(num_responses + 1, responses_.size());

    VerifyResponse(result == UpdateDeviceRequestResult::kSuccess
                       ? std::make_optional(TestResponse())
                       : std::nullopt,
                   responses_.back());
  }

 private:
  // Fast-forwards mock time by |delta| and fires relevant timers.
  void FastForward(base::TimeDelta delta) {
    task_environment_.FastForwardBy(delta);
  }

  // The callback passed into UpdateDeviceData().
  void OnResult(
      const std::optional<nearby::sharing::proto::UpdateDeviceResponse>&
          response) {
    responses_.push_back(response);
  }

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  std::vector<std::optional<nearby::sharing::proto::UpdateDeviceResponse>>
      responses_;
  FakeNearbyShareClientFactory fake_client_factory_;
  std::unique_ptr<NearbyShareDeviceDataUpdater> updater_;
};

TEST_F(NearbyShareDeviceDataUpdaterImplTest, Success_NoParameters) {
  CallUpdateDeviceData(/*contacts=*/std::nullopt,
                       /*certificates=*/std::nullopt);
  ProcessNextUpdateDeviceDataRequest(
      /*expected_contacts=*/std::nullopt,
      /*expected_certificates=*/std::nullopt,
      UpdateDeviceRequestResult::kSuccess);
}

TEST_F(NearbyShareDeviceDataUpdaterImplTest, Success_AllParameters) {
  CallUpdateDeviceData(TestContactList(), TestCertificateList());
  ProcessNextUpdateDeviceDataRequest(TestContactList(), TestCertificateList(),
                                     UpdateDeviceRequestResult::kSuccess);
}

TEST_F(NearbyShareDeviceDataUpdaterImplTest, Success_OneParameter) {
  CallUpdateDeviceData(TestContactList(),
                       /*certificates=*/std::nullopt);
  ProcessNextUpdateDeviceDataRequest(TestContactList(),
                                     /*expected_certificates=*/std::nullopt,
                                     UpdateDeviceRequestResult::kSuccess);
}

TEST_F(NearbyShareDeviceDataUpdaterImplTest, Failure_Timeout) {
  CallUpdateDeviceData(TestContactList(), TestCertificateList());
  ProcessNextUpdateDeviceDataRequest(TestContactList(), TestCertificateList(),
                                     UpdateDeviceRequestResult::kTimeout);
}

TEST_F(NearbyShareDeviceDataUpdaterImplTest, Failure_HttpError) {
  CallUpdateDeviceData(TestContactList(), TestCertificateList());
  ProcessNextUpdateDeviceDataRequest(TestContactList(), TestCertificateList(),
                                     UpdateDeviceRequestResult::kHttpFailure);
}

TEST_F(NearbyShareDeviceDataUpdaterImplTest, QueuedRequests) {
  // Queue requests while waiting to process.
  CallUpdateDeviceData(/*contacts=*/std::nullopt,
                       /*certificates=*/std::nullopt);
  CallUpdateDeviceData(TestContactList(), TestCertificateList());
  CallUpdateDeviceData(/*contacts=*/std::nullopt, TestCertificateList());

  // Requests are processed in the order they are received.
  ProcessNextUpdateDeviceDataRequest(
      /*expected_contacts=*/std::nullopt,
      /*expected_certificates=*/std::nullopt,
      UpdateDeviceRequestResult::kSuccess);
  ProcessNextUpdateDeviceDataRequest(TestContactList(), TestCertificateList(),
                                     UpdateDeviceRequestResult::kTimeout);
  ProcessNextUpdateDeviceDataRequest(/*expected_contacts=*/std::nullopt,
                                     TestCertificateList(),
                                     UpdateDeviceRequestResult::kHttpFailure);
}