chromium/chromeos/ash/components/tether/gms_core_notifications_state_tracker_impl_unittest.cc

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

#include "chromeos/ash/components/tether/gms_core_notifications_state_tracker_impl.h"

#include <memory>
#include <sstream>
#include <string>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "chromeos/ash/components/multidevice/remote_device_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::tether {

namespace {

const int kNumTestDevices = 3;

// Creates test devices which have the naming scheme of "testDevice0",
// "testDevice1", etc.
std::vector<ScannedDeviceInfo> CreateTestDevices() {
  multidevice::RemoteDeviceRefList remote_devices =
      multidevice::CreateRemoteDeviceRefListForTest(kNumTestDevices);
  std::vector<ScannedDeviceInfo> test_devices;
  for (size_t i = 0; i < kNumTestDevices; ++i) {
    test_devices.emplace_back(
        remote_devices[i].GetDeviceId(), remote_devices[i].name(),
        /*device_status=*/std::nullopt, /*setup_required=*/false,
        /*notifications_enabled=*/false);
  }

  return test_devices;
}

class TestObserver final : public GmsCoreNotificationsStateTracker::Observer {
 public:
  explicit TestObserver(GmsCoreNotificationsStateTrackerImpl* tracker)
      : tracker_(tracker) {}

  TestObserver(const TestObserver&) = delete;
  TestObserver& operator=(const TestObserver&) = delete;

  ~TestObserver() = default;

  uint32_t change_count() const { return change_count_; }

  const std::vector<std::string>& names_from_last_update() {
    return names_from_last_update_;
  }

  // GmsCoreNotificationsStateTracker::Observer:
  void OnGmsCoreNotificationStateChanged() override {
    names_from_last_update_ =
        tracker_->GetGmsCoreNotificationsDisabledDeviceNames();
    ++change_count_;
  }

 private:
  raw_ptr<GmsCoreNotificationsStateTrackerImpl, DanglingUntriaged> tracker_;

  uint32_t change_count_ = 0;
  std::vector<std::string> names_from_last_update_;
};

}  // namespace

class GmsCoreNotificationsStateTrackerImplTest : public testing::Test {
 public:
  GmsCoreNotificationsStateTrackerImplTest(
      const GmsCoreNotificationsStateTrackerImplTest&) = delete;
  GmsCoreNotificationsStateTrackerImplTest& operator=(
      const GmsCoreNotificationsStateTrackerImplTest&) = delete;

 protected:
  GmsCoreNotificationsStateTrackerImplTest()
      : test_devices_(CreateTestDevices()) {}

  void SetUp() override {
    devices_to_send_.clear();
    scanned_device_infos_.clear();

    tracker_ = std::make_unique<GmsCoreNotificationsStateTrackerImpl>();

    observer_ = std::make_unique<TestObserver>(tracker_.get());
    tracker_->AddObserver(observer_.get());
  }

  void VerifyExpectedNames(const std::vector<std::string> expected_names,
                           size_t expected_change_count) {
    EXPECT_EQ(expected_names, observer_->names_from_last_update());
    EXPECT_EQ(expected_change_count, observer_->change_count());
  }

  void ReceiveTetherAvailabilityResponse(bool is_final_scan_result) {
    tracker_->OnTetherAvailabilityResponse(
        scanned_device_infos_, devices_to_send_, is_final_scan_result);
  }

  void AddScannedTetherHost(ScannedDeviceInfo scanned_device_info) {
    scanned_device_infos_.emplace_back(
        scanned_device_info.device_id, scanned_device_info.device_name,
        DeviceStatus(), false /* setup_required */,
        /*notifications_enabled=*/true);
  }

  const std::vector<ScannedDeviceInfo> test_devices_;

  std::vector<ScannedDeviceInfo> scanned_device_infos_;
  std::vector<ScannedDeviceInfo> devices_to_send_;

  std::unique_ptr<GmsCoreNotificationsStateTrackerImpl> tracker_;
  std::unique_ptr<TestObserver> observer_;
};

TEST_F(GmsCoreNotificationsStateTrackerImplTest, TestTracking) {
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames(std::vector<std::string>(),
                      0 /* expected_change_count */);

  // Add two devices and verify that they are now tracked.
  devices_to_send_.push_back(test_devices_[0]);
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name} /* expected_names */,
                      1 /* expected_change_count */);
  devices_to_send_.push_back(test_devices_[1]);
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name,
                       test_devices_[1].device_name} /* expected_names */,
                      2 /* expected_change_count */);

  // Receive another response with the same list; this should not result in an
  // additional "state changed" event.
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name,
                       test_devices_[1].device_name} /* expected_names */,
                      2 /* expected_change_count */);

  // End the scan session.
  ReceiveTetherAvailabilityResponse(true /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name,
                       test_devices_[1].device_name} /* expected_names */,
                      2 /* expected_change_count */);

  // Start a new session; since the previous session contains device 0 and
  // device 1, these devices should remain at least until the scan session ends.
  devices_to_send_.clear();
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name,
                       test_devices_[1].device_name} /* expected_names */,
                      2 /* expected_change_count */);

  // Add two devices (one new and one from a previous session).
  devices_to_send_.push_back(test_devices_[0]);
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name,
                       test_devices_[1].device_name} /* expected_names */,
                      2 /* expected_change_count */);
  devices_to_send_.push_back(test_devices_[2]);
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames(
      {test_devices_[0].device_name, test_devices_[1].device_name,
       test_devices_[2].device_name} /* expected_names */,
      3 /* expected_change_count */);

  // End the scan session; since "session2" was not present in this session, it
  // should be removed.
  ReceiveTetherAvailabilityResponse(true /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name,
                       test_devices_[2].device_name} /* expected_names */,
                      4 /* expected_change_count */);

  // Start another session (devices 0 and 2 should still be present).
  devices_to_send_.clear();
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name,
                       test_devices_[2].device_name} /* expected_names */,
                      4 /* expected_change_count */);

  // Add device 0 as a potential tether host. This should cause it to be removed
  // from the list without notifications, even though the scan session has not
  // yet ended.
  AddScannedTetherHost(test_devices_[0]);
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[2].device_name} /* expected_names */,
                      5 /* expected_change_count */);

  // Keep device 2 in the list; since it already existed from the previous scan
  // session, no new event should have occurred.
  devices_to_send_.push_back(test_devices_[2]);
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[2].device_name} /* expected_names */,
                      5 /* expected_change_count */);

  // End the scan session.
  ReceiveTetherAvailabilityResponse(true /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[2].device_name} /* expected_names */,
                      5 /* expected_change_count */);

  // Now, destroy |tracker_|; this should result in one more change event.
  tracker_.reset();
  VerifyExpectedNames({} /* expected_names */, 6 /* expected_change_count */);
}

TEST_F(GmsCoreNotificationsStateTrackerImplTest, TestTracking_SameName) {
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames(std::vector<std::string>(),
                      0 /* expected_change_count */);

  // Add device 0.
  devices_to_send_.push_back(test_devices_[0]);
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name} /* expected_names */,
                      1 /* expected_change_count */);

  // Add the updated device 1. Both names should be sent in the event, even
  // though they are the same name.
  devices_to_send_.emplace_back(
      test_devices_[1].device_id, test_devices_[0].device_name,
      /*device_status=*/std::nullopt, /*setup_required=*/false,
      /*notifications_enabled=*/false);
  ReceiveTetherAvailabilityResponse(false /* is_final_scan_result */);
  VerifyExpectedNames({test_devices_[0].device_name,
                       test_devices_[0].device_name} /* expected_names */,
                      2 /* expected_change_count */);

  tracker_.reset();
  VerifyExpectedNames({} /* expected_names */, 3 /* expected_change_count */);
}

}  // namespace ash::tether