chromium/chromeos/ash/components/tether/message_transfer_operation_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/message_transfer_operation.h"

#include <memory>

#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.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_test_util.h"
#include "chromeos/ash/components/timer_factory/fake_one_shot_timer.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"

namespace ash::tether {

namespace {

// Arbitrarily chosen value. The MessageType used in this test does not matter
// except that it must be consistent throughout the test.
const MessageType kTestMessageType = MessageType::TETHER_AVAILABILITY_REQUEST;

const uint32_t kTestTimeoutSeconds = 5;

// A test double for MessageTransferOperation is needed because
// MessageTransferOperation has pure virtual methods which must be overridden in
// order to create a concrete instantiation of the class.
class TestOperation : public MessageTransferOperation {
 public:
  TestOperation(const TetherHost& tether_host,
                raw_ptr<HostConnection::Factory> host_connection_factory)
      : MessageTransferOperation(
            tether_host,
            HostConnection::Factory::ConnectionPriority::kLow,
            host_connection_factory) {}

  ~TestOperation() override = default;

  // MessageTransferOperation:
  void OnDeviceAuthenticated() override { has_device_authenticated_ = true; }

  void OnMessageReceived(
      std::unique_ptr<MessageWrapper> message_wrapper) override {
    received_messages_.push_back(std::move(message_wrapper));

    if (should_stop_operation_on_message_received_) {
      StopOperation();
    }
  }

  void OnOperationStarted() override { has_operation_started_ = true; }

  void OnOperationFinished() override { has_operation_finished_ = true; }

  MessageType GetMessageTypeForConnection() override {
    return kTestMessageType;
  }

  uint32_t GetMessageTimeoutSeconds() override { return timeout_seconds_; }

  void set_timeout_seconds(uint32_t timeout_seconds) {
    timeout_seconds_ = timeout_seconds;
  }

  void set_should_stop_operation_on_message_received(
      bool should_stop_operation_on_message_received) {
    should_stop_operation_on_message_received_ =
        should_stop_operation_on_message_received;
  }

  bool has_device_authenticated() { return has_device_authenticated_; }

  bool has_operation_started() { return has_operation_started_; }

  bool has_operation_finished() { return has_operation_finished_; }

  const std::vector<std::unique_ptr<MessageWrapper>>& get_received_messages() {
    return received_messages_;
  }

 private:
  bool has_device_authenticated_ = false;
  std::vector<std::unique_ptr<MessageWrapper>> received_messages_;

  uint32_t timeout_seconds_ = kTestTimeoutSeconds;
  bool should_stop_operation_on_message_received_ = false;
  bool has_operation_started_ = false;
  bool has_operation_finished_ = false;
};

TetherAvailabilityResponse CreateTetherAvailabilityResponse() {
  TetherAvailabilityResponse response;
  response.set_response_code(
      TetherAvailabilityResponse_ResponseCode::
          TetherAvailabilityResponse_ResponseCode_TETHER_AVAILABLE);
  response.mutable_device_status()->CopyFrom(
      CreateDeviceStatusWithFakeFields());
  return response;
}

}  // namespace

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

 protected:
  MessageTransferOperationTest()
      : tether_host_(TetherHost(multidevice::CreateRemoteDeviceRefForTest())) {}

  void SetUp() override {
    fake_host_connection_factory_ =
        std::make_unique<FakeHostConnection::Factory>();
    operation_ = std::make_unique<TestOperation>(
        tether_host_, fake_host_connection_factory_.get());
    operation_->SetTimerFactoryForTest(
        std::make_unique<ash::timer_factory::FakeTimerFactory>());
    VerifyOperationStartedAndFinished(false /* has_started */,
                                      false /* has_finished */);
  }

  void VerifyOperationStartedAndFinished(bool has_started, bool has_finished) {
    EXPECT_EQ(has_started, operation_->has_operation_started());
    EXPECT_EQ(has_finished, operation_->has_operation_finished());
  }

  ash::timer_factory::FakeOneShotTimer* GetOperationTimer() {
    return static_cast<ash::timer_factory::FakeOneShotTimer*>(
        operation_->remote_device_timer_.get());
  }

  void VerifyDefaultTimerCreated() { VerifyTimerCreated(kTestTimeoutSeconds); }

  void VerifyConnectionTimerCreated() {
    VerifyTimerCreated(MessageTransferOperation::kConnectionTimeoutSeconds);
  }

  void VerifyTimerCreated(uint32_t timeout_seconds) {
    EXPECT_TRUE(GetOperationTimer());
    EXPECT_EQ(base::Seconds(timeout_seconds),
              GetOperationTimer()->GetCurrentDelay());
  }

  void SendMessageToDevice(std::unique_ptr<MessageWrapper> message_wrapper) {
    return operation_->SendMessage(std::move(message_wrapper),
                                   base::DoNothing());
  }

  const TetherHost tether_host_;

  std::unique_ptr<FakeHostConnection::Factory> fake_host_connection_factory_;
  std::unique_ptr<TestOperation> operation_;
};

TEST_F(MessageTransferOperationTest, TestFailedConnection) {
  fake_host_connection_factory_->FailConnectionAttempt(tether_host_);

  operation_->Initialize();

  VerifyOperationStartedAndFinished(true /* has_started */,
                                    true /* has_finished */);
  EXPECT_FALSE(operation_->has_device_authenticated());
  EXPECT_TRUE(operation_->get_received_messages().empty());
}

TEST_F(MessageTransferOperationTest,
       TestSuccessfulConnectionSendAndReceiveMessage) {
  // Simulate how subclasses behave after a successful response: unregister the
  // device.
  operation_->set_should_stop_operation_on_message_received(true);

  fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);

  operation_->Initialize();
  EXPECT_TRUE(operation_->has_device_authenticated());
  VerifyDefaultTimerCreated();

  auto message_wrapper =
      std::make_unique<MessageWrapper>(TetherAvailabilityRequest());
  std::string expected_payload = message_wrapper->ToRawMessage();
  SendMessageToDevice(std::move(message_wrapper));
  const std::vector<std::pair<std::unique_ptr<MessageWrapper>,
                              base::OnceClosure>>& sent_messages =
      fake_host_connection_factory_
          ->GetActiveConnection(tether_host_.GetDeviceId())
          ->sent_messages();
  EXPECT_EQ(1u, sent_messages.size());
  EXPECT_EQ(expected_payload, sent_messages[0].first->ToRawMessage());

  fake_host_connection_factory_->GetActiveConnection(tether_host_.GetDeviceId())
      ->ReceiveMessage(
          std::make_unique<MessageWrapper>(CreateTetherAvailabilityResponse()));

  EXPECT_EQ(1u, operation_->get_received_messages().size());
  const auto& message = operation_->get_received_messages()[0];
  EXPECT_EQ(MessageType::TETHER_AVAILABILITY_RESPONSE,
            message->GetMessageType());
  EXPECT_EQ(CreateTetherAvailabilityResponse().SerializeAsString(),
            message->GetProto()->SerializeAsString());
}

TEST_F(MessageTransferOperationTest, TestTimesOutBeforeAuthentication) {
  operation_->Initialize();
  GetOperationTimer()->Fire();
  EXPECT_TRUE(operation_->has_operation_finished());
}

TEST_F(MessageTransferOperationTest, TestAuthenticatesButThenTimesOut) {
  fake_host_connection_factory_->SetupConnectionAttempt(tether_host_);

  operation_->Initialize();

  EXPECT_TRUE(operation_->has_device_authenticated());
  VerifyDefaultTimerCreated();

  GetOperationTimer()->Fire();

  EXPECT_TRUE(operation_->has_operation_finished());
}

}  // namespace ash::tether