// Copyright 2016 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/connect_tethering_operation.h"
#include <memory>
#include <optional>
#include <vector>
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/timer/mock_timer.h"
#include "chromeos/ash/components/multidevice/remote_device_test_util.h"
#include "chromeos/ash/components/tether/fake_host_connection.h"
#include "chromeos/ash/components/tether/message_wrapper.h"
#include "chromeos/ash/components/tether/proto/tether.pb.h"
#include "chromeos/ash/components/tether/proto_test_util.h"
#include "chromeos/ash/components/timer_factory/fake_timer_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::StrictMock;
namespace ash::tether {
namespace {
constexpr base::TimeDelta kConnectTetheringResponseTimeSeconds =
base::Seconds(15);
// Used to verify the ConnectTetheringOperation notifies the observer
// when appropriate.
class MockOperationObserver : public ConnectTetheringOperation::Observer {
public:
MockOperationObserver() = default;
MockOperationObserver(const MockOperationObserver&) = delete;
MockOperationObserver& operator=(const MockOperationObserver&) = delete;
~MockOperationObserver() = default;
MOCK_METHOD0(OnConnectTetheringRequestSent, void());
MOCK_METHOD2(OnSuccessfulConnectTetheringResponse,
void(const std::string&, const std::string&));
MOCK_METHOD1(OnConnectTetheringFailure,
void(ConnectTetheringOperation::HostResponseErrorCode));
};
} // namespace
class ConnectTetheringOperationTest : public testing::Test {
protected:
ConnectTetheringOperationTest()
: tether_host_(TetherHost(multidevice::CreateRemoteDeviceRefForTest())) {}
void SetUp() override {
fake_host_connection_factory_ =
std::make_unique<FakeHostConnection::Factory>();
operation_ = base::WrapUnique(new ConnectTetheringOperation(
tether_host_, fake_host_connection_factory_.get(),
/*setup_required=*/false));
operation_->SetTimerFactoryForTest(
std::make_unique<ash::timer_factory::FakeTimerFactory>());
operation_->AddObserver(&mock_observer_);
test_clock_.SetNow(base::Time::UnixEpoch());
operation_->SetClockForTest(&test_clock_);
fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
}
const TetherHost tether_host_;
std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
base::SimpleTestClock test_clock_;
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";
// Verify that the Observer is called with success and the correct parameters.
EXPECT_CALL(mock_observer_,
OnSuccessfulConnectTetheringResponse(kTestSsid, kTestPassword));
operation_->Initialize();
// 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));
// 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) {
// Verify that the observer is called with failure and the appropriate error
// code.
EXPECT_CALL(mock_observer_,
OnConnectTetheringFailure(
ConnectTetheringOperation::HostResponseErrorCode::
INVALID_HOTSPOT_CREDENTIALS));
operation_->Initialize();
// 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));
}
TEST_F(ConnectTetheringOperationTest, UnknownError) {
// Verify that the observer is called with failure and the appropriate error
// code.
EXPECT_CALL(
mock_observer_,
OnConnectTetheringFailure(
ConnectTetheringOperation::HostResponseErrorCode::UNKNOWN_ERROR));
operation_->Initialize();
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));
}
TEST_F(ConnectTetheringOperationTest, ProvisioningFailed) {
// Verify that the observer is called with failure and the appropriate error
// code.
EXPECT_CALL(mock_observer_,
OnConnectTetheringFailure(
ConnectTetheringOperation::HostResponseErrorCode::
PROVISIONING_FAILED));
operation_->Initialize();
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));
}
TEST_F(ConnectTetheringOperationTest, InvalidWifiApConfig) {
// Verify that the observer is called with failure and the appropriate error
// code.
EXPECT_CALL(mock_observer_,
OnConnectTetheringFailure(
ConnectTetheringOperation::HostResponseErrorCode::
INVALID_WIFI_AP_CONFIG));
operation_->Initialize();
ConnectTetheringResponse response;
response.set_response_code(
ConnectTetheringResponse_ResponseCode::
ConnectTetheringResponse_ResponseCode_INVALID_WIFI_AP_CONFIG);
std::unique_ptr<MessageWrapper> message(new MessageWrapper(response));
operation_->OnMessageReceived(std::move(message));
}
TEST_F(ConnectTetheringOperationTest, InvalidActiveExistingSoftApConfig) {
// Verify that the observer is called with failure and the appropriate error
// code.
EXPECT_CALL(mock_observer_,
OnConnectTetheringFailure(
ConnectTetheringOperation::HostResponseErrorCode::
INVALID_ACTIVE_EXISTING_SOFT_AP_CONFIG));
operation_->Initialize();
ConnectTetheringResponse response;
response.set_response_code(
ConnectTetheringResponse_ResponseCode::
ConnectTetheringResponse_ResponseCode_INVALID_ACTIVE_EXISTING_SOFT_AP_CONFIG);
std::unique_ptr<MessageWrapper> message(new MessageWrapper(response));
operation_->OnMessageReceived(std::move(message));
}
TEST_F(ConnectTetheringOperationTest, InvalidNewSoftApConfig) {
// Verify that the observer is called with failure and the appropriate error
// code.
EXPECT_CALL(mock_observer_,
OnConnectTetheringFailure(
ConnectTetheringOperation::HostResponseErrorCode::
INVALID_NEW_SOFT_AP_CONFIG));
operation_->Initialize();
ConnectTetheringResponse response;
response.set_response_code(
ConnectTetheringResponse_ResponseCode::
ConnectTetheringResponse_ResponseCode_INVALID_NEW_SOFT_AP_CONFIG);
std::unique_ptr<MessageWrapper> message(new MessageWrapper(response));
operation_->OnMessageReceived(std::move(message));
}
// 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(tether_host_,
fake_host_connection_factory_.get(),
true /* setup_required */));
EXPECT_EQ(ConnectTetheringOperation::kSetupRequiredResponseTimeoutSeconds,
operation->GetMessageTimeoutSeconds());
// Setup not required case.
operation.reset(new ConnectTetheringOperation(
tether_host_, fake_host_connection_factory_.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) {
fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);
// Connect and authenticate the client channel.
operation_->Initialize();
// Verify the ConnectTetheringRequest message is sent.
auto message_wrapper =
std::make_unique<MessageWrapper>(ConnectTetheringRequest());
auto& sent_messages = fake_host_connection_factory_
->GetActiveConnection(tether_host_.GetDeviceId())
->sent_messages();
std::string expected_payload = message_wrapper->ToRawMessage();
EXPECT_EQ(1u, sent_messages.size());
EXPECT_EQ(expected_payload, sent_messages[0].first->ToRawMessage());
}
} // namespace ash::tether