blob: 8769d337c81731517530008e981b7c5bb6b318ca [file] [log] [blame]
// Copyright 2016 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/tether/connect_tethering_operation.h"
#include <memory>
#include <vector>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_task_environment.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/components/tether/message_wrapper.h"
#include "chromeos/components/tether/mock_tether_host_response_recorder.h"
#include "chromeos/components/tether/proto/tether.pb.h"
#include "chromeos/components/tether/proto_test_util.h"
#include "chromeos/components/tether/test_timer_factory.h"
#include "chromeos/services/device_sync/public/cpp/fake_device_sync_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/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::StrictMock;
namespace chromeos {
namespace tether {
namespace {
constexpr base::TimeDelta kConnectTetheringResponseTimeSeconds =
base::TimeDelta::FromSeconds(15);
// Used to verify the ConnectTetheringOperation notifies the observer
// when appropriate.
class MockOperationObserver : public ConnectTetheringOperation::Observer {
public:
MockOperationObserver() = default;
~MockOperationObserver() = default;
MOCK_METHOD1(OnConnectTetheringRequestSent,
void(multidevice::RemoteDeviceRef));
MOCK_METHOD3(OnSuccessfulConnectTetheringResponse,
void(multidevice::RemoteDeviceRef,
const std::string&,
const std::string&));
MOCK_METHOD2(OnConnectTetheringFailure,
void(multidevice::RemoteDeviceRef,
ConnectTetheringOperation::HostResponseErrorCode));
private:
DISALLOW_COPY_AND_ASSIGN(MockOperationObserver);
};
} // namespace
class ConnectTetheringOperationTest : public testing::Test {
protected:
ConnectTetheringOperationTest()
: test_local_device_(multidevice::RemoteDeviceRefBuilder()
.SetPublicKey("local device")
.Build()),
remote_device_(multidevice::CreateRemoteDeviceRefForTest()) {}
void SetUp() override {
mock_tether_host_response_recorder_ =
std::make_unique<StrictMock<MockTetherHostResponseRecorder>>();
fake_device_sync_client_ =
std::make_unique<device_sync::FakeDeviceSyncClient>();
fake_device_sync_client_->set_local_device_metadata(test_local_device_);
fake_secure_channel_client_ =
std::make_unique<secure_channel::FakeSecureChannelClient>();
operation_ = ConstructOperation();
operation_->Initialize();
ConnectAuthenticatedChannelForDevice(remote_device_);
}
std::unique_ptr<ConnectTetheringOperation> ConstructOperation() {
std::unique_ptr<ConnectTetheringOperation> operation;
test_timer_factory_ = new TestTimerFactory();
// Prepare the connection timeout timer to be made for the remote device.
test_timer_factory_->set_device_id_for_next_timer(
remote_device_.GetDeviceId());
auto fake_connection_attempt =
std::make_unique<secure_channel::FakeConnectionAttempt>();
remote_device_to_fake_connection_attempt_map_[remote_device_] =
fake_connection_attempt.get();
fake_secure_channel_client_->set_next_listen_connection_attempt(
remote_device_, test_local_device_, std::move(fake_connection_attempt));
operation = base::WrapUnique(new ConnectTetheringOperation(
remote_device_, fake_device_sync_client_.get(),
fake_secure_channel_client_.get(),
mock_tether_host_response_recorder_.get(), false /* setup_required */));
operation->SetTimerFactoryForTest(base::WrapUnique(test_timer_factory_));
operation->AddObserver(&mock_observer_);
test_clock_.SetNow(base::Time::UnixEpoch());
operation->SetClockForTest(&test_clock_);
return operation;
}
void ConnectAuthenticatedChannelForDevice(
multidevice::RemoteDeviceRef remote_device) {
auto fake_client_channel =
std::make_unique<secure_channel::FakeClientChannel>();
remote_device_to_fake_client_channel_map_[remote_device] =
fake_client_channel.get();
remote_device_to_fake_connection_attempt_map_[remote_device]
->NotifyConnection(std::move(fake_client_channel));
}
const multidevice::RemoteDeviceRef test_local_device_;
const multidevice::RemoteDeviceRef remote_device_;
base::flat_map<multidevice::RemoteDeviceRef,
secure_channel::FakeConnectionAttempt*>
remote_device_to_fake_connection_attempt_map_;
base::flat_map<multidevice::RemoteDeviceRef,
secure_channel::FakeClientChannel*>
remote_device_to_fake_client_channel_map_;
std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
std::unique_ptr<secure_channel::FakeSecureChannelClient>
fake_secure_channel_client_;
std::unique_ptr<StrictMock<MockTetherHostResponseRecorder>>
mock_tether_host_response_recorder_;
base::SimpleTestClock test_clock_;
TestTimerFactory* test_timer_factory_;
MockOperationObserver mock_observer_;
base::HistogramTester histogram_tester_;
std::unique_ptr<ConnectTetheringOperation> operation_;
};
TEST_F(ConnectTetheringOperationTest, SuccessWithValidResponse) {
static const std::string kTestSsid = "testSsid";
static const std::string kTestPassword = "testPassword";
EXPECT_CALL(*mock_tether_host_response_recorder_,
RecordSuccessfulConnectTetheringResponse(remote_device_));
// Verify that the Observer is called with success and the correct parameters.
EXPECT_CALL(mock_observer_, OnSuccessfulConnectTetheringResponse(
remote_device_, kTestSsid, kTestPassword));
// Advance the clock in order to verify a non-zero response duration is
// recorded and verified (below).
test_clock_.Advance(kConnectTetheringResponseTimeSeconds);
// The ConnectTetheringResponse message contains the success response code and
// the required SSID and password parameters.
ConnectTetheringResponse response;
response.set_response_code(ConnectTetheringResponse_ResponseCode::
ConnectTetheringResponse_ResponseCode_SUCCESS);
response.set_ssid(kTestSsid);
response.set_password(kTestPassword);
std::unique_ptr<MessageWrapper> message(new MessageWrapper(response));
operation_->OnMessageReceived(std::move(message), remote_device_);
// Verify the response duration metric is recorded.
histogram_tester_.ExpectTimeBucketCount(
"InstantTethering.Performance.ConnectTetheringResponseDuration",
kConnectTetheringResponseTimeSeconds, 1);
}
// Tests that the SSID and password parameters are a required parameters of the
// success response code; failure to provide these parameters results in a
// failed tethering connection.
TEST_F(ConnectTetheringOperationTest, SuccessButInvalidResponse) {
EXPECT_CALL(*mock_tether_host_response_recorder_,
RecordSuccessfulConnectTetheringResponse(_))
.Times(0);
// Verify that the observer is called with failure and the appropriate error
// code.
EXPECT_CALL(
mock_observer_,
OnConnectTetheringFailure(
remote_device_, ConnectTetheringOperation::HostResponseErrorCode::
INVALID_HOTSPOT_CREDENTIALS));
// The ConnectTetheringResponse message does not contain the required SSID and
// password fields.
ConnectTetheringResponse response;
response.set_response_code(ConnectTetheringResponse_ResponseCode::
ConnectTetheringResponse_ResponseCode_SUCCESS);
std::unique_ptr<MessageWrapper> message(new MessageWrapper(response));
operation_->OnMessageReceived(std::move(message), remote_device_);
}
TEST_F(ConnectTetheringOperationTest, UnknownError) {
EXPECT_CALL(*mock_tether_host_response_recorder_,
RecordSuccessfulConnectTetheringResponse(_))
.Times(0);
// Verify that the observer is called with failure and the appropriate error
// code.
EXPECT_CALL(
mock_observer_,
OnConnectTetheringFailure(
remote_device_,
ConnectTetheringOperation::HostResponseErrorCode::UNKNOWN_ERROR));
ConnectTetheringResponse response;
response.set_response_code(
ConnectTetheringResponse_ResponseCode::
ConnectTetheringResponse_ResponseCode_UNKNOWN_ERROR);
std::unique_ptr<MessageWrapper> message(new MessageWrapper(response));
operation_->OnMessageReceived(std::move(message), remote_device_);
}
TEST_F(ConnectTetheringOperationTest, ProvisioningFailed) {
EXPECT_CALL(*mock_tether_host_response_recorder_,
RecordSuccessfulConnectTetheringResponse(_))
.Times(0);
// Verify that the observer is called with failure and the appropriate error
// code.
EXPECT_CALL(
mock_observer_,
OnConnectTetheringFailure(
remote_device_, ConnectTetheringOperation::HostResponseErrorCode::
PROVISIONING_FAILED));
ConnectTetheringResponse response;
response.set_response_code(
ConnectTetheringResponse_ResponseCode::
ConnectTetheringResponse_ResponseCode_PROVISIONING_FAILED);
std::unique_ptr<MessageWrapper> message(new MessageWrapper(response));
operation_->OnMessageReceived(std::move(message), remote_device_);
}
// Tests that observers are notified when the connection request is sent.
TEST_F(ConnectTetheringOperationTest, NotifyConnectTetheringRequest) {
EXPECT_CALL(mock_observer_, OnConnectTetheringRequestSent(remote_device_));
operation_->OnMessageSent(0 /* sequence_number */);
}
// Tests that the message timeout value varies based on whether setup is
// required or not.
TEST_F(ConnectTetheringOperationTest, GetMessageTimeoutSeconds) {
// Setup required case.
std::unique_ptr<ConnectTetheringOperation> operation(
new ConnectTetheringOperation(remote_device_,
fake_device_sync_client_.get(),
fake_secure_channel_client_.get(),
mock_tether_host_response_recorder_.get(),
true /* setup_required */));
EXPECT_EQ(ConnectTetheringOperation::kSetupRequiredResponseTimeoutSeconds,
operation->GetMessageTimeoutSeconds());
// Setup not required case.
operation.reset(new ConnectTetheringOperation(
remote_device_, fake_device_sync_client_.get(),
fake_secure_channel_client_.get(),
mock_tether_host_response_recorder_.get(), false /* setup_required */));
EXPECT_EQ(ConnectTetheringOperation::kSetupNotRequiredResponseTimeoutSeconds,
operation->GetMessageTimeoutSeconds());
}
// Tests that the ConnectTetheringRequest message is sent to the remote device
// once the communication channel is connected and authenticated.
TEST_F(ConnectTetheringOperationTest, ConnectRequestSentOnceAuthenticated) {
std::unique_ptr<ConnectTetheringOperation> operation = ConstructOperation();
operation->Initialize();
// Create the client channel to the remote device.
auto fake_client_channel =
std::make_unique<secure_channel::FakeClientChannel>();
remote_device_to_fake_client_channel_map_[remote_device_] =
fake_client_channel.get();
// No requests as a result of creating the client channel.
auto& sent_messages = fake_client_channel->sent_messages();
EXPECT_EQ(0u, sent_messages.size());
// Connect and authenticate the client channel.
remote_device_to_fake_connection_attempt_map_[remote_device_]
->NotifyConnection(std::move(fake_client_channel));
// Verify the ConnectTetheringRequest message is sent.
auto message_wrapper =
std::make_unique<MessageWrapper>(ConnectTetheringRequest());
std::string expected_payload = message_wrapper->ToRawMessage();
EXPECT_EQ(1u, sent_messages.size());
EXPECT_EQ(expected_payload, sent_messages[0].first);
}
} // namespace tether
} // namespace chromeos