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

// 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