chromium/chrome/browser/nearby_sharing/contacts/nearby_share_contact_downloader_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/contacts/nearby_share_contact_downloader_impl.h"

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

#include "base/no_destructor.h"
#include "base/test/scoped_feature_list.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 "chrome/browser/nearby_sharing/common/nearby_share_features.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/contact_rpc.pb.h"
#include "third_party/nearby/sharing/proto/rpc_resources.pb.h"

namespace {

const char kTestDeviceId[] = "test_device_id";
const char kTestContactRecordId1[] = "contact_id_1";
const char kTestContactRecordId2[] = "contact_id_2";
const char kTestContactRecordId3[] = "contact_id_3";
const char kTestContactRecordId4[] = "contact_id_4";
const char kTestContactRecordId5[] = "contact_id_5";
const char kTestPageToken[] = "token";

constexpr base::TimeDelta kTestTimeout = base::Minutes(123);

const std::vector<nearby::sharing::proto::ContactRecord>&
TestContactRecordList() {
  static const base::NoDestructor<
      std::vector<nearby::sharing::proto::ContactRecord>>
      list([] {
        nearby::sharing::proto::ContactRecord contact1;
        contact1.set_id(kTestContactRecordId1);
        contact1.set_type(
            nearby::sharing::proto::ContactRecord::GOOGLE_CONTACT);
        contact1.set_is_reachable(true);
        nearby::sharing::proto::ContactRecord contact2;
        contact2.set_id(kTestContactRecordId2);
        contact2.set_type(
            nearby::sharing::proto::ContactRecord::DEVICE_CONTACT);
        contact2.set_is_reachable(true);
        nearby::sharing::proto::ContactRecord contact3;
        contact3.set_id(kTestContactRecordId3);
        contact3.set_type(nearby::sharing::proto::ContactRecord::UNKNOWN);
        contact3.set_is_reachable(true);
        nearby::sharing::proto::ContactRecord contact4;
        contact4.set_id(kTestContactRecordId4);
        contact4.set_type(
            nearby::sharing::proto::ContactRecord::GOOGLE_CONTACT);
        contact4.set_is_reachable(false);
        nearby::sharing::proto::ContactRecord contact5;
        contact5.set_id(kTestContactRecordId5);
        contact5.set_type(
            nearby::sharing::proto::ContactRecord::GOOGLE_CONTACT);
        contact5.set_is_reachable(false);
        return std::vector<nearby::sharing::proto::ContactRecord>{
            contact1, contact2, contact3, contact4, contact5};
      }());
  return *list;
}

nearby::sharing::proto::ListContactPeopleResponse
CreateListContactPeopleResponse(
    const std::vector<nearby::sharing::proto::ContactRecord>& contact_records,
    const std::optional<std::string>& next_page_token) {
  nearby::sharing::proto::ListContactPeopleResponse response;
  *response.mutable_contact_records() = {contact_records.begin(),
                                         contact_records.end()};
  if (next_page_token)
    response.set_next_page_token(*next_page_token);

  return response;
}

}  // namespace

class NearbyShareContactDownloaderImplTest
    : public ::testing::TestWithParam<std::tuple<bool, bool>> {
 protected:
  struct Result {
    bool success;
    std::optional<std::vector<nearby::sharing::proto::ContactRecord>> contacts;
    std::optional<uint32_t> num_unreachable_contacts_filtered_out;
  };

  NearbyShareContactDownloaderImplTest() = default;
  ~NearbyShareContactDownloaderImplTest() override = default;

  void RunDownload() {
    downloader_ = NearbyShareContactDownloaderImpl::Factory::Create(
        kTestDeviceId, kTestTimeout, &fake_client_factory_,
        base::BindOnce(&NearbyShareContactDownloaderImplTest::OnSuccess,
                       base::Unretained(this)),
        base::BindOnce(&NearbyShareContactDownloaderImplTest::OnFailure,
                       base::Unretained(this)));
    downloader_->Run();
  }

  void SucceedListContactPeopleRequest(
      const std::optional<std::string>& expected_page_token_in_request,
      const nearby::sharing::proto::ListContactPeopleResponse& response) {
    VerifyListContactPeopleRequest(expected_page_token_in_request);

    EXPECT_FALSE(result_);
    FakeNearbyShareClient* client = fake_client_factory_.instances().back();
    std::move(client->list_contact_people_requests()[0].callback).Run(response);
  }

  void FailListContactPeopleRequest(
      const std::optional<std::string>& expected_page_token_in_request) {
    VerifyListContactPeopleRequest(expected_page_token_in_request);

    EXPECT_FALSE(result_);
    FakeNearbyShareClient* client = fake_client_factory_.instances().back();
    std::move(client->list_contact_people_requests()[0].error_callback)
        .Run(ash::nearby::NearbyHttpError::kBadRequest);
  }

  void TimeoutListContactPeopleRequest(
      const std::optional<std::string>& expected_page_token_in_request) {
    VerifyListContactPeopleRequest(expected_page_token_in_request);

    EXPECT_FALSE(result_);
    FastForward(kTestTimeout);
  }

  void VerifySuccess(const std::vector<nearby::sharing::proto::ContactRecord>&
                         expected_contacts,
                     uint32_t expected_num_unreachable_contacts_filtered_out) {
    ASSERT_TRUE(result_);
    EXPECT_TRUE(result_->success);
    ASSERT_TRUE(result_->contacts);
    EXPECT_EQ(expected_num_unreachable_contacts_filtered_out,
              result_->num_unreachable_contacts_filtered_out);

    ASSERT_EQ(expected_contacts.size(), result_->contacts->size());
    for (size_t i = 0; i < expected_contacts.size(); ++i) {
      EXPECT_EQ(expected_contacts[i].SerializeAsString(),
                result_->contacts->at(i).SerializeAsString());
    }
  }

  void VerifyFailure() {
    ASSERT_TRUE(result_);
    EXPECT_FALSE(result_->success);
    EXPECT_FALSE(result_->contacts);
    EXPECT_FALSE(result_->num_unreachable_contacts_filtered_out);
  }

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

  void VerifyListContactPeopleRequest(
      const std::optional<std::string>& expected_page_token) {
    ASSERT_FALSE(fake_client_factory_.instances().empty());
    FakeNearbyShareClient* client = fake_client_factory_.instances().back();
    ASSERT_EQ(1u, client->list_contact_people_requests().size());

    const nearby::sharing::proto::ListContactPeopleRequest& request =
        client->list_contact_people_requests()[0].request;
    EXPECT_EQ(expected_page_token.value_or(std::string()),
              request.page_token());
  }

  // The callbacks passed into NearbyShareContactDownloader ctor.
  void OnSuccess(std::vector<nearby::sharing::proto::ContactRecord> contacts,
                 uint32_t num_unreachable_contacts_filtered_out) {
    result_ = Result();
    result_->success = true;
    result_->contacts = std::move(contacts);
    result_->num_unreachable_contacts_filtered_out =
        num_unreachable_contacts_filtered_out;
  }
  void OnFailure() {
    result_ = Result();
    result_->success = false;
    result_->contacts.reset();
    result_->num_unreachable_contacts_filtered_out.reset();
  }

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  std::optional<Result> result_;
  FakeNearbyShareClientFactory fake_client_factory_;
  std::unique_ptr<NearbyShareContactDownloader> downloader_;
};

TEST_F(NearbyShareContactDownloaderImplTest, Success) {
  RunDownload();

  // Contacts are sent in two ListContactPeople responses.
  SucceedListContactPeopleRequest(
      /*expected_page_token=*/std::nullopt,
      CreateListContactPeopleResponse(
          std::vector<nearby::sharing::proto::ContactRecord>(
              TestContactRecordList().begin(),
              TestContactRecordList().begin() + 1),
          kTestPageToken));
  SucceedListContactPeopleRequest(
      /*expected_page_token=*/kTestPageToken,
      CreateListContactPeopleResponse(
          std::vector<nearby::sharing::proto::ContactRecord>(
              TestContactRecordList().begin() + 1,
              TestContactRecordList().end()),
          /*next_page_token=*/std::nullopt));

  // The last two records are filtered out because the are not reachable.
  VerifySuccess(/*expected_contacts=*/
                std::vector<nearby::sharing::proto::ContactRecord>(
                    TestContactRecordList().begin(),
                    TestContactRecordList().begin() + 3),
                /*expected_num_unreachable_contacts_filtered_out=*/2);
}

TEST_F(NearbyShareContactDownloaderImplTest, Failure_ListContactPeople) {
  RunDownload();

  // Contacts should be sent in two ListContactPeople responses, but second
  // request fails.
  SucceedListContactPeopleRequest(
      /*expected_page_token=*/std::nullopt,
      CreateListContactPeopleResponse(
          std::vector<nearby::sharing::proto::ContactRecord>(
              TestContactRecordList().begin(),
              TestContactRecordList().begin() + 1),
          kTestPageToken));
  FailListContactPeopleRequest(
      /*expected_page_token=*/kTestPageToken);

  VerifyFailure();
}

TEST_F(NearbyShareContactDownloaderImplTest, Timeout_ListContactPeople) {
  RunDownload();

  // Contacts should be sent in two ListContactPeople responses. Timeout before
  // second response.
  SucceedListContactPeopleRequest(
      /*expected_page_token=*/std::nullopt,
      CreateListContactPeopleResponse(
          std::vector<nearby::sharing::proto::ContactRecord>(
              TestContactRecordList().begin(),
              TestContactRecordList().begin() + 1),
          kTestPageToken));
  TimeoutListContactPeopleRequest(
      /*expected_page_token=*/kTestPageToken);

  VerifyFailure();
}

TEST_F(NearbyShareContactDownloaderImplTest, Success_FilterOutDeviceContacts) {
  // Disable use of device contacts.
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      features::kNearbySharingDeviceContacts);

  RunDownload();

  SucceedListContactPeopleRequest(
      /*expected_page_token=*/std::nullopt,
      CreateListContactPeopleResponse(TestContactRecordList(),
                                      /*next_page_token=*/std::nullopt));

  // The device contact is filtered out.
  VerifySuccess(/*expected_contacts=*/{TestContactRecordList()[0],
                                       TestContactRecordList()[2]},
                /*expected_num_unreachable_contacts_filtered_out=*/2);
}