blob: 1cbfd640ee037295c28c75bc3bdbe33405209b13 [file]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/phonehub/connection_manager_impl.h"
#include <memory>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time.h"
#include "base/timer/mock_timer.h"
#include "chromeos/components/multidevice/remote_device_test_util.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
#include "chromeos/services/secure_channel/public/cpp/client/fake_client_channel.h"
#include "chromeos/services/secure_channel/public/cpp/client/fake_connection_attempt.h"
#include "chromeos/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace phonehub {
namespace {
using multidevice_setup::mojom::HostStatus;
constexpr base::TimeDelta kFakeConnectionLatencyTime(
base::TimeDelta::FromSeconds(3u));
constexpr base::TimeDelta kFakeConnectionDurationTime(
base::TimeDelta::FromSeconds(10u));
constexpr base::TimeDelta kExpectedTimeoutSeconds(
base::TimeDelta::FromSeconds(15u));
class FakeObserver : public ConnectionManager::Observer {
public:
FakeObserver() = default;
~FakeObserver() override = default;
size_t status_num_calls() const { return status_changed_num_calls_; }
size_t message_cb_num_calls() const { return message_received_num_calls_; }
const std::string& last_message() const { return last_message_; }
// ConnectionManager::Observer:
void OnConnectionStatusChanged() override { ++status_changed_num_calls_; }
void OnMessageReceived(const std::string& payload) override {
last_message_ = payload;
++message_received_num_calls_;
}
private:
size_t status_changed_num_calls_ = 0;
size_t message_received_num_calls_ = 0;
std::string last_message_;
};
} // namespace
class ConnectionManagerImplTest : public testing::Test {
protected:
ConnectionManagerImplTest()
: test_remote_device_(
chromeos::multidevice::CreateRemoteDeviceRefForTest()),
test_local_device_(
chromeos::multidevice::CreateRemoteDeviceRefForTest()),
fake_secure_channel_client_(
std::make_unique<
chromeos::secure_channel::FakeSecureChannelClient>()) {}
ConnectionManagerImplTest(const ConnectionManagerImplTest&) = delete;
ConnectionManagerImplTest& operator=(const ConnectionManagerImplTest&) =
delete;
~ConnectionManagerImplTest() override = default;
// testing::Test:
void SetUp() override {
auto timer = std::make_unique<base::MockOneShotTimer>();
mock_timer_ = timer.get();
test_clock_ = std::make_unique<base::SimpleTestClock>();
test_clock_->SetNow(base::Time::UnixEpoch());
fake_device_sync_client_.set_local_device_metadata(test_local_device_);
fake_multidevice_setup_client_.SetHostStatusWithDevice(
std::make_pair(HostStatus::kHostVerified, test_remote_device_));
connection_manager_ = base::WrapUnique(new ConnectionManagerImpl(
&fake_multidevice_setup_client_, &fake_device_sync_client_,
fake_secure_channel_client_.get(), std::move(timer),
test_clock_.get()));
connection_manager_->AddObserver(&fake_observer_);
EXPECT_EQ(ConnectionManager::Status::kDisconnected, GetStatus());
}
void TearDown() override {
connection_manager_->RemoveObserver(&fake_observer_);
}
ConnectionManager::Status GetStatus() const {
return connection_manager_->GetStatus();
}
size_t GetNumStatusObserverCalls() const {
return fake_observer_.status_num_calls();
}
size_t GetNumMessageReceivedObserverCalls() const {
return fake_observer_.message_cb_num_calls();
}
void CreateFakeConnectionAttempt() {
auto fake_connection_attempt =
std::make_unique<chromeos::secure_channel::FakeConnectionAttempt>();
fake_connection_attempt_ = fake_connection_attempt.get();
if (features::IsPhoneHubUseBleEnabled()) {
fake_secure_channel_client_->set_next_listen_connection_attempt(
test_remote_device_, test_local_device_,
std::move(fake_connection_attempt));
} else {
fake_secure_channel_client_->set_next_initiate_connection_attempt(
test_remote_device_, test_local_device_,
std::move(fake_connection_attempt));
}
}
void VerifyTimerSet() {
EXPECT_TRUE(mock_timer_->IsRunning());
EXPECT_EQ(kExpectedTimeoutSeconds, mock_timer_->GetCurrentDelay());
}
void VerifyTimerStopped() { EXPECT_FALSE(mock_timer_->IsRunning()); }
void InvokeTimerTask() {
VerifyTimerSet();
mock_timer_->Fire();
}
void VerifyConnectionResultHistogram(
base::HistogramBase::Sample sample,
base::HistogramBase::Count expected_count) {
histogram_tester_.ExpectBucketCount("PhoneHub.Connection.Result", sample,
expected_count);
}
base::MockOneShotTimer* mock_timer_;
chromeos::multidevice::RemoteDeviceRef test_remote_device_;
chromeos::multidevice::RemoteDeviceRef test_local_device_;
device_sync::FakeDeviceSyncClient fake_device_sync_client_;
multidevice_setup::FakeMultiDeviceSetupClient fake_multidevice_setup_client_;
std::unique_ptr<chromeos::secure_channel::FakeSecureChannelClient>
fake_secure_channel_client_;
std::unique_ptr<ConnectionManagerImpl> connection_manager_;
FakeObserver fake_observer_;
chromeos::secure_channel::FakeConnectionAttempt* fake_connection_attempt_;
std::unique_ptr<base::SimpleTestClock> test_clock_;
base::HistogramTester histogram_tester_;
};
TEST_F(ConnectionManagerImplTest, SuccessfullyAttemptConnection) {
CreateFakeConnectionAttempt();
connection_manager_->AttemptConnection();
// Status has been updated to connecting, verify that the status observer
// has been called.
EXPECT_EQ(1u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnecting, GetStatus());
test_clock_->Advance(kFakeConnectionLatencyTime);
auto fake_client_channel =
std::make_unique<chromeos::secure_channel::FakeClientChannel>();
fake_connection_attempt_->NotifyConnection(std::move(fake_client_channel));
// Status has been updated to connected, verify that the status observer has
// been called.
EXPECT_EQ(2u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnected, GetStatus());
histogram_tester_.ExpectTimeBucketCount("PhoneHub.Connectivity.Latency",
kFakeConnectionLatencyTime, 1);
VerifyConnectionResultHistogram(true, 1);
}
TEST_F(ConnectionManagerImplTest, FailedToAttemptConnection) {
CreateFakeConnectionAttempt();
connection_manager_->AttemptConnection();
// Status has been updated to connecting, verify that the status observer
// has been called.
EXPECT_EQ(1u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnecting, GetStatus());
fake_connection_attempt_->NotifyConnectionAttemptFailure(
chromeos::secure_channel::mojom::ConnectionAttemptFailureReason::
AUTHENTICATION_ERROR);
// Status has been updated to disconnected, verify that the status observer
// has been called.
EXPECT_EQ(2u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kDisconnected, GetStatus());
VerifyConnectionResultHistogram(false, 1);
}
TEST_F(ConnectionManagerImplTest, SuccessfulAttemptConnectionButDisconnected) {
CreateFakeConnectionAttempt();
connection_manager_->AttemptConnection();
// Status has been updated to connecting, verify that the status observer
// has been called.
EXPECT_EQ(1u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnecting, GetStatus());
test_clock_->Advance(kFakeConnectionLatencyTime);
auto fake_client_channel =
std::make_unique<chromeos::secure_channel::FakeClientChannel>();
chromeos::secure_channel::FakeClientChannel* fake_client_channel_raw =
fake_client_channel.get();
fake_connection_attempt_->NotifyConnection(std::move(fake_client_channel));
// Status has been updated to connected, verify that the status observer has
// been called.
EXPECT_EQ(2u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnected, GetStatus());
histogram_tester_.ExpectTimeBucketCount("PhoneHub.Connectivity.Latency",
kFakeConnectionLatencyTime, 1);
VerifyConnectionResultHistogram(true, 1);
// Simulate a disconnected channel.
test_clock_->Advance(kFakeConnectionDurationTime);
fake_client_channel_raw->NotifyDisconnected();
// Expect status to be updated to disconnected.
EXPECT_EQ(3u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kDisconnected, GetStatus());
histogram_tester_.ExpectTimeBucketCount("PhoneHub.Connection.Duration",
kFakeConnectionDurationTime, 1);
}
TEST_F(ConnectionManagerImplTest, AttemptConnectionWithMessageReceived) {
CreateFakeConnectionAttempt();
connection_manager_->AttemptConnection();
// Status has been updated to connecting, verify that the status observer
// has been called.
EXPECT_EQ(1u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnecting, GetStatus());
test_clock_->Advance(kFakeConnectionLatencyTime);
auto fake_client_channel =
std::make_unique<chromeos::secure_channel::FakeClientChannel>();
chromeos::secure_channel::FakeClientChannel* fake_client_channel_raw =
fake_client_channel.get();
fake_connection_attempt_->NotifyConnection(std::move(fake_client_channel));
histogram_tester_.ExpectTimeBucketCount("PhoneHub.Connectivity.Latency",
kFakeConnectionLatencyTime, 1);
VerifyConnectionResultHistogram(true, 1);
// Status has been updated to connected, verify that the status observer has
// been called.
EXPECT_EQ(2u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnected, GetStatus());
// Simulate a message being sent.
const std::string expected_payload = "payload";
fake_client_channel_raw->NotifyMessageReceived(expected_payload);
// Expected MessageReceived() callback to be called.
EXPECT_EQ(1u, GetNumMessageReceivedObserverCalls());
EXPECT_EQ(expected_payload, fake_observer_.last_message());
}
TEST_F(ConnectionManagerImplTest, AttemptConnectionWithoutLocalDevice) {
// Simulate a missing local device.
fake_device_sync_client_.set_local_device_metadata(
base::Optional<chromeos::multidevice::RemoteDeviceRef>());
connection_manager_->AttemptConnection();
// Status is still disconnected since there is a missing device, verify that
// the status observer did not get called (exited early).
EXPECT_EQ(0u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kDisconnected, GetStatus());
}
TEST_F(ConnectionManagerImplTest, AttemptConnectionWithoutRemoteDevice) {
// Simulate a missing remote device.
fake_multidevice_setup_client_.SetHostStatusWithDevice(
std::make_pair(HostStatus::kHostVerified,
base::Optional<chromeos::multidevice::RemoteDeviceRef>()));
connection_manager_->AttemptConnection();
// Status is still disconnected since there is a missing device, verify that
// the status observer did not get called (exited early).
EXPECT_EQ(0u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kDisconnected, GetStatus());
}
TEST_F(ConnectionManagerImplTest, SuccessfullyAttemptConnectionWithBle) {
base::test::ScopedFeatureList scoped_feature_list_;
scoped_feature_list_.InitWithFeatures(
{features::kPhoneHub, features::kPhoneHubUseBle}, {});
CreateFakeConnectionAttempt();
connection_manager_->AttemptConnection();
// Status has been updated to connecting, verify that the status observer
// has been called.
EXPECT_EQ(1u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnecting, GetStatus());
test_clock_->Advance(kFakeConnectionLatencyTime);
auto fake_client_channel =
std::make_unique<chromeos::secure_channel::FakeClientChannel>();
fake_connection_attempt_->NotifyConnection(std::move(fake_client_channel));
histogram_tester_.ExpectTimeBucketCount("PhoneHub.Connectivity.Latency",
kFakeConnectionLatencyTime, 1);
VerifyConnectionResultHistogram(true, 1);
// Status has been updated to connected, verify that the status observer has
// been called.
EXPECT_EQ(2u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnected, GetStatus());
}
TEST_F(ConnectionManagerImplTest, ConnectionTimeout) {
CreateFakeConnectionAttempt();
connection_manager_->AttemptConnection();
// Status has been updated to connecting, verify that the status observer
// has been called.
EXPECT_EQ(1u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnecting, GetStatus());
VerifyTimerSet();
// Simulate fast forwarding time to time out the connection request.
InvokeTimerTask();
VerifyTimerStopped();
EXPECT_EQ(2u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kDisconnected, GetStatus());
VerifyConnectionResultHistogram(false, 1);
}
TEST_F(ConnectionManagerImplTest, DisconnectConnection) {
CreateFakeConnectionAttempt();
connection_manager_->AttemptConnection();
// Status has been updated to connecting, verify that the status observer
// has been called.
EXPECT_EQ(1u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kConnecting, GetStatus());
VerifyTimerSet();
// Disconnect the connection attempt.
connection_manager_->Disconnect();
EXPECT_EQ(2u, GetNumStatusObserverCalls());
EXPECT_EQ(ConnectionManager::Status::kDisconnected, GetStatus());
VerifyTimerStopped();
VerifyConnectionResultHistogram(false, 1);
}
} // namespace phonehub
} // namespace chromeos