chromium/chromeos/ash/services/cellular_setup/ota_activator_impl_unittest.cc

// Copyright 2019 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/services/cellular_setup/ota_activator_impl.h"

#include <memory>
#include <utility>

#include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "chromeos/ash/components/network/fake_network_activation_handler.h"
#include "chromeos/ash/components/network/fake_network_connection_handler.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_state_test_helper.h"
#include "chromeos/ash/services/cellular_setup/public/cpp/fake_activation_delegate.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash::cellular_setup {

namespace {

const char kTestCellularDevicePath[] = "/device/wwan0";
const char kTestCellularDeviceName[] = "testDeviceName";
const char kTestCellularDeviceCarrier[] = "testDeviceCarrier";
const char kTestCellularDeviceMeid[] = "testDeviceMeid";
const char kTestCellularDeviceImei[] = "testDeviceImei";
const char kTestCellularDeviceMdn[] = "testDeviceMdn";

const char kTestCellularServicePath[] = "/service/cellular0";
const char kTestCellularServiceIccid[] = "1234567890";
const char kTestCellularServiceGuid[] = "testServiceGuid";
const char kTestCellularServiceName[] = "testServiceName";
const char kTestCellularServicePaymentUrl[] = "testServicePaymentUrl.com";
const char kTestCellularServicePaymentPostData[] = "testServicePostData";

const char kPaymentPortalMethodPost[] = "POST";

}  // namespace

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

 protected:
  CellularSetupOtaActivatorImplTest()
      : test_helper_(/*use_default_devices_and_services=*/false) {}
  ~CellularSetupOtaActivatorImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    fake_activation_delegate_ = std::make_unique<FakeActivationDelegate>();
    fake_network_connection_handler_ =
        std::make_unique<FakeNetworkConnectionHandler>();
    fake_network_activation_handler_ =
        std::make_unique<FakeNetworkActivationHandler>();
  }

  void BuildOtaActivator() {
    auto test_task_runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
    ota_activator_ = OtaActivatorImpl::Factory::Create(
        fake_activation_delegate_->GenerateRemote(),
        base::BindOnce(&CellularSetupOtaActivatorImplTest::OnFinished,
                       base::Unretained(this)),
        test_helper_.network_state_handler(),
        fake_network_connection_handler_.get(),
        fake_network_activation_handler_.get(), test_task_runner);
    test_task_runner->RunUntilIdle();
    carrier_portal_handler_remote_.Bind(ota_activator_->GenerateRemote());
  }

  void AddCellularDevice(bool has_valid_sim, bool has_physical_slots = true) {
    ShillDeviceClient::TestInterface* device_test = test_helper_.device_test();
    device_test->AddDevice(kTestCellularDevicePath, shill::kTypeCellular,
                           kTestCellularDeviceName);

    if (has_valid_sim) {
      device_test->SetDeviceProperty(
          kTestCellularDevicePath, shill::kSIMPresentProperty,
          base::Value(true), false /* notify_changed */);

      device_test->SetDeviceProperty(
          kTestCellularDevicePath, shill::kSIMSlotInfoProperty,
          CreateCellularSIMSlotInfo(kTestCellularServiceIccid),
          false /* notify_changed */);

      base::Value::Dict home_provider;
      home_provider.Set(shill::kOperatorNameKey, kTestCellularDeviceCarrier);
      device_test->SetDeviceProperty(
          kTestCellularDevicePath, shill::kHomeProviderProperty,
          base::Value(std::move(home_provider)), false /* notify_changed */);
      device_test->SetDeviceProperty(
          kTestCellularDevicePath, shill::kMeidProperty,
          base::Value(kTestCellularDeviceMeid), false /* notify_changed */);
      device_test->SetDeviceProperty(
          kTestCellularDevicePath, shill::kImeiProperty,
          base::Value(kTestCellularDeviceImei), false /* notify_changed */);
      device_test->SetDeviceProperty(
          kTestCellularDevicePath, shill::kMdnProperty,
          base::Value(kTestCellularDeviceMdn), false /* notify_changed */);
    } else {
      std::string eid = has_physical_slots ? std::string() : "test_eid";
      device_test->SetDeviceProperty(
          kTestCellularDevicePath, shill::kSIMSlotInfoProperty,
          CreateCellularSIMSlotInfo(kTestCellularServiceIccid, eid),
          false /* notify_changed */);
    }

    base::RunLoop().RunUntilIdle();
  }

  void RemoveCellularDevice() {
    test_helper_.device_test()->RemoveDevice(kTestCellularDevicePath);
    base::RunLoop().RunUntilIdle();
  }

  void AddCellularNetwork(bool has_valid_payment_info,
                          bool is_connected,
                          bool is_already_activated) {
    ShillServiceClient::TestInterface* service_test =
        test_helper_.service_test();
    service_test->AddService(
        kTestCellularServicePath, kTestCellularServiceGuid,
        kTestCellularServiceName, shill::kTypeCellular,
        is_connected ? shill::kStateOnline : shill::kStateIdle, true);
    service_test->SetServiceProperty(kTestCellularServicePath,
                                     shill::kIccidProperty,
                                     base::Value(kTestCellularServiceIccid));
    service_test->SetServiceProperty(
        kTestCellularServicePath, shill::kActivationStateProperty,
        is_already_activated
            ? base::Value(shill::kActivationStateActivated)
            : base::Value(shill::kActivationStateNotActivated));

    if (has_valid_payment_info) {
      base::Value::Dict payment_portal;
      payment_portal.Set(shill::kPaymentPortalURL,
                         kTestCellularServicePaymentUrl);
      payment_portal.Set(shill::kPaymentPortalMethod, kPaymentPortalMethodPost);
      payment_portal.Set(shill::kPaymentPortalPostData,
                         kTestCellularServicePaymentPostData);
      service_test->SetServiceProperty(kTestCellularServicePath,
                                       shill::kPaymentPortalProperty,
                                       base::Value(std::move(payment_portal)));
    }

    base::RunLoop().RunUntilIdle();
  }

  void RemoveCellularNetwork() {
    test_helper_.service_test()->RemoveService(kTestCellularServicePath);
    base::RunLoop().RunUntilIdle();
  }

  void FlushForTesting() {
    static_cast<OtaActivatorImpl*>(ota_activator_.get())->FlushForTesting();
  }

  void VerifyCellularMetadataReceivedByDelegate() {
    const std::vector<mojom::CellularMetadataPtr>& cellular_metadata_list =
        fake_activation_delegate_->cellular_metadata_list();
    ASSERT_EQ(1u, cellular_metadata_list.size());

    EXPECT_EQ(GURL(kTestCellularServicePaymentUrl),
              cellular_metadata_list[0]->payment_url);
    EXPECT_EQ(kTestCellularDeviceCarrier, cellular_metadata_list[0]->carrier);
    EXPECT_EQ(kTestCellularDeviceMeid, cellular_metadata_list[0]->meid);
    EXPECT_EQ(kTestCellularDeviceImei, cellular_metadata_list[0]->imei);
    EXPECT_EQ(kTestCellularDeviceMdn, cellular_metadata_list[0]->mdn);
  }

  void UpdateCarrierPortalState(
      mojom::CarrierPortalStatus carrier_portal_status) {
    carrier_portal_handler_remote_->OnCarrierPortalStatusChange(
        carrier_portal_status);
    carrier_portal_handler_remote_.FlushForTesting();
  }

  void ConnectCellularNetwork() {
    const std::vector<FakeNetworkConnectionHandler::ConnectionParams>&
        connect_calls = fake_network_connection_handler_->connect_calls();
    ASSERT_FALSE(connect_calls.empty());

    // A connection should have been requested by |ota_activator_|.
    EXPECT_EQ(kTestCellularServicePath, connect_calls.back().service_path());

    // Simulate the connection succeeding.
    test_helper_.service_test()->SetServiceProperty(
        kTestCellularServicePath, shill::kStateProperty,
        base::Value(shill::kStateOnline));
    base::RunLoop().RunUntilIdle();
  }

  void FailCellularNetworkConnect() {
    std::vector<FakeNetworkConnectionHandler::ConnectionParams>& connect_calls =
        fake_network_connection_handler_->connect_calls();
    ASSERT_FALSE(connect_calls.empty());

    // A connection should have been requested by |ota_activator_|.
    EXPECT_EQ(kTestCellularServicePath, connect_calls.back().service_path());
    EXPECT_EQ(ConnectCallbackMode::ON_COMPLETED,
              connect_calls.back().connect_callback_mode());
    connect_calls.back().InvokeErrorCallback("fake_error");
    base::RunLoop().RunUntilIdle();
  }

  size_t ConnectCallCount() {
    return fake_network_connection_handler_->connect_calls().size();
  }

  void InvokePendingActivationCallback(bool success) {
    std::vector<FakeNetworkActivationHandler::ActivationParams>&
        complete_activation_calls =
            fake_network_activation_handler_->complete_activation_calls();
    ASSERT_EQ(1u, complete_activation_calls.size());
    EXPECT_EQ(kTestCellularServicePath,
              complete_activation_calls[0].service_path());

    if (success) {
      complete_activation_calls[0].InvokeSuccessCallback();
    } else {
      complete_activation_calls[0].InvokeErrorCallback("error");
    }
  }

  void VerifyActivationFinished(mojom::ActivationResult activation_result) {
    const std::vector<mojom::ActivationResult>& activation_results =
        fake_activation_delegate_->activation_results();
    ASSERT_EQ(1u, activation_results.size());
    EXPECT_EQ(activation_result, activation_results[0]);

    histogram_tester_.ExpectBucketCount(
        "Network.Cellular.PSim.OtaActivationResult", activation_result,
        /*expected_count=*/1);

    EXPECT_TRUE(is_finished_);
  }

  void DisconnectDelegate() {
    fake_activation_delegate_->DisconnectReceivers();
  }

  void FastForwardByConnectRetryDelay() {
    task_environment_.FastForwardBy(OtaActivatorImpl::kConnectRetryDelay);
  }

  bool is_finished() { return is_finished_; }

 private:
  void OnFinished() {
    EXPECT_FALSE(is_finished_);
    is_finished_ = true;
  }

  base::Value CreateCellularSIMSlotInfo(
      const std::string& iccid,
      const std::string& eid = std::string()) {
    base::Value::List sim_slot_infos;
    base::Value::Dict slot_info_item;
    slot_info_item.Set(shill::kSIMSlotInfoEID, eid);
    slot_info_item.Set(shill::kSIMSlotInfoICCID, iccid);
    slot_info_item.Set(shill::kSIMSlotInfoPrimary, false);
    sim_slot_infos.Append(std::move(slot_info_item));
    return base::Value(std::move(sim_slot_infos));
  }

  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  NetworkStateTestHelper test_helper_;
  base::HistogramTester histogram_tester_;

  std::unique_ptr<FakeActivationDelegate> fake_activation_delegate_;
  std::unique_ptr<FakeNetworkConnectionHandler>
      fake_network_connection_handler_;
  std::unique_ptr<FakeNetworkActivationHandler>
      fake_network_activation_handler_;

  std::unique_ptr<OtaActivator> ota_activator_;
  mojo::Remote<mojom::CarrierPortalHandler> carrier_portal_handler_remote_;

  bool is_finished_ = false;
};

TEST_F(CellularSetupOtaActivatorImplTest, Success) {
  AddCellularDevice(true /* has_valid_sim */);
  AddCellularNetwork(true /* has_valid_payment_info */, true /* is_connected */,
                     false /* is_already_activated */);

  BuildOtaActivator();

  FlushForTesting();
  VerifyCellularMetadataReceivedByDelegate();

  UpdateCarrierPortalState(
      mojom::CarrierPortalStatus::kPortalLoadedWithoutPaidUser);
  UpdateCarrierPortalState(
      mojom::CarrierPortalStatus::kPortalLoadedAndUserCompletedPayment);

  InvokePendingActivationCallback(true /* success */);

  FlushForTesting();
  VerifyActivationFinished(
      mojom::ActivationResult::kSuccessfullyStartedActivation);
}

TEST_F(CellularSetupOtaActivatorImplTest, SuccessWithPaymentError) {
  AddCellularDevice(true /* has_valid_sim */);
  AddCellularNetwork(true /* has_valid_payment_info */, true /* is_connected */,
                     false /* is_already_activated */);

  BuildOtaActivator();

  FlushForTesting();
  VerifyCellularMetadataReceivedByDelegate();

  UpdateCarrierPortalState(
      mojom::CarrierPortalStatus::kPortalLoadedWithoutPaidUser);
  UpdateCarrierPortalState(
      mojom::CarrierPortalStatus::kPortalLoadedButErrorOccurredDuringPayment);

  InvokePendingActivationCallback(true /* success */);

  FlushForTesting();
  VerifyActivationFinished(
      mojom::ActivationResult::kSuccessfullyStartedActivation);
}

TEST_F(CellularSetupOtaActivatorImplTest, HasNoPhysicalSlots) {
  AddCellularDevice(false /* has_valid_sim */, false /* has_physical_slots */);

  BuildOtaActivator();

  FlushForTesting();

  VerifyActivationFinished(mojom::ActivationResult::kFailedToActivate);
}

TEST_F(CellularSetupOtaActivatorImplTest, ConnectRetry) {
  AddCellularDevice(true /* has_valid_sim */);
  AddCellularNetwork(true /* has_valid_payment_info */,
                     false /* is_connected */,
                     false /* is_already_activated */);

  BuildOtaActivator();

  EXPECT_EQ(1u, ConnectCallCount());
  FailCellularNetworkConnect();
  // Ensure the a new connect call is not issued immediately.
  EXPECT_EQ(1u, ConnectCallCount());

  FastForwardByConnectRetryDelay();
  EXPECT_EQ(2u, ConnectCallCount());

  ConnectCellularNetwork();
  FlushForTesting();
  VerifyCellularMetadataReceivedByDelegate();

  UpdateCarrierPortalState(
      mojom::CarrierPortalStatus::kPortalLoadedWithoutPaidUser);
  UpdateCarrierPortalState(
      mojom::CarrierPortalStatus::kPortalLoadedAndUserCompletedPayment);

  InvokePendingActivationCallback(true /* success */);

  FlushForTesting();
  VerifyActivationFinished(
      mojom::ActivationResult::kSuccessfullyStartedActivation);
}

TEST_F(CellularSetupOtaActivatorImplTest,
       SimAndPaymentInfoNotInitiallyPresent_AndNetworkNotConnected) {
  AddCellularDevice(false /* has_valid_sim */);
  AddCellularNetwork(false /* has_valid_payment_info */,
                     false /* is_connected */,
                     false /* is_already_activated */);

  BuildOtaActivator();

  RemoveCellularDevice();
  RemoveCellularNetwork();

  AddCellularDevice(true /* has_valid_sim */);
  AddCellularNetwork(true /* has_valid_payment_info */,
                     false /* is_connected */,
                     false /* is_already_activated */);

  ConnectCellularNetwork();

  FlushForTesting();
  VerifyCellularMetadataReceivedByDelegate();

  UpdateCarrierPortalState(
      mojom::CarrierPortalStatus::kPortalLoadedWithoutPaidUser);
  UpdateCarrierPortalState(
      mojom::CarrierPortalStatus::kPortalLoadedAndUserCompletedPayment);

  InvokePendingActivationCallback(true /* success */);

  FlushForTesting();
  VerifyActivationFinished(
      mojom::ActivationResult::kSuccessfullyStartedActivation);
}

TEST_F(CellularSetupOtaActivatorImplTest, AlreadyActivated) {
  AddCellularDevice(true /* has_valid_sim */);
  AddCellularNetwork(true /* has_valid_payment_info */, true /* is_connected */,
                     true /* is_already_activated */);

  BuildOtaActivator();

  FlushForTesting();
  VerifyActivationFinished(mojom::ActivationResult::kAlreadyActivated);
}

TEST_F(CellularSetupOtaActivatorImplTest, DelegateBecomesDisconnected) {
  AddCellularDevice(true /* has_valid_sim */);
  AddCellularNetwork(true /* has_valid_payment_info */, true /* is_connected */,
                     false /* is_already_activated */);

  BuildOtaActivator();
  DisconnectDelegate();
  FlushForTesting();

  // Note: Cannot check the ActivationResult received by the delegate because
  // the delegate was disconnected and did not receive the result.
  EXPECT_TRUE(is_finished());
}

}  // namespace ash::cellular_setup